浏览代码

chore: listen on cell update

appflowy 3 年之前
父节点
当前提交
6a36e2bcfd
共有 24 个文件被更改,包括 640 次插入117 次删除
  1. 19 0
      frontend/app_flowy/lib/core/notification_helper.dart
  2. 40 0
      frontend/app_flowy/lib/workspace/application/grid/cell_bloc/cell_listener.dart
  3. 15 2
      frontend/app_flowy/lib/workspace/application/grid/cell_bloc/selection_cell_bloc.dart
  4. 9 14
      frontend/app_flowy/lib/workspace/application/grid/field/field_listener.dart
  5. 6 14
      frontend/app_flowy/lib/workspace/application/grid/field/grid_listenr.dart
  6. 6 14
      frontend/app_flowy/lib/workspace/application/grid/grid_block_service.dart
  7. 0 1
      frontend/app_flowy/lib/workspace/application/grid/row/row_bloc.dart
  8. 4 15
      frontend/app_flowy/lib/workspace/application/grid/row/row_listener.dart
  9. 0 2
      frontend/app_flowy/lib/workspace/application/grid/row/row_service.dart
  10. 12 14
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/grid_row.dart
  11. 102 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pb.dart
  12. 16 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pbjson.dart
  13. 2 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/dart_notification.pbenum.dart
  14. 2 1
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/dart_notification.pbjson.dart
  15. 1 1
      frontend/rust-lib/dart-ffi/Cargo.toml
  16. 1 0
      frontend/rust-lib/flowy-grid/src/dart_notification.rs
  17. 1 1
      frontend/rust-lib/flowy-grid/src/event_handler.rs
  18. 7 3
      frontend/rust-lib/flowy-grid/src/protobuf/model/dart_notification.rs
  19. 1 0
      frontend/rust-lib/flowy-grid/src/protobuf/proto/dart_notification.proto
  20. 21 5
      frontend/rust-lib/flowy-grid/src/services/block_meta_editor.rs
  21. 1 6
      frontend/rust-lib/flowy-grid/src/services/grid_editor.rs
  22. 19 4
      shared-lib/flowy-grid-data-model/src/entities/grid.rs
  23. 349 20
      shared-lib/flowy-grid-data-model/src/protobuf/model/grid.rs
  24. 6 0
      shared-lib/flowy-grid-data-model/src/protobuf/proto/grid.proto

+ 19 - 0
frontend/app_flowy/lib/core/notification_helper.dart

@@ -1,3 +1,4 @@
+import 'dart:async';
 import 'dart:typed_data';
 import 'package:flowy_sdk/protobuf/dart-notify/protobuf.dart';
 import 'package:flowy_sdk/protobuf/flowy-user/protobuf.dart';
@@ -5,6 +6,7 @@ import 'package:dartz/dartz.dart';
 import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-folder/dart_notification.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/dart_notification.pb.dart';
+import 'package:flowy_sdk/rust_stream.dart';
 
 // User
 typedef UserNotificationCallback = void Function(UserNotification, Either<Uint8List, FlowyError>);
@@ -45,6 +47,23 @@ class GridNotificationParser extends NotificationParser<GridNotification, FlowyE
         );
 }
 
+typedef GridNotificationHandler = Function(GridNotification ty, Either<Uint8List, FlowyError> result);
+
+class GridNotificationListener {
+  StreamSubscription<SubscribeObject>? _subscription;
+  GridNotificationParser? _parser;
+
+  GridNotificationListener({required String objectId, required GridNotificationHandler handler})
+      : _parser = GridNotificationParser(id: objectId, callback: handler) {
+    _subscription = RustStreamReceiver.listen((observable) => _parser?.parse(observable));
+  }
+
+  Future<void> stop() async {
+    _parser = null;
+    await _subscription?.cancel();
+  }
+}
+
 class NotificationParser<T, E> {
   String? id;
   void Function(T, Either<Uint8List, E>) callback;

+ 40 - 0
frontend/app_flowy/lib/workspace/application/grid/cell_bloc/cell_listener.dart

@@ -0,0 +1,40 @@
+import 'package:dartz/dartz.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/dart_notification.pb.dart';
+import 'package:flowy_infra/notifier.dart';
+import 'dart:async';
+import 'dart:typed_data';
+import 'package:app_flowy/core/notification_helper.dart';
+
+typedef UpdateFieldNotifiedValue = Either<CellNotificationData, FlowyError>;
+
+class CellListener {
+  final String rowId;
+  final String fieldId;
+  PublishNotifier<UpdateFieldNotifiedValue> updateCellNotifier = PublishNotifier();
+  GridNotificationListener? _listener;
+  CellListener({required this.rowId, required this.fieldId});
+
+  void start() {
+    _listener = GridNotificationListener(objectId: "$rowId:$fieldId", handler: _handler);
+  }
+
+  void _handler(GridNotification ty, Either<Uint8List, FlowyError> result) {
+    switch (ty) {
+      case GridNotification.DidUpdateCell:
+        result.fold(
+          (payload) => updateCellNotifier.value = left(CellNotificationData.fromBuffer(payload)),
+          (error) => updateCellNotifier.value = right(error),
+        );
+        break;
+      default:
+        break;
+    }
+  }
+
+  Future<void> stop() async {
+    await _listener?.stop();
+    updateCellNotifier.dispose();
+  }
+}

+ 15 - 2
frontend/app_flowy/lib/workspace/application/grid/cell_bloc/selection_cell_bloc.dart

@@ -1,7 +1,6 @@
-import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
+import 'package:app_flowy/workspace/application/grid/cell_bloc/cell_listener.dart';
 import 'package:app_flowy/workspace/application/grid/row/row_service.dart';
 import 'package:flowy_sdk/log.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid-data-model/meta.pb.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';
@@ -12,17 +11,20 @@ part 'selection_cell_bloc.freezed.dart';
 
 class SelectionCellBloc extends Bloc<SelectionCellEvent, SelectionCellState> {
   final CellService _service;
+  final CellListener _listener;
 
   SelectionCellBloc({
     required CellService service,
     required CellData cellData,
   })  : _service = service,
+        _listener = CellListener(rowId: cellData.rowId, fieldId: cellData.field.id),
         super(SelectionCellState.initial(cellData)) {
     on<SelectionCellEvent>(
       (event, emit) async {
         await event.map(
           initial: (_InitialCell value) async {
             _loadOptions();
+            _startListening();
           },
           didReceiveOptions: (_DidReceiveOptions value) {
             emit(state.copyWith(options: value.options, selectedOptions: value.selectedOptions));
@@ -34,6 +36,7 @@ class SelectionCellBloc extends Bloc<SelectionCellEvent, SelectionCellState> {
 
   @override
   Future<void> close() async {
+    await _listener.stop();
     return super.close();
   }
 
@@ -52,6 +55,16 @@ class SelectionCellBloc extends Bloc<SelectionCellEvent, SelectionCellState> {
       (err) => Log.error(err),
     );
   }
+
+  void _startListening() {
+    _listener.updateCellNotifier.addPublishListener((result) {
+      result.fold(
+        (notificationData) => _loadOptions(),
+        (err) => Log.error(err),
+      );
+    });
+    _listener.start();
+  }
 }
 
 @freezed

+ 9 - 14
frontend/app_flowy/lib/workspace/application/grid/field/field_listener.dart

@@ -1,9 +1,7 @@
 import 'package:dartz/dartz.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
-import 'package:flowy_sdk/protobuf/dart-notify/subject.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/dart_notification.pb.dart';
-import 'package:flowy_sdk/rust_stream.dart';
 import 'package:flowy_infra/notifier.dart';
 import 'dart:async';
 import 'dart:typed_data';
@@ -14,23 +12,21 @@ typedef UpdateFieldNotifiedValue = Either<Field, FlowyError>;
 class FieldListener {
   final String fieldId;
   PublishNotifier<UpdateFieldNotifiedValue> updateFieldNotifier = PublishNotifier();
-  StreamSubscription<SubscribeObject>? _subscription;
-  GridNotificationParser? _parser;
+  GridNotificationListener? _listener;
 
   FieldListener({required this.fieldId});
 
   void start() {
-    _parser = GridNotificationParser(
-      id: fieldId,
-      callback: (ty, result) {
-        _handleObservableType(ty, result);
-      },
+    _listener = GridNotificationListener(
+      objectId: fieldId,
+      handler: _handler,
     );
-
-    _subscription = RustStreamReceiver.listen((observable) => _parser?.parse(observable));
   }
 
-  void _handleObservableType(GridNotification ty, Either<Uint8List, FlowyError> result) {
+  void _handler(
+    GridNotification ty,
+    Either<Uint8List, FlowyError> result,
+  ) {
     switch (ty) {
       case GridNotification.DidUpdateField:
         result.fold(
@@ -44,8 +40,7 @@ class FieldListener {
   }
 
   Future<void> stop() async {
-    _parser = null;
-    await _subscription?.cancel();
+    await _listener?.stop();
     updateFieldNotifier.dispose();
   }
 }

+ 6 - 14
frontend/app_flowy/lib/workspace/application/grid/field/grid_listenr.dart

@@ -1,9 +1,7 @@
 import 'package:dartz/dartz.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
-import 'package:flowy_sdk/protobuf/dart-notify/subject.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/dart_notification.pb.dart';
-import 'package:flowy_sdk/rust_stream.dart';
 import 'package:flowy_infra/notifier.dart';
 import 'dart:async';
 import 'dart:typed_data';
@@ -14,22 +12,17 @@ typedef UpdateFieldNotifiedValue = Either<List<Field>, FlowyError>;
 class GridFieldsListener {
   final String gridId;
   PublishNotifier<UpdateFieldNotifiedValue> updateFieldsNotifier = PublishNotifier();
-  StreamSubscription<SubscribeObject>? _subscription;
-  GridNotificationParser? _parser;
+  GridNotificationListener? _listener;
   GridFieldsListener({required this.gridId});
 
   void start() {
-    _parser = GridNotificationParser(
-      id: gridId,
-      callback: (ty, result) {
-        _handleObservableType(ty, result);
-      },
+    _listener = GridNotificationListener(
+      objectId: gridId,
+      handler: _handler,
     );
-
-    _subscription = RustStreamReceiver.listen((observable) => _parser?.parse(observable));
   }
 
-  void _handleObservableType(GridNotification ty, Either<Uint8List, FlowyError> result) {
+  void _handler(GridNotification ty, Either<Uint8List, FlowyError> result) {
     switch (ty) {
       case GridNotification.DidUpdateFields:
         result.fold(
@@ -43,8 +36,7 @@ class GridFieldsListener {
   }
 
   Future<void> stop() async {
-    _parser = null;
-    await _subscription?.cancel();
+    await _listener?.stop();
     updateFieldsNotifier.dispose();
   }
 }

+ 6 - 14
frontend/app_flowy/lib/workspace/application/grid/grid_block_service.dart

@@ -3,10 +3,8 @@ import 'package:dartz/dartz.dart';
 import 'package:flowy_sdk/dispatch/dispatch.dart';
 import 'package:flowy_sdk/log.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
-import 'package:flowy_sdk/protobuf/dart-notify/subject.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/dart_notification.pb.dart';
-import 'package:flowy_sdk/rust_stream.dart';
 import 'package:flowy_infra/notifier.dart';
 import 'dart:async';
 import 'dart:typed_data';
@@ -62,23 +60,18 @@ class GridBlockService {
 class GridBlockListener {
   final String gridId;
   PublishNotifier<Either<List<GridBlockOrder>, FlowyError>> blockUpdateNotifier = PublishNotifier(comparable: null);
-  StreamSubscription<SubscribeObject>? _subscription;
-  GridNotificationParser? _parser;
+  GridNotificationListener? _listener;
 
   GridBlockListener({required this.gridId});
 
   void start() {
-    _parser = GridNotificationParser(
-      id: gridId,
-      callback: (ty, result) {
-        _handleObservableType(ty, result);
-      },
+    _listener = GridNotificationListener(
+      objectId: gridId,
+      handler: _handler,
     );
-
-    _subscription = RustStreamReceiver.listen((observable) => _parser?.parse(observable));
   }
 
-  void _handleObservableType(GridNotification ty, Either<Uint8List, FlowyError> result) {
+  void _handler(GridNotification ty, Either<Uint8List, FlowyError> result) {
     switch (ty) {
       case GridNotification.DidUpdateBlock:
         result.fold(
@@ -93,8 +86,7 @@ class GridBlockListener {
   }
 
   Future<void> stop() async {
-    _parser = null;
-    await _subscription?.cancel();
+    await _listener?.stop();
     blockUpdateNotifier.dispose();
   }
 }

+ 0 - 1
frontend/app_flowy/lib/workspace/application/grid/row/row_bloc.dart

@@ -114,7 +114,6 @@ class RowBloc extends Bloc<RowEvent, RowState> {
         map[field.id] = CellData(
           rowId: row.id,
           gridId: rowService.gridId,
-          blockId: rowService.blockId,
           cell: row.cellByFieldId[field.id],
           field: field,
         );

+ 4 - 15
frontend/app_flowy/lib/workspace/application/grid/row/row_listener.dart

@@ -1,8 +1,6 @@
-import 'package:flowy_sdk/protobuf/dart-notify/subject.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/dart_notification.pb.dart';
-import 'package:flowy_sdk/rust_stream.dart';
 import 'package:flowy_infra/notifier.dart';
 import 'dart:async';
 import 'dart:typed_data';
@@ -15,23 +13,15 @@ typedef UpdateFieldNotifiedValue = Either<List<Field>, FlowyError>;
 class RowListener {
   final String rowId;
   PublishNotifier<UpdateRowNotifiedValue> updateRowNotifier = PublishNotifier();
-  StreamSubscription<SubscribeObject>? _subscription;
-  GridNotificationParser? _parser;
+  GridNotificationListener? _listener;
 
   RowListener({required this.rowId});
 
   void start() {
-    _parser = GridNotificationParser(
-      id: rowId,
-      callback: (ty, result) {
-        _handleObservableType(ty, result);
-      },
-    );
-
-    _subscription = RustStreamReceiver.listen((observable) => _parser?.parse(observable));
+    _listener = GridNotificationListener(objectId: rowId, handler: _handler);
   }
 
-  void _handleObservableType(GridNotification ty, Either<Uint8List, FlowyError> result) {
+  void _handler(GridNotification ty, Either<Uint8List, FlowyError> result) {
     switch (ty) {
       case GridNotification.DidUpdateRow:
         result.fold(
@@ -45,8 +35,7 @@ class RowListener {
   }
 
   Future<void> stop() async {
-    _parser = null;
-    await _subscription?.cancel();
+    await _listener?.stop();
     updateRowNotifier.dispose();
   }
 }

+ 0 - 2
frontend/app_flowy/lib/workspace/application/grid/row/row_service.dart

@@ -31,14 +31,12 @@ class RowService {
 class CellData extends Equatable {
   final String gridId;
   final String rowId;
-  final String blockId;
   final Field field;
   final Cell? cell;
 
   const CellData({
     required this.rowId,
     required this.gridId,
-    required this.blockId,
     required this.field,
     required this.cell,
   });

+ 12 - 14
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/grid_row.dart

@@ -122,24 +122,22 @@ class _RowCells extends StatelessWidget {
     return BlocBuilder<RowBloc, RowState>(
       buildWhen: (previous, current) => previous.cellDataMap != current.cellDataMap,
       builder: (context, state) {
-        final List<Widget> children = state.cellDataMap.fold(
-          () => [],
-          (dataMap) {
-            return dataMap.values.map(
-              (value) {
-                return CellContainer(
-                  width: value.field.width.toDouble(),
-                  child: buildGridCell(value),
-                );
-              },
-            ).toList();
-          },
-        );
-
+        final List<Widget> children = state.cellDataMap.fold(() => [], _toCells);
         return Row(children: children);
       },
     );
   }
+
+  List<Widget> _toCells(CellDataMap dataMap) {
+    return dataMap.values.map(
+      (cellData) {
+        return CellContainer(
+          width: cellData.field.width.toDouble(),
+          child: buildGridCell(cellData),
+        );
+      },
+    ).toList();
+  }
 }
 
 class RowRegionStateNotifier extends ChangeNotifier {

+ 102 - 0
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pb.dart

@@ -1099,6 +1099,108 @@ class CellIdentifierPayload extends $pb.GeneratedMessage {
   void clearRowId() => clearField(3);
 }
 
+enum CellNotificationData_OneOfContent {
+  content, 
+  notSet
+}
+
+class CellNotificationData extends $pb.GeneratedMessage {
+  static const $core.Map<$core.int, CellNotificationData_OneOfContent> _CellNotificationData_OneOfContentByTag = {
+    4 : CellNotificationData_OneOfContent.content,
+    0 : CellNotificationData_OneOfContent.notSet
+  };
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'CellNotificationData', createEmptyInstance: create)
+    ..oo(0, [4])
+    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'gridId')
+    ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'fieldId')
+    ..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'rowId')
+    ..aOS(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'content')
+    ..hasRequiredFields = false
+  ;
+
+  CellNotificationData._() : super();
+  factory CellNotificationData({
+    $core.String? gridId,
+    $core.String? fieldId,
+    $core.String? rowId,
+    $core.String? content,
+  }) {
+    final _result = create();
+    if (gridId != null) {
+      _result.gridId = gridId;
+    }
+    if (fieldId != null) {
+      _result.fieldId = fieldId;
+    }
+    if (rowId != null) {
+      _result.rowId = rowId;
+    }
+    if (content != null) {
+      _result.content = content;
+    }
+    return _result;
+  }
+  factory CellNotificationData.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory CellNotificationData.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+  'Will be removed in next major version')
+  CellNotificationData clone() => CellNotificationData()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  CellNotificationData copyWith(void Function(CellNotificationData) updates) => super.copyWith((message) => updates(message as CellNotificationData)) as CellNotificationData; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static CellNotificationData create() => CellNotificationData._();
+  CellNotificationData createEmptyInstance() => create();
+  static $pb.PbList<CellNotificationData> createRepeated() => $pb.PbList<CellNotificationData>();
+  @$core.pragma('dart2js:noInline')
+  static CellNotificationData getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<CellNotificationData>(create);
+  static CellNotificationData? _defaultInstance;
+
+  CellNotificationData_OneOfContent whichOneOfContent() => _CellNotificationData_OneOfContentByTag[$_whichOneof(0)]!;
+  void clearOneOfContent() => clearField($_whichOneof(0));
+
+  @$pb.TagNumber(1)
+  $core.String get gridId => $_getSZ(0);
+  @$pb.TagNumber(1)
+  set gridId($core.String v) { $_setString(0, v); }
+  @$pb.TagNumber(1)
+  $core.bool hasGridId() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearGridId() => clearField(1);
+
+  @$pb.TagNumber(2)
+  $core.String get fieldId => $_getSZ(1);
+  @$pb.TagNumber(2)
+  set fieldId($core.String v) { $_setString(1, v); }
+  @$pb.TagNumber(2)
+  $core.bool hasFieldId() => $_has(1);
+  @$pb.TagNumber(2)
+  void clearFieldId() => clearField(2);
+
+  @$pb.TagNumber(3)
+  $core.String get rowId => $_getSZ(2);
+  @$pb.TagNumber(3)
+  set rowId($core.String v) { $_setString(2, v); }
+  @$pb.TagNumber(3)
+  $core.bool hasRowId() => $_has(2);
+  @$pb.TagNumber(3)
+  void clearRowId() => clearField(3);
+
+  @$pb.TagNumber(4)
+  $core.String get content => $_getSZ(3);
+  @$pb.TagNumber(4)
+  set content($core.String v) { $_setString(3, v); }
+  @$pb.TagNumber(4)
+  $core.bool hasContent() => $_has(3);
+  @$pb.TagNumber(4)
+  void clearContent() => clearField(4);
+}
+
 class RepeatedCell extends $pb.GeneratedMessage {
   static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'RepeatedCell', createEmptyInstance: create)
     ..pc<Cell>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'items', $pb.PbFieldType.PM, subBuilder: Cell.create)

+ 16 - 0
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pbjson.dart

@@ -215,6 +215,22 @@ const CellIdentifierPayload$json = const {
 
 /// Descriptor for `CellIdentifierPayload`. Decode as a `google.protobuf.DescriptorProto`.
 final $typed_data.Uint8List cellIdentifierPayloadDescriptor = $convert.base64Decode('ChVDZWxsSWRlbnRpZmllclBheWxvYWQSFwoHZ3JpZF9pZBgBIAEoCVIGZ3JpZElkEhkKCGZpZWxkX2lkGAIgASgJUgdmaWVsZElkEhUKBnJvd19pZBgDIAEoCVIFcm93SWQ=');
+@$core.Deprecated('Use cellNotificationDataDescriptor instead')
+const CellNotificationData$json = const {
+  '1': 'CellNotificationData',
+  '2': const [
+    const {'1': 'grid_id', '3': 1, '4': 1, '5': 9, '10': 'gridId'},
+    const {'1': 'field_id', '3': 2, '4': 1, '5': 9, '10': 'fieldId'},
+    const {'1': 'row_id', '3': 3, '4': 1, '5': 9, '10': 'rowId'},
+    const {'1': 'content', '3': 4, '4': 1, '5': 9, '9': 0, '10': 'content'},
+  ],
+  '8': const [
+    const {'1': 'one_of_content'},
+  ],
+};
+
+/// Descriptor for `CellNotificationData`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List cellNotificationDataDescriptor = $convert.base64Decode('ChRDZWxsTm90aWZpY2F0aW9uRGF0YRIXCgdncmlkX2lkGAEgASgJUgZncmlkSWQSGQoIZmllbGRfaWQYAiABKAlSB2ZpZWxkSWQSFQoGcm93X2lkGAMgASgJUgVyb3dJZBIaCgdjb250ZW50GAQgASgJSABSB2NvbnRlbnRCEAoOb25lX29mX2NvbnRlbnQ=');
 @$core.Deprecated('Use repeatedCellDescriptor instead')
 const RepeatedCell$json = const {
   '1': 'RepeatedCell',

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

@@ -14,6 +14,7 @@ class GridNotification extends $pb.ProtobufEnum {
   static const GridNotification DidCreateBlock = GridNotification._(11, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DidCreateBlock');
   static const GridNotification DidUpdateBlock = GridNotification._(20, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DidUpdateBlock');
   static const GridNotification DidUpdateRow = GridNotification._(30, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DidUpdateRow');
+  static const GridNotification DidUpdateCell = GridNotification._(31, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DidUpdateCell');
   static const GridNotification DidUpdateFields = GridNotification._(40, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DidUpdateFields');
   static const GridNotification DidUpdateField = GridNotification._(41, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DidUpdateField');
 
@@ -22,6 +23,7 @@ class GridNotification extends $pb.ProtobufEnum {
     DidCreateBlock,
     DidUpdateBlock,
     DidUpdateRow,
+    DidUpdateCell,
     DidUpdateFields,
     DidUpdateField,
   ];

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

@@ -16,10 +16,11 @@ const GridNotification$json = const {
     const {'1': 'DidCreateBlock', '2': 11},
     const {'1': 'DidUpdateBlock', '2': 20},
     const {'1': 'DidUpdateRow', '2': 30},
+    const {'1': 'DidUpdateCell', '2': 31},
     const {'1': 'DidUpdateFields', '2': 40},
     const {'1': 'DidUpdateField', '2': 41},
   ],
 };
 
 /// Descriptor for `GridNotification`. Decode as a `google.protobuf.EnumDescriptorProto`.
-final $typed_data.Uint8List gridNotificationDescriptor = $convert.base64Decode('ChBHcmlkTm90aWZpY2F0aW9uEgsKB1Vua25vd24QABISCg5EaWRDcmVhdGVCbG9jaxALEhIKDkRpZFVwZGF0ZUJsb2NrEBQSEAoMRGlkVXBkYXRlUm93EB4SEwoPRGlkVXBkYXRlRmllbGRzECgSEgoORGlkVXBkYXRlRmllbGQQKQ==');
+final $typed_data.Uint8List gridNotificationDescriptor = $convert.base64Decode('ChBHcmlkTm90aWZpY2F0aW9uEgsKB1Vua25vd24QABISCg5EaWRDcmVhdGVCbG9jaxALEhIKDkRpZFVwZGF0ZUJsb2NrEBQSEAoMRGlkVXBkYXRlUm93EB4SEQoNRGlkVXBkYXRlQ2VsbBAfEhMKD0RpZFVwZGF0ZUZpZWxkcxAoEhIKDkRpZFVwZGF0ZUZpZWxkECk=');

+ 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]

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

@@ -8,6 +8,7 @@ pub enum GridNotification {
     DidCreateBlock = 11,
     DidUpdateBlock = 20,
     DidUpdateRow = 30,
+    DidUpdateCell = 31,
     DidUpdateFields = 40,
     DidUpdateField = 41,
 }

+ 1 - 1
frontend/rust-lib/flowy-grid/src/event_handler.rs

@@ -132,7 +132,7 @@ pub(crate) async fn get_select_option_handler(
     data: Data<CellIdentifierPayload>,
     manager: AppData<Arc<GridManager>>,
 ) -> DataResult<SelectOptionContext, FlowyError> {
-    let params: CellIdentifierParams = data.into_inner().try_into()?;
+    let params: CellIdentifier = data.into_inner().try_into()?;
     let editor = manager.get_grid_editor(&params.grid_id)?;
     match editor
         .get_field_metas(Some(vec![params.field_id.as_str()]))

+ 7 - 3
frontend/rust-lib/flowy-grid/src/protobuf/model/dart_notification.rs

@@ -29,6 +29,7 @@ pub enum GridNotification {
     DidCreateBlock = 11,
     DidUpdateBlock = 20,
     DidUpdateRow = 30,
+    DidUpdateCell = 31,
     DidUpdateFields = 40,
     DidUpdateField = 41,
 }
@@ -44,6 +45,7 @@ impl ::protobuf::ProtobufEnum for GridNotification {
             11 => ::std::option::Option::Some(GridNotification::DidCreateBlock),
             20 => ::std::option::Option::Some(GridNotification::DidUpdateBlock),
             30 => ::std::option::Option::Some(GridNotification::DidUpdateRow),
+            31 => ::std::option::Option::Some(GridNotification::DidUpdateCell),
             40 => ::std::option::Option::Some(GridNotification::DidUpdateFields),
             41 => ::std::option::Option::Some(GridNotification::DidUpdateField),
             _ => ::std::option::Option::None
@@ -56,6 +58,7 @@ impl ::protobuf::ProtobufEnum for GridNotification {
             GridNotification::DidCreateBlock,
             GridNotification::DidUpdateBlock,
             GridNotification::DidUpdateRow,
+            GridNotification::DidUpdateCell,
             GridNotification::DidUpdateFields,
             GridNotification::DidUpdateField,
         ];
@@ -86,10 +89,11 @@ impl ::protobuf::reflect::ProtobufValue for GridNotification {
 }
 
 static file_descriptor_proto_data: &'static [u8] = b"\
-    \n\x17dart_notification.proto*\x82\x01\n\x10GridNotification\x12\x0b\n\
+    \n\x17dart_notification.proto*\x95\x01\n\x10GridNotification\x12\x0b\n\
     \x07Unknown\x10\0\x12\x12\n\x0eDidCreateBlock\x10\x0b\x12\x12\n\x0eDidUp\
-    dateBlock\x10\x14\x12\x10\n\x0cDidUpdateRow\x10\x1e\x12\x13\n\x0fDidUpda\
-    teFields\x10(\x12\x12\n\x0eDidUpdateField\x10)b\x06proto3\
+    dateBlock\x10\x14\x12\x10\n\x0cDidUpdateRow\x10\x1e\x12\x11\n\rDidUpdate\
+    Cell\x10\x1f\x12\x13\n\x0fDidUpdateFields\x10(\x12\x12\n\x0eDidUpdateFie\
+    ld\x10)b\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

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

@@ -5,6 +5,7 @@ enum GridNotification {
     DidCreateBlock = 11;
     DidUpdateBlock = 20;
     DidUpdateRow = 30;
+    DidUpdateCell = 31;
     DidUpdateFields = 40;
     DidUpdateField = 41;
 }

+ 21 - 5
frontend/rust-lib/flowy-grid/src/services/block_meta_editor.rs

@@ -6,8 +6,8 @@ use bytes::Bytes;
 use dashmap::DashMap;
 use flowy_error::{FlowyError, FlowyResult};
 use flowy_grid_data_model::entities::{
-    CellMeta, FieldMeta, GridBlockMeta, GridBlockMetaChangeset, GridBlockOrder, RepeatedCell, Row, RowMeta,
-    RowMetaChangeset, RowOrder,
+    CellIdentifier, CellMeta, CellMetaChangeset, CellNotificationData, FieldMeta, GridBlockMeta,
+    GridBlockMetaChangeset, GridBlockOrder, RepeatedCell, Row, RowMeta, RowMetaChangeset, RowOrder,
 };
 use flowy_revision::disk::SQLiteGridBlockMetaRevisionPersistence;
 use flowy_revision::{
@@ -119,11 +119,19 @@ impl GridBlockMetaEditorManager {
         Ok(())
     }
 
-    pub async fn update_row_cells(&self, field_metas: &[FieldMeta], changeset: RowMetaChangeset) -> FlowyResult<()> {
+    pub async fn update_cell(&self, changeset: CellMetaChangeset) -> FlowyResult<()> {
         let row_id = changeset.row_id.clone();
         let editor = self.get_editor_from_row_id(&row_id).await?;
-        let _ = editor.update_row(changeset.clone()).await?;
-        self.notify_did_update_row(&row_id, field_metas).await?;
+        let row_changeset: RowMetaChangeset = changeset.clone().into();
+        let _ = editor.update_row(row_changeset).await?;
+
+        let cell_notification_data = CellNotificationData {
+            grid_id: changeset.grid_id,
+            field_id: changeset.field_id,
+            row_id: changeset.row_id,
+            content: changeset.data,
+        };
+        self.notify_did_update_cell(cell_notification_data).await?;
         Ok(())
     }
 
@@ -178,6 +186,14 @@ impl GridBlockMetaEditorManager {
         Ok(())
     }
 
+    async fn notify_did_update_cell(&self, data: CellNotificationData) -> FlowyResult<()> {
+        let id = format!("{}:{}", data.row_id, data.field_id);
+        send_dart_notification(&id, GridNotification::DidUpdateCell)
+            .payload(data)
+            .send();
+        Ok(())
+    }
+
     async fn notify_did_update_row(&self, row_id: &str, field_metas: &[FieldMeta]) -> FlowyResult<()> {
         match self.get_row_meta(row_id).await? {
             None => {}

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

@@ -278,12 +278,7 @@ impl ClientGridEditor {
             Some(field_meta) => {
                 // Update the changeset.data property with the return value.
                 changeset.data = Some(apply_cell_data_changeset(cell_data_changeset, cell_meta, field_meta)?);
-                let field_metas = self.get_field_metas::<FieldOrder>(None).await?;
-                let row_changeset: RowMetaChangeset = changeset.into();
-                let _ = self
-                    .block_meta_manager
-                    .update_row_cells(&field_metas, row_changeset)
-                    .await?;
+                let _ = self.block_meta_manager.update_cell(changeset).await?;
                 Ok(())
             }
         }

+ 19 - 4
shared-lib/flowy-grid-data-model/src/entities/grid.rs

@@ -343,20 +343,20 @@ pub struct CellIdentifierPayload {
     pub row_id: String,
 }
 
-pub struct CellIdentifierParams {
+pub struct CellIdentifier {
     pub grid_id: String,
     pub field_id: String,
     pub row_id: String,
 }
 
-impl TryInto<CellIdentifierParams> for CellIdentifierPayload {
+impl TryInto<CellIdentifier> for CellIdentifierPayload {
     type Error = ErrorCode;
 
-    fn try_into(self) -> Result<CellIdentifierParams, Self::Error> {
+    fn try_into(self) -> Result<CellIdentifier, Self::Error> {
         let grid_id = NotEmptyUuid::parse(self.grid_id).map_err(|_| ErrorCode::GridIdIsEmpty)?;
         let field_id = NotEmptyUuid::parse(self.field_id).map_err(|_| ErrorCode::FieldIdIsEmpty)?;
         let row_id = NotEmptyUuid::parse(self.row_id).map_err(|_| ErrorCode::RowIdIsEmpty)?;
-        Ok(CellIdentifierParams {
+        Ok(CellIdentifier {
             grid_id: grid_id.0,
             field_id: field_id.0,
             row_id: row_id.0,
@@ -364,6 +364,21 @@ impl TryInto<CellIdentifierParams> for CellIdentifierPayload {
     }
 }
 
+#[derive(Debug, Clone, Default, ProtoBuf)]
+pub struct CellNotificationData {
+    #[pb(index = 1)]
+    pub grid_id: String,
+
+    #[pb(index = 2)]
+    pub field_id: String,
+
+    #[pb(index = 3)]
+    pub row_id: String,
+
+    #[pb(index = 4, one_of)]
+    pub content: Option<String>,
+}
+
 #[derive(Debug, Default, ProtoBuf)]
 pub struct RepeatedCell {
     #[pb(index = 1)]

+ 349 - 20
shared-lib/flowy-grid-data-model/src/protobuf/model/grid.rs

@@ -3723,6 +3723,331 @@ impl ::protobuf::reflect::ProtobufValue for CellIdentifierPayload {
     }
 }
 
+#[derive(PartialEq,Clone,Default)]
+pub struct CellNotificationData {
+    // message fields
+    pub grid_id: ::std::string::String,
+    pub field_id: ::std::string::String,
+    pub row_id: ::std::string::String,
+    // message oneof groups
+    pub one_of_content: ::std::option::Option<CellNotificationData_oneof_one_of_content>,
+    // special fields
+    pub unknown_fields: ::protobuf::UnknownFields,
+    pub cached_size: ::protobuf::CachedSize,
+}
+
+impl<'a> ::std::default::Default for &'a CellNotificationData {
+    fn default() -> &'a CellNotificationData {
+        <CellNotificationData as ::protobuf::Message>::default_instance()
+    }
+}
+
+#[derive(Clone,PartialEq,Debug)]
+pub enum CellNotificationData_oneof_one_of_content {
+    content(::std::string::String),
+}
+
+impl CellNotificationData {
+    pub fn new() -> CellNotificationData {
+        ::std::default::Default::default()
+    }
+
+    // string grid_id = 1;
+
+
+    pub fn get_grid_id(&self) -> &str {
+        &self.grid_id
+    }
+    pub fn clear_grid_id(&mut self) {
+        self.grid_id.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_grid_id(&mut self, v: ::std::string::String) {
+        self.grid_id = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_grid_id(&mut self) -> &mut ::std::string::String {
+        &mut self.grid_id
+    }
+
+    // Take field
+    pub fn take_grid_id(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.grid_id, ::std::string::String::new())
+    }
+
+    // string field_id = 2;
+
+
+    pub fn get_field_id(&self) -> &str {
+        &self.field_id
+    }
+    pub fn clear_field_id(&mut self) {
+        self.field_id.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_field_id(&mut self, v: ::std::string::String) {
+        self.field_id = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_field_id(&mut self) -> &mut ::std::string::String {
+        &mut self.field_id
+    }
+
+    // Take field
+    pub fn take_field_id(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.field_id, ::std::string::String::new())
+    }
+
+    // string row_id = 3;
+
+
+    pub fn get_row_id(&self) -> &str {
+        &self.row_id
+    }
+    pub fn clear_row_id(&mut self) {
+        self.row_id.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_row_id(&mut self, v: ::std::string::String) {
+        self.row_id = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_row_id(&mut self) -> &mut ::std::string::String {
+        &mut self.row_id
+    }
+
+    // Take field
+    pub fn take_row_id(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.row_id, ::std::string::String::new())
+    }
+
+    // string content = 4;
+
+
+    pub fn get_content(&self) -> &str {
+        match self.one_of_content {
+            ::std::option::Option::Some(CellNotificationData_oneof_one_of_content::content(ref v)) => v,
+            _ => "",
+        }
+    }
+    pub fn clear_content(&mut self) {
+        self.one_of_content = ::std::option::Option::None;
+    }
+
+    pub fn has_content(&self) -> bool {
+        match self.one_of_content {
+            ::std::option::Option::Some(CellNotificationData_oneof_one_of_content::content(..)) => true,
+            _ => false,
+        }
+    }
+
+    // Param is passed by value, moved
+    pub fn set_content(&mut self, v: ::std::string::String) {
+        self.one_of_content = ::std::option::Option::Some(CellNotificationData_oneof_one_of_content::content(v))
+    }
+
+    // Mutable pointer to the field.
+    pub fn mut_content(&mut self) -> &mut ::std::string::String {
+        if let ::std::option::Option::Some(CellNotificationData_oneof_one_of_content::content(_)) = self.one_of_content {
+        } else {
+            self.one_of_content = ::std::option::Option::Some(CellNotificationData_oneof_one_of_content::content(::std::string::String::new()));
+        }
+        match self.one_of_content {
+            ::std::option::Option::Some(CellNotificationData_oneof_one_of_content::content(ref mut v)) => v,
+            _ => panic!(),
+        }
+    }
+
+    // Take field
+    pub fn take_content(&mut self) -> ::std::string::String {
+        if self.has_content() {
+            match self.one_of_content.take() {
+                ::std::option::Option::Some(CellNotificationData_oneof_one_of_content::content(v)) => v,
+                _ => panic!(),
+            }
+        } else {
+            ::std::string::String::new()
+        }
+    }
+}
+
+impl ::protobuf::Message for CellNotificationData {
+    fn is_initialized(&self) -> bool {
+        true
+    }
+
+    fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::ProtobufResult<()> {
+        while !is.eof()? {
+            let (field_number, wire_type) = is.read_tag_unpack()?;
+            match field_number {
+                1 => {
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.grid_id)?;
+                },
+                2 => {
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.field_id)?;
+                },
+                3 => {
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.row_id)?;
+                },
+                4 => {
+                    if wire_type != ::protobuf::wire_format::WireTypeLengthDelimited {
+                        return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
+                    }
+                    self.one_of_content = ::std::option::Option::Some(CellNotificationData_oneof_one_of_content::content(is.read_string()?));
+                },
+                _ => {
+                    ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
+                },
+            };
+        }
+        ::std::result::Result::Ok(())
+    }
+
+    // Compute sizes of nested messages
+    #[allow(unused_variables)]
+    fn compute_size(&self) -> u32 {
+        let mut my_size = 0;
+        if !self.grid_id.is_empty() {
+            my_size += ::protobuf::rt::string_size(1, &self.grid_id);
+        }
+        if !self.field_id.is_empty() {
+            my_size += ::protobuf::rt::string_size(2, &self.field_id);
+        }
+        if !self.row_id.is_empty() {
+            my_size += ::protobuf::rt::string_size(3, &self.row_id);
+        }
+        if let ::std::option::Option::Some(ref v) = self.one_of_content {
+            match v {
+                &CellNotificationData_oneof_one_of_content::content(ref v) => {
+                    my_size += ::protobuf::rt::string_size(4, &v);
+                },
+            };
+        }
+        my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
+        self.cached_size.set(my_size);
+        my_size
+    }
+
+    fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> {
+        if !self.grid_id.is_empty() {
+            os.write_string(1, &self.grid_id)?;
+        }
+        if !self.field_id.is_empty() {
+            os.write_string(2, &self.field_id)?;
+        }
+        if !self.row_id.is_empty() {
+            os.write_string(3, &self.row_id)?;
+        }
+        if let ::std::option::Option::Some(ref v) = self.one_of_content {
+            match v {
+                &CellNotificationData_oneof_one_of_content::content(ref v) => {
+                    os.write_string(4, v)?;
+                },
+            };
+        }
+        os.write_unknown_fields(self.get_unknown_fields())?;
+        ::std::result::Result::Ok(())
+    }
+
+    fn get_cached_size(&self) -> u32 {
+        self.cached_size.get()
+    }
+
+    fn get_unknown_fields(&self) -> &::protobuf::UnknownFields {
+        &self.unknown_fields
+    }
+
+    fn mut_unknown_fields(&mut self) -> &mut ::protobuf::UnknownFields {
+        &mut self.unknown_fields
+    }
+
+    fn as_any(&self) -> &dyn (::std::any::Any) {
+        self as &dyn (::std::any::Any)
+    }
+    fn as_any_mut(&mut self) -> &mut dyn (::std::any::Any) {
+        self as &mut dyn (::std::any::Any)
+    }
+    fn into_any(self: ::std::boxed::Box<Self>) -> ::std::boxed::Box<dyn (::std::any::Any)> {
+        self
+    }
+
+    fn descriptor(&self) -> &'static ::protobuf::reflect::MessageDescriptor {
+        Self::descriptor_static()
+    }
+
+    fn new() -> CellNotificationData {
+        CellNotificationData::new()
+    }
+
+    fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
+        static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::LazyV2::INIT;
+        descriptor.get(|| {
+            let mut fields = ::std::vec::Vec::new();
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
+                "grid_id",
+                |m: &CellNotificationData| { &m.grid_id },
+                |m: &mut CellNotificationData| { &mut m.grid_id },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
+                "field_id",
+                |m: &CellNotificationData| { &m.field_id },
+                |m: &mut CellNotificationData| { &mut m.field_id },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
+                "row_id",
+                |m: &CellNotificationData| { &m.row_id },
+                |m: &mut CellNotificationData| { &mut m.row_id },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_singular_string_accessor::<_>(
+                "content",
+                CellNotificationData::has_content,
+                CellNotificationData::get_content,
+            ));
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<CellNotificationData>(
+                "CellNotificationData",
+                fields,
+                file_descriptor_proto()
+            )
+        })
+    }
+
+    fn default_instance() -> &'static CellNotificationData {
+        static instance: ::protobuf::rt::LazyV2<CellNotificationData> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(CellNotificationData::new)
+    }
+}
+
+impl ::protobuf::Clear for CellNotificationData {
+    fn clear(&mut self) {
+        self.grid_id.clear();
+        self.field_id.clear();
+        self.row_id.clear();
+        self.one_of_content = ::std::option::Option::None;
+        self.unknown_fields.clear();
+    }
+}
+
+impl ::std::fmt::Debug for CellNotificationData {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+        ::protobuf::text_format::fmt(self, f)
+    }
+}
+
+impl ::protobuf::reflect::ProtobufValue for CellNotificationData {
+    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
+        ::protobuf::reflect::ReflectValueRef::Message(self)
+    }
+}
+
 #[derive(PartialEq,Clone,Default)]
 pub struct RepeatedCell {
     // message fields
@@ -5806,26 +6131,30 @@ static file_descriptor_proto_data: &'static [u8] = b"\
     \x01\x20\x01(\tR\x07fieldId\x12\x18\n\x07content\x18\x02\x20\x01(\tR\x07\
     content\"b\n\x15CellIdentifierPayload\x12\x17\n\x07grid_id\x18\x01\x20\
     \x01(\tR\x06gridId\x12\x19\n\x08field_id\x18\x02\x20\x01(\tR\x07fieldId\
-    \x12\x15\n\x06row_id\x18\x03\x20\x01(\tR\x05rowId\"+\n\x0cRepeatedCell\
-    \x12\x1b\n\x05items\x18\x01\x20\x03(\x0b2\x05.CellR\x05items\"'\n\x11Cre\
-    ateGridPayload\x12\x12\n\x04name\x18\x01\x20\x01(\tR\x04name\"\x1e\n\x06\
-    GridId\x12\x14\n\x05value\x18\x01\x20\x01(\tR\x05value\"#\n\x0bGridBlock\
-    Id\x12\x14\n\x05value\x18\x01\x20\x01(\tR\x05value\"f\n\x10CreateRowPayl\
-    oad\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12\"\n\x0cstart_\
-    row_id\x18\x02\x20\x01(\tH\0R\nstartRowIdB\x15\n\x13one_of_start_row_id\
-    \"\xb6\x01\n\x12CreateFieldPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\
-    \tR\x06gridId\x12\x1c\n\x05field\x18\x02\x20\x01(\x0b2\x06.FieldR\x05fie\
-    ld\x12(\n\x10type_option_data\x18\x03\x20\x01(\x0cR\x0etypeOptionData\
-    \x12&\n\x0estart_field_id\x18\x04\x20\x01(\tH\0R\x0cstartFieldIdB\x17\n\
-    \x15one_of_start_field_id\"d\n\x11QueryFieldPayload\x12\x17\n\x07grid_id\
-    \x18\x01\x20\x01(\tR\x06gridId\x126\n\x0cfield_orders\x18\x02\x20\x01(\
-    \x0b2\x13.RepeatedFieldOrderR\x0bfieldOrders\"e\n\x16QueryGridBlocksPayl\
-    oad\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x122\n\x0cblock_o\
-    rders\x18\x02\x20\x03(\x0b2\x0f.GridBlockOrderR\x0bblockOrders\"A\n\x0fQ\
-    ueryRowPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12\
-    \x15\n\x06row_id\x18\x03\x20\x01(\tR\x05rowId\"X\n\x19CreateSelectOption\
-    Payload\x12\x1f\n\x0boption_name\x18\x01\x20\x01(\tR\noptionName\x12\x1a\
-    \n\x08selected\x18\x02\x20\x01(\x08R\x08selectedb\x06proto3\
+    \x12\x15\n\x06row_id\x18\x03\x20\x01(\tR\x05rowId\"\x8f\x01\n\x14CellNot\
+    ificationData\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12\x19\
+    \n\x08field_id\x18\x02\x20\x01(\tR\x07fieldId\x12\x15\n\x06row_id\x18\
+    \x03\x20\x01(\tR\x05rowId\x12\x1a\n\x07content\x18\x04\x20\x01(\tH\0R\
+    \x07contentB\x10\n\x0eone_of_content\"+\n\x0cRepeatedCell\x12\x1b\n\x05i\
+    tems\x18\x01\x20\x03(\x0b2\x05.CellR\x05items\"'\n\x11CreateGridPayload\
+    \x12\x12\n\x04name\x18\x01\x20\x01(\tR\x04name\"\x1e\n\x06GridId\x12\x14\
+    \n\x05value\x18\x01\x20\x01(\tR\x05value\"#\n\x0bGridBlockId\x12\x14\n\
+    \x05value\x18\x01\x20\x01(\tR\x05value\"f\n\x10CreateRowPayload\x12\x17\
+    \n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12\"\n\x0cstart_row_id\x18\
+    \x02\x20\x01(\tH\0R\nstartRowIdB\x15\n\x13one_of_start_row_id\"\xb6\x01\
+    \n\x12CreateFieldPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gri\
+    dId\x12\x1c\n\x05field\x18\x02\x20\x01(\x0b2\x06.FieldR\x05field\x12(\n\
+    \x10type_option_data\x18\x03\x20\x01(\x0cR\x0etypeOptionData\x12&\n\x0es\
+    tart_field_id\x18\x04\x20\x01(\tH\0R\x0cstartFieldIdB\x17\n\x15one_of_st\
+    art_field_id\"d\n\x11QueryFieldPayload\x12\x17\n\x07grid_id\x18\x01\x20\
+    \x01(\tR\x06gridId\x126\n\x0cfield_orders\x18\x02\x20\x01(\x0b2\x13.Repe\
+    atedFieldOrderR\x0bfieldOrders\"e\n\x16QueryGridBlocksPayload\x12\x17\n\
+    \x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x122\n\x0cblock_orders\x18\x02\
+    \x20\x03(\x0b2\x0f.GridBlockOrderR\x0bblockOrders\"A\n\x0fQueryRowPayloa\
+    d\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12\x15\n\x06row_id\
+    \x18\x03\x20\x01(\tR\x05rowId\"X\n\x19CreateSelectOptionPayload\x12\x1f\
+    \n\x0boption_name\x18\x01\x20\x01(\tR\noptionName\x12\x1a\n\x08selected\
+    \x18\x02\x20\x01(\x08R\x08selectedb\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

+ 6 - 0
shared-lib/flowy-grid-data-model/src/protobuf/proto/grid.proto

@@ -75,6 +75,12 @@ message CellIdentifierPayload {
     string field_id = 2;
     string row_id = 3;
 }
+message CellNotificationData {
+    string grid_id = 1;
+    string field_id = 2;
+    string row_id = 3;
+    oneof one_of_content { string content = 4; };
+}
 message RepeatedCell {
     repeated Cell items = 1;
 }