Browse Source

chore: refactor cell context

appflowy 3 years ago
parent
commit
be49784f5a
17 changed files with 350 additions and 443 deletions
  1. 6 4
      frontend/app_flowy/lib/startup/deps_resolver.dart
  2. 122 51
      frontend/app_flowy/lib/workspace/application/grid/cell/cell_service.dart
  3. 11 39
      frontend/app_flowy/lib/workspace/application/grid/cell/checkbox_cell_bloc.dart
  4. 13 44
      frontend/app_flowy/lib/workspace/application/grid/cell/date_cell_bloc.dart
  5. 13 47
      frontend/app_flowy/lib/workspace/application/grid/cell/number_cell_bloc.dart
  6. 36 32
      frontend/app_flowy/lib/workspace/application/grid/cell/select_option_service.dart
  7. 25 66
      frontend/app_flowy/lib/workspace/application/grid/cell/selection_cell_bloc.dart
  8. 42 92
      frontend/app_flowy/lib/workspace/application/grid/cell/selection_editor_bloc.dart
  9. 1 1
      frontend/app_flowy/lib/workspace/application/grid/cell/text_cell_bloc.dart
  10. 3 3
      frontend/app_flowy/lib/workspace/application/grid/row/row_bloc.dart
  11. 4 4
      frontend/app_flowy/lib/workspace/application/grid/row/row_detail_bloc.dart
  12. 5 5
      frontend/app_flowy/lib/workspace/application/grid/row/row_service.dart
  13. 34 7
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart
  14. 5 8
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_cell.dart
  15. 5 15
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_editor.dart
  16. 6 11
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/grid_row.dart
  17. 19 14
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/row_detail.dart

+ 6 - 4
frontend/app_flowy/lib/startup/deps_resolver.dart

@@ -16,8 +16,10 @@ import 'package:app_flowy/user/presentation/router.dart';
 import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
 import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
 import 'package:flowy_sdk/protobuf/flowy-folder-data-model/app.pb.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-folder-data-model/view.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Cell;
 import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart';
 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-grid/number_type_option.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-user-data-model/user_profile.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-user-data-model/user_profile.pb.dart';
 import 'package:get_it/get_it.dart';
 import 'package:get_it/get_it.dart';
 
 
@@ -174,25 +176,25 @@ void _resolveGridDeps(GetIt getIt) {
     ),
     ),
   );
   );
 
 
-  getIt.registerFactoryParam<SelectionCellBloc, GridCellContext, void>(
+  getIt.registerFactoryParam<SelectionCellBloc, GridCellContext<SelectOptionContext>, void>(
     (context, _) => SelectionCellBloc(
     (context, _) => SelectionCellBloc(
       cellContext: context,
       cellContext: context,
     ),
     ),
   );
   );
 
 
-  getIt.registerFactoryParam<NumberCellBloc, GridCellContext, void>(
+  getIt.registerFactoryParam<NumberCellBloc, GridCellContext<Cell>, void>(
     (context, _) => NumberCellBloc(
     (context, _) => NumberCellBloc(
       cellContext: context,
       cellContext: context,
     ),
     ),
   );
   );
 
 
-  getIt.registerFactoryParam<DateCellBloc, GridCellContext, void>(
+  getIt.registerFactoryParam<DateCellBloc, GridCellContext<Cell>, void>(
     (context, _) => DateCellBloc(
     (context, _) => DateCellBloc(
       cellContext: context,
       cellContext: context,
     ),
     ),
   );
   );
 
 
-  getIt.registerFactoryParam<CheckboxCellBloc, GridCellContext, void>(
+  getIt.registerFactoryParam<CheckboxCellBloc, GridCellContext<Cell>, void>(
     (cellData, _) => CheckboxCellBloc(
     (cellData, _) => CheckboxCellBloc(
       service: CellService(),
       service: CellService(),
       cellContext: cellData,
       cellContext: cellData,

+ 122 - 51
frontend/app_flowy/lib/workspace/application/grid/cell/cell_service.dart

@@ -9,55 +9,126 @@ import 'package:flowy_sdk/protobuf/flowy-grid/cell_entities.pb.dart';
 import 'package:flutter/foundation.dart';
 import 'package:flutter/foundation.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 
 
+import 'package:app_flowy/workspace/application/grid/cell/cell_listener.dart';
+
 part 'cell_service.freezed.dart';
 part 'cell_service.freezed.dart';
 
 
-class GridCellContext {
-  GridCell cellData;
-  GridCellCache cellCache;
+class GridCellContext<T> {
+  final GridCell gridCell;
+  final GridCellCache cellCache;
   late GridCellCacheKey _cacheKey;
   late GridCellCacheKey _cacheKey;
+  final GridCellDataLoader<T> cellDataLoader;
+
+  final CellListener _cellListener;
+  final CellService _cellService = CellService();
+  final ValueNotifier<dynamic> _cellDataNotifier = ValueNotifier(null);
+
   GridCellContext({
   GridCellContext({
-    required this.cellData,
+    required this.gridCell,
     required this.cellCache,
     required this.cellCache,
-  }) {
+    required this.cellDataLoader,
+  }) : _cellListener = CellListener(rowId: gridCell.rowId, fieldId: gridCell.field.id) {
+    _cellListener.updateCellNotifier?.addPublishListener((result) {
+      result.fold(
+        (notification) => _loadData(),
+        (err) => Log.error(err),
+      );
+    });
+
+    _cellListener.start();
+
     _cacheKey = GridCellCacheKey(
     _cacheKey = GridCellCacheKey(
       objectId: "$hashCode",
       objectId: "$hashCode",
-      fieldId: cellData.field.id,
+      fieldId: gridCell.field.id,
     );
     );
   }
   }
 
 
-  String get gridId => cellData.gridId;
+  String get gridId => gridCell.gridId;
 
 
-  String get cellId => cellData.rowId + (cellData.cell?.fieldId ?? "");
+  String get rowId => gridCell.rowId;
 
 
-  String get rowId => cellData.rowId;
+  String get cellId => gridCell.rowId + gridCell.field.id;
 
 
-  String get fieldId => cellData.field.id;
+  String get fieldId => gridCell.field.id;
 
 
-  FieldType get fieldType => cellData.field.fieldType;
+  Field get field => gridCell.field;
 
 
-  Field get field => cellData.field;
+  FieldType get fieldType => gridCell.field.fieldType;
 
 
   GridCellCacheKey get cacheKey => _cacheKey;
   GridCellCacheKey get cacheKey => _cacheKey;
 
 
-  T? getCacheData<T>() {
-    return cellCache.get(cacheKey);
+  T? getCellData() {
+    final data = cellCache.get(cacheKey);
+    if (data == null) {
+      _loadData();
+    }
+    return data;
   }
   }
 
 
-  void setCacheData(dynamic data) {
+  void setCellData(T? data) {
     cellCache.insert(GridCellCacheData(key: cacheKey, object: data));
     cellCache.insert(GridCellCacheData(key: cacheKey, object: data));
   }
   }
 
 
+  void saveCellData(String data) {
+    _cellService.updateCell(gridId: gridId, fieldId: field.id, rowId: rowId, data: data);
+  }
+
+  void _loadData() {
+    // It may trigger getCell multiple times. Use cancel operation to fix this.
+    cellDataLoader.loadData().then((data) {
+      _cellDataNotifier.value = data;
+      setCellData(data);
+    });
+  }
+
   void onFieldChanged(VoidCallback callback) {
   void onFieldChanged(VoidCallback callback) {
     cellCache.addListener(cacheKey, callback);
     cellCache.addListener(cacheKey, callback);
   }
   }
 
 
+  void onCellChanged(void Function(T) callback) {
+    _cellDataNotifier.addListener(() {
+      final value = _cellDataNotifier.value;
+      if (value is T) {
+        callback(value);
+      }
+    });
+  }
+
   void removeListener() {
   void removeListener() {
     cellCache.removeListener(cacheKey);
     cellCache.removeListener(cacheKey);
   }
   }
 }
 }
 
 
+abstract class GridCellDataLoader<T> {
+  Future<T?> loadData();
+}
+
+class DefaultCellDataLoader implements GridCellDataLoader<Cell> {
+  final CellService service = CellService();
+  final GridCell gridCell;
+
+  DefaultCellDataLoader({
+    required this.gridCell,
+  });
+
+  @override
+  Future<Cell?> loadData() {
+    final fut = service.getCell(
+      gridId: gridCell.gridId,
+      fieldId: gridCell.field.id,
+      rowId: gridCell.rowId,
+    );
+    return fut.then((result) {
+      return result.fold((data) => data, (err) {
+        Log.error(err);
+        return null;
+      });
+    });
+  }
+}
+
 // key: rowId
 // key: rowId
-typedef CellDataMap = LinkedHashMap<String, GridCell>;
+typedef GridCellMap = LinkedHashMap<String, GridCell>;
 
 
 class GridCellCacheData {
 class GridCellCacheData {
   GridCellCacheKey key;
   GridCellCacheKey key;
@@ -175,41 +246,41 @@ class CellService {
   }
   }
 }
 }
 
 
-class CellCache {
-  final CellService _cellService;
-  final HashMap<String, Cell> _cellDataMap = HashMap();
-
-  CellCache() : _cellService = CellService();
-
-  Future<Option<Cell>> getCellData(GridCell identifier) async {
-    final cellId = _cellId(identifier);
-    final Cell? data = _cellDataMap[cellId];
-    if (data != null) {
-      return Future(() => Some(data));
-    }
-
-    final result = await _cellService.getCell(
-      gridId: identifier.gridId,
-      fieldId: identifier.field.id,
-      rowId: identifier.rowId,
-    );
-
-    return result.fold(
-      (cell) {
-        _cellDataMap[_cellId(identifier)] = cell;
-        return Some(cell);
-      },
-      (err) {
-        Log.error(err);
-        return none();
-      },
-    );
-  }
-
-  String _cellId(GridCell identifier) {
-    return "${identifier.rowId}/${identifier.field.id}";
-  }
-}
+// class CellCache {
+//   final CellService _cellService;
+//   final HashMap<String, Cell> _cellDataMap = HashMap();
+
+//   CellCache() : _cellService = CellService();
+
+//   Future<Option<Cell>> getCellData(GridCell identifier) async {
+//     final cellId = _cellId(identifier);
+//     final Cell? data = _cellDataMap[cellId];
+//     if (data != null) {
+//       return Future(() => Some(data));
+//     }
+
+//     final result = await _cellService.getCell(
+//       gridId: identifier.gridId,
+//       fieldId: identifier.field.id,
+//       rowId: identifier.rowId,
+//     );
+
+//     return result.fold(
+//       (cell) {
+//         _cellDataMap[_cellId(identifier)] = cell;
+//         return Some(cell);
+//       },
+//       (err) {
+//         Log.error(err);
+//         return none();
+//       },
+//     );
+//   }
+
+//   String _cellId(GridCell identifier) {
+//     return "${identifier.rowId}/${identifier.field.id}";
+//   }
+// }
 
 
 @freezed
 @freezed
 class GridCell with _$GridCell {
 class GridCell with _$GridCell {

+ 11 - 39
frontend/app_flowy/lib/workspace/application/grid/cell/checkbox_cell_bloc.dart

@@ -1,5 +1,3 @@
-import 'package:app_flowy/workspace/application/grid/cell/cell_listener.dart';
-import 'package:flowy_sdk/log.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Cell;
 import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Cell;
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
@@ -9,15 +7,13 @@ import 'cell_service.dart';
 part 'checkbox_cell_bloc.freezed.dart';
 part 'checkbox_cell_bloc.freezed.dart';
 
 
 class CheckboxCellBloc extends Bloc<CheckboxCellEvent, CheckboxCellState> {
 class CheckboxCellBloc extends Bloc<CheckboxCellEvent, CheckboxCellState> {
-  final CellService _service;
-  final CellListener _cellListener;
+  final GridCellContext<Cell> _cellContext;
 
 
   CheckboxCellBloc({
   CheckboxCellBloc({
     required CellService service,
     required CellService service,
-    required GridCellContext cellContext,
-  })  : _service = service,
-        _cellListener = CellListener(rowId: cellContext.rowId, fieldId: cellContext.fieldId),
-        super(CheckboxCellState.initial(cellContext.cellData)) {
+    required GridCellContext<Cell> cellContext,
+  })  : _cellContext = cellContext,
+        super(CheckboxCellState.initial(cellContext)) {
     on<CheckboxCellEvent>(
     on<CheckboxCellEvent>(
       (event, emit) async {
       (event, emit) async {
         await event.map(
         await event.map(
@@ -37,42 +33,19 @@ class CheckboxCellBloc extends Bloc<CheckboxCellEvent, CheckboxCellState> {
 
 
   @override
   @override
   Future<void> close() async {
   Future<void> close() async {
-    await _cellListener.stop();
     return super.close();
     return super.close();
   }
   }
 
 
   void _startListening() {
   void _startListening() {
-    _cellListener.updateCellNotifier?.addPublishListener((result) {
-      result.fold(
-        (notificationData) async => await _loadCellData(),
-        (err) => Log.error(err),
-      );
+    _cellContext.onCellChanged((cell) {
+      if (!isClosed) {
+        add(CheckboxCellEvent.didReceiveCellUpdate(cell));
+      }
     });
     });
-    _cellListener.start();
-  }
-
-  Future<void> _loadCellData() async {
-    final result = await _service.getCell(
-      gridId: state.cellData.gridId,
-      fieldId: state.cellData.field.id,
-      rowId: state.cellData.rowId,
-    );
-    if (isClosed) {
-      return;
-    }
-    result.fold(
-      (cell) => add(CheckboxCellEvent.didReceiveCellUpdate(cell)),
-      (err) => Log.error(err),
-    );
   }
   }
 
 
   void _updateCellData() {
   void _updateCellData() {
-    _service.updateCell(
-      gridId: state.cellData.gridId,
-      fieldId: state.cellData.field.id,
-      rowId: state.cellData.rowId,
-      data: !state.isSelected ? "Yes" : "No",
-    );
+    _cellContext.saveCellData(!state.isSelected ? "Yes" : "No");
   }
   }
 }
 }
 
 
@@ -86,12 +59,11 @@ class CheckboxCellEvent with _$CheckboxCellEvent {
 @freezed
 @freezed
 class CheckboxCellState with _$CheckboxCellState {
 class CheckboxCellState with _$CheckboxCellState {
   const factory CheckboxCellState({
   const factory CheckboxCellState({
-    required GridCell cellData,
     required bool isSelected,
     required bool isSelected,
   }) = _CheckboxCellState;
   }) = _CheckboxCellState;
 
 
-  factory CheckboxCellState.initial(GridCell cellData) {
-    return CheckboxCellState(cellData: cellData, isSelected: _isSelected(cellData.cell));
+  factory CheckboxCellState.initial(GridCellContext context) {
+    return CheckboxCellState(isSelected: _isSelected(context.getCellData()));
   }
   }
 }
 }
 
 

+ 13 - 44
frontend/app_flowy/lib/workspace/application/grid/cell/date_cell_bloc.dart

@@ -1,4 +1,3 @@
-import 'package:app_flowy/workspace/application/grid/cell/cell_listener.dart';
 import 'package:app_flowy/workspace/application/grid/field/field_listener.dart';
 import 'package:app_flowy/workspace/application/grid/field/field_listener.dart';
 import 'package:flowy_sdk/log.dart';
 import 'package:flowy_sdk/log.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Cell, Field;
 import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Cell, Field;
@@ -10,15 +9,12 @@ import 'cell_service.dart';
 part 'date_cell_bloc.freezed.dart';
 part 'date_cell_bloc.freezed.dart';
 
 
 class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
 class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
-  final CellService _service;
-  final CellListener _cellListener;
   final SingleFieldListener _fieldListener;
   final SingleFieldListener _fieldListener;
+  final GridCellContext<Cell> cellContext;
 
 
-  DateCellBloc({required GridCellContext cellContext})
-      : _service = CellService(),
-        _cellListener = CellListener(rowId: cellContext.rowId, fieldId: cellContext.fieldId),
-        _fieldListener = SingleFieldListener(fieldId: cellContext.fieldId),
-        super(DateCellState.initial(cellContext.cellData)) {
+  DateCellBloc({required this.cellContext})
+      : _fieldListener = SingleFieldListener(fieldId: cellContext.fieldId),
+        super(DateCellState.initial(cellContext)) {
     on<DateCellEvent>(
     on<DateCellEvent>(
       (event, emit) async {
       (event, emit) async {
         event.map(
         event.map(
@@ -30,13 +26,11 @@ class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
           },
           },
           didReceiveCellUpdate: (_DidReceiveCellUpdate value) {
           didReceiveCellUpdate: (_DidReceiveCellUpdate value) {
             emit(state.copyWith(
             emit(state.copyWith(
-              cellData: state.cellData.copyWith(cell: value.cell),
               content: value.cell.content,
               content: value.cell.content,
             ));
             ));
           },
           },
           didReceiveFieldUpdate: (_DidReceiveFieldUpdate value) {
           didReceiveFieldUpdate: (_DidReceiveFieldUpdate value) {
             emit(state.copyWith(field: value.field));
             emit(state.copyWith(field: value.field));
-            _loadCellData();
           },
           },
         );
         );
       },
       },
@@ -45,19 +39,16 @@ class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
 
 
   @override
   @override
   Future<void> close() async {
   Future<void> close() async {
-    await _cellListener.stop();
     await _fieldListener.stop();
     await _fieldListener.stop();
     return super.close();
     return super.close();
   }
   }
 
 
   void _startListening() {
   void _startListening() {
-    _cellListener.updateCellNotifier?.addPublishListener((result) {
-      result.fold(
-        (notificationData) => _loadCellData(),
-        (err) => Log.error(err),
-      );
-    }, listenWhen: () => !isClosed);
-    _cellListener.start();
+    cellContext.onCellChanged((cell) {
+      if (!isClosed) {
+        add(DateCellEvent.didReceiveCellUpdate(cell));
+      }
+    });
 
 
     _fieldListener.updateFieldNotifier?.addPublishListener((result) {
     _fieldListener.updateFieldNotifier?.addPublishListener((result) {
       result.fold(
       result.fold(
@@ -68,29 +59,9 @@ class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
     _fieldListener.start();
     _fieldListener.start();
   }
   }
 
 
-  Future<void> _loadCellData() async {
-    final result = await _service.getCell(
-      gridId: state.cellData.gridId,
-      fieldId: state.cellData.field.id,
-      rowId: state.cellData.rowId,
-    );
-    if (isClosed) {
-      return;
-    }
-    result.fold(
-      (cell) => add(DateCellEvent.didReceiveCellUpdate(cell)),
-      (err) => Log.error(err),
-    );
-  }
-
   void _updateCellData(DateTime day) {
   void _updateCellData(DateTime day) {
     final data = day.millisecondsSinceEpoch ~/ 1000;
     final data = day.millisecondsSinceEpoch ~/ 1000;
-    _service.updateCell(
-      gridId: state.cellData.gridId,
-      fieldId: state.cellData.field.id,
-      rowId: state.cellData.rowId,
-      data: data.toString(),
-    );
+    cellContext.saveCellData(data.toString());
   }
   }
 }
 }
 
 
@@ -105,15 +76,13 @@ class DateCellEvent with _$DateCellEvent {
 @freezed
 @freezed
 class DateCellState with _$DateCellState {
 class DateCellState with _$DateCellState {
   const factory DateCellState({
   const factory DateCellState({
-    required GridCell cellData,
     required String content,
     required String content,
     required Field field,
     required Field field,
     DateTime? selectedDay,
     DateTime? selectedDay,
   }) = _DateCellState;
   }) = _DateCellState;
 
 
-  factory DateCellState.initial(GridCell cellData) => DateCellState(
-        cellData: cellData,
-        field: cellData.field,
-        content: cellData.cell?.content ?? "",
+  factory DateCellState.initial(GridCellContext context) => DateCellState(
+        field: context.field,
+        content: context.getCellData()?.content ?? "",
       );
       );
 }
 }

+ 13 - 47
frontend/app_flowy/lib/workspace/application/grid/cell/number_cell_bloc.dart

@@ -1,4 +1,3 @@
-import 'package:app_flowy/workspace/application/grid/cell/cell_listener.dart';
 import 'package:app_flowy/workspace/application/grid/field/field_listener.dart';
 import 'package:app_flowy/workspace/application/grid/field/field_listener.dart';
 import 'package:flowy_sdk/log.dart';
 import 'package:flowy_sdk/log.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
@@ -10,16 +9,13 @@ import 'cell_service.dart';
 part 'number_cell_bloc.freezed.dart';
 part 'number_cell_bloc.freezed.dart';
 
 
 class NumberCellBloc extends Bloc<NumberCellEvent, NumberCellState> {
 class NumberCellBloc extends Bloc<NumberCellEvent, NumberCellState> {
-  final CellService _service;
-  final CellListener _cellListener;
+  final GridCellContext<Cell> cellContext;
   final SingleFieldListener _fieldListener;
   final SingleFieldListener _fieldListener;
 
 
   NumberCellBloc({
   NumberCellBloc({
-    required GridCellContext cellContext,
-  })  : _service = CellService(),
-        _cellListener = CellListener(rowId: cellContext.rowId, fieldId: cellContext.fieldId),
-        _fieldListener = SingleFieldListener(fieldId: cellContext.fieldId),
-        super(NumberCellState.initial(cellContext.cellData)) {
+    required this.cellContext,
+  })  : _fieldListener = SingleFieldListener(fieldId: cellContext.fieldId),
+        super(NumberCellState.initial(cellContext)) {
     on<NumberCellEvent>(
     on<NumberCellEvent>(
       (event, emit) async {
       (event, emit) async {
         await event.map(
         await event.map(
@@ -38,60 +34,31 @@ class NumberCellBloc extends Bloc<NumberCellEvent, NumberCellState> {
   }
   }
 
 
   Future<void> _updateCellValue(_UpdateCell value, Emitter<NumberCellState> emit) async {
   Future<void> _updateCellValue(_UpdateCell value, Emitter<NumberCellState> emit) async {
-    final result = await _service.updateCell(
-      gridId: state.cellData.gridId,
-      fieldId: state.cellData.field.id,
-      rowId: state.cellData.rowId,
-      data: value.text,
-    );
-    result.fold(
-      (field) => _getCellData(),
-      (err) => Log.error(err),
-    );
+    cellContext.saveCellData(value.text);
+    cellContext.reloadCellData();
   }
   }
 
 
   @override
   @override
   Future<void> close() async {
   Future<void> close() async {
-    await _cellListener.stop();
     await _fieldListener.stop();
     await _fieldListener.stop();
     return super.close();
     return super.close();
   }
   }
 
 
   void _startListening() {
   void _startListening() {
-    _cellListener.updateCellNotifier?.addPublishListener((result) {
-      result.fold(
-        (notificationData) async {
-          await _getCellData();
-        },
-        (err) => Log.error(err),
-      );
+    cellContext.onCellChanged((cell) {
+      if (!isClosed) {
+        add(NumberCellEvent.didReceiveCellUpdate(cell));
+      }
     });
     });
-    _cellListener.start();
 
 
     _fieldListener.updateFieldNotifier?.addPublishListener((result) {
     _fieldListener.updateFieldNotifier?.addPublishListener((result) {
       result.fold(
       result.fold(
-        (field) => _getCellData(),
+        (field) => cellContext.reloadCellData(),
         (err) => Log.error(err),
         (err) => Log.error(err),
       );
       );
     });
     });
     _fieldListener.start();
     _fieldListener.start();
   }
   }
-
-  Future<void> _getCellData() async {
-    final result = await _service.getCell(
-      gridId: state.cellData.gridId,
-      fieldId: state.cellData.field.id,
-      rowId: state.cellData.rowId,
-    );
-
-    if (isClosed) {
-      return;
-    }
-    result.fold(
-      (cell) => add(NumberCellEvent.didReceiveCellUpdate(cell)),
-      (err) => Log.error(err),
-    );
-  }
 }
 }
 
 
 @freezed
 @freezed
@@ -104,11 +71,10 @@ class NumberCellEvent with _$NumberCellEvent {
 @freezed
 @freezed
 class NumberCellState with _$NumberCellState {
 class NumberCellState with _$NumberCellState {
   const factory NumberCellState({
   const factory NumberCellState({
-    required GridCell cellData,
     required String content,
     required String content,
   }) = _NumberCellState;
   }) = _NumberCellState;
 
 
-  factory NumberCellState.initial(GridCell cellData) {
-    return NumberCellState(cellData: cellData, content: cellData.cell?.content ?? "");
+  factory NumberCellState.initial(GridCellContext context) {
+    return NumberCellState(content: context.getCellData().cell?.content ?? "");
   }
   }
 }
 }

+ 36 - 32
frontend/app_flowy/lib/workspace/application/grid/cell/select_option_service.dart

@@ -1,19 +1,43 @@
-import 'package:app_flowy/workspace/application/grid/field/type_option/type_option_service.dart';
-import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
 import 'package:dartz/dartz.dart';
 import 'package:dartz/dartz.dart';
 import 'package:flowy_sdk/dispatch/dispatch.dart';
 import 'package:flowy_sdk/dispatch/dispatch.dart';
+import 'package:flowy_sdk/log.dart';
+import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/cell_entities.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/cell_entities.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
 
 
+import 'package:app_flowy/workspace/application/grid/field/type_option/type_option_service.dart';
+
+import 'cell_service.dart';
+
+class SelectOptionCellDataLoader implements GridCellDataLoader<SelectOptionContext> {
+  final SelectOptionService service;
+  final GridCell gridCell;
+  SelectOptionCellDataLoader({
+    required this.gridCell,
+  }) : service = SelectOptionService(gridCell: gridCell);
+  @override
+  Future<SelectOptionContext?> loadData() async {
+    return service.getOpitonContext().then((result) {
+      return result.fold(
+        (data) => data,
+        (err) {
+          Log.error(err);
+          return null;
+        },
+      );
+    });
+  }
+}
+
 class SelectOptionService {
 class SelectOptionService {
-  SelectOptionService();
+  final GridCell gridCell;
+  SelectOptionService({required this.gridCell});
 
 
-  Future<Either<Unit, FlowyError>> create({
-    required String gridId,
-    required String fieldId,
-    required String rowId,
-    required String name,
-  }) {
+  String get gridId => gridCell.gridId;
+  String get fieldId => gridCell.field.id;
+  String get rowId => gridCell.rowId;
+
+  Future<Either<Unit, FlowyError>> create({required String name}) {
     return TypeOptionService(gridId: gridId, fieldId: fieldId).newOption(name: name).then(
     return TypeOptionService(gridId: gridId, fieldId: fieldId).newOption(name: name).then(
       (result) {
       (result) {
         return result.fold(
         return result.fold(
@@ -34,9 +58,6 @@ class SelectOptionService {
   }
   }
 
 
   Future<Either<Unit, FlowyError>> update({
   Future<Either<Unit, FlowyError>> update({
-    required String gridId,
-    required String fieldId,
-    required String rowId,
     required SelectOption option,
     required SelectOption option,
   }) {
   }) {
     final cellIdentifier = CellIdentifierPayload.create()
     final cellIdentifier = CellIdentifierPayload.create()
@@ -50,9 +71,6 @@ class SelectOptionService {
   }
   }
 
 
   Future<Either<Unit, FlowyError>> delete({
   Future<Either<Unit, FlowyError>> delete({
-    required String gridId,
-    required String fieldId,
-    required String rowId,
     required SelectOption option,
     required SelectOption option,
   }) {
   }) {
     final cellIdentifier = CellIdentifierPayload.create()
     final cellIdentifier = CellIdentifierPayload.create()
@@ -67,11 +85,7 @@ class SelectOptionService {
     return GridEventUpdateSelectOption(payload).send();
     return GridEventUpdateSelectOption(payload).send();
   }
   }
 
 
-  Future<Either<SelectOptionContext, FlowyError>> getOpitonContext({
-    required String gridId,
-    required String fieldId,
-    required String rowId,
-  }) {
+  Future<Either<SelectOptionContext, FlowyError>> getOpitonContext() {
     final payload = CellIdentifierPayload.create()
     final payload = CellIdentifierPayload.create()
       ..gridId = gridId
       ..gridId = gridId
       ..fieldId = fieldId
       ..fieldId = fieldId
@@ -80,12 +94,7 @@ class SelectOptionService {
     return GridEventGetSelectOptionContext(payload).send();
     return GridEventGetSelectOptionContext(payload).send();
   }
   }
 
 
-  Future<Either<void, FlowyError>> select({
-    required String gridId,
-    required String fieldId,
-    required String rowId,
-    required String optionId,
-  }) {
+  Future<Either<void, FlowyError>> select({required String optionId}) {
     final payload = SelectOptionCellChangesetPayload.create()
     final payload = SelectOptionCellChangesetPayload.create()
       ..gridId = gridId
       ..gridId = gridId
       ..fieldId = fieldId
       ..fieldId = fieldId
@@ -94,12 +103,7 @@ class SelectOptionService {
     return GridEventUpdateCellSelectOption(payload).send();
     return GridEventUpdateCellSelectOption(payload).send();
   }
   }
 
 
-  Future<Either<void, FlowyError>> unSelect({
-    required String gridId,
-    required String fieldId,
-    required String rowId,
-    required String optionId,
-  }) {
+  Future<Either<void, FlowyError>> unSelect({required String optionId}) {
     final payload = SelectOptionCellChangesetPayload.create()
     final payload = SelectOptionCellChangesetPayload.create()
       ..gridId = gridId
       ..gridId = gridId
       ..fieldId = fieldId
       ..fieldId = fieldId

+ 25 - 66
frontend/app_flowy/lib/workspace/application/grid/cell/selection_cell_bloc.dart

@@ -1,39 +1,31 @@
 import 'dart:async';
 import 'dart:async';
-
-import 'package:flowy_sdk/log.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
-
-import 'package:app_flowy/workspace/application/grid/cell/cell_listener.dart';
 import 'package:app_flowy/workspace/application/grid/cell/cell_service.dart';
 import 'package:app_flowy/workspace/application/grid/cell/cell_service.dart';
-import 'package:app_flowy/workspace/application/grid/cell/select_option_service.dart';
 import 'package:app_flowy/workspace/application/grid/field/field_listener.dart';
 import 'package:app_flowy/workspace/application/grid/field/field_listener.dart';
 
 
 part 'selection_cell_bloc.freezed.dart';
 part 'selection_cell_bloc.freezed.dart';
 
 
 class SelectionCellBloc extends Bloc<SelectionCellEvent, SelectionCellState> {
 class SelectionCellBloc extends Bloc<SelectionCellEvent, SelectionCellState> {
-  final SelectOptionService _service;
-  final CellListener _cellListener;
   final SingleFieldListener _fieldListener;
   final SingleFieldListener _fieldListener;
-  final GridCellContext _cellContext;
+  final GridCellContext<SelectOptionContext> cellContext;
 
 
   SelectionCellBloc({
   SelectionCellBloc({
-    required GridCellContext cellContext,
-  })  : _service = SelectOptionService(),
-        _cellContext = cellContext,
-        _cellListener = CellListener(rowId: cellContext.rowId, fieldId: cellContext.fieldId),
-        _fieldListener = SingleFieldListener(fieldId: cellContext.fieldId),
-        super(SelectionCellState.initial(cellContext.cellData)) {
+    required this.cellContext,
+  })  : _fieldListener = SingleFieldListener(fieldId: cellContext.fieldId),
+        super(SelectionCellState.initial(cellContext)) {
     on<SelectionCellEvent>(
     on<SelectionCellEvent>(
       (event, emit) async {
       (event, emit) async {
         await event.map(
         await event.map(
           initial: (_InitialCell value) async {
           initial: (_InitialCell value) async {
             _startListening();
             _startListening();
-            _loadOptions();
           },
           },
           didReceiveOptions: (_DidReceiveOptions value) {
           didReceiveOptions: (_DidReceiveOptions value) {
-            emit(state.copyWith(options: value.options, selectedOptions: value.selectedOptions));
+            emit(state.copyWith(
+              options: value.options,
+              selectedOptions: value.selectedOptions,
+            ));
           },
           },
         );
         );
       },
       },
@@ -42,57 +34,22 @@ class SelectionCellBloc extends Bloc<SelectionCellEvent, SelectionCellState> {
 
 
   @override
   @override
   Future<void> close() async {
   Future<void> close() async {
-    await _cellListener.stop();
     await _fieldListener.stop();
     await _fieldListener.stop();
-    _cellContext.removeListener();
+    cellContext.removeListener();
     return super.close();
     return super.close();
   }
   }
 
 
-  void _loadOptions() async {
-    var selectOptionContext = _cellContext.getCacheData<SelectOptionContext>();
-    if (selectOptionContext == null) {
-      final result = await _service.getOpitonContext(
-        gridId: state.cellData.gridId,
-        fieldId: state.cellData.field.id,
-        rowId: state.cellData.rowId,
-      );
-      if (isClosed) {
-        return;
-      }
-
-      result.fold(
-        (newSelectOptionContext) {
-          _cellContext.setCacheData(newSelectOptionContext);
-          selectOptionContext = newSelectOptionContext;
-        },
-        (err) => Log.error(err),
-      );
-    }
-
-    add(SelectionCellEvent.didReceiveOptions(
-      selectOptionContext!.options,
-      selectOptionContext!.selectOptions,
-    ));
-  }
-
   void _startListening() {
   void _startListening() {
-    _cellListener.updateCellNotifier?.addPublishListener((result) {
-      result.fold(
-        (notificationData) => _loadOptions(),
-        (err) => Log.error(err),
-      );
+    cellContext.onCellChanged((selectOptionContext) {
+      if (!isClosed) {
+        add(SelectionCellEvent.didReceiveOptions(
+          selectOptionContext.options,
+          selectOptionContext.selectOptions,
+        ));
+      }
     });
     });
-    _cellListener.start();
-
-    _cellContext.onFieldChanged(() => _loadOptions());
 
 
-    // _fieldListener.updateFieldNotifier?.addPublishListener((result) {
-    //   result.fold(
-    //     (field) => _loadOptions(),
-    //     (err) => Log.error(err),
-    //   );
-    // });
-    // _fieldListener.start();
+    cellContext.onFieldChanged(() => cellContext.reloadCellData());
   }
   }
 }
 }
 
 
@@ -108,14 +65,16 @@ class SelectionCellEvent with _$SelectionCellEvent {
 @freezed
 @freezed
 class SelectionCellState with _$SelectionCellState {
 class SelectionCellState with _$SelectionCellState {
   const factory SelectionCellState({
   const factory SelectionCellState({
-    required GridCell cellData,
     required List<SelectOption> options,
     required List<SelectOption> options,
     required List<SelectOption> selectedOptions,
     required List<SelectOption> selectedOptions,
   }) = _SelectionCellState;
   }) = _SelectionCellState;
 
 
-  factory SelectionCellState.initial(GridCell cellData) => SelectionCellState(
-        cellData: cellData,
-        options: [],
-        selectedOptions: [],
-      );
+  factory SelectionCellState.initial(GridCellContext<SelectOptionContext> context) {
+    final data = context.getCellData();
+
+    return SelectionCellState(
+      options: data?.options ?? [],
+      selectedOptions: data?.selectOptions ?? [],
+    );
+  }
 }
 }

+ 42 - 92
frontend/app_flowy/lib/workspace/application/grid/cell/selection_editor_bloc.dart

@@ -13,28 +13,19 @@ part 'selection_editor_bloc.freezed.dart';
 
 
 class SelectOptionEditorBloc extends Bloc<SelectOptionEditorEvent, SelectOptionEditorState> {
 class SelectOptionEditorBloc extends Bloc<SelectOptionEditorEvent, SelectOptionEditorState> {
   final SelectOptionService _selectOptionService;
   final SelectOptionService _selectOptionService;
-  final SingleFieldListener _fieldListener;
-  final CellListener _cellListener;
+  final GridCellContext<SelectOptionContext> cellContext;
   Timer? _delayOperation;
   Timer? _delayOperation;
 
 
   SelectOptionEditorBloc({
   SelectOptionEditorBloc({
-    required GridCell cellData,
-    required List<SelectOption> options,
-    required List<SelectOption> selectedOptions,
-  })  : _selectOptionService = SelectOptionService(),
-        _fieldListener = SingleFieldListener(fieldId: cellData.field.id),
-        _cellListener = CellListener(rowId: cellData.rowId, fieldId: cellData.field.id),
-        super(SelectOptionEditorState.initial(cellData, options, selectedOptions)) {
+    required this.cellContext,
+  })  : _selectOptionService = SelectOptionService(gridCell: cellContext.gridCell),
+        super(SelectOptionEditorState.initial(cellContext)) {
     on<SelectOptionEditorEvent>(
     on<SelectOptionEditorEvent>(
       (event, emit) async {
       (event, emit) async {
         await event.map(
         await event.map(
           initial: (_Initial value) async {
           initial: (_Initial value) async {
             _startListening();
             _startListening();
           },
           },
-          didReceiveFieldUpdate: (_DidReceiveFieldUpdate value) {
-            emit(state.copyWith(field: value.field));
-            _loadOptions();
-          },
           didReceiveOptions: (_DidReceiveOptions value) {
           didReceiveOptions: (_DidReceiveOptions value) {
             emit(state.copyWith(
             emit(state.copyWith(
               options: value.options,
               options: value.options,
@@ -61,26 +52,17 @@ class SelectOptionEditorBloc extends Bloc<SelectOptionEditorEvent, SelectOptionE
   @override
   @override
   Future<void> close() async {
   Future<void> close() async {
     _delayOperation?.cancel();
     _delayOperation?.cancel();
-    await _fieldListener.stop();
-    await _cellListener.stop();
+    cellContext.removeListener();
     return super.close();
     return super.close();
   }
   }
 
 
   void _createOption(String name) async {
   void _createOption(String name) async {
-    final result = await _selectOptionService.create(
-      gridId: state.gridId,
-      fieldId: state.field.id,
-      rowId: state.rowId,
-      name: name,
-    );
-    result.fold((l) => _loadOptions(), (err) => Log.error(err));
+    final result = await _selectOptionService.create(name: name);
+    result.fold((l) => {}, (err) => Log.error(err));
   }
   }
 
 
   void _deleteOption(SelectOption option) async {
   void _deleteOption(SelectOption option) async {
     final result = await _selectOptionService.delete(
     final result = await _selectOptionService.delete(
-      gridId: state.gridId,
-      fieldId: state.field.id,
-      rowId: state.rowId,
       option: option,
       option: option,
     );
     );
 
 
@@ -89,9 +71,6 @@ class SelectOptionEditorBloc extends Bloc<SelectOptionEditorEvent, SelectOptionE
 
 
   void _updateOption(SelectOption option) async {
   void _updateOption(SelectOption option) async {
     final result = await _selectOptionService.update(
     final result = await _selectOptionService.update(
-      gridId: state.gridId,
-      fieldId: state.field.id,
-      rowId: state.rowId,
       option: option,
       option: option,
     );
     );
 
 
@@ -101,70 +80,50 @@ class SelectOptionEditorBloc extends Bloc<SelectOptionEditorEvent, SelectOptionE
   void _onSelectOption(String optionId) {
   void _onSelectOption(String optionId) {
     final hasSelected = state.selectedOptions.firstWhereOrNull((option) => option.id == optionId);
     final hasSelected = state.selectedOptions.firstWhereOrNull((option) => option.id == optionId);
     if (hasSelected != null) {
     if (hasSelected != null) {
-      _selectOptionService.unSelect(
-        gridId: state.gridId,
-        fieldId: state.field.id,
-        rowId: state.rowId,
-        optionId: optionId,
-      );
+      _selectOptionService.unSelect(optionId: optionId);
     } else {
     } else {
-      _selectOptionService.select(
-        gridId: state.gridId,
-        fieldId: state.field.id,
-        rowId: state.rowId,
-        optionId: optionId,
-      );
+      _selectOptionService.select(optionId: optionId);
     }
     }
   }
   }
 
 
-  void _loadOptions() async {
-    _delayOperation?.cancel();
-    _delayOperation = Timer(
-      const Duration(milliseconds: 1),
-      () async {
-        final result = await _selectOptionService.getOpitonContext(
-          gridId: state.gridId,
-          fieldId: state.field.id,
-          rowId: state.rowId,
-        );
-        if (isClosed) {
-          return;
-        }
-
-        result.fold(
-          (selectOptionContext) => add(SelectOptionEditorEvent.didReceiveOptions(
-            selectOptionContext.options,
-            selectOptionContext.selectOptions,
-          )),
-          (err) => Log.error(err),
-        );
-      },
-    );
-  }
+  // void _loadOptions() async {
+  //   _delayOperation?.cancel();
+  //   _delayOperation = Timer(
+  //     const Duration(milliseconds: 1),
+  //     () async {
+  //       final result = await _selectOptionService.getOpitonContext();
+  //       if (isClosed) {
+  //         return;
+  //       }
+
+  //       result.fold(
+  //         (selectOptionContext) => add(SelectOptionEditorEvent.didReceiveOptions(
+  //           selectOptionContext.options,
+  //           selectOptionContext.selectOptions,
+  //         )),
+  //         (err) => Log.error(err),
+  //       );
+  //     },
+  //   );
+  // }
 
 
   void _startListening() {
   void _startListening() {
-    _cellListener.updateCellNotifier?.addPublishListener((result) {
-      result.fold(
-        (notificationData) => _loadOptions(),
-        (err) => Log.error(err),
-      );
+    cellContext.onCellChanged((selectOptionContext) {
+      if (!isClosed) {
+        add(SelectOptionEditorEvent.didReceiveOptions(
+          selectOptionContext.options,
+          selectOptionContext.selectOptions,
+        ));
+      }
     });
     });
-    _cellListener.start();
-
-    _fieldListener.updateFieldNotifier?.addPublishListener((result) {
-      result.fold(
-        (field) => add(SelectOptionEditorEvent.didReceiveFieldUpdate(field)),
-        (err) => Log.error(err),
-      );
-    }, listenWhen: () => !isClosed);
-    _fieldListener.start();
+
+    cellContext.onFieldChanged(() => cellContext.reloadCellData());
   }
   }
 }
 }
 
 
 @freezed
 @freezed
 class SelectOptionEditorEvent with _$SelectOptionEditorEvent {
 class SelectOptionEditorEvent with _$SelectOptionEditorEvent {
   const factory SelectOptionEditorEvent.initial() = _Initial;
   const factory SelectOptionEditorEvent.initial() = _Initial;
-  const factory SelectOptionEditorEvent.didReceiveFieldUpdate(Field field) = _DidReceiveFieldUpdate;
   const factory SelectOptionEditorEvent.didReceiveOptions(
   const factory SelectOptionEditorEvent.didReceiveOptions(
       List<SelectOption> options, List<SelectOption> selectedOptions) = _DidReceiveOptions;
       List<SelectOption> options, List<SelectOption> selectedOptions) = _DidReceiveOptions;
   const factory SelectOptionEditorEvent.newOption(String optionName) = _NewOption;
   const factory SelectOptionEditorEvent.newOption(String optionName) = _NewOption;
@@ -176,24 +135,15 @@ class SelectOptionEditorEvent with _$SelectOptionEditorEvent {
 @freezed
 @freezed
 class SelectOptionEditorState with _$SelectOptionEditorState {
 class SelectOptionEditorState with _$SelectOptionEditorState {
   const factory SelectOptionEditorState({
   const factory SelectOptionEditorState({
-    required String gridId,
-    required Field field,
-    required String rowId,
     required List<SelectOption> options,
     required List<SelectOption> options,
     required List<SelectOption> selectedOptions,
     required List<SelectOption> selectedOptions,
   }) = _SelectOptionEditorState;
   }) = _SelectOptionEditorState;
 
 
-  factory SelectOptionEditorState.initial(
-    GridCell cellData,
-    List<SelectOption> options,
-    List<SelectOption> selectedOptions,
-  ) {
+  factory SelectOptionEditorState.initial(GridCellContext<SelectOptionContext> context) {
+    final data = context.getCellData();
     return SelectOptionEditorState(
     return SelectOptionEditorState(
-      gridId: cellData.gridId,
-      field: cellData.field,
-      rowId: cellData.rowId,
-      options: options,
-      selectedOptions: selectedOptions,
+      options: data?.options ?? [],
+      selectedOptions: data?.selectOptions ?? [],
     );
     );
   }
   }
 }
 }

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

@@ -16,7 +16,7 @@ class TextCellBloc extends Bloc<TextCellEvent, TextCellState> {
     required GridCellContext cellContext,
     required GridCellContext cellContext,
   })  : _service = CellService(),
   })  : _service = CellService(),
         _cellListener = CellListener(rowId: cellContext.rowId, fieldId: cellContext.fieldId),
         _cellListener = CellListener(rowId: cellContext.rowId, fieldId: cellContext.fieldId),
-        super(TextCellState.initial(cellContext.cellData)) {
+        super(TextCellState.initial(cellContext.gridCell)) {
     on<TextCellEvent>(
     on<TextCellEvent>(
       (event, emit) async {
       (event, emit) async {
         await event.map(
         await event.map(

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

@@ -57,17 +57,17 @@ class RowBloc extends Bloc<RowEvent, RowState> {
 class RowEvent with _$RowEvent {
 class RowEvent with _$RowEvent {
   const factory RowEvent.initial() = _InitialRow;
   const factory RowEvent.initial() = _InitialRow;
   const factory RowEvent.createRow() = _CreateRow;
   const factory RowEvent.createRow() = _CreateRow;
-  const factory RowEvent.didReceiveCellDatas(CellDataMap cellData) = _DidReceiveCellDatas;
+  const factory RowEvent.didReceiveCellDatas(GridCellMap cellData) = _DidReceiveCellDatas;
 }
 }
 
 
 @freezed
 @freezed
 class RowState with _$RowState {
 class RowState with _$RowState {
   const factory RowState({
   const factory RowState({
     required GridRow rowData,
     required GridRow rowData,
-    required CellDataMap cellDataMap,
+    required GridCellMap cellDataMap,
   }) = _RowState;
   }) = _RowState;
 
 
-  factory RowState.initial(GridRow rowData, CellDataMap cellDataMap) => RowState(
+  factory RowState.initial(GridRow rowData, GridCellMap cellDataMap) => RowState(
         rowData: rowData,
         rowData: rowData,
         cellDataMap: cellDataMap,
         cellDataMap: cellDataMap,
       );
       );

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

@@ -24,7 +24,7 @@ class RowDetailBloc extends Bloc<RowDetailEvent, RowDetailState> {
             _loadCellData();
             _loadCellData();
           },
           },
           didReceiveCellDatas: (_DidReceiveCellDatas value) {
           didReceiveCellDatas: (_DidReceiveCellDatas value) {
-            emit(state.copyWith(cellDatas: value.cellDatas));
+            emit(state.copyWith(gridCells: value.gridCells));
           },
           },
         );
         );
       },
       },
@@ -58,16 +58,16 @@ class RowDetailBloc extends Bloc<RowDetailEvent, RowDetailState> {
 @freezed
 @freezed
 class RowDetailEvent with _$RowDetailEvent {
 class RowDetailEvent with _$RowDetailEvent {
   const factory RowDetailEvent.initial() = _Initial;
   const factory RowDetailEvent.initial() = _Initial;
-  const factory RowDetailEvent.didReceiveCellDatas(List<GridCell> cellDatas) = _DidReceiveCellDatas;
+  const factory RowDetailEvent.didReceiveCellDatas(List<GridCell> gridCells) = _DidReceiveCellDatas;
 }
 }
 
 
 @freezed
 @freezed
 class RowDetailState with _$RowDetailState {
 class RowDetailState with _$RowDetailState {
   const factory RowDetailState({
   const factory RowDetailState({
-    required List<GridCell> cellDatas,
+    required List<GridCell> gridCells,
   }) = _RowDetailState;
   }) = _RowDetailState;
 
 
   factory RowDetailState.initial() => RowDetailState(
   factory RowDetailState.initial() => RowDetailState(
-        cellDatas: List.empty(),
+        gridCells: List.empty(),
       );
       );
 }
 }

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

@@ -84,7 +84,7 @@ class GridRowCache {
 
 
   RowUpdateCallback addRowListener({
   RowUpdateCallback addRowListener({
     required String rowId,
     required String rowId,
-    void Function(CellDataMap)? onUpdated,
+    void Function(GridCellMap)? onUpdated,
     bool Function()? listenWhen,
     bool Function()? listenWhen,
   }) {
   }) {
     listenrHandler() {
     listenrHandler() {
@@ -99,7 +99,7 @@ class GridRowCache {
       notify() {
       notify() {
         final row = _rowNotifier.rowDataWithId(rowId);
         final row = _rowNotifier.rowDataWithId(rowId);
         if (row != null) {
         if (row != null) {
-          final CellDataMap cellDataMap = _makeCellDataMap(rowId, row);
+          final GridCellMap cellDataMap = _makeCellDataMap(rowId, row);
           onUpdated(cellDataMap);
           onUpdated(cellDataMap);
         }
         }
       }
       }
@@ -118,8 +118,8 @@ class GridRowCache {
     return listenrHandler;
     return listenrHandler;
   }
   }
 
 
-  CellDataMap _makeCellDataMap(String rowId, Row? row) {
-    var cellDataMap = CellDataMap.new();
+  GridCellMap _makeCellDataMap(String rowId, Row? row) {
+    var cellDataMap = GridCellMap.new();
     for (final field in _fieldDelegate.fields) {
     for (final field in _fieldDelegate.fields) {
       if (field.visibility) {
       if (field.visibility) {
         cellDataMap[field.id] = GridCell(
         cellDataMap[field.id] = GridCell(
@@ -137,7 +137,7 @@ class GridRowCache {
     _rowNotifier.removeListener(callback);
     _rowNotifier.removeListener(callback);
   }
   }
 
 
-  CellDataMap loadCellData(String rowId) {
+  GridCellMap loadCellData(String rowId) {
     final Row? data = _rowNotifier.rowDataWithId(rowId);
     final Row? data = _rowNotifier.rowDataWithId(rowId);
     if (data == null) {
     if (data == null) {
       final payload = RowIdentifierPayload.create()
       final payload = RowIdentifierPayload.create()

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

@@ -1,6 +1,8 @@
 import 'package:app_flowy/workspace/application/grid/cell/cell_service.dart';
 import 'package:app_flowy/workspace/application/grid/cell/cell_service.dart';
+import 'package:app_flowy/workspace/application/grid/cell/select_option_service.dart';
 import 'package:flowy_infra_ui/style_widget/hover.dart';
 import 'package:flowy_infra_ui/style_widget/hover.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show FieldType;
+import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Cell, FieldType;
+import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
 import 'package:flutter/widgets.dart';
 import 'package:flutter/widgets.dart';
 import 'checkbox_cell.dart';
 import 'checkbox_cell.dart';
 import 'date_cell.dart';
 import 'date_cell.dart';
@@ -8,22 +10,47 @@ import 'number_cell.dart';
 import 'selection_cell/selection_cell.dart';
 import 'selection_cell/selection_cell.dart';
 import 'text_cell.dart';
 import 'text_cell.dart';
 
 
-GridCellWidget buildGridCell(GridCellContext cellContext, {GridCellStyle? style}) {
-  final key = ValueKey(cellContext.cellId);
-  final fieldType = cellContext.cellData.field.fieldType;
-  switch (fieldType) {
+GridCellWidget buildGridCellWidget(GridCell gridCell, GridCellCache cellCache, {GridCellStyle? style}) {
+  final key = ValueKey(gridCell.rowId + gridCell.field.id);
+
+  final cellContext = makeCellContext(gridCell, cellCache);
+
+  switch (gridCell.field.fieldType) {
     case FieldType.Checkbox:
     case FieldType.Checkbox:
       return CheckboxCell(cellContext: cellContext, key: key);
       return CheckboxCell(cellContext: cellContext, key: key);
     case FieldType.DateTime:
     case FieldType.DateTime:
       return DateCell(cellContext: cellContext, key: key);
       return DateCell(cellContext: cellContext, key: key);
     case FieldType.MultiSelect:
     case FieldType.MultiSelect:
-      return MultiSelectCell(cellContext: cellContext, style: style, key: key);
+      return MultiSelectCell(cellContext: cellContext as GridCellContext<SelectOptionContext>, style: style, key: key);
     case FieldType.Number:
     case FieldType.Number:
       return NumberCell(cellContext: cellContext, key: key);
       return NumberCell(cellContext: cellContext, key: key);
     case FieldType.RichText:
     case FieldType.RichText:
       return GridTextCell(cellContext: cellContext, style: style, key: key);
       return GridTextCell(cellContext: cellContext, style: style, key: key);
     case FieldType.SingleSelect:
     case FieldType.SingleSelect:
-      return SingleSelectCell(cellContext: cellContext, style: style, key: key);
+      return SingleSelectCell(cellContext: cellContext as GridCellContext<SelectOptionContext>, style: style, key: key);
+    default:
+      throw UnimplementedError;
+  }
+}
+
+GridCellContext makeCellContext(GridCell gridCell, GridCellCache cellCache) {
+  switch (gridCell.field.fieldType) {
+    case FieldType.Checkbox:
+    case FieldType.DateTime:
+    case FieldType.Number:
+    case FieldType.RichText:
+      return GridCellContext<Cell>(
+        gridCell: gridCell,
+        cellCache: cellCache,
+        cellDataLoader: DefaultCellDataLoader(gridCell: gridCell),
+      );
+    case FieldType.MultiSelect:
+    case FieldType.SingleSelect:
+      return GridCellContext<SelectOptionContext>(
+        gridCell: gridCell,
+        cellCache: cellCache,
+        cellDataLoader: SelectOptionCellDataLoader(gridCell: gridCell),
+      );
     default:
     default:
       throw UnimplementedError;
       throw UnimplementedError;
   }
   }

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

@@ -3,6 +3,7 @@ import 'package:app_flowy/workspace/application/grid/prelude.dart';
 import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart';
 import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra_ui/style_widget/text.dart';
 import 'package:flowy_infra_ui/style_widget/text.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 
 
@@ -18,7 +19,7 @@ class SelectOptionCellStyle extends GridCellStyle {
 }
 }
 
 
 class SingleSelectCell extends GridCellWidget {
 class SingleSelectCell extends GridCellWidget {
-  final GridCellContext cellContext;
+  final GridCellContext<SelectOptionContext> cellContext;
   late final SelectOptionCellStyle? cellStyle;
   late final SelectOptionCellStyle? cellStyle;
 
 
   SingleSelectCell({
   SingleSelectCell({
@@ -66,9 +67,7 @@ class _SingleSelectCellState extends State<SingleSelectCell> {
                 widget.onFocus.value = true;
                 widget.onFocus.value = true;
                 SelectOptionCellEditor.show(
                 SelectOptionCellEditor.show(
                   context,
                   context,
-                  state.cellData,
-                  state.options,
-                  state.selectedOptions,
+                  widget.cellContext,
                   () => widget.onFocus.value = false,
                   () => widget.onFocus.value = false,
                 );
                 );
               },
               },
@@ -89,7 +88,7 @@ class _SingleSelectCellState extends State<SingleSelectCell> {
 
 
 //----------------------------------------------------------------
 //----------------------------------------------------------------
 class MultiSelectCell extends GridCellWidget {
 class MultiSelectCell extends GridCellWidget {
-  final GridCellContext cellContext;
+  final GridCellContext<SelectOptionContext> cellContext;
   late final SelectOptionCellStyle? cellStyle;
   late final SelectOptionCellStyle? cellStyle;
 
 
   MultiSelectCell({
   MultiSelectCell({
@@ -135,9 +134,7 @@ class _MultiSelectCellState extends State<MultiSelectCell> {
                 widget.onFocus.value = true;
                 widget.onFocus.value = true;
                 SelectOptionCellEditor.show(
                 SelectOptionCellEditor.show(
                   context,
                   context,
-                  state.cellData,
-                  state.options,
-                  state.selectedOptions,
+                  widget.cellContext,
                   () => widget.onFocus.value = false,
                   () => widget.onFocus.value = false,
                 );
                 );
               },
               },

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

@@ -26,15 +26,11 @@ import 'text_field.dart';
 const double _editorPannelWidth = 300;
 const double _editorPannelWidth = 300;
 
 
 class SelectOptionCellEditor extends StatelessWidget with FlowyOverlayDelegate {
 class SelectOptionCellEditor extends StatelessWidget with FlowyOverlayDelegate {
-  final GridCell cellData;
-  final List<SelectOption> options;
-  final List<SelectOption> selectedOptions;
+  final GridCellContext<SelectOptionContext> cellContext;
   final VoidCallback onDismissed;
   final VoidCallback onDismissed;
 
 
   const SelectOptionCellEditor({
   const SelectOptionCellEditor({
-    required this.cellData,
-    required this.options,
-    required this.selectedOptions,
+    required this.cellContext,
     required this.onDismissed,
     required this.onDismissed,
     Key? key,
     Key? key,
   }) : super(key: key);
   }) : super(key: key);
@@ -43,9 +39,7 @@ class SelectOptionCellEditor extends StatelessWidget with FlowyOverlayDelegate {
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     return BlocProvider(
     return BlocProvider(
       create: (context) => SelectOptionEditorBloc(
       create: (context) => SelectOptionEditorBloc(
-        cellData: cellData,
-        options: options,
-        selectedOptions: selectedOptions,
+        cellContext: cellContext,
       )..add(const SelectOptionEditorEvent.initial()),
       )..add(const SelectOptionEditorEvent.initial()),
       child: BlocBuilder<SelectOptionEditorBloc, SelectOptionEditorState>(
       child: BlocBuilder<SelectOptionEditorBloc, SelectOptionEditorState>(
         builder: (context, state) {
         builder: (context, state) {
@@ -67,16 +61,12 @@ class SelectOptionCellEditor extends StatelessWidget with FlowyOverlayDelegate {
 
 
   static void show(
   static void show(
     BuildContext context,
     BuildContext context,
-    GridCell cellData,
-    List<SelectOption> options,
-    List<SelectOption> selectedOptions,
+    GridCellContext<SelectOptionContext> cellContext,
     VoidCallback onDismissed,
     VoidCallback onDismissed,
   ) {
   ) {
     SelectOptionCellEditor.remove(context);
     SelectOptionCellEditor.remove(context);
     final editor = SelectOptionCellEditor(
     final editor = SelectOptionCellEditor(
-      cellData: cellData,
-      options: options,
-      selectedOptions: selectedOptions,
+      cellContext: cellContext,
       onDismissed: onDismissed,
       onDismissed: onDismissed,
     );
     );
 
 

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

@@ -170,22 +170,17 @@ class _RowCells extends StatelessWidget {
     );
     );
   }
   }
 
 
-  List<Widget> _makeCells(CellDataMap cellDataMap) {
-    return cellDataMap.values.map(
-      (cellData) {
+  List<Widget> _makeCells(GridCellMap gridCellMap) {
+    return gridCellMap.values.map(
+      (gridCell) {
         Widget? expander;
         Widget? expander;
-        if (cellData.field.isPrimary) {
+        if (gridCell.field.isPrimary) {
           expander = _CellExpander(onExpand: onExpand);
           expander = _CellExpander(onExpand: onExpand);
         }
         }
 
 
-        final cellContext = GridCellContext(
-          cellData: cellData,
-          cellCache: cellCache,
-        );
-
         return CellContainer(
         return CellContainer(
-          width: cellData.field.width.toDouble(),
-          child: buildGridCell(cellContext),
+          width: gridCell.field.width.toDouble(),
+          child: buildGridCellWidget(gridCell, cellCache),
           expander: expander,
           expander: expander,
         );
         );
       },
       },

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

@@ -84,7 +84,7 @@ class _PropertyList extends StatelessWidget {
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     return BlocBuilder<RowDetailBloc, RowDetailState>(
     return BlocBuilder<RowDetailBloc, RowDetailState>(
-      buildWhen: (previous, current) => previous.cellDatas != current.cellDatas,
+      buildWhen: (previous, current) => previous.gridCells != current.gridCells,
       builder: (context, state) {
       builder: (context, state) {
         return ScrollbarListStack(
         return ScrollbarListStack(
           axis: Axis.vertical,
           axis: Axis.vertical,
@@ -92,13 +92,12 @@ class _PropertyList extends StatelessWidget {
           barSize: GridSize.scrollBarSize,
           barSize: GridSize.scrollBarSize,
           child: ListView.separated(
           child: ListView.separated(
             controller: _scrollController,
             controller: _scrollController,
-            itemCount: state.cellDatas.length,
+            itemCount: state.gridCells.length,
             itemBuilder: (BuildContext context, int index) {
             itemBuilder: (BuildContext context, int index) {
-              final cellContext = GridCellContext(
-                cellData: state.cellDatas[index],
+              return _RowDetailCell(
+                gridCell: state.gridCells[index],
                 cellCache: cellCache,
                 cellCache: cellCache,
               );
               );
-              return _RowDetailCell(cellContext: cellContext);
             },
             },
             separatorBuilder: (BuildContext context, int index) {
             separatorBuilder: (BuildContext context, int index) {
               return const VSpace(2);
               return const VSpace(2);
@@ -111,16 +110,22 @@ class _PropertyList extends StatelessWidget {
 }
 }
 
 
 class _RowDetailCell extends StatelessWidget {
 class _RowDetailCell extends StatelessWidget {
-  final GridCellContext cellContext;
-  const _RowDetailCell({required this.cellContext, Key? key}) : super(key: key);
+  final GridCell gridCell;
+  final GridCellCache cellCache;
+  const _RowDetailCell({
+    required this.gridCell,
+    required this.cellCache,
+    Key? key,
+  }) : super(key: key);
 
 
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     final theme = context.watch<AppTheme>();
     final theme = context.watch<AppTheme>();
 
 
-    final cell = buildGridCell(
-      cellContext,
-      style: _buildCellStyle(theme, cellContext.fieldType),
+    final cell = buildGridCellWidget(
+      gridCell,
+      cellCache,
+      style: _buildCellStyle(theme, gridCell.field.fieldType),
     );
     );
     return SizedBox(
     return SizedBox(
       height: 36,
       height: 36,
@@ -130,7 +135,7 @@ class _RowDetailCell extends StatelessWidget {
         children: [
         children: [
           SizedBox(
           SizedBox(
             width: 150,
             width: 150,
-            child: FieldCellButton(field: cellContext.field, onTap: () => _showFieldEditor(context)),
+            child: FieldCellButton(field: gridCell.field, onTap: () => _showFieldEditor(context)),
           ),
           ),
           const HSpace(10),
           const HSpace(10),
           Expanded(
           Expanded(
@@ -146,10 +151,10 @@ class _RowDetailCell extends StatelessWidget {
 
 
   void _showFieldEditor(BuildContext context) {
   void _showFieldEditor(BuildContext context) {
     FieldEditor(
     FieldEditor(
-      gridId: cellContext.gridId,
+      gridId: gridCell.gridId,
       fieldContextLoader: FieldContextLoaderAdaptor(
       fieldContextLoader: FieldContextLoaderAdaptor(
-        gridId: cellContext.gridId,
-        field: cellContext.field,
+        gridId: gridCell.gridId,
+        field: gridCell.field,
       ),
       ),
     ).show(context);
     ).show(context);
   }
   }