Explorar o código

refactor: customize cell data persistence

appflowy %!s(int64=3) %!d(string=hai) anos
pai
achega
6fb163b296
Modificáronse 27 ficheiros con 595 adicións e 452 borrados
  1. 4 4
      frontend/app_flowy/lib/startup/deps_resolver.dart
  2. 0 379
      frontend/app_flowy/lib/workspace/application/grid/cell/cell_service.dart
  3. 73 0
      frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_service.dart
  4. 182 0
      frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/context_builder.dart
  5. 109 0
      frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/data_cache.dart
  6. 83 0
      frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/data_loader.dart
  7. 67 0
      frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/data_persistence.dart
  8. 2 2
      frontend/app_flowy/lib/workspace/application/grid/cell/checkbox_cell_bloc.dart
  9. 3 4
      frontend/app_flowy/lib/workspace/application/grid/cell/date_cal_bloc.dart
  10. 5 10
      frontend/app_flowy/lib/workspace/application/grid/cell/date_cell_bloc.dart
  11. 3 3
      frontend/app_flowy/lib/workspace/application/grid/cell/number_cell_bloc.dart
  12. 1 24
      frontend/app_flowy/lib/workspace/application/grid/cell/select_option_service.dart
  13. 1 1
      frontend/app_flowy/lib/workspace/application/grid/cell/selection_cell_bloc.dart
  14. 1 1
      frontend/app_flowy/lib/workspace/application/grid/cell/selection_editor_bloc.dart
  15. 3 3
      frontend/app_flowy/lib/workspace/application/grid/cell/text_cell_bloc.dart
  16. 1 1
      frontend/app_flowy/lib/workspace/application/grid/grid_bloc.dart
  17. 1 1
      frontend/app_flowy/lib/workspace/application/grid/grid_service.dart
  18. 1 1
      frontend/app_flowy/lib/workspace/application/grid/prelude.dart
  19. 1 1
      frontend/app_flowy/lib/workspace/application/grid/row/row_bloc.dart
  20. 1 1
      frontend/app_flowy/lib/workspace/application/grid/row/row_detail_bloc.dart
  21. 1 1
      frontend/app_flowy/lib/workspace/application/grid/row/row_service.dart
  22. 1 1
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart
  23. 5 5
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell/calendar.dart
  24. 1 1
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_editor.dart
  25. 1 1
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/row_detail.dart
  26. 1 0
      frontend/app_flowy/packages/flowy_sdk/lib/dispatch/dispatch.dart
  27. 43 7
      frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs

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

@@ -164,7 +164,7 @@ void _resolveGridDeps(GetIt getIt) {
     ),
   );
 
-  getIt.registerFactoryParam<TextCellBloc, GridDefaultCellContext, void>(
+  getIt.registerFactoryParam<TextCellBloc, GridCellContext, void>(
     (context, _) => TextCellBloc(
       cellContext: context,
     ),
@@ -176,19 +176,19 @@ void _resolveGridDeps(GetIt getIt) {
     ),
   );
 
-  getIt.registerFactoryParam<NumberCellBloc, GridDefaultCellContext, void>(
+  getIt.registerFactoryParam<NumberCellBloc, GridCellContext, void>(
     (context, _) => NumberCellBloc(
       cellContext: context,
     ),
   );
 
-  getIt.registerFactoryParam<DateCellBloc, GridDefaultCellContext, void>(
+  getIt.registerFactoryParam<DateCellBloc, GridDateCellContext, void>(
     (context, _) => DateCellBloc(
       cellContext: context,
     ),
   );
 
-  getIt.registerFactoryParam<CheckboxCellBloc, GridDefaultCellContext, void>(
+  getIt.registerFactoryParam<CheckboxCellBloc, GridCellContext, void>(
     (cellData, _) => CheckboxCellBloc(
       service: CellService(),
       cellContext: cellData,

+ 0 - 379
frontend/app_flowy/lib/workspace/application/grid/cell/cell_service.dart

@@ -1,379 +0,0 @@
-import 'dart:async';
-import 'dart:collection';
-
-import 'package:app_flowy/workspace/application/grid/cell/select_option_service.dart';
-import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
-import 'package:dartz/dartz.dart';
-import 'package:equatable/equatable.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-data-model/grid.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:flutter/foundation.dart';
-import 'package:freezed_annotation/freezed_annotation.dart';
-import 'package:app_flowy/workspace/application/grid/cell/cell_listener.dart';
-
-part 'cell_service.freezed.dart';
-
-typedef GridDefaultCellContext = GridCellContext<Cell>;
-typedef GridSelectOptionCellContext = GridCellContext<SelectOptionContext>;
-
-class GridCellContextBuilder {
-  final GridCellCache _cellCache;
-  final GridCell _gridCell;
-  GridCellContextBuilder({
-    required GridCellCache cellCache,
-    required GridCell gridCell,
-  })  : _cellCache = cellCache,
-        _gridCell = gridCell;
-
-  GridCellContext build() {
-    switch (_gridCell.field.fieldType) {
-      case FieldType.Checkbox:
-      case FieldType.DateTime:
-      case FieldType.Number:
-        return GridDefaultCellContext(
-          gridCell: _gridCell,
-          cellCache: _cellCache,
-          cellDataLoader: DefaultCellDataLoader(gridCell: _gridCell, reloadOnCellChanged: true),
-        );
-      case FieldType.RichText:
-        return GridDefaultCellContext(
-          gridCell: _gridCell,
-          cellCache: _cellCache,
-          cellDataLoader: DefaultCellDataLoader(gridCell: _gridCell),
-        );
-      case FieldType.MultiSelect:
-      case FieldType.SingleSelect:
-        return GridSelectOptionCellContext(
-          gridCell: _gridCell,
-          cellCache: _cellCache,
-          cellDataLoader: SelectOptionCellDataLoader(gridCell: _gridCell),
-        );
-      default:
-        throw UnimplementedError;
-    }
-  }
-}
-
-// ignore: must_be_immutable
-class GridCellContext<T> extends Equatable {
-  final GridCell gridCell;
-  final GridCellCache cellCache;
-  final GridCellCacheKey _cacheKey;
-  final GridCellDataLoader<T> cellDataLoader;
-  final CellService _cellService = CellService();
-  final FieldService _fieldService;
-
-  late final CellListener _cellListener;
-  late final ValueNotifier<T?> _cellDataNotifier;
-  bool isListening = false;
-  VoidCallback? _onFieldChangedFn;
-  Timer? _delayOperation;
-
-  GridCellContext({
-    required this.gridCell,
-    required this.cellCache,
-    required this.cellDataLoader,
-  })  : _fieldService = FieldService(gridId: gridCell.gridId, fieldId: gridCell.field.id),
-        _cacheKey = GridCellCacheKey(objectId: gridCell.rowId, fieldId: gridCell.field.id);
-
-  GridCellContext<T> clone() {
-    return GridCellContext(
-      gridCell: gridCell,
-      cellDataLoader: cellDataLoader,
-      cellCache: cellCache,
-    );
-  }
-
-  String get gridId => gridCell.gridId;
-
-  String get rowId => gridCell.rowId;
-
-  String get cellId => gridCell.rowId + gridCell.field.id;
-
-  String get fieldId => gridCell.field.id;
-
-  Field get field => gridCell.field;
-
-  FieldType get fieldType => gridCell.field.fieldType;
-
-  VoidCallback? startListening({required void Function(T) onCellChanged}) {
-    if (isListening) {
-      Log.error("Already started. It seems like you should call clone first");
-      return null;
-    }
-
-    isListening = true;
-    _cellDataNotifier = ValueNotifier(cellCache.get(_cacheKey));
-    _cellListener = CellListener(rowId: gridCell.rowId, fieldId: gridCell.field.id);
-    _cellListener.start(onCellChanged: (result) {
-      result.fold(
-        (_) => _loadData(),
-        (err) => Log.error(err),
-      );
-    });
-
-    if (cellDataLoader.reloadOnFieldChanged) {
-      _onFieldChangedFn = () {
-        _loadData();
-      };
-      cellCache.addListener(_cacheKey, _onFieldChangedFn!);
-    }
-
-    onCellChangedFn() {
-      final value = _cellDataNotifier.value;
-      if (value is T) {
-        onCellChanged(value);
-      }
-
-      if (cellDataLoader.reloadOnCellChanged) {
-        _loadData();
-      }
-    }
-
-    _cellDataNotifier.addListener(onCellChangedFn);
-    return onCellChangedFn;
-  }
-
-  void removeListener(VoidCallback fn) {
-    _cellDataNotifier.removeListener(fn);
-  }
-
-  T? getCellData() {
-    final data = cellCache.get(_cacheKey);
-    if (data == null) {
-      _loadData();
-    }
-    return data;
-  }
-
-  Future<Either<List<int>, FlowyError>> getTypeOptionData() {
-    return _fieldService.getTypeOptionData(fieldType: fieldType);
-  }
-
-  void saveCellData(String data) {
-    _cellService.updateCell(gridId: gridId, fieldId: field.id, rowId: rowId, data: data).then((result) {
-      result.fold((l) => null, (err) => Log.error(err));
-    });
-  }
-
-  void _loadData() {
-    _delayOperation?.cancel();
-    _delayOperation = Timer(const Duration(milliseconds: 10), () {
-      cellDataLoader.loadData().then((data) {
-        _cellDataNotifier.value = data;
-        cellCache.insert(GridCellCacheData(key: _cacheKey, object: data));
-      });
-    });
-  }
-
-  void dispose() {
-    _delayOperation?.cancel();
-
-    if (_onFieldChangedFn != null) {
-      cellCache.removeListener(_cacheKey, _onFieldChangedFn!);
-      _onFieldChangedFn = null;
-    }
-  }
-
-  @override
-  List<Object> get props => [cellCache.get(_cacheKey) ?? "", cellId];
-}
-
-abstract class GridCellDataLoader<T> {
-  Future<T?> loadData();
-
-  bool get reloadOnFieldChanged => true;
-  bool get reloadOnCellChanged => false;
-}
-
-abstract class GridCellDataConfig {
-  bool get reloadOnFieldChanged => true;
-  bool get reloadOnCellChanged => false;
-}
-
-class DefaultCellDataLoader extends GridCellDataLoader<Cell> {
-  final CellService service = CellService();
-  final GridCell gridCell;
-  @override
-  final bool reloadOnCellChanged;
-
-  DefaultCellDataLoader({
-    required this.gridCell,
-    this.reloadOnCellChanged = false,
-  });
-
-  @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
-typedef GridCellMap = LinkedHashMap<String, GridCell>;
-
-class GridCellCacheData {
-  GridCellCacheKey key;
-  dynamic object;
-  GridCellCacheData({
-    required this.key,
-    required this.object,
-  });
-}
-
-class GridCellCacheKey {
-  final String fieldId;
-  final String objectId;
-  GridCellCacheKey({
-    required this.fieldId,
-    required this.objectId,
-  });
-}
-
-abstract class GridCellFieldDelegate {
-  void onFieldChanged(void Function(String) callback);
-  void dispose();
-}
-
-class GridCellCache {
-  final String gridId;
-  final GridCellFieldDelegate fieldDelegate;
-
-  /// fieldId: {objectId: callback}
-  final Map<String, Map<String, List<VoidCallback>>> _listenerByFieldId = {};
-
-  /// fieldId: {cacheKey: cacheData}
-  final Map<String, Map<String, dynamic>> _cellDataByFieldId = {};
-  GridCellCache({
-    required this.gridId,
-    required this.fieldDelegate,
-  }) {
-    fieldDelegate.onFieldChanged((fieldId) {
-      _cellDataByFieldId.remove(fieldId);
-      final map = _listenerByFieldId[fieldId];
-      if (map != null) {
-        for (final callbacks in map.values) {
-          for (final callback in callbacks) {
-            callback();
-          }
-        }
-      }
-    });
-  }
-
-  void addListener(GridCellCacheKey cacheKey, VoidCallback callback) {
-    var map = _listenerByFieldId[cacheKey.fieldId];
-    if (map == null) {
-      _listenerByFieldId[cacheKey.fieldId] = {};
-      map = _listenerByFieldId[cacheKey.fieldId];
-      map![cacheKey.objectId] = [callback];
-    } else {
-      var objects = map[cacheKey.objectId];
-      if (objects == null) {
-        map[cacheKey.objectId] = [callback];
-      } else {
-        objects.add(callback);
-      }
-    }
-  }
-
-  void removeListener(GridCellCacheKey cacheKey, VoidCallback fn) {
-    var callbacks = _listenerByFieldId[cacheKey.fieldId]?[cacheKey.objectId];
-    final index = callbacks?.indexWhere((callback) => callback == fn);
-    if (index != null && index != -1) {
-      callbacks?.removeAt(index);
-    }
-  }
-
-  void insert<T extends GridCellCacheData>(T item) {
-    var map = _cellDataByFieldId[item.key.fieldId];
-    if (map == null) {
-      _cellDataByFieldId[item.key.fieldId] = {};
-      map = _cellDataByFieldId[item.key.fieldId];
-    }
-
-    map![item.key.objectId] = item.object;
-  }
-
-  T? get<T>(GridCellCacheKey key) {
-    final map = _cellDataByFieldId[key.fieldId];
-    if (map == null) {
-      return null;
-    } else {
-      final object = map[key.objectId];
-      if (object is T) {
-        return object;
-      } else {
-        if (object != null) {
-          Log.error("Cache data type does not match the cache data type");
-        }
-
-        return null;
-      }
-    }
-  }
-
-  Future<void> dispose() async {
-    fieldDelegate.dispose();
-  }
-}
-
-class CellService {
-  CellService();
-
-  Future<Either<void, FlowyError>> updateCell({
-    required String gridId,
-    required String fieldId,
-    required String rowId,
-    required String data,
-  }) {
-    final payload = CellChangeset.create()
-      ..gridId = gridId
-      ..fieldId = fieldId
-      ..rowId = rowId
-      ..cellContentChangeset = data;
-    return GridEventUpdateCell(payload).send();
-  }
-
-  Future<Either<Cell, FlowyError>> getCell({
-    required String gridId,
-    required String fieldId,
-    required String rowId,
-  }) {
-    final payload = CellIdentifierPayload.create()
-      ..gridId = gridId
-      ..fieldId = fieldId
-      ..rowId = rowId;
-    return GridEventGetCell(payload).send();
-  }
-}
-
-@freezed
-class GridCell with _$GridCell {
-  const factory GridCell({
-    required String gridId,
-    required String rowId,
-    required Field field,
-    Cell? cell,
-  }) = _GridCell;
-
-  // ignore: unused_element
-  const GridCell._();
-
-  String cellId() {
-    return rowId + field.id + "${field.fieldType}";
-  }
-}

+ 73 - 0
frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_service.dart

@@ -0,0 +1,73 @@
+import 'dart:async';
+import 'dart:collection';
+
+import 'package:dartz/dartz.dart';
+import 'package:equatable/equatable.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-data-model/grid.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/cell_entities.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
+import 'package:flutter/foundation.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/select_option_service.dart';
+import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
+
+part 'cell_service.freezed.dart';
+part 'data_loader.dart';
+part 'context_builder.dart';
+part 'data_cache.dart';
+part 'data_persistence.dart';
+
+// key: rowId
+
+class CellService {
+  CellService();
+
+  Future<Either<void, FlowyError>> updateCell({
+    required String gridId,
+    required String fieldId,
+    required String rowId,
+    required String data,
+  }) {
+    final payload = CellChangeset.create()
+      ..gridId = gridId
+      ..fieldId = fieldId
+      ..rowId = rowId
+      ..cellContentChangeset = data;
+    return GridEventUpdateCell(payload).send();
+  }
+
+  Future<Either<Cell, FlowyError>> getCell({
+    required String gridId,
+    required String fieldId,
+    required String rowId,
+  }) {
+    final payload = CellIdentifierPayload.create()
+      ..gridId = gridId
+      ..fieldId = fieldId
+      ..rowId = rowId;
+    return GridEventGetCell(payload).send();
+  }
+}
+
+@freezed
+class GridCell with _$GridCell {
+  const factory GridCell({
+    required String gridId,
+    required String rowId,
+    required Field field,
+    Cell? cell,
+  }) = _GridCell;
+
+  // ignore: unused_element
+  const GridCell._();
+
+  String cellId() {
+    return rowId + field.id + "${field.fieldType}";
+  }
+}

+ 182 - 0
frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/context_builder.dart

@@ -0,0 +1,182 @@
+part of 'cell_service.dart';
+
+typedef GridCellContext = _GridCellContext<Cell, String>;
+typedef GridSelectOptionCellContext = _GridCellContext<SelectOptionContext, String>;
+typedef GridDateCellContext = _GridCellContext<Cell, DateCellPersistenceData>;
+
+class GridCellContextBuilder {
+  final GridCellCache _cellCache;
+  final GridCell _gridCell;
+  GridCellContextBuilder({
+    required GridCellCache cellCache,
+    required GridCell gridCell,
+  })  : _cellCache = cellCache,
+        _gridCell = gridCell;
+
+  _GridCellContext build() {
+    switch (_gridCell.field.fieldType) {
+      case FieldType.Checkbox:
+        return GridCellContext(
+          gridCell: _gridCell,
+          cellCache: _cellCache,
+          cellDataLoader: CellDataLoader(gridCell: _gridCell),
+          cellDataPersistence: CellDataPersistence(gridCell: _gridCell),
+        );
+      case FieldType.DateTime:
+        return GridDateCellContext(
+          gridCell: _gridCell,
+          cellCache: _cellCache,
+          cellDataLoader: CellDataLoader(gridCell: _gridCell),
+          cellDataPersistence: NumberCellDataPersistence(gridCell: _gridCell),
+        );
+      case FieldType.Number:
+        return GridCellContext(
+          gridCell: _gridCell,
+          cellCache: _cellCache,
+          cellDataLoader: CellDataLoader(gridCell: _gridCell, reloadOnCellChanged: true),
+          cellDataPersistence: CellDataPersistence(gridCell: _gridCell),
+        );
+      case FieldType.RichText:
+        return GridCellContext(
+          gridCell: _gridCell,
+          cellCache: _cellCache,
+          cellDataLoader: CellDataLoader(gridCell: _gridCell),
+          cellDataPersistence: CellDataPersistence(gridCell: _gridCell),
+        );
+      case FieldType.MultiSelect:
+      case FieldType.SingleSelect:
+        return GridSelectOptionCellContext(
+          gridCell: _gridCell,
+          cellCache: _cellCache,
+          cellDataLoader: SelectOptionCellDataLoader(gridCell: _gridCell),
+          cellDataPersistence: CellDataPersistence(gridCell: _gridCell),
+        );
+      default:
+        throw UnimplementedError;
+    }
+  }
+}
+
+// ignore: must_be_immutable
+class _GridCellContext<T, D> extends Equatable {
+  final GridCell gridCell;
+  final GridCellCache cellCache;
+  final GridCellCacheKey _cacheKey;
+  final _GridCellDataLoader<T> cellDataLoader;
+  final _GridCellDataPersistence<D> cellDataPersistence;
+  final FieldService _fieldService;
+
+  late final CellListener _cellListener;
+  late final ValueNotifier<T?> _cellDataNotifier;
+  bool isListening = false;
+  VoidCallback? _onFieldChangedFn;
+  Timer? _delayOperation;
+
+  _GridCellContext({
+    required this.gridCell,
+    required this.cellCache,
+    required this.cellDataLoader,
+    required this.cellDataPersistence,
+  })  : _fieldService = FieldService(gridId: gridCell.gridId, fieldId: gridCell.field.id),
+        _cacheKey = GridCellCacheKey(objectId: gridCell.rowId, fieldId: gridCell.field.id);
+
+  _GridCellContext<T, D> clone() {
+    return _GridCellContext(
+        gridCell: gridCell,
+        cellDataLoader: cellDataLoader,
+        cellCache: cellCache,
+        cellDataPersistence: cellDataPersistence);
+  }
+
+  String get gridId => gridCell.gridId;
+
+  String get rowId => gridCell.rowId;
+
+  String get cellId => gridCell.rowId + gridCell.field.id;
+
+  String get fieldId => gridCell.field.id;
+
+  Field get field => gridCell.field;
+
+  FieldType get fieldType => gridCell.field.fieldType;
+
+  VoidCallback? startListening({required void Function(T) onCellChanged}) {
+    if (isListening) {
+      Log.error("Already started. It seems like you should call clone first");
+      return null;
+    }
+
+    isListening = true;
+    _cellDataNotifier = ValueNotifier(cellCache.get(_cacheKey));
+    _cellListener = CellListener(rowId: gridCell.rowId, fieldId: gridCell.field.id);
+    _cellListener.start(onCellChanged: (result) {
+      result.fold(
+        (_) => _loadData(),
+        (err) => Log.error(err),
+      );
+    });
+
+    if (cellDataLoader.config.reloadOnFieldChanged) {
+      _onFieldChangedFn = () {
+        _loadData();
+      };
+      cellCache.addListener(_cacheKey, _onFieldChangedFn!);
+    }
+
+    onCellChangedFn() {
+      final value = _cellDataNotifier.value;
+      if (value is T) {
+        onCellChanged(value);
+      }
+
+      if (cellDataLoader.config.reloadOnCellChanged) {
+        _loadData();
+      }
+    }
+
+    _cellDataNotifier.addListener(onCellChangedFn);
+    return onCellChangedFn;
+  }
+
+  void removeListener(VoidCallback fn) {
+    _cellDataNotifier.removeListener(fn);
+  }
+
+  T? getCellData() {
+    final data = cellCache.get(_cacheKey);
+    if (data == null) {
+      _loadData();
+    }
+    return data;
+  }
+
+  Future<Either<List<int>, FlowyError>> getTypeOptionData() {
+    return _fieldService.getTypeOptionData(fieldType: fieldType);
+  }
+
+  void saveCellData(D data) {
+    cellDataPersistence.save(data);
+  }
+
+  void _loadData() {
+    _delayOperation?.cancel();
+    _delayOperation = Timer(const Duration(milliseconds: 10), () {
+      cellDataLoader.loadData().then((data) {
+        _cellDataNotifier.value = data;
+        cellCache.insert(GridCellCacheData(key: _cacheKey, object: data));
+      });
+    });
+  }
+
+  void dispose() {
+    _delayOperation?.cancel();
+
+    if (_onFieldChangedFn != null) {
+      cellCache.removeListener(_cacheKey, _onFieldChangedFn!);
+      _onFieldChangedFn = null;
+    }
+  }
+
+  @override
+  List<Object> get props => [cellCache.get(_cacheKey) ?? "", cellId];
+}

+ 109 - 0
frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/data_cache.dart

@@ -0,0 +1,109 @@
+part of 'cell_service.dart';
+
+typedef GridCellMap = LinkedHashMap<String, GridCell>;
+
+class GridCellCacheData {
+  GridCellCacheKey key;
+  dynamic object;
+  GridCellCacheData({
+    required this.key,
+    required this.object,
+  });
+}
+
+class GridCellCacheKey {
+  final String fieldId;
+  final String objectId;
+  GridCellCacheKey({
+    required this.fieldId,
+    required this.objectId,
+  });
+}
+
+abstract class GridCellFieldDelegate {
+  void onFieldChanged(void Function(String) callback);
+  void dispose();
+}
+
+class GridCellCache {
+  final String gridId;
+  final GridCellFieldDelegate fieldDelegate;
+
+  /// fieldId: {objectId: callback}
+  final Map<String, Map<String, List<VoidCallback>>> _listenerByFieldId = {};
+
+  /// fieldId: {cacheKey: cacheData}
+  final Map<String, Map<String, dynamic>> _cellDataByFieldId = {};
+  GridCellCache({
+    required this.gridId,
+    required this.fieldDelegate,
+  }) {
+    fieldDelegate.onFieldChanged((fieldId) {
+      _cellDataByFieldId.remove(fieldId);
+      final map = _listenerByFieldId[fieldId];
+      if (map != null) {
+        for (final callbacks in map.values) {
+          for (final callback in callbacks) {
+            callback();
+          }
+        }
+      }
+    });
+  }
+
+  void addListener(GridCellCacheKey cacheKey, VoidCallback callback) {
+    var map = _listenerByFieldId[cacheKey.fieldId];
+    if (map == null) {
+      _listenerByFieldId[cacheKey.fieldId] = {};
+      map = _listenerByFieldId[cacheKey.fieldId];
+      map![cacheKey.objectId] = [callback];
+    } else {
+      var objects = map[cacheKey.objectId];
+      if (objects == null) {
+        map[cacheKey.objectId] = [callback];
+      } else {
+        objects.add(callback);
+      }
+    }
+  }
+
+  void removeListener(GridCellCacheKey cacheKey, VoidCallback fn) {
+    var callbacks = _listenerByFieldId[cacheKey.fieldId]?[cacheKey.objectId];
+    final index = callbacks?.indexWhere((callback) => callback == fn);
+    if (index != null && index != -1) {
+      callbacks?.removeAt(index);
+    }
+  }
+
+  void insert<T extends GridCellCacheData>(T item) {
+    var map = _cellDataByFieldId[item.key.fieldId];
+    if (map == null) {
+      _cellDataByFieldId[item.key.fieldId] = {};
+      map = _cellDataByFieldId[item.key.fieldId];
+    }
+
+    map![item.key.objectId] = item.object;
+  }
+
+  T? get<T>(GridCellCacheKey key) {
+    final map = _cellDataByFieldId[key.fieldId];
+    if (map == null) {
+      return null;
+    } else {
+      final object = map[key.objectId];
+      if (object is T) {
+        return object;
+      } else {
+        if (object != null) {
+          Log.error("Cache data type does not match the cache data type");
+        }
+
+        return null;
+      }
+    }
+  }
+
+  Future<void> dispose() async {
+    fieldDelegate.dispose();
+  }
+}

+ 83 - 0
frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/data_loader.dart

@@ -0,0 +1,83 @@
+part of 'cell_service.dart';
+
+abstract class GridCellDataConfig {
+  // The cell data will reload if it receives the field's change notification.
+  bool get reloadOnFieldChanged;
+
+  // The cell data will reload if it receives the cell's change notification.
+  // For example, the number cell should be reloaded after user input the number.
+  // user input: 12
+  // cell display: $12
+  bool get reloadOnCellChanged;
+}
+
+class DefaultCellDataConfig implements GridCellDataConfig {
+  @override
+  final bool reloadOnCellChanged;
+
+  @override
+  final bool reloadOnFieldChanged;
+
+  DefaultCellDataConfig({
+    this.reloadOnCellChanged = false,
+    this.reloadOnFieldChanged = false,
+  });
+}
+
+abstract class _GridCellDataLoader<T> {
+  Future<T?> loadData();
+
+  GridCellDataConfig get config;
+}
+
+class CellDataLoader extends _GridCellDataLoader<Cell> {
+  final CellService service = CellService();
+  final GridCell gridCell;
+  final GridCellDataConfig _config;
+
+  CellDataLoader({
+    required this.gridCell,
+    bool reloadOnCellChanged = false,
+  }) : _config = DefaultCellDataConfig(reloadOnCellChanged: reloadOnCellChanged);
+
+  @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;
+      });
+    });
+  }
+
+  @override
+  GridCellDataConfig get config => _config;
+}
+
+class SelectOptionCellDataLoader extends _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;
+        },
+      );
+    });
+  }
+
+  @override
+  GridCellDataConfig get config => DefaultCellDataConfig();
+}

+ 67 - 0
frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/data_persistence.dart

@@ -0,0 +1,67 @@
+part of 'cell_service.dart';
+
+abstract class _GridCellDataPersistence<D> {
+  void save(D data);
+}
+
+class CellDataPersistence implements _GridCellDataPersistence<String> {
+  final GridCell gridCell;
+
+  CellDataPersistence({
+    required this.gridCell,
+  });
+  final CellService _cellService = CellService();
+
+  @override
+  void save(String data) {
+    _cellService
+        .updateCell(
+      gridId: gridCell.gridId,
+      fieldId: gridCell.field.id,
+      rowId: gridCell.rowId,
+      data: data,
+    )
+        .then((result) {
+      result.fold((l) => null, (err) => Log.error(err));
+    });
+  }
+}
+
+class DateCellPersistenceData {
+  final DateTime date;
+  final String? time;
+  DateCellPersistenceData({
+    required this.date,
+    this.time,
+  });
+}
+
+class NumberCellDataPersistence implements _GridCellDataPersistence<DateCellPersistenceData> {
+  final GridCell gridCell;
+  NumberCellDataPersistence({
+    required this.gridCell,
+  });
+
+  @override
+  void save(DateCellPersistenceData data) {
+    var payload = DateChangesetPayload.create()..cellIdentifier = _cellIdentifier(gridCell);
+
+    final date = (data.date.millisecondsSinceEpoch ~/ 1000).toString();
+    payload.date = date;
+
+    if (data.time != null) {
+      payload.time = data.time!;
+    }
+
+    GridEventUpdateDateCell(payload).send().then((result) {
+      result.fold((l) => null, (err) => Log.error(err));
+    });
+  }
+}
+
+CellIdentifierPayload _cellIdentifier(GridCell gridCell) {
+  return CellIdentifierPayload.create()
+    ..gridId = gridCell.gridId
+    ..fieldId = gridCell.field.id
+    ..rowId = gridCell.rowId;
+}

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

@@ -2,12 +2,12 @@ import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Cell
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'dart:async';
-import 'cell_service.dart';
+import 'cell_service/cell_service.dart';
 
 part 'checkbox_cell_bloc.freezed.dart';
 
 class CheckboxCellBloc extends Bloc<CheckboxCellEvent, CheckboxCellState> {
-  final GridDefaultCellContext cellContext;
+  final GridCellContext cellContext;
   void Function()? _onCellChangedFn;
 
   CheckboxCellBloc({

+ 3 - 4
frontend/app_flowy/lib/workspace/application/grid/cell/date_cal_bloc.dart

@@ -7,14 +7,14 @@ import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'package:table_calendar/table_calendar.dart';
 import 'dart:async';
-import 'cell_service.dart';
+import 'cell_service/cell_service.dart';
 import 'package:dartz/dartz.dart';
 import 'package:fixnum/fixnum.dart' as $fixnum;
 import 'package:protobuf/protobuf.dart';
 part 'date_cal_bloc.freezed.dart';
 
 class DateCalBloc extends Bloc<DateCalEvent, DateCalState> {
-  final GridDefaultCellContext cellContext;
+  final GridDateCellContext cellContext;
   void Function()? _onCellChangedFn;
 
   DateCalBloc({
@@ -103,8 +103,7 @@ class DateCalBloc extends Bloc<DateCalEvent, DateCalState> {
   }
 
   void _updateCellData(DateTime day) {
-    final data = day.millisecondsSinceEpoch ~/ 1000;
-    cellContext.saveCellData(data.toString());
+    cellContext.saveCellData(DateCellPersistenceData(date: day));
   }
 
   Future<void>? _updateTypeOption(

+ 5 - 10
frontend/app_flowy/lib/workspace/application/grid/cell/date_cell_bloc.dart

@@ -2,11 +2,11 @@ import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Cell
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'dart:async';
-import 'cell_service.dart';
+import 'cell_service/cell_service.dart';
 part 'date_cell_bloc.freezed.dart';
 
 class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
-  final GridDefaultCellContext cellContext;
+  final GridDateCellContext cellContext;
   void Function()? _onCellChangedFn;
 
   DateCellBloc({required this.cellContext}) : super(DateCellState.initial(cellContext)) {
@@ -17,7 +17,7 @@ class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
             _startListening();
           },
           selectDay: (_SelectDay value) {
-            _updateCellData(value.day);
+            cellContext.saveCellData(value.data);
           },
           didReceiveCellUpdate: (_DidReceiveCellUpdate value) {
             emit(state.copyWith(
@@ -51,17 +51,12 @@ class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
       }),
     );
   }
-
-  void _updateCellData(DateTime day) {
-    final data = day.millisecondsSinceEpoch ~/ 1000;
-    cellContext.saveCellData(data.toString());
-  }
 }
 
 @freezed
 class DateCellEvent with _$DateCellEvent {
   const factory DateCellEvent.initial() = _InitialCell;
-  const factory DateCellEvent.selectDay(DateTime day) = _SelectDay;
+  const factory DateCellEvent.selectDay(DateCellPersistenceData data) = _SelectDay;
   const factory DateCellEvent.didReceiveCellUpdate(Cell cell) = _DidReceiveCellUpdate;
   const factory DateCellEvent.didReceiveFieldUpdate(Field field) = _DidReceiveFieldUpdate;
 }
@@ -73,7 +68,7 @@ class DateCellState with _$DateCellState {
     required Field field,
   }) = _DateCellState;
 
-  factory DateCellState.initial(GridCellContext context) => DateCellState(
+  factory DateCellState.initial(GridDateCellContext context) => DateCellState(
         field: context.field,
         content: context.getCellData()?.content ?? "",
       );

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

@@ -2,12 +2,12 @@ import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'dart:async';
-import 'cell_service.dart';
+import 'cell_service/cell_service.dart';
 
 part 'number_cell_bloc.freezed.dart';
 
 class NumberCellBloc extends Bloc<NumberCellEvent, NumberCellState> {
-  final GridDefaultCellContext cellContext;
+  final GridCellContext cellContext;
   void Function()? _onCellChangedFn;
 
   NumberCellBloc({
@@ -68,7 +68,7 @@ class NumberCellState with _$NumberCellState {
     required String content,
   }) = _NumberCellState;
 
-  factory NumberCellState.initial(GridDefaultCellContext context) {
+  factory NumberCellState.initial(GridCellContext context) {
     final cell = context.getCellData();
     return NumberCellState(content: cell?.content ?? "");
   }

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

@@ -1,33 +1,10 @@
 import 'package:dartz/dartz.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/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 extends 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;
-        },
-      );
-    });
-  }
-}
+import 'cell_service/cell_service.dart';
 
 class SelectOptionService {
   final GridCell gridCell;

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

@@ -2,7 +2,7 @@ import 'dart:async';
 import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
-import 'package:app_flowy/workspace/application/grid/cell/cell_service.dart';
+import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.dart';
 
 part 'selection_cell_bloc.freezed.dart';
 

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

@@ -4,7 +4,7 @@ import 'package:flowy_sdk/log.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
-import 'package:app_flowy/workspace/application/grid/cell/cell_service.dart';
+import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.dart';
 import 'select_option_service.dart';
 
 part 'selection_editor_bloc.freezed.dart';

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

@@ -2,12 +2,12 @@ import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Cell
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'dart:async';
-import 'cell_service.dart';
+import 'cell_service/cell_service.dart';
 
 part 'text_cell_bloc.freezed.dart';
 
 class TextCellBloc extends Bloc<TextCellEvent, TextCellState> {
-  final GridDefaultCellContext cellContext;
+  final GridCellContext cellContext;
   void Function()? _onCellChangedFn;
   TextCellBloc({
     required this.cellContext,
@@ -70,7 +70,7 @@ class TextCellState with _$TextCellState {
     required String content,
   }) = _TextCellState;
 
-  factory TextCellState.initial(GridDefaultCellContext context) => TextCellState(
+  factory TextCellState.initial(GridCellContext context) => TextCellState(
         content: context.getCellData()?.content ?? "",
       );
 }

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

@@ -5,7 +5,7 @@ import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid-data-model/protobuf.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
-import 'cell/cell_service.dart';
+import 'cell/cell_service/cell_service.dart';
 import 'grid_service.dart';
 import 'row/row_service.dart';
 

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

@@ -8,7 +8,7 @@ import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
 import 'package:flutter/foundation.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 
-import 'cell/cell_service.dart';
+import 'cell/cell_service/cell_service.dart';
 import 'row/row_service.dart';
 
 class GridService {

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

@@ -21,7 +21,7 @@ export 'cell/number_cell_bloc.dart';
 export 'cell/selection_cell_bloc.dart';
 export 'cell/date_cell_bloc.dart';
 export 'cell/checkbox_cell_bloc.dart';
-export 'cell/cell_service.dart';
+export 'cell/cell_service/cell_service.dart';
 
 // Setting
 export 'setting/setting_bloc.dart';

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

@@ -1,5 +1,5 @@
 import 'dart:collection';
-import 'package:app_flowy/workspace/application/grid/cell/cell_service.dart';
+import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.dart';
 import 'package:equatable/equatable.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Field;
 import 'package:flutter_bloc/flutter_bloc.dart';

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

@@ -1,4 +1,4 @@
-import 'package:app_flowy/workspace/application/grid/cell/cell_service.dart';
+import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'dart:async';

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

@@ -1,6 +1,6 @@
 import 'dart:collection';
 
-import 'package:app_flowy/workspace/application/grid/cell/cell_service.dart';
+import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.dart';
 import 'package:dartz/dartz.dart';
 import 'package:flowy_sdk/dispatch/dispatch.dart';
 import 'package:flowy_sdk/log.dart';

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

@@ -1,4 +1,4 @@
-import 'package:app_flowy/workspace/application/grid/cell/cell_service.dart';
+import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.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:flutter/widgets.dart';

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

@@ -32,8 +32,8 @@ class CellCalendar with FlowyOverlayDelegate {
 
   Future<void> show(
     BuildContext context, {
-    required GridDefaultCellContext cellContext,
-    required void Function(DateTime) onSelected,
+    required GridDateCellContext cellContext,
+    required void Function(DateCellPersistenceData) onSelected,
   }) async {
     CellCalendar.remove(context);
 
@@ -88,10 +88,10 @@ class CellCalendar with FlowyOverlayDelegate {
 }
 
 class _CellCalendarWidget extends StatelessWidget {
-  final GridDefaultCellContext cellContext;
+  final GridDateCellContext cellContext;
   final DateTypeOption dateTypeOption;
   final DateTime? selectedDay;
-  final void Function(DateTime) onSelected;
+  final void Function(DateCellPersistenceData) onSelected;
 
   const _CellCalendarWidget({
     required this.onSelected,
@@ -113,7 +113,7 @@ class _CellCalendarWidget extends StatelessWidget {
       child: BlocConsumer<DateCalBloc, DateCalState>(
         listener: (context, state) {
           if (state.selectedDay != null) {
-            onSelected(state.selectedDay!);
+            onSelected(DateCellPersistenceData(date: state.selectedDay!));
           }
         },
         listenWhen: (p, c) => p.selectedDay != c.selectedDay,

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

@@ -1,5 +1,5 @@
 import 'dart:collection';
-import 'package:app_flowy/workspace/application/grid/cell/cell_service.dart';
+import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.dart';
 import 'package:app_flowy/workspace/application/grid/cell/selection_editor_bloc.dart';
 import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
 import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/type_option/edit_option_pannel.dart';

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

@@ -1,4 +1,4 @@
-import 'package:app_flowy/workspace/application/grid/cell/cell_service.dart';
+import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.dart';
 import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
 import 'package:app_flowy/workspace/application/grid/row/row_detail_bloc.dart';
 import 'package:app_flowy/workspace/application/grid/row/row_service.dart';

+ 1 - 0
frontend/app_flowy/packages/flowy_sdk/lib/dispatch/dispatch.dart

@@ -5,6 +5,7 @@ import 'package:flowy_sdk/log.dart';
 import 'package:flowy_sdk/protobuf/dart-ffi/ffi_response.pb.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/date_type_option.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/row_entities.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';

+ 43 - 7
frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs

@@ -79,14 +79,25 @@ impl CellDataOperation for DateTypeOption {
     fn apply_changeset<T: Into<CellContentChangeset>>(
         &self,
         changeset: T,
-        _cell_meta: Option<CellMeta>,
+        cell_meta: Option<CellMeta>,
     ) -> Result<String, FlowyError> {
-        let changeset = changeset.into();
-        if changeset.parse::<f64>().is_err() || changeset.parse::<i64>().is_err() {
-            return Err(FlowyError::internal().context(format!("Parse {} failed", changeset)));
-        };
-
-        Ok(TypeOptionCellData::new(changeset, self.field_type()).json())
+        let content_changeset: DateCellContentChangeset = serde_json::from_str(&changeset.into())?;
+        match cell_meta {
+            None => Ok(TypeOptionCellData::new("", self.field_type()).json()),
+            Some(cell_meta) => {
+                let s = match content_changeset.timestamp() {
+                    None => get_cell_data(&cell_meta),
+                    Some(timestamp) => timestamp.to_string(),
+                };
+
+                Ok(TypeOptionCellData::new(s, self.field_type()).json())
+
+                // let changeset = changeset.into();
+                // if changeset.parse::<f64>().is_err() || changeset.parse::<i64>().is_err() {
+                //     return Err(FlowyError::internal().context(format!("Parse {} failed", changeset)));
+                // };
+            }
+        }
     }
 }
 
@@ -235,6 +246,31 @@ pub struct DateCellContentChangeset {
     pub time: Option<String>,
 }
 
+impl DateCellContentChangeset {
+    pub fn timestamp(self) -> Option<i64> {
+        let mut timestamp = 0;
+        if let Some(date) = self.date {
+            match date.parse::<i64>() {
+                Ok(date_timestamp) => {
+                    timestamp += date_timestamp;
+                }
+                Err(_) => {}
+            }
+        } else {
+            return None;
+        }
+
+        if let Some(time) = self.time {
+            match time.parse::<i64>() {
+                Ok(time_timestamp) => timestamp += time_timestamp,
+                Err(_) => {}
+            }
+        }
+
+        return Some(timestamp);
+    }
+}
+
 impl std::convert::From<DateChangesetParams> for CellChangeset {
     fn from(params: DateChangesetParams) -> Self {
         let changeset = DateCellContentChangeset {