Sfoglia il codice sorgente

Merge pull request #584 from AppFlowy-IO/refactor/grid_block_cache

refactor: grid block cache
Nathan.fooo 2 anni fa
parent
commit
6fc7f63a8b
17 ha cambiato i file con 364 aggiunte e 361 eliminazioni
  1. 2 39
      frontend/app_flowy/lib/workspace/application/grid/block/block_listener.dart
  2. 55 0
      frontend/app_flowy/lib/workspace/application/grid/block/block_service.dart
  3. 9 11
      frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cache.dart
  4. 1 1
      frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_service.dart
  5. 3 3
      frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/context_builder.dart
  6. 54 46
      frontend/app_flowy/lib/workspace/application/grid/grid_bloc.dart
  7. 1 1
      frontend/app_flowy/lib/workspace/application/grid/grid_header_bloc.dart
  8. 58 48
      frontend/app_flowy/lib/workspace/application/grid/grid_service.dart
  9. 4 4
      frontend/app_flowy/lib/workspace/application/grid/row/row_bloc.dart
  10. 4 4
      frontend/app_flowy/lib/workspace/application/grid/row/row_detail_bloc.dart
  11. 141 171
      frontend/app_flowy/lib/workspace/application/grid/row/row_service.dart
  12. 7 5
      frontend/app_flowy/lib/workspace/application/grid/setting/property_bloc.dart
  13. 15 13
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/grid_page.dart
  14. 1 1
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart
  15. 3 6
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/grid_row.dart
  16. 4 6
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/row_detail.dart
  17. 2 2
      frontend/rust-lib/flowy-grid/src/entities/row_entities.rs

+ 2 - 39
frontend/app_flowy/lib/workspace/application/grid/block/block_listener.dart

@@ -1,58 +1,21 @@
 import 'dart:async';
-import 'dart:collection';
 import 'dart:typed_data';
 
 import 'package:app_flowy/core/notification_helper.dart';
 import 'package:dartz/dartz.dart';
 import 'package:flowy_infra/notifier.dart';
-import 'package:flowy_sdk/log.dart';
 import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/block_entities.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/dart_notification.pb.dart';
 
-class GridBlockCache {
-  final String gridId;
-  void Function(GridBlockUpdateNotifierValue)? _onBlockChanged;
-  final LinkedHashMap<String, _GridBlockListener> _listeners = LinkedHashMap();
-  GridBlockCache({required this.gridId});
-
-  void start(void Function(GridBlockUpdateNotifierValue) onBlockChanged) {
-    _onBlockChanged = onBlockChanged;
-    for (final listener in _listeners.values) {
-      listener.start(onBlockChanged);
-    }
-  }
-
-  Future<void> dispose() async {
-    for (final listener in _listeners.values) {
-      await listener.stop();
-    }
-  }
-
-  void addBlockListener(String blockId) {
-    if (_onBlockChanged == null) {
-      Log.error("Should call start() first");
-      return;
-    }
-    if (_listeners.containsKey(blockId)) {
-      Log.error("Duplicate block listener");
-      return;
-    }
-
-    final listener = _GridBlockListener(blockId: blockId);
-    listener.start(_onBlockChanged!);
-    _listeners[blockId] = listener;
-  }
-}
-
 typedef GridBlockUpdateNotifierValue = Either<List<GridRowsChangeset>, FlowyError>;
 
-class _GridBlockListener {
+class GridBlockListener {
   final String blockId;
   PublishNotifier<GridBlockUpdateNotifierValue>? _rowsUpdateNotifier = PublishNotifier();
   GridNotificationListener? _listener;
 
-  _GridBlockListener({required this.blockId});
+  GridBlockListener({required this.blockId});
 
   void start(void Function(GridBlockUpdateNotifierValue) onBlockChanged) {
     if (_listener != null) {

+ 55 - 0
frontend/app_flowy/lib/workspace/application/grid/block/block_service.dart

@@ -0,0 +1,55 @@
+import 'dart:async';
+import 'package:app_flowy/workspace/application/grid/grid_service.dart';
+import 'package:app_flowy/workspace/application/grid/row/row_service.dart';
+import 'package:flowy_sdk/log.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/block_entities.pb.dart';
+
+import 'block_listener.dart';
+
+class GridBlockCacheService {
+  final String gridId;
+  final GridBlock block;
+  late GridRowCacheService _rowCache;
+  late GridBlockListener _listener;
+
+  List<GridRow> get rows => _rowCache.rows;
+  GridRowCacheService get rowCache => _rowCache;
+
+  GridBlockCacheService({
+    required this.gridId,
+    required this.block,
+    required GridFieldCache fieldCache,
+  }) {
+    _rowCache = GridRowCacheService(
+      gridId: gridId,
+      block: block,
+      delegate: GridRowCacheDelegateImpl(fieldCache),
+    );
+
+    _listener = GridBlockListener(blockId: block.id);
+    _listener.start((result) {
+      result.fold(
+        (changesets) => _rowCache.applyChangesets(changesets),
+        (err) => Log.error(err),
+      );
+    });
+  }
+
+  Future<void> dispose() async {
+    await _listener.stop();
+    await _rowCache.dispose();
+  }
+
+  void addListener({
+    required void Function(GridRowChangeReason) onChangeReason,
+    bool Function()? listenWhen,
+  }) {
+    _rowCache.onRowsChanged((reason) {
+      if (listenWhen != null && listenWhen() == false) {
+        return;
+      }
+
+      onChangeReason(reason);
+    });
+  }
+}

+ 9 - 11
frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_data_cache.dart → frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cache.dart

@@ -20,27 +20,26 @@ class _GridCellCacheKey {
   });
 }
 
-abstract class GridCellFieldDelegate {
-  void onFieldChanged(void Function(String) callback);
-  void dispose();
+abstract class GridCellCacheDelegate {
+  void onFieldUpdated(void Function(Field) callback);
 }
 
-class GridCellCache {
+class GridCellCacheService {
   final String gridId;
-  final GridCellFieldDelegate fieldDelegate;
+  final GridCellCacheDelegate delegate;
 
   /// fieldId: {objectId: callback}
   final Map<String, Map<String, List<VoidCallback>>> _fieldListenerByFieldId = {};
 
   /// fieldId: {cacheKey: cacheData}
   final Map<String, Map<String, dynamic>> _cellDataByFieldId = {};
-  GridCellCache({
+  GridCellCacheService({
     required this.gridId,
-    required this.fieldDelegate,
+    required this.delegate,
   }) {
-    fieldDelegate.onFieldChanged((fieldId) {
-      _cellDataByFieldId.remove(fieldId);
-      final map = _fieldListenerByFieldId[fieldId];
+    delegate.onFieldUpdated((field) {
+      _cellDataByFieldId.remove(field.id);
+      final map = _fieldListenerByFieldId[field.id];
       if (map != null) {
         for (final callbacks in map.values) {
           for (final callback in callbacks) {
@@ -106,6 +105,5 @@ class GridCellCache {
   Future<void> dispose() async {
     _fieldListenerByFieldId.clear();
     _cellDataByFieldId.clear();
-    fieldDelegate.dispose();
   }
 }

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

@@ -20,7 +20,7 @@ import 'dart:convert' show utf8;
 part 'cell_service.freezed.dart';
 part 'cell_data_loader.dart';
 part 'context_builder.dart';
-part 'cell_data_cache.dart';
+part 'cache.dart';
 part 'cell_data_persistence.dart';
 
 // key: rowId

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

@@ -6,10 +6,10 @@ typedef GridDateCellContext = _GridCellContext<DateCellData, CalendarData>;
 typedef GridURLCellContext = _GridCellContext<URLCellData, String>;
 
 class GridCellContextBuilder {
-  final GridCellCache _cellCache;
+  final GridCellCacheService _cellCache;
   final GridCell _gridCell;
   GridCellContextBuilder({
-    required GridCellCache cellCache,
+    required GridCellCacheService cellCache,
     required GridCell gridCell,
   })  : _cellCache = cellCache,
         _gridCell = gridCell;
@@ -99,7 +99,7 @@ class GridCellContextBuilder {
 // ignore: must_be_immutable
 class _GridCellContext<T, D> extends Equatable {
   final GridCell gridCell;
-  final GridCellCache cellCache;
+  final GridCellCacheService cellCache;
   final _GridCellCacheKey _cacheKey;
   final IGridCellDataLoader<T> cellDataLoader;
   final _GridCellDataPersistence<D> cellDataPersistence;

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

@@ -7,8 +7,7 @@ import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/protobuf.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
-import 'block/block_listener.dart';
-import 'cell/cell_service/cell_service.dart';
+import 'block/block_service.dart';
 import 'grid_service.dart';
 import 'row/row_service.dart';
 import 'dart:collection';
@@ -16,36 +15,27 @@ import 'dart:collection';
 part 'grid_bloc.freezed.dart';
 
 class GridBloc extends Bloc<GridEvent, GridState> {
+  final String gridId;
   final GridService _gridService;
   final GridFieldCache fieldCache;
-  late final GridRowCache rowCache;
-  late final GridCellCache cellCache;
 
-  final GridBlockCache blockCache;
+  // key: the block id
+  final LinkedHashMap<String, GridBlockCacheService> _blocks;
+
+  List<GridRow> get rows {
+    final List<GridRow> rows = [];
+    for (var block in _blocks.values) {
+      rows.addAll(block.rows);
+    }
+    return rows;
+  }
 
   GridBloc({required View view})
-      : _gridService = GridService(gridId: view.id),
+      : gridId = view.id,
+        _blocks = LinkedHashMap.identity(),
+        _gridService = GridService(gridId: view.id),
         fieldCache = GridFieldCache(gridId: view.id),
-        blockCache = GridBlockCache(gridId: view.id),
         super(GridState.initial(view.id)) {
-    rowCache = GridRowCache(
-      gridId: view.id,
-      blockId: "",
-      fieldDelegate: GridRowCacheDelegateImpl(fieldCache),
-    );
-
-    cellCache = GridCellCache(
-      gridId: view.id,
-      fieldDelegate: GridCellCacheDelegateImpl(fieldCache),
-    );
-
-    blockCache.start((result) {
-      result.fold(
-        (changesets) => rowCache.applyChangesets(changesets),
-        (err) => Log.error(err),
-      );
-    });
-
     on<GridEvent>(
       (event, emit) async {
         await event.when(
@@ -56,11 +46,11 @@ class GridBloc extends Bloc<GridEvent, GridState> {
           createRow: () {
             _gridService.createRow();
           },
-          didReceiveRowUpdate: (rows, listState) {
-            emit(state.copyWith(rows: rows, listState: listState));
+          didReceiveRowUpdate: (rows, reason) {
+            emit(state.copyWith(rows: rows, reason: reason));
           },
           didReceiveFieldUpdate: (fields) {
-            emit(state.copyWith(rows: rowCache.clonedRows, fields: GridFieldEquatable(fields)));
+            emit(state.copyWith(rows: rows, fields: GridFieldEquatable(fields)));
           },
         );
       },
@@ -70,22 +60,23 @@ class GridBloc extends Bloc<GridEvent, GridState> {
   @override
   Future<void> close() async {
     await _gridService.closeGrid();
-    await cellCache.dispose();
-    await rowCache.dispose();
     await fieldCache.dispose();
-    await blockCache.dispose();
+
+    for (final blockCache in _blocks.values) {
+      blockCache.dispose();
+    }
     return super.close();
   }
 
+  GridRowCacheService? getRowCache(String blockId, String rowId) {
+    final GridBlockCacheService? blockCache = _blocks[blockId];
+    return blockCache?.rowCache;
+  }
+
   void _startListening() {
     fieldCache.addListener(
       listenWhen: () => !isClosed,
-      onChanged: (fields) => add(GridEvent.didReceiveFieldUpdate(fields)),
-    );
-
-    rowCache.addListener(
-      listenWhen: () => !isClosed,
-      onChanged: (rows, listState) => add(GridEvent.didReceiveRowUpdate(rowCache.clonedRows, listState)),
+      onFields: (fields) => add(GridEvent.didReceiveFieldUpdate(fields)),
     );
   }
 
@@ -94,12 +85,7 @@ class GridBloc extends Bloc<GridEvent, GridState> {
     return Future(
       () => result.fold(
         (grid) async {
-          for (final block in grid.blocks) {
-            blockCache.addBlockListener(block.id);
-          }
-          final rowInfos = grid.blocks.expand((block) => block.rowInfos).toList();
-          rowCache.initialRows(rowInfos);
-
+          _initialBlocks(grid.blocks);
           await _loadFields(grid, emit);
         },
         (err) => emit(state.copyWith(loadingState: GridLoadingState.finish(right(err)))),
@@ -117,7 +103,7 @@ class GridBloc extends Bloc<GridEvent, GridState> {
           emit(state.copyWith(
             grid: Some(grid),
             fields: GridFieldEquatable(fieldCache.fields),
-            rows: rowCache.clonedRows,
+            rows: rows,
             loadingState: GridLoadingState.finish(left(unit)),
           ));
         },
@@ -125,6 +111,28 @@ class GridBloc extends Bloc<GridEvent, GridState> {
       ),
     );
   }
+
+  void _initialBlocks(List<GridBlock> blocks) {
+    for (final block in blocks) {
+      if (_blocks[block.id] != null) {
+        Log.warn("Intial duplicate block's cache: ${block.id}");
+        return;
+      }
+
+      final cache = GridBlockCacheService(
+        gridId: gridId,
+        block: block,
+        fieldCache: fieldCache,
+      );
+
+      cache.addListener(
+        listenWhen: () => !isClosed,
+        onChangeReason: (reason) => add(GridEvent.didReceiveRowUpdate(rows, reason)),
+      );
+
+      _blocks[block.id] = cache;
+    }
+  }
 }
 
 @freezed
@@ -143,7 +151,7 @@ class GridState with _$GridState {
     required GridFieldEquatable fields,
     required List<GridRow> rows,
     required GridLoadingState loadingState,
-    required GridRowChangeReason listState,
+    required GridRowChangeReason reason,
   }) = _GridState;
 
   factory GridState.initial(String gridId) => GridState(
@@ -152,7 +160,7 @@ class GridState with _$GridState {
         grid: none(),
         gridId: gridId,
         loadingState: const _Loading(),
-        listState: const InitialListState(),
+        reason: const InitialListState(),
       );
 }
 

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

@@ -48,7 +48,7 @@ class GridHeaderBloc extends Bloc<GridHeaderEvent, GridHeaderState> {
 
   Future<void> _startListening() async {
     fieldCache.addListener(
-      onChanged: (fields) => add(GridHeaderEvent.didReceiveFieldUpdate(fields)),
+      onFields: (fields) => add(GridHeaderEvent.didReceiveFieldUpdate(fields)),
       listenWhen: () => !isClosed,
     );
   }

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

@@ -11,7 +11,6 @@ import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/grid_entities.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/row_entities.pb.dart';
 import 'package:flutter/foundation.dart';
-import 'cell/cell_service/cell_service.dart';
 import 'row/row_service.dart';
 
 class GridService {
@@ -57,13 +56,15 @@ class FieldsNotifier extends ChangeNotifier {
   List<Field> get fields => _fields;
 }
 
-typedef ChangesetListener = void Function(GridFieldChangeset);
+typedef FieldChangesetCallback = void Function(GridFieldChangeset);
+typedef FieldsCallback = void Function(List<Field>);
 
 class GridFieldCache {
   final String gridId;
   late final GridFieldsListener _fieldListener;
   FieldsNotifier? _fieldNotifier = FieldsNotifier();
-  final List<ChangesetListener> _changesetListener = [];
+  final Map<FieldsCallback, VoidCallback> _fieldsCallbackMap = {};
+  final Map<FieldChangesetCallback, FieldChangesetCallback> _changesetCallbackMap = {};
 
   GridFieldCache({required this.gridId}) {
     _fieldListener = GridFieldsListener(gridId: gridId);
@@ -73,7 +74,7 @@ class GridFieldCache {
           _deleteFields(changeset.deletedFields);
           _insertFields(changeset.insertedFields);
           _updateFields(changeset.updatedFields);
-          for (final listener in _changesetListener) {
+          for (final listener in _changesetCallbackMap.values) {
             listener(changeset);
           }
         },
@@ -96,38 +97,48 @@ class GridFieldCache {
     _fieldNotifier?.fields = [...fields];
   }
 
-  VoidCallback addListener(
-      {VoidCallback? listener, void Function(List<Field>)? onChanged, bool Function()? listenWhen}) {
-    f() {
-      if (listenWhen != null && listenWhen() == false) {
-        return;
+  void addListener({
+    FieldsCallback? onFields,
+    FieldChangesetCallback? onChangeset,
+    bool Function()? listenWhen,
+  }) {
+    if (onChangeset != null) {
+      fn(c) {
+        if (listenWhen != null && listenWhen() == false) {
+          return;
+        }
+        onChangeset(c);
       }
 
-      if (onChanged != null) {
-        onChanged(fields);
-      }
-
-      if (listener != null) {
-        listener();
-      }
+      _changesetCallbackMap[onChangeset] = fn;
     }
 
-    _fieldNotifier?.addListener(f);
-    return f;
-  }
+    if (onFields != null) {
+      fn() {
+        if (listenWhen != null && listenWhen() == false) {
+          return;
+        }
+        onFields(fields);
+      }
 
-  void removeListener(VoidCallback f) {
-    _fieldNotifier?.removeListener(f);
+      _fieldsCallbackMap[onFields] = fn;
+      _fieldNotifier?.addListener(fn);
+    }
   }
 
-  void addChangesetListener(ChangesetListener listener) {
-    _changesetListener.add(listener);
-  }
+  void removeListener({
+    FieldsCallback? onFieldsListener,
+    FieldChangesetCallback? onChangsetListener,
+  }) {
+    if (onFieldsListener != null) {
+      final fn = _fieldsCallbackMap.remove(onFieldsListener);
+      if (fn != null) {
+        _fieldNotifier?.removeListener(fn);
+      }
+    }
 
-  void removeChangesetListener(ChangesetListener listener) {
-    final index = _changesetListener.indexWhere((element) => element == listener);
-    if (index != -1) {
-      _changesetListener.removeAt(index);
+    if (onChangsetListener != null) {
+      _changesetCallbackMap.remove(onChangsetListener);
     }
   }
 
@@ -175,43 +186,42 @@ class GridFieldCache {
   }
 }
 
-class GridRowCacheDelegateImpl extends GridRowFieldDelegate {
+class GridRowCacheDelegateImpl extends GridRowCacheDelegate {
   final GridFieldCache _cache;
+  FieldChangesetCallback? _onChangesetFn;
+  FieldsCallback? _onFieldFn;
   GridRowCacheDelegateImpl(GridFieldCache cache) : _cache = cache;
 
   @override
   UnmodifiableListView<Field> get fields => _cache.unmodifiableFields;
 
   @override
-  void onFieldChanged(FieldDidUpdateCallback callback) {
-    _cache.addListener(listener: () {
-      callback();
-    });
+  void onFieldsChanged(VoidCallback callback) {
+    _onFieldFn = (_) => callback();
+    _cache.addListener(onFields: _onFieldFn);
   }
-}
-
-class GridCellCacheDelegateImpl extends GridCellFieldDelegate {
-  final GridFieldCache _cache;
-  ChangesetListener? _changesetFn;
-  GridCellCacheDelegateImpl(GridFieldCache cache) : _cache = cache;
 
   @override
-  void onFieldChanged(void Function(String) callback) {
-    changesetFn(GridFieldChangeset changeset) {
+  void onFieldUpdated(void Function(Field) callback) {
+    _onChangesetFn = (GridFieldChangeset changeset) {
       for (final updatedField in changeset.updatedFields) {
-        callback(updatedField.id);
+        callback(updatedField);
       }
-    }
+    };
 
-    _cache.addChangesetListener(changesetFn);
-    _changesetFn = changesetFn;
+    _cache.addListener(onChangeset: _onChangesetFn);
   }
 
   @override
   void dispose() {
-    if (_changesetFn != null) {
-      _cache.removeChangesetListener(_changesetFn!);
-      _changesetFn = null;
+    if (_onFieldFn != null) {
+      _cache.removeListener(onFieldsListener: _onFieldFn!);
+      _onFieldFn = null;
+    }
+
+    if (_onChangesetFn != null) {
+      _cache.removeListener(onChangsetListener: _onChangesetFn!);
+      _onChangesetFn = null;
     }
   }
 }

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

@@ -11,12 +11,12 @@ part 'row_bloc.freezed.dart';
 
 class RowBloc extends Bloc<RowEvent, RowState> {
   final RowService _rowService;
-  final GridRowCache _rowCache;
+  final GridRowCacheService _rowCache;
   void Function()? _rowListenFn;
 
   RowBloc({
     required GridRow rowData,
-    required GridRowCache rowCache,
+    required GridRowCacheService rowCache,
   })  : _rowService = RowService(
           gridId: rowData.gridId,
           blockId: rowData.blockId,
@@ -57,9 +57,9 @@ class RowBloc extends Bloc<RowEvent, RowState> {
   }
 
   Future<void> _startListening() async {
-    _rowListenFn = _rowCache.addRowListener(
+    _rowListenFn = _rowCache.addListener(
       rowId: state.rowData.rowId,
-      onUpdated: (cellDatas, reason) => add(RowEvent.didReceiveCellDatas(cellDatas, reason)),
+      onCellUpdated: (cellDatas, reason) => add(RowEvent.didReceiveCellDatas(cellDatas, reason)),
       listenWhen: () => !isClosed,
     );
   }

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

@@ -8,12 +8,12 @@ part 'row_detail_bloc.freezed.dart';
 
 class RowDetailBloc extends Bloc<RowDetailEvent, RowDetailState> {
   final GridRow rowData;
-  final GridRowCache _rowCache;
+  final GridRowCacheService _rowCache;
   void Function()? _rowListenFn;
 
   RowDetailBloc({
     required this.rowData,
-    required GridRowCache rowCache,
+    required GridRowCacheService rowCache,
   })  : _rowCache = rowCache,
         super(RowDetailState.initial()) {
     on<RowDetailEvent>(
@@ -40,9 +40,9 @@ class RowDetailBloc extends Bloc<RowDetailEvent, RowDetailState> {
   }
 
   Future<void> _startListening() async {
-    _rowListenFn = _rowCache.addRowListener(
+    _rowListenFn = _rowCache.addListener(
       rowId: rowData.rowId,
-      onUpdated: (cellDatas, reason) => add(RowDetailEvent.didReceiveCellDatas(cellDatas.values.toList())),
+      onCellUpdated: (cellDatas, reason) => add(RowDetailEvent.didReceiveCellDatas(cellDatas.values.toList())),
       listenWhen: () => !isClosed,
     );
   }

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

@@ -14,141 +14,182 @@ import 'package:freezed_annotation/freezed_annotation.dart';
 part 'row_service.freezed.dart';
 
 typedef RowUpdateCallback = void Function();
-typedef FieldDidUpdateCallback = void Function();
 
-abstract class GridRowFieldDelegate {
+abstract class GridRowCacheDelegate with GridCellCacheDelegate {
   UnmodifiableListView<Field> get fields;
-  void onFieldChanged(FieldDidUpdateCallback callback);
+  void onFieldsChanged(void Function() callback);
+  void dispose();
 }
 
-class GridRowCache {
+class GridRowCacheService {
   final String gridId;
-  final String blockId;
-  final RowsNotifier _rowsNotifier;
-  final GridRowFieldDelegate _fieldDelegate;
-  List<GridRow> get clonedRows => _rowsNotifier.clonedRows;
+  final GridBlock block;
+  final _Notifier _notifier;
+  List<GridRow> _rows = [];
+  final HashMap<String, Row> _rowByRowId;
+  final GridRowCacheDelegate _delegate;
+  final GridCellCacheService _cellCache;
+
+  List<GridRow> get rows => _rows;
+  GridCellCacheService get cellCache => _cellCache;
 
-  GridRowCache({
+  GridRowCacheService({
     required this.gridId,
-    required this.blockId,
-    required GridRowFieldDelegate fieldDelegate,
-  })  : _rowsNotifier = RowsNotifier(
-          rowBuilder: (rowInfo) {
-            return GridRow(
-              gridId: gridId,
-              blockId: "test",
-              fields: fieldDelegate.fields,
-              rowId: rowInfo.rowId,
-              height: rowInfo.height.toDouble(),
-            );
-          },
-        ),
-        _fieldDelegate = fieldDelegate {
+    required this.block,
+    required GridRowCacheDelegate delegate,
+  })  : _cellCache = GridCellCacheService(gridId: gridId, delegate: delegate),
+        _rowByRowId = HashMap(),
+        _notifier = _Notifier(),
+        _delegate = delegate {
     //
-    fieldDelegate.onFieldChanged(() => _rowsNotifier.fieldDidChange());
+    delegate.onFieldsChanged(() => _notifier.receive(const GridRowChangeReason.fieldDidChange()));
+    _rows = block.rowInfos.map((rowInfo) => buildGridRow(rowInfo)).toList();
   }
 
   Future<void> dispose() async {
-    _rowsNotifier.dispose();
+    _delegate.dispose();
+    _notifier.dispose();
+    await _cellCache.dispose();
   }
 
   void applyChangesets(List<GridRowsChangeset> changesets) {
     for (final changeset in changesets) {
-      _rowsNotifier.deleteRows(changeset.deletedRows);
-      _rowsNotifier.insertRows(changeset.insertedRows);
-      _rowsNotifier.updateRows(changeset.updatedRows);
+      _deleteRows(changeset.deletedRows);
+      _insertRows(changeset.insertedRows);
+      _updateRows(changeset.updatedRows);
     }
   }
 
-  void addListener({
-    void Function(List<GridRow>, GridRowChangeReason)? onChanged,
-    bool Function()? listenWhen,
-  }) {
-    _rowsNotifier.addListener(() {
-      if (onChanged == null) {
-        return;
+  void _deleteRows(List<GridRowId> deletedRows) {
+    if (deletedRows.isEmpty) {
+      return;
+    }
+
+    final List<GridRow> newRows = [];
+    final DeletedIndexs deletedIndex = [];
+    final Map<String, GridRowId> deletedRowByRowId = {for (var e in deletedRows) e.rowId: e};
+
+    _rows.asMap().forEach((index, row) {
+      if (deletedRowByRowId[row.rowId] == null) {
+        newRows.add(row);
+      } else {
+        deletedIndex.add(DeletedIndex(index: index, row: row));
       }
+    });
+    _rows = newRows;
+    _notifier.receive(GridRowChangeReason.delete(deletedIndex));
+  }
 
-      if (listenWhen != null && listenWhen() == false) {
-        return;
+  void _insertRows(List<IndexRowOrder> insertRows) {
+    if (insertRows.isEmpty) {
+      return;
+    }
+
+    InsertedIndexs insertIndexs = [];
+    final List<GridRow> newRows = _rows;
+    for (final insertRow in insertRows) {
+      final insertIndex = InsertedIndex(
+        index: insertRow.index,
+        rowId: insertRow.rowInfo.rowId,
+      );
+      insertIndexs.add(insertIndex);
+      newRows.insert(insertRow.index, (buildGridRow(insertRow.rowInfo)));
+    }
+
+    _notifier.receive(GridRowChangeReason.insert(insertIndexs));
+  }
+
+  void _updateRows(List<UpdatedRowOrder> updatedRows) {
+    if (updatedRows.isEmpty) {
+      return;
+    }
+
+    final UpdatedIndexs updatedIndexs = UpdatedIndexs();
+    final List<GridRow> newRows = _rows;
+    for (final updatedRow in updatedRows) {
+      final rowOrder = updatedRow.rowInfo;
+      final rowId = updatedRow.rowInfo.rowId;
+      final index = newRows.indexWhere((row) => row.rowId == rowId);
+      if (index != -1) {
+        _rowByRowId[rowId] = updatedRow.row;
+
+        newRows.removeAt(index);
+        newRows.insert(index, buildGridRow(rowOrder));
+        updatedIndexs[rowId] = UpdatedIndex(index: index, rowId: rowId);
       }
+    }
 
-      onChanged(clonedRows, _rowsNotifier._changeReason);
+    _notifier.receive(GridRowChangeReason.update(updatedIndexs));
+  }
+
+  void onRowsChanged(
+    void Function(GridRowChangeReason) onRowChanged,
+  ) {
+    _notifier.addListener(() {
+      onRowChanged(_notifier._reason);
     });
   }
 
-  RowUpdateCallback addRowListener({
+  RowUpdateCallback addListener({
     required String rowId,
-    void Function(GridCellMap, GridRowChangeReason)? onUpdated,
+    void Function(GridCellMap, GridRowChangeReason)? onCellUpdated,
     bool Function()? listenWhen,
   }) {
     listenrHandler() async {
-      if (onUpdated == null) {
-        return;
-      }
-
       if (listenWhen != null && listenWhen() == false) {
         return;
       }
 
-      notify() {
-        final row = _rowsNotifier.rowDataWithId(rowId);
-        if (row != null) {
-          final GridCellMap cellDataMap = _makeGridCells(rowId, row);
-          onUpdated(cellDataMap, _rowsNotifier._changeReason);
+      notifyUpdate() {
+        if (onCellUpdated != null) {
+          final row = _rowByRowId[rowId];
+          if (row != null) {
+            final GridCellMap cellDataMap = _makeGridCells(rowId, row);
+            onCellUpdated(cellDataMap, _notifier._reason);
+          }
         }
       }
 
-      _rowsNotifier._changeReason.whenOrNull(
+      _notifier._reason.whenOrNull(
         update: (indexs) {
-          if (indexs[rowId] != null) {
-            notify();
-          }
+          if (indexs[rowId] != null) notifyUpdate();
         },
-        fieldDidChange: () => notify(),
+        fieldDidChange: () => notifyUpdate(),
       );
     }
 
-    _rowsNotifier.addListener(listenrHandler);
+    _notifier.addListener(listenrHandler);
     return listenrHandler;
   }
 
   void removeRowListener(VoidCallback callback) {
-    _rowsNotifier.removeListener(callback);
+    _notifier.removeListener(callback);
   }
 
   GridCellMap loadGridCells(String rowId) {
-    final Row? data = _rowsNotifier.rowDataWithId(rowId);
+    final Row? data = _rowByRowId[rowId];
     if (data == null) {
       _loadRow(rowId);
     }
     return _makeGridCells(rowId, data);
   }
 
-  void initialRows(List<BlockRowInfo> rowInfos) {
-    _rowsNotifier.initialRows(rowInfos);
-  }
-
   Future<void> _loadRow(String rowId) async {
     final payload = GridRowIdPayload.create()
       ..gridId = gridId
-      ..blockId = blockId
+      ..blockId = block.id
       ..rowId = rowId;
 
     final result = await GridEventGetRow(payload).send();
     result.fold(
-      (rowData) {
-        if (rowData.hasRow()) {
-          _rowsNotifier.rowData = rowData.row;
-        }
-      },
+      (optionRow) => _refreshRow(optionRow),
       (err) => Log.error(err),
     );
   }
 
   GridCellMap _makeGridCells(String rowId, Row? row) {
     var cellDataMap = GridCellMap.new();
-    for (final field in _fieldDelegate.fields) {
+    for (final field in _delegate.fields) {
       if (field.visibility) {
         cellDataMap[field.id] = GridCell(
           rowId: rowId,
@@ -159,96 +200,51 @@ class GridRowCache {
     }
     return cellDataMap;
   }
-}
-
-class RowsNotifier extends ChangeNotifier {
-  List<GridRow> _allRows = [];
-  HashMap<String, Row> _rowByRowId = HashMap();
-  GridRowChangeReason _changeReason = const InitialListState();
-  final GridRow Function(BlockRowInfo) rowBuilder;
-
-  RowsNotifier({
-    required this.rowBuilder,
-  });
-
-  List<GridRow> get clonedRows => [..._allRows];
-
-  void initialRows(List<BlockRowInfo> rowInfos) {
-    _rowByRowId = HashMap();
-    final rows = rowInfos.map((rowOrder) => rowBuilder(rowOrder)).toList();
-    _update(rows, const GridRowChangeReason.initial());
-  }
-
-  void deleteRows(List<GridRowId> deletedRows) {
-    if (deletedRows.isEmpty) {
-      return;
-    }
-
-    final List<GridRow> newRows = [];
-    final DeletedIndexs deletedIndex = [];
-    final Map<String, GridRowId> deletedRowByRowId = {for (var e in deletedRows) e.rowId: e};
-
-    _allRows.asMap().forEach((index, row) {
-      if (deletedRowByRowId[row.rowId] == null) {
-        newRows.add(row);
-      } else {
-        deletedIndex.add(DeletedIndex(index: index, row: row));
-      }
-    });
-
-    _update(newRows, GridRowChangeReason.delete(deletedIndex));
-  }
 
-  void insertRows(List<IndexRowOrder> insertRows) {
-    if (insertRows.isEmpty) {
+  void _refreshRow(OptionalRow optionRow) {
+    if (!optionRow.hasRow()) {
       return;
     }
+    final updatedRow = optionRow.row;
+    updatedRow.freeze();
 
-    InsertedIndexs insertIndexs = [];
-    final List<GridRow> newRows = clonedRows;
-    for (final insertRow in insertRows) {
-      final insertIndex = InsertedIndex(
-        index: insertRow.index,
-        rowId: insertRow.rowInfo.rowId,
-      );
-      insertIndexs.add(insertIndex);
-      newRows.insert(insertRow.index, (rowBuilder(insertRow.rowInfo)));
-    }
-    _update(newRows, GridRowChangeReason.insert(insertIndexs));
-  }
-
-  void updateRows(List<UpdatedRowOrder> updatedRows) {
-    if (updatedRows.isEmpty) {
-      return;
-    }
+    _rowByRowId[updatedRow.id] = updatedRow;
+    final index = _rows.indexWhere((gridRow) => gridRow.rowId == updatedRow.id);
+    if (index != -1) {
+      // update the corresponding row in _rows if they are not the same
+      if (_rows[index].data != updatedRow) {
+        final row = _rows.removeAt(index).copyWith(data: updatedRow);
+        _rows.insert(index, row);
 
-    final UpdatedIndexs updatedIndexs = UpdatedIndexs();
-    final List<GridRow> newRows = clonedRows;
-    for (final updatedRow in updatedRows) {
-      final rowOrder = updatedRow.rowInfo;
-      final rowId = updatedRow.rowInfo.rowId;
-      final index = newRows.indexWhere((row) => row.rowId == rowId);
-      if (index != -1) {
-        _rowByRowId[rowId] = updatedRow.row;
+        // Calculate the update index
+        final UpdatedIndexs updatedIndexs = UpdatedIndexs();
+        updatedIndexs[row.rowId] = UpdatedIndex(index: index, rowId: row.rowId);
 
-        newRows.removeAt(index);
-        newRows.insert(index, rowBuilder(rowOrder));
-        updatedIndexs[rowId] = UpdatedIndex(index: index, rowId: rowId);
+        //
+        _notifier.receive(GridRowChangeReason.update(updatedIndexs));
       }
     }
-
-    _update(newRows, GridRowChangeReason.update(updatedIndexs));
   }
 
-  void fieldDidChange() {
-    _update(_allRows, const GridRowChangeReason.fieldDidChange());
+  GridRow buildGridRow(BlockRowInfo rowInfo) {
+    return GridRow(
+      gridId: gridId,
+      blockId: block.id,
+      fields: _delegate.fields,
+      rowId: rowInfo.rowId,
+      height: rowInfo.height.toDouble(),
+    );
   }
+}
 
-  void _update(List<GridRow> rows, GridRowChangeReason reason) {
-    _allRows = rows;
-    _changeReason = reason;
+class _Notifier extends ChangeNotifier {
+  GridRowChangeReason _reason = const InitialListState();
 
-    _changeReason.map(
+  _Notifier();
+
+  void receive(GridRowChangeReason reason) {
+    _reason = reason;
+    reason.map(
       insert: (_) => notifyListeners(),
       delete: (_) => notifyListeners(),
       update: (_) => notifyListeners(),
@@ -256,32 +252,6 @@ class RowsNotifier extends ChangeNotifier {
       initial: (_) {},
     );
   }
-
-  set rowData(Row rowData) {
-    rowData.freeze();
-
-    _rowByRowId[rowData.id] = rowData;
-    final index = _allRows.indexWhere((row) => row.rowId == rowData.id);
-    if (index != -1) {
-      // update the corresponding row in _rows if they are not the same
-      if (_allRows[index].data != rowData) {
-        final row = _allRows.removeAt(index).copyWith(data: rowData);
-        _allRows.insert(index, row);
-
-        // Calculate the update index
-        final UpdatedIndexs updatedIndexs = UpdatedIndexs();
-        updatedIndexs[row.rowId] = UpdatedIndex(index: index, rowId: row.rowId);
-        _changeReason = GridRowChangeReason.update(updatedIndexs);
-
-        //
-        notifyListeners();
-      }
-    }
-  }
-
-  Row? rowDataWithId(String rowId) {
-    return _rowByRowId[rowId];
-  }
 }
 
 class RowService {

+ 7 - 5
frontend/app_flowy/lib/workspace/application/grid/setting/property_bloc.dart

@@ -10,7 +10,7 @@ part 'property_bloc.freezed.dart';
 
 class GridPropertyBloc extends Bloc<GridPropertyEvent, GridPropertyState> {
   final GridFieldCache _fieldCache;
-  Function()? _listenFieldCallback;
+  Function(List<Field>)? _onFieldsFn;
 
   GridPropertyBloc({required String gridId, required GridFieldCache fieldCache})
       : _fieldCache = fieldCache,
@@ -42,15 +42,17 @@ class GridPropertyBloc extends Bloc<GridPropertyEvent, GridPropertyState> {
 
   @override
   Future<void> close() async {
-    if (_listenFieldCallback != null) {
-      _fieldCache.removeListener(_listenFieldCallback!);
+    if (_onFieldsFn != null) {
+      _fieldCache.removeListener(onFieldsListener: _onFieldsFn!);
+      _onFieldsFn = null;
     }
     return super.close();
   }
 
   void _startListening() {
-    _listenFieldCallback = _fieldCache.addListener(
-      onChanged: (fields) => add(GridPropertyEvent.didReceiveFieldUpdate(fields)),
+    _onFieldsFn = (fields) => add(GridPropertyEvent.didReceiveFieldUpdate(fields));
+    _fieldCache.addListener(
+      onFields: _onFieldsFn,
       listenWhen: () => !isClosed,
     );
   }

+ 15 - 13
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/grid_page.dart

@@ -190,9 +190,9 @@ class _GridRowsState extends State<_GridRows> {
   @override
   Widget build(BuildContext context) {
     return BlocConsumer<GridBloc, GridState>(
-      listenWhen: (previous, current) => previous.listState != current.listState,
+      listenWhen: (previous, current) => previous.reason != current.reason,
       listener: (context, state) {
-        state.listState.mapOrNull(
+        state.reason.mapOrNull(
           insert: (value) {
             for (final item in value.items) {
               _key.currentState?.insertItem(item.index);
@@ -227,17 +227,19 @@ class _GridRowsState extends State<_GridRows> {
     GridRow rowData,
     Animation<double> animation,
   ) {
-    final rowCache = context.read<GridBloc>().rowCache;
-    final cellCache = context.read<GridBloc>().cellCache;
-    return SizeTransition(
-      sizeFactor: animation,
-      child: GridRowWidget(
-        rowData: rowData,
-        rowCache: rowCache,
-        cellCache: cellCache,
-        key: ValueKey(rowData.rowId),
-      ),
-    );
+    final rowCache = context.read<GridBloc>().getRowCache(rowData.blockId, rowData.rowId);
+    if (rowCache != null) {
+      return SizeTransition(
+        sizeFactor: animation,
+        child: GridRowWidget(
+          rowData: rowData,
+          rowCache: rowCache,
+          key: ValueKey(rowData.rowId),
+        ),
+      );
+    } else {
+      return const SizedBox();
+    }
   }
 }
 

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

@@ -12,7 +12,7 @@ import 'select_option_cell/select_option_cell.dart';
 import 'text_cell.dart';
 import 'url_cell/url_cell.dart';
 
-GridCellWidget buildGridCellWidget(GridCell gridCell, GridCellCache cellCache, {GridCellStyle? style}) {
+GridCellWidget buildGridCellWidget(GridCell gridCell, GridCellCacheService cellCache, {GridCellStyle? style}) {
   final key = ValueKey(gridCell.cellId());
 
   final cellContextBuilder = GridCellContextBuilder(gridCell: gridCell, cellCache: cellCache);

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

@@ -16,13 +16,11 @@ import 'row_detail.dart';
 
 class GridRowWidget extends StatefulWidget {
   final GridRow rowData;
-  final GridRowCache rowCache;
-  final GridCellCache cellCache;
+  final GridRowCacheService rowCache;
 
   const GridRowWidget({
     required this.rowData,
     required this.rowCache,
-    required this.cellCache,
     Key? key,
   }) : super(key: key);
 
@@ -54,7 +52,7 @@ class _GridRowWidgetState extends State<GridRowWidget> {
             return Row(
               children: [
                 const _RowLeading(),
-                Expanded(child: _RowCells(cellCache: widget.cellCache, onExpand: () => _expandRow(context))),
+                Expanded(child: _RowCells(cellCache: widget.rowCache.cellCache, onExpand: () => _expandRow(context))),
                 const _RowTrailing(),
               ],
             );
@@ -74,7 +72,6 @@ class _GridRowWidgetState extends State<GridRowWidget> {
     final page = RowDetailPage(
       rowData: widget.rowData,
       rowCache: widget.rowCache,
-      cellCache: widget.cellCache,
     );
     page.show(context);
   }
@@ -149,7 +146,7 @@ class _DeleteRowButton extends StatelessWidget {
 }
 
 class _RowCells extends StatelessWidget {
-  final GridCellCache cellCache;
+  final GridCellCacheService cellCache;
   final VoidCallback onExpand;
   const _RowCells({required this.cellCache, required this.onExpand, Key? key}) : super(key: key);
 

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

@@ -23,13 +23,11 @@ import 'package:window_size/window_size.dart';
 
 class RowDetailPage extends StatefulWidget with FlowyOverlayDelegate {
   final GridRow rowData;
-  final GridRowCache rowCache;
-  final GridCellCache cellCache;
+  final GridRowCacheService rowCache;
 
   const RowDetailPage({
     required this.rowData,
     required this.rowCache,
-    required this.cellCache,
     Key? key,
   }) : super(key: key);
 
@@ -77,7 +75,7 @@ class _RowDetailPageState extends State<RowDetailPage> {
                 children: const [Spacer(), _CloseButton()],
               ),
             ),
-            Expanded(child: _PropertyList(cellCache: widget.cellCache)),
+            Expanded(child: _PropertyList(cellCache: widget.rowCache.cellCache)),
           ],
         ),
       ),
@@ -101,7 +99,7 @@ class _CloseButton extends StatelessWidget {
 }
 
 class _PropertyList extends StatelessWidget {
-  final GridCellCache cellCache;
+  final GridCellCacheService cellCache;
   final ScrollController _scrollController;
   _PropertyList({
     required this.cellCache,
@@ -139,7 +137,7 @@ class _PropertyList extends StatelessWidget {
 
 class _RowDetailCell extends StatelessWidget {
   final GridCell gridCell;
-  final GridCellCache cellCache;
+  final GridCellCacheService cellCache;
   const _RowDetailCell({
     required this.gridCell,
     required this.cellCache,

+ 2 - 2
frontend/rust-lib/flowy-grid/src/entities/row_entities.rs

@@ -31,12 +31,12 @@ impl TryInto<GridRowId> for GridRowIdPayload {
 
     fn try_into(self) -> Result<GridRowId, Self::Error> {
         let grid_id = NotEmptyStr::parse(self.grid_id).map_err(|_| ErrorCode::GridIdIsEmpty)?;
-        // let block_id = NotEmptyStr::parse(self.block_id).map_err(|_| ErrorCode::BlockIdIsEmpty)?;
+        let block_id = NotEmptyStr::parse(self.block_id).map_err(|_| ErrorCode::BlockIdIsEmpty)?;
         let row_id = NotEmptyStr::parse(self.row_id).map_err(|_| ErrorCode::RowIdIsEmpty)?;
 
         Ok(GridRowId {
             grid_id: grid_id.0,
-            block_id: self.block_id,
+            block_id: block_id.0,
             row_id: row_id.0,
         })
     }