From 4f592e80b61fea23dfa3a907c8780783b8b8df62 Mon Sep 17 00:00:00 2001 From: Mathias Mogensen <42929161+Xazin@users.noreply.github.com> Date: Thu, 5 Sep 2024 12:48:44 +0200 Subject: [PATCH] fix: edit group title + rebuild improvement (#6156) * fix: edit group title + rebuild improvement * chore: fix clippy * chore: fix clippy * fix: stop widget replacement causing perf issues * fix: after merge main * chore: minor cleanup in view_editor * fix: attempt to fix hover issue in tests --- .../shared/common_operations.dart | 9 +- .../widgets/mobile_hidden_groups_column.dart | 6 +- .../board/application/board_bloc.dart | 117 ++---------------- .../board/application/column_header_bloc.dart | 113 +++++++++++++++++ .../lib/plugins/database/board/group_ext.dart | 106 ++++++++++++++++ .../board/presentation/board_page.dart | 19 ++- .../widgets/board_column_header.dart | 34 ++--- .../widgets/board_hidden_groups.dart | 12 +- .../database/domain/group_service.dart | 14 +++ .../plugins/database/widgets/card/card.dart | 12 +- .../card_cell_skeleton/text_card_cell.dart | 20 +-- .../widgets/cell/editable_cell_builder.dart | 6 + .../lib/style_widget/hover.dart | 55 +++----- .../src/entities/group_entities/group.rs | 63 ++++++++++ .../group_entities/group_changeset.rs | 9 ++ .../flowy-database2/src/event_handler.rs | 16 +++ .../rust-lib/flowy-database2/src/event_map.rs | 4 + .../flowy-database2/src/notification.rs | 3 + .../src/services/database/database_editor.rs | 6 + .../src/services/database_view/notifier.rs | 14 ++- .../src/services/database_view/view_editor.rs | 109 ++++++++++++++-- .../src/services/group/action.rs | 10 ++ .../src/services/group/controller.rs | 25 ++++ .../controller_impls/default_controller.rs | 7 ++ frontend/rust-lib/flowy-error/src/code.rs | 3 + 25 files changed, 592 insertions(+), 200 deletions(-) create mode 100644 frontend/appflowy_flutter/lib/plugins/database/board/application/column_header_bloc.dart create mode 100644 frontend/appflowy_flutter/lib/plugins/database/board/group_ext.dart diff --git a/frontend/appflowy_flutter/integration_test/shared/common_operations.dart b/frontend/appflowy_flutter/integration_test/shared/common_operations.dart index fad17a00c786..84ed1de3d30b 100644 --- a/frontend/appflowy_flutter/integration_test/shared/common_operations.dart +++ b/frontend/appflowy_flutter/integration_test/shared/common_operations.dart @@ -1,5 +1,10 @@ import 'dart:io'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + import 'package:appflowy/core/config/kv.dart'; import 'package:appflowy/core/config/kv_keys.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart'; @@ -30,10 +35,6 @@ import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/widget/buttons/primary_button.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/gestures.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'emoji.dart'; diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/database/board/widgets/mobile_hidden_groups_column.dart b/frontend/appflowy_flutter/lib/mobile/presentation/database/board/widgets/mobile_hidden_groups_column.dart index 0b0c16f951e3..f80525786ed6 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/database/board/widgets/mobile_hidden_groups_column.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/database/board/widgets/mobile_hidden_groups_column.dart @@ -1,9 +1,12 @@ +import 'package:flutter/material.dart'; + 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'; import 'package:appflowy/mobile/presentation/widgets/widgets.dart'; import 'package:appflowy/plugins/database/application/database_controller.dart'; import 'package:appflowy/plugins/database/board/application/board_bloc.dart'; +import 'package:appflowy/plugins/database/board/group_ext.dart'; import 'package:appflowy/plugins/database/widgets/cell/card_cell_builder.dart'; import 'package:appflowy/plugins/database/widgets/cell/card_cell_skeleton/text_card_cell.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; @@ -11,7 +14,6 @@ import 'package:collection/collection.dart'; import 'package:easy_localization/easy_localization.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:go_router/go_router.dart'; @@ -200,7 +202,7 @@ class MobileHiddenGroup extends StatelessWidget { children: [ Expanded( child: Text( - context.read().generateGroupNameFromGroup(group), + group.generateGroupName(databaseController), style: Theme.of(context).textTheme.bodyMedium, maxLines: 2, overflow: TextOverflow.ellipsis, diff --git a/frontend/appflowy_flutter/lib/plugins/database/board/application/board_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database/board/application/board_bloc.dart index 15b873c23cad..6f15c0533929 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/board/application/board_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/board/application/board_bloc.dart @@ -1,10 +1,13 @@ import 'dart:async'; import 'dart:collection'; +import 'package:flutter/foundation.dart'; + import 'package:appflowy/plugins/database/application/defines.dart'; import 'package:appflowy/plugins/database/application/field/field_info.dart'; -import 'package:appflowy/plugins/database/domain/group_service.dart'; import 'package:appflowy/plugins/database/application/row/row_service.dart'; +import 'package:appflowy/plugins/database/board/group_ext.dart'; +import 'package:appflowy/plugins/database/domain/group_service.dart'; import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; @@ -13,17 +16,14 @@ import 'package:appflowy_editor/appflowy_editor.dart' hide Log; import 'package:appflowy_result/appflowy_result.dart'; import 'package:collection/collection.dart'; import 'package:equatable/equatable.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:protobuf/protobuf.dart' hide FieldInfo; -import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:easy_localization/easy_localization.dart'; -import 'package:calendar_view/calendar_view.dart'; import '../../application/database_controller.dart'; import '../../application/field/field_controller.dart'; import '../../application/row/row_cache.dart'; + import 'group_controller.dart'; part 'board_bloc.freezed.dart'; @@ -204,13 +204,13 @@ class BoardBloc extends Bloc { endEditingHeader: (String groupId, String? groupName) async { final group = groupControllers[groupId]?.group; if (group != null) { - if (generateGroupNameFromGroup(group) != groupName) { + final currentName = group.generateGroupName(databaseController); + if (currentName != groupName) { await groupBackendSvc.updateGroup( fieldId: groupControllers.values.first.group.fieldId, groupId: groupId, name: groupName, ); - return; } } @@ -436,7 +436,9 @@ class BoardBloc extends Bloc { boardController.getGroupController(group.groupId); if (columnController != null) { // remove the group or update its name - columnController.updateGroupName(generateGroupNameFromGroup(group)); + columnController.updateGroupName( + group.generateGroupName(databaseController), + ); if (!group.isVisible) { boardController.removeGroup(group.groupId); } @@ -516,7 +518,7 @@ class BoardBloc extends Bloc { AppFlowyGroupData _initializeGroupData(GroupPB group) { return AppFlowyGroupData( id: group.groupId, - name: generateGroupNameFromGroup(group), + name: group.generateGroupName(databaseController), items: _buildGroupItems(group), customData: GroupData( group: group, @@ -524,103 +526,6 @@ class BoardBloc extends Bloc { ), ); } - - String generateGroupNameFromGroup(GroupPB group) { - final field = fieldController.getField(group.fieldId); - if (field == null) { - return ""; - } - - // if the group is the default group, then - if (group.isDefault) { - return "No ${field.name}"; - } - - final groupSettings = databaseController.fieldController.groupSettings - .firstWhereOrNull((gs) => gs.fieldId == field.id); - - switch (field.fieldType) { - case FieldType.SingleSelect: - final options = - SingleSelectTypeOptionPB.fromBuffer(field.field.typeOptionData) - .options; - final option = - options.firstWhereOrNull((option) => option.id == group.groupId); - return option == null ? "" : option.name; - case FieldType.MultiSelect: - final options = - MultiSelectTypeOptionPB.fromBuffer(field.field.typeOptionData) - .options; - final option = - options.firstWhereOrNull((option) => option.id == group.groupId); - return option == null ? "" : option.name; - case FieldType.Checkbox: - return group.groupId; - case FieldType.URL: - return group.groupId; - case FieldType.DateTime: - final config = groupSettings?.content != null - ? DateGroupConfigurationPB.fromBuffer(groupSettings!.content) - : DateGroupConfigurationPB(); - final dateFormat = DateFormat("y/MM/dd"); - try { - final targetDateTime = dateFormat.parseLoose(group.groupId); - switch (config.condition) { - case DateConditionPB.Day: - return DateFormat("MMM dd, y").format(targetDateTime); - case DateConditionPB.Week: - final beginningOfWeek = targetDateTime - .subtract(Duration(days: targetDateTime.weekday - 1)); - final endOfWeek = targetDateTime.add( - Duration(days: DateTime.daysPerWeek - targetDateTime.weekday), - ); - - final beginningOfWeekFormat = - beginningOfWeek.year != endOfWeek.year - ? "MMM dd y" - : "MMM dd"; - final endOfWeekFormat = beginningOfWeek.month != endOfWeek.month - ? "MMM dd y" - : "dd y"; - - return LocaleKeys.board_dateCondition_weekOf.tr( - args: [ - DateFormat(beginningOfWeekFormat).format(beginningOfWeek), - DateFormat(endOfWeekFormat).format(endOfWeek), - ], - ); - case DateConditionPB.Month: - return DateFormat("MMM y").format(targetDateTime); - case DateConditionPB.Year: - return DateFormat("y").format(targetDateTime); - case DateConditionPB.Relative: - final targetDateTimeDay = DateTime( - targetDateTime.year, - targetDateTime.month, - targetDateTime.day, - ); - final nowDay = DateTime.now().withoutTime; - final diff = targetDateTimeDay.difference(nowDay).inDays; - return switch (diff) { - 0 => LocaleKeys.board_dateCondition_today.tr(), - -1 => LocaleKeys.board_dateCondition_yesterday.tr(), - 1 => LocaleKeys.board_dateCondition_tomorrow.tr(), - -7 => LocaleKeys.board_dateCondition_lastSevenDays.tr(), - 2 => LocaleKeys.board_dateCondition_nextSevenDays.tr(), - -30 => LocaleKeys.board_dateCondition_lastThirtyDays.tr(), - 8 => LocaleKeys.board_dateCondition_nextThirtyDays.tr(), - _ => DateFormat("MMM y").format(targetDateTimeDay) - }; - default: - return ""; - } - } on FormatException { - return ""; - } - default: - return ""; - } - } } @freezed diff --git a/frontend/appflowy_flutter/lib/plugins/database/board/application/column_header_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database/board/application/column_header_bloc.dart new file mode 100644 index 000000000000..dcb1578930da --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/database/board/application/column_header_bloc.dart @@ -0,0 +1,113 @@ +import 'package:appflowy/plugins/database/board/group_ext.dart'; +import 'package:appflowy/plugins/database/domain/group_service.dart'; +import 'package:appflowy_backend/protobuf/flowy-database2/group.pb.dart'; +import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +import '../../application/database_controller.dart'; +import '../../application/field/field_controller.dart'; + +part 'column_header_bloc.freezed.dart'; + +class ColumnHeaderBloc extends Bloc { + ColumnHeaderBloc({ + required this.databaseController, + required this.fieldId, + required this.group, + }) : super(const ColumnHeaderState.loading()) { + groupBackendSvc = GroupBackendService(viewId); + _dispatch(); + } + + final DatabaseController databaseController; + final String fieldId; + final GroupPB group; + + late final GroupBackendService groupBackendSvc; + + FieldController get fieldController => databaseController.fieldController; + String get viewId => databaseController.viewId; + + void _dispatch() { + on( + (event, emit) async { + await event.when( + initial: () { + final name = group.generateGroupName(databaseController); + emit(ColumnHeaderState.initial(name)); + }, + startEditing: () async { + state.maybeMap( + ready: (state) => emit(state.copyWith(isEditing: true)), + orElse: () {}, + ); + }, + endEditing: (String? groupName) async { + if (groupName != null) { + final stateGroupName = state.maybeMap( + ready: (state) => state.groupName, + orElse: () => null, + ); + + if (stateGroupName == null || stateGroupName == groupName) { + state.maybeMap( + ready: (state) => emit( + state.copyWith( + groupName: stateGroupName!, + isEditing: false, + ), + ), + orElse: () {}, + ); + } + + await groupBackendSvc.renameGroup( + groupId: group.groupId, + fieldId: fieldId, + name: groupName, + ); + state.maybeMap( + ready: (state) { + emit(state.copyWith(groupName: groupName, isEditing: false)); + }, + orElse: () {}, + ); + } + }, + ); + }, + ); + } +} + +@freezed +class ColumnHeaderEvent with _$ColumnHeaderEvent { + const factory ColumnHeaderEvent.initial() = _Initial; + const factory ColumnHeaderEvent.startEditing() = _StartEditing; + const factory ColumnHeaderEvent.endEditing(String? groupName) = _EndEditing; +} + +@freezed +class ColumnHeaderState with _$ColumnHeaderState { + const ColumnHeaderState._(); + + const factory ColumnHeaderState.loading() = _ColumnHeaderLoadingState; + + const factory ColumnHeaderState.error({ + required FlowyError error, + }) = _ColumnHeaderErrorState; + + const factory ColumnHeaderState.ready({ + required String groupName, + @Default(false) bool isEditing, + @Default(false) bool canEdit, + }) = _ColumnHeaderReadyState; + + factory ColumnHeaderState.initial(String name) => + ColumnHeaderState.ready(groupName: name); + + bool get isLoading => maybeMap(loading: (_) => true, orElse: () => false); + bool get isError => maybeMap(error: (_) => true, orElse: () => false); + bool get isReady => maybeMap(ready: (_) => true, orElse: () => false); +} diff --git a/frontend/appflowy_flutter/lib/plugins/database/board/group_ext.dart b/frontend/appflowy_flutter/lib/plugins/database/board/group_ext.dart new file mode 100644 index 000000000000..5c69a66e62a0 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/database/board/group_ext.dart @@ -0,0 +1,106 @@ +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/database/application/database_controller.dart'; +import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; +import 'package:calendar_view/calendar_view.dart'; +import 'package:collection/collection.dart'; +import 'package:easy_localization/easy_localization.dart'; + +extension GroupName on GroupPB { + String generateGroupName(DatabaseController databaseController) { + final fieldController = databaseController.fieldController; + final field = fieldController.getField(fieldId); + if (field == null) { + return ""; + } + + // if the group is the default group, then + if (isDefault) { + return "No ${field.name}"; + } + + final groupSettings = databaseController.fieldController.groupSettings + .firstWhereOrNull((gs) => gs.fieldId == field.id); + + switch (field.fieldType) { + case FieldType.SingleSelect: + final options = + SingleSelectTypeOptionPB.fromBuffer(field.field.typeOptionData) + .options; + final option = + options.firstWhereOrNull((option) => option.id == groupId); + return option == null ? "" : option.name; + case FieldType.MultiSelect: + final options = + MultiSelectTypeOptionPB.fromBuffer(field.field.typeOptionData) + .options; + final option = + options.firstWhereOrNull((option) => option.id == groupId); + return option == null ? "" : option.name; + case FieldType.Checkbox: + return groupId; + case FieldType.URL: + return groupId; + case FieldType.DateTime: + final config = groupSettings?.content != null + ? DateGroupConfigurationPB.fromBuffer(groupSettings!.content) + : DateGroupConfigurationPB(); + final dateFormat = DateFormat("y/MM/dd"); + try { + final targetDateTime = dateFormat.parseLoose(groupId); + switch (config.condition) { + case DateConditionPB.Day: + return DateFormat("MMM dd, y").format(targetDateTime); + case DateConditionPB.Week: + final beginningOfWeek = targetDateTime + .subtract(Duration(days: targetDateTime.weekday - 1)); + final endOfWeek = targetDateTime.add( + Duration(days: DateTime.daysPerWeek - targetDateTime.weekday), + ); + + final beginningOfWeekFormat = + beginningOfWeek.year != endOfWeek.year + ? "MMM dd y" + : "MMM dd"; + final endOfWeekFormat = beginningOfWeek.month != endOfWeek.month + ? "MMM dd y" + : "dd y"; + + return LocaleKeys.board_dateCondition_weekOf.tr( + args: [ + DateFormat(beginningOfWeekFormat).format(beginningOfWeek), + DateFormat(endOfWeekFormat).format(endOfWeek), + ], + ); + case DateConditionPB.Month: + return DateFormat("MMM y").format(targetDateTime); + case DateConditionPB.Year: + return DateFormat("y").format(targetDateTime); + case DateConditionPB.Relative: + final targetDateTimeDay = DateTime( + targetDateTime.year, + targetDateTime.month, + targetDateTime.day, + ); + final nowDay = DateTime.now().withoutTime; + final diff = targetDateTimeDay.difference(nowDay).inDays; + return switch (diff) { + 0 => LocaleKeys.board_dateCondition_today.tr(), + -1 => LocaleKeys.board_dateCondition_yesterday.tr(), + 1 => LocaleKeys.board_dateCondition_tomorrow.tr(), + -7 => LocaleKeys.board_dateCondition_lastSevenDays.tr(), + 2 => LocaleKeys.board_dateCondition_nextSevenDays.tr(), + -30 => LocaleKeys.board_dateCondition_lastThirtyDays.tr(), + 8 => LocaleKeys.board_dateCondition_nextThirtyDays.tr(), + _ => DateFormat("MMM y").format(targetDateTimeDay) + }; + default: + return ""; + } + } on FormatException { + return ""; + } + default: + return ""; + } + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/database/board/presentation/board_page.dart b/frontend/appflowy_flutter/lib/plugins/database/board/presentation/board_page.dart index 670eefb7f856..c224749b71ca 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/board/presentation/board_page.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/board/presentation/board_page.dart @@ -9,6 +9,7 @@ import 'package:appflowy/mobile/presentation/database/board/mobile_board_page.da import 'package:appflowy/plugins/database/application/database_controller.dart'; import 'package:appflowy/plugins/database/application/row/row_controller.dart'; import 'package:appflowy/plugins/database/board/application/board_actions_bloc.dart'; +import 'package:appflowy/plugins/database/board/application/column_header_bloc.dart'; import 'package:appflowy/plugins/database/board/presentation/widgets/board_column_header.dart'; import 'package:appflowy/plugins/database/grid/presentation/grid_page.dart'; import 'package:appflowy/plugins/database/grid/presentation/widgets/header/field_type_extension.dart'; @@ -341,8 +342,22 @@ class _BoardContentState extends State<_BoardContent> { false ? BoardTrailing(scrollController: scrollController) : const HSpace(40), - headerBuilder: (_, groupData) => BlocProvider.value( - value: context.read(), + headerBuilder: (_, groupData) => MultiBlocProvider( + providers: [ + BlocProvider.value( + value: context.read(), + ), + BlocProvider( + create: (context) => ColumnHeaderBloc( + databaseController: databaseController, + fieldId: (groupData.customData as GroupData).fieldInfo.id, + group: context + .read() + .groupControllers[groupData.headerData.groupId]! + .group, + )..add(const ColumnHeaderEvent.initial()), + ), + ], child: BoardColumnHeader( groupData: groupData, margin: config.groupHeaderPadding, diff --git a/frontend/appflowy_flutter/lib/plugins/database/board/presentation/widgets/board_column_header.dart b/frontend/appflowy_flutter/lib/plugins/database/board/presentation/widgets/board_column_header.dart index 0ee31e41f42a..8e08b69c6431 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/board/presentation/widgets/board_column_header.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/board/presentation/widgets/board_column_header.dart @@ -1,6 +1,10 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/database/board/application/board_bloc.dart'; +import 'package:appflowy/plugins/database/board/application/column_header_bloc.dart'; import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart'; import 'package:appflowy/plugins/database/grid/presentation/widgets/header/field_type_extension.dart'; import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; @@ -9,8 +13,6 @@ import 'package:appflowy_board/appflowy_board.dart'; import 'package:appflowy_popover/appflowy_popover.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/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class BoardColumnHeader extends StatefulWidget { @@ -63,12 +65,12 @@ class _BoardColumnHeaderState extends State { Widget build(BuildContext context) { final boardCustomData = widget.groupData.customData as GroupData; - return BlocBuilder( + return BlocBuilder( builder: (context, state) { return state.maybeMap( orElse: () => const SizedBox.shrink(), ready: (state) { - if (state.editingHeaderId != null) { + if (state.isEditing) { WidgetsBinding.instance.addPostFrameCallback((_) { _focusNode.requestFocus(); }); @@ -76,7 +78,7 @@ class _BoardColumnHeaderState extends State { Widget title = Expanded( child: FlowyText.medium( - widget.groupData.headerData.groupName, + state.groupName, overflow: TextOverflow.ellipsis, ), ); @@ -90,11 +92,11 @@ class _BoardColumnHeaderState extends State { child: MouseRegion( cursor: SystemMouseCursors.click, child: GestureDetector( - onTap: () => context.read().add( - BoardEvent.startEditingHeader(widget.groupData.id), - ), + onTap: () => context + .read() + .add(const ColumnHeaderEvent.startEditing()), child: FlowyText.medium( - widget.groupData.headerData.groupName, + state.groupName, overflow: TextOverflow.ellipsis, ), ), @@ -103,7 +105,7 @@ class _BoardColumnHeaderState extends State { ); } - if (state.editingHeaderId == widget.groupData.id) { + if (state.isEditing) { title = _buildTextField(context); } @@ -190,9 +192,11 @@ class _BoardColumnHeaderState extends State { ); } - void _saveEdit() => context - .read() - .add(BoardEvent.endEditingHeader(widget.groupData.id, _controller.text)); + void _saveEdit() { + context + .read() + .add(ColumnHeaderEvent.endEditing(_controller.text)); + } Widget _buildHeaderIcon(GroupData customData) => switch (customData.fieldType) { @@ -263,8 +267,8 @@ enum GroupOptions { switch (this) { case rename: context - .read() - .add(BoardEvent.startEditingHeader(group.groupId)); + .read() + .add(const ColumnHeaderEvent.startEditing()); break; case hide: context diff --git a/frontend/appflowy_flutter/lib/plugins/database/board/presentation/widgets/board_hidden_groups.dart b/frontend/appflowy_flutter/lib/plugins/database/board/presentation/widgets/board_hidden_groups.dart index 48cdcf8ef641..a013d370f8be 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/board/presentation/widgets/board_hidden_groups.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/board/presentation/widgets/board_hidden_groups.dart @@ -1,5 +1,7 @@ import 'dart:io'; +import 'package:flutter/material.dart'; + import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/database/application/cell/cell_controller.dart'; @@ -7,6 +9,7 @@ import 'package:appflowy/plugins/database/application/database_controller.dart'; import 'package:appflowy/plugins/database/application/row/row_cache.dart'; import 'package:appflowy/plugins/database/application/row/row_controller.dart'; import 'package:appflowy/plugins/database/board/application/board_bloc.dart'; +import 'package:appflowy/plugins/database/board/group_ext.dart'; import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart'; import 'package:appflowy/plugins/database/tab_bar/tab_bar_view.dart'; import 'package:appflowy/plugins/database/widgets/cell/card_cell_builder.dart'; @@ -18,7 +21,6 @@ import 'package:collection/collection.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/style_widget/hover.dart'; -import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class HiddenGroupsColumn extends StatelessWidget { @@ -278,7 +280,8 @@ class HiddenGroupButtonContent extends StatelessWidget { ), const HSpace(4), FlowyText.medium( - bloc.generateGroupNameFromGroup(group), + group + .generateGroupName(bloc.databaseController), overflow: TextOverflow.ellipsis, ), const HSpace(6), @@ -355,11 +358,11 @@ class HiddenGroupCardActions extends StatelessWidget { class HiddenGroupPopupItemList extends StatelessWidget { const HiddenGroupPopupItemList({ + super.key, required this.groupId, required this.viewId, required this.primaryFieldId, required this.rowCache, - super.key, }); final String groupId; @@ -380,11 +383,12 @@ class HiddenGroupPopupItemList extends StatelessWidget { if (group == null) { return const SizedBox.shrink(); } + final bloc = context.read(); final cells = [ Padding( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4), child: FlowyText.medium( - context.read().generateGroupNameFromGroup(group), + group.generateGroupName(bloc.databaseController), fontSize: 10, color: Theme.of(context).hintColor, overflow: TextOverflow.ellipsis, diff --git a/frontend/appflowy_flutter/lib/plugins/database/domain/group_service.dart b/frontend/appflowy_flutter/lib/plugins/database/domain/group_service.dart index 934bbba8d163..36bb84735619 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/domain/group_service.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/domain/group_service.dart @@ -60,4 +60,18 @@ class GroupBackendService { return DatabaseEventDeleteGroup(payload).send(); } + + Future> renameGroup({ + required String groupId, + required String fieldId, + required String name, + }) { + final payload = RenameGroupPB.create() + ..fieldId = fieldId + ..viewId = viewId + ..groupId = groupId + ..name = name; + + return DatabaseEventRenameGroup(payload).send(); + } } diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/card/card.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/card/card.dart index da09fbb63bc6..44f62d0d619b 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/card/card.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/card/card.dart @@ -151,13 +151,11 @@ class _RowCardState extends State { triggerActions: PopoverTriggerFlags.none, constraints: BoxConstraints.loose(const Size(140, 200)), direction: PopoverDirection.rightWithCenterAligned, - popupBuilder: (_) { - return RowActionMenu.board( - viewId: _cardBloc.viewId, - rowId: _cardBloc.rowController.rowId, - groupId: widget.groupId, - ); - }, + popupBuilder: (_) => RowActionMenu.board( + viewId: _cardBloc.viewId, + rowId: _cardBloc.rowController.rowId, + groupId: widget.groupId, + ), child: RowCardContainer( buildAccessoryWhen: () => state.isEditing == false, accessories: accessories ?? [], diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/card_cell_skeleton/text_card_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/card_cell_skeleton/text_card_cell.dart index b4c1b62f7188..187cd6af4a4d 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/card_cell_skeleton/text_card_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/card_cell_skeleton/text_card_cell.dart @@ -62,7 +62,6 @@ class _TextCellState extends State { @override void initState() { super.initState(); - _textEditingController = TextEditingController(text: cellBloc.state.content) ..addListener(() { if (_textEditingController.value.composing.isCollapsed) { @@ -81,15 +80,17 @@ class _TextCellState extends State { // If the focusNode lost its focus, the widget's editableNotifier will // set to false, which will cause the [EditableRowNotifier] to receive // end edit event. - focusNode.addListener(() { - if (!focusNode.hasFocus) { - widget.editableNotifier?.isCellEditing.value = false; - cellBloc.add(const TextCellEvent.enableEdit(false)); - } - }); + focusNode.addListener(_onFocusChanged); _bindEditableNotifier(); } + void _onFocusChanged() { + if (!focusNode.hasFocus) { + widget.editableNotifier?.isCellEditing.value = false; + cellBloc.add(const TextCellEvent.enableEdit(false)); + } + } + void _bindEditableNotifier() { widget.editableNotifier?.isCellEditing.addListener(() { if (!mounted) { @@ -98,9 +99,8 @@ class _TextCellState extends State { final isEditing = widget.editableNotifier?.isCellEditing.value ?? false; if (isEditing) { - WidgetsBinding.instance.addPostFrameCallback((_) { - focusNode.requestFocus(); - }); + WidgetsBinding.instance + .addPostFrameCallback((_) => focusNode.requestFocus()); } cellBloc.add(TextCellEvent.enableEdit(isEditing)); }); diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_builder.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_builder.dart index bf9bc1ee170a..9e0f334ec8fe 100755 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_builder.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_builder.dart @@ -383,6 +383,12 @@ class SingleListenerFocusNode extends FocusNode { removeListener(_listener!); } } + + @override + void dispose() { + removeAllListener(); + super.dispose(); + } } class EditableCellSkinMap { diff --git a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/hover.dart b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/hover.dart index 27bff59045b9..b9d31f57ca39 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/hover.dart +++ b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/hover.dart @@ -57,23 +57,21 @@ class _FlowyHoverState extends State { return MouseRegion( cursor: widget.cursor != null ? widget.cursor! : SystemMouseCursors.click, opaque: false, - onHover: (p) { - if (_onHover) return; - _setOnHover(true); - }, - onEnter: (p) { - if (_onHover) return; - _setOnHover(true); - }, - onExit: (p) { - if (!_onHover) return; - _setOnHover(false); - }, - child: renderWidget(), + onHover: (_) => _setOnHover(true), + onEnter: (_) => _setOnHover(true), + onExit: (_) => _setOnHover(false), + child: FlowyHoverContainer( + style: widget.style ?? + HoverStyle(hoverColor: Theme.of(context).colorScheme.secondary), + applyStyle: _onHover, + child: widget.child ?? widget.builder!(context, _onHover), + ), ); } void _setOnHover(bool isHovering) { + if (isHovering == _onHover) return; + if (widget.buildWhenOnHover?.call() ?? true) { setState(() => _onHover = isHovering); if (widget.onHover != null) { @@ -81,31 +79,6 @@ class _FlowyHoverState extends State { } } } - - Widget renderWidget() { - bool showHover = _onHover; - if (!showHover && widget.isSelected != null) { - showHover = widget.isSelected!(); - } - - final child = widget.child ?? widget.builder!(context, _onHover); - final style = widget.style ?? - HoverStyle(hoverColor: Theme.of(context).colorScheme.secondary); - if (showHover) { - return FlowyHoverContainer( - style: style, - child: child, - ); - } else { - return Container( - decoration: BoxDecoration( - color: style.backgroundColor, - borderRadius: style.borderRadius, - ), - child: child, - ); - } - } } class HoverStyle { @@ -140,11 +113,13 @@ class HoverStyle { class FlowyHoverContainer extends StatelessWidget { final HoverStyle style; final Widget child; + final bool applyStyle; const FlowyHoverContainer({ super.key, required this.child, required this.style, + this.applyStyle = false, }); @override @@ -173,7 +148,9 @@ class FlowyHoverContainer extends StatelessWidget { margin: style.contentMargin, decoration: BoxDecoration( border: hoverBorder, - color: style.hoverColor ?? Theme.of(context).colorScheme.secondary, + color: applyStyle + ? style.hoverColor ?? Theme.of(context).colorScheme.secondary + : style.backgroundColor, borderRadius: style.borderRadius, ), child: Theme( diff --git a/frontend/rust-lib/flowy-database2/src/entities/group_entities/group.rs b/frontend/rust-lib/flowy-database2/src/entities/group_entities/group.rs index ba61bb53dc38..f33fd36708c2 100644 --- a/frontend/rust-lib/flowy-database2/src/entities/group_entities/group.rs +++ b/frontend/rust-lib/flowy-database2/src/entities/group_entities/group.rs @@ -267,3 +267,66 @@ impl TryFrom for DeleteGroupParams { Ok(Self { view_id, group_id }) } } + +#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone, Validate)] +pub struct RenameGroupPB { + #[pb(index = 1)] + #[validate(custom = "lib_infra::validator_fn::required_not_empty_str")] + pub view_id: String, + + #[pb(index = 2)] + #[validate(custom = "lib_infra::validator_fn::required_not_empty_str")] + pub group_id: String, + + #[pb(index = 3)] + #[validate(custom = "lib_infra::validator_fn::required_not_empty_str")] + pub field_id: String, + + #[pb(index = 4)] + #[validate(custom = "lib_infra::validator_fn::required_not_empty_str")] + pub name: String, +} + +pub struct RenameGroupParams { + pub view_id: String, + pub group_id: String, + pub field_id: String, + pub name: String, +} + +impl TryFrom for RenameGroupParams { + type Error = ErrorCode; + + fn try_from(value: RenameGroupPB) -> Result { + let view_id = NotEmptyStr::parse(value.view_id) + .map_err(|_| ErrorCode::ViewIdIsInvalid)? + .0; + let group_id = NotEmptyStr::parse(value.group_id) + .map_err(|_| ErrorCode::GroupIdIsEmpty)? + .0; + let field_id = NotEmptyStr::parse(value.field_id) + .map_err(|_| ErrorCode::FieldIdIsEmpty)? + .0; + let name = NotEmptyStr::parse(value.name) + .map_err(|_| ErrorCode::GroupNameIsEmpty)? + .0; + + Ok(Self { + view_id, + group_id, + field_id, + name, + }) + } +} + +impl From for GroupChangeset { + fn from(params: RenameGroupParams) -> Self { + Self { + group_id: params.group_id, + field_id: params.field_id, + name: Some(params.name), + visible: None, + } + } +} diff --git a/frontend/rust-lib/flowy-database2/src/entities/group_entities/group_changeset.rs b/frontend/rust-lib/flowy-database2/src/entities/group_entities/group_changeset.rs index f002e93bd26e..4ae78f053329 100644 --- a/frontend/rust-lib/flowy-database2/src/entities/group_entities/group_changeset.rs +++ b/frontend/rust-lib/flowy-database2/src/entities/group_entities/group_changeset.rs @@ -149,3 +149,12 @@ pub struct InsertedGroupPB { #[pb(index = 2)] pub index: i32, } + +#[derive(Debug, Default, ProtoBuf)] +pub struct GroupRenameNotificationPB { + #[pb(index = 1)] + pub view_id: String, + + #[pb(index = 2)] + pub group_id: String, +} diff --git a/frontend/rust-lib/flowy-database2/src/event_handler.rs b/frontend/rust-lib/flowy-database2/src/event_handler.rs index 031c409528e5..c5184aafb345 100644 --- a/frontend/rust-lib/flowy-database2/src/event_handler.rs +++ b/frontend/rust-lib/flowy-database2/src/event_handler.rs @@ -792,6 +792,22 @@ pub(crate) async fn update_group_handler( Ok(()) } +#[tracing::instrument(level = "trace", skip_all, err)] +pub(crate) async fn rename_group_handler( + data: AFPluginData, + manager: AFPluginState>, +) -> FlowyResult<()> { + let manager = upgrade_manager(manager)?; + let params: RenameGroupParams = data.into_inner().try_into()?; + let view_id = params.view_id.clone(); + let database_editor = manager.get_database_editor_with_view_id(&view_id).await?; + let group_changeset = GroupChangeset::from(params); + database_editor + .rename_group(&view_id, group_changeset) + .await?; + Ok(()) +} + #[tracing::instrument(level = "debug", skip(data, manager), err)] pub(crate) async fn move_group_handler( data: AFPluginData, diff --git a/frontend/rust-lib/flowy-database2/src/event_map.rs b/frontend/rust-lib/flowy-database2/src/event_map.rs index 5bd9cf6f3398..e668052a73c1 100644 --- a/frontend/rust-lib/flowy-database2/src/event_map.rs +++ b/frontend/rust-lib/flowy-database2/src/event_map.rs @@ -62,6 +62,7 @@ pub fn init(database_manager: Weak) -> AFPlugin { .event(DatabaseEvent::UpdateGroup, update_group_handler) .event(DatabaseEvent::CreateGroup, create_group_handler) .event(DatabaseEvent::DeleteGroup, delete_group_handler) + .event(DatabaseEvent::RenameGroup, rename_group_handler) // Database .event(DatabaseEvent::GetDatabaseMeta, get_database_meta_handler) .event(DatabaseEvent::GetDatabases, get_databases_handler) @@ -301,6 +302,9 @@ pub enum DatabaseEvent { #[event(input = "DeleteGroupPayloadPB")] DeleteGroup = 115, + #[event(input = "RenameGroupPB")] + RenameGroup = 116, + #[event(input = "DatabaseIdPB", output = "DatabaseMetaPB")] GetDatabaseMeta = 119, diff --git a/frontend/rust-lib/flowy-database2/src/notification.rs b/frontend/rust-lib/flowy-database2/src/notification.rs index 227ea74a2ab9..bd79843f352f 100644 --- a/frontend/rust-lib/flowy-database2/src/notification.rs +++ b/frontend/rust-lib/flowy-database2/src/notification.rs @@ -38,6 +38,8 @@ pub enum DatabaseNotification { DidUpdateRowMeta = 67, /// Trigger when the settings of the database are changed DidUpdateSettings = 70, + /// Trigger after a group is renamed + DidRenameGroup = 71, // Trigger when the layout setting of the database is updated DidUpdateLayoutSettings = 80, // Trigger when the layout of the database is changed @@ -78,6 +80,7 @@ impl std::convert::From for DatabaseNotification { 66 => DatabaseNotification::DidReorderSingleRow, 67 => DatabaseNotification::DidUpdateRowMeta, 70 => DatabaseNotification::DidUpdateSettings, + 71 => DatabaseNotification::DidRenameGroup, 80 => DatabaseNotification::DidUpdateLayoutSettings, 82 => DatabaseNotification::DidUpdateDatabaseLayout, 83 => DatabaseNotification::DidDeleteDatabaseView, diff --git a/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs b/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs index d610dfd291be..0c9e44ce96de 100644 --- a/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs +++ b/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs @@ -254,6 +254,12 @@ impl DatabaseEditor { Ok(()) } + pub async fn rename_group(&self, view_id: &str, changeset: GroupChangeset) -> FlowyResult<()> { + let view_editor = self.database_views.get_view_editor(view_id).await?; + view_editor.v_rename_group(changeset).await?; + Ok(()) + } + pub async fn modify_view_filters( &self, view_id: &str, diff --git a/frontend/rust-lib/flowy-database2/src/services/database_view/notifier.rs b/frontend/rust-lib/flowy-database2/src/services/database_view/notifier.rs index 99c3efa45db2..0a7442cc61bc 100644 --- a/frontend/rust-lib/flowy-database2/src/services/database_view/notifier.rs +++ b/frontend/rust-lib/flowy-database2/src/services/database_view/notifier.rs @@ -1,8 +1,9 @@ #![allow(clippy::while_let_loop)] use crate::entities::{ CalculationChangesetNotificationPB, DatabaseViewSettingPB, FilterChangesetNotificationPB, - GroupChangesPB, GroupRowsNotificationPB, InsertedRowPB, ReorderAllRowsPB, ReorderSingleRowPB, - RowMetaPB, RowsChangePB, RowsVisibilityChangePB, SortChangesetNotificationPB, + GroupChangesPB, GroupRenameNotificationPB, GroupRowsNotificationPB, InsertedRowPB, + ReorderAllRowsPB, ReorderSingleRowPB, RowMetaPB, RowsChangePB, RowsVisibilityChangePB, + SortChangesetNotificationPB, }; use crate::notification::{send_notification, DatabaseNotification}; use crate::services::filter::FilterResultNotification; @@ -137,6 +138,15 @@ pub(crate) async fn notify_did_update_num_of_groups(view_id: &str, changeset: Gr .send(); } +pub(crate) async fn notify_did_rename_group( + view_id: &str, + notification: GroupRenameNotificationPB, +) { + send_notification(view_id, DatabaseNotification::DidRenameGroup) + .payload(notification) + .send(); +} + pub(crate) async fn notify_did_update_setting(view_id: &str, setting: DatabaseViewSettingPB) { send_notification(view_id, DatabaseNotification::DidUpdateSettings) .payload(setting) diff --git a/frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs b/frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs index 3734c301845c..14e48681322b 100644 --- a/frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs +++ b/frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs @@ -5,7 +5,7 @@ use std::sync::Arc; use crate::entities::{ CalendarEventPB, CreateRowParams, CreateRowPayloadPB, DatabaseLayoutMetaPB, DatabaseLayoutSettingPB, DeleteSortPayloadPB, FieldSettingsChangesetPB, FieldType, - GroupChangesPB, GroupPB, LayoutSettingChangeset, LayoutSettingParams, + GroupChangesPB, GroupPB, GroupRenameNotificationPB, LayoutSettingChangeset, LayoutSettingParams, RemoveCalculationChangesetPB, ReorderSortPayloadPB, RowMetaPB, RowsChangePB, SortChangesetNotificationPB, SortPB, UpdateCalculationChangesetPB, UpdateSortPayloadPB, }; @@ -24,6 +24,7 @@ use crate::services::database_view::{ notify_did_update_setting, notify_did_update_sort, DatabaseLayoutDepsResolver, DatabaseViewChangedNotifier, DatabaseViewChangedReceiverRunner, }; +use crate::services::field::{MultiSelectTypeOption, SingleSelectTypeOption}; use crate::services::field_settings::FieldSettings; use crate::services::filter::{Filter, FilterChangeset, FilterController}; use crate::services::group::{GroupChangeset, GroupController, MoveGroupRowContext, RowChangeset}; @@ -39,8 +40,8 @@ use lib_infra::util::timestamp; use tokio::sync::{broadcast, RwLock}; use tracing::instrument; -use super::notify_did_update_calculation; use super::view_calculations::make_calculations_controller; +use super::{notify_did_rename_group, notify_did_update_calculation}; pub struct DatabaseViewEditor { database_id: String, @@ -529,6 +530,43 @@ impl DatabaseViewEditor { Ok(()) } + pub async fn v_rename_group(&self, changeset: GroupChangeset) -> FlowyResult<()> { + let mut type_option_data = None; + let (old_field, updated_group) = + if let Some(controller) = self.group_controller.write().await.as_mut() { + let old_field = self + .delegate + .get_field(controller.get_grouping_field_id()) + .await; + let (updated_group, new_type_option) = controller.apply_group_rename(&changeset).await?; + + if new_type_option.is_some() { + type_option_data = new_type_option; + } + + (old_field, Some(updated_group)) + } else { + (None, None) + }; + + if let (Some(old_field), Some(updated_group)) = (old_field, updated_group) { + if let Some(type_option_data) = type_option_data { + self + .delegate + .update_field(type_option_data, old_field) + .await?; + } + + let notification = GroupRenameNotificationPB { + view_id: self.view_id.clone(), + group_id: updated_group.group_id.clone(), + }; + notify_did_rename_group(&self.view_id, notification).await; + } + + Ok(()) + } + pub async fn v_get_all_sorts(&self) -> Vec { self.delegate.get_all_sorts(&self.view_id).await } @@ -863,17 +901,72 @@ impl DatabaseViewEditor { let notification = self.filter_controller.apply_changeset(changeset).await; notify_did_update_filter(notification).await; } - } - // If the id of the grouping field is equal to the updated field's id, then we need to - // update the group setting - if self.is_grouping_field(field_id).await { - self.v_group_by_field(field_id).await?; + // If the id of the grouping field is equal to the updated field's id + // and something critical changed, then we need to update the group setting + if self.is_grouping_field(field_id).await + && self.did_type_options_change(field.field_type.into(), old_field, &field) + { + self.v_group_by_field(field_id).await?; + } } Ok(()) } + /// This method returns true if the field type options have changed enough to warrant + /// updating the group by field in the client. + /// + /// This check should only be done when the field is the grouping field, and will by default + /// return false for fields that don't quality to be grouped by. + /// + fn did_type_options_change( + &self, + field_type: FieldType, + old_field: &Field, + new_field: &Field, + ) -> bool { + if old_field.field_type != new_field.field_type { + return true; + } + + if !field_type.can_be_group() { + return false; + } + + match field_type { + // Checkbox & Url can also be grouped by, but they work differently to select fields + // and thus don't need to be updated when the type option itself changes + FieldType::Checkbox | FieldType::URL => false, + FieldType::SingleSelect => { + let old_field_type_option = old_field.get_type_option::(field_type); + let new_field_type_option = new_field.get_type_option::(field_type); + + if let (Some(old_field_type_option), Some(new_field_type_option)) = + (old_field_type_option, new_field_type_option) + { + return old_field_type_option.options.len() != new_field_type_option.options.len(); + } + + false + }, + FieldType::MultiSelect => { + let old_field_type_option = old_field.get_type_option::(field_type); + let new_field_type_option = new_field.get_type_option::(field_type); + + if let (Some(old_field_type_option), Some(new_field_type_option)) = + (old_field_type_option, new_field_type_option) + { + return old_field_type_option.options.len() != new_field_type_option.options.len(); + } + + false + }, + // All other FieldTypes + _ => false, + } + } + /// Called when a grouping field is updated. #[tracing::instrument(level = "debug", skip_all, err)] pub async fn v_group_by_field(&self, field_id: &str) -> FlowyResult<()> { @@ -900,7 +993,6 @@ impl DatabaseViewEditor { initial_groups: new_groups, ..Default::default() }; - tracing::trace!("notify did group by field1"); debug_assert!(!changeset.is_empty()); if !changeset.is_empty() { @@ -909,7 +1001,6 @@ impl DatabaseViewEditor { .send(); } } - tracing::trace!("notify did group by field2"); *self.group_controller.write().await = new_group_controller; diff --git a/frontend/rust-lib/flowy-database2/src/services/group/action.rs b/frontend/rust-lib/flowy-database2/src/services/group/action.rs index 002fad5a71e7..2ba7ce27d359 100644 --- a/frontend/rust-lib/flowy-database2/src/services/group/action.rs +++ b/frontend/rust-lib/flowy-database2/src/services/group/action.rs @@ -192,6 +192,16 @@ pub trait GroupController: Send + Sync { changesets: &[GroupChangeset], ) -> FlowyResult<(Vec, Option)>; + /// Updates the name of a group. + /// + /// Returns a non-empty `TypeOptionData` when the change require a change + /// in the field type option data. + /// + async fn apply_group_rename( + &mut self, + changeset: &GroupChangeset, + ) -> FlowyResult<(GroupPB, Option)>; + /// Called before the row was created. fn will_create_row(&self, cells: &mut Cells, field: &Field, group_id: &str); } diff --git a/frontend/rust-lib/flowy-database2/src/services/group/controller.rs b/frontend/rust-lib/flowy-database2/src/services/group/controller.rs index d4e702de7059..1a94a51c92d5 100644 --- a/frontend/rust-lib/flowy-database2/src/services/group/controller.rs +++ b/frontend/rust-lib/flowy-database2/src/services/group/controller.rs @@ -429,6 +429,31 @@ where )) } + async fn apply_group_rename( + &mut self, + changeset: &GroupChangeset, + ) -> FlowyResult<(GroupPB, Option)> { + let type_option = self.get_grouping_field_type_option().await.ok_or_else(|| { + FlowyError::internal().with_context("Failed to get grouping field type option") + })?; + + let mut updated_type_option = None; + + if let Some(type_option) = self.update_type_option_when_update_group(changeset, &type_option) { + updated_type_option = Some(type_option); + } + + let updated_group = self + .get_group(&changeset.group_id) + .map(|(_, group)| GroupPB::from(group)) + .ok_or_else(|| FlowyError::internal().with_context("Failed to get group"))?; + + Ok(( + updated_group, + updated_type_option.map(|type_option| type_option.into()), + )) + } + fn will_create_row(&self, cells: &mut Cells, field: &Field, group_id: &str) { ::will_create_row(self, cells, field, group_id); } diff --git a/frontend/rust-lib/flowy-database2/src/services/group/controller_impls/default_controller.rs b/frontend/rust-lib/flowy-database2/src/services/group/controller_impls/default_controller.rs index a973f916747a..317fee022b45 100644 --- a/frontend/rust-lib/flowy-database2/src/services/group/controller_impls/default_controller.rs +++ b/frontend/rust-lib/flowy-database2/src/services/group/controller_impls/default_controller.rs @@ -137,5 +137,12 @@ impl GroupController for DefaultGroupController { Ok((Vec::new(), None)) } + async fn apply_group_rename( + &mut self, + _changeset: &GroupChangeset, + ) -> FlowyResult<(GroupPB, Option)> { + Ok((GroupPB::default(), None)) + } + fn will_create_row(&self, _cells: &mut Cells, _field: &Field, _group_id: &str) {} } diff --git a/frontend/rust-lib/flowy-error/src/code.rs b/frontend/rust-lib/flowy-error/src/code.rs index 85f1cc03034e..dbeb62cf11cc 100644 --- a/frontend/rust-lib/flowy-error/src/code.rs +++ b/frontend/rust-lib/flowy-error/src/code.rs @@ -314,6 +314,9 @@ pub enum ErrorCode { #[error("Upload part size exceeds the limit")] SingleUploadLimitExceeded = 108, + + #[error("Group name is empty")] + GroupNameIsEmpty = 109, } impl ErrorCode {