From 66835a5409a674f7b8d5708b3d08747d1919e861 Mon Sep 17 00:00:00 2001 From: Richard Shiue <71320345+richardshiue@users.noreply.github.com> Date: Thu, 23 Nov 2023 16:43:29 +0800 Subject: [PATCH] feat: field editing bloc refactor and add mobile field editor (#3981) --- .../integration_test/database_cell_test.dart | 2 +- .../integration_test/database_field_test.dart | 2 +- .../database_row_page_test.dart | 1 - .../util/database_test_op.dart | 15 +- .../bottom_sheet_database_field_editor.dart | 211 ++++++++ .../bottom_sheet_database_field_header.dart | 52 ++ .../bottom_sheet_rename_widget.dart | 15 +- .../mobile_card_detail_screen.dart | 6 + .../mobile_create_row_field_screen.dart | 8 +- .../mobile_create_row_field_button.dart | 9 +- .../widgets/mobile_field_name_text_field.dart | 2 +- .../widgets/mobile_row_property_list.dart | 8 + .../card_property_edit_screen.dart | 7 +- .../mobile_field_editor.dart | 53 +- .../field/field_action_sheet_bloc.dart | 108 ---- .../application/field/field_editor_bloc.dart | 159 +++--- .../application/field/field_service.dart | 5 - .../board/presentation/board_page.dart | 6 +- .../widgets/board_hidden_groups.dart | 2 + .../calendar/presentation/calendar_day.dart | 1 + .../presentation/calendar_event_card.dart | 6 +- .../presentation/calendar_event_editor.dart | 11 +- .../calendar/presentation/calendar_page.dart | 21 +- .../grid/presentation/grid_page.dart | 3 +- .../grid/presentation/layout/sizes.dart | 2 +- .../grid/presentation/mobile_grid_page.dart | 3 +- .../widgets/header/field_cell.dart | 11 +- .../header/field_cell_action_sheet.dart | 266 --------- .../widgets/header/field_editor.dart | 507 ++++++++++++------ .../header/field_type_option_editor.dart | 46 +- .../widgets/header/grid_header.dart | 73 +-- .../widgets/header/mobile_field_cell.dart | 15 +- .../widgets/header/type_option/number.dart | 16 +- .../header/type_option/select_option.dart | 91 +--- .../type_option/select_option_editor.dart | 21 +- .../header/type_option/single_select.dart | 1 - .../cells/select_option_cell/extension.dart | 2 +- .../select_option_editor.dart | 4 +- .../database_view/widgets/row/row_detail.dart | 4 + .../widgets/row/row_property.dart | 76 +-- .../setting/setting_property_list.dart | 20 +- .../lib/startup/tasks/generate_router.dart | 13 +- .../lib/style_widget/button.dart | 2 +- .../board_test/create_or_edit_field_test.dart | 5 +- .../group_by_unsupport_field_test.dart | 7 +- .../test/bloc_test/board_test/util.dart | 10 +- .../grid_test/field/edit_field_test.dart | 67 +-- .../grid_test/grid_header_bloc_test.dart | 128 ----- .../test/bloc_test/grid_test/util.dart | 17 +- 49 files changed, 1002 insertions(+), 1118 deletions(-) create mode 100644 frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_database_field_editor.dart create mode 100644 frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_database_field_header.dart delete mode 100644 frontend/appflowy_flutter/lib/plugins/database_view/application/field/field_action_sheet_bloc.dart delete mode 100644 frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/field_cell_action_sheet.dart delete mode 100644 frontend/appflowy_flutter/test/bloc_test/grid_test/grid_header_bloc_test.dart diff --git a/frontend/appflowy_flutter/integration_test/database_cell_test.dart b/frontend/appflowy_flutter/integration_test/database_cell_test.dart index 0131c5834e..614b82e210 100644 --- a/frontend/appflowy_flutter/integration_test/database_cell_test.dart +++ b/frontend/appflowy_flutter/integration_test/database_cell_test.dart @@ -32,7 +32,7 @@ void main() { await tester.pumpAndSettle(); }); - // Makesure the text cells are filled with the right content when there are + // Make sure the text cells are filled with the right content when there are // multiple text cell testWidgets('edit multiple text cells', (tester) async { await tester.initializeAppFlowy(); diff --git a/frontend/appflowy_flutter/integration_test/database_field_test.dart b/frontend/appflowy_flutter/integration_test/database_field_test.dart index 01492364a4..bfd67ec2fc 100644 --- a/frontend/appflowy_flutter/integration_test/database_field_test.dart +++ b/frontend/appflowy_flutter/integration_test/database_field_test.dart @@ -106,7 +106,7 @@ void main() { await tester.pumpAndSettle(); }); - testWidgets('create checklist field ', (tester) async { + testWidgets('create checklist field', (tester) async { await tester.initializeAppFlowy(); await tester.tapGoButton(); diff --git a/frontend/appflowy_flutter/integration_test/database_row_page_test.dart b/frontend/appflowy_flutter/integration_test/database_row_page_test.dart index 9a771eb876..797c6231c6 100644 --- a/frontend/appflowy_flutter/integration_test/database_row_page_test.dart +++ b/frontend/appflowy_flutter/integration_test/database_row_page_test.dart @@ -123,7 +123,6 @@ void main() { await tester.tapTypeOptionButton(); await tester.selectFieldType(fieldType); - await tester.dismissFieldEditor(); // After update the field type, the cells should be updated await tester.findCellByFieldType(fieldType); diff --git a/frontend/appflowy_flutter/integration_test/util/database_test_op.dart b/frontend/appflowy_flutter/integration_test/util/database_test_op.dart index e99dbdc0df..381adf9e95 100644 --- a/frontend/appflowy_flutter/integration_test/util/database_test_op.dart +++ b/frontend/appflowy_flutter/integration_test/util/database_test_op.dart @@ -21,7 +21,6 @@ import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/ import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/filter_menu_item.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/widgets/footer/grid_footer.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_cell.dart'; -import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_cell_action_sheet.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_editor.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_type_extension.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_type_list.dart'; @@ -688,7 +687,10 @@ extension AppFlowyDatabaseTest on WidgetTester { } Future tapDeletePropertyInFieldEditor() async { - final deleteButton = find.byType(DeleteFieldButton); + final deleteButton = find.byWidgetPredicate( + (widget) => + widget is FieldActionCell && widget.action == FieldAction.delete, + ); await tapButton(deleteButton); final confirmButton = find.descendant( @@ -765,13 +767,18 @@ extension AppFlowyDatabaseTest on WidgetTester { Future tapHidePropertyButton() async { final field = find.byWidgetPredicate( (widget) => - widget is FieldActionCell && widget.action == FieldAction.hide, + widget is FieldActionCell && + widget.action == FieldAction.toggleVisibility, ); await tapButton(field); } Future tapHidePropertyButtonInFieldEditor() async { - final button = find.byType(FieldVisibilityToggleButton); + final button = find.byWidgetPredicate( + (widget) => + widget is FieldActionCell && + widget.action == FieldAction.toggleVisibility, + ); await tapButton(button); } diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_database_field_editor.dart b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_database_field_editor.dart new file mode 100644 index 0000000000..2db2b26c2e --- /dev/null +++ b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_database_field_editor.dart @@ -0,0 +1,211 @@ +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/database_view/application/field/field_controller.dart'; +import 'package:appflowy/plugins/database_view/application/field/field_editor_bloc.dart'; +import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart'; +import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:go_router/go_router.dart'; + +import 'bottom_sheet_action_widget.dart'; +import 'bottom_sheet_database_field_header.dart'; +import 'bottom_sheet_rename_widget.dart'; + +/// The mobile bottom bar field editor is a two-deep menu. The type option +/// sub-menu may have its own sub-menus as well though. +enum MobileDBBottomSheetViewMode { + // operations shared between all fields + general, + // operations specific to the field type + typeOption, +} + +class MobileDBBottomSheetFieldEditor extends StatefulWidget { + final String viewId; + final FieldController fieldController; + final FieldPB field; + final MobileDBBottomSheetViewMode initialPage; + + const MobileDBBottomSheetFieldEditor({ + super.key, + required this.viewId, + required this.fieldController, + required this.field, + this.initialPage = MobileDBBottomSheetViewMode.general, + }); + + @override + State createState() => + _MobileDBBottomSheetFieldEditorState(); +} + +class _MobileDBBottomSheetFieldEditorState + extends State { + late MobileDBBottomSheetViewMode viewMode; + late final FieldEditorBloc _fieldEditorBloc; + + @override + void initState() { + super.initState(); + viewMode = widget.initialPage; + final loader = FieldTypeOptionLoader( + viewId: widget.viewId, + field: widget.field, + ); + _fieldEditorBloc = FieldEditorBloc( + viewId: widget.viewId, + field: widget.field, + loader: loader, + fieldController: widget.fieldController, + )..add(const FieldEditorEvent.initial()); + } + + @override + Widget build(BuildContext context) { + return BlocProvider.value( + value: _fieldEditorBloc, + child: Padding( + padding: const EdgeInsets.fromLTRB(16, 16, 16, 32), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + _buildHeader(), + const VSpace(16), + _buildBody(), + ], + ), + ), + ); + } + + Widget _buildHeader() { + return MobileDBFieldBottomSheetHeader( + showBackButton: viewMode == MobileDBBottomSheetViewMode.typeOption, + onBack: () { + if (viewMode == MobileDBBottomSheetViewMode.typeOption) { + setState(() { + viewMode = MobileDBBottomSheetViewMode.general; + }); + } + }, + ); + } + + Widget _buildBody() { + return switch (viewMode) { + MobileDBBottomSheetViewMode.general => MobileDBFieldBottomSheetBody( + onAction: (action) { + switch (action) { + case MobileDBBottomSheetGeneralAction.typeOption: + break; + case MobileDBBottomSheetGeneralAction.toggleVisibility: + _fieldEditorBloc + .add(const FieldEditorEvent.toggleFieldVisibility()); + context.pop(); + break; + case MobileDBBottomSheetGeneralAction.delete: + _fieldEditorBloc.add(const FieldEditorEvent.deleteField()); + context.pop(); + break; + case MobileDBBottomSheetGeneralAction.duplicate: + _fieldEditorBloc.add(const FieldEditorEvent.duplicateField()); + context.pop(); + } + }, + onRename: (name) { + _fieldEditorBloc.add(FieldEditorEvent.renameField(name)); + }, + ), + MobileDBBottomSheetViewMode.typeOption => const SizedBox.shrink(), + }; + } +} + +enum MobileDBBottomSheetGeneralAction { + toggleVisibility, + duplicate, + delete, + typeOption, +} + +class MobileDBFieldBottomSheetBody extends StatelessWidget { + const MobileDBFieldBottomSheetBody({ + super.key, + required this.onAction, + required this.onRename, + }); + + final void Function(MobileDBBottomSheetGeneralAction action) onAction; + final void Function(String name) onRename; + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisSize: MainAxisSize.min, + children: [ + // field name editor + MobileBottomSheetRenameWidget( + name: context.read().state.field.name, + onRename: (newName) => onRename(newName), + padding: EdgeInsets.zero, + ), + const VSpace(8), + // type option button + BottomSheetActionWidget( + svg: FlowySvgs.date_s, + text: LocaleKeys.grid_field_editProperty.tr(), + onTap: () { + onAction(MobileDBBottomSheetGeneralAction.typeOption); + }, + ), + const VSpace(8), + Row( + children: [ + // hide/show field + Expanded( + child: BottomSheetActionWidget( + svg: FlowySvgs.hide_m, + text: LocaleKeys.grid_field_hide.tr(), + onTap: () { + onAction(MobileDBBottomSheetGeneralAction.toggleVisibility); + }, + ), + ), + const HSpace(8), + // duplicate field + Expanded( + child: BottomSheetActionWidget( + svg: FlowySvgs.copy_s, + text: LocaleKeys.grid_field_duplicate.tr(), + onTap: () { + onAction(MobileDBBottomSheetGeneralAction.duplicate); + }, + ), + ), + ], + ), + const VSpace(8), + Row( + children: [ + // delete field + Expanded( + child: BottomSheetActionWidget( + svg: FlowySvgs.delete_s, + text: LocaleKeys.grid_field_delete.tr(), + onTap: () { + onAction(MobileDBBottomSheetGeneralAction.delete); + }, + ), + ), + const HSpace(8), + const Expanded(child: SizedBox.shrink()), + ], + ), + ], + ); + } +} diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_database_field_header.dart b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_database_field_header.dart new file mode 100644 index 0000000000..5aa7e12ad9 --- /dev/null +++ b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_database_field_header.dart @@ -0,0 +1,52 @@ +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +class MobileDBFieldBottomSheetHeader extends StatelessWidget { + const MobileDBFieldBottomSheetHeader({ + super.key, + required this.showBackButton, + required this.onBack, + }); + + final bool showBackButton; + final VoidCallback onBack; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + return Row( + children: [ + // back button + if (showBackButton) + InkWell( + onTap: onBack, + child: const Padding( + padding: EdgeInsets.symmetric(horizontal: 8.0), + child: Icon( + Icons.arrow_back_ios_new_rounded, + size: 24.0, + ), + ), + ), + // field name + Expanded( + child: Text( + LocaleKeys.grid_field_editProperty.tr(), + style: theme.textTheme.labelSmall, + ), + ), + IconButton( + icon: Icon( + Icons.close, + color: theme.hintColor, + ), + onPressed: () { + context.pop(); + }, + ), + ], + ); + } +} diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_rename_widget.dart b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_rename_widget.dart index e4be17616e..3ecd0dfe11 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_rename_widget.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_rename_widget.dart @@ -8,10 +8,12 @@ class MobileBottomSheetRenameWidget extends StatefulWidget { super.key, required this.name, required this.onRename, + this.padding = const EdgeInsets.symmetric(horizontal: 12.0, vertical: 16.0), }); final String name; final void Function(String name) onRename; + final EdgeInsets padding; @override State createState() => @@ -39,17 +41,13 @@ class _MobileBottomSheetRenameWidgetState @override Widget build(BuildContext context) { return Padding( - padding: const EdgeInsets.symmetric( - horizontal: 4.0, - vertical: 16.0, - ), + padding: widget.padding, child: Row( mainAxisSize: MainAxisSize.min, children: [ - const HSpace(8.0), Expanded( child: SizedBox( - height: 44.0, + height: 42.0, child: FlowyTextField( controller: controller, ), @@ -58,17 +56,16 @@ class _MobileBottomSheetRenameWidgetState const HSpace(12.0), FlowyTextButton( LocaleKeys.button_edit.tr(), + constraints: const BoxConstraints.tightFor(height: 42), padding: const EdgeInsets.symmetric( - vertical: 12.0, horizontal: 16.0, ), fontColor: Colors.white, - fillColor: Colors.lightBlue.shade300, + fillColor: Theme.of(context).primaryColor, onPressed: () { widget.onRename(controller.text); }, ), - const HSpace(8.0), ], ), ); diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/mobile_card_detail_screen.dart b/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/mobile_card_detail_screen.dart index d0ca5a5135..4626973ded 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/mobile_card_detail_screen.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/mobile_card_detail_screen.dart @@ -4,6 +4,7 @@ import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart'; import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet_action_widget.dart'; import 'package:appflowy/mobile/presentation/widgets/show_flowy_mobile_bottom_sheet.dart'; import 'package:appflowy/plugins/database_view/application/cell/cell_service.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/row/row_banner_bloc.dart'; import 'package:appflowy/plugins/database_view/application/row/row_controller.dart'; @@ -24,12 +25,16 @@ class MobileCardDetailScreen extends StatefulWidget { const MobileCardDetailScreen({ super.key, required this.rowController, + required this.fieldController, }); static const routeName = '/MobileCardDetailScreen'; static const argRowController = 'rowController'; static const argCellBuilder = 'cellBuilder'; + static const argFieldController = 'fieldController'; + final RowController rowController; + final FieldController fieldController; @override State createState() => _MobileCardDetailScreenState(); @@ -169,6 +174,7 @@ class _MobileCardDetailScreenState extends State { MobileRowPropertyList( cellBuilder: _cellBuilder, viewId: widget.rowController.viewId, + fieldController: widget.fieldController, ), const Divider(), const VSpace(16), diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/mobile_create_row_field_screen.dart b/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/mobile_create_row_field_screen.dart index 53ad24255d..cb15f38ba2 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/mobile_create_row_field_screen.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/mobile_create_row_field_screen.dart @@ -1,5 +1,6 @@ import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/mobile/presentation/database/card/card_property_edit/mobile_field_editor.dart'; +import 'package:appflowy/plugins/database_view/application/field/field_controller.dart'; import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart'; import 'package:easy_localization/easy_localization.dart'; @@ -9,15 +10,18 @@ import 'package:go_router/go_router.dart'; class MobileCreateRowFieldScreen extends StatefulWidget { static const routeName = '/MobileCreateRowFieldScreen'; static const argViewId = 'viewId'; + static const argFieldController = 'fieldController'; static const argTypeOption = 'typeOption'; const MobileCreateRowFieldScreen({ + super.key, required this.viewId, required this.typeOption, - super.key, + required this.fieldController, }); final String viewId; + final FieldController fieldController; final TypeOptionPB typeOption; @override @@ -53,6 +57,8 @@ class _MobileCreateRowFieldScreenState viewId: widget.viewId, field: widget.typeOption.field_2, ), + fieldController: widget.fieldController, + field: widget.typeOption.field_2, ), ); } diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/widgets/mobile_create_row_field_button.dart b/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/widgets/mobile_create_row_field_button.dart index 634d695283..703c12b654 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/widgets/mobile_create_row_field_button.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/widgets/mobile_create_row_field_button.dart @@ -1,6 +1,7 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/mobile/presentation/database/card/card_detail/mobile_create_row_field_screen.dart'; +import 'package:appflowy/plugins/database_view/application/field/field_controller.dart'; import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_service.dart'; import 'package:appflowy_backend/log.dart'; import 'package:easy_localization/easy_localization.dart'; @@ -8,9 +9,14 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; class MobileCreateRowFieldButton extends StatelessWidget { - const MobileCreateRowFieldButton({super.key, required this.viewId}); + const MobileCreateRowFieldButton({ + super.key, + required this.viewId, + required this.fieldController, + }); final String viewId; + final FieldController fieldController; @override Widget build(BuildContext context) { @@ -32,6 +38,7 @@ class MobileCreateRowFieldButton extends StatelessWidget { extra: { MobileCreateRowFieldScreen.argViewId: viewId, MobileCreateRowFieldScreen.argTypeOption: typeOption, + MobileCreateRowFieldScreen.argFieldController: fieldController, }, ); }, diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/widgets/mobile_field_name_text_field.dart b/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/widgets/mobile_field_name_text_field.dart index be10ffedf7..defb539d63 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/widgets/mobile_field_name_text_field.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/widgets/mobile_field_name_text_field.dart @@ -38,7 +38,7 @@ class _MobileFieldNameTextFieldState extends State { onChanged: (newName) { context .read() - .add(FieldEditorEvent.updateName(newName)); + .add(FieldEditorEvent.renameField(newName)); }, ); } diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/widgets/mobile_row_property_list.dart b/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/widgets/mobile_row_property_list.dart index fb5eac6ce4..64fc3bc978 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/widgets/mobile_row_property_list.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/widgets/mobile_row_property_list.dart @@ -3,6 +3,7 @@ import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/mobile/presentation/database/card/card_detail/widgets/mobile_create_row_field_button.dart'; import 'package:appflowy/mobile/presentation/database/card/card_property_edit/card_property_edit_screen.dart'; import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart'; +import 'package:appflowy/plugins/database_view/application/field/field_controller.dart'; import 'package:appflowy/plugins/database_view/grid/application/row/row_detail_bloc.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_type_extension.dart'; import 'package:appflowy/plugins/database_view/widgets/row/accessory/cell_accessory.dart'; @@ -23,10 +24,12 @@ class MobileRowPropertyList extends StatelessWidget { const MobileRowPropertyList({ super.key, required this.viewId, + required this.fieldController, required this.cellBuilder, }); final String viewId; + final FieldController fieldController; final GridCellBuilder cellBuilder; @override @@ -44,6 +47,7 @@ class MobileRowPropertyList extends StatelessWidget { itemBuilder: (context, index) => _PropertyCell( key: ValueKey('row_detail_${visibleCells[index].fieldId}'), cellContext: visibleCells[index], + fieldController: fieldController, cellBuilder: cellBuilder, index: index, ), @@ -75,6 +79,7 @@ class MobileRowPropertyList extends StatelessWidget { // add new field MobileCreateRowFieldButton( viewId: viewId, + fieldController: fieldController, ), ], ), @@ -93,11 +98,13 @@ class _PropertyCell extends StatefulWidget { const _PropertyCell({ super.key, required this.cellContext, + required this.fieldController, required this.cellBuilder, required this.index, }); final DatabaseCellContext cellContext; + final FieldController fieldController; final GridCellBuilder cellBuilder; final int index; @@ -142,6 +149,7 @@ class _PropertyCellState extends State<_PropertyCell> { CardPropertyEditScreen.routeName, extra: { CardPropertyEditScreen.argCellContext: widget.cellContext, + CardPropertyEditScreen.argFieldController: widget.fieldController, CardPropertyEditScreen.argRowDetailBloc: context.read(), }, diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_property_edit/card_property_edit_screen.dart b/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_property_edit/card_property_edit_screen.dart index 1f1573a301..21659db4a4 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_property_edit/card_property_edit_screen.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_property_edit/card_property_edit_screen.dart @@ -3,6 +3,7 @@ import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/mobile/presentation/database/card/card_property_edit/mobile_field_editor.dart'; import 'package:appflowy/mobile/presentation/widgets/show_flowy_mobile_confirm_dialog.dart'; import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart'; +import 'package:appflowy/plugins/database_view/application/field/field_controller.dart'; import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart'; import 'package:appflowy/plugins/database_view/grid/application/row/row_detail_bloc.dart'; import 'package:easy_localization/easy_localization.dart'; @@ -14,13 +15,16 @@ class CardPropertyEditScreen extends StatelessWidget { const CardPropertyEditScreen({ super.key, required this.cellContext, + required this.fieldController, }); static const routeName = '/CardPropertyEditScreen'; static const argCellContext = 'cellContext'; + static const argFieldController = 'fieldController'; static const argRowDetailBloc = 'rowDetailBloc'; final DatabaseCellContext cellContext; + final FieldController fieldController; @override Widget build(BuildContext context) { @@ -57,7 +61,8 @@ class CardPropertyEditScreen extends StatelessWidget { viewId: cellContext.viewId, field: cellContext.fieldInfo.field, ), - fieldInfo: cellContext.fieldInfo, + fieldController: fieldController, + field: cellContext.fieldInfo.field, ), ); } diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_property_edit/mobile_field_editor.dart b/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_property_edit/mobile_field_editor.dart index 0ebe4b2fa5..0d80903c93 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_property_edit/mobile_field_editor.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_property_edit/mobile_field_editor.dart @@ -2,12 +2,11 @@ import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/mobile/presentation/database/card/card_detail/widgets/widgets.dart'; import 'package:appflowy/mobile/presentation/database/card/card_property_edit/mobile_field_type_option_editor.dart'; import 'package:appflowy/mobile/presentation/database/card/card_property_edit/widgets/property_title.dart'; +import 'package:appflowy/plugins/database_view/application/field/field_controller.dart'; import 'package:appflowy/plugins/database_view/application/field/field_editor_bloc.dart'; -import 'package:appflowy/plugins/database_view/application/field/field_info.dart'; import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart'; import 'package:appflowy/plugins/database_view/grid/application/row/row_detail_bloc.dart'; -import 'package:appflowy_backend/log.dart'; -import 'package:appflowy_backend/protobuf/flowy-database2/field_settings_entities.pbenum.dart'; +import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/widget/spacing.dart'; import 'package:flutter/material.dart'; @@ -19,65 +18,63 @@ class MobileFieldEditor extends StatelessWidget { super.key, required this.viewId, required this.typeOptionLoader, - this.isGroupingField = false, - this.fieldInfo, + required this.field, + required this.fieldController, }); final String viewId; - final bool isGroupingField; + final FieldController fieldController; final FieldTypeOptionLoader typeOptionLoader; - final FieldInfo? fieldInfo; + final FieldPB field; @override Widget build(BuildContext context) { return BlocProvider( create: (context) { return FieldEditorBloc( - // group field is the field to be used to group cards in database view, it can not be deleted - isGroupField: isGroupingField, + viewId: viewId, loader: typeOptionLoader, - field: typeOptionLoader.field, + field: field, + fieldController: fieldController, )..add(const FieldEditorEvent.initial()); }, child: BlocBuilder( builder: (context, state) { // for field type edit option - final dataController = context.read().dataController; + final dataController = + context.read().typeOptionController; return Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // property name + // field name // TODO(yijing): improve hint text PropertyTitle(LocaleKeys.settings_user_name.tr()), BlocSelector( - selector: (state) { - return state.name; - }, - builder: (context, propertyName) { + selector: (state) => state.field.name, + builder: (context, fieldName) { return MobileFieldNameTextField( - text: propertyName, + text: fieldName, ); }, ), Row( children: [ - PropertyTitle(LocaleKeys.grid_field_visibility.tr()), - const Spacer(), + Expanded( + child: + PropertyTitle(LocaleKeys.grid_field_visibility.tr()), + ), VisibilitySwitch( - isFieldHidden: - fieldInfo?.visibility == FieldVisibility.AlwaysHidden, + isFieldHidden: state.field.visibility == + FieldVisibility.AlwaysHidden, onChanged: () { - state.field.fold( - () => Log.error('Can not hidden the field'), - (field) => context.read().add( - RowDetailEvent.toggleFieldVisibility( - field.id, - ), + context.read().add( + RowDetailEvent.toggleFieldVisibility( + state.field.id, ), - ); + ); }, ), ], diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/application/field/field_action_sheet_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database_view/application/field/field_action_sheet_bloc.dart deleted file mode 100644 index 929cbb9a3c..0000000000 --- a/frontend/appflowy_flutter/lib/plugins/database_view/application/field/field_action_sheet_bloc.dart +++ /dev/null @@ -1,108 +0,0 @@ -import 'package:appflowy/plugins/database_view/application/field_settings/field_settings_service.dart'; -import 'package:appflowy_backend/log.dart'; -import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart'; -import 'package:appflowy_backend/protobuf/flowy-database2/field_settings_entities.pb.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; -import 'field_info.dart'; -import 'field_service.dart'; - -part 'field_action_sheet_bloc.freezed.dart'; - -class FieldActionSheetBloc - extends Bloc { - final String fieldId; - final FieldBackendService fieldService; - final FieldSettingsBackendService fieldSettingsService; - - FieldActionSheetBloc({ - required String viewId, - required FieldInfo fieldInfo, - }) : fieldId = fieldInfo.id, - fieldService = FieldBackendService( - viewId: viewId, - fieldId: fieldInfo.id, - ), - fieldSettingsService = FieldSettingsBackendService(viewId: viewId), - super( - FieldActionSheetState.initial( - TypeOptionPB.create()..field_2 = fieldInfo.field, - ), - ) { - on( - (event, emit) async { - await event.map( - updateFieldName: (_UpdateFieldName value) async { - final result = await fieldService.updateField(name: value.name); - result.fold( - (l) => null, - (err) => Log.error(err), - ); - }, - hideField: (_HideField value) async { - final result = await fieldSettingsService.updateFieldSettings( - fieldId: fieldId, - fieldVisibility: FieldVisibility.AlwaysHidden, - ); - result.fold( - (l) => null, - (err) => Log.error(err), - ); - }, - showField: (_ShowField value) async { - final result = await fieldSettingsService.updateFieldSettings( - fieldId: fieldId, - fieldVisibility: FieldVisibility.AlwaysShown, - ); - result.fold( - (l) => null, - (err) => Log.error(err), - ); - }, - deleteField: (_DeleteField value) async { - final result = await fieldService.deleteField(); - result.fold( - (l) => null, - (err) => Log.error(err), - ); - }, - duplicateField: (_DuplicateField value) async { - final result = await fieldService.duplicateField(); - result.fold( - (l) => null, - (err) => Log.error(err), - ); - }, - saveField: (_SaveField value) {}, - ); - }, - ); - } -} - -@freezed -class FieldActionSheetEvent with _$FieldActionSheetEvent { - const factory FieldActionSheetEvent.updateFieldName(String name) = - _UpdateFieldName; - const factory FieldActionSheetEvent.hideField() = _HideField; - const factory FieldActionSheetEvent.showField() = _ShowField; - const factory FieldActionSheetEvent.duplicateField() = _DuplicateField; - const factory FieldActionSheetEvent.deleteField() = _DeleteField; - const factory FieldActionSheetEvent.saveField() = _SaveField; -} - -@freezed -class FieldActionSheetState with _$FieldActionSheetState { - const factory FieldActionSheetState({ - required TypeOptionPB fieldTypeOptionData, - required String errorText, - required String fieldName, - }) = _FieldActionSheetState; - - factory FieldActionSheetState.initial(TypeOptionPB data) => - FieldActionSheetState( - fieldTypeOptionData: data, - errorText: '', - fieldName: data.field_2.name, - ); -} diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/application/field/field_editor_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database_view/application/field/field_editor_bloc.dart index 32cada4ff1..d2a5f397eb 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/application/field/field_editor_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/application/field/field_editor_bloc.dart @@ -1,112 +1,135 @@ +import 'package:appflowy/plugins/database_view/application/field_settings/field_settings_service.dart'; +import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart'; +import 'package:appflowy_backend/protobuf/flowy-database2/field_settings_entities.pbenum.dart'; +import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:dartz/dartz.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +import 'field_controller.dart'; +import 'field_info.dart'; +import 'field_listener.dart'; import 'field_service.dart'; import 'type_option/type_option_context.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; import 'type_option/type_option_data_controller.dart'; part 'field_editor_bloc.freezed.dart'; class FieldEditorBloc extends Bloc { - final TypeOptionController dataController; + final FieldPB field; + + final String viewId; + final FieldController fieldController; + final SingleFieldListener _singleFieldListener; + final FieldBackendService fieldService; + final FieldSettingsBackendService fieldSettingsService; + final TypeOptionController typeOptionController; FieldEditorBloc({ - required bool isGroupField, - required FieldPB field, + required this.viewId, + required this.field, + required this.fieldController, required FieldTypeOptionLoader loader, - }) : dataController = TypeOptionController( + }) : typeOptionController = TypeOptionController( field: field, loader: loader, ), - super( - FieldEditorState.initial( - loader.viewId, - loader.field.name, - isGroupField, - ), - ) { + _singleFieldListener = SingleFieldListener(fieldId: field.id), + fieldService = FieldBackendService( + viewId: viewId, + fieldId: field.id, + ), + fieldSettingsService = FieldSettingsBackendService(viewId: viewId), + super(FieldEditorState(field: FieldInfo.initial(field))) { on( (event, emit) async { await event.when( initial: () async { - dataController.addFieldListener((field) { + final fieldId = field.id; + typeOptionController.addFieldListener((field) { if (!isClosed) { - add(FieldEditorEvent.didReceiveFieldChanged(field)); + add(FieldEditorEvent.didReceiveFieldChanged(fieldId)); } }); - await dataController.reloadTypeOption(); - add(FieldEditorEvent.didReceiveFieldChanged(dataController.field)); - }, - updateName: (name) { - if (state.name != name) { - dataController.fieldName = name; - emit(state.copyWith(name: name)); - } - }, - didReceiveFieldChanged: (FieldPB field) { - emit( - state.copyWith( - field: Some(field), - name: field.name, - canDelete: field.isPrimary, - ), - ); - }, - deleteField: () { - state.field.fold( - () => null, - (field) { - final fieldService = FieldBackendService( - viewId: loader.viewId, - fieldId: field.id, - ); - fieldService.deleteField(); + _singleFieldListener.start( + onFieldChanged: (field) { + if (!isClosed) { + add(FieldEditorEvent.didReceiveFieldChanged(fieldId)); + } }, ); + await typeOptionController.reloadTypeOption(); + add(FieldEditorEvent.didReceiveFieldChanged(fieldId)); }, - switchToField: (FieldType fieldType) async { - await dataController.switchToField(fieldType); + didReceiveFieldChanged: (fieldId) async { + await Future.delayed(const Duration(milliseconds: 50)); + emit(state.copyWith(field: fieldController.getField(fieldId)!)); + }, + switchFieldType: (fieldType) async { + await typeOptionController.switchToField(fieldType); + }, + renameField: (newName) async { + final result = await fieldService.updateField(name: newName); + _logIfError(result); + }, + toggleFieldVisibility: () async { + final currentVisibility = + state.field.visibility ?? FieldVisibility.AlwaysShown; + final newVisibility = + currentVisibility == FieldVisibility.AlwaysHidden + ? FieldVisibility.AlwaysShown + : FieldVisibility.AlwaysHidden; + final result = await fieldSettingsService.updateFieldSettings( + fieldId: state.field.id, + fieldVisibility: newVisibility, + ); + _logIfError(result); + }, + deleteField: () async { + final result = await fieldService.deleteField(); + _logIfError(result); + }, + duplicateField: () async { + final result = await fieldService.duplicateField(); + _logIfError(result); }, ); }, ); } + + void _logIfError(Either result) { + result.fold( + (l) => null, + (err) => Log.error(err), + ); + } + + @override + Future close() { + _singleFieldListener.stop(); + return super.close(); + } } @freezed class FieldEditorEvent with _$FieldEditorEvent { const factory FieldEditorEvent.initial() = _InitialField; - const factory FieldEditorEvent.updateName(String name) = _UpdateName; - const factory FieldEditorEvent.deleteField() = _DeleteField; - const factory FieldEditorEvent.switchToField(FieldType fieldType) = - _SwitchToField; - const factory FieldEditorEvent.didReceiveFieldChanged(FieldPB field) = + const factory FieldEditorEvent.didReceiveFieldChanged(String fieldId) = _DidReceiveFieldChanged; + const factory FieldEditorEvent.switchFieldType(FieldType fieldType) = + _SwitchFieldType; + const factory FieldEditorEvent.renameField(String name) = _RenameField; + const factory FieldEditorEvent.toggleFieldVisibility() = + _ToggleFieldVisiblity; + const factory FieldEditorEvent.deleteField() = _DeleteField; + const factory FieldEditorEvent.duplicateField() = _DuplicateField; } @freezed class FieldEditorState with _$FieldEditorState { const factory FieldEditorState({ - required String viewId, - required String errorText, - required String name, - required Option field, - required bool canDelete, - required bool isGroupField, + required FieldInfo field, }) = _FieldEditorState; - - factory FieldEditorState.initial( - String viewId, - String fieldName, - bool isGroupField, - ) => - FieldEditorState( - viewId: viewId, - errorText: '', - field: none(), - canDelete: false, - name: fieldName, - isGroupField: isGroupField, - ); } diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/application/field/field_service.dart b/frontend/appflowy_flutter/lib/plugins/database_view/application/field/field_service.dart index 9d83019063..4fbd2a8047 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/application/field/field_service.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/application/field/field_service.dart @@ -27,7 +27,6 @@ class FieldBackendService { Future> updateField({ String? name, bool? frozen, - double? width, }) { final payload = FieldChangesetPB.create() ..viewId = viewId @@ -41,10 +40,6 @@ class FieldBackendService { payload.frozen = frozen; } - if (width != null) { - payload.width = width.toInt(); - } - return DatabaseEventUpdateField(payload).send(); } diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/board_page.dart b/frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/board_page.dart index 81cec7815c..9c6ac62fc7 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/board_page.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/board_page.dart @@ -1,4 +1,5 @@ import 'dart:collection'; + import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/mobile/presentation/database/card/card.dart'; @@ -16,7 +17,6 @@ import 'package:appflowy_backend/protobuf/flowy-database2/row_entities.pb.dart'; import 'package:appflowy_board/appflowy_board.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; - import 'package:flowy_infra_ui/widget/error_page.dart'; import 'package:flowy_infra_ui/widget/flowy_tooltip.dart'; import 'package:flutter/material.dart' hide Card; @@ -326,13 +326,15 @@ class _BoardContentState extends State { context.push( MobileCardDetailScreen.routeName, extra: { - 'rowController': dataController, + MobileCardDetailScreen.argRowController: dataController, + MobileCardDetailScreen.argFieldController: fieldController, }, ); } else { FlowyOverlay.show( context: context, builder: (_) => RowDetailPage( + fieldController: fieldController, cellBuilder: GridCellBuilder(cellCache: dataController.cellCache), rowController: dataController, ), diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/widgets/board_hidden_groups.dart b/frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/widgets/board_hidden_groups.dart index 6d7665371b..ec2cc4e05b 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/widgets/board_hidden_groups.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/widgets/board_hidden_groups.dart @@ -421,6 +421,8 @@ class HiddenGroupPopupItemList extends StatelessWidget { context: context, builder: (BuildContext context) { return RowDetailPage( + fieldController: + context.read().fieldController, cellBuilder: GridCellBuilder( cellCache: rowController.cellCache, ), diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/calendar_day.dart b/frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/calendar_day.dart index 8027ac1549..4acb54a0ad 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/calendar_day.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/calendar_day.dart @@ -297,6 +297,7 @@ class _EventList extends StatelessWidget { final autoEdit = editingEvent?.event?.eventId == events[index].eventId; return EventCard( + fieldController: context.read().fieldController, event: events[index], viewId: viewId, rowCache: rowCache, diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/calendar_event_card.dart b/frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/calendar_event_card.dart index b7301bc880..6251068d6a 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/calendar_event_card.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/calendar_event_card.dart @@ -1,4 +1,5 @@ import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/database_view/application/field/field_controller.dart'; import 'package:appflowy/plugins/database_view/application/row/row_cache.dart'; import 'package:appflowy/plugins/database_view/widgets/card/card.dart'; import 'package:appflowy/plugins/database_view/widgets/card/card_cell_builder.dart'; @@ -20,6 +21,7 @@ import '../application/calendar_bloc.dart'; import 'calendar_event_editor.dart'; class EventCard extends StatefulWidget { + final FieldController fieldController; final CalendarDayEvent event; final String viewId; final RowCache rowCache; @@ -27,12 +29,13 @@ class EventCard extends StatefulWidget { final bool autoEdit; const EventCard({ + super.key, required this.event, required this.viewId, required this.rowCache, required this.constraints, required this.autoEdit, - super.key, + required this.fieldController, }); @override @@ -164,6 +167,7 @@ class _EventCardState extends State { rowMeta: widget.event.event.rowMeta, viewId: widget.viewId, layoutSettings: settings, + fieldController: widget.fieldController, ); }, child: DecoratedBox( diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/calendar_event_editor.dart b/frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/calendar_event_editor.dart index 12b568dfb4..04985a4dbb 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/calendar_event_editor.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/calendar_event_editor.dart @@ -1,6 +1,7 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart'; +import 'package:appflowy/plugins/database_view/application/field/field_controller.dart'; import 'package:appflowy/plugins/database_view/application/row/row_cache.dart'; import 'package:appflowy/plugins/database_view/application/row/row_controller.dart'; import 'package:appflowy/plugins/database_view/calendar/application/calendar_event_editor_bloc.dart'; @@ -19,6 +20,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; class CalendarEventEditor extends StatelessWidget { final RowController rowController; + final FieldController fieldController; final CalendarLayoutSettingPB layoutSettings; final GridCellBuilder cellBuilder; @@ -28,6 +30,7 @@ class CalendarEventEditor extends StatelessWidget { required RowMetaPB rowMeta, required String viewId, required this.layoutSettings, + required this.fieldController, }) : rowController = RowController( rowMeta: rowMeta, viewId: viewId, @@ -45,7 +48,10 @@ class CalendarEventEditor extends StatelessWidget { child: Column( mainAxisSize: MainAxisSize.min, children: [ - EventEditorControls(rowController: rowController), + EventEditorControls( + rowController: rowController, + fieldController: fieldController, + ), Flexible( child: EventPropertyList( dateFieldId: layoutSettings.fieldId, @@ -60,9 +66,11 @@ class CalendarEventEditor extends StatelessWidget { class EventEditorControls extends StatelessWidget { final RowController rowController; + final FieldController fieldController; const EventEditorControls({ super.key, required this.rowController, + required this.fieldController, }); @override @@ -91,6 +99,7 @@ class EventEditorControls extends StatelessWidget { context: context, builder: (BuildContext context) { return RowDetailPage( + fieldController: fieldController, cellBuilder: GridCellBuilder( cellCache: rowController.cellCache, ), diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/calendar_page.dart b/frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/calendar_page.dart index a3618aab6c..bcf6bb7999 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/calendar_page.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/calendar_page.dart @@ -1,6 +1,7 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/database_view/application/database_controller.dart'; +import 'package:appflowy/plugins/database_view/application/field/field_controller.dart'; import 'package:appflowy/plugins/database_view/calendar/application/calendar_bloc.dart'; import 'package:appflowy/plugins/database_view/calendar/application/unschedule_event_bloc.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart'; @@ -304,6 +305,7 @@ void showEventDetails({ required CalendarEventPB event, required String viewId, required RowCache rowCache, + required FieldController fieldController, }) { final dataController = RowController( rowMeta: event.rowMeta, @@ -313,12 +315,13 @@ void showEventDetails({ FlowyOverlay.show( context: context, - builder: (BuildContext context) { + builder: (BuildContext overlayContext) { return RowDetailPage( cellBuilder: GridCellBuilder( cellCache: rowCache.cellCache, ), rowController: dataController, + fieldController: fieldController, ); }, ); @@ -387,8 +390,7 @@ class _UnscheduledEventsButtonState extends State { ), popupBuilder: (context) { return UnscheduleEventsList( - viewId: widget.databaseController.viewId, - rowCache: widget.databaseController.rowCache, + databaseController: widget.databaseController, unscheduleEvents: state.unscheduleEvents, ); }, @@ -400,14 +402,12 @@ class _UnscheduledEventsButtonState extends State { } class UnscheduleEventsList extends StatelessWidget { - final String viewId; - final RowCache rowCache; + final DatabaseController databaseController; final List unscheduleEvents; const UnscheduleEventsList({ - required this.viewId, - required this.unscheduleEvents, - required this.rowCache, super.key, + required this.unscheduleEvents, + required this.databaseController, }); @override @@ -429,8 +429,9 @@ class UnscheduleEventsList extends StatelessWidget { showEventDetails( context: context, event: e, - viewId: viewId, - rowCache: rowCache, + viewId: databaseController.viewId, + rowCache: databaseController.rowCache, + fieldController: databaseController.fieldController, ); PopoverContainer.of(context).close(); }, diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/grid_page.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/grid_page.dart index 9c845787e0..b091d0df8a 100755 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/grid_page.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/grid_page.dart @@ -202,8 +202,6 @@ class _GridHeader extends StatelessWidget { builder: (context, state) { return GridHeaderSliverAdaptor( viewId: state.viewId, - fieldController: - context.read().databaseController.fieldController, anchorScrollController: headerScrollController, ); }, @@ -360,6 +358,7 @@ class _GridRows extends StatelessWidget { return RowDetailPage( cellBuilder: cellBuilder, rowController: dataController, + fieldController: fieldController, ); }, ); diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/layout/sizes.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/layout/sizes.dart index fbd05e952e..13d1d17f97 100755 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/layout/sizes.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/layout/sizes.dart @@ -11,7 +11,7 @@ class GridSize { static double get headerContainerPadding => 0 * scale; static double get cellHPadding => 10 * scale; static double get cellVPadding => 10 * scale; - static double get popoverItemHeight => 32 * scale; + static double get popoverItemHeight => 26 * scale; static double get typeOptionSeparatorHeight => 4 * scale; static EdgeInsets get headerContentInsets => EdgeInsets.symmetric( 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 b559e55692..8f0d8f5a1b 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 @@ -184,8 +184,6 @@ class _GridHeader extends StatelessWidget { builder: (context, state) { return GridHeaderSliverAdaptor( viewId: state.viewId, - fieldController: - context.read().databaseController.fieldController, anchorScrollController: headerScrollController, ); }, @@ -363,6 +361,7 @@ class _GridRows extends StatelessWidget { return RowDetailPage( cellBuilder: cellBuilder, rowController: dataController, + fieldController: fieldController, ); }, ); diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/field_cell.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/field_cell.dart index 9dd538d958..0607f6e3b3 100755 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/field_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/field_cell.dart @@ -1,5 +1,6 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/plugins/database_view/application/field/field_cell_bloc.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_popover/appflowy_popover.dart'; @@ -11,15 +12,17 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../../layout/sizes.dart'; -import 'field_cell_action_sheet.dart'; +import 'field_editor.dart'; import 'field_type_extension.dart'; class GridFieldCell extends StatefulWidget { final String viewId; + final FieldController fieldController; final FieldInfo fieldInfo; const GridFieldCell({ super.key, required this.viewId, + required this.fieldController, required this.fieldInfo, }); @@ -59,9 +62,11 @@ class _GridFieldCellState extends State { direction: PopoverDirection.bottomWithLeftAligned, controller: popoverController, popupBuilder: (BuildContext context) { - return GridFieldCellActionSheet( + return FieldEditor( viewId: widget.viewId, - fieldInfo: widget.fieldInfo, + fieldController: widget.fieldController, + field: widget.fieldInfo.field, + initialPage: FieldEditorPage.general, ); }, child: FieldCellButton( diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/field_cell_action_sheet.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/field_cell_action_sheet.dart deleted file mode 100644 index 199ac1e6e0..0000000000 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/field_cell_action_sheet.dart +++ /dev/null @@ -1,266 +0,0 @@ -import 'package:appflowy/generated/flowy_svgs.g.dart'; -import 'package:appflowy/plugins/database_view/application/field/field_action_sheet_bloc.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/application/field/type_option/type_option_context.dart'; -import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; -import 'package:appflowy_popover/appflowy_popover.dart'; - -import 'package:flowy_infra/theme_extension.dart'; -import 'package:flowy_infra_ui/style_widget/button.dart'; -import 'package:flowy_infra_ui/style_widget/text.dart'; -import 'package:flowy_infra_ui/widget/spacing.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:easy_localization/easy_localization.dart'; -import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:styled_widget/styled_widget.dart'; - -import '../../layout/sizes.dart'; -import 'field_editor.dart'; - -class GridFieldCellActionSheet extends StatefulWidget { - final String viewId; - final FieldInfo fieldInfo; - const GridFieldCellActionSheet({ - required this.viewId, - required this.fieldInfo, - Key? key, - }) : super(key: key); - - @override - State createState() => _GridFieldCellActionSheetState(); -} - -class _GridFieldCellActionSheetState extends State { - bool _showFieldEditor = false; - - @override - Widget build(BuildContext context) { - if (_showFieldEditor) { - return SizedBox( - width: 400, - child: FieldEditor( - viewId: widget.viewId, - fieldInfo: widget.fieldInfo, - typeOptionLoader: FieldTypeOptionLoader( - viewId: widget.viewId, - field: widget.fieldInfo.field, - ), - ), - ); - } - return BlocProvider( - create: (context) => FieldActionSheetBloc( - viewId: widget.viewId, - fieldInfo: widget.fieldInfo, - ), - child: IntrinsicWidth( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - _EditFieldButton( - onTap: () { - setState(() => _showFieldEditor = true); - }, - ), - VSpace(GridSize.typeOptionSeparatorHeight), - _FieldOperationList( - viewId: widget.viewId, - fieldInfo: widget.fieldInfo, - ), - ], - ), - ), - ).padding(all: 6.0); - } -} - -class _EditFieldButton extends StatelessWidget { - final void Function()? onTap; - const _EditFieldButton({Key? key, this.onTap}) : super(key: key); - - @override - Widget build(BuildContext context) { - return BlocBuilder( - builder: (context, state) { - return SizedBox( - height: GridSize.popoverItemHeight, - child: FlowyButton( - hoverColor: AFThemeExtension.of(context).lightGreyHover, - text: FlowyText.medium( - LocaleKeys.grid_field_editProperty.tr(), - color: AFThemeExtension.of(context).textColor, - ), - onTap: onTap, - ), - ); - }, - ); - } -} - -class _FieldOperationList extends StatelessWidget { - final String viewId; - final FieldInfo fieldInfo; - const _FieldOperationList({ - required this.viewId, - required this.fieldInfo, - Key? key, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - return Column( - children: [ - Flex( - direction: Axis.horizontal, - children: [ - _actionCell(FieldAction.hide), - HSpace(GridSize.typeOptionSeparatorHeight), - _actionCell(FieldAction.duplicate), - ], - ), - VSpace(GridSize.typeOptionSeparatorHeight), - Flex( - direction: Axis.horizontal, - children: [ - _actionCell(FieldAction.delete), - HSpace(GridSize.typeOptionSeparatorHeight), - const Spacer(), - ], - ), - ], - ); - } - - Widget _actionCell(FieldAction action) { - bool enable = true; - - // If the field is primary, delete and duplicate are disabled. - if (fieldInfo.isPrimary) { - switch (action) { - case FieldAction.hide: - break; - case FieldAction.duplicate: - enable = false; - break; - case FieldAction.delete: - enable = false; - break; - } - } - - return Flexible( - child: SizedBox( - height: GridSize.popoverItemHeight, - child: FieldActionCell( - viewId: viewId, - fieldInfo: fieldInfo, - action: action, - enable: enable, - ), - ), - ); - } -} - -class FieldActionCell extends StatelessWidget { - final String viewId; - final FieldInfo fieldInfo; - final FieldAction action; - final bool enable; - - const FieldActionCell({ - required this.viewId, - required this.fieldInfo, - required this.action, - required this.enable, - Key? key, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - return FlowyButton( - hoverColor: AFThemeExtension.of(context).lightGreyHover, - disable: !enable, - text: FlowyText.medium( - action.title(), - color: enable - ? AFThemeExtension.of(context).textColor - : Theme.of(context).disabledColor, - ), - onTap: () => action.run(context, viewId, fieldInfo), - leftIcon: FlowySvg( - action.icon(), - color: enable - ? AFThemeExtension.of(context).textColor - : Theme.of(context).disabledColor, - ), - ); - } -} - -enum FieldAction { - hide, - duplicate, - delete, -} - -extension _FieldActionExtension on FieldAction { - FlowySvgData icon() { - switch (this) { - case FieldAction.hide: - return FlowySvgs.hide_s; - case FieldAction.duplicate: - return FlowySvgs.copy_s; - case FieldAction.delete: - return FlowySvgs.delete_s; - } - } - - String title() { - switch (this) { - case FieldAction.hide: - return LocaleKeys.grid_field_hide.tr(); - case FieldAction.duplicate: - return LocaleKeys.grid_field_duplicate.tr(); - case FieldAction.delete: - return LocaleKeys.grid_field_delete.tr(); - } - } - - void run(BuildContext context, String viewId, FieldInfo fieldInfo) { - switch (this) { - case FieldAction.hide: - context - .read() - .add(const FieldActionSheetEvent.hideField()); - break; - case FieldAction.duplicate: - PopoverContainer.of(context).close(); - - FieldBackendService( - viewId: viewId, - fieldId: fieldInfo.id, - ).duplicateField(); - - break; - case FieldAction.delete: - PopoverContainer.of(context).close(); - - NavigatorAlertDialog( - title: LocaleKeys.grid_field_deleteFieldPromptMessage.tr(), - confirm: () { - FieldBackendService( - viewId: viewId, - fieldId: fieldInfo.field.id, - ).deleteField(); - }, - ).show(context); - - break; - } - } -} diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/field_editor.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/field_editor.dart index bf571a3b74..57a21ea7fb 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/field_editor.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/field_editor.dart @@ -1,38 +1,41 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/database_view/application/field/field_controller.dart'; import 'package:appflowy/plugins/database_view/application/field/field_editor_bloc.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/application/field/type_option/type_option_context.dart'; -import 'package:appflowy_backend/protobuf/flowy-database2/field_settings_entities.pb.dart'; +import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart'; +import 'package:appflowy/plugins/database_view/grid/presentation/widgets/common/type_option_separator.dart'; +import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; +import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart'; +import 'package:appflowy_backend/protobuf/flowy-database2/field_settings_entities.pbenum.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; -import 'package:easy_localization/easy_localization.dart'; - -import 'package:flowy_infra_ui/style_widget/button.dart'; -import 'package:flowy_infra_ui/style_widget/text.dart'; -import 'package:flowy_infra_ui/style_widget/text_field.dart'; -import 'package:flowy_infra_ui/widget/spacing.dart'; -import 'package:appflowy_backend/log.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:appflowy/generated/locale_keys.g.dart'; -import '../../layout/sizes.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:styled_widget/styled_widget.dart'; + import 'field_type_option_editor.dart'; +enum FieldEditorPage { + general, + details, +} + class FieldEditor extends StatefulWidget { final String viewId; - final bool isGroupingField; - final Function(String)? onDeleted; - final Function(String)? onToggleVisibility; - final FieldTypeOptionLoader typeOptionLoader; - final FieldInfo? fieldInfo; + final FieldController fieldController; + final FieldPB field; + final FieldEditorPage initialPage; const FieldEditor({ - required this.viewId, - required this.typeOptionLoader, - this.fieldInfo, - this.isGroupingField = false, - this.onDeleted, - this.onToggleVisibility, super.key, + required this.viewId, + required this.field, + required this.fieldController, + this.initialPage = FieldEditorPage.details, }); @override @@ -40,6 +43,227 @@ class FieldEditor extends StatefulWidget { } class _FieldEditorState extends State { + late FieldEditorPage _currentPage; + late final TextEditingController textController; + + @override + void initState() { + super.initState(); + _currentPage = widget.initialPage; + textController = TextEditingController(text: widget.field.name); + } + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (_) => FieldEditorBloc( + viewId: widget.viewId, + field: widget.field, + fieldController: widget.fieldController, + loader: FieldTypeOptionLoader( + viewId: widget.viewId, + field: widget.field, + ), + )..add(const FieldEditorEvent.initial()), + child: _currentPage == FieldEditorPage.details + ? _fieldDetails() + : _fieldGeneral(), + ); + } + + Widget _fieldGeneral() { + return SizedBox( + width: 240, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + FieldNameTextField( + padding: const EdgeInsets.fromLTRB(4, 4, 4, 8), + textEditingController: textController, + ), + _EditFieldButton( + onTap: () { + setState(() => _currentPage = FieldEditorPage.details); + }, + ), + VSpace(GridSize.typeOptionSeparatorHeight), + _actionCell(FieldAction.toggleVisibility), + VSpace(GridSize.typeOptionSeparatorHeight), + _actionCell(FieldAction.duplicate), + VSpace(GridSize.typeOptionSeparatorHeight), + _actionCell(FieldAction.delete), + ], + ).padding(all: 8.0), + ); + } + + Widget _fieldDetails() { + return SizedBox( + width: 260, + child: FieldDetailsEditor( + viewId: widget.viewId, + textEditingController: textController, + ), + ); + } + + Widget _actionCell(FieldAction action) { + return BlocBuilder( + builder: (context, state) => FieldActionCell( + viewId: widget.viewId, + fieldInfo: state.field, + action: action, + ), + ); + } +} + +class _EditFieldButton extends StatelessWidget { + final void Function()? onTap; + const _EditFieldButton({this.onTap}); + + @override + Widget build(BuildContext context) { + return SizedBox( + height: GridSize.popoverItemHeight, + child: FlowyButton( + leftIcon: const FlowySvg(FlowySvgs.edit_s), + text: FlowyText.medium( + LocaleKeys.grid_field_editProperty.tr(), + ), + onTap: onTap, + ), + ); + } +} + +class FieldActionCell extends StatelessWidget { + final String viewId; + final FieldInfo fieldInfo; + final FieldAction action; + final PopoverMutex? popoverMutex; + + const FieldActionCell({ + super.key, + required this.viewId, + required this.fieldInfo, + required this.action, + this.popoverMutex, + }); + + @override + Widget build(BuildContext context) { + bool enable = true; + // If the field is primary, delete and duplicate are disabled. + if (fieldInfo.isPrimary && + (action == FieldAction.duplicate || action == FieldAction.delete)) { + enable = false; + } + + return FlowyButton( + disable: !enable, + text: FlowyText.medium( + action.title(fieldInfo), + color: enable ? null : Theme.of(context).disabledColor, + ), + onHover: (_) => popoverMutex?.close(), + onTap: () => action.run(context, viewId, fieldInfo), + leftIcon: FlowySvg( + action.icon(fieldInfo), + size: const Size.square(16), + color: enable ? null : Theme.of(context).disabledColor, + ), + ); + } +} + +enum FieldAction { + toggleVisibility, + duplicate, + delete, +} + +extension _FieldActionExtension on FieldAction { + FlowySvgData icon(FieldInfo fieldInfo) { + switch (this) { + case FieldAction.toggleVisibility: + if (fieldInfo.visibility != null && + fieldInfo.visibility == FieldVisibility.AlwaysHidden) { + return FlowySvgs.show_m; + } else { + return FlowySvgs.hide_s; + } + case FieldAction.duplicate: + return FlowySvgs.copy_s; + case FieldAction.delete: + return FlowySvgs.delete_s; + } + } + + String title(FieldInfo fieldInfo) { + switch (this) { + case FieldAction.toggleVisibility: + if (fieldInfo.visibility != null && + fieldInfo.visibility == FieldVisibility.AlwaysHidden) { + return LocaleKeys.grid_field_show.tr(); + } else { + return LocaleKeys.grid_field_hide.tr(); + } + case FieldAction.duplicate: + return LocaleKeys.grid_field_duplicate.tr(); + case FieldAction.delete: + return LocaleKeys.grid_field_delete.tr(); + } + } + + void run(BuildContext context, String viewId, FieldInfo fieldInfo) { + switch (this) { + case FieldAction.toggleVisibility: + PopoverContainer.of(context).close(); + context + .read() + .add(const FieldEditorEvent.toggleFieldVisibility()); + break; + case FieldAction.duplicate: + PopoverContainer.of(context).close(); + context + .read() + .add(const FieldEditorEvent.duplicateField()); + break; + case FieldAction.delete: + NavigatorAlertDialog( + title: LocaleKeys.grid_field_deleteFieldPromptMessage.tr(), + confirm: () { + FieldBackendService( + viewId: viewId, + fieldId: fieldInfo.id, + ).deleteField(); + }, + ).show(context); + PopoverContainer.of(context).close(); + break; + } + } +} + +class FieldDetailsEditor extends StatefulWidget { + final String viewId; + final TextEditingController textEditingController; + final Function()? onAction; + + const FieldDetailsEditor({ + super.key, + required this.viewId, + required this.textEditingController, + this.onAction, + }); + + @override + State createState() => _FieldDetailsEditorState(); +} + +class _FieldDetailsEditorState extends State { late PopoverMutex popoverMutex; @override @@ -56,71 +280,77 @@ class _FieldEditorState extends State { @override Widget build(BuildContext context) { - final bool requireSpace = widget.onDeleted != null || - widget.onToggleVisibility != null || - !widget.typeOptionLoader.field.isPrimary; - final List children = [ - FieldNameTextField(popoverMutex: popoverMutex), - if (requireSpace) const VSpace(4), - if (widget.onDeleted != null) _addDeleteFieldButton(), - if (widget.onToggleVisibility != null) _addHideFieldButton(), - if (!widget.typeOptionLoader.field.isPrimary) - FieldTypeOptionCell(popoverMutex: popoverMutex), + FieldNameTextField( + popoverMutex: popoverMutex, + padding: const EdgeInsets.fromLTRB(12.0, 4.0, 12.0, 0.0), + textEditingController: widget.textEditingController, + ), + const VSpace(8), + FieldTypeOptionCell(popoverMutex: popoverMutex), + const TypeOptionSeparator(), + _addFieldVisibilityToggleButton(), + _addDuplicateFieldButton(), + _addDeleteFieldButton(), ]; - return BlocProvider( - create: (context) { - return FieldEditorBloc( - isGroupField: widget.isGroupingField, - loader: widget.typeOptionLoader, - field: widget.typeOptionLoader.field, - )..add(const FieldEditorEvent.initial()); - }, - child: ListView.separated( - shrinkWrap: true, - itemCount: children.length, - itemBuilder: (context, index) => children[index], - separatorBuilder: (context, index) => - VSpace(GridSize.typeOptionSeparatorHeight), - padding: const EdgeInsets.symmetric(vertical: 12.0), + + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: children, ), ); } - Widget _addDeleteFieldButton() { + Widget _addFieldVisibilityToggleButton() { return BlocBuilder( builder: (context, state) { return Padding( - padding: const EdgeInsets.symmetric(horizontal: 12.0), - child: DeleteFieldButton( + padding: const EdgeInsets.fromLTRB(8.0, 2.0, 8.0, 0), + child: FieldActionCell( + viewId: widget.viewId, + fieldInfo: state.field, + action: FieldAction.toggleVisibility, popoverMutex: popoverMutex, - onDeleted: () { - state.field.fold( - () => Log.error('Can not delete the field'), - (field) => widget.onDeleted?.call(field.id), - ); - }, ), ); }, ); } - Widget _addHideFieldButton() { + Widget _addDeleteFieldButton() { return BlocBuilder( builder: (context, state) { + if (state.field.isPrimary) { + return const SizedBox.shrink(); + } return Padding( - padding: const EdgeInsets.symmetric(horizontal: 12.0), - child: FieldVisibilityToggleButton( - isFieldHidden: - widget.fieldInfo!.visibility == FieldVisibility.AlwaysHidden, + padding: const EdgeInsets.fromLTRB(8.0, 4.0, 8.0, 0), + child: FieldActionCell( + viewId: widget.viewId, + fieldInfo: state.field, + action: FieldAction.delete, + popoverMutex: popoverMutex, + ), + ); + }, + ); + } + + Widget _addDuplicateFieldButton() { + return BlocBuilder( + builder: (context, state) { + if (state.field.isPrimary) { + return const SizedBox.shrink(); + } + return Padding( + padding: const EdgeInsets.fromLTRB(8.0, 4.0, 8.0, 0), + child: FieldActionCell( + viewId: widget.viewId, + fieldInfo: state.field, + action: FieldAction.duplicate, popoverMutex: popoverMutex, - onTap: () { - state.field.fold( - () => Log.error('Can not hidden the field'), - (field) => widget.onToggleVisibility?.call(field.id), - ); - }, ), ); }, @@ -132,25 +362,25 @@ class FieldTypeOptionCell extends StatelessWidget { final PopoverMutex popoverMutex; const FieldTypeOptionCell({ - Key? key, + super.key, required this.popoverMutex, - }) : super(key: key); + }); @override Widget build(BuildContext context) { return BlocBuilder( - buildWhen: (p, c) => p.field != c.field, builder: (context, state) { - return state.field.fold( - () => const SizedBox.shrink(), - (fieldInfo) { - final dataController = - context.read().dataController; - return FieldTypeOptionEditor( - dataController: dataController, - popoverMutex: popoverMutex, - ); - }, + if (state.field.isPrimary) { + return const SizedBox.shrink(); + } + final dataController = + context.read().typeOptionController; + return Padding( + padding: const EdgeInsets.only(bottom: 2.0), + child: FieldTypeOptionEditor( + dataController: dataController, + popoverMutex: popoverMutex, + ), ); }, ); @@ -158,18 +388,21 @@ class FieldTypeOptionCell extends StatelessWidget { } class FieldNameTextField extends StatefulWidget { - final PopoverMutex popoverMutex; + final TextEditingController textEditingController; + final PopoverMutex? popoverMutex; + final EdgeInsets padding; const FieldNameTextField({ - required this.popoverMutex, - Key? key, - }) : super(key: key); + super.key, + required this.textEditingController, + this.popoverMutex, + this.padding = EdgeInsets.zero, + }); @override State createState() => _FieldNameTextFieldState(); } class _FieldNameTextFieldState extends State { - final textController = TextEditingController(); FocusNode focusNode = FocusNode(); @override @@ -178,11 +411,11 @@ class _FieldNameTextFieldState extends State { focusNode.addListener(() { if (focusNode.hasFocus) { - widget.popoverMutex.close(); + widget.popoverMutex?.close(); } }); - widget.popoverMutex.listenOnPopoverChanged(() { + widget.popoverMutex?.listenOnPopoverChanged(() { if (focusNode.hasFocus) { focusNode.unfocus(); } @@ -191,24 +424,18 @@ class _FieldNameTextFieldState extends State { @override Widget build(BuildContext context) { - return BlocBuilder( - builder: (context, state) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 12.0), - child: FlowyTextField( - focusNode: focusNode, - controller: textController, - onSubmitted: (String _) => PopoverContainer.of(context).close(), - text: state.name, - errorText: state.errorText.isEmpty ? null : state.errorText, - onChanged: (newName) { - context - .read() - .add(FieldEditorEvent.updateName(newName)); - }, - ), - ); - }, + return Padding( + padding: widget.padding, + child: FlowyTextField( + focusNode: focusNode, + controller: widget.textEditingController, + onSubmitted: (_) => PopoverContainer.of(context).close(), + onChanged: (newName) { + context + .read() + .add(FieldEditorEvent.renameField(newName)); + }, + ), ); } @@ -216,80 +443,10 @@ class _FieldNameTextFieldState extends State { void dispose() { focusNode.removeListener(() { if (focusNode.hasFocus) { - widget.popoverMutex.close(); + widget.popoverMutex?.close(); } }); focusNode.dispose(); super.dispose(); } } - -@visibleForTesting -class DeleteFieldButton extends StatelessWidget { - final PopoverMutex popoverMutex; - final VoidCallback? onDeleted; - - const DeleteFieldButton({ - required this.popoverMutex, - required this.onDeleted, - super.key, - }); - - @override - Widget build(BuildContext context) { - return BlocBuilder( - buildWhen: (previous, current) => previous != current, - builder: (context, state) { - final enable = !state.canDelete && !state.isGroupField; - final Widget button = FlowyButton( - disable: !enable, - text: FlowyText.medium( - LocaleKeys.grid_field_delete.tr(), - color: enable ? null : Theme.of(context).disabledColor, - ), - leftIcon: const FlowySvg(FlowySvgs.delete_s), - onTap: () { - if (enable) onDeleted?.call(); - }, - onHover: (_) => popoverMutex.close(), - ); - return SizedBox(height: GridSize.popoverItemHeight, child: button); - }, - ); - } -} - -@visibleForTesting -class FieldVisibilityToggleButton extends StatelessWidget { - final bool isFieldHidden; - final PopoverMutex popoverMutex; - final VoidCallback? onTap; - - const FieldVisibilityToggleButton({ - required this.isFieldHidden, - required this.popoverMutex, - required this.onTap, - super.key, - }); - - @override - Widget build(BuildContext context) { - return BlocBuilder( - buildWhen: (previous, current) => previous != current, - builder: (context, state) { - final Widget button = FlowyButton( - text: FlowyText.medium( - isFieldHidden - ? LocaleKeys.grid_field_show.tr() - : LocaleKeys.grid_field_hide.tr(), - ), - leftIcon: - FlowySvg(isFieldHidden ? FlowySvgs.show_m : FlowySvgs.hide_m), - onTap: onTap, - onHover: (_) => popoverMutex.close(), - ); - return SizedBox(height: GridSize.popoverItemHeight, child: button); - }, - ); - } -} diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/field_type_option_editor.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/field_type_option_editor.dart index a8d79dd92c..a99126b724 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/field_type_option_editor.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/field_type_option_editor.dart @@ -2,6 +2,7 @@ import 'dart:typed_data'; import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/plugins/database_view/application/field/field_type_option_edit_bloc.dart'; import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_data_controller.dart'; +import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:dartz/dartz.dart' show Either; @@ -10,7 +11,7 @@ import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import '../../layout/sizes.dart'; + import 'field_type_extension.dart'; import 'field_type_list.dart'; import 'type_option/builder.dart'; @@ -27,19 +28,17 @@ class FieldTypeOptionEditor extends StatelessWidget { final PopoverMutex popoverMutex; const FieldTypeOptionEditor({ + super.key, required TypeOptionController dataController, required this.popoverMutex, - Key? key, - }) : _dataController = dataController, - super(key: key); + }) : _dataController = dataController; @override Widget build(BuildContext context) { return BlocProvider( create: (context) { - final bloc = FieldTypeOptionEditBloc(_dataController); - bloc.add(const FieldTypeOptionEditEvent.initial()); - return bloc; + return FieldTypeOptionEditBloc(_dataController) + ..add(const FieldTypeOptionEditEvent.initial()); }, child: BlocBuilder( builder: (context, state) { @@ -53,8 +52,8 @@ class FieldTypeOptionEditor extends StatelessWidget { if (typeOptionWidget != null) typeOptionWidget, ]; - return ListView( - shrinkWrap: true, + return Column( + mainAxisSize: MainAxisSize.min, children: children, ); }, @@ -74,22 +73,30 @@ class FieldTypeOptionEditor extends StatelessWidget { } } -class SwitchFieldButton extends StatelessWidget { +class SwitchFieldButton extends StatefulWidget { final PopoverMutex popoverMutex; const SwitchFieldButton({ + super.key, required this.popoverMutex, - Key? key, - }) : super(key: key); + }); + + @override + State createState() => _SwitchFieldButtonState(); +} + +class _SwitchFieldButtonState extends State { + final PopoverController _popoverController = PopoverController(); @override Widget build(BuildContext context) { - final widget = AppFlowyPopover( + final child = AppFlowyPopover( constraints: BoxConstraints.loose(const Size(460, 540)), - asBarrier: true, - triggerActions: PopoverTriggerFlags.click, - mutex: popoverMutex, + triggerActions: PopoverTriggerFlags.hover, + mutex: widget.popoverMutex, + controller: _popoverController, offset: const Offset(8, 0), - popupBuilder: (popOverContext) { + margin: const EdgeInsets.all(8), + popupBuilder: (BuildContext popoverContext) { return FieldTypeList( onSelectField: (newFieldType) { context @@ -99,20 +106,21 @@ class SwitchFieldButton extends StatelessWidget { ); }, child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 12.0), + padding: const EdgeInsets.symmetric(horizontal: 8.0), child: _buildMoreButton(context), ), ); return SizedBox( height: GridSize.popoverItemHeight, - child: widget, + child: child, ); } Widget _buildMoreButton(BuildContext context) { final bloc = context.read(); return FlowyButton( + onTap: () => _popoverController.show(), text: FlowyText.medium( bloc.state.field.fieldType.title(), ), diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/grid_header.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/grid_header.dart index d580496bef..ea822601c7 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/grid_header.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/grid_header.dart @@ -1,7 +1,7 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/database_view/application/field/field_controller.dart'; -import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart'; +import 'package:appflowy/plugins/database_view/grid/application/grid_bloc.dart'; import 'package:appflowy/plugins/database_view/grid/application/grid_header_bloc.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/mobile_field_cell.dart'; import 'package:appflowy_backend/log.dart'; @@ -22,15 +22,13 @@ import 'field_cell.dart'; class GridHeaderSliverAdaptor extends StatefulWidget { final String viewId; - final FieldController fieldController; final ScrollController anchorScrollController; const GridHeaderSliverAdaptor({ required this.viewId, - required this.fieldController, required this.anchorScrollController, - Key? key, - }) : super(key: key); + super.key, + }); @override State createState() => @@ -40,11 +38,13 @@ class GridHeaderSliverAdaptor extends StatefulWidget { class _GridHeaderSliverAdaptorState extends State { @override Widget build(BuildContext context) { + final fieldController = + context.read().databaseController.fieldController; return BlocProvider( create: (context) { return GridHeaderBloc( viewId: widget.viewId, - fieldController: widget.fieldController, + fieldController: fieldController, )..add(const GridHeaderEvent.initial()); }, child: BlocBuilder( @@ -54,7 +54,10 @@ class _GridHeaderSliverAdaptorState extends State { return SingleChildScrollView( scrollDirection: Axis.horizontal, controller: widget.anchorScrollController, - child: _GridHeader(viewId: widget.viewId), + child: _GridHeader( + viewId: widget.viewId, + fieldController: fieldController, + ), ); }, ), @@ -64,7 +67,8 @@ class _GridHeaderSliverAdaptorState extends State { class _GridHeader extends StatefulWidget { final String viewId; - const _GridHeader({Key? key, required this.viewId}) : super(key: key); + final FieldController fieldController; + const _GridHeader({required this.viewId, required this.fieldController}); @override State<_GridHeader> createState() => _GridHeaderState(); @@ -98,6 +102,7 @@ class _GridHeaderState extends State<_GridHeader> { key: _getKeyById(fieldInfo.id), viewId: widget.viewId, fieldInfo: fieldInfo, + fieldController: widget.fieldController, ) : MobileFieldButton( key: _getKeyById(fieldInfo.id), @@ -159,8 +164,9 @@ class _CellLeading extends StatelessWidget { } class _CellTrailing extends StatelessWidget { + const _CellTrailing({required this.viewId}); + final String viewId; - const _CellTrailing({required this.viewId, Key? key}) : super(key: key); @override Widget build(BuildContext context) { @@ -179,7 +185,10 @@ class _CellTrailing extends StatelessWidget { class CreateFieldButton extends StatefulWidget { final String viewId; - const CreateFieldButton({required this.viewId, Key? key}) : super(key: key); + const CreateFieldButton({ + super.key, + required this.viewId, + }); @override State createState() => _CreateFieldButtonState(); @@ -191,6 +200,8 @@ class _CreateFieldButtonState extends State { @override Widget build(BuildContext context) { + final fieldController = + context.read().databaseController.fieldController; return AppFlowyPopover( controller: popoverController, direction: PopoverDirection.bottomWithRightAligned, @@ -222,49 +233,13 @@ class _CreateFieldButtonState extends State { }, leftIcon: const FlowySvg(FlowySvgs.add_s), ), - popupBuilder: (BuildContext popover) { + popupBuilder: (BuildContext popoverContext) { return FieldEditor( viewId: widget.viewId, - typeOptionLoader: FieldTypeOptionLoader( - viewId: widget.viewId, - field: typeOption.field_2, - ), + fieldController: fieldController, + field: typeOption.field_2, ); }, ); } } - -class SliverHeaderDelegateImplementation - extends SliverPersistentHeaderDelegate { - final String gridId; - final List fields; - - SliverHeaderDelegateImplementation({ - required this.gridId, - required this.fields, - }); - - @override - Widget build( - BuildContext context, - double shrinkOffset, - bool overlapsContent, - ) { - return _GridHeader(viewId: gridId); - } - - @override - double get maxExtent => GridSize.headerHeight; - - @override - double get minExtent => GridSize.headerHeight; - - @override - bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) { - if (oldDelegate is SliverHeaderDelegateImplementation) { - return fields.length != oldDelegate.fields.length; - } - return true; - } -} diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/mobile_field_cell.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/mobile_field_cell.dart index 996de3ad03..7561bb12ab 100755 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/mobile_field_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/mobile_field_cell.dart @@ -1,9 +1,13 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart'; +import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet_database_field_editor.dart'; import 'package:appflowy/plugins/database_view/application/field/field_info.dart'; +import 'package:appflowy/plugins/database_view/grid/application/grid_bloc.dart'; import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'field_type_extension.dart'; @@ -29,6 +33,8 @@ class MobileFieldButton extends StatelessWidget { color: Theme.of(context).dividerColor, width: 1.0, ); + final fieldController = + context.read().databaseController.fieldController; return Container( width: field.fieldSettings!.width.toDouble(), decoration: BoxDecoration( @@ -36,7 +42,14 @@ class MobileFieldButton extends StatelessWidget { ), child: TextButton( onLongPress: () { - debugPrint("gimme the bottom drawer"); + showMobileBottomSheet( + context: context, + builder: (context) => MobileDBBottomSheetFieldEditor( + viewId: viewId, + field: field.field, + fieldController: fieldController, + ), + ); }, style: TextButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 8), diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/type_option/number.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/type_option/number.dart index 1a5a918828..2b88aa2e76 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/type_option/number.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/type_option/number.dart @@ -42,11 +42,12 @@ class NumberTypeOptionWidgetBuilder extends TypeOptionWidgetBuilder { class NumberTypeOptionWidget extends TypeOptionWidget { final NumberTypeOptionContext typeOptionContext; final PopoverMutex popoverMutex; + const NumberTypeOptionWidget({ + super.key, required this.typeOptionContext, required this.popoverMutex, - Key? key, - }) : super(key: key); + }); @override Widget build(BuildContext context) { @@ -60,7 +61,6 @@ class NumberTypeOptionWidget extends TypeOptionWidget { final selectNumUnitButton = SizedBox( height: GridSize.popoverItemHeight, child: FlowyButton( - margin: GridSize.typeOptionContentInsets, rightIcon: const FlowySvg(FlowySvgs.more_s), text: FlowyText.regular( state.typeOption.format.title(), @@ -72,13 +72,15 @@ class NumberTypeOptionWidget extends TypeOptionWidget { padding: const EdgeInsets.only(left: 6), height: GridSize.popoverItemHeight, alignment: Alignment.centerLeft, - child: FlowyText.medium( - color: Theme.of(context).colorScheme.outline, + child: FlowyText.regular( LocaleKeys.grid_field_numberFormat.tr(), + color: Theme.of(context).hintColor, + fontSize: 11, ), ); + return Padding( - padding: const EdgeInsets.symmetric(horizontal: 12), + padding: const EdgeInsets.symmetric(horizontal: 8), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -87,7 +89,7 @@ class NumberTypeOptionWidget extends TypeOptionWidget { mutex: popoverMutex, triggerActions: PopoverTriggerFlags.hover | PopoverTriggerFlags.click, - offset: const Offset(8, 0), + offset: const Offset(16, 0), constraints: BoxConstraints.loose(const Size(460, 440)), margin: EdgeInsets.zero, child: selectNumUnitButton, diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/type_option/select_option.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/type_option/select_option.dart index e3399e682c..8eea0c05da 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/type_option/select_option.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/type_option/select_option.dart @@ -3,7 +3,6 @@ import 'package:appflowy/plugins/database_view/application/field/type_option/sel import 'package:appflowy_backend/protobuf/flowy-database2/select_option.pb.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; -import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -40,23 +39,21 @@ class SelectOptionTypeOptionWidget extends StatelessWidget { BlocBuilder( builder: (context, state) { final List children = [ - const TypeOptionSeparator(), - const OptionTitle(), - if (state.isEditingOption) + const TypeOptionSeparator(spacing: 8), + const _OptionTitle(), + const VSpace(4), + if (state.isEditingOption) ...[ CreateOptionTextField(popoverMutex: popoverMutex), - if (state.options.isNotEmpty && state.isEditingOption) - const VSpace(10), - if (state.options.isEmpty && !state.isEditingOption) + const VSpace(4), + ] else const _AddOptionButton(), + const VSpace(4), _OptionList(popoverMutex: popoverMutex), ]; - return ListView.builder( - shrinkWrap: true, - itemCount: children.length, - itemBuilder: (context, index) { - return children[index]; - }, + return Column( + mainAxisSize: MainAxisSize.min, + children: children, ); }, ), @@ -64,31 +61,22 @@ class SelectOptionTypeOptionWidget extends StatelessWidget { } } -class OptionTitle extends StatelessWidget { - const OptionTitle({Key? key}) : super(key: key); +class _OptionTitle extends StatelessWidget { + const _OptionTitle(); @override Widget build(BuildContext context) { return BlocBuilder( builder: (context, state) { - final List children = [ - Padding( - padding: const EdgeInsets.only(left: 9), - child: FlowyText.medium( - LocaleKeys.grid_field_optionTitle.tr(), - ), - ), - ]; - if (state.options.isNotEmpty && !state.isEditingOption) { - children.add(const Spacer()); - children.add(const _OptionTitleButton()); - } - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 12.0), - child: SizedBox( - height: GridSize.popoverItemHeight, - child: Row(children: children), + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Align( + alignment: AlignmentDirectional.centerStart, + child: FlowyText.regular( + LocaleKeys.grid_field_optionTitle.tr(), + fontSize: 11, + color: Theme.of(context).hintColor, + ), ), ); }, @@ -96,29 +84,6 @@ class OptionTitle extends StatelessWidget { } } -class _OptionTitleButton extends StatelessWidget { - const _OptionTitleButton({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return SizedBox( - width: 100, - height: 26, - child: FlowyButton( - text: FlowyText.medium( - LocaleKeys.grid_field_addOption.tr(), - textAlign: TextAlign.center, - ), - onTap: () { - context - .read() - .add(const SelectOptionTypeOptionEvent.addingOption()); - }, - ), - ); - } -} - class _OptionList extends StatelessWidget { final PopoverMutex? popoverMutex; const _OptionList({Key? key, this.popoverMutex}) : super(key: key); @@ -140,7 +105,6 @@ class _OptionList extends StatelessWidget { return ListView.separated( shrinkWrap: true, - controller: ScrollController(), separatorBuilder: (context, index) { return VSpace(GridSize.typeOptionSeparatorHeight); }, @@ -187,7 +151,7 @@ class _OptionCellState extends State<_OptionCell> { @override Widget build(BuildContext context) { final child = SizedBox( - height: GridSize.popoverItemHeight, + height: 28, child: SelectOptionTagCell( option: widget.option, onSelected: (SelectOptionPB pb) { @@ -212,7 +176,7 @@ class _OptionCellState extends State<_OptionCell> { asBarrier: true, constraints: BoxConstraints.loose(const Size(460, 470)), child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 12.0), + padding: const EdgeInsets.symmetric(horizontal: 8.0), child: child, ), popupBuilder: (BuildContext popoverContext) { @@ -243,24 +207,19 @@ class _AddOptionButton extends StatelessWidget { @override Widget build(BuildContext context) { return Padding( - padding: const EdgeInsets.symmetric(horizontal: 12.0), + padding: const EdgeInsets.symmetric(horizontal: 8.0), child: SizedBox( height: GridSize.popoverItemHeight, child: FlowyButton( - hoverColor: AFThemeExtension.of(context).lightGreyHover, text: FlowyText.medium( LocaleKeys.grid_field_addSelectOption.tr(), - color: AFThemeExtension.of(context).textColor, ), onTap: () { context .read() .add(const SelectOptionTypeOptionEvent.addingOption()); }, - leftIcon: FlowySvg( - FlowySvgs.add_s, - color: Theme.of(context).iconTheme.color, - ), + leftIcon: const FlowySvg(FlowySvgs.add_s), ), ), ); @@ -303,7 +262,7 @@ class _CreateOptionTextFieldState extends State { builder: (context, state) { final text = state.newOptionName.foldRight("", (a, previous) => a); return Padding( - padding: const EdgeInsets.symmetric(horizontal: 12.0), + padding: const EdgeInsets.symmetric(horizontal: 14.0), child: FlowyTextField( autoClearWhenDone: true, text: text, diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/type_option/select_option_editor.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/type_option/select_option_editor.dart index 79a420c0ae..03354e9ec0 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/type_option/select_option_editor.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/type_option/select_option_editor.dart @@ -62,20 +62,14 @@ class SelectOptionTypeOptionEditor extends StatelessWidget { ), const VSpace(10), const _DeleteTag(), + const TypeOptionSeparator(), + SelectOptionColorList( + selectedColor: state.option.color, + onSelectedColor: (color) => context + .read() + .add(EditSelectOptionEvent.updateColor(color)), + ), ]; - - if (showOptions) { - cells.add(const TypeOptionSeparator()); - cells.add( - SelectOptionColorList( - selectedColor: state.option.color, - onSelectedColor: (color) => context - .read() - .add(EditSelectOptionEvent.updateColor(color)), - ), - ); - } - return SizedBox( width: 180, child: ListView.builder( @@ -188,7 +182,6 @@ class SelectOptionColorList extends StatelessWidget { ), ListView.separated( shrinkWrap: true, - controller: ScrollController(), separatorBuilder: (context, index) { return VSpace(GridSize.typeOptionSeparatorHeight); }, diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/type_option/single_select.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/type_option/single_select.dart index 4b8db69941..1ba4ee1eaa 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/type_option/single_select.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/type_option/single_select.dart @@ -44,7 +44,6 @@ class SingleSelectTypeOptionWidget extends TypeOptionWidget { }, popoverMutex: popoverMutex, typeOptionAction: selectOptionAction, - // key: ValueKey(state.typeOption.hashCode), ); } } diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/select_option_cell/extension.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/select_option_cell/extension.dart index 4349616c49..19955b9342 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/select_option_cell/extension.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/select_option_cell/extension.dart @@ -91,7 +91,7 @@ class SelectOptionTag extends StatelessWidget { @override Widget build(BuildContext context) { EdgeInsets padding = - const EdgeInsets.symmetric(vertical: 2, horizontal: 8.0); + const EdgeInsets.symmetric(vertical: 1, horizontal: 8.0); if (onRemove != null) { padding = padding.copyWith(right: 2.0); } diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/select_option_cell/select_option_editor.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/select_option_cell/select_option_editor.dart index 64cb97b2da..905a021bcb 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/select_option_cell/select_option_editor.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/select_option_cell/select_option_editor.dart @@ -109,7 +109,6 @@ class _OptionList extends StatelessWidget { return ListView.separated( shrinkWrap: true, - controller: ScrollController(), itemCount: cells.length, separatorBuilder: (_, __) => VSpace(GridSize.typeOptionSeparatorHeight), @@ -302,7 +301,7 @@ class _SelectOptionCellState extends State<_SelectOptionCell> { @override Widget build(BuildContext context) { final child = SizedBox( - height: GridSize.popoverItemHeight, + height: 28, child: SelectOptionTagCell( option: widget.option, onSelected: (option) { @@ -328,7 +327,6 @@ class _SelectOptionCellState extends State<_SelectOptionCell> { FlowyIconButton( onPressed: () => _popoverController.show(), iconPadding: const EdgeInsets.symmetric(horizontal: 6.0), - // If hover color is none, it will use secondary color from the theme, we use [Colors.transparent] to remove the hover color hoverColor: Colors.transparent, icon: FlowySvg( FlowySvgs.details_s, diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/row_detail.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/row_detail.dart index c773fe5fc0..73ee188f66 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/row_detail.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/row_detail.dart @@ -1,3 +1,4 @@ +import 'package:appflowy/plugins/database_view/application/field/field_controller.dart'; import 'package:appflowy/plugins/database_view/application/row/row_controller.dart'; import 'package:appflowy/plugins/database_view/grid/application/row/row_detail_bloc.dart'; import 'package:appflowy/plugins/database_view/widgets/row/row_document.dart'; @@ -11,11 +12,13 @@ import 'row_banner.dart'; import 'row_property.dart'; class RowDetailPage extends StatefulWidget with FlowyOverlayDelegate { + final FieldController fieldController; final RowController rowController; final GridCellBuilder cellBuilder; const RowDetailPage({ super.key, + required this.fieldController, required this.rowController, required this.cellBuilder, }); @@ -56,6 +59,7 @@ class _RowDetailPageState extends State { child: RowPropertyList( cellBuilder: widget.cellBuilder, viewId: widget.rowController.viewId, + fieldController: widget.fieldController, ), ), const VSpace(20), diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/row_property.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/row_property.dart index 3f2b47b05f..f737e33e23 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/row_property.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/row_property.dart @@ -3,14 +3,13 @@ import 'dart:io'; import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart'; -import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart'; +import 'package:appflowy/plugins/database_view/application/field/field_controller.dart'; import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_service.dart'; import 'package:appflowy/plugins/database_view/grid/application/row/row_detail_bloc.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_cell.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_editor.dart'; import 'package:appflowy/plugins/database_view/widgets/row/cells/cells.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/block_action_button.dart'; -import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; @@ -28,12 +27,15 @@ import 'cell_builder.dart'; /// [RowDetailPage]. class RowPropertyList extends StatelessWidget { final String viewId; + final FieldController fieldController; final GridCellBuilder cellBuilder; + const RowPropertyList({ + super.key, required this.viewId, + required this.fieldController, required this.cellBuilder, - Key? key, - }) : super(key: key); + }); @override Widget build(BuildContext context) { @@ -46,6 +48,7 @@ class RowPropertyList extends StatelessWidget { key: ValueKey('row_detail_${cell.fieldId}'), cellContext: cell, cellBuilder: cellBuilder, + fieldController: fieldController, index: index, ), ) @@ -99,7 +102,10 @@ class RowPropertyList extends StatelessWidget { padding: EdgeInsets.only(bottom: 4.0), child: ToggleHiddenFieldsVisibilityButton(), ), - CreateRowFieldButton(viewId: viewId), + CreateRowFieldButton( + viewId: viewId, + fieldController: fieldController, + ), ], ), ), @@ -113,14 +119,16 @@ class RowPropertyList extends StatelessWidget { class _PropertyCell extends StatefulWidget { final DatabaseCellContext cellContext; final GridCellBuilder cellBuilder; - + final FieldController fieldController; final int index; + const _PropertyCell({ + super.key, required this.cellContext, required this.cellBuilder, - Key? key, + required this.fieldController, required this.index, - }) : super(key: key); + }); @override State createState() => _PropertyCellState(); @@ -219,30 +227,8 @@ class _PropertyCellState extends State<_PropertyCell> { Widget buildFieldEditor() { return FieldEditor( viewId: widget.cellContext.viewId, - fieldInfo: widget.cellContext.fieldInfo, - isGroupingField: widget.cellContext.fieldInfo.isGroupField, - typeOptionLoader: FieldTypeOptionLoader( - viewId: widget.cellContext.viewId, - field: widget.cellContext.fieldInfo.field, - ), - onToggleVisibility: (fieldId) { - _popoverController.close(); - context - .read() - .add(RowDetailEvent.toggleFieldVisibility(fieldId)); - }, - onDeleted: (fieldId) { - _popoverController.close(); - - NavigatorAlertDialog( - title: LocaleKeys.grid_field_deleteFieldPromptMessage.tr(), - confirm: () { - context - .read() - .add(RowDetailEvent.deleteField(fieldId)); - }, - ).show(context); - }, + field: widget.cellContext.fieldInfo.field, + fieldController: widget.fieldController, ); } } @@ -342,8 +328,13 @@ class ToggleHiddenFieldsVisibilityButton extends StatelessWidget { class CreateRowFieldButton extends StatefulWidget { final String viewId; + final FieldController fieldController; - const CreateRowFieldButton({required this.viewId, super.key}); + const CreateRowFieldButton({ + super.key, + required this.viewId, + required this.fieldController, + }); @override State createState() => _CreateRowFieldButtonState(); @@ -394,24 +385,11 @@ class _CreateRowFieldButtonState extends State { ), ), ), - popupBuilder: (BuildContext popOverContext) { + popupBuilder: (BuildContext popoverContext) { return FieldEditor( viewId: widget.viewId, - typeOptionLoader: FieldTypeOptionLoader( - viewId: widget.viewId, - field: typeOption.field_2, - ), - onDeleted: (fieldId) { - popoverController.close(); - NavigatorAlertDialog( - title: LocaleKeys.grid_field_deleteFieldPromptMessage.tr(), - confirm: () { - context - .read() - .add(RowDetailEvent.deleteField(fieldId)); - }, - ).show(context); - }, + field: typeOption.field_2, + fieldController: widget.fieldController, ); }, ); diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/setting/setting_property_list.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/setting/setting_property_list.dart index f2fb63970e..17e3e97e2e 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/setting/setting_property_list.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/setting/setting_property_list.dart @@ -3,22 +3,19 @@ import 'dart:io'; import 'package:appflowy/generated/flowy_svgs.g.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/type_option/type_option_context.dart'; import 'package:appflowy/plugins/database_view/application/setting/property_bloc.dart'; +import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart'; +import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_editor.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_type_extension.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:collection/collection.dart'; - import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:styled_widget/styled_widget.dart'; -import '../../grid/presentation/layout/sizes.dart'; -import '../../grid/presentation/widgets/header/field_editor.dart'; - class DatabasePropertyList extends StatefulWidget { final String viewId; final FieldController fieldController; @@ -49,6 +46,7 @@ class _DatabasePropertyListState extends State { return DatabasePropertyCell( key: ValueKey(field.id), viewId: widget.viewId, + fieldController: widget.fieldController, fieldInfo: field, popoverMutex: _popoverMutex, index: index, @@ -93,6 +91,7 @@ class _DatabasePropertyListState extends State { @visibleForTesting class DatabasePropertyCell extends StatefulWidget { + final FieldController fieldController; final FieldInfo fieldInfo; final String viewId; final PopoverMutex popoverMutex; @@ -104,6 +103,7 @@ class DatabasePropertyCell extends StatefulWidget { required this.viewId, required this.popoverMutex, required this.index, + required this.fieldController, }); @override @@ -120,6 +120,7 @@ class _DatabasePropertyCellState extends State { visiblity != null && visiblity != FieldVisibility.AlwaysHidden ? FlowySvgs.show_m : FlowySvgs.hide_m, + size: const Size.square(16), color: Theme.of(context).iconTheme.color, ); @@ -182,7 +183,7 @@ class _DatabasePropertyCellState extends State { ), ); }, - icon: visibleIcon.padding(all: 4.0), + icon: visibleIcon, ), onTap: () => _popoverController.show(), ).padding(horizontal: 6.0), @@ -190,11 +191,8 @@ class _DatabasePropertyCellState extends State { popupBuilder: (BuildContext context) { return FieldEditor( viewId: widget.viewId, - fieldInfo: widget.fieldInfo, - typeOptionLoader: FieldTypeOptionLoader( - viewId: widget.viewId, - field: widget.fieldInfo.field, - ), + field: widget.fieldInfo.field, + fieldController: widget.fieldController, ); }, ); diff --git a/frontend/appflowy_flutter/lib/startup/tasks/generate_router.dart b/frontend/appflowy_flutter/lib/startup/tasks/generate_router.dart index de0e9a20be..b73739427f 100644 --- a/frontend/appflowy_flutter/lib/startup/tasks/generate_router.dart +++ b/frontend/appflowy_flutter/lib/startup/tasks/generate_router.dart @@ -454,10 +454,12 @@ GoRoute _mobileCardDetailScreenRoute() { pageBuilder: (context, state) { final args = state.extra as Map; final rowController = args[MobileCardDetailScreen.argRowController]; + final fieldController = args[MobileCardDetailScreen.argFieldController]; return MaterialPage( child: MobileCardDetailScreen( rowController: rowController, + fieldController: fieldController, ), ); }, @@ -471,6 +473,7 @@ GoRoute _mobileCardPropertyEditScreenRoute() { pageBuilder: (context, state) { final args = state.extra as Map; final cellContext = args[CardPropertyEditScreen.argCellContext]; + final fieldController = args[CardPropertyEditScreen.argFieldController]; final rowDetailBloc = args[CardPropertyEditScreen.argRowDetailBloc]; return MaterialPage( @@ -478,6 +481,7 @@ GoRoute _mobileCardPropertyEditScreenRoute() { value: rowDetailBloc as RowDetailBloc, child: CardPropertyEditScreen( cellContext: cellContext, + fieldController: fieldController, ), ), ); @@ -508,11 +512,16 @@ GoRoute _mobileCreateRowFieldScreenRoute() { pageBuilder: (context, state) { final args = state.extra as Map; final viewId = args[MobileCreateRowFieldScreen.argViewId]; + final fieldController = + args[MobileCreateRowFieldScreen.argFieldController]; final typeOption = args[MobileCreateRowFieldScreen.argTypeOption]; return MaterialPage( - child: - MobileCreateRowFieldScreen(viewId: viewId, typeOption: typeOption), + child: MobileCreateRowFieldScreen( + viewId: viewId, + typeOption: typeOption, + fieldController: fieldController, + ), fullscreenDialog: true, ); }, diff --git a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/button.dart b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/button.dart index 176cc7c3db..4d803f0126 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/button.dart +++ b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/button.dart @@ -185,7 +185,7 @@ class FlowyTextButton extends StatelessWidget { List children = []; if (heading != null) { children.add(heading!); - children.add(const HSpace(6)); + children.add(const HSpace(8)); } children.add( FlowyText( diff --git a/frontend/appflowy_flutter/test/bloc_test/board_test/create_or_edit_field_test.dart b/frontend/appflowy_flutter/test/bloc_test/board_test/create_or_edit_field_test.dart index 6b851f0207..93bf088151 100644 --- a/frontend/appflowy_flutter/test/bloc_test/board_test/create_or_edit_field_test.dart +++ b/frontend/appflowy_flutter/test/bloc_test/board_test/create_or_edit_field_test.dart @@ -40,13 +40,14 @@ void main() { ); final editorBloc = FieldEditorBloc( - isGroupField: fieldInfo.isGroupField, + viewId: context.gridView.id, loader: loader, field: fieldInfo.field, + fieldController: context.fieldController, )..add(const FieldEditorEvent.initial()); await boardResponseFuture(); - editorBloc.add(const FieldEditorEvent.updateName('Hello world')); + editorBloc.add(const FieldEditorEvent.renameField('Hello world')); await boardResponseFuture(); // assert the groups were not changed diff --git a/frontend/appflowy_flutter/test/bloc_test/board_test/group_by_unsupport_field_test.dart b/frontend/appflowy_flutter/test/bloc_test/board_test/group_by_unsupport_field_test.dart index 44a6307e9b..8349de53e3 100644 --- a/frontend/appflowy_flutter/test/bloc_test/board_test/group_by_unsupport_field_test.dart +++ b/frontend/appflowy_flutter/test/bloc_test/board_test/group_by_unsupport_field_test.dart @@ -29,13 +29,10 @@ void main() { build: () => editorBloc, wait: boardResponseDuration(), act: (bloc) async { - bloc.add(const FieldEditorEvent.switchToField(FieldType.RichText)); + bloc.add(const FieldEditorEvent.switchFieldType(FieldType.RichText)); }, verify: (bloc) { - bloc.state.field.fold( - () => throw Exception(), - (field) => field.fieldType == FieldType.RichText, - ); + assert(bloc.state.field.fieldType == FieldType.RichText); }, ); blocTest( diff --git a/frontend/appflowy_flutter/test/bloc_test/board_test/util.dart b/frontend/appflowy_flutter/test/bloc_test/board_test/util.dart index 0e4b92b9e8..edc31623f3 100644 --- a/frontend/appflowy_flutter/test/bloc_test/board_test/util.dart +++ b/frontend/appflowy_flutter/test/bloc_test/board_test/util.dart @@ -88,7 +88,8 @@ class BoardTestContext { ); final editorBloc = FieldEditorBloc( - isGroupField: fieldInfo.isGroupField, + viewId: databaseController.viewId, + fieldController: fieldController, loader: loader, field: fieldInfo.field, ); @@ -126,10 +127,11 @@ class BoardTestContext { } Future createField(FieldType fieldType) async { - final editorBloc = await createFieldEditor(viewId: gridView.id) - ..add(const FieldEditorEvent.initial()); + final editorBloc = + await createFieldEditor(databaseController: _boardDataController) + ..add(const FieldEditorEvent.initial()); await gridResponseFuture(); - editorBloc.add(FieldEditorEvent.switchToField(fieldType)); + editorBloc.add(FieldEditorEvent.switchFieldType(fieldType)); await gridResponseFuture(); return Future(() => editorBloc); } diff --git a/frontend/appflowy_flutter/test/bloc_test/grid_test/field/edit_field_test.dart b/frontend/appflowy_flutter/test/bloc_test/grid_test/field/edit_field_test.dart index f47e5181ec..e22fa8e91a 100644 --- a/frontend/appflowy_flutter/test/bloc_test/grid_test/field/edit_field_test.dart +++ b/frontend/appflowy_flutter/test/bloc_test/grid_test/field/edit_field_test.dart @@ -13,7 +13,8 @@ Future createEditorBloc(AppFlowyGridTest gridTest) async { ); return FieldEditorBloc( - isGroupField: fieldInfo.isGroupField, + viewId: context.gridView.id, + fieldController: context.fieldController, loader: loader, field: fieldInfo.field, )..add(const FieldEditorEvent.initial()); @@ -27,67 +28,21 @@ void main() { }); test('rename field', () async { - final editorBloc = await makeEditorBloc(gridTest); - editorBloc.add(const FieldEditorEvent.updateName('Hello world')); - await gridResponseFuture(); + final editorBloc = await createEditorBloc(gridTest); + editorBloc.add(const FieldEditorEvent.renameField('Hello world')); - editorBloc.state.field.fold( - () => throw Exception("The field should not be none"), - (field) { - assert(field.name == 'Hello world'); - }, - ); + await gridResponseFuture(); + expect(editorBloc.state.field.name, equals("Hello world")); }); test('switch to text field', () async { - final editorBloc = await makeEditorBloc(gridTest); + final editorBloc = await createEditorBloc(gridTest); - editorBloc.add(const FieldEditorEvent.switchToField(FieldType.RichText)); + editorBloc.add(const FieldEditorEvent.switchFieldType(FieldType.RichText)); await gridResponseFuture(); - editorBloc.state.field.fold( - () => throw Exception("The field should not be none"), - (field) { - // The default length of the fields is 3. The length of the fields - // should not change after switching to other field type - // assert(gridTest.fieldContexts.length == 3); - assert(field.fieldType == FieldType.RichText); - }, - ); - }); - - test('delete field', () async { - final editorBloc = await makeEditorBloc(gridTest); - editorBloc.add(const FieldEditorEvent.switchToField(FieldType.RichText)); - await gridResponseFuture(); - - editorBloc.state.field.fold( - () => throw Exception("The field should not be none"), - (field) { - // The default length of the fields is 3. The length of the fields - // should not change after switching to other field type - // assert(gridTest.fieldContexts.length == 3); - assert(field.fieldType == FieldType.RichText); - }, - ); + // The default length of the fields is 3. The length of the fields + // should not change after switching to other field type + expect(editorBloc.state.field.fieldType, equals(FieldType.RichText)); }); } - -Future makeEditorBloc(AppFlowyGridTest gridTest) async { - final context = await gridTest.createTestGrid(); - final fieldInfo = context.singleSelectFieldContext(); - final loader = FieldTypeOptionLoader( - viewId: context.gridView.id, - field: fieldInfo.field, - ); - - final editorBloc = FieldEditorBloc( - isGroupField: fieldInfo.isGroupField, - loader: loader, - field: fieldInfo.field, - )..add(const FieldEditorEvent.initial()); - - await gridResponseFuture(); - - return editorBloc; -} diff --git a/frontend/appflowy_flutter/test/bloc_test/grid_test/grid_header_bloc_test.dart b/frontend/appflowy_flutter/test/bloc_test/grid_test/grid_header_bloc_test.dart deleted file mode 100644 index 29bb34d7b6..0000000000 --- a/frontend/appflowy_flutter/test/bloc_test/grid_test/grid_header_bloc_test.dart +++ /dev/null @@ -1,128 +0,0 @@ -import 'package:appflowy/plugins/database_view/application/field/field_action_sheet_bloc.dart'; -import 'package:appflowy/plugins/database_view/grid/application/grid_header_bloc.dart'; -import 'package:bloc_test/bloc_test.dart'; -import 'package:flutter_test/flutter_test.dart'; - -import 'util.dart'; - -void main() { - late AppFlowyGridTest gridTest; - - setUpAll(() async { - gridTest = await AppFlowyGridTest.ensureInitialized(); - }); - - group('$GridHeaderBloc', () { - late FieldActionSheetBloc actionSheetBloc; - late GridTestContext context; - setUp(() async { - context = await gridTest.createTestGrid(); - actionSheetBloc = FieldActionSheetBloc( - viewId: context.gridView.id, - fieldInfo: context.singleSelectFieldContext(), - ); - }); - - blocTest( - "hides property", - build: () { - final bloc = GridHeaderBloc( - viewId: context.gridView.id, - fieldController: context.fieldController, - )..add(const GridHeaderEvent.initial()); - return bloc; - }, - act: (bloc) async { - actionSheetBloc.add(const FieldActionSheetEvent.hideField()); - await Future.delayed(gridResponseDuration()); - }, - wait: gridResponseDuration(), - verify: (bloc) { - assert(bloc.state.fields.length == 2); - }, - ); - - blocTest( - "shows property", - build: () { - final bloc = GridHeaderBloc( - viewId: context.gridView.id, - fieldController: context.fieldController, - )..add(const GridHeaderEvent.initial()); - return bloc; - }, - act: (bloc) async { - actionSheetBloc.add(const FieldActionSheetEvent.hideField()); - await Future.delayed(gridResponseDuration()); - actionSheetBloc.add(const FieldActionSheetEvent.showField()); - await Future.delayed(gridResponseDuration()); - }, - wait: gridResponseDuration(), - verify: (bloc) { - assert(bloc.state.fields.length == 3); - }, - ); - - blocTest( - "duplicate property", - build: () { - final bloc = GridHeaderBloc( - viewId: context.gridView.id, - fieldController: context.fieldController, - )..add(const GridHeaderEvent.initial()); - return bloc; - }, - act: (bloc) async { - actionSheetBloc.add(const FieldActionSheetEvent.duplicateField()); - await Future.delayed(gridResponseDuration()); - }, - wait: gridResponseDuration(), - verify: (bloc) { - expect(bloc.state.fields.length, 4); - }, - ); - - blocTest( - "delete property", - build: () { - final bloc = GridHeaderBloc( - viewId: context.gridView.id, - fieldController: context.fieldController, - )..add(const GridHeaderEvent.initial()); - return bloc; - }, - act: (bloc) async { - actionSheetBloc.add(const FieldActionSheetEvent.deleteField()); - await Future.delayed(gridResponseDuration()); - }, - wait: gridResponseDuration(), - verify: (bloc) { - expect(bloc.state.fields.length, 2); - }, - ); - - blocTest( - "update name", - build: () { - final bloc = GridHeaderBloc( - viewId: context.gridView.id, - fieldController: context.fieldController, - )..add(const GridHeaderEvent.initial()); - return bloc; - }, - act: (bloc) async { - actionSheetBloc - .add(const FieldActionSheetEvent.updateFieldName("Hello world")); - await Future.delayed(gridResponseDuration()); - }, - wait: gridResponseDuration(), - verify: (bloc) { - final field = bloc.state.fields.firstWhere( - (element) => element.id == actionSheetBloc.fieldService.fieldId, - ); - - expect(field.name, "Hello world"); - }, - ); - }); -} diff --git a/frontend/appflowy_flutter/test/bloc_test/grid_test/util.dart b/frontend/appflowy_flutter/test/bloc_test/grid_test/util.dart index 7df94f3944..22e474e96e 100644 --- a/frontend/appflowy_flutter/test/bloc_test/grid_test/util.dart +++ b/frontend/appflowy_flutter/test/bloc_test/grid_test/util.dart @@ -73,10 +73,11 @@ class GridTestContext { } Future createField(FieldType fieldType) async { - final editorBloc = await createFieldEditor(viewId: gridView.id) - ..add(const FieldEditorEvent.initial()); + final editorBloc = + await createFieldEditor(databaseController: gridController) + ..add(const FieldEditorEvent.initial()); await gridResponseFuture(); - editorBloc.add(FieldEditorEvent.switchToField(fieldType)); + editorBloc.add(FieldEditorEvent.switchFieldType(fieldType)); await gridResponseFuture(); return Future(() => editorBloc); } @@ -132,19 +133,21 @@ class GridTestContext { } Future createFieldEditor({ - required String viewId, + required DatabaseController databaseController, }) async { final result = await TypeOptionBackendService.createFieldTypeOption( - viewId: viewId, + viewId: databaseController.viewId, ); + await gridResponseFuture(); return result.fold( (data) { final loader = FieldTypeOptionLoader( - viewId: viewId, + viewId: databaseController.viewId, field: data.field_2, ); return FieldEditorBloc( - isGroupField: FieldInfo.initial(data.field_2).isGroupField, + viewId: databaseController.viewId, + fieldController: databaseController.fieldController, loader: loader, field: data.field_2, );