瀏覽代碼

Merge pull request #440 from AppFlowy-IO/fix_grid_ui_bugs

Fix grid UI bugs
Nathan.fooo 3 年之前
父節點
當前提交
1e997ccaad
共有 18 個文件被更改,包括 200 次插入118 次删除
  1. 66 0
      frontend/app_flowy/lib/workspace/application/grid/field/field_cell_bloc.dart
  2. 12 12
      frontend/app_flowy/lib/workspace/application/grid/field/type_option/edit_select_option_bloc.dart
  3. 14 14
      frontend/app_flowy/lib/workspace/application/grid/field/type_option/field_option_pannel_bloc.dart
  4. 2 2
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_cell.dart
  5. 11 12
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_editor.dart
  6. 32 22
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_cell.dart
  7. 6 6
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_cell_action_sheet.dart
  8. 2 2
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_editor.dart
  9. 1 1
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/grid_header.dart
  10. 6 6
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/date.dart
  11. 10 8
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/edit_option_pannel.dart
  12. 20 18
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/field_option_pannel.dart
  13. 2 2
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/multi_select.dart
  14. 3 3
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/number.dart
  15. 2 2
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/single_select.dart
  16. 4 5
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/widget.dart
  17. 1 1
      frontend/rust-lib/dart-ffi/Cargo.toml
  18. 6 2
      frontend/rust-lib/flowy-grid/src/services/grid_editor.rs

+ 66 - 0
frontend/app_flowy/lib/workspace/application/grid/field/field_cell_bloc.dart

@@ -0,0 +1,66 @@
+import 'package:app_flowy/workspace/application/grid/field/field_listener.dart';
+import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
+import 'package:flowy_sdk/log.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Field;
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:freezed_annotation/freezed_annotation.dart';
+import 'dart:async';
+
+part 'field_cell_bloc.freezed.dart';
+
+class FieldCellBloc extends Bloc<FieldCellEvent, FieldCellState> {
+  final FieldListener _fieldListener;
+
+  FieldCellBloc({
+    required GridFieldCellContext cellContext,
+  })  : _fieldListener = FieldListener(fieldId: cellContext.field.id),
+        super(FieldCellState.initial(cellContext)) {
+    on<FieldCellEvent>(
+      (event, emit) async {
+        await event.map(
+          initial: (_InitialCell value) async {
+            _startListening();
+          },
+          didReceiveFieldUpdate: (_DidReceiveFieldUpdate value) {
+            emit(state.copyWith(field: value.field));
+          },
+        );
+      },
+    );
+  }
+
+  @override
+  Future<void> close() async {
+    await _fieldListener.stop();
+    return super.close();
+  }
+
+  void _startListening() {
+    _fieldListener.updateFieldNotifier.addPublishListener((result) {
+      result.fold(
+        (field) => add(FieldCellEvent.didReceiveFieldUpdate(field)),
+        (err) => Log.error(err),
+      );
+    });
+    _fieldListener.start();
+  }
+}
+
+@freezed
+class FieldCellEvent with _$FieldCellEvent {
+  const factory FieldCellEvent.initial() = _InitialCell;
+  const factory FieldCellEvent.didReceiveFieldUpdate(Field field) = _DidReceiveFieldUpdate;
+}
+
+@freezed
+class FieldCellState with _$FieldCellState {
+  const factory FieldCellState({
+    required String gridId,
+    required Field field,
+  }) = _FieldCellState;
+
+  factory FieldCellState.initial(GridFieldCellContext cellContext) => FieldCellState(
+        gridId: cellContext.gridId,
+        field: cellContext.field,
+      );
+}

+ 12 - 12
frontend/app_flowy/lib/workspace/application/grid/field/type_option/edit_option_bloc.dart → frontend/app_flowy/lib/workspace/application/grid/field/type_option/edit_select_option_bloc.dart

@@ -4,11 +4,11 @@ import 'package:freezed_annotation/freezed_annotation.dart';
 import 'dart:async';
 import 'package:protobuf/protobuf.dart';
 import 'package:dartz/dartz.dart';
-part 'edit_option_bloc.freezed.dart';
+part 'edit_select_option_bloc.freezed.dart';
 
-class EditOptionBloc extends Bloc<EditOptionEvent, EditOptionState> {
-  EditOptionBloc({required SelectOption option}) : super(EditOptionState.initial(option)) {
-    on<EditOptionEvent>(
+class EditSelectOptionBloc extends Bloc<EditSelectOptionEvent, EditSelectOptionState> {
+  EditSelectOptionBloc({required SelectOption option}) : super(EditSelectOptionState.initial(option)) {
+    on<EditSelectOptionEvent>(
       (event, emit) async {
         event.map(
           updateName: (_UpdateName value) {
@@ -46,20 +46,20 @@ class EditOptionBloc extends Bloc<EditOptionEvent, EditOptionState> {
 }
 
 @freezed
-class EditOptionEvent with _$EditOptionEvent {
-  const factory EditOptionEvent.updateName(String name) = _UpdateName;
-  const factory EditOptionEvent.updateColor(SelectOptionColor color) = _UpdateColor;
-  const factory EditOptionEvent.delete() = _Delete;
+class EditSelectOptionEvent with _$EditSelectOptionEvent {
+  const factory EditSelectOptionEvent.updateName(String name) = _UpdateName;
+  const factory EditSelectOptionEvent.updateColor(SelectOptionColor color) = _UpdateColor;
+  const factory EditSelectOptionEvent.delete() = _Delete;
 }
 
 @freezed
-class EditOptionState with _$EditOptionState {
-  const factory EditOptionState({
+class EditSelectOptionState with _$EditSelectOptionState {
+  const factory EditSelectOptionState({
     required SelectOption option,
     required Option<bool> deleted,
-  }) = _EditOptionState;
+  }) = _EditSelectOptionState;
 
-  factory EditOptionState.initial(SelectOption option) => EditOptionState(
+  factory EditSelectOptionState.initial(SelectOption option) => EditSelectOptionState(
         option: option,
         deleted: none(),
       );

+ 14 - 14
frontend/app_flowy/lib/workspace/application/grid/field/type_option/option_pannel_bloc.dart → frontend/app_flowy/lib/workspace/application/grid/field/type_option/field_option_pannel_bloc.dart

@@ -3,11 +3,11 @@ import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'dart:async';
 import 'package:dartz/dartz.dart';
-part 'option_pannel_bloc.freezed.dart';
+part 'field_option_pannel_bloc.freezed.dart';
 
-class OptionPannelBloc extends Bloc<OptionPannelEvent, OptionPannelState> {
-  OptionPannelBloc({required List<SelectOption> options}) : super(OptionPannelState.initial(options)) {
-    on<OptionPannelEvent>(
+class FieldOptionPannelBloc extends Bloc<FieldOptionPannelEvent, FieldOptionPannelState> {
+  FieldOptionPannelBloc({required List<SelectOption> options}) : super(FieldOptionPannelState.initial(options)) {
+    on<FieldOptionPannelEvent>(
       (event, emit) async {
         await event.map(
           createOption: (_CreateOption value) async {
@@ -37,25 +37,25 @@ class OptionPannelBloc extends Bloc<OptionPannelEvent, OptionPannelState> {
 }
 
 @freezed
-class OptionPannelEvent with _$OptionPannelEvent {
-  const factory OptionPannelEvent.createOption(String optionName) = _CreateOption;
-  const factory OptionPannelEvent.beginAddingOption() = _BeginAddingOption;
-  const factory OptionPannelEvent.endAddingOption() = _EndAddingOption;
-  const factory OptionPannelEvent.updateOption(SelectOption option) = _UpdateOption;
-  const factory OptionPannelEvent.deleteOption(SelectOption option) = _DeleteOption;
+class FieldOptionPannelEvent with _$FieldOptionPannelEvent {
+  const factory FieldOptionPannelEvent.createOption(String optionName) = _CreateOption;
+  const factory FieldOptionPannelEvent.beginAddingOption() = _BeginAddingOption;
+  const factory FieldOptionPannelEvent.endAddingOption() = _EndAddingOption;
+  const factory FieldOptionPannelEvent.updateOption(SelectOption option) = _UpdateOption;
+  const factory FieldOptionPannelEvent.deleteOption(SelectOption option) = _DeleteOption;
 }
 
 @freezed
-class OptionPannelState with _$OptionPannelState {
-  const factory OptionPannelState({
+class FieldOptionPannelState with _$FieldOptionPannelState {
+  const factory FieldOptionPannelState({
     required List<SelectOption> options,
     required bool isEditingOption,
     required Option<String> newOptionName,
     required Option<SelectOption> updateOption,
     required Option<SelectOption> deleteOption,
-  }) = _OptionPannelState;
+  }) = _FieldOptionPannelState;
 
-  factory OptionPannelState.initial(List<SelectOption> options) => OptionPannelState(
+  factory FieldOptionPannelState.initial(List<SelectOption> options) => FieldOptionPannelState(
         options: options,
         isEditingOption: false,
         newOptionName: none(),

+ 2 - 2
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_cell.dart

@@ -37,7 +37,7 @@ class _SingleSelectCellState extends State<SingleSelectCell> {
           return SizedBox.expand(
             child: InkWell(
               onTap: () {
-                SelectOptionEditor.show(context, state.cellData, state.options, state.selectedOptions);
+                SelectOptionCellEditor.show(context, state.cellData, state.options, state.selectedOptions);
               },
               child: Row(children: children),
             ),
@@ -86,7 +86,7 @@ class _MultiSelectCellState extends State<MultiSelectCell> {
           return SizedBox.expand(
             child: InkWell(
               onTap: () {
-                SelectOptionEditor.show(context, state.cellData, state.options, state.selectedOptions);
+                SelectOptionCellEditor.show(context, state.cellData, state.options, state.selectedOptions);
               },
               child: Row(children: children),
             ),

+ 11 - 12
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_editor.dart

@@ -18,19 +18,18 @@ import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:easy_localization/easy_localization.dart';
 import 'package:app_flowy/generated/locale_keys.g.dart';
-import 'package:styled_widget/styled_widget.dart';
 import 'package:textfield_tags/textfield_tags.dart';
 
 import 'extension.dart';
 
 const double _editorPannelWidth = 300;
 
-class SelectOptionEditor extends StatelessWidget with FlowyOverlayDelegate {
+class SelectOptionCellEditor extends StatelessWidget with FlowyOverlayDelegate {
   final CellData cellData;
   final List<SelectOption> options;
   final List<SelectOption> selectedOptions;
 
-  const SelectOptionEditor({
+  const SelectOptionCellEditor({
     required this.cellData,
     required this.options,
     required this.selectedOptions,
@@ -38,7 +37,7 @@ class SelectOptionEditor extends StatelessWidget with FlowyOverlayDelegate {
   }) : super(key: key);
 
   static String identifier() {
-    return (SelectOptionEditor).toString();
+    return (SelectOptionCellEditor).toString();
   }
 
   @override
@@ -73,8 +72,8 @@ class SelectOptionEditor extends StatelessWidget with FlowyOverlayDelegate {
     List<SelectOption> options,
     List<SelectOption> selectedOptions,
   ) {
-    SelectOptionEditor.hide(context);
-    final editor = SelectOptionEditor(
+    SelectOptionCellEditor.remove(context);
+    final editor = SelectOptionCellEditor(
       cellData: cellData,
       options: options,
       selectedOptions: selectedOptions,
@@ -86,14 +85,14 @@ class SelectOptionEditor extends StatelessWidget with FlowyOverlayDelegate {
         child: SizedBox(width: _editorPannelWidth, child: editor),
         constraints: BoxConstraints.loose(const Size(_editorPannelWidth, 300)),
       ),
-      identifier: SelectOptionEditor.identifier(),
+      identifier: SelectOptionCellEditor.identifier(),
       anchorContext: context,
       anchorDirection: AnchorDirection.bottomWithCenterAligned,
       delegate: editor,
     );
   }
 
-  static void hide(BuildContext context) {
+  static void remove(BuildContext context) {
     FlowyOverlay.of(context).remove(identifier());
   }
 
@@ -208,7 +207,7 @@ class _SelectOptionCell extends StatelessWidget {
             if (onHover) {
               children.add(FlowyIconButton(
                 width: 30,
-                onPressed: () => _showEditOptionPannel(context),
+                onPressed: () => _showEditPannel(context),
                 iconPadding: const EdgeInsets.fromLTRB(4, 4, 4, 4),
                 icon: svgWidget("editor/details", color: theme.iconColor),
               ));
@@ -224,7 +223,7 @@ class _SelectOptionCell extends StatelessWidget {
     );
   }
 
-  void _showEditOptionPannel(BuildContext context) {
+  void _showEditPannel(BuildContext context) {
     final pannel = EditSelectOptionPannel(
       option: option,
       onDeleted: () {
@@ -233,9 +232,9 @@ class _SelectOptionCell extends StatelessWidget {
       onUpdated: (updatedOption) {
         context.read<SelectOptionEditorBloc>().add(SelectOptionEditorEvent.updateOption(updatedOption));
       },
-      // key: ValueKey(option.id),
+      key: ValueKey(option.id), // Use ValueKey to refresh the UI, otherwise, it will remain the old value.
     );
-    final overlayIdentifier = pannel.toString();
+    final overlayIdentifier = (EditSelectOptionPannel).toString();
 
     FlowyOverlay.of(context).remove(overlayIdentifier);
     FlowyOverlay.of(context).insertWithAnchor(

+ 32 - 22
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_cell.dart

@@ -1,3 +1,4 @@
+import 'package:app_flowy/workspace/application/grid/field/field_cell_bloc.dart';
 import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
 import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
 import 'package:flowy_infra/image.dart';
@@ -12,46 +13,55 @@ import 'field_cell_action_sheet.dart';
 import 'field_editor.dart';
 
 class GridFieldCell extends StatelessWidget {
-  final GridFieldCellContext fieldCellContext;
-  const GridFieldCell(this.fieldCellContext, {Key? key}) : super(key: key);
+  final GridFieldCellContext cellContext;
+  const GridFieldCell(this.cellContext, {Key? key}) : super(key: key);
 
   @override
   Widget build(BuildContext context) {
     final theme = context.watch<AppTheme>();
-    final field = fieldCellContext.field;
-
-    final button = FlowyButton(
-      hoverColor: theme.hover,
-      onTap: () => _showActionSheet(context),
-      rightIcon: svgWidget("editor/details", color: theme.iconColor),
-      leftIcon: svgWidget(field.fieldType.iconName(), color: theme.iconColor),
-      text: FlowyText.medium(field.name, fontSize: 12),
-      padding: GridSize.cellContentInsets,
-    );
 
-    final borderSide = BorderSide(color: theme.shader4, width: 0.4);
-    final decoration = BoxDecoration(border: Border(top: borderSide, right: borderSide, bottom: borderSide));
+    return BlocProvider(
+      create: (context) => FieldCellBloc(cellContext: cellContext)..add(const FieldCellEvent.initial()),
+      child: BlocBuilder<FieldCellBloc, FieldCellState>(
+        builder: (context, state) {
+          final button = FlowyButton(
+            hoverColor: theme.hover,
+            onTap: () => _showActionSheet(context),
+            rightIcon: svgWidget("editor/details", color: theme.iconColor),
+            leftIcon: svgWidget(state.field.fieldType.iconName(), color: theme.iconColor),
+            text: FlowyText.medium(state.field.name, fontSize: 12),
+            padding: GridSize.cellContentInsets,
+          );
+
+          final borderSide = BorderSide(color: theme.shader4, width: 0.4);
+          final decoration = BoxDecoration(border: Border(top: borderSide, right: borderSide, bottom: borderSide));
 
-    return Container(
-      width: field.width.toDouble(),
-      decoration: decoration,
-      child: button,
+          return Container(
+            width: state.field.width.toDouble(),
+            decoration: decoration,
+            child: button,
+          );
+        },
+      ),
     );
   }
 
   void _showActionSheet(BuildContext context) {
+    final state = context.read<FieldCellBloc>().state;
     GridFieldCellActionSheet(
-      fieldCellContext: fieldCellContext,
+      cellContext: GridFieldCellContext(gridId: state.gridId, field: state.field),
       onEdited: () => _showFieldEditor(context),
     ).show(context);
   }
 
   void _showFieldEditor(BuildContext context) {
+    final state = context.read<FieldCellBloc>().state;
+
     FieldEditor(
-      gridId: fieldCellContext.gridId,
+      gridId: state.gridId,
       fieldContextLoader: FieldContextLoaderAdaptor(
-        gridId: fieldCellContext.gridId,
-        field: fieldCellContext.field,
+        gridId: state.gridId,
+        field: state.field,
       ),
     ).show(context);
   }

+ 6 - 6
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_cell_action_sheet.dart

@@ -13,9 +13,9 @@ import 'package:easy_localization/easy_localization.dart';
 import 'package:app_flowy/generated/locale_keys.g.dart';
 
 class GridFieldCellActionSheet extends StatelessWidget with FlowyOverlayDelegate {
-  final GridFieldCellContext fieldCellContext;
+  final GridFieldCellContext cellContext;
   final VoidCallback onEdited;
-  const GridFieldCellActionSheet({required this.fieldCellContext, required this.onEdited, Key? key}) : super(key: key);
+  const GridFieldCellActionSheet({required this.cellContext, required this.onEdited, Key? key}) : super(key: key);
 
   void show(BuildContext overlayContext) {
     FlowyOverlay.of(overlayContext).insertWithAnchor(
@@ -33,7 +33,7 @@ class GridFieldCellActionSheet extends StatelessWidget with FlowyOverlayDelegate
   @override
   Widget build(BuildContext context) {
     return BlocProvider(
-      create: (context) => getIt<FieldActionSheetBloc>(param1: fieldCellContext),
+      create: (context) => getIt<FieldActionSheetBloc>(param1: cellContext),
       child: SingleChildScrollView(
         child: Column(
           children: [
@@ -44,15 +44,15 @@ class GridFieldCellActionSheet extends StatelessWidget with FlowyOverlayDelegate
               },
             ),
             const VSpace(6),
-            _FieldOperationList(fieldCellContext, () => FlowyOverlay.of(context).remove(identifier())),
+            _FieldOperationList(cellContext, () => FlowyOverlay.of(context).remove(identifier())),
           ],
         ),
       ),
     );
   }
 
-  String identifier() {
-    return toString();
+  static String identifier() {
+    return (GridFieldCellActionSheet).toString();
   }
 
   @override

+ 2 - 2
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_editor.dart

@@ -41,8 +41,8 @@ class FieldEditor extends FlowyOverlayDelegate {
     );
   }
 
-  String identifier() {
-    return toString();
+  static String identifier() {
+    return (FieldEditor).toString();
   }
 
   @override

+ 1 - 1
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/grid_header.dart

@@ -58,7 +58,7 @@ class _GridHeaderDelegate extends SliverPersistentHeaderDelegate {
   @override
   bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) {
     if (oldDelegate is _GridHeaderDelegate) {
-      return fields != oldDelegate.fields;
+      return fields.length != oldDelegate.fields.length;
     }
     return true;
   }

+ 6 - 6
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/date.dart

@@ -114,7 +114,7 @@ class DateFormatList extends StatelessWidget {
           dateFormat: format,
           onSelected: (format) {
             onSelected(format);
-            FlowyOverlay.of(context).remove(identifier());
+            FlowyOverlay.of(context).remove(DateFormatList.identifier());
           },
           isSelected: selectedFormat == format);
     }).toList();
@@ -135,8 +135,8 @@ class DateFormatList extends StatelessWidget {
     );
   }
 
-  String identifier() {
-    return toString();
+  static String identifier() {
+    return (DateFormatList).toString();
   }
 }
 
@@ -205,7 +205,7 @@ class TimeFormatList extends StatelessWidget {
           timeFormat: format,
           onSelected: (format) {
             onSelected(format);
-            FlowyOverlay.of(context).remove(identifier());
+            FlowyOverlay.of(context).remove(TimeFormatList.identifier());
           });
     }).toList();
 
@@ -225,8 +225,8 @@ class TimeFormatList extends StatelessWidget {
     );
   }
 
-  String identifier() {
-    return toString();
+  static String identifier() {
+    return (TimeFormatList).toString();
   }
 }
 

+ 10 - 8
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/edit_option_pannel.dart

@@ -1,4 +1,4 @@
-import 'package:app_flowy/workspace/application/grid/field/type_option/edit_option_bloc.dart';
+import 'package:app_flowy/workspace/application/grid/field/type_option/edit_select_option_bloc.dart';
 import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
 import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/extension.dart';
 import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/type_option/widget.dart';
@@ -28,23 +28,23 @@ class EditSelectOptionPannel extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
     return BlocProvider(
-      create: (context) => EditOptionBloc(option: option),
+      create: (context) => EditSelectOptionBloc(option: option),
       child: MultiBlocListener(
         listeners: [
-          BlocListener<EditOptionBloc, EditOptionState>(
+          BlocListener<EditSelectOptionBloc, EditSelectOptionState>(
             listenWhen: (p, c) => p.deleted != c.deleted,
             listener: (context, state) {
               state.deleted.fold(() => null, (_) => onDeleted());
             },
           ),
-          BlocListener<EditOptionBloc, EditOptionState>(
+          BlocListener<EditSelectOptionBloc, EditSelectOptionState>(
             listenWhen: (p, c) => p.option != c.option,
             listener: (context, state) {
               onUpdated(state.option);
             },
           ),
         ],
-        child: BlocBuilder<EditOptionBloc, EditOptionState>(
+        child: BlocBuilder<EditSelectOptionBloc, EditSelectOptionState>(
           builder: (context, state) {
             List<Widget> slivers = [
               SliverToBoxAdapter(child: _OptionNameTextField(state.option.name)),
@@ -82,7 +82,7 @@ class _DeleteTag extends StatelessWidget {
         hoverColor: theme.hover,
         leftIcon: svgWidget("grid/delete", color: theme.iconColor),
         onTap: () {
-          context.read<EditOptionBloc>().add(const EditOptionEvent.delete());
+          context.read<EditSelectOptionBloc>().add(const EditSelectOptionEvent.delete());
         },
       ),
     );
@@ -99,7 +99,9 @@ class _OptionNameTextField extends StatelessWidget {
       name: name,
       onCanceled: () {},
       onDone: (optionName) {
-        context.read<EditOptionBloc>().add(EditOptionEvent.updateName(optionName));
+        if (name != optionName) {
+          context.read<EditSelectOptionBloc>().add(EditSelectOptionEvent.updateName(optionName));
+        }
       },
     );
   }
@@ -178,7 +180,7 @@ class _SelectOptionColorCell extends StatelessWidget {
         leftIcon: colorIcon,
         rightIcon: checkmark,
         onTap: () {
-          context.read<EditOptionBloc>().add(EditOptionEvent.updateColor(color));
+          context.read<EditSelectOptionBloc>().add(EditSelectOptionEvent.updateColor(color));
         },
       ),
     );

+ 20 - 18
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/option_pannel.dart → frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/field_option_pannel.dart

@@ -1,4 +1,4 @@
-import 'package:app_flowy/workspace/application/grid/field/type_option/option_pannel_bloc.dart';
+import 'package:app_flowy/workspace/application/grid/field/type_option/field_option_pannel_bloc.dart';
 import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
 import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_switcher.dart';
 import 'package:flowy_infra/image.dart';
@@ -15,7 +15,7 @@ import 'package:app_flowy/generated/locale_keys.g.dart';
 import 'edit_option_pannel.dart';
 import 'widget.dart';
 
-class OptionPannel extends StatelessWidget {
+class FieldSelectOptionPannel extends StatelessWidget {
   final List<SelectOption> options;
   final VoidCallback beginEdit;
   final Function(String optionName) createOptionCallback;
@@ -23,7 +23,7 @@ class OptionPannel extends StatelessWidget {
   final Function(SelectOption) deleteOptionCallback;
   final TypeOptionOverlayDelegate overlayDelegate;
 
-  const OptionPannel({
+  const FieldSelectOptionPannel({
     required this.options,
     required this.beginEdit,
     required this.createOptionCallback,
@@ -36,8 +36,8 @@ class OptionPannel extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
     return BlocProvider(
-      create: (context) => OptionPannelBloc(options: options),
-      child: BlocConsumer<OptionPannelBloc, OptionPannelState>(
+      create: (context) => FieldOptionPannelBloc(options: options),
+      child: BlocConsumer<FieldOptionPannelBloc, FieldOptionPannelState>(
         listener: (context, state) {
           if (state.isEditingOption) {
             beginEdit();
@@ -88,7 +88,7 @@ class OptionTitle extends StatelessWidget {
   Widget build(BuildContext context) {
     final theme = context.watch<AppTheme>();
 
-    return BlocBuilder<OptionPannelBloc, OptionPannelState>(
+    return BlocBuilder<FieldOptionPannelBloc, FieldOptionPannelState>(
       builder: (context, state) {
         List<Widget> children = [FlowyText.medium(LocaleKeys.grid_field_optionTitle.tr(), fontSize: 12)];
         if (state.options.isNotEmpty) {
@@ -105,7 +105,7 @@ class OptionTitle extends StatelessWidget {
                 ),
                 hoverColor: theme.hover,
                 onTap: () {
-                  context.read<OptionPannelBloc>().add(const OptionPannelEvent.beginAddingOption());
+                  context.read<FieldOptionPannelBloc>().add(const FieldOptionPannelEvent.beginAddingOption());
                 },
               ),
             ),
@@ -127,7 +127,7 @@ class _OptionList extends StatelessWidget {
 
   @override
   Widget build(BuildContext context) {
-    return BlocBuilder<OptionPannelBloc, OptionPannelState>(
+    return BlocBuilder<FieldOptionPannelBloc, FieldOptionPannelState>(
       buildWhen: (previous, current) {
         return previous.options != current.options;
       },
@@ -159,12 +159,13 @@ class _OptionList extends StatelessWidget {
           option: option,
           onDeleted: () {
             delegate.hideOverlay(context);
-            context.read<OptionPannelBloc>().add(OptionPannelEvent.deleteOption(option));
+            context.read<FieldOptionPannelBloc>().add(FieldOptionPannelEvent.deleteOption(option));
           },
           onUpdated: (updatedOption) {
             delegate.hideOverlay(context);
-            context.read<OptionPannelBloc>().add(OptionPannelEvent.updateOption(updatedOption));
+            context.read<FieldOptionPannelBloc>().add(FieldOptionPannelEvent.updateOption(updatedOption));
           },
+          key: ValueKey(option.id),
         );
         delegate.showOverlay(context, pannel);
       },
@@ -208,7 +209,7 @@ class _AddOptionButton extends StatelessWidget {
         text: FlowyText.medium(LocaleKeys.grid_field_addSelectOption.tr(), fontSize: 12),
         hoverColor: theme.hover,
         onTap: () {
-          context.read<OptionPannelBloc>().add(const OptionPannelEvent.beginAddingOption());
+          context.read<FieldOptionPannelBloc>().add(const FieldOptionPannelEvent.beginAddingOption());
         },
         leftIcon: svgWidget("home/add", color: theme.iconColor),
       ),
@@ -222,12 +223,13 @@ class _OptionNameTextField extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
     return NameTextField(
-        name: "",
-        onCanceled: () {
-          context.read<OptionPannelBloc>().add(const OptionPannelEvent.endAddingOption());
-        },
-        onDone: (optionName) {
-          context.read<OptionPannelBloc>().add(OptionPannelEvent.createOption(optionName));
-        });
+      name: "",
+      onCanceled: () {
+        context.read<FieldOptionPannelBloc>().add(const FieldOptionPannelEvent.endAddingOption());
+      },
+      onDone: (optionName) {
+        context.read<FieldOptionPannelBloc>().add(FieldOptionPannelEvent.createOption(optionName));
+      },
+    );
   }
 }

+ 2 - 2
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/multi_select.dart

@@ -5,7 +5,7 @@ import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 
-import 'option_pannel.dart';
+import 'field_option_pannel.dart';
 
 class MultiSelectTypeOptionBuilder extends TypeOptionBuilder {
   final MultiSelectTypeOptionWidget _widget;
@@ -48,7 +48,7 @@ class MultiSelectTypeOptionWidget extends TypeOptionWidget {
           dataDelegate.didUpdateTypeOptionData(state.typeOption.writeToBuffer());
         },
         builder: (context, state) {
-          return OptionPannel(
+          return FieldSelectOptionPannel(
             options: state.typeOption.options,
             beginEdit: () {
               overlayDelegate.hideOverlay(context);

+ 3 - 3
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/number.dart

@@ -81,7 +81,7 @@ class NumberFormatList extends StatelessWidget {
           format: format,
           onSelected: (format) {
             onSelected(format);
-            FlowyOverlay.of(context).remove(identifier());
+            FlowyOverlay.of(context).remove(NumberFormatList.identifier());
           });
     }).toList();
 
@@ -101,8 +101,8 @@ class NumberFormatList extends StatelessWidget {
     );
   }
 
-  String identifier() {
-    return toString();
+  static String identifier() {
+    return (NumberFormatList).toString();
   }
 }
 

+ 2 - 2
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/single_select.dart

@@ -4,7 +4,7 @@ import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header
 import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
-import 'option_pannel.dart';
+import 'field_option_pannel.dart';
 
 class SingleSelectTypeOptionBuilder extends TypeOptionBuilder {
   final SingleSelectTypeOptionWidget _widget;
@@ -47,7 +47,7 @@ class SingleSelectTypeOptionWidget extends TypeOptionWidget {
           dataDelegate.didUpdateTypeOptionData(state.typeOption.writeToBuffer());
         },
         builder: (context, state) {
-          return OptionPannel(
+          return FieldSelectOptionPannel(
             options: state.typeOption.options,
             beginEdit: () {
               overlayDelegate.hideOverlay(context);

+ 4 - 5
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/widget.dart

@@ -60,13 +60,12 @@ class _NameTextFieldState extends State<NameTextField> {
   }
 
   void notifyDidEndEditing() {
-    if (_controller.text.isEmpty) {
-      if (isEdited) {
+    if (!_focusNode.hasFocus) {
+      if (_controller.text.isEmpty) {
         widget.onCanceled();
+      } else {
+        widget.onDone(_controller.text);
       }
-      isEdited = true;
-    } else {
-      widget.onDone(_controller.text);
     }
   }
 }

+ 1 - 1
frontend/rust-lib/dart-ffi/Cargo.toml

@@ -8,7 +8,7 @@ edition = "2018"
 name = "dart_ffi"
 # this value will change depending on the target os
 # default static lib
-crate-type = ["staticlib"]
+crate-type = ["cdylib"]
 
 
 [dependencies]

+ 6 - 2
frontend/rust-lib/flowy-grid/src/services/grid_editor.rs

@@ -58,7 +58,7 @@ impl ClientGridEditor {
             start_field_id,
             grid_id,
         } = params;
-
+        let field_id = field.id.clone();
         let _ = self
             .modify(|grid| {
                 if grid.contain_field(&field.id) {
@@ -84,6 +84,7 @@ impl ClientGridEditor {
             })
             .await?;
         let _ = self.notify_did_update_grid().await?;
+        let _ = self.notify_did_update_field(&field_id).await?;
         Ok(())
     }
 
@@ -113,7 +114,9 @@ impl ClientGridEditor {
     }
 
     pub async fn replace_field(&self, field_meta: FieldMeta) -> FlowyResult<()> {
+        let field_id = field_meta.id.clone();
         let _ = self.modify(|pad| Ok(pad.replace_field(field_meta)?)).await?;
+        let _ = self.notify_did_update_field(&field_id).await?;
         Ok(())
     }
 
@@ -407,12 +410,13 @@ impl ClientGridEditor {
         Ok(())
     }
 
+    #[tracing::instrument(level = "trace", skip_all, err)]
     async fn notify_did_update_field(&self, field_id: &str) -> FlowyResult<()> {
         let mut field_metas = self.get_field_metas(Some(vec![field_id])).await?;
         debug_assert!(field_metas.len() == 1);
 
         if let Some(field_meta) = field_metas.pop() {
-            send_dart_notification(&self.grid_id, GridNotification::DidUpdateField)
+            send_dart_notification(&field_id, GridNotification::DidUpdateField)
                 .payload(field_meta)
                 .send();
         }