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 5b909f4723..ce07d0d64b 100644 --- a/frontend/appflowy_flutter/integration_test/util/database_test_op.dart +++ b/frontend/appflowy_flutter/integration_test/util/database_test_op.dart @@ -38,7 +38,7 @@ import 'package:appflowy/plugins/database_view/grid/presentation/widgets/toolbar import 'package:appflowy/plugins/database_view/tar_bar/tab_bar_header.dart'; import 'package:appflowy/plugins/database_view/tar_bar/tar_bar_add_button.dart'; import 'package:appflowy/plugins/database_view/widgets/database_layout_ext.dart'; -import 'package:appflowy/plugins/database_view/widgets/field/grid_property.dart'; +import 'package:appflowy/plugins/database_view/widgets/setting/setting_property_list.dart'; import 'package:appflowy/plugins/database_view/widgets/row/accessory/cell_accessory.dart'; import 'package:appflowy/plugins/database_view/widgets/row/cells/cells.dart'; import 'package:appflowy/plugins/database_view/widgets/row/cells/checklist_cell/checklist_cell_editor.dart'; @@ -52,7 +52,6 @@ import 'package:appflowy/plugins/database_view/widgets/row/row_banner.dart'; import 'package:appflowy/plugins/database_view/widgets/row/row_detail.dart'; import 'package:appflowy/plugins/database_view/widgets/row/row_document.dart'; import 'package:appflowy/plugins/database_view/widgets/row/row_property.dart'; -import 'package:appflowy/plugins/database_view/widgets/setting/database_setting.dart'; import 'package:appflowy/plugins/database_view/widgets/setting/setting_button.dart'; import 'package:appflowy/workspace/presentation/settings/widgets/emoji_picker/emoji_menu_item.dart'; import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; @@ -1132,7 +1131,7 @@ extension AppFlowyDatabaseTest on WidgetTester { /// Should call [tapDatabaseSettingButton] first. Future tapViewPropertiesButton() async { - final findSettingItem = find.byType(DatabaseSettingItem); + final findSettingItem = find.byType(DatabaseSettingListPopover); final findLayoutButton = find.byWidgetPredicate( (widget) => widget is FlowyText && @@ -1149,7 +1148,7 @@ extension AppFlowyDatabaseTest on WidgetTester { /// Should call [tapDatabaseSettingButton] first. Future tapDatabaseLayoutButton() async { - final findSettingItem = find.byType(DatabaseSettingItem); + final findSettingItem = find.byType(DatabaseSettingListPopover); final findLayoutButton = find.byWidgetPredicate( (widget) => widget is FlowyText && @@ -1165,7 +1164,7 @@ extension AppFlowyDatabaseTest on WidgetTester { } Future tapCalendarLayoutSettingButton() async { - final findSettingItem = find.byType(DatabaseSettingItem); + final findSettingItem = find.byType(DatabaseSettingListPopover); final findLayoutButton = find.byWidgetPredicate( (widget) => widget is FlowyText && @@ -1505,7 +1504,7 @@ extension AppFlowyDatabaseTest on WidgetTester { ) async { final field = find.byWidgetPredicate( (widget) => - widget is GridPropertyCell && widget.fieldInfo.name == fieldName, + widget is DatabasePropertyCell && widget.fieldInfo.name == fieldName, ); final toggleVisibilityButton = find.descendant(of: field, matching: find.byType(FlowyIconButton)); diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/application/setting/property_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database_view/application/setting/property_bloc.dart index bbef6db894..658c433583 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/application/setting/property_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/application/setting/property_bloc.dart @@ -25,34 +25,44 @@ class DatabasePropertyBloc ) { on( (event, emit) async { - await event.map( - initial: (_Initial value) { + await event.when( + initial: () { _startListening(); }, - setFieldVisibility: (_SetFieldVisibility value) async { + setFieldVisibility: (fieldId, visibility) async { final fieldSettingsSvc = FieldSettingsBackendService( viewId: viewId, ); final result = await fieldSettingsSvc.updateFieldSettings( - fieldId: value.fieldId, - fieldVisibility: value.visibility, + fieldId: fieldId, + fieldVisibility: visibility, ); result.fold((l) => null, (err) => Log.error(err)); }, - didReceiveFieldUpdate: (_DidReceiveFieldUpdate value) { - emit(state.copyWith(fieldContexts: value.fields)); + didReceiveFieldUpdate: (fields) { + emit(state.copyWith(fieldContexts: fields)); }, - moveField: (_MoveField value) async { + moveField: (fieldId, fromIndex, toIndex) async { + if (fromIndex < toIndex) { + toIndex--; + } + final fieldContexts = List.from(state.fieldContexts); + fieldContexts.insert( + toIndex, + fieldContexts.removeAt(fromIndex), + ); + emit(state.copyWith(fieldContexts: fieldContexts)); + final fieldBackendService = FieldBackendService( viewId: viewId, - fieldId: value.fieldId, + fieldId: fieldId, ); final result = await fieldBackendService.moveField( - value.fromIndex, - value.toIndex, + fromIndex, + toIndex, ); result.fold((l) => null, (r) => Log.error(r)); diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/toolbar/calendar_layout_setting.dart b/frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/toolbar/calendar_layout_setting.dart index 47e828072e..97dc3cbef7 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/toolbar/calendar_layout_setting.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/toolbar/calendar_layout_setting.dart @@ -228,6 +228,7 @@ class LayoutDateField extends StatelessWidget { Widget build(BuildContext context) { return AppFlowyPopover( direction: PopoverDirection.leftWithTopAligned, + triggerActions: PopoverTriggerFlags.hover | PopoverTriggerFlags.click, constraints: BoxConstraints.loose(const Size(300, 400)), mutex: popoverMutex, offset: const Offset(-16, 0), @@ -346,6 +347,7 @@ class FirstDayOfWeek extends StatelessWidget { return AppFlowyPopover( direction: PopoverDirection.leftWithTopAligned, constraints: BoxConstraints.loose(const Size(300, 400)), + triggerActions: PopoverTriggerFlags.hover | PopoverTriggerFlags.click, mutex: popoverMutex, offset: const Offset(-16, 0), popupBuilder: (context) { diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/setting/database_setting.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/setting/database_setting.dart deleted file mode 100644 index 8c77cbf0a6..0000000000 --- a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/setting/database_setting.dart +++ /dev/null @@ -1,75 +0,0 @@ -import 'package:appflowy/generated/flowy_svgs.g.dart'; -import 'package:appflowy/plugins/database_view/application/database_controller.dart'; -import 'package:flowy_infra/theme_extension.dart'; -import 'package:flowy_infra_ui/style_widget/button.dart'; -import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart'; -import 'package:flowy_infra_ui/style_widget/text.dart'; -import 'package:flowy_infra_ui/widget/spacing.dart'; -import 'package:flutter/material.dart'; - -import '../../grid/presentation/layout/sizes.dart'; -import 'setting_button.dart'; - -class DatabaseSettingList extends StatelessWidget { - final DatabaseController databaseContoller; - final Function(DatabaseSettingAction, DatabaseController) onAction; - const DatabaseSettingList({ - required this.databaseContoller, - required this.onAction, - Key? key, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - final cells = actionsForDatabaseLayout(databaseContoller.databaseLayout) - .map((action) { - return DatabaseSettingItem( - action: action, - onAction: (action) => onAction(action, databaseContoller), - ); - }).toList(); - - return ListView.separated( - shrinkWrap: true, - controller: ScrollController(), - itemCount: cells.length, - separatorBuilder: (context, index) { - return VSpace(GridSize.typeOptionSeparatorHeight); - }, - physics: StyledScrollPhysics(), - itemBuilder: (BuildContext context, int index) { - return cells[index]; - }, - ); - } -} - -class DatabaseSettingItem extends StatelessWidget { - final DatabaseSettingAction action; - final Function(DatabaseSettingAction) onAction; - - const DatabaseSettingItem({ - required this.action, - required this.onAction, - Key? key, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - return SizedBox( - height: GridSize.popoverItemHeight, - child: FlowyButton( - hoverColor: AFThemeExtension.of(context).lightGreyHover, - text: FlowyText.medium( - action.title(), - color: AFThemeExtension.of(context).textColor, - ), - onTap: () => onAction(action), - leftIcon: FlowySvg( - action.iconData(), - color: Theme.of(context).iconTheme.color, - ), - ), - ); - } -} diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/setting/setting_button.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/setting/setting_button.dart index ccea4aa933..653aaf96f6 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/setting/setting_button.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/setting/setting_button.dart @@ -11,12 +11,10 @@ import 'package:flowy_infra/size.dart'; import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; -import 'package:styled_widget/styled_widget.dart'; import '../../grid/presentation/layout/sizes.dart'; import '../../grid/presentation/widgets/toolbar/grid_layout.dart'; -import '../field/grid_property.dart'; -import 'database_setting.dart'; +import 'setting_property_list.dart'; class SettingButton extends StatefulWidget { final DatabaseController databaseController; @@ -39,7 +37,6 @@ class _SettingButtonState extends State { constraints: BoxConstraints.loose(const Size(200, 400)), direction: PopoverDirection.bottomWithCenterAligned, offset: const Offset(0, 8), - margin: EdgeInsets.zero, triggerActions: PopoverTriggerFlags.none, child: FlowyTextButton( LocaleKeys.settings_title.tr(), @@ -53,7 +50,7 @@ class _SettingButtonState extends State { onPressed: () => _popoverController.show(), ), popupBuilder: (BuildContext context) { - return _DatabaseSettingListPopover( + return DatabaseSettingListPopover( databaseController: widget.databaseController, ); }, @@ -61,10 +58,10 @@ class _SettingButtonState extends State { } } -class _DatabaseSettingListPopover extends StatefulWidget { +class DatabaseSettingListPopover extends StatefulWidget { final DatabaseController databaseController; - const _DatabaseSettingListPopover({ + const DatabaseSettingListPopover({ required this.databaseController, Key? key, }) : super(key: key); @@ -74,59 +71,40 @@ class _DatabaseSettingListPopover extends StatefulWidget { } class _DatabaseSettingListPopoverState - extends State<_DatabaseSettingListPopover> { - DatabaseSettingAction? _action; + extends State { + late final PopoverMutex popoverMutex; + + @override + void initState() { + super.initState(); + popoverMutex = PopoverMutex(); + } @override Widget build(BuildContext context) { - if (_action == null) { - return DatabaseSettingList( - databaseContoller: widget.databaseController, - onAction: (action, settingContext) { - setState(() { - _action = action; - }); - }, - ).padding(all: 6.0); - } else { - switch (_action!) { - case DatabaseSettingAction.showLayout: - return DatabaseLayoutList( - viewId: widget.databaseController.viewId, - currentLayout: widget.databaseController.databaseLayout, - ); - case DatabaseSettingAction.showGroup: - return DatabaseGroupList( - viewId: widget.databaseController.viewId, - fieldController: widget.databaseController.fieldController, - onDismissed: () { - // widget.popoverController.close(); - }, - ); - case DatabaseSettingAction.showProperties: - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - DatabasePropertyList( - viewId: widget.databaseController.viewId, - fieldController: widget.databaseController.fieldController, + final cells = + actionsForDatabaseLayout(widget.databaseController.databaseLayout) + .map( + (action) => action.build( + context, + widget.databaseController, + popoverMutex, ), - FlowyText.regular( - LocaleKeys.grid_settings_reorderPropertiesTooltip.tr(), - ), - const VSpace(8), - ], - ); - case DatabaseSettingAction.showCalendarLayout: - return CalendarLayoutSetting( - viewId: widget.databaseController.viewId, - fieldController: widget.databaseController.fieldController, - calendarSettingController: ICalendarSettingImpl( - widget.databaseController, - ), - ); - } - } + ) + .toList(); + + return ListView.separated( + shrinkWrap: true, + controller: ScrollController(), + itemCount: cells.length, + separatorBuilder: (context, index) { + return VSpace(GridSize.typeOptionSeparatorHeight); + }, + physics: StyledScrollPhysics(), + itemBuilder: (BuildContext context, int index) { + return cells[index]; + }, + ); } } @@ -179,6 +157,58 @@ extension DatabaseSettingActionExtension on DatabaseSettingAction { return LocaleKeys.calendar_settings_name.tr(); } } + + Widget build( + BuildContext context, + DatabaseController databaseController, + PopoverMutex popoverMutex, + ) { + final popover = switch (this) { + DatabaseSettingAction.showLayout => DatabaseLayoutList( + viewId: databaseController.viewId, + currentLayout: databaseController.databaseLayout, + ), + DatabaseSettingAction.showGroup => DatabaseGroupList( + viewId: databaseController.viewId, + fieldController: databaseController.fieldController, + onDismissed: () {}, + ), + DatabaseSettingAction.showProperties => DatabasePropertyList( + viewId: databaseController.viewId, + fieldController: databaseController.fieldController, + ), + DatabaseSettingAction.showCalendarLayout => CalendarLayoutSetting( + viewId: databaseController.viewId, + fieldController: databaseController.fieldController, + calendarSettingController: ICalendarSettingImpl( + databaseController, + ), + ), + }; + + return AppFlowyPopover( + triggerActions: PopoverTriggerFlags.hover | PopoverTriggerFlags.click, + direction: PopoverDirection.leftWithTopAligned, + mutex: popoverMutex, + margin: EdgeInsets.zero, + offset: const Offset(-16, 0), + child: SizedBox( + height: GridSize.popoverItemHeight, + child: FlowyButton( + hoverColor: AFThemeExtension.of(context).lightGreyHover, + text: FlowyText.medium( + title(), + color: AFThemeExtension.of(context).textColor, + ), + leftIcon: FlowySvg( + iconData(), + color: Theme.of(context).iconTheme.color, + ), + ), + ), + popupBuilder: (context) => popover, + ); + } } /// Returns the list of actions that should be shown for the given database layout. diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/field/grid_property.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/setting/setting_property_list.dart similarity index 67% rename from frontend/appflowy_flutter/lib/plugins/database_view/widgets/field/grid_property.dart rename to frontend/appflowy_flutter/lib/plugins/database_view/widgets/setting/setting_property_list.dart index c06b9eaf13..53e43ea4dc 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/field/grid_property.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/setting/setting_property_list.dart @@ -1,3 +1,5 @@ +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'; @@ -7,12 +9,12 @@ import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/ import 'package:appflowy/startup/startup.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:reorderables/reorderables.dart'; import 'package:styled_widget/styled_widget.dart'; import '../../grid/presentation/layout/sizes.dart'; @@ -44,29 +46,43 @@ class _DatabasePropertyListState extends State { )..add(const DatabasePropertyEvent.initial()), child: BlocBuilder( builder: (context, state) { - final cells = state.fieldContexts.map((field) { - return GridPropertyCell( + final cells = state.fieldContexts.mapIndexed((index, field) { + return DatabasePropertyCell( key: ValueKey(field.id), viewId: widget.viewId, fieldInfo: field, popoverMutex: _popoverMutex, + index: index, ); }).toList(); - return ReorderableColumn( - needsLongPressDraggable: false, - buildDraggableFeedback: (context, constraints, child) => - ConstrainedBox( - constraints: constraints, - child: Material(color: Colors.transparent, child: child), - ), - onReorder: (from, to) => context.read().add( - DatabasePropertyEvent.moveField( - fieldId: cells[from].fieldInfo.id, - fromIndex: from, - toIndex: to, + return ReorderableListView( + proxyDecorator: (child, index, _) => Material( + color: Colors.transparent, + child: Stack( + children: [ + child, + MouseRegion( + cursor: Platform.isWindows + ? SystemMouseCursors.click + : SystemMouseCursors.grabbing, + child: const SizedBox.expand(), ), - ), + ], + ), + ), + buildDefaultDragHandles: false, + shrinkWrap: true, + onReorder: (from, to) { + context.read().add( + DatabasePropertyEvent.moveField( + fieldId: cells[from].fieldInfo.id, + fromIndex: from, + toIndex: to, + ), + ); + }, + onReorderStart: (_) => _popoverMutex.close(), padding: const EdgeInsets.symmetric(vertical: 6.0), children: cells, ); @@ -77,23 +93,25 @@ class _DatabasePropertyListState extends State { } @visibleForTesting -class GridPropertyCell extends StatefulWidget { +class DatabasePropertyCell extends StatefulWidget { final FieldInfo fieldInfo; final String viewId; final PopoverMutex popoverMutex; + final int index; - const GridPropertyCell({ + const DatabasePropertyCell({ super.key, required this.fieldInfo, required this.viewId, required this.popoverMutex, + required this.index, }); @override - State createState() => _GridPropertyCellState(); + State createState() => _DatabasePropertyCellState(); } -class _GridPropertyCellState extends State { +class _DatabasePropertyCellState extends State { final PopoverController _popoverController = PopoverController(); @override @@ -109,7 +127,7 @@ class _GridPropertyCellState extends State { return AppFlowyPopover( mutex: widget.popoverMutex, controller: _popoverController, - offset: const Offset(8, 0), + offset: const Offset(-8, 0), direction: PopoverDirection.leftWithTopAligned, constraints: BoxConstraints.loose(const Size(240, 400)), triggerActions: PopoverTriggerFlags.none, @@ -122,9 +140,31 @@ class _GridPropertyCellState extends State { widget.fieldInfo.name, color: AFThemeExtension.of(context).textColor, ), - leftIcon: FlowySvg( - widget.fieldInfo.fieldType.icon(), - color: Theme.of(context).iconTheme.color, + leftIconSize: const Size(36, 18), + leftIcon: Row( + children: [ + ReorderableDragStartListener( + index: widget.index, + child: MouseRegion( + cursor: Platform.isWindows + ? SystemMouseCursors.click + : SystemMouseCursors.grab, + child: SizedBox( + width: 14, + height: 14, + child: FlowySvg( + FlowySvgs.drag_element_s, + color: Theme.of(context).iconTheme.color, + ), + ), + ), + ), + const HSpace(6.0), + FlowySvg( + widget.fieldInfo.fieldType.icon(), + color: Theme.of(context).iconTheme.color, + ), + ], ), rightIcon: FlowyIconButton( hoverColor: Colors.transparent,