From 63c22feb8f6fa17a9c1e8ce45b4e295f7c547f9c Mon Sep 17 00:00:00 2001 From: Richard Shiue <71320345+richardshiue@users.noreply.github.com> Date: Mon, 29 Jan 2024 13:19:35 +0800 Subject: [PATCH] fix: mobile field editing related bugs (#4534) * fix: mobile field editing related bugs * chore: review * chore: restore podfile.lock --- .../field/mobile_create_field_screen.dart | 2 +- .../field/mobile_edit_field_screen.dart | 129 +++++++++--------- .../field/mobile_field_bottom_sheets.dart | 18 ++- .../field/mobile_field_type_grid.dart | 4 +- .../mobile_field_type_option_editor.dart | 41 +++--- .../field/mobile_quick_field_editor.dart | 46 +++++-- .../database/view/database_field_list.dart | 4 +- 7 files changed, 136 insertions(+), 108 deletions(-) diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_create_field_screen.dart b/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_create_field_screen.dart index 770ff052e5..95c2c0b9ac 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_create_field_screen.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_create_field_screen.dart @@ -69,7 +69,7 @@ class _MobileNewPropertyScreenState extends State { ), ], ), - body: FieldOptionEditor( + body: MobileFieldEditor( mode: FieldOptionMode.add, defaultValues: optionValues, onOptionValuesChanged: (optionValues) { diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_edit_field_screen.dart b/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_edit_field_screen.dart index 781fa65bca..c937bb73b8 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_edit_field_screen.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_edit_field_screen.dart @@ -32,12 +32,12 @@ class MobileEditPropertyScreen extends StatefulWidget { class _MobileEditPropertyScreenState extends State { late final FieldBackendService fieldService; - late FieldOptionValues field; + late FieldOptionValues _fieldOptionValues; @override void initState() { super.initState(); - field = FieldOptionValues.fromField(field: widget.field.field); + _fieldOptionValues = FieldOptionValues.fromField(field: widget.field.field); fieldService = FieldBackendService( viewId: widget.viewId, fieldId: widget.field.id, @@ -49,76 +49,69 @@ class _MobileEditPropertyScreenState extends State { final viewId = widget.viewId; final fieldId = widget.field.id; - return Scaffold( - appBar: AppBar( - centerTitle: true, - title: FlowyText.medium( - LocaleKeys.grid_field_editProperty.tr(), - ), - elevation: 0, - bottom: const PreferredSize( - preferredSize: Size.fromHeight(1), - child: Divider( - height: 1, - thickness: 1, + return PopScope( + onPopInvoked: (didPop) { + if (didPop) { + context.pop(_fieldOptionValues); + } + }, + child: Scaffold( + appBar: AppBar( + centerTitle: true, + title: FlowyText.medium( + LocaleKeys.grid_field_editProperty.tr(), + ), + elevation: 0, + bottom: const PreferredSize( + preferredSize: Size.fromHeight(1), + child: Divider( + height: 1, + thickness: 1, + ), + ), + leading: AppBarBackButton( + onTap: () => context.pop(_fieldOptionValues), ), ), - leading: AppBarBackButton( - onTap: () => context.pop(field), - ), - ), - body: FieldOptionEditor( - mode: FieldOptionMode.edit, - isPrimary: widget.field.isPrimary, - defaultValues: field, - actions: [ - if (widget.field.fieldSettings?.visibility.isVisibleState() ?? true) - FieldOptionAction.hide - else - FieldOptionAction.show, - FieldOptionAction.duplicate, - FieldOptionAction.delete, - ], - onOptionValuesChanged: (newField) async { - if (newField.name != field.name) { - await fieldService.updateField(name: newField.name); - } - - if (newField.type != widget.field.fieldType) { - await fieldService.updateType(fieldType: newField.type); - } - - final data = newField.getTypeOptionData(); - if (data != null) { - await FieldBackendService.updateFieldTypeOption( + body: MobileFieldEditor( + mode: FieldOptionMode.edit, + isPrimary: widget.field.isPrimary, + defaultValues: _fieldOptionValues, + actions: [ + widget.field.fieldSettings?.visibility.isVisibleState() ?? true + ? FieldOptionAction.hide + : FieldOptionAction.show, + FieldOptionAction.duplicate, + FieldOptionAction.delete, + ], + onOptionValuesChanged: (newFieldOptionValues) { + setState(() { + _fieldOptionValues = newFieldOptionValues; + }); + }, + onAction: (action) { + final service = FieldServices( viewId: viewId, - fieldId: widget.field.id, - typeOptionData: data, + fieldId: fieldId, ); - } - // setState(() => field = newField); - }, - onAction: (action) { - final service = FieldServices( - viewId: viewId, - fieldId: fieldId, - ); - switch (action) { - case FieldOptionAction.delete: - service.delete(); - break; - case FieldOptionAction.duplicate: - service.duplicate(); - break; - case FieldOptionAction.hide: - service.hide(); - break; - case FieldOptionAction.show: - service.show(); - break; - } - context.pop(field); - }, + switch (action) { + case FieldOptionAction.delete: + fieldService.delete(); + context.pop(); + return; + case FieldOptionAction.duplicate: + fieldService.duplicate(); + break; + case FieldOptionAction.hide: + service.hide(); + break; + case FieldOptionAction.show: + service.show(); + break; + } + context.pop(_fieldOptionValues); + }, + ), ), ); } diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_field_bottom_sheets.dart b/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_field_bottom_sheets.dart index a59f571347..fb2c781dc0 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_field_bottom_sheets.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_field_bottom_sheets.dart @@ -12,6 +12,8 @@ import 'mobile_field_type_grid.dart'; import 'mobile_field_type_option_editor.dart'; import 'mobile_quick_field_editor.dart'; +/// Shows the field type grid and upon selection, allow users to edit the +/// field's properties and saving it when the user clicks save. void showCreateFieldBottomSheet( BuildContext context, String viewId, { @@ -24,9 +26,10 @@ void showCreateFieldBottomSheet( return DraggableScrollableSheet( expand: false, snap: true, - initialChildSize: 0.7, - minChildSize: 0.7, - builder: (context, controller) => FieldOptions( + initialChildSize: 0.97, + minChildSize: 0.97, + maxChildSize: 0.97, + builder: (context, controller) => MobileFieldTypeGrid( scrollController: controller, mode: FieldOptionMode.add, onSelectFieldType: (type) async { @@ -52,22 +55,22 @@ void showCreateFieldBottomSheet( ); } +/// Used to edit a field. Future showEditFieldScreen( BuildContext context, String viewId, FieldInfo field, -) async { - final optionValues = await context.push( +) { + return context.push( MobileEditPropertyScreen.routeName, extra: { MobileEditPropertyScreen.argViewId: viewId, MobileEditPropertyScreen.argField: field, }, ); - - return optionValues; } +/// Shows some quick field options in a bottom sheet. void showQuickEditField( BuildContext context, String viewId, @@ -89,6 +92,7 @@ void showQuickEditField( ); } +/// Display a list of fields in the current database that users can choose from. Future showFieldPicker( BuildContext context, String? selectedFieldId, diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_field_type_grid.dart b/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_field_type_grid.dart index f2b2600092..b834dc05f7 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_field_type_grid.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_field_type_grid.dart @@ -23,8 +23,8 @@ const _supportedFieldTypes = [ FieldType.CreatedTime, ]; -class FieldOptions extends StatelessWidget { - const FieldOptions({ +class MobileFieldTypeGrid extends StatelessWidget { + const MobileFieldTypeGrid({ super.key, required this.mode, required this.onSelectFieldType, diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_field_type_option_editor.dart b/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_field_type_option_editor.dart index c2c580f497..0c2fe3510f 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_field_type_option_editor.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_field_type_option_editor.dart @@ -159,8 +159,8 @@ enum FieldOptionAction { delete, } -class FieldOptionEditor extends StatefulWidget { - const FieldOptionEditor({ +class MobileFieldEditor extends StatefulWidget { + const MobileFieldEditor({ super.key, required this.mode, required this.defaultValues, @@ -182,11 +182,12 @@ class FieldOptionEditor extends StatefulWidget { final bool isPrimary; @override - State createState() => _FieldOptionEditorState(); + State createState() => _MobileFieldEditorState(); } -class _FieldOptionEditorState extends State { +class _MobileFieldEditorState extends State { final controller = TextEditingController(); + bool isFieldNameChanged = false; late FieldOptionValues values; @@ -219,6 +220,7 @@ class _FieldOptionEditorState extends State { controller: controller, type: values.type, onTextChanged: (value) { + isFieldNameChanged = true; _updateOptionValues(name: value); }, ), @@ -226,16 +228,18 @@ class _FieldOptionEditorState extends State { if (!widget.isPrimary) ...[ _PropertyType( type: values.type, - onSelected: (type) => setState( - () { - if (widget.mode == FieldOptionMode.add) { - controller.text = type.i18n; - _updateOptionValues(name: type.i18n, type: type); - } else { + onSelected: (type) { + setState( + () { + if (widget.mode == FieldOptionMode.add && + !isFieldNameChanged) { + controller.text = type.i18n; + _updateOptionValues(name: type.i18n); + } _updateOptionValues(type: type); - } - }, - ), + }, + ); + }, ), const _Divider(), if (option.isNotEmpty) ...[ @@ -446,9 +450,10 @@ class _PropertyType extends StatelessWidget { return DraggableScrollableSheet( expand: false, snap: true, - initialChildSize: 0.7, - minChildSize: 0.7, - builder: (context, controller) => FieldOptions( + initialChildSize: 0.97, + minChildSize: 0.97, + maxChildSize: 0.97, + builder: (context, controller) => MobileFieldTypeGrid( scrollController: controller, mode: FieldOptionMode.edit, onSelectFieldType: (type) { @@ -894,10 +899,10 @@ class _SelectOptionTile extends StatefulWidget { final void Function(SelectOptionPB option) onUpdateOption; @override - State<_SelectOptionTile> createState() => __SelectOptionTileState(); + State<_SelectOptionTile> createState() => _SelectOptionTileState(); } -class __SelectOptionTileState extends State<_SelectOptionTile> { +class _SelectOptionTileState extends State<_SelectOptionTile> { final TextEditingController controller = TextEditingController(); late SelectOptionPB option; diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_quick_field_editor.dart b/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_quick_field_editor.dart index 5aa3a05855..9dce31cf85 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_quick_field_editor.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_quick_field_editor.dart @@ -3,9 +3,11 @@ import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/mobile/presentation/base/app_bar_actions.dart'; import 'package:appflowy/mobile/presentation/database/card/card_detail/widgets/widgets.dart'; import 'package:appflowy/mobile/presentation/database/field/mobile_field_bottom_sheets.dart'; +import 'package:appflowy/mobile/presentation/database/field/mobile_field_type_option_editor.dart'; import 'package:appflowy/mobile/presentation/widgets/widgets.dart'; import 'package:appflowy/plugins/database/application/field/field_backend_service.dart'; import 'package:appflowy/plugins/database/application/field/field_info.dart'; +import 'package:appflowy/plugins/database/application/field/field_service.dart'; import 'package:appflowy/plugins/database/widgets/setting/field_visibility_extension.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; import 'package:easy_localization/easy_localization.dart'; @@ -36,14 +38,15 @@ class _QuickEditFieldState extends State { fieldId: widget.fieldInfo.field.id, ); - late FieldType fieldType; late FieldVisibility fieldVisibility; + late FieldOptionValues _fieldOptionValues; @override void initState() { super.initState(); - fieldType = widget.fieldInfo.fieldType; + _fieldOptionValues = + FieldOptionValues.fromField(field: widget.fieldInfo.field); fieldVisibility = widget.fieldInfo.fieldSettings?.visibility ?? FieldVisibility.AlwaysShown; controller.text = widget.fieldInfo.field.name; @@ -52,7 +55,6 @@ class _QuickEditFieldState extends State { @override void dispose() { controller.dispose(); - super.dispose(); } @@ -64,7 +66,7 @@ class _QuickEditFieldState extends State { const AppBarCloseButton(), OptionTextField( controller: controller, - type: fieldType, + type: _fieldOptionValues.type, onTextChanged: (text) async { await service.updateName(text); }, @@ -77,18 +79,44 @@ class _QuickEditFieldState extends State { widget.fieldInfo.field.freeze(); final field = widget.fieldInfo.field.rebuild((field) { field.name = controller.text; - field.fieldType = fieldType; + field.fieldType = _fieldOptionValues.type; + field.typeOptionData = + _fieldOptionValues.getTypeOptionData() ?? []; }); - final optionValues = await showEditFieldScreen( + final fieldOptionValues = await showEditFieldScreen( context, widget.viewId, widget.fieldInfo.copyWith(field: field), ); - if (optionValues != null) { + if (fieldOptionValues != null) { + if (fieldOptionValues.name != _fieldOptionValues.name) { + await service.updateName(fieldOptionValues.name); + } + + if (fieldOptionValues.type != _fieldOptionValues.type) { + await FieldBackendService.updateFieldType( + viewId: widget.viewId, + fieldId: widget.fieldInfo.id, + fieldType: fieldOptionValues.type, + ); + } + + final data = fieldOptionValues.getTypeOptionData(); + if (data != null) { + await FieldBackendService.updateFieldTypeOption( + viewId: widget.viewId, + fieldId: widget.fieldInfo.id, + typeOptionData: data, + ); + } setState(() { - fieldType = optionValues.type; - controller.text = optionValues.name; + _fieldOptionValues = fieldOptionValues; + controller.text = fieldOptionValues.name; }); + } else { + if (mounted) { + context.pop(); + } } }, ), diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/database/view/database_field_list.dart b/frontend/appflowy_flutter/lib/mobile/presentation/database/view/database_field_list.dart index 6451b72951..0809ebc3e7 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/database/view/database_field_list.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/database/view/database_field_list.dart @@ -220,9 +220,7 @@ class DatabaseFieldListTile extends StatelessWidget { size: const Size.square(20), ), showTopBorder: showTopBorder, - onTap: () { - showEditFieldScreen(context, viewId, fieldInfo); - }, + onTap: () => showEditFieldScreen(context, viewId, fieldInfo), onValueChanged: (value) { final newVisibility = fieldInfo.visibility!.toggle(); context.read().add(