Forráskód Böngészése

Merge pull request #485 from AppFlowy-IO/fix_0.0.4_beta3

Fix 0.0.4 beta3
Nathan.fooo 3 éve
szülő
commit
cb0a86893b
46 módosított fájl, 640 hozzáadás és 342 törlés
  1. 1 0
      frontend/app_flowy/assets/translations/en.json
  2. 3 9
      frontend/app_flowy/lib/startup/deps_resolver.dart
  3. 54 18
      frontend/app_flowy/lib/user/application/user_listener.dart
  4. 3 3
      frontend/app_flowy/lib/workspace/application/app/app_bloc.dart
  5. 4 4
      frontend/app_flowy/lib/workspace/application/app/app_listener.dart
  6. 15 16
      frontend/app_flowy/lib/workspace/application/doc/doc_bloc.dart
  7. 54 12
      frontend/app_flowy/lib/workspace/application/grid/cell/selection_editor_bloc.dart
  8. 52 19
      frontend/app_flowy/lib/workspace/application/grid/field/field_editor_bloc.dart
  9. 53 0
      frontend/app_flowy/lib/workspace/application/grid/field/field_editor_pannel_bloc.dart
  10. 0 61
      frontend/app_flowy/lib/workspace/application/grid/field/field_switch_bloc.dart
  11. 1 1
      frontend/app_flowy/lib/workspace/application/grid/prelude.dart
  12. 48 3
      frontend/app_flowy/lib/workspace/application/home/home_bloc.dart
  13. 0 54
      frontend/app_flowy/lib/workspace/application/home/home_listen_bloc.dart
  14. 1 1
      frontend/app_flowy/lib/workspace/application/home/prelude.dart
  15. 5 4
      frontend/app_flowy/lib/workspace/application/menu/menu_user_bloc.dart
  16. 5 11
      frontend/app_flowy/lib/workspace/application/view/view_bloc.dart
  17. 36 14
      frontend/app_flowy/lib/workspace/application/view/view_listener.dart
  18. 3 6
      frontend/app_flowy/lib/workspace/application/workspace/welcome_bloc.dart
  19. 19 20
      frontend/app_flowy/lib/workspace/presentation/home/home_screen.dart
  20. 2 3
      frontend/app_flowy/lib/workspace/presentation/plugins/doc/document.dart
  21. 1 1
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart
  22. 17 2
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell.dart
  23. 23 5
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/extension.dart
  24. 8 1
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_cell.dart
  25. 46 8
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_editor.dart
  26. 5 3
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/text_field.dart
  27. 21 24
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_editor.dart
  28. 12 12
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart
  29. 1 1
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/date.dart
  30. 1 1
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/field_option_pannel.dart
  31. 1 1
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/multi_select.dart
  32. 1 1
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/number.dart
  33. 1 1
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/single_select.dart
  34. 30 2
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/row_detail.dart
  35. 23 6
      frontend/app_flowy/lib/workspace/presentation/plugins/widgets/left_bar_item.dart
  36. 1 1
      frontend/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/flowy_overlay.dart
  37. 2 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-folder/dart_notification.pbenum.dart
  38. 2 1
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-folder/dart_notification.pbjson.dart
  39. 1 0
      frontend/rust-lib/flowy-folder/src/dart_notification.rs
  40. 9 5
      frontend/rust-lib/flowy-folder/src/protobuf/model/dart_notification.rs
  41. 1 0
      frontend/rust-lib/flowy-folder/src/protobuf/proto/dart_notification.proto
  42. 4 1
      frontend/rust-lib/flowy-folder/src/services/view/event_handler.rs
  43. 39 0
      frontend/rust-lib/flowy-folder/src/services/workspace/controller.rs
  44. 18 5
      frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option.rs
  45. 5 1
      frontend/rust-lib/flowy-grid/src/services/grid_editor.rs
  46. 8 0
      shared-lib/flowy-folder-data-model/src/entities/view.rs

+ 1 - 0
frontend/app_flowy/assets/translations/en.json

@@ -181,6 +181,7 @@
       "textPlaceholder": "Empty"
     },
     "selectOption": {
+      "create": "Create",
       "purpleColor": "Purple",
       "pinkColor": "Pink",
       "lightPinkColor": "Light Pink",

+ 3 - 9
frontend/app_flowy/lib/startup/deps_resolver.dart

@@ -7,9 +7,7 @@ import 'package:app_flowy/workspace/application/grid/prelude.dart';
 import 'package:app_flowy/workspace/application/trash/prelude.dart';
 import 'package:app_flowy/workspace/application/workspace/prelude.dart';
 import 'package:app_flowy/workspace/application/edit_pannel/edit_pannel_bloc.dart';
-import 'package:app_flowy/workspace/application/home/home_bloc.dart';
 import 'package:app_flowy/workspace/application/view/prelude.dart';
-import 'package:app_flowy/workspace/application/home/prelude.dart';
 import 'package:app_flowy/workspace/application/menu/prelude.dart';
 import 'package:app_flowy/user/application/prelude.dart';
 import 'package:app_flowy/user/presentation/router.dart';
@@ -17,6 +15,7 @@ import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
 import 'package:app_flowy/workspace/presentation/home/menu/menu.dart';
 import 'package:flowy_sdk/protobuf/flowy-folder-data-model/app.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show EditFieldContext;
 import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/number_type_option.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-user-data-model/user_profile.pb.dart';
@@ -44,7 +43,6 @@ void _resolveUserDeps(GetIt getIt) {
   getIt.registerFactory<SignUpBloc>(() => SignUpBloc(getIt<AuthService>()));
 
   getIt.registerFactory<SplashRoute>(() => SplashRoute());
-  getIt.registerFactory<HomeBloc>(() => HomeBloc());
   getIt.registerFactory<EditPannelBloc>(() => EditPannelBloc());
   getIt.registerFactory<SplashBloc>(() => SplashBloc());
   getIt.registerLazySingleton<NetworkListener>(() => NetworkListener());
@@ -57,10 +55,6 @@ void _resolveHomeDeps(GetIt getIt) {
     (user, _) => UserListener(user: user),
   );
 
-  getIt.registerFactoryParam<HomeListenBloc, UserProfile, void>(
-    (user, _) => HomeListenBloc(getIt<UserListener>(param1: user)),
-  );
-
   //
   getIt.registerLazySingleton<HomeStackManager>(() => HomeStackManager());
 
@@ -201,8 +195,8 @@ void _resolveGridDeps(GetIt getIt) {
     ),
   );
 
-  getIt.registerFactoryParam<FieldSwitcherBloc, SwitchFieldContext, void>(
-    (context, _) => FieldSwitcherBloc(context),
+  getIt.registerFactoryParam<FieldEditorPannelBloc, EditFieldContext, void>(
+    (context, _) => FieldEditorPannelBloc(context),
   );
 
   getIt.registerFactoryParam<DateTypeOptionBloc, DateTypeOption, void>(

+ 54 - 18
frontend/app_flowy/lib/user/application/user_listener.dart

@@ -12,26 +12,55 @@ import 'package:flowy_sdk/protobuf/flowy-user-data-model/user_profile.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-user/dart_notification.pb.dart' as user;
 import 'package:flowy_sdk/rust_stream.dart';
 
-typedef UserProfileUpdatedNotifierValue = Either<UserProfile, FlowyError>;
-typedef AuthNotifierValue = Either<Unit, FlowyError>;
-typedef WorkspaceUpdatedNotifierValue = Either<List<Workspace>, FlowyError>;
+typedef UserProfileNotifyValue = Either<UserProfile, FlowyError>;
+typedef AuthNotifyValue = Either<Unit, FlowyError>;
+typedef WorkspaceListNotifyValue = Either<List<Workspace>, FlowyError>;
+typedef WorkspaceSettingNotifyValue = Either<CurrentWorkspaceSetting, FlowyError>;
 
 class UserListener {
   StreamSubscription<SubscribeObject>? _subscription;
-  final profileUpdatedNotifier = PublishNotifier<UserProfileUpdatedNotifierValue>();
-  final authDidChangedNotifier = PublishNotifier<AuthNotifierValue>();
-  final workspaceUpdatedNotifier = PublishNotifier<WorkspaceUpdatedNotifierValue>();
+  final _profileNotifier = PublishNotifier<UserProfileNotifyValue>();
+  final _authNotifier = PublishNotifier<AuthNotifyValue>();
+  final _workspaceListNotifier = PublishNotifier<WorkspaceListNotifyValue>();
+  final _workSettingNotifier = PublishNotifier<WorkspaceSettingNotifyValue>();
 
   FolderNotificationParser? _workspaceParser;
   UserNotificationParser? _userParser;
-  late UserProfile _user;
+  final UserProfile _user;
   UserListener({
     required UserProfile user,
+  }) : _user = user;
+
+  void start({
+    void Function(AuthNotifyValue)? onAuthChanged,
+    void Function(UserProfileNotifyValue)? onProfileUpdated,
+    void Function(WorkspaceListNotifyValue)? onWorkspaceListUpdated,
+    void Function(WorkspaceSettingNotifyValue)? onWorkspaceSettingUpdated,
   }) {
-    _user = user;
-  }
+    if (onAuthChanged != null) {
+      _authNotifier.addListener(() {
+        onAuthChanged(_authNotifier.currentValue!);
+      });
+    }
+
+    if (onProfileUpdated != null) {
+      _profileNotifier.addListener(() {
+        onProfileUpdated(_profileNotifier.currentValue!);
+      });
+    }
+
+    if (onWorkspaceListUpdated != null) {
+      _workspaceListNotifier.addListener(() {
+        onWorkspaceListUpdated(_workspaceListNotifier.currentValue!);
+      });
+    }
+
+    if (onWorkspaceSettingUpdated != null) {
+      _workSettingNotifier.addListener(() {
+        onWorkspaceSettingUpdated(_workSettingNotifier.currentValue!);
+      });
+    }
 
-  void start() {
     _workspaceParser = FolderNotificationParser(id: _user.token, callback: _notificationCallback);
     _userParser = UserNotificationParser(id: _user.token, callback: _userNotificationCallback);
     _subscription = RustStreamReceiver.listen((observable) {
@@ -44,9 +73,9 @@ class UserListener {
     _workspaceParser = null;
     _userParser = null;
     await _subscription?.cancel();
-    profileUpdatedNotifier.dispose();
-    authDidChangedNotifier.dispose();
-    workspaceUpdatedNotifier.dispose();
+    _profileNotifier.dispose();
+    _authNotifier.dispose();
+    _workspaceListNotifier.dispose();
   }
 
   void _notificationCallback(FolderNotification ty, Either<Uint8List, FlowyError> result) {
@@ -55,16 +84,23 @@ class UserListener {
       case FolderNotification.UserDeleteWorkspace:
       case FolderNotification.WorkspaceListUpdated:
         result.fold(
-          (payload) => workspaceUpdatedNotifier.value = left(RepeatedWorkspace.fromBuffer(payload).items),
-          (error) => workspaceUpdatedNotifier.value = right(error),
+          (payload) => _workspaceListNotifier.value = left(RepeatedWorkspace.fromBuffer(payload).items),
+          (error) => _workspaceListNotifier.value = right(error),
+        );
+        break;
+      case FolderNotification.WorkspaceSetting:
+        result.fold(
+          (payload) => _workSettingNotifier.value = left(CurrentWorkspaceSetting.fromBuffer(payload)),
+          (error) => _workSettingNotifier.value = right(error),
         );
         break;
       case FolderNotification.UserUnauthorized:
         result.fold(
           (_) {},
-          (error) => authDidChangedNotifier.value = right(FlowyError.create()..code = ErrorCode.UserUnauthorized.value),
+          (error) => _authNotifier.value = right(FlowyError.create()..code = ErrorCode.UserUnauthorized.value),
         );
         break;
+
       default:
         break;
     }
@@ -74,8 +110,8 @@ class UserListener {
     switch (ty) {
       case user.UserNotification.UserUnauthorized:
         result.fold(
-          (payload) => profileUpdatedNotifier.value = left(UserProfile.fromBuffer(payload)),
-          (error) => profileUpdatedNotifier.value = right(error),
+          (payload) => _profileNotifier.value = left(UserProfile.fromBuffer(payload)),
+          (error) => _profileNotifier.value = right(error),
         );
         break;
       default:

+ 3 - 3
frontend/app_flowy/lib/workspace/application/app/app_bloc.dart

@@ -41,7 +41,7 @@ class AppBloc extends Bloc<AppEvent, AppState> {
 
   void _startListening() {
     appListener.start(
-      viewsChanged: (result) {
+      onViewsChanged: (result) {
         result.fold(
           (views) {
             if (!isClosed) {
@@ -51,7 +51,7 @@ class AppBloc extends Bloc<AppEvent, AppState> {
           (error) => Log.error(error),
         );
       },
-      appUpdated: (app) {
+      onAppUpdated: (app) {
         if (!isClosed) {
           add(AppEvent.appDidUpdate(app));
         }
@@ -97,7 +97,7 @@ class AppBloc extends Bloc<AppEvent, AppState> {
 
   @override
   Future<void> close() async {
-    await appListener.close();
+    await appListener.stop();
     return super.close();
   }
 

+ 4 - 4
frontend/app_flowy/lib/workspace/application/app/app_listener.dart

@@ -24,9 +24,9 @@ class AppListener {
     required this.appId,
   });
 
-  void start({ViewsDidChangeCallback? viewsChanged, AppDidUpdateCallback? appUpdated}) {
-    _viewsChanged = viewsChanged;
-    _updated = appUpdated;
+  void start({ViewsDidChangeCallback? onViewsChanged, AppDidUpdateCallback? onAppUpdated}) {
+    _viewsChanged = onViewsChanged;
+    _updated = onAppUpdated;
     _parser = FolderNotificationParser(id: appId, callback: _bservableCallback);
     _subscription = RustStreamReceiver.listen((observable) => _parser?.parse(observable));
   }
@@ -60,7 +60,7 @@ class AppListener {
     }
   }
 
-  Future<void> close() async {
+  Future<void> stop() async {
     _parser = null;
     await _subscription?.cancel();
     _viewsChanged = null;

+ 15 - 16
frontend/app_flowy/lib/workspace/application/doc/doc_bloc.dart

@@ -59,7 +59,7 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
 
   @override
   Future<void> close() async {
-    await listener.close();
+    await listener.stop();
 
     if (_subscription != null) {
       await _subscription?.cancel();
@@ -70,21 +70,20 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
   }
 
   Future<void> _initial(Initial value, Emitter<DocumentState> emit) async {
-    listener.deletedNotifier.addPublishListener((result) {
-      result.fold(
-        (view) => add(const DocumentEvent.deleted()),
-        (error) {},
-      );
-    });
-
-    listener.restoredNotifier.addPublishListener((result) {
-      result.fold(
-        (view) => add(const DocumentEvent.restore()),
-        (error) {},
-      );
-    });
-
-    listener.start();
+    listener.start(
+      onViewDeleted: (result) {
+        result.fold(
+          (view) => add(const DocumentEvent.deleted()),
+          (error) {},
+        );
+      },
+      onViewRestored: (result) {
+        result.fold(
+          (view) => add(const DocumentEvent.restore()),
+          (error) {},
+        );
+      },
+    );
     final result = await service.openDocument(docId: view.id);
     result.fold(
       (block) {

+ 54 - 12
frontend/app_flowy/lib/workspace/application/grid/cell/selection_editor_bloc.dart

@@ -1,9 +1,13 @@
-import 'package:app_flowy/workspace/application/grid/cell/cell_service.dart';
+import 'dart:async';
+
+import 'package:dartz/dartz.dart';
 import 'package:flowy_sdk/log.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
-import 'dart:async';
+
+import 'package:app_flowy/workspace/application/grid/cell/cell_service.dart';
+
 import 'select_option_service.dart';
 
 part 'selection_editor_bloc.freezed.dart';
@@ -24,14 +28,19 @@ class SelectOptionEditorBloc extends Bloc<SelectOptionEditorEvent, SelectOptionE
             _startListening();
           },
           didReceiveOptions: (_DidReceiveOptions value) {
+            final result = _makeOptions(state.filter, value.options);
             emit(state.copyWith(
               allOptions: value.options,
-              options: _makeOptions(state.filter, value.options),
+              options: result.options,
+              createOption: result.createOption,
               selectedOptions: value.selectedOptions,
             ));
           },
           newOption: (_NewOption value) {
             _createOption(value.optionName);
+            emit(state.copyWith(
+              filter: none(),
+            ));
           },
           deleteOption: (_DeleteOption value) {
             _deleteOption(value.option);
@@ -91,16 +100,37 @@ class SelectOptionEditorBloc extends Bloc<SelectOptionEditorEvent, SelectOptionE
   }
 
   void _filterOption(String optionName, Emitter<SelectOptionEditorState> emit) {
-    emit(state.copyWith(filter: optionName, options: _makeOptions(optionName, state.allOptions)));
+    final _MakeOptionResult result = _makeOptions(Some(optionName), state.allOptions);
+    emit(state.copyWith(
+      filter: Some(optionName),
+      options: result.options,
+      createOption: result.createOption,
+    ));
   }
 
-  List<SelectOption> _makeOptions(String filter, List<SelectOption> allOptions) {
+  _MakeOptionResult _makeOptions(Option<String> filter, List<SelectOption> allOptions) {
     final List<SelectOption> options = List.from(allOptions);
-    if (filter.isNotEmpty) {
-      options.retainWhere((option) => option.name.toLowerCase().contains(filter.toLowerCase()));
-    }
-
-    return options;
+    Option<String> createOption = filter;
+
+    filter.foldRight(null, (filter, previous) {
+      if (filter.isNotEmpty) {
+        options.retainWhere((option) {
+          final name = option.name.toLowerCase();
+          final lFilter = filter.toLowerCase();
+
+          if (name == lFilter) {
+            createOption = none();
+          }
+
+          return name.contains(lFilter);
+        });
+      }
+    });
+
+    return _MakeOptionResult(
+      options: options,
+      createOption: createOption,
+    );
   }
 
   void _startListening() {
@@ -135,7 +165,8 @@ class SelectOptionEditorState with _$SelectOptionEditorState {
     required List<SelectOption> options,
     required List<SelectOption> allOptions,
     required List<SelectOption> selectedOptions,
-    required String filter,
+    required Option<String> createOption,
+    required Option<String> filter,
   }) = _SelectOptionEditorState;
 
   factory SelectOptionEditorState.initial(GridSelectOptionCellContext context) {
@@ -144,7 +175,18 @@ class SelectOptionEditorState with _$SelectOptionEditorState {
       options: data?.options ?? [],
       allOptions: data?.options ?? [],
       selectedOptions: data?.selectOptions ?? [],
-      filter: "",
+      createOption: none(),
+      filter: none(),
     );
   }
 }
+
+class _MakeOptionResult {
+  List<SelectOption> options;
+  Option<String> createOption;
+
+  _MakeOptionResult({
+    required this.options,
+    required this.createOption,
+  });
+}

+ 52 - 19
frontend/app_flowy/lib/workspace/application/grid/field/field_editor_bloc.dart

@@ -6,6 +6,7 @@ import 'package:freezed_annotation/freezed_annotation.dart';
 import 'dart:async';
 import 'field_service.dart';
 import 'package:dartz/dartz.dart';
+import 'package:protobuf/protobuf.dart';
 
 part 'field_editor_bloc.freezed.dart';
 
@@ -25,10 +26,13 @@ class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
             await _getEditFieldContext(emit);
           },
           updateName: (_UpdateName value) {
-            emit(state.copyWith(fieldName: value.name));
+            final newContext = _updateEditContext(name: value.name);
+            emit(state.copyWith(editFieldContext: newContext));
           },
-          switchField: (_SwitchField value) {
-            emit(state.copyWith(field: Some(value.field), typeOptionData: value.typeOptionData));
+          updateField: (_UpdateField value) {
+            final newContext = _updateEditContext(field: value.field, typeOptionData: value.typeOptionData);
+
+            emit(state.copyWith(editFieldContext: newContext));
           },
           done: (_Done value) async {
             await _saveField(emit);
@@ -43,14 +47,49 @@ class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
     return super.close();
   }
 
+  Option<EditFieldContext> _updateEditContext({
+    String? name,
+    Field? field,
+    List<int>? typeOptionData,
+  }) {
+    return state.editFieldContext.fold(
+      () => none(),
+      (context) {
+        context.freeze();
+        final newContext = context.rebuild((newContext) {
+          newContext.gridField.rebuild((newField) {
+            if (name != null) {
+              newField.name = name;
+            }
+
+            newContext.gridField = newField;
+          });
+
+          if (field != null) {
+            newContext.gridField = field;
+          }
+
+          if (typeOptionData != null) {
+            newContext.typeOptionData = typeOptionData;
+          }
+        });
+        service.insertField(
+          field: newContext.gridField,
+          typeOptionData: newContext.typeOptionData,
+        );
+
+        return Some(newContext);
+      },
+    );
+  }
+
   Future<void> _saveField(Emitter<FieldEditorState> emit) async {
-    await state.field.fold(
+    await state.editFieldContext.fold(
       () async => null,
-      (field) async {
-        field.name = state.fieldName;
+      (context) async {
         final result = await service.insertField(
-          field: field,
-          typeOptionData: state.typeOptionData,
+          field: context.gridField,
+          typeOptionData: context.typeOptionData,
         );
         result.fold((l) => null, (r) => null);
       },
@@ -60,11 +99,9 @@ class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
   Future<void> _getEditFieldContext(Emitter<FieldEditorState> emit) async {
     final result = await _loader.load();
     result.fold(
-      (editContext) {
+      (context) {
         emit(state.copyWith(
-          field: Some(editContext.gridField),
-          typeOptionData: editContext.typeOptionData,
-          fieldName: editContext.gridField.name,
+          editFieldContext: Some(context),
         ));
       },
       (err) => Log.error(err),
@@ -76,25 +113,21 @@ class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
 class FieldEditorEvent with _$FieldEditorEvent {
   const factory FieldEditorEvent.initial() = _InitialField;
   const factory FieldEditorEvent.updateName(String name) = _UpdateName;
-  const factory FieldEditorEvent.switchField(Field field, Uint8List typeOptionData) = _SwitchField;
+  const factory FieldEditorEvent.updateField(Field field, Uint8List typeOptionData) = _UpdateField;
   const factory FieldEditorEvent.done() = _Done;
 }
 
 @freezed
 class FieldEditorState with _$FieldEditorState {
   const factory FieldEditorState({
-    required String fieldName,
     required String gridId,
     required String errorText,
-    required Option<Field> field,
-    required List<int> typeOptionData,
+    required Option<EditFieldContext> editFieldContext,
   }) = _FieldEditorState;
 
   factory FieldEditorState.initial(String gridId) => FieldEditorState(
         gridId: gridId,
-        fieldName: '',
-        field: none(),
+        editFieldContext: none(),
         errorText: '',
-        typeOptionData: List<int>.empty(),
       );
 }

+ 53 - 0
frontend/app_flowy/lib/workspace/application/grid/field/field_editor_pannel_bloc.dart

@@ -0,0 +1,53 @@
+import 'dart:typed_data';
+import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:freezed_annotation/freezed_annotation.dart';
+import 'dart:async';
+
+part 'field_editor_pannel_bloc.freezed.dart';
+
+class FieldEditorPannelBloc extends Bloc<FieldEditorPannelEvent, FieldEditorPannelState> {
+  FieldEditorPannelBloc(EditFieldContext editContext) : super(FieldEditorPannelState.initial(editContext)) {
+    on<FieldEditorPannelEvent>(
+      (event, emit) async {
+        await event.map(
+          toFieldType: (_ToFieldType value) async {
+            emit(state.copyWith(
+              field: value.field,
+              typeOptionData: Uint8List.fromList(value.typeOptionData),
+            ));
+          },
+          didUpdateTypeOptionData: (_DidUpdateTypeOptionData value) {
+            emit(state.copyWith(typeOptionData: value.typeOptionData));
+          },
+        );
+      },
+    );
+  }
+
+  @override
+  Future<void> close() async {
+    return super.close();
+  }
+}
+
+@freezed
+class FieldEditorPannelEvent with _$FieldEditorPannelEvent {
+  const factory FieldEditorPannelEvent.toFieldType(Field field, List<int> typeOptionData) = _ToFieldType;
+  const factory FieldEditorPannelEvent.didUpdateTypeOptionData(Uint8List typeOptionData) = _DidUpdateTypeOptionData;
+}
+
+@freezed
+class FieldEditorPannelState with _$FieldEditorPannelState {
+  const factory FieldEditorPannelState({
+    required String gridId,
+    required Field field,
+    required Uint8List typeOptionData,
+  }) = _FieldEditorPannelState;
+
+  factory FieldEditorPannelState.initial(EditFieldContext context) => FieldEditorPannelState(
+        gridId: context.gridId,
+        field: context.gridField,
+        typeOptionData: Uint8List.fromList(context.typeOptionData),
+      );
+}

+ 0 - 61
frontend/app_flowy/lib/workspace/application/grid/field/field_switch_bloc.dart

@@ -1,61 +0,0 @@
-import 'dart:typed_data';
-import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
-import 'package:flutter_bloc/flutter_bloc.dart';
-import 'package:freezed_annotation/freezed_annotation.dart';
-import 'dart:async';
-
-part 'field_switch_bloc.freezed.dart';
-
-class FieldSwitcherBloc extends Bloc<FieldSwitchEvent, FieldSwitchState> {
-  FieldSwitcherBloc(SwitchFieldContext editContext) : super(FieldSwitchState.initial(editContext)) {
-    on<FieldSwitchEvent>(
-      (event, emit) async {
-        await event.map(
-          toFieldType: (_ToFieldType value) async {
-            emit(state.copyWith(
-              field: value.field,
-              typeOptionData: Uint8List.fromList(value.typeOptionData),
-            ));
-          },
-          didUpdateTypeOptionData: (_DidUpdateTypeOptionData value) {
-            emit(state.copyWith(typeOptionData: value.typeOptionData));
-          },
-        );
-      },
-    );
-  }
-
-  @override
-  Future<void> close() async {
-    return super.close();
-  }
-}
-
-@freezed
-class FieldSwitchEvent with _$FieldSwitchEvent {
-  const factory FieldSwitchEvent.toFieldType(Field field, List<int> typeOptionData) = _ToFieldType;
-  const factory FieldSwitchEvent.didUpdateTypeOptionData(Uint8List typeOptionData) = _DidUpdateTypeOptionData;
-}
-
-@freezed
-class FieldSwitchState with _$FieldSwitchState {
-  const factory FieldSwitchState({
-    required String gridId,
-    required Field field,
-    required Uint8List typeOptionData,
-  }) = _FieldSwitchState;
-
-  factory FieldSwitchState.initial(SwitchFieldContext switchContext) => FieldSwitchState(
-        gridId: switchContext.gridId,
-        field: switchContext.field,
-        typeOptionData: Uint8List.fromList(switchContext.typeOptionData),
-      );
-}
-
-class SwitchFieldContext {
-  final String gridId;
-  final Field field;
-  final List<int> typeOptionData;
-
-  SwitchFieldContext(this.gridId, this.field, this.typeOptionData);
-}

+ 1 - 1
frontend/app_flowy/lib/workspace/application/grid/prelude.dart

@@ -8,7 +8,7 @@ export 'grid_header_bloc.dart';
 export 'field/field_service.dart';
 export 'field/field_action_sheet_bloc.dart';
 export 'field/field_editor_bloc.dart';
-export 'field/field_switch_bloc.dart';
+export 'field/field_editor_pannel_bloc.dart';
 
 // Field Type Option
 export 'field/type_option/date_bloc.dart';

+ 48 - 3
frontend/app_flowy/lib/workspace/application/home/home_bloc.dart

@@ -1,13 +1,36 @@
+import 'package:app_flowy/user/application/user_listener.dart';
 import 'package:app_flowy/workspace/application/edit_pannel/edit_context.dart';
+import 'package:flowy_sdk/log.dart';
+import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-folder-data-model/workspace.pb.dart' show CurrentWorkspaceSetting;
+import 'package:flowy_sdk/protobuf/flowy-user-data-model/errors.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-user-data-model/user_profile.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'package:dartz/dartz.dart';
 part 'home_bloc.freezed.dart';
 
 class HomeBloc extends Bloc<HomeEvent, HomeState> {
-  HomeBloc() : super(HomeState.initial()) {
+  final UserListener _listener;
+
+  HomeBloc(UserProfile user, CurrentWorkspaceSetting workspaceSetting)
+      : _listener = UserListener(user: user),
+        super(HomeState.initial(workspaceSetting)) {
     on<HomeEvent>((event, emit) async {
       await event.map(
+        initial: (_Initial value) {
+          _listener.start(
+            onAuthChanged: (result) {
+              _authDidChanged(result);
+            },
+            onWorkspaceSettingUpdated: (result) {
+              result.fold(
+                (setting) => add(HomeEvent.didReceiveWorkspaceSetting(setting)),
+                (r) => Log.error(r),
+              );
+            },
+          );
+        },
         showLoading: (e) async {
           emit(state.copyWith(isLoading: e.isLoading));
         },
@@ -20,22 +43,40 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
         forceCollapse: (e) async {
           emit(state.copyWith(forceCollapse: e.forceCollapse));
         },
+        didReceiveWorkspaceSetting: (_DidReceiveWorkspaceSetting value) {
+          emit(state.copyWith(workspaceSetting: value.setting));
+        },
+        unauthorized: (_Unauthorized value) {
+          emit(state.copyWith(unauthorized: true));
+        },
       );
     });
   }
 
   @override
-  Future<void> close() {
+  Future<void> close() async {
+    await _listener.stop();
     return super.close();
   }
+
+  void _authDidChanged(Either<Unit, FlowyError> errorOrNothing) {
+    errorOrNothing.fold((_) {}, (error) {
+      if (error.code == ErrorCode.UserUnauthorized.value) {
+        add(HomeEvent.unauthorized(error.msg));
+      }
+    });
+  }
 }
 
 @freezed
 class HomeEvent with _$HomeEvent {
+  const factory HomeEvent.initial() = _Initial;
   const factory HomeEvent.showLoading(bool isLoading) = _ShowLoading;
   const factory HomeEvent.forceCollapse(bool forceCollapse) = _ForceCollapse;
   const factory HomeEvent.setEditPannel(EditPannelContext editContext) = _ShowEditPannel;
   const factory HomeEvent.dismissEditPannel() = _DismissEditPannel;
+  const factory HomeEvent.didReceiveWorkspaceSetting(CurrentWorkspaceSetting setting) = _DidReceiveWorkspaceSetting;
+  const factory HomeEvent.unauthorized(String msg) = _Unauthorized;
 }
 
 @freezed
@@ -44,11 +85,15 @@ class HomeState with _$HomeState {
     required bool isLoading,
     required bool forceCollapse,
     required Option<EditPannelContext> pannelContext,
+    required CurrentWorkspaceSetting workspaceSetting,
+    required bool unauthorized,
   }) = _HomeState;
 
-  factory HomeState.initial() => HomeState(
+  factory HomeState.initial(CurrentWorkspaceSetting workspaceSetting) => HomeState(
         isLoading: false,
         forceCollapse: false,
         pannelContext: none(),
+        workspaceSetting: workspaceSetting,
+        unauthorized: false,
       );
 }

+ 0 - 54
frontend/app_flowy/lib/workspace/application/home/home_listen_bloc.dart

@@ -1,54 +0,0 @@
-import 'package:app_flowy/user/application/user_listener.dart';
-import 'package:flowy_sdk/protobuf/error-code/error_code.pbenum.dart';
-import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
-import 'package:freezed_annotation/freezed_annotation.dart';
-import 'package:flutter_bloc/flutter_bloc.dart';
-import 'package:dartz/dartz.dart';
-part 'home_listen_bloc.freezed.dart';
-
-class HomeListenBloc extends Bloc<HomeListenEvent, HomeListenState> {
-  final UserListener listener;
-  HomeListenBloc(this.listener) : super(const HomeListenState.loading()) {
-    on<HomeListenEvent>((event, emit) async {
-      await event.map(
-        started: (_) async {
-          listener.authDidChangedNotifier.addPublishListener((result) {
-            _authDidChanged(result);
-          });
-          listener.start();
-        },
-        stop: (_) async {},
-        unauthorized: (e) async {
-          emit(HomeListenState.unauthorized(e.msg));
-        },
-      );
-    });
-  }
-
-  @override
-  Future<void> close() async {
-    await listener.stop();
-    super.close();
-  }
-
-  void _authDidChanged(Either<Unit, FlowyError> errorOrNothing) {
-    errorOrNothing.fold((_) {}, (error) {
-      if (error.code == ErrorCode.UserUnauthorized.value) {
-        add(HomeListenEvent.unauthorized(error.msg));
-      }
-    });
-  }
-}
-
-@freezed
-class HomeListenEvent with _$HomeListenEvent {
-  const factory HomeListenEvent.started() = _Started;
-  const factory HomeListenEvent.stop() = _Stop;
-  const factory HomeListenEvent.unauthorized(String msg) = _Unauthorized;
-}
-
-@freezed
-class HomeListenState with _$HomeListenState {
-  const factory HomeListenState.loading() = Loading;
-  const factory HomeListenState.unauthorized(String msg) = Unauthorized;
-}

+ 1 - 1
frontend/app_flowy/lib/workspace/application/home/prelude.dart

@@ -1 +1 @@
-export 'home_listen_bloc.dart';
+

+ 5 - 4
frontend/app_flowy/lib/workspace/application/menu/menu_user_bloc.dart

@@ -19,9 +19,10 @@ class MenuUserBloc extends Bloc<MenuUserEvent, MenuUserState> {
     on<MenuUserEvent>((event, emit) async {
       await event.map(
         initial: (_) async {
-          userListener.profileUpdatedNotifier.addPublishListener(_profileUpdated);
-          userListener.workspaceUpdatedNotifier.addPublishListener(_workspacesUpdated);
-          userListener.start();
+          userListener.start(
+            onProfileUpdated: _profileUpdated,
+            onWorkspaceListUpdated: _workspaceListUpdated,
+          );
           await _initUser();
         },
         fetchWorkspaces: (_FetchWorkspaces value) async {},
@@ -41,7 +42,7 @@ class MenuUserBloc extends Bloc<MenuUserEvent, MenuUserState> {
   }
 
   void _profileUpdated(Either<UserProfile, FlowyError> userOrFailed) {}
-  void _workspacesUpdated(Either<List<Workspace>, FlowyError> workspacesOrFailed) {
+  void _workspaceListUpdated(Either<List<Workspace>, FlowyError> workspacesOrFailed) {
     // fetch workspaces
     // iUserImpl.fetchWorkspaces().then((result) {
     //   result.fold(

+ 5 - 11
frontend/app_flowy/lib/workspace/application/view/view_bloc.dart

@@ -21,12 +21,9 @@ class ViewBloc extends Bloc<ViewEvent, ViewState> {
     on<ViewEvent>((event, emit) async {
       await event.map(
         initial: (e) {
-          // TODO: Listener can be refactored to a stream.
-          listener.updatedNotifier.addPublishListener((result) {
-            // emit.forEach(stream, onData: onData)
+          listener.start(onViewUpdated: (result) {
             add(ViewEvent.viewDidUpdate(result));
           });
-          listener.start();
           emit(state);
         },
         setIsEditing: (e) {
@@ -34,14 +31,12 @@ class ViewBloc extends Bloc<ViewEvent, ViewState> {
         },
         viewDidUpdate: (e) {
           e.result.fold(
-            (view) =>
-                emit(state.copyWith(view: view, successOrFailure: left(unit))),
+            (view) => emit(state.copyWith(view: view, successOrFailure: left(unit))),
             (error) => emit(state.copyWith(successOrFailure: right(error))),
           );
         },
         rename: (e) async {
-          final result =
-              await service.updateView(viewId: view.id, name: e.newName);
+          final result = await service.updateView(viewId: view.id, name: e.newName);
           emit(
             result.fold(
               (l) => state.copyWith(successOrFailure: left(unit)),
@@ -74,7 +69,7 @@ class ViewBloc extends Bloc<ViewEvent, ViewState> {
 
   @override
   Future<void> close() async {
-    await listener.close();
+    await listener.stop();
     return super.close();
   }
 }
@@ -86,8 +81,7 @@ class ViewEvent with _$ViewEvent {
   const factory ViewEvent.rename(String newName) = Rename;
   const factory ViewEvent.delete() = Delete;
   const factory ViewEvent.duplicate() = Duplicate;
-  const factory ViewEvent.viewDidUpdate(Either<View, FlowyError> result) =
-      ViewDidUpdate;
+  const factory ViewEvent.viewDidUpdate(Either<View, FlowyError> result) = ViewDidUpdate;
 }
 
 @freezed

+ 36 - 14
frontend/app_flowy/lib/workspace/application/view/view_listener.dart

@@ -15,9 +15,9 @@ typedef RestoreViewNotifiedValue = Either<View, FlowyError>;
 
 class ViewListener {
   StreamSubscription<SubscribeObject>? _subscription;
-  PublishNotifier<UpdateViewNotifiedValue> updatedNotifier = PublishNotifier<UpdateViewNotifiedValue>();
-  PublishNotifier<DeleteViewNotifyValue> deletedNotifier = PublishNotifier<DeleteViewNotifyValue>();
-  PublishNotifier<RestoreViewNotifiedValue> restoredNotifier = PublishNotifier<RestoreViewNotifiedValue>();
+  final PublishNotifier<UpdateViewNotifiedValue> _updatedViewNotifier = PublishNotifier();
+  final PublishNotifier<DeleteViewNotifyValue> _deletedNotifier = PublishNotifier();
+  final PublishNotifier<RestoreViewNotifiedValue> _restoredNotifier = PublishNotifier();
   FolderNotificationParser? _parser;
   View view;
 
@@ -25,7 +25,29 @@ class ViewListener {
     required this.view,
   });
 
-  void start() {
+  void start({
+    void Function(UpdateViewNotifiedValue)? onViewUpdated,
+    void Function(DeleteViewNotifyValue)? onViewDeleted,
+    void Function(RestoreViewNotifiedValue)? onViewRestored,
+  }) {
+    if (onViewUpdated != null) {
+      _updatedViewNotifier.addListener(() {
+        onViewUpdated(_updatedViewNotifier.currentValue!);
+      });
+    }
+
+    if (onViewDeleted != null) {
+      _deletedNotifier.addListener(() {
+        onViewDeleted(_deletedNotifier.currentValue!);
+      });
+    }
+
+    if (onViewRestored != null) {
+      _restoredNotifier.addListener(() {
+        onViewRestored(_restoredNotifier.currentValue!);
+      });
+    }
+
     _parser = FolderNotificationParser(
       id: view.id,
       callback: (ty, result) {
@@ -40,20 +62,20 @@ class ViewListener {
     switch (ty) {
       case FolderNotification.ViewUpdated:
         result.fold(
-          (payload) => updatedNotifier.value = left(View.fromBuffer(payload)),
-          (error) => updatedNotifier.value = right(error),
+          (payload) => _updatedViewNotifier.value = left(View.fromBuffer(payload)),
+          (error) => _updatedViewNotifier.value = right(error),
         );
         break;
       case FolderNotification.ViewDeleted:
         result.fold(
-          (payload) => deletedNotifier.value = left(View.fromBuffer(payload)),
-          (error) => deletedNotifier.value = right(error),
+          (payload) => _deletedNotifier.value = left(View.fromBuffer(payload)),
+          (error) => _deletedNotifier.value = right(error),
         );
         break;
       case FolderNotification.ViewRestored:
         result.fold(
-          (payload) => restoredNotifier.value = left(View.fromBuffer(payload)),
-          (error) => restoredNotifier.value = right(error),
+          (payload) => _restoredNotifier.value = left(View.fromBuffer(payload)),
+          (error) => _restoredNotifier.value = right(error),
         );
         break;
       default:
@@ -61,11 +83,11 @@ class ViewListener {
     }
   }
 
-  Future<void> close() async {
+  Future<void> stop() async {
     _parser = null;
     await _subscription?.cancel();
-    updatedNotifier.dispose();
-    deletedNotifier.dispose();
-    restoredNotifier.dispose();
+    _updatedViewNotifier.dispose();
+    _deletedNotifier.dispose();
+    _restoredNotifier.dispose();
   }
 }

+ 3 - 6
frontend/app_flowy/lib/workspace/application/workspace/welcome_bloc.dart

@@ -16,8 +16,9 @@ class WelcomeBloc extends Bloc<WelcomeEvent, WelcomeState> {
     on<WelcomeEvent>(
       (event, emit) async {
         await event.map(initial: (e) async {
-          userListener.workspaceUpdatedNotifier.addPublishListener(_workspacesUpdated);
-          userListener.start();
+          userListener.start(
+            onWorkspaceListUpdated: (result) => add(WelcomeEvent.workspacesReveived(result)),
+          );
           //
           await _fetchWorkspaces(emit);
         }, openWorkspace: (e) async {
@@ -74,10 +75,6 @@ class WelcomeBloc extends Bloc<WelcomeEvent, WelcomeState> {
       },
     ));
   }
-
-  void _workspacesUpdated(Either<List<Workspace>, FlowyError> workspacesOrFail) {
-    add(WelcomeEvent.workspacesReveived(workspacesOrFail));
-  }
 }
 
 @freezed

+ 19 - 20
frontend/app_flowy/lib/workspace/presentation/home/home_screen.dart

@@ -1,6 +1,5 @@
 import 'package:app_flowy/plugin/plugin.dart';
 import 'package:app_flowy/workspace/application/home/home_bloc.dart';
-import 'package:app_flowy/workspace/application/home/home_listen_bloc.dart';
 import 'package:app_flowy/workspace/presentation/widgets/edit_pannel/pannel_animation.dart';
 import 'package:app_flowy/workspace/presentation/widgets/float_bubble/question_bubble.dart';
 import 'package:app_flowy/startup/startup.dart';
@@ -46,22 +45,20 @@ class _HomeScreenState extends State<HomeScreen> {
   Widget build(BuildContext context) {
     return MultiBlocProvider(
       providers: [
-        BlocProvider<HomeListenBloc>(
-          create: (context) => getIt<HomeListenBloc>(param1: widget.user)..add(const HomeListenEvent.started()),
+        BlocProvider<HomeBloc>(
+          create: (context) {
+            return HomeBloc(widget.user, widget.workspaceSetting)..add(const HomeEvent.initial());
+          },
         ),
-        BlocProvider<HomeBloc>(create: (context) => getIt<HomeBloc>()),
       ],
       child: Scaffold(
         key: HomeScreen.scaffoldKey,
-        body: BlocListener<HomeListenBloc, HomeListenState>(
+        body: BlocListener<HomeBloc, HomeState>(
+          listenWhen: (p, c) => p.unauthorized != c.unauthorized,
           listener: (context, state) {
-            state.map(
-              loading: (_) {},
-              unauthorized: (unauthorized) {
-                // TODO: push to login screen when user token was invalid
-                Log.error("Push to login screen when user token was invalid");
-              },
-            );
+            if (state.unauthorized) {
+              Log.error("Push to login screen when user token was invalid");
+            }
           },
           child: BlocBuilder<HomeBloc, HomeState>(
             buildWhen: (previous, current) => previous != current,
@@ -73,7 +70,7 @@ class _HomeScreenState extends State<HomeScreen> {
               return FlowyContainer(
                 Theme.of(context).colorScheme.surface,
                 // Colors.white,
-                child: _buildBody(state, context.read<HomeBloc>().state.forceCollapse),
+                child: _buildBody(state),
               );
             },
           ),
@@ -82,14 +79,15 @@ class _HomeScreenState extends State<HomeScreen> {
     );
   }
 
-  Widget _buildBody(HomeState state, bool forceCollapse) {
+  Widget _buildBody(HomeState state) {
     return LayoutBuilder(
       builder: (BuildContext context, BoxConstraints constraints) {
-        final layout = HomeLayout(context, constraints, forceCollapse);
+        final layout = HomeLayout(context, constraints, state.forceCollapse);
         const homeStack = HomeStack();
         final menu = _buildHomeMenu(
           layout: layout,
           context: context,
+          state: state,
         );
         final editPannel = _buildEditPannel(
           homeState: state,
@@ -108,20 +106,21 @@ class _HomeScreenState extends State<HomeScreen> {
     );
   }
 
-  Widget _buildHomeMenu({required HomeLayout layout, required BuildContext context}) {
-    if (initialView == null && widget.workspaceSetting.hasLatestView()) {
-      initialView = widget.workspaceSetting.latestView;
+  Widget _buildHomeMenu({required HomeLayout layout, required BuildContext context, required HomeState state}) {
+    final workspaceSetting = state.workspaceSetting;
+    if (initialView == null && workspaceSetting.hasLatestView()) {
+      initialView = workspaceSetting.latestView;
       final plugin = makePlugin(pluginType: initialView!.pluginType, data: initialView);
       getIt<HomeStackManager>().setPlugin(plugin);
     }
 
     HomeMenu homeMenu = HomeMenu(
       user: widget.user,
-      workspaceSetting: widget.workspaceSetting,
+      workspaceSetting: workspaceSetting,
       collapsedNotifier: getIt<HomeStackManager>().collapsedNotifier,
     );
 
-    final latestView = widget.workspaceSetting.hasLatestView() ? widget.workspaceSetting.latestView : null;
+    final latestView = workspaceSetting.hasLatestView() ? workspaceSetting.latestView : null;
     getIt<MenuSharedState>().latestOpenView = latestView;
 
     return FocusTraversalGroup(child: RepaintBoundary(child: homeMenu));

+ 2 - 3
frontend/app_flowy/lib/workspace/presentation/plugins/doc/document.dart

@@ -60,7 +60,7 @@ class DocumentPlugin implements Plugin {
   DocumentPlugin({required PluginType pluginType, required View view, Key? key}) : _view = view {
     _pluginType = pluginType;
     _listener = getIt<ViewListener>(param1: view);
-    _listener?.updatedNotifier.addPublishListener((result) {
+    _listener?.start(onViewUpdated: (result) {
       result.fold(
         (newView) {
           _view = newView;
@@ -69,12 +69,11 @@ class DocumentPlugin implements Plugin {
         (error) {},
       );
     });
-    _listener?.start();
   }
 
   @override
   void dispose() {
-    _listener?.close();
+    _listener?.stop();
     _listener = null;
   }
 

+ 1 - 1
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart

@@ -23,7 +23,7 @@ GridCellWidget buildGridCellWidget(GridCell gridCell, GridCellCache cellCache, {
     case FieldType.Checkbox:
       return CheckboxCell(cellContextBuilder: cellContextBuilder, key: key);
     case FieldType.DateTime:
-      return DateCell(cellContextBuilder: cellContextBuilder, key: key);
+      return DateCell(cellContextBuilder: cellContextBuilder, key: key, style: style);
     case FieldType.SingleSelect:
       return SingleSelectCell(cellContextBuilder: cellContextBuilder, style: style, key: key);
     case FieldType.MultiSelect:

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

@@ -8,6 +8,12 @@ import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:table_calendar/table_calendar.dart';
 import 'cell_builder.dart';
 
+class DateCellStyle extends GridCellStyle {
+  Alignment alignment;
+
+  DateCellStyle({this.alignment = Alignment.center});
+}
+
 abstract class GridCellDelegate {
   void onFocus(bool isFocus);
   GridCellDelegate get delegate;
@@ -15,11 +21,19 @@ abstract class GridCellDelegate {
 
 class DateCell extends GridCellWidget {
   final GridCellContextBuilder cellContextBuilder;
+  late final DateCellStyle? cellStyle;
 
   DateCell({
+    GridCellStyle? style,
     required this.cellContextBuilder,
     Key? key,
-  }) : super(key: key);
+  }) : super(key: key) {
+    if (style != null) {
+      cellStyle = (style as DateCellStyle);
+    } else {
+      cellStyle = null;
+    }
+  }
 
   @override
   State<DateCell> createState() => _DateCellState();
@@ -37,6 +51,7 @@ class _DateCellState extends State<DateCell> {
 
   @override
   Widget build(BuildContext context) {
+    final alignment = widget.cellStyle != null ? widget.cellStyle!.alignment : Alignment.center;
     return BlocProvider.value(
       value: _cellBloc,
       child: BlocBuilder<DateCellBloc, DateCellState>(
@@ -57,7 +72,7 @@ class _DateCellState extends State<DateCell> {
               child: MouseRegion(
                 opaque: false,
                 cursor: SystemMouseCursors.click,
-                child: Center(child: FlowyText.medium(state.content, fontSize: 12)),
+                child: Align(alignment: alignment, child: FlowyText.medium(state.content, fontSize: 12)),
               ),
             ),
           );

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

@@ -60,17 +60,35 @@ extension SelectOptionColorExtension on SelectOptionColor {
 }
 
 class SelectOptionTag extends StatelessWidget {
-  final SelectOption option;
+  final String name;
+  final Color color;
   final bool isSelected;
-  const SelectOptionTag({required this.option, this.isSelected = false, Key? key}) : super(key: key);
+  const SelectOptionTag({
+    required this.name,
+    required this.color,
+    this.isSelected = false,
+    Key? key,
+  }) : super(key: key);
+
+  factory SelectOptionTag.fromSelectOption({
+    required BuildContext context,
+    required SelectOption option,
+    bool isSelected = false,
+  }) {
+    return SelectOptionTag(
+      name: option.name,
+      color: option.color.make(context),
+      isSelected: isSelected,
+    );
+  }
 
   @override
   Widget build(BuildContext context) {
     return ChoiceChip(
       pressElevation: 1,
-      label: FlowyText.medium(option.name, fontSize: 12),
-      selectedColor: option.color.make(context),
-      backgroundColor: option.color.make(context),
+      label: FlowyText.medium(name, fontSize: 12),
+      selectedColor: color,
+      backgroundColor: color,
       labelPadding: const EdgeInsets.symmetric(horizontal: 6),
       selected: true,
       onSelected: (_) {},

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

@@ -150,7 +150,14 @@ class _SelectOptionCell extends StatelessWidget {
         child: FlowyText.medium(cellStyle!.placeholder, fontSize: 14, color: theme.shader3),
       );
     } else {
-      final tags = selectOptions.map((option) => SelectOptionTag(option: option)).toList();
+      final tags = selectOptions
+          .map(
+            (option) => SelectOptionTag.fromSelectOption(
+              context: context,
+              option: option,
+            ),
+          )
+          .toList();
       child = Align(
         alignment: Alignment.centerLeft,
         child: Wrap(children: tags, spacing: 4, runSpacing: 4),

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

@@ -104,9 +104,18 @@ class _OptionList extends StatelessWidget {
   Widget build(BuildContext context) {
     return BlocBuilder<SelectOptionEditorBloc, SelectOptionEditorState>(
       builder: (context, state) {
-        final cells = state.options.map((option) {
+        List<Widget> cells = [];
+        cells.addAll(state.options.map((option) {
           return _SelectOptionCell(option, state.selectedOptions.contains(option));
-        }).toList();
+        }).toList());
+
+        state.createOption.fold(
+          () => null,
+          (createOption) {
+            cells.add(_CreateOptionCell(name: createOption));
+          },
+        );
+
         final list = ListView.separated(
           shrinkWrap: true,
           controller: ScrollController(),
@@ -119,7 +128,11 @@ class _OptionList extends StatelessWidget {
             return cells[index];
           },
         );
-        return list;
+
+        return Padding(
+          padding: const EdgeInsets.all(3.0),
+          child: list,
+        );
       },
     );
   }
@@ -177,6 +190,30 @@ class _Title extends StatelessWidget {
   }
 }
 
+class _CreateOptionCell extends StatelessWidget {
+  final String name;
+  const _CreateOptionCell({required this.name, Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    final theme = context.watch<AppTheme>();
+    return Row(
+      children: [
+        FlowyText.medium(
+          LocaleKeys.grid_selectOption_create.tr(),
+          fontSize: 12,
+          color: theme.shader3,
+        ),
+        const HSpace(10),
+        SelectOptionTag(
+          name: name,
+          color: theme.shader6,
+        ),
+      ],
+    );
+  }
+}
+
 class _SelectOptionCell extends StatelessWidget {
   final SelectOption option;
   final bool isSelected;
@@ -206,7 +243,11 @@ class _SelectOptionCell extends StatelessWidget {
       style: HoverStyle(hoverColor: theme.hover),
       builder: (_, onHover) {
         List<Widget> children = [
-          SelectOptionTag(option: option, isSelected: isSelected),
+          SelectOptionTag(
+            name: option.name,
+            color: option.color.make(context),
+            isSelected: isSelected,
+          ),
           const Spacer(),
         ];
 
@@ -223,10 +264,7 @@ class _SelectOptionCell extends StatelessWidget {
           ));
         }
 
-        return Padding(
-          padding: const EdgeInsets.all(3.0),
-          child: Row(children: children),
-        );
+        return Row(children: children);
       },
     );
   }

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

@@ -76,7 +76,7 @@ class SelectOptionTextField extends StatelessWidget {
                 borderRadius: Corners.s10Border,
               ),
               isDense: true,
-              prefixIcon: _renderTags(sc),
+              prefixIcon: _renderTags(context, sc),
               hintText: LocaleKeys.grid_selectOption_searchOption.tr(),
               prefixIconConstraints: BoxConstraints(maxWidth: distanceToText),
               focusedBorder: OutlineInputBorder(
@@ -90,12 +90,14 @@ class SelectOptionTextField extends StatelessWidget {
     );
   }
 
-  Widget? _renderTags(ScrollController sc) {
+  Widget? _renderTags(BuildContext context, ScrollController sc) {
     if (selectedOptionMap.isEmpty) {
       return null;
     }
 
-    final children = selectedOptionMap.values.map((option) => SelectOptionTag(option: option)).toList();
+    final children = selectedOptionMap.values
+        .map((option) => SelectOptionTag.fromSelectOption(context: context, option: option))
+        .toList();
     return Padding(
       padding: const EdgeInsets.all(8.0),
       child: SingleChildScrollView(

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

@@ -1,18 +1,15 @@
 import 'package:app_flowy/startup/startup.dart';
 import 'package:app_flowy/workspace/application/grid/field/field_editor_bloc.dart';
 import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
-import 'package:app_flowy/workspace/application/grid/field/field_switch_bloc.dart';
 import 'package:easy_localization/easy_localization.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui.dart';
 import 'package:flowy_infra_ui/style_widget/text.dart';
 import 'package:flowy_infra_ui/widget/spacing.dart';
-import 'package:flowy_sdk/log.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Field;
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:app_flowy/generated/locale_keys.g.dart';
 import 'field_name_input.dart';
-import 'field_switcher.dart';
+import 'field_editor_pannel.dart';
 
 class FieldEditor extends FlowyOverlayDelegate {
   final String gridId;
@@ -30,7 +27,6 @@ class FieldEditor extends FlowyOverlayDelegate {
     BuildContext context, {
     AnchorDirection anchorDirection = AnchorDirection.bottomWithLeftAligned,
   }) {
-    Log.trace("Show $identifier()");
     FlowyOverlay.of(context).remove(identifier());
     FlowyOverlay.of(context).insertWithAnchor(
       widget: OverlayContainer(
@@ -69,16 +65,24 @@ class _FieldEditorWidget extends StatelessWidget {
       value: editorBloc,
       child: BlocBuilder<FieldEditorBloc, FieldEditorState>(
         builder: (context, state) {
-          return state.field.fold(
+          return state.editFieldContext.fold(
             () => const SizedBox(),
-            (field) => ListView(
+            (editFieldContext) => ListView(
               shrinkWrap: true,
               children: [
                 FlowyText.medium(LocaleKeys.grid_field_editProperty.tr(), fontSize: 12),
                 const VSpace(10),
                 const _FieldNameTextField(),
                 const VSpace(10),
-                _renderSwitchButton(context, field, state),
+                FieldEditorPannel(
+                  editFieldContext: editFieldContext,
+                  onSwitchToField: (fieldId, fieldType) {
+                    return fieldContextLoader.switchToField(fieldId, fieldType);
+                  },
+                  onUpdated: (field, typeOptionData) {
+                    context.read<FieldEditorBloc>().add(FieldEditorEvent.updateField(field, typeOptionData));
+                  },
+                ),
               ],
             ),
           );
@@ -86,18 +90,6 @@ class _FieldEditorWidget extends StatelessWidget {
       ),
     );
   }
-
-  Widget _renderSwitchButton(BuildContext context, Field field, FieldEditorState state) {
-    return FieldSwitcher(
-      switchContext: SwitchFieldContext(state.gridId, field, state.typeOptionData),
-      onSwitchToField: (fieldId, fieldType) {
-        return fieldContextLoader.switchToField(fieldId, fieldType);
-      },
-      onUpdated: (field, typeOptionData) {
-        context.read<FieldEditorBloc>().add(FieldEditorEvent.switchField(field, typeOptionData));
-      },
-    );
-  }
 }
 
 class _FieldNameTextField extends StatelessWidget {
@@ -105,11 +97,16 @@ class _FieldNameTextField extends StatelessWidget {
 
   @override
   Widget build(BuildContext context) {
-    return BlocBuilder<FieldEditorBloc, FieldEditorState>(
-      buildWhen: (previous, current) => previous.fieldName != current.fieldName,
-      builder: (context, state) {
+    return BlocSelector<FieldEditorBloc, FieldEditorState, String>(
+      selector: (state) {
+        return state.editFieldContext.fold(
+          () => "",
+          (editFieldContext) => editFieldContext.gridField.name,
+        );
+      },
+      builder: (context, name) {
         return FieldNameTextField(
-          name: state.fieldName,
+          name: name,
           errorText: context.read<FieldEditorBloc>().state.errorText,
           onNameChanged: (newName) {
             context.read<FieldEditorBloc>().add(FieldEditorEvent.updateName(newName));

+ 12 - 12
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_switcher.dart → frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart

@@ -30,30 +30,30 @@ typedef SwitchToFieldCallback = Future<Either<EditFieldContext, FlowyError>> Fun
   FieldType fieldType,
 );
 
-class FieldSwitcher extends StatefulWidget {
-  final SwitchFieldContext switchContext;
+class FieldEditorPannel extends StatefulWidget {
+  final EditFieldContext editFieldContext;
   final UpdateFieldCallback onUpdated;
   final SwitchToFieldCallback onSwitchToField;
 
-  const FieldSwitcher({
-    required this.switchContext,
+  const FieldEditorPannel({
+    required this.editFieldContext,
     required this.onUpdated,
     required this.onSwitchToField,
     Key? key,
   }) : super(key: key);
 
   @override
-  State<FieldSwitcher> createState() => _FieldSwitcherState();
+  State<FieldEditorPannel> createState() => _FieldEditorPannelState();
 }
 
-class _FieldSwitcherState extends State<FieldSwitcher> {
+class _FieldEditorPannelState extends State<FieldEditorPannel> {
   String? currentOverlayIdentifier;
 
   @override
   Widget build(BuildContext context) {
     return BlocProvider(
-      create: (context) => getIt<FieldSwitcherBloc>(param1: widget.switchContext),
-      child: BlocConsumer<FieldSwitcherBloc, FieldSwitchState>(
+      create: (context) => getIt<FieldEditorPannelBloc>(param1: widget.editFieldContext),
+      child: BlocConsumer<FieldEditorPannelBloc, FieldEditorPannelState>(
         listener: (context, state) {
           widget.onUpdated(state.field, state.typeOptionData);
         },
@@ -87,8 +87,8 @@ class _FieldSwitcherState extends State<FieldSwitcher> {
             widget.onSwitchToField(field.id, newFieldType).then((result) {
               result.fold(
                 (editFieldContext) {
-                  context.read<FieldSwitcherBloc>().add(
-                        FieldSwitchEvent.toFieldType(
+                  context.read<FieldEditorPannelBloc>().add(
+                        FieldEditorPannelEvent.toFieldType(
                           editFieldContext.gridField,
                           editFieldContext.typeOptionData,
                         ),
@@ -108,7 +108,7 @@ class _FieldSwitcherState extends State<FieldSwitcher> {
 
   Widget? _typeOptionWidget({
     required BuildContext context,
-    required FieldSwitchState state,
+    required FieldEditorPannelState state,
   }) {
     final overlayDelegate = TypeOptionOverlayDelegate(
       showOverlay: _showOverlay,
@@ -116,7 +116,7 @@ class _FieldSwitcherState extends State<FieldSwitcher> {
     );
 
     final dataDelegate = TypeOptionDataDelegate(didUpdateTypeOptionData: (data) {
-      context.read<FieldSwitcherBloc>().add(FieldSwitchEvent.didUpdateTypeOptionData(data));
+      context.read<FieldEditorPannelBloc>().add(FieldEditorPannelEvent.didUpdateTypeOptionData(data));
     });
 
     final typeOptionContext = TypeOptionContext(

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

@@ -1,7 +1,7 @@
 import 'package:app_flowy/startup/startup.dart';
 import 'package:app_flowy/workspace/application/grid/field/type_option/date_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:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart';
 import 'package:easy_localization/easy_localization.dart' hide DateFormat;
 import 'package:app_flowy/generated/locale_keys.g.dart';
 import 'package:flowy_infra/image.dart';

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

@@ -1,7 +1,7 @@
 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/common/text_field.dart';
-import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_switcher.dart';
+import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra_ui/style_widget/button.dart';

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

@@ -1,6 +1,6 @@
 import 'package:app_flowy/workspace/application/grid/field/type_option/multi_select_bloc.dart';
 import 'package:app_flowy/workspace/application/grid/field/type_option/type_option_service.dart';
-import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_switcher.dart';
+import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 

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

@@ -3,7 +3,7 @@ import 'package:app_flowy/workspace/application/grid/field/type_option/number_bl
 import 'package:app_flowy/workspace/application/grid/field/type_option/number_format_bloc.dart';
 import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
 import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/common/text_field.dart';
-import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_switcher.dart';
+import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui.dart';

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

@@ -1,6 +1,6 @@
 import 'package:app_flowy/workspace/application/grid/field/type_option/single_select_bloc.dart';
 import 'package:app_flowy/workspace/application/grid/field/type_option/type_option_service.dart';
-import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_switcher.dart';
+import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'field_option_pannel.dart';

+ 30 - 2
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/row_detail.dart

@@ -6,9 +6,11 @@ import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.d
 import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/prelude.dart';
 import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_cell.dart';
 import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_editor.dart';
+import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui.dart';
 import 'package:flowy_infra_ui/style_widget/hover.dart';
+import 'package:flowy_infra_ui/style_widget/icon_button.dart';
 import 'package:flowy_infra_ui/style_widget/scrolling/styled_scroll_bar.dart';
 import 'package:flowy_infra_ui/widget/spacing.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show FieldType;
@@ -66,12 +68,36 @@ class _RowDetailPageState extends State<RowDetailPage> {
       },
       child: Padding(
         padding: const EdgeInsets.symmetric(horizontal: 80, vertical: 40),
-        child: _PropertyList(cellCache: widget.cellCache),
+        child: Column(
+          children: [
+            SizedBox(
+                height: 40,
+                child: Row(
+                  children: const [Spacer(), _CloseButton()],
+                )),
+            Expanded(child: _PropertyList(cellCache: widget.cellCache)),
+          ],
+        ),
       ),
     );
   }
 }
 
+class _CloseButton extends StatelessWidget {
+  const _CloseButton({Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    final theme = context.watch<AppTheme>();
+    return FlowyIconButton(
+      width: 24,
+      onPressed: () => FlowyOverlay.of(context).remove(RowDetailPage.identifier()),
+      iconPadding: const EdgeInsets.fromLTRB(2, 2, 2, 2),
+      icon: svgWidget("home/close", color: theme.iconColor),
+    );
+  }
+}
+
 class _PropertyList extends StatelessWidget {
   final GridCellCache cellCache;
   final ScrollController _scrollController;
@@ -165,7 +191,9 @@ GridCellStyle? _buildCellStyle(AppTheme theme, FieldType fieldType) {
     case FieldType.Checkbox:
       return null;
     case FieldType.DateTime:
-      return null;
+      return DateCellStyle(
+        alignment: Alignment.centerLeft,
+      );
     case FieldType.MultiSelect:
       return SelectOptionCellStyle(
         placeholder: LocaleKeys.grid_row_textPlaceholder.tr(),

+ 23 - 6
frontend/app_flowy/lib/workspace/presentation/plugins/widgets/left_bar_item.dart

@@ -1,5 +1,7 @@
+import 'package:app_flowy/workspace/application/view/view_listener.dart';
 import 'package:app_flowy/workspace/application/view/view_service.dart';
 import 'package:flowy_infra/theme.dart';
+import 'package:flowy_sdk/log.dart';
 import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
@@ -16,12 +18,26 @@ class ViewLeftBarItem extends StatefulWidget {
 class _ViewLeftBarItemState extends State<ViewLeftBarItem> {
   final _controller = TextEditingController();
   final _focusNode = FocusNode();
-  late ViewService serviceService;
+  late ViewService _viewService;
+  late ViewListener _viewListener;
+  late View view;
 
   @override
   void initState() {
-    serviceService = ViewService(/*view: widget.view*/);
+    view = widget.view;
+    _viewService = ViewService();
     _focusNode.addListener(_handleFocusChanged);
+    _viewListener = ViewListener(view: widget.view);
+    _viewListener.start(onViewUpdated: (result) {
+      result.fold(
+        (updatedView) {
+          if (mounted) {
+            setState(() => view = updatedView);
+          }
+        },
+        (err) => Log.error(err),
+      );
+    });
     super.initState();
   }
 
@@ -30,12 +46,13 @@ class _ViewLeftBarItemState extends State<ViewLeftBarItem> {
     _controller.dispose();
     _focusNode.removeListener(_handleFocusChanged);
     _focusNode.dispose();
+    _viewListener.stop();
     super.dispose();
   }
 
   @override
   Widget build(BuildContext context) {
-    _controller.text = widget.view.name;
+    _controller.text = view.name;
 
     final theme = context.watch<AppTheme>();
     return IntrinsicWidth(
@@ -63,12 +80,12 @@ class _ViewLeftBarItemState extends State<ViewLeftBarItem> {
 
   void _handleFocusChanged() {
     if (_controller.text.isEmpty) {
-      _controller.text = widget.view.name;
+      _controller.text = view.name;
       return;
     }
 
-    if (_controller.text != widget.view.name) {
-      serviceService.updateView(viewId: widget.view.id, name: _controller.text);
+    if (_controller.text != view.name) {
+      _viewService.updateView(viewId: view.id, name: _controller.text);
     }
   }
 }

+ 1 - 1
frontend/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/flowy_overlay.dart

@@ -176,7 +176,6 @@ class FlowyOverlayState extends State<FlowyOverlay> {
     FlowyOverlayStyle? style,
     Offset? anchorOffset,
   }) {
-    debugPrint("Show overlay: $identifier");
     this.style = style ?? FlowyOverlayStyle();
 
     _showOverlay(
@@ -245,6 +244,7 @@ class FlowyOverlayState extends State<FlowyOverlay> {
     OverlapBehaviour? overlapBehaviour,
     FlowyOverlayDelegate? delegate,
   }) {
+    debugPrint("Show overlay: $identifier");
     Widget overlay = widget;
     final offset = anchorOffset ?? Offset.zero;
 

+ 2 - 0
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-folder/dart_notification.pbenum.dart

@@ -16,6 +16,7 @@ class FolderNotification extends $pb.ProtobufEnum {
   static const FolderNotification WorkspaceUpdated = FolderNotification._(12, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'WorkspaceUpdated');
   static const FolderNotification WorkspaceListUpdated = FolderNotification._(13, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'WorkspaceListUpdated');
   static const FolderNotification WorkspaceAppsChanged = FolderNotification._(14, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'WorkspaceAppsChanged');
+  static const FolderNotification WorkspaceSetting = FolderNotification._(15, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'WorkspaceSetting');
   static const FolderNotification AppUpdated = FolderNotification._(21, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'AppUpdated');
   static const FolderNotification AppViewsChanged = FolderNotification._(24, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'AppViewsChanged');
   static const FolderNotification ViewUpdated = FolderNotification._(31, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ViewUpdated');
@@ -31,6 +32,7 @@ class FolderNotification extends $pb.ProtobufEnum {
     WorkspaceUpdated,
     WorkspaceListUpdated,
     WorkspaceAppsChanged,
+    WorkspaceSetting,
     AppUpdated,
     AppViewsChanged,
     ViewUpdated,

+ 2 - 1
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-folder/dart_notification.pbjson.dart

@@ -18,6 +18,7 @@ const FolderNotification$json = const {
     const {'1': 'WorkspaceUpdated', '2': 12},
     const {'1': 'WorkspaceListUpdated', '2': 13},
     const {'1': 'WorkspaceAppsChanged', '2': 14},
+    const {'1': 'WorkspaceSetting', '2': 15},
     const {'1': 'AppUpdated', '2': 21},
     const {'1': 'AppViewsChanged', '2': 24},
     const {'1': 'ViewUpdated', '2': 31},
@@ -29,4 +30,4 @@ const FolderNotification$json = const {
 };
 
 /// Descriptor for `FolderNotification`. Decode as a `google.protobuf.EnumDescriptorProto`.
-final $typed_data.Uint8List folderNotificationDescriptor = $convert.base64Decode('ChJGb2xkZXJOb3RpZmljYXRpb24SCwoHVW5rbm93bhAAEhcKE1VzZXJDcmVhdGVXb3Jrc3BhY2UQChIXChNVc2VyRGVsZXRlV29ya3NwYWNlEAsSFAoQV29ya3NwYWNlVXBkYXRlZBAMEhgKFFdvcmtzcGFjZUxpc3RVcGRhdGVkEA0SGAoUV29ya3NwYWNlQXBwc0NoYW5nZWQQDhIOCgpBcHBVcGRhdGVkEBUSEwoPQXBwVmlld3NDaGFuZ2VkEBgSDwoLVmlld1VwZGF0ZWQQHxIPCgtWaWV3RGVsZXRlZBAgEhAKDFZpZXdSZXN0b3JlZBAhEhQKEFVzZXJVbmF1dGhvcml6ZWQQZBIRCgxUcmFzaFVwZGF0ZWQQ6Ac=');
+final $typed_data.Uint8List folderNotificationDescriptor = $convert.base64Decode('ChJGb2xkZXJOb3RpZmljYXRpb24SCwoHVW5rbm93bhAAEhcKE1VzZXJDcmVhdGVXb3Jrc3BhY2UQChIXChNVc2VyRGVsZXRlV29ya3NwYWNlEAsSFAoQV29ya3NwYWNlVXBkYXRlZBAMEhgKFFdvcmtzcGFjZUxpc3RVcGRhdGVkEA0SGAoUV29ya3NwYWNlQXBwc0NoYW5nZWQQDhIUChBXb3Jrc3BhY2VTZXR0aW5nEA8SDgoKQXBwVXBkYXRlZBAVEhMKD0FwcFZpZXdzQ2hhbmdlZBAYEg8KC1ZpZXdVcGRhdGVkEB8SDwoLVmlld0RlbGV0ZWQQIBIQCgxWaWV3UmVzdG9yZWQQIRIUChBVc2VyVW5hdXRob3JpemVkEGQSEQoMVHJhc2hVcGRhdGVkEOgH');

+ 1 - 0
frontend/rust-lib/flowy-folder/src/dart_notification.rs

@@ -10,6 +10,7 @@ pub(crate) enum FolderNotification {
     WorkspaceUpdated = 12,
     WorkspaceListUpdated = 13,
     WorkspaceAppsChanged = 14,
+    WorkspaceSetting = 15,
     AppUpdated = 21,
     AppViewsChanged = 24,
     ViewUpdated = 31,

+ 9 - 5
frontend/rust-lib/flowy-folder/src/protobuf/model/dart_notification.rs

@@ -31,6 +31,7 @@ pub enum FolderNotification {
     WorkspaceUpdated = 12,
     WorkspaceListUpdated = 13,
     WorkspaceAppsChanged = 14,
+    WorkspaceSetting = 15,
     AppUpdated = 21,
     AppViewsChanged = 24,
     ViewUpdated = 31,
@@ -53,6 +54,7 @@ impl ::protobuf::ProtobufEnum for FolderNotification {
             12 => ::std::option::Option::Some(FolderNotification::WorkspaceUpdated),
             13 => ::std::option::Option::Some(FolderNotification::WorkspaceListUpdated),
             14 => ::std::option::Option::Some(FolderNotification::WorkspaceAppsChanged),
+            15 => ::std::option::Option::Some(FolderNotification::WorkspaceSetting),
             21 => ::std::option::Option::Some(FolderNotification::AppUpdated),
             24 => ::std::option::Option::Some(FolderNotification::AppViewsChanged),
             31 => ::std::option::Option::Some(FolderNotification::ViewUpdated),
@@ -72,6 +74,7 @@ impl ::protobuf::ProtobufEnum for FolderNotification {
             FolderNotification::WorkspaceUpdated,
             FolderNotification::WorkspaceListUpdated,
             FolderNotification::WorkspaceAppsChanged,
+            FolderNotification::WorkspaceSetting,
             FolderNotification::AppUpdated,
             FolderNotification::AppViewsChanged,
             FolderNotification::ViewUpdated,
@@ -107,14 +110,15 @@ impl ::protobuf::reflect::ProtobufValue for FolderNotification {
 }
 
 static file_descriptor_proto_data: &'static [u8] = b"\
-    \n\x17dart_notification.proto*\x9f\x02\n\x12FolderNotification\x12\x0b\n\
+    \n\x17dart_notification.proto*\xb5\x02\n\x12FolderNotification\x12\x0b\n\
     \x07Unknown\x10\0\x12\x17\n\x13UserCreateWorkspace\x10\n\x12\x17\n\x13Us\
     erDeleteWorkspace\x10\x0b\x12\x14\n\x10WorkspaceUpdated\x10\x0c\x12\x18\
     \n\x14WorkspaceListUpdated\x10\r\x12\x18\n\x14WorkspaceAppsChanged\x10\
-    \x0e\x12\x0e\n\nAppUpdated\x10\x15\x12\x13\n\x0fAppViewsChanged\x10\x18\
-    \x12\x0f\n\x0bViewUpdated\x10\x1f\x12\x0f\n\x0bViewDeleted\x10\x20\x12\
-    \x10\n\x0cViewRestored\x10!\x12\x14\n\x10UserUnauthorized\x10d\x12\x11\n\
-    \x0cTrashUpdated\x10\xe8\x07b\x06proto3\
+    \x0e\x12\x14\n\x10WorkspaceSetting\x10\x0f\x12\x0e\n\nAppUpdated\x10\x15\
+    \x12\x13\n\x0fAppViewsChanged\x10\x18\x12\x0f\n\x0bViewUpdated\x10\x1f\
+    \x12\x0f\n\x0bViewDeleted\x10\x20\x12\x10\n\x0cViewRestored\x10!\x12\x14\
+    \n\x10UserUnauthorized\x10d\x12\x11\n\x0cTrashUpdated\x10\xe8\x07b\x06pr\
+    oto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

+ 1 - 0
frontend/rust-lib/flowy-folder/src/protobuf/proto/dart_notification.proto

@@ -7,6 +7,7 @@ enum FolderNotification {
     WorkspaceUpdated = 12;
     WorkspaceListUpdated = 13;
     WorkspaceAppsChanged = 14;
+    WorkspaceSetting = 15;
     AppUpdated = 21;
     AppViewsChanged = 24;
     ViewUpdated = 31;

+ 4 - 1
frontend/rust-lib/flowy-folder/src/services/view/event_handler.rs

@@ -1,4 +1,5 @@
-use crate::services::AppController;
+use crate::manager::FolderManager;
+use crate::services::{notify_workspace_setting_did_change, AppController};
 use crate::{
     entities::{
         trash::Trash,
@@ -69,10 +70,12 @@ pub(crate) async fn delete_view_handler(
 
 pub(crate) async fn set_latest_view_handler(
     data: Data<ViewId>,
+    folder: AppData<Arc<FolderManager>>,
     controller: AppData<Arc<ViewController>>,
 ) -> Result<(), FlowyError> {
     let view_id: ViewId = data.into_inner();
     let _ = controller.set_latest_view(&view_id.value)?;
+    let _ = notify_workspace_setting_did_change(&folder, &view_id).await?;
     Ok(())
 }
 

+ 39 - 0
frontend/rust-lib/flowy-folder/src/services/workspace/controller.rs

@@ -1,3 +1,4 @@
+use crate::manager::FolderManager;
 use crate::{
     dart_notification::*,
     errors::*,
@@ -190,6 +191,44 @@ impl WorkspaceController {
     }
 }
 
+pub async fn notify_workspace_setting_did_change(
+    folder_manager: &Arc<FolderManager>,
+    view_id: &str,
+) -> FlowyResult<()> {
+    let user_id = folder_manager.user.user_id()?;
+    let token = folder_manager.user.token()?;
+    let workspace_id = get_current_workspace()?;
+
+    let workspace_setting = folder_manager
+        .persistence
+        .begin_transaction(|transaction| {
+            let workspace = folder_manager.workspace_controller.read_local_workspace(
+                workspace_id.clone(),
+                &user_id,
+                &transaction,
+            )?;
+
+            let setting = match transaction.read_view(view_id) {
+                Ok(latest_view) => CurrentWorkspaceSetting {
+                    workspace,
+                    latest_view: Some(latest_view),
+                },
+                Err(_) => CurrentWorkspaceSetting {
+                    workspace,
+                    latest_view: None,
+                },
+            };
+
+            Ok(setting)
+        })
+        .await?;
+
+    send_dart_notification(&token, FolderNotification::WorkspaceSetting)
+        .payload(workspace_setting)
+        .send();
+    Ok(())
+}
+
 const CURRENT_WORKSPACE_ID: &str = "current_workspace_id";
 
 pub fn set_current_workspace(workspace_id: &str) {

+ 18 - 5
frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option.rs

@@ -85,7 +85,17 @@ impl CellDataOperation for NumberTypeOption {
 
             let cell_data = type_option_cell_data.data;
             match self.format {
-                NumberFormat::Number => cell_data.parse::<i64>().map_or(String::new(), |v| v.to_string()),
+                NumberFormat::Number => {
+                    if let Ok(v) = cell_data.parse::<f64>() {
+                        return v.to_string();
+                    }
+
+                    if let Ok(v) = cell_data.parse::<i64>() {
+                        return v.to_string();
+                    }
+
+                    return String::new();
+                }
                 NumberFormat::Percent => cell_data.parse::<f64>().map_or(String::new(), |v| v.to_string()),
                 _ => self.money_from_str(&cell_data),
             }
@@ -100,10 +110,13 @@ impl CellDataOperation for NumberTypeOption {
         _cell_meta: Option<CellMeta>,
     ) -> Result<String, FlowyError> {
         let changeset = changeset.into();
-        let data = self.strip_symbol(changeset);
+        let mut data = changeset.trim().to_string();
 
-        if !data.chars().all(char::is_numeric) {
-            return Err(FlowyError::invalid_data().context("Should only contain numbers"));
+        if self.format != NumberFormat::Number {
+            data = self.strip_symbol(data);
+            if !data.chars().all(char::is_numeric) {
+                return Err(FlowyError::invalid_data().context("Should only contain numbers"));
+            }
         }
 
         Ok(TypeOptionCellData::new(&data, self.field_type()).json())
@@ -157,7 +170,7 @@ impl NumberTypeOption {
     }
 }
 
-#[derive(Clone, Copy, Debug, EnumIter, Serialize, Deserialize, ProtoBuf_Enum)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, EnumIter, Serialize, Deserialize, ProtoBuf_Enum)]
 pub enum NumberFormat {
     Number = 0,
     USD = 1,

+ 5 - 1
frontend/rust-lib/flowy-grid/src/services/grid_editor.rs

@@ -316,7 +316,11 @@ impl ClientGridEditor {
 
         let cell_data_changeset = changeset.data.unwrap();
         let cell_meta = self.get_cell_meta(&changeset.row_id, &changeset.field_id).await?;
-        tracing::trace!("{}: {:?}", &changeset.field_id, cell_meta);
+        tracing::trace!(
+            "field changeset: id:{} / value:{}",
+            &changeset.field_id,
+            cell_data_changeset
+        );
         match self.grid_pad.read().await.get_field_meta(&changeset.field_id) {
             None => {
                 let msg = format!("Field not found with id: {}", &changeset.field_id);

+ 8 - 0
shared-lib/flowy-folder-data-model/src/entities/view.rs

@@ -201,6 +201,14 @@ impl std::convert::From<&str> for ViewId {
     }
 }
 
+impl std::ops::Deref for ViewId {
+    type Target = str;
+
+    fn deref(&self) -> &Self::Target {
+        &self.value
+    }
+}
+
 #[derive(Default, ProtoBuf)]
 pub struct RepeatedViewId {
     #[pb(index = 1)]