From fedc57a52c6cd8631e2f71e5a479c44d8e1aae6b Mon Sep 17 00:00:00 2001 From: appflowy Date: Sun, 3 Jul 2022 15:53:28 +0800 Subject: [PATCH] refactor: grid block cache --- .../grid/block/block_listener.dart | 41 +-- .../application/grid/block/block_service.dart | 55 +++ .../{cell_data_cache.dart => cache.dart} | 20 +- .../grid/cell/cell_service/cell_service.dart | 2 +- .../cell/cell_service/context_builder.dart | 6 +- .../workspace/application/grid/grid_bloc.dart | 100 +++--- .../application/grid/grid_header_bloc.dart | 2 +- .../application/grid/grid_service.dart | 108 +++--- .../application/grid/row/row_bloc.dart | 8 +- .../application/grid/row/row_detail_bloc.dart | 8 +- .../application/grid/row/row_service.dart | 334 ++++++++---------- .../grid/setting/property_bloc.dart | 12 +- .../plugins/grid/src/grid_page.dart | 28 +- .../grid/src/widgets/cell/cell_builder.dart | 2 +- .../grid/src/widgets/row/grid_row.dart | 9 +- .../grid/src/widgets/row/row_detail.dart | 10 +- .../flowy-grid/src/entities/row_entities.rs | 4 +- 17 files changed, 376 insertions(+), 373 deletions(-) rename frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/{cell_data_cache.dart => cache.dart} (86%) diff --git a/frontend/app_flowy/lib/workspace/application/grid/block/block_listener.dart b/frontend/app_flowy/lib/workspace/application/grid/block/block_listener.dart index 225f969a84..bdcecb0324 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/block/block_listener.dart +++ b/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 _listeners = LinkedHashMap(); - GridBlockCache({required this.gridId}); - - void start(void Function(GridBlockUpdateNotifierValue) onBlockChanged) { - _onBlockChanged = onBlockChanged; - for (final listener in _listeners.values) { - listener.start(onBlockChanged); - } - } - - Future 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, FlowyError>; -class _GridBlockListener { +class GridBlockListener { final String blockId; PublishNotifier? _rowsUpdateNotifier = PublishNotifier(); GridNotificationListener? _listener; - _GridBlockListener({required this.blockId}); + GridBlockListener({required this.blockId}); void start(void Function(GridBlockUpdateNotifierValue) onBlockChanged) { if (_listener != null) { diff --git a/frontend/app_flowy/lib/workspace/application/grid/block/block_service.dart b/frontend/app_flowy/lib/workspace/application/grid/block/block_service.dart index e69de29bb2..3fb734db70 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/block/block_service.dart +++ b/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 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 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); + }); + } +} diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_data_cache.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cache.dart similarity index 86% rename from frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_data_cache.dart rename to frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cache.dart index fb7ce5734a..ccf47fddb3 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_data_cache.dart +++ b/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>> _fieldListenerByFieldId = {}; /// fieldId: {cacheKey: cacheData} final Map> _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 dispose() async { _fieldListenerByFieldId.clear(); _cellDataByFieldId.clear(); - fieldDelegate.dispose(); } } diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_service.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_service.dart index a78a66d67e..25c51e9c07 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_service.dart +++ b/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 diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/context_builder.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/context_builder.dart index 6b97bc1456..00264ad4b0 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/context_builder.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/context_builder.dart @@ -6,10 +6,10 @@ typedef GridDateCellContext = _GridCellContext; typedef GridURLCellContext = _GridCellContext; 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 extends Equatable { final GridCell gridCell; - final GridCellCache cellCache; + final GridCellCacheService cellCache; final _GridCellCacheKey _cacheKey; final IGridCellDataLoader cellDataLoader; final _GridCellDataPersistence cellDataPersistence; diff --git a/frontend/app_flowy/lib/workspace/application/grid/grid_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/grid_bloc.dart index 813c101d53..3423dc34a4 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/grid_bloc.dart +++ b/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 { + 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 _blocks; + + List get rows { + final List 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( (event, emit) async { await event.when( @@ -56,11 +46,11 @@ class GridBloc extends Bloc { 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 { @override Future 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 { 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 { 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 { ), ); } + + void _initialBlocks(List 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 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(), ); } diff --git a/frontend/app_flowy/lib/workspace/application/grid/grid_header_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/grid_header_bloc.dart index 74c801f4ff..3e4db25b3d 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/grid_header_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/grid_header_bloc.dart @@ -48,7 +48,7 @@ class GridHeaderBloc extends Bloc { Future _startListening() async { fieldCache.addListener( - onChanged: (fields) => add(GridHeaderEvent.didReceiveFieldUpdate(fields)), + onFields: (fields) => add(GridHeaderEvent.didReceiveFieldUpdate(fields)), listenWhen: () => !isClosed, ); } diff --git a/frontend/app_flowy/lib/workspace/application/grid/grid_service.dart b/frontend/app_flowy/lib/workspace/application/grid/grid_service.dart index c21d548ae0..798db9c1e7 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/grid_service.dart +++ b/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 get fields => _fields; } -typedef ChangesetListener = void Function(GridFieldChangeset); +typedef FieldChangesetCallback = void Function(GridFieldChangeset); +typedef FieldsCallback = void Function(List); class GridFieldCache { final String gridId; late final GridFieldsListener _fieldListener; FieldsNotifier? _fieldNotifier = FieldsNotifier(); - final List _changesetListener = []; + final Map _fieldsCallbackMap = {}; + final Map _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)? 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); + _changesetCallbackMap[onChangeset] = fn; + } + + if (onFields != null) { + fn() { + if (listenWhen != null && listenWhen() == false) { + return; + } + onFields(fields); } - if (listener != null) { - listener(); + _fieldsCallbackMap[onFields] = fn; + _fieldNotifier?.addListener(fn); + } + } + + void removeListener({ + FieldsCallback? onFieldsListener, + FieldChangesetCallback? onChangsetListener, + }) { + if (onFieldsListener != null) { + final fn = _fieldsCallbackMap.remove(onFieldsListener); + if (fn != null) { + _fieldNotifier?.removeListener(fn); } } - _fieldNotifier?.addListener(f); - return f; - } - - void removeListener(VoidCallback f) { - _fieldNotifier?.removeListener(f); - } - - void addChangesetListener(ChangesetListener listener) { - _changesetListener.add(listener); - } - - 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 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; } } } diff --git a/frontend/app_flowy/lib/workspace/application/grid/row/row_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/row/row_bloc.dart index 72acc08720..7f565b500a 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/row/row_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/row/row_bloc.dart @@ -11,12 +11,12 @@ part 'row_bloc.freezed.dart'; class RowBloc extends Bloc { 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 { } Future _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, ); } diff --git a/frontend/app_flowy/lib/workspace/application/grid/row/row_detail_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/row/row_detail_bloc.dart index 671bbe5fa1..f17f0f4cb6 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/row/row_detail_bloc.dart +++ b/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 { 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( @@ -40,9 +40,9 @@ class RowDetailBloc extends Bloc { } Future _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, ); } diff --git a/frontend/app_flowy/lib/workspace/application/grid/row/row_service.dart b/frontend/app_flowy/lib/workspace/application/grid/row/row_service.dart index 24ef1807c0..c31a0660aa 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/row/row_service.dart +++ b/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 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 get clonedRows => _rowsNotifier.clonedRows; + final GridBlock block; + final _Notifier _notifier; + List _rows = []; + final HashMap _rowByRowId; + final GridRowCacheDelegate _delegate; + final GridCellCacheService _cellCache; - GridRowCache({ + List get rows => _rows; + GridCellCacheService get cellCache => _cellCache; + + 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 dispose() async { - _rowsNotifier.dispose(); + _delegate.dispose(); + _notifier.dispose(); + await _cellCache.dispose(); } void applyChangesets(List 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, GridRowChangeReason)? onChanged, - bool Function()? listenWhen, - }) { - _rowsNotifier.addListener(() { - if (onChanged == null) { - return; - } + void _deleteRows(List deletedRows) { + if (deletedRows.isEmpty) { + return; + } - if (listenWhen != null && listenWhen() == false) { - return; - } + final List newRows = []; + final DeletedIndexs deletedIndex = []; + final Map deletedRowByRowId = {for (var e in deletedRows) e.rowId: e}; - onChanged(clonedRows, _rowsNotifier._changeReason); + _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)); + } + + void _insertRows(List insertRows) { + if (insertRows.isEmpty) { + return; + } + + InsertedIndexs insertIndexs = []; + final List 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 updatedRows) { + if (updatedRows.isEmpty) { + return; + } + + final UpdatedIndexs updatedIndexs = UpdatedIndexs(); + final List 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); + } + } + + _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 rowInfos) { - _rowsNotifier.initialRows(rowInfos); - } - Future _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; } + + void _refreshRow(OptionalRow optionRow) { + if (!optionRow.hasRow()) { + return; + } + final updatedRow = optionRow.row; + updatedRow.freeze(); + + _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); + + // Calculate the update index + final UpdatedIndexs updatedIndexs = UpdatedIndexs(); + updatedIndexs[row.rowId] = UpdatedIndex(index: index, rowId: row.rowId); + + // + _notifier.receive(GridRowChangeReason.update(updatedIndexs)); + } + } + } + + GridRow buildGridRow(BlockRowInfo rowInfo) { + return GridRow( + gridId: gridId, + blockId: block.id, + fields: _delegate.fields, + rowId: rowInfo.rowId, + height: rowInfo.height.toDouble(), + ); + } } -class RowsNotifier extends ChangeNotifier { - List _allRows = []; - HashMap _rowByRowId = HashMap(); - GridRowChangeReason _changeReason = const InitialListState(); - final GridRow Function(BlockRowInfo) rowBuilder; +class _Notifier extends ChangeNotifier { + GridRowChangeReason _reason = const InitialListState(); - RowsNotifier({ - required this.rowBuilder, - }); + _Notifier(); - List get clonedRows => [..._allRows]; - - void initialRows(List rowInfos) { - _rowByRowId = HashMap(); - final rows = rowInfos.map((rowOrder) => rowBuilder(rowOrder)).toList(); - _update(rows, const GridRowChangeReason.initial()); - } - - void deleteRows(List deletedRows) { - if (deletedRows.isEmpty) { - return; - } - - final List newRows = []; - final DeletedIndexs deletedIndex = []; - final Map 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 insertRows) { - if (insertRows.isEmpty) { - return; - } - - InsertedIndexs insertIndexs = []; - final List 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 updatedRows) { - if (updatedRows.isEmpty) { - return; - } - - final UpdatedIndexs updatedIndexs = UpdatedIndexs(); - final List 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; - - newRows.removeAt(index); - newRows.insert(index, rowBuilder(rowOrder)); - updatedIndexs[rowId] = UpdatedIndex(index: index, rowId: rowId); - } - } - - _update(newRows, GridRowChangeReason.update(updatedIndexs)); - } - - void fieldDidChange() { - _update(_allRows, const GridRowChangeReason.fieldDidChange()); - } - - void _update(List rows, GridRowChangeReason reason) { - _allRows = rows; - _changeReason = reason; - - _changeReason.map( + 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 { diff --git a/frontend/app_flowy/lib/workspace/application/grid/setting/property_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/setting/property_bloc.dart index 587bebcfe4..10c59559d3 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/setting/property_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/setting/property_bloc.dart @@ -10,7 +10,7 @@ part 'property_bloc.freezed.dart'; class GridPropertyBloc extends Bloc { final GridFieldCache _fieldCache; - Function()? _listenFieldCallback; + Function(List)? _onFieldsFn; GridPropertyBloc({required String gridId, required GridFieldCache fieldCache}) : _fieldCache = fieldCache, @@ -42,15 +42,17 @@ class GridPropertyBloc extends Bloc { @override Future 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, ); } diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/grid_page.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/grid_page.dart index 19c94817d8..dbf24fdfa5 100755 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/grid_page.dart +++ b/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( - 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 animation, ) { - final rowCache = context.read().rowCache; - final cellCache = context.read().cellCache; - return SizeTransition( - sizeFactor: animation, - child: GridRowWidget( - rowData: rowData, - rowCache: rowCache, - cellCache: cellCache, - key: ValueKey(rowData.rowId), - ), - ); + final rowCache = context.read().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(); + } } } diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart index 64b5cda4ef..9f3c7a632e 100755 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart +++ b/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); diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/grid_row.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/grid_row.dart index 15dbb0bfc7..de7f117d51 100755 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/grid_row.dart +++ b/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 { 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 { 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); diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/row_detail.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/row_detail.dart index 23f8b50f63..d1c2f96e75 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/row_detail.dart +++ b/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 { 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, diff --git a/frontend/rust-lib/flowy-grid/src/entities/row_entities.rs b/frontend/rust-lib/flowy-grid/src/entities/row_entities.rs index 146c7e9715..4b3c8a7a78 100644 --- a/frontend/rust-lib/flowy-grid/src/entities/row_entities.rs +++ b/frontend/rust-lib/flowy-grid/src/entities/row_entities.rs @@ -31,12 +31,12 @@ impl TryInto for GridRowIdPayload { fn try_into(self) -> Result { 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, }) }