From ce58737ec5ae258d255d1f6f3776d9c0afa7163f Mon Sep 17 00:00:00 2001 From: Richard Shiue <71320345+richardshiue@users.noreply.github.com> Date: Wed, 27 Dec 2023 23:52:54 +0800 Subject: [PATCH] fix: bottom sheet updates apply immediately (#4220) * fix: mobile field editor * fix: immediately update select option * fix: insert left and right field flow * fix: create row behavior --- .../field/mobile_edit_field_screen.dart | 83 +++++++------- .../field/mobile_field_bottom_sheets.dart | 35 ++---- .../field/mobile_field_picker_list.dart | 2 +- .../mobile_field_type_option_editor.dart | 73 ++++++++----- .../field/mobile_quick_field_editor.dart | 39 ++++++- .../database/view/database_field_list.dart | 25 ++++- .../field/field_backend_service.dart | 7 ++ .../grid/application/grid_bloc.dart | 15 ++- .../grid/presentation/mobile_grid_page.dart | 2 +- .../grid/presentation/widgets/mobile_fab.dart | 4 +- .../mobile_select_option_editor.dart | 103 ++++++------------ frontend/resources/translations/en.json | 3 +- 12 files changed, 203 insertions(+), 188 deletions(-) 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 8d3787d0f0..1a606bc61b 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 @@ -2,7 +2,9 @@ import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/mobile/presentation/base/app_bar_actions.dart'; import 'package:appflowy/mobile/presentation/database/field/mobile_field_type_option_editor.dart'; import 'package:appflowy/plugins/database_view/application/field/field_backend_service.dart'; -import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; +import 'package:appflowy/plugins/database_view/application/field/field_info.dart'; +import 'package:appflowy/plugins/database_view/application/field/field_service.dart'; +import 'package:appflowy/plugins/database_view/widgets/setting/field_visibility_extension.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; @@ -20,7 +22,7 @@ class MobileEditPropertyScreen extends StatefulWidget { }); final String viewId; - final FieldPB field; + final FieldInfo field; @override State createState() => @@ -28,12 +30,17 @@ class MobileEditPropertyScreen extends StatefulWidget { } class _MobileEditPropertyScreenState extends State { - late FieldOptionValues optionValues; + late final FieldBackendService fieldService; + late FieldOptionValues field; @override void initState() { super.initState(); - optionValues = FieldOptionValues.fromField(field: widget.field); + field = FieldOptionValues.fromField(field: widget.field.field); + fieldService = FieldBackendService( + viewId: widget.viewId, + fieldId: widget.field.id, + ); } @override @@ -47,24 +54,40 @@ class _MobileEditPropertyScreenState extends State { title: FlowyText.medium( LocaleKeys.grid_field_editProperty.tr(), ), - leading: AppBarCancelButton( + leading: AppBarBackButton( onTap: () => context.pop(), ), - leadingWidth: 120, - actions: [ - _SaveButton( - onSave: () { - context.pop(optionValues); - }, - ), - ], ), body: FieldOptionEditor( mode: FieldOptionMode.edit, isPrimary: widget.field.isPrimary, - defaultValues: optionValues, - onOptionValuesChanged: (optionValues) { - this.optionValues = optionValues; + 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 != field.type) { + await fieldService.updateFieldType(fieldType: newField.type); + } + + final data = newField.getTypeOptionData(); + if (data != null) { + await FieldBackendService.updateFieldTypeOption( + viewId: viewId, + fieldId: widget.field.id, + typeOptionData: data, + ); + } + // setState(() => field = newField); }, onAction: (action) { final service = FieldServices( @@ -81,6 +104,9 @@ class _MobileEditPropertyScreenState extends State { case FieldOptionAction.hide: service.hide(); break; + case FieldOptionAction.show: + service.show(); + break; } context.pop(); }, @@ -88,28 +114,3 @@ class _MobileEditPropertyScreenState extends State { ); } } - -class _SaveButton extends StatelessWidget { - const _SaveButton({ - required this.onSave, - }); - - final VoidCallback onSave; - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.only(right: 16.0), - child: Align( - alignment: Alignment.center, - child: GestureDetector( - onTap: onSave, - child: FlowyText.medium( - LocaleKeys.button_save.tr(), - color: const Color(0xFF00ADDC), - ), - ), - ), - ); - } -} 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 29e9b1e961..8f97ef43e4 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 @@ -1,7 +1,7 @@ import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart'; import 'package:appflowy/plugins/database_view/application/field/field_controller.dart'; import 'package:appflowy/plugins/database_view/application/field/field_info.dart'; -import 'package:appflowy/plugins/database_view/application/field/field_service.dart'; +import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; @@ -12,7 +12,11 @@ import 'mobile_field_type_grid.dart'; import 'mobile_field_type_option_editor.dart'; import 'mobile_quick_field_editor.dart'; -void showCreateFieldBottomSheet(BuildContext context, String viewId) { +void showCreateFieldBottomSheet( + BuildContext context, + String viewId, { + OrderObjectPositionPB? position, +}) { showMobileBottomSheet( context, padding: EdgeInsets.zero, @@ -36,7 +40,7 @@ void showCreateFieldBottomSheet(BuildContext context, String viewId) { ).toString(), ); if (optionValues != null) { - await optionValues.create(viewId: viewId); + await optionValues.create(viewId: viewId, position: position); if (context.mounted) { context.pop(); } @@ -57,32 +61,9 @@ Future showEditFieldScreen( MobileEditPropertyScreen.routeName, extra: { MobileEditPropertyScreen.argViewId: viewId, - MobileEditPropertyScreen.argField: field.field, + MobileEditPropertyScreen.argField: field, }, ); - if (optionValues != null) { - final service = FieldBackendService( - viewId: viewId, - fieldId: field.id, - ); - - if (optionValues.name != field.name) { - await service.updateField(name: optionValues.name); - } - - if (optionValues.type != field.fieldType) { - await service.updateFieldType(fieldType: optionValues.type); - } - - final data = optionValues.toTypeOptionBuffer(); - if (data != null) { - await FieldBackendService.updateFieldTypeOption( - viewId: viewId, - fieldId: field.id, - typeOptionData: data, - ); - } - } return optionValues; } diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_field_picker_list.dart b/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_field_picker_list.dart index 934836a757..e73ecffc69 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_field_picker_list.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_field_picker_list.dart @@ -101,7 +101,7 @@ class _Header extends StatelessWidget { ), onPressed: () => context.pop(newFieldId), child: FlowyText.medium( - LocaleKeys.button_save.tr(), + LocaleKeys.button_done.tr(), fontSize: 16, color: Theme.of(context).colorScheme.onPrimary, overflow: TextOverflow.ellipsis, 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 d8eb66afa7..9d28ba6b2e 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 @@ -55,16 +55,18 @@ class FieldOptionValues { Future create({ required String viewId, + OrderObjectPositionPB? position, }) async { await FieldBackendService.createField( viewId: viewId, fieldType: type, fieldName: name, - typeOptionData: toTypeOptionBuffer(), + typeOptionData: getTypeOptionData(), + position: position, ); } - Uint8List? toTypeOptionBuffer() { + Uint8List? getTypeOptionData() { switch (type) { case FieldType.RichText: case FieldType.URL: @@ -124,6 +126,7 @@ class FieldOptionValues { enum FieldOptionAction { hide, + show, duplicate, delete, } @@ -134,6 +137,7 @@ class FieldOptionEditor extends StatefulWidget { required this.mode, required this.defaultValues, required this.onOptionValuesChanged, + this.actions = const [], this.onAction, this.isPrimary = false, }); @@ -143,6 +147,7 @@ class FieldOptionEditor extends StatefulWidget { final void Function(FieldOptionValues values) onOptionValuesChanged; // only used in edit mode + final List actions; final void Function(FieldOptionAction action)? onAction; // the primary field can't be deleted, duplicated, and changed type @@ -273,34 +278,44 @@ class _FieldOptionEditorState extends State { } List _buildOptionActions() { - return switch (widget.mode) { - FieldOptionMode.add => [], - FieldOptionMode.edit => [ - FlowyOptionTile.text( - text: LocaleKeys.grid_field_hide.tr(), - leftIcon: const FlowySvg(FlowySvgs.hide_s), - onTap: () => widget.onAction?.call(FieldOptionAction.hide), + if (widget.mode == FieldOptionMode.add || widget.actions.isEmpty) { + return []; + } + + return [ + if (widget.actions.contains(FieldOptionAction.hide)) + FlowyOptionTile.text( + text: LocaleKeys.grid_field_hide.tr(), + leftIcon: const FlowySvg(FlowySvgs.hide_s), + onTap: () => widget.onAction?.call(FieldOptionAction.hide), + ), + if (widget.actions.contains(FieldOptionAction.show)) + FlowyOptionTile.text( + text: LocaleKeys.grid_field_show.tr(), + leftIcon: const FlowySvg(FlowySvgs.show_m, size: Size.square(16)), + onTap: () => widget.onAction?.call(FieldOptionAction.show), + ), + if (widget.actions.contains(FieldOptionAction.duplicate) && + !widget.isPrimary) + FlowyOptionTile.text( + showTopBorder: false, + text: LocaleKeys.button_duplicate.tr(), + leftIcon: const FlowySvg(FlowySvgs.copy_s), + onTap: () => widget.onAction?.call(FieldOptionAction.duplicate), + ), + if (widget.actions.contains(FieldOptionAction.delete) && + !widget.isPrimary) + FlowyOptionTile.text( + showTopBorder: false, + text: LocaleKeys.button_delete.tr(), + textColor: Theme.of(context).colorScheme.error, + leftIcon: FlowySvg( + FlowySvgs.delete_s, + color: Theme.of(context).colorScheme.error, ), - if (!widget.isPrimary) ...[ - FlowyOptionTile.text( - showTopBorder: false, - text: LocaleKeys.button_duplicate.tr(), - leftIcon: const FlowySvg(FlowySvgs.copy_s), - onTap: () => widget.onAction?.call(FieldOptionAction.duplicate), - ), - FlowyOptionTile.text( - showTopBorder: false, - text: LocaleKeys.button_delete.tr(), - textColor: Theme.of(context).colorScheme.error, - leftIcon: FlowySvg( - FlowySvgs.delete_s, - color: Theme.of(context).colorScheme.error, - ), - onTap: () => widget.onAction?.call(FieldOptionAction.delete), - ), - ], - ] - }; + onTap: () => widget.onAction?.call(FieldOptionAction.delete), + ), + ]; } void _updateOptionValues({ 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 a668c2954c..95223f25b6 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 @@ -6,11 +6,13 @@ import 'package:appflowy/mobile/presentation/database/field/mobile_field_bottom_ import 'package:appflowy/mobile/presentation/widgets/widgets.dart'; import 'package:appflowy/plugins/database_view/application/field/field_backend_service.dart'; import 'package:appflowy/plugins/database_view/application/field/field_info.dart'; +import 'package:appflowy/plugins/database_view/widgets/setting/field_visibility_extension.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; +import 'package:protobuf/protobuf.dart' hide FieldInfo; class QuickEditField extends StatefulWidget { const QuickEditField({ @@ -35,12 +37,15 @@ class _QuickEditFieldState extends State { ); late FieldType fieldType; + late FieldVisibility fieldVisibility; @override void initState() { super.initState(); fieldType = widget.fieldInfo.fieldType; + fieldVisibility = widget.fieldInfo.fieldSettings?.visibility ?? + FieldVisibility.AlwaysShown; controller.text = widget.fieldInfo.field.name; } @@ -69,10 +74,14 @@ class _QuickEditFieldState extends State { text: LocaleKeys.grid_field_editProperty.tr(), leftIcon: const FlowySvg(FlowySvgs.edit_s), onTap: () async { + widget.fieldInfo.field.freeze(); + final field = widget.fieldInfo.field.rebuild((field) { + field.name = controller.text; + }); final optionValues = await showEditFieldScreen( context, widget.viewId, - widget.fieldInfo, + widget.fieldInfo.copyWith(field: field), ); if (optionValues != null) { setState(() { @@ -85,11 +94,17 @@ class _QuickEditFieldState extends State { if (!widget.fieldInfo.isPrimary) FlowyOptionTile.text( showTopBorder: false, - text: LocaleKeys.grid_field_hide.tr(), + text: fieldVisibility.isVisibleState() + ? LocaleKeys.grid_field_hide.tr() + : LocaleKeys.grid_field_show.tr(), leftIcon: const FlowySvg(FlowySvgs.hide_s), onTap: () async { context.pop(); - await service.hide(); + if (fieldVisibility.isVisibleState()) { + await service.hide(); + } else { + await service.hide(); + } }, ), if (!widget.fieldInfo.isPrimary) @@ -99,7 +114,14 @@ class _QuickEditFieldState extends State { leftIcon: const FlowySvg(FlowySvgs.insert_left_s), onTap: () async { context.pop(); - await service.insertLeft(); + showCreateFieldBottomSheet( + context, + widget.viewId, + position: OrderObjectPositionPB( + position: OrderObjectPositionTypePB.Before, + objectId: widget.fieldInfo.id, + ), + ); }, ), FlowyOptionTile.text( @@ -108,7 +130,14 @@ class _QuickEditFieldState extends State { leftIcon: const FlowySvg(FlowySvgs.insert_right_s), onTap: () async { context.pop(); - await service.insertRight(); + showCreateFieldBottomSheet( + context, + widget.viewId, + position: OrderObjectPositionPB( + position: OrderObjectPositionTypePB.After, + objectId: widget.fieldInfo.id, + ), + ); }, ), if (!widget.fieldInfo.isPrimary) ...[ 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 2bb53a10bd..15aeeeebc0 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 @@ -110,7 +110,19 @@ class _MobileDatabaseFieldListBody extends StatelessWidget { )..add(const DatabasePropertyEvent.initial()), child: BlocBuilder( builder: (context, state) { - final cells = state.fieldContexts + if (state.fieldContexts.isEmpty) { + return const SizedBox.shrink(); + } + final fields = [...state.fieldContexts]; + final firstField = fields.removeAt(0); + final firstCell = DatabaseFieldListTile( + key: ValueKey(firstField.id), + viewId: view.id, + fieldController: databaseController.fieldController, + fieldInfo: firstField, + showTopBorder: true, + ); + final cells = fields .mapIndexed( (index, field) => DatabaseFieldListTile( key: ValueKey(field.id), @@ -118,14 +130,14 @@ class _MobileDatabaseFieldListBody extends StatelessWidget { fieldController: databaseController.fieldController, fieldInfo: field, index: index, - showTopBorder: index == 0, + showTopBorder: false, ), ) .toList(); return ReorderableListView.builder( proxyDecorator: (_, index, anim) { - final field = state.fieldContexts[index]; + final field = fields[index]; return AnimatedBuilder( animation: anim, builder: (BuildContext context, Widget? child) { @@ -151,10 +163,13 @@ class _MobileDatabaseFieldListBody extends StatelessWidget { buildDefaultDragHandles: true, shrinkWrap: true, onReorder: (from, to) { + from++; + to++; context .read() .add(DatabasePropertyEvent.moveField(from, to)); }, + header: firstCell, footer: Column( mainAxisSize: MainAxisSize.min, children: [ @@ -176,14 +191,14 @@ class _MobileDatabaseFieldListBody extends StatelessWidget { class DatabaseFieldListTile extends StatelessWidget { const DatabaseFieldListTile({ super.key, - required this.index, + this.index, required this.fieldInfo, required this.viewId, required this.fieldController, required this.showTopBorder, }); - final int index; + final int? index; final FieldInfo fieldInfo; final String viewId; final FieldController fieldController; diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/application/field/field_backend_service.dart b/frontend/appflowy_flutter/lib/plugins/database_view/application/field/field_backend_service.dart index b96064722e..b50baf2a1f 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/application/field/field_backend_service.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/application/field/field_backend_service.dart @@ -36,6 +36,13 @@ class FieldServices { ); } + Future show() async { + await fieldSettingsService.updateFieldSettings( + fieldId: fieldId, + fieldVisibility: FieldVisibility.AlwaysShown, + ); + } + Future delete() async { await fieldBackendService.delete(); } diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/application/grid_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/application/grid_bloc.dart index 1d5b0c5c73..4354a11716 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/application/grid_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/application/grid_bloc.dart @@ -27,15 +27,20 @@ class GridBloc extends Bloc { _startListening(); await _openGrid(emit); }, - createRow: () async { + createRow: (openRowDetail) async { final result = await RowBackendService.createRow(viewId: viewId); result.fold( - (createdRow) => emit(state.copyWith(createdRow: createdRow)), + (createdRow) => emit( + state.copyWith( + createdRow: createdRow, + openRowDetail: openRowDetail ?? false, + ), + ), (err) => Log.error(err), ); }, resetCreatedRow: () { - emit(state.copyWith(createdRow: null)); + emit(state.copyWith(createdRow: null, openRowDetail: false)); }, deleteRow: (rowInfo) async { await RowBackendService.deleteRow(rowInfo.viewId, rowInfo.rowId); @@ -151,7 +156,7 @@ class GridBloc extends Bloc { @freezed class GridEvent with _$GridEvent { const factory GridEvent.initial() = InitialGrid; - const factory GridEvent.createRow() = _CreateRow; + const factory GridEvent.createRow({bool? openRowDetail}) = _CreateRow; const factory GridEvent.resetCreatedRow() = _ResetCreatedRow; const factory GridEvent.deleteRow(RowInfo rowInfo) = _DeleteRow; const factory GridEvent.moveRow(int from, int to) = _MoveRow; @@ -187,6 +192,7 @@ class GridState with _$GridState { required ChangedReason reason, required List sorts, required List filters, + required bool openRowDetail, }) = _GridState; factory GridState.initial(String viewId) => GridState( @@ -201,5 +207,6 @@ class GridState with _$GridState { reason: const InitialListState(), filters: [], sorts: [], + openRowDetail: false, ); } diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/mobile_grid_page.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/mobile_grid_page.dart index 5bff94f7f2..66bca1ec84 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/mobile_grid_page.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/mobile_grid_page.dart @@ -139,7 +139,7 @@ class _GridPageContentState extends State { listenWhen: (previous, current) => previous.createdRow != current.createdRow, listener: (context, state) { - if (state.createdRow == null) { + if (state.createdRow == null || !state.openRowDetail) { return; } final bloc = context.read(); diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/mobile_fab.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/mobile_fab.dart index e26b395bb7..7e6616fab5 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/mobile_fab.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/mobile_fab.dart @@ -39,7 +39,9 @@ Widget getGridFabs(BuildContext context) { backgroundColor: Theme.of(context).primaryColor, foregroundColor: Colors.white, onTap: () { - context.read().add(const GridEvent.createRow()); + context + .read() + .add(const GridEvent.createRow(openRowDetail: true)); }, overlayColor: const MaterialStatePropertyAll(Color(0xFF009FD1)), boxShadow: const BoxShadow( diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/select_option_cell/mobile_select_option_editor.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/select_option_cell/mobile_select_option_editor.dart index 3749b4d25c..2d025058a3 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/select_option_cell/mobile_select_option_editor.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/select_option_cell/mobile_select_option_editor.dart @@ -10,7 +10,6 @@ import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_c import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/select_option.pb.dart'; import 'package:easy_localization/easy_localization.dart'; -import 'package:flowy_infra/size.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -64,14 +63,8 @@ class _MobileSelectOptionEditorState extends State { const DragHandler(), Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: _buildHeader( - context, - showSaveButton: state.createOption - .fold(() => false, (a) => a.isNotEmpty) || - showMoreOptions, - ), + child: _buildHeader(context), ), - const Divider(), Expanded( child: Padding( padding: EdgeInsets.symmetric( @@ -88,7 +81,7 @@ class _MobileSelectOptionEditorState extends State { ); } - Widget _buildHeader(BuildContext context, {required bool showSaveButton}) { + Widget _buildHeader(BuildContext context) { const iconWidth = 36.0; const height = 44.0; return Stack( @@ -96,9 +89,9 @@ class _MobileSelectOptionEditorState extends State { Align( alignment: Alignment.centerLeft, child: FlowyIconButton( - icon: const FlowySvg( - FlowySvgs.close_s, - size: Size.square(iconWidth), + icon: FlowySvg( + showMoreOptions ? FlowySvgs.arrow_left_s : FlowySvgs.close_s, + size: const Size.square(iconWidth), ), width: iconWidth, iconPadding: EdgeInsets.zero, @@ -115,54 +108,6 @@ class _MobileSelectOptionEditorState extends State { ), ), ), - Align( - alignment: Alignment.centerRight, - child: !showSaveButton - ? const HSpace(iconWidth) - : Container( - padding: const EdgeInsets.symmetric( - vertical: 2.0, - horizontal: 8.0, - ), - decoration: const BoxDecoration( - color: Color(0xFF00bcf0), - borderRadius: Corners.s10Border, - ), - child: FlowyButton( - text: FlowyText( - LocaleKeys.button_save.tr(), - color: Colors.white, - ), - useIntrinsicWidth: true, - onTap: () { - if (showMoreOptions) { - final option = this.option; - if (option == null) { - return; - } - option.freeze(); - context.read().add( - SelectOptionEditorEvent.updateOption( - option.rebuild((p0) { - if (p0.name != renameController.text) { - p0.name = renameController.text; - } - }), - ), - ); - _popOrBack(); - } else if (typingOption.isNotEmpty) { - context.read().add( - SelectOptionEditorEvent.trySelectOption( - typingOption, - ), - ); - searchController.clear(); - } - }, - ), - ), - ), ].map((e) => SizedBox(height: height, child: e)).toList(), ); } @@ -170,13 +115,13 @@ class _MobileSelectOptionEditorState extends State { Widget _buildBody(BuildContext context) { if (showMoreOptions && option != null) { return _MoreOptions( - option: option!, - controller: renameController..text = option!.name, + initialOption: option!, + controller: renameController, onDelete: () { context .read() .add(SelectOptionEditorEvent.deleteOption(option!)); - context.pop(); + _popOrBack(); }, onUpdate: (name, color) { final option = this.option; @@ -196,7 +141,6 @@ class _MobileSelectOptionEditorState extends State { }), ), ); - _popOrBack(); }, ); } @@ -244,6 +188,7 @@ class _MobileSelectOptionEditorState extends State { onMoreOptions: (option) { setState(() { this.option = option; + renameController.text = option.name; showMoreOptions = true; }); }, @@ -461,19 +406,26 @@ class _CreateOptionCell extends StatelessWidget { } } -class _MoreOptions extends StatelessWidget { +class _MoreOptions extends StatefulWidget { const _MoreOptions({ - required this.option, + required this.initialOption, required this.onDelete, required this.onUpdate, required this.controller, }); - final SelectOptionPB option; + final SelectOptionPB initialOption; final VoidCallback onDelete; final void Function(String? name, SelectOptionColorPB? color) onUpdate; final TextEditingController controller; + @override + State<_MoreOptions> createState() => _MoreOptionsState(); +} + +class _MoreOptionsState extends State<_MoreOptions> { + late SelectOptionPB option = widget.initialOption; + @override Widget build(BuildContext context) { final color = Theme.of(context).colorScheme.secondaryContainer; @@ -481,7 +433,6 @@ class _MoreOptions extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const VSpace(8.0), _buildRenameTextField(context), const VSpace(16.0), _buildDeleteButton(context), @@ -491,8 +442,9 @@ class _MoreOptions extends StatelessWidget { child: ColoredBox( color: color, child: FlowyText( - LocaleKeys.grid_field_optionTitle.tr(), + LocaleKeys.grid_selectOption_colorPanelTitle.tr().toUpperCase(), color: Theme.of(context).hintColor, + fontSize: 13, ), ), ), @@ -508,7 +460,13 @@ class _MoreOptions extends StatelessWidget { ), child: OptionColorList( selectedColor: option.color, - onSelectedColor: (color) => onUpdate(null, color), + onSelectedColor: (color) { + widget.onUpdate(null, color); + setState(() { + option.freeze(); + option = option.rebuild((option) => option.color = color); + }); + }, ), ), ), @@ -521,7 +479,8 @@ class _MoreOptions extends StatelessWidget { return ConstrainedBox( constraints: const BoxConstraints.tightFor(height: 52.0), child: FlowyOptionTile.textField( - controller: controller, + onTextChanged: (name) => widget.onUpdate(name, null), + controller: widget.controller, ), ); } @@ -530,7 +489,7 @@ class _MoreOptions extends StatelessWidget { return FlowyOptionTile.text( text: LocaleKeys.button_delete.tr(), leftIcon: const FlowySvg(FlowySvgs.delete_s), - onTap: onDelete, + onTap: widget.onDelete, ); } } diff --git a/frontend/resources/translations/en.json b/frontend/resources/translations/en.json index 80971dcf20..a1ee4fd272 100644 --- a/frontend/resources/translations/en.json +++ b/frontend/resources/translations/en.json @@ -620,7 +620,7 @@ "aquaColor": "Aqua", "blueColor": "Blue", "deleteTag": "Delete tag", - "colorPanelTitle": "Colors", + "colorPanelTitle": "Color", "panelTitle": "Select an option or create one", "searchOption": "Search for an option", "searchOrCreateOption": "Search or create an option...", @@ -778,7 +778,6 @@ "textBlock": { "placeholder": "Type '/' for commands" }, - "title": { "placeholder": "Untitled" },