diff --git a/lib/consts.dart b/lib/consts.dart index d5201956..967a20b7 100644 --- a/lib/consts.dart +++ b/lib/consts.dart @@ -1,10 +1,9 @@ -import 'dart:convert'; import 'dart:io'; - -import 'package:davi/davi.dart'; +import 'dart:convert'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; +import 'package:davi/davi.dart'; const kDiscordUrl = "https://bit.ly/heyfoss"; const kGitUrl = "https://github.com/foss42/apidash"; @@ -49,6 +48,10 @@ const kHintOpacity = 0.6; const kForegroundOpacity = 0.05; const kTextStyleButton = TextStyle(fontWeight: FontWeight.bold); +const kFormDataButton = TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, +); const kBorderRadius8 = BorderRadius.all(Radius.circular(8)); final kBorderRadius10 = BorderRadius.circular(10); @@ -74,7 +77,7 @@ const kPh60 = EdgeInsets.symmetric(horizontal: 60); const kP24CollectionPane = EdgeInsets.only(top: 24, left: 8.0, bottom: 8.0); const kP8CollectionPane = EdgeInsets.only(top: 8.0, left: 8.0, bottom: 8.0); const kPr8CollectionPane = EdgeInsets.only(right: 8.0); - +const kpsV5 = EdgeInsets.symmetric(vertical: 2); const kHSpacer4 = SizedBox(width: 4); const kHSpacer5 = SizedBox(width: 5); const kHSpacer10 = SizedBox(width: 10); @@ -300,14 +303,6 @@ const kContentTypeMap = { ContentType.text: "$kTypeText/$kSubTypePlain", ContentType.formdata: "multipart/form-data", }; -const kFormDataTypeMap = { - FormDataType.file: "file", - FormDataType.text: "text", -}; -const kMapFormDataType = { - "file": FormDataType.file, - "text": FormDataType.text, -}; enum ResponseBodyView { preview, code, raw, none } diff --git a/lib/models/form_data_model.dart b/lib/models/form_data_model.dart index 90991419..add20833 100644 --- a/lib/models/form_data_model.dart +++ b/lib/models/form_data_model.dart @@ -8,7 +8,7 @@ part 'form_data_model.g.dart'; class FormDataModel with _$FormDataModel { const factory FormDataModel({ required String name, - required dynamic value, + required String value, required FormDataType type, }) = _FormDataModel; diff --git a/lib/models/form_data_model.freezed.dart b/lib/models/form_data_model.freezed.dart index e1babd78..0958bd4e 100644 --- a/lib/models/form_data_model.freezed.dart +++ b/lib/models/form_data_model.freezed.dart @@ -21,7 +21,7 @@ FormDataModel _$FormDataModelFromJson(Map json) { /// @nodoc mixin _$FormDataModel { String get name => throw _privateConstructorUsedError; - dynamic get value => throw _privateConstructorUsedError; + String get value => throw _privateConstructorUsedError; FormDataType get type => throw _privateConstructorUsedError; Map toJson() => throw _privateConstructorUsedError; @@ -36,7 +36,7 @@ abstract class $FormDataModelCopyWith<$Res> { FormDataModel value, $Res Function(FormDataModel) then) = _$FormDataModelCopyWithImpl<$Res, FormDataModel>; @useResult - $Res call({String name, dynamic value, FormDataType type}); + $Res call({String name, String value, FormDataType type}); } /// @nodoc @@ -53,7 +53,7 @@ class _$FormDataModelCopyWithImpl<$Res, $Val extends FormDataModel> @override $Res call({ Object? name = null, - Object? value = freezed, + Object? value = null, Object? type = null, }) { return _then(_value.copyWith( @@ -61,10 +61,10 @@ class _$FormDataModelCopyWithImpl<$Res, $Val extends FormDataModel> ? _value.name : name // ignore: cast_nullable_to_non_nullable as String, - value: freezed == value + value: null == value ? _value.value : value // ignore: cast_nullable_to_non_nullable - as dynamic, + as String, type: null == type ? _value.type : type // ignore: cast_nullable_to_non_nullable @@ -81,7 +81,7 @@ abstract class _$$FormDataModelImplCopyWith<$Res> __$$FormDataModelImplCopyWithImpl<$Res>; @override @useResult - $Res call({String name, dynamic value, FormDataType type}); + $Res call({String name, String value, FormDataType type}); } /// @nodoc @@ -96,7 +96,7 @@ class __$$FormDataModelImplCopyWithImpl<$Res> @override $Res call({ Object? name = null, - Object? value = freezed, + Object? value = null, Object? type = null, }) { return _then(_$FormDataModelImpl( @@ -104,10 +104,10 @@ class __$$FormDataModelImplCopyWithImpl<$Res> ? _value.name : name // ignore: cast_nullable_to_non_nullable as String, - value: freezed == value + value: null == value ? _value.value : value // ignore: cast_nullable_to_non_nullable - as dynamic, + as String, type: null == type ? _value.type : type // ignore: cast_nullable_to_non_nullable @@ -128,7 +128,7 @@ class _$FormDataModelImpl implements _FormDataModel { @override final String name; @override - final dynamic value; + final String value; @override final FormDataType type; @@ -143,14 +143,13 @@ class _$FormDataModelImpl implements _FormDataModel { (other.runtimeType == runtimeType && other is _$FormDataModelImpl && (identical(other.name, name) || other.name == name) && - const DeepCollectionEquality().equals(other.value, value) && + (identical(other.value, value) || other.value == value) && (identical(other.type, type) || other.type == type)); } @JsonKey(ignore: true) @override - int get hashCode => Object.hash( - runtimeType, name, const DeepCollectionEquality().hash(value), type); + int get hashCode => Object.hash(runtimeType, name, value, type); @JsonKey(ignore: true) @override @@ -169,7 +168,7 @@ class _$FormDataModelImpl implements _FormDataModel { abstract class _FormDataModel implements FormDataModel { const factory _FormDataModel( {required final String name, - required final dynamic value, + required final String value, required final FormDataType type}) = _$FormDataModelImpl; factory _FormDataModel.fromJson(Map json) = @@ -178,7 +177,7 @@ abstract class _FormDataModel implements FormDataModel { @override String get name; @override - dynamic get value; + String get value; @override FormDataType get type; @override diff --git a/lib/models/form_data_model.g.dart b/lib/models/form_data_model.g.dart index 7d9d353b..f539458f 100644 --- a/lib/models/form_data_model.g.dart +++ b/lib/models/form_data_model.g.dart @@ -9,7 +9,7 @@ part of 'form_data_model.dart'; _$FormDataModelImpl _$$FormDataModelImplFromJson(Map json) => _$FormDataModelImpl( name: json['name'] as String, - value: json['value'], + value: json['value'] as String, type: $enumDecode(_$FormDataTypeEnumMap, json['type']), ); diff --git a/lib/models/models.dart b/lib/models/models.dart index 3820e3c2..66d6f6ce 100644 --- a/lib/models/models.dart +++ b/lib/models/models.dart @@ -2,3 +2,4 @@ export 'name_value_model.dart'; export 'request_model.dart'; export 'response_model.dart'; export 'settings_model.dart'; +export 'form_data_model.dart'; diff --git a/lib/screens/home_page/editor_pane/details_card/request_pane/request_body.dart b/lib/screens/home_page/editor_pane/details_card/request_pane/request_body.dart index 21dd63c6..f6e06332 100644 --- a/lib/screens/home_page/editor_pane/details_card/request_pane/request_body.dart +++ b/lib/screens/home_page/editor_pane/details_card/request_pane/request_body.dart @@ -1,5 +1,3 @@ -import 'dart:math'; - import 'package:apidash/consts.dart'; import 'package:apidash/providers/providers.dart'; import 'package:apidash/widgets/form_data_widget.dart'; @@ -15,12 +13,9 @@ class EditRequestBody extends ConsumerStatefulWidget { } class _EditRequestBodyState extends ConsumerState { - final random = Random.secure(); - late int seed; @override void initState() { super.initState(); - seed = random.nextInt(kRandMax); } @override @@ -54,12 +49,7 @@ class _EditRequestBodyState extends ConsumerState { ), Expanded( child: requestBodyStateWatcher == ContentType.formdata - ? FormDataWidget( - seed: seed, - onFormDataRemove: () { - seed = random.nextInt(kRandMax); - }, - ) + ? const FormDataWidget() : TextFieldEditor( key: Key("$activeId-body"), fieldKey: "$activeId-body-editor", diff --git a/lib/utils/convert_utils.dart b/lib/utils/convert_utils.dart index 63b1f3eb..72a88520 100644 --- a/lib/utils/convert_utils.dart +++ b/lib/utils/convert_utils.dart @@ -1,12 +1,9 @@ import 'dart:convert'; import 'dart:typed_data'; - -import 'package:apidash/models/form_data_model.dart'; +import 'package:apidash/consts.dart'; +import 'package:apidash/models/models.dart'; import 'package:http/http.dart' as http; -import '../consts.dart'; -import '../models/models.dart'; - String humanizeDuration(Duration? duration) { if (duration == null) { return ""; @@ -113,16 +110,24 @@ List? listToFormDataModel(List? kvMap) { if (kvMap == null) { return null; } - List finalRows = kvMap - .map((formData) => FormDataModel( - name: formData["name"], - value: formData["value"], - type: kMapFormDataType[formData["type"]] ?? FormDataType.text, - )) - .toList(); + List finalRows = kvMap.map( + (formData) { + return FormDataModel( + name: formData["name"], + value: formData["value"], + type: getFormDataType(formData["type"]), + ); + }, + ).toList(); return finalRows; } +FormDataType getFormDataType(String? type) { + List formData = FormDataType.values; + return formData.firstWhere((element) => element.name == type, + orElse: () => FormDataType.text); +} + Uint8List? stringToBytes(String? text) { if (text == null) { return null; diff --git a/lib/widgets/dropdowns.dart b/lib/widgets/dropdowns.dart index 98bd10d4..6ca7a698 100644 --- a/lib/widgets/dropdowns.dart +++ b/lib/widgets/dropdowns.dart @@ -129,6 +129,7 @@ class _DropdownButtonFormData extends State { Widget build(BuildContext context) { final surfaceColor = Theme.of(context).colorScheme.surface; return DropdownButton( + dropdownColor: surfaceColor, focusColor: surfaceColor, value: widget.formDataType, icon: const Icon( diff --git a/lib/widgets/form_data_field.dart b/lib/widgets/form_data_field.dart index d00dd70b..0353d033 100644 --- a/lib/widgets/form_data_field.dart +++ b/lib/widgets/form_data_field.dart @@ -27,10 +27,8 @@ class FormDataField extends StatefulWidget { } class _FormDataFieldState extends State { - TextEditingController valueController = TextEditingController(); @override void initState() { - valueController.text = widget.initialValue ?? ""; super.initState(); } @@ -42,45 +40,41 @@ class _FormDataFieldState extends State { Expanded( flex: 1, child: TextFormField( - controller: valueController, + initialValue: widget.initialValue, key: Key(widget.keyId), style: kCodeStyle.copyWith( color: colorScheme.onSurface, ), decoration: InputDecoration( - hintStyle: kCodeStyle.copyWith( - color: colorScheme.outline.withOpacity( - kHintOpacity, - ), - ), - hintText: widget.hintText, - focusedBorder: UnderlineInputBorder( - borderSide: BorderSide( - color: colorScheme.primary.withOpacity( + hintStyle: kCodeStyle.copyWith( + color: colorScheme.outline.withOpacity( kHintOpacity, ), ), - ), - enabledBorder: UnderlineInputBorder( - borderSide: BorderSide( - color: colorScheme.surfaceVariant, + hintText: widget.hintText, + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: colorScheme.primary.withOpacity( + kHintOpacity, + ), + ), ), - ), - ), + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: colorScheme.surfaceVariant, + ), + ), + suffixIcon: DropdownButtonFormData( + formDataType: widget.formDataType, + onChanged: (p0) { + if (widget.onFormDataTypeChanged != null) { + widget.onFormDataTypeChanged!(p0); + } + }, + )), onChanged: widget.onChanged, ), ), - Expanded( - child: DropdownButtonFormData( - formDataType: widget.formDataType, - onChanged: (p0) { - if (widget.onFormDataTypeChanged != null) { - widget.onFormDataTypeChanged!(p0); - valueController.clear(); - } - }, - ), - ) ], ); } diff --git a/lib/widgets/form_data_widget.dart b/lib/widgets/form_data_widget.dart index 85318d70..e28c2fc3 100644 --- a/lib/widgets/form_data_widget.dart +++ b/lib/widgets/form_data_widget.dart @@ -1,5 +1,7 @@ +import 'dart:math'; import 'package:apidash/consts.dart'; import 'package:apidash/models/form_data_model.dart'; +import 'package:apidash/models/models.dart'; import 'package:apidash/providers/collection_providers.dart'; import 'package:apidash/widgets/form_data_field.dart'; import 'package:apidash/widgets/textfields.dart'; @@ -9,43 +11,48 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; class FormDataWidget extends ConsumerStatefulWidget { - const FormDataWidget({ - super.key, - required this.seed, - required this.onFormDataRemove, - }); - final int seed; - final Function onFormDataRemove; + const FormDataWidget({super.key}); @override ConsumerState createState() => _FormDataBodyState(); } class _FormDataBodyState extends ConsumerState { + late int seed; + final random = Random.secure(); + late List rows; + @override + void initState() { + super.initState(); + seed = random.nextInt(kRandMax); + } + @override Widget build(BuildContext context) { final activeId = ref.watch(activeIdStateProvider); - final requestModel = ref - .read(collectionStateNotifierProvider.notifier) - .getRequestModel(activeId!); - List rows = requestModel?.formDataList ?? []; - DaviModel model = DaviModel( + var formRows = ref.read(activeRequestModelProvider)?.formDataList; + rows = + formRows == null || formRows.isEmpty ? [kFormDataEmptyModel] : formRows; + + DaviModel daviModelRows = DaviModel( rows: rows, columns: [ DaviColumn( + cellPadding: kpsV5, name: 'Key', grow: 1, cellBuilder: (_, row) { int idx = row.index; - return SizedBox( + return Theme( + data: Theme.of(context), child: FormDataField( - keyId: "$activeId-$idx-form-v-${widget.seed}", + keyId: "$activeId-$idx-form-v-$seed", initialValue: rows[idx].name, hintText: " Key", onChanged: (value) { rows[idx] = rows[idx].copyWith( name: value, ); - _onFieldChange(activeId); + _onFieldChange(activeId!); }, colorScheme: Theme.of(context).colorScheme, formDataType: rows[idx].type, @@ -54,7 +61,8 @@ class _FormDataBodyState extends ConsumerState { type: value ?? FormDataType.text, ); rows[idx] = rows[idx].copyWith(value: ""); - _onFieldChange(activeId); + setState(() {}); + _onFieldChange(activeId!); }, ), ); @@ -62,7 +70,9 @@ class _FormDataBodyState extends ConsumerState { sortable: false, ), DaviColumn( - width: 10, + width: 30, + cellPadding: kpsV5, + cellAlignment: Alignment.center, cellBuilder: (_, row) { return Text( "=", @@ -73,6 +83,7 @@ class _FormDataBodyState extends ConsumerState { DaviColumn( name: 'Value', grow: 4, + cellPadding: kpsV5, cellBuilder: (_, row) { int idx = row.index; return rows[idx].type == FormDataType.file @@ -83,36 +94,35 @@ class _FormDataBodyState extends ConsumerState { child: Row( children: [ Expanded( - child: ElevatedButtonTheme( - data: const ElevatedButtonThemeData(), + child: Theme( + data: Theme.of(context), child: ElevatedButton.icon( + icon: const Icon( + Icons.snippet_folder_rounded, + size: 20, + ), + style: const ButtonStyle(), onPressed: () async { FilePickerResult? pickedResult = await FilePicker.platform.pickFiles(); if (pickedResult != null && - pickedResult.files.isNotEmpty) { + pickedResult.files.isNotEmpty && + pickedResult.files.first.path != null) { rows[idx] = rows[idx].copyWith( - value: pickedResult.files.first.path, + value: pickedResult.files.first.path!, ); - _onFieldChange(activeId); + setState(() {}); + _onFieldChange(activeId!); } }, - icon: const Icon( - Icons.snippet_folder_rounded, - size: 18, - ), label: Text( - rows[idx].type == FormDataType.file - ? (rows[idx].value != null - ? rows[idx].value.toString() - : "Select File") + (rows[idx].type == FormDataType.file && + rows[idx].value.isNotEmpty) + ? rows[idx].value.toString() : "Select File", textAlign: TextAlign.center, overflow: TextOverflow.ellipsis, - style: kTextStyleButton.copyWith( - fontSize: 12, - fontWeight: FontWeight.w500, - ), + style: kFormDataButton, ), ), ), @@ -122,12 +132,12 @@ class _FormDataBodyState extends ConsumerState { ), ) : CellField( - keyId: "$activeId-$idx-form-v-${widget.seed}", + keyId: "$activeId-$idx-form-v-$seed", initialValue: rows[idx].value, hintText: " Value", onChanged: (value) { rows[idx] = rows[idx].copyWith(value: value); - _onFieldChange(activeId); + _onFieldChange(activeId!); }, colorScheme: Theme.of(context).colorScheme, ); @@ -143,17 +153,16 @@ class _FormDataBodyState extends ConsumerState { ? kIconRemoveDark : kIconRemoveLight, onTap: () { - widget.onFormDataRemove(); + seed = random.nextInt(kRandMax); if (rows.length == 1) { setState(() { - rows = [ - kFormDataEmptyModel, - ]; + rows = [kFormDataEmptyModel]; }); } else { rows.removeAt(row.index); } - _onFieldChange(activeId); + _onFieldChange(activeId!); + setState(() {}); }, ); }, @@ -173,7 +182,7 @@ class _FormDataBodyState extends ConsumerState { Expanded( child: DaviTheme( data: kTableThemeData, - child: Davi(model), + child: Davi(daviModelRows), ), ), ], @@ -185,8 +194,10 @@ class _FormDataBodyState extends ConsumerState { padding: const EdgeInsets.only(bottom: 30), child: ElevatedButton.icon( onPressed: () { - rows.add(kFormDataEmptyModel); - _onFieldChange(activeId); + setState(() { + rows.add(kFormDataEmptyModel); + }); + _onFieldChange(activeId!); }, icon: const Icon(Icons.add), label: const Text( @@ -201,12 +212,9 @@ class _FormDataBodyState extends ConsumerState { } void _onFieldChange(String activeId) { - List formDataList = - ref.read(collectionStateNotifierProvider)?[activeId]?.formDataList ?? - []; ref.read(collectionStateNotifierProvider.notifier).update( activeId, - formDataList: formDataList, + formDataList: rows, ); } }