diff --git a/frontend/app_flowy/lib/workspace/application/grid/block/block_service.dart b/frontend/app_flowy/lib/workspace/application/grid/block/block_cache.dart similarity index 77% rename from frontend/app_flowy/lib/workspace/application/grid/block/block_service.dart rename to frontend/app_flowy/lib/workspace/application/grid/block/block_cache.dart index a531e65d2e..caf61e64b3 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/block/block_service.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/block/block_cache.dart @@ -6,24 +6,25 @@ import 'package:flowy_sdk/protobuf/flowy-grid/block_entities.pb.dart'; import 'block_listener.dart'; -class GridBlockCacheService { +/// Read https://appflowy.gitbook.io/docs/essential-documentation/contribute-to-appflowy/architecture/frontend/grid for more information +class GridBlockCache { final String gridId; final GridBlock block; - late GridRowsCache _rowCache; + late GridRowCache _rowCache; late GridBlockListener _listener; List get rows => _rowCache.rows; - GridRowsCache get rowCache => _rowCache; + GridRowCache get rowCache => _rowCache; - GridBlockCacheService({ + GridBlockCache({ required this.gridId, required this.block, required GridFieldCache fieldCache, }) { - _rowCache = GridRowsCache( + _rowCache = GridRowCache( gridId: gridId, block: block, - delegate: GridRowCacheDelegateImpl(fieldCache), + notifier: GridRowCacheFieldNotifierImpl(fieldCache), ); _listener = GridBlockListener(blockId: block.id); diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cache.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cache.dart deleted file mode 100644 index a6079bae6d..0000000000 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cache.dart +++ /dev/null @@ -1,67 +0,0 @@ -part of 'cell_service.dart'; - -typedef GridCellMap = LinkedHashMap; - -class _GridCellCacheItem { - GridCellId key; - dynamic object; - _GridCellCacheItem({ - required this.key, - required this.object, - }); -} - -class GridCellId { - final String fieldId; - final String rowId; - GridCellId({ - required this.fieldId, - required this.rowId, - }); -} - -class GridCellsCache { - final String gridId; - - /// fieldId: {cacheKey: cacheData} - final Map> _cellDataByFieldId = {}; - GridCellsCache({ - required this.gridId, - }); - - void remove(String fieldId) { - _cellDataByFieldId.remove(fieldId); - } - - void insert(T item) { - var map = _cellDataByFieldId[item.key.fieldId]; - if (map == null) { - _cellDataByFieldId[item.key.fieldId] = {}; - map = _cellDataByFieldId[item.key.fieldId]; - } - - map![item.key.rowId] = item.object; - } - - T? get(GridCellId key) { - final map = _cellDataByFieldId[key.fieldId]; - if (map == null) { - return null; - } else { - final object = map[key.rowId]; - 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 dispose() async { - _cellDataByFieldId.clear(); - } -} diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_cache.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_cache.dart new file mode 100644 index 0000000000..1f14c7c54a --- /dev/null +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_cache.dart @@ -0,0 +1,70 @@ +part of 'cell_service.dart'; + +typedef GridCellMap = LinkedHashMap; + +class GridCell { + dynamic object; + GridCell({ + required this.object, + }); +} + +/// Use to index the cell in the grid. +/// We use [fieldId + rowId] to identify the cell. +class GridCellCacheKey { + final String fieldId; + final String rowId; + GridCellCacheKey({ + required this.fieldId, + required this.rowId, + }); +} + +/// GridCellCache is used to cache cell data of each block. +/// We use GridCellCacheKey to index the cell in the cache. +/// Read https://appflowy.gitbook.io/docs/essential-documentation/contribute-to-appflowy/architecture/frontend/grid +/// for more information +class GridCellCache { + final String gridId; + + /// fieldId: {cacheKey: GridCell} + final Map> _cellDataByFieldId = {}; + GridCellCache({ + required this.gridId, + }); + + void remove(String fieldId) { + _cellDataByFieldId.remove(fieldId); + } + + void insert(GridCellCacheKey key, T value) { + var map = _cellDataByFieldId[key.fieldId]; + if (map == null) { + _cellDataByFieldId[key.fieldId] = {}; + map = _cellDataByFieldId[key.fieldId]; + } + + map![key.rowId] = value.object; + } + + T? get(GridCellCacheKey key) { + final map = _cellDataByFieldId[key.fieldId]; + if (map == null) { + return null; + } else { + final value = map[key.rowId]; + if (value is T) { + return value; + } else { + if (value != null) { + Log.error("Expected value type: $T, but receive $value"); + } + return null; + } + } + } + + Future dispose() async { + _cellDataByFieldId.clear(); + } +} diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_data_loader.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_data_loader.dart index 95b7b77b61..324c65c5f5 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_data_loader.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_data_loader.dart @@ -5,46 +5,24 @@ abstract class IGridCellDataConfig { bool get reloadOnFieldChanged; } -class GridCellDataConfig implements IGridCellDataConfig { - @override - final bool reloadOnFieldChanged; - - const GridCellDataConfig({ - this.reloadOnFieldChanged = false, - }); -} - -abstract class IGridCellDataLoader { - Future loadData(); - - IGridCellDataConfig get config; -} - abstract class ICellDataParser { T? parserData(List data); } -class GridCellDataLoader extends IGridCellDataLoader { +class GridCellDataLoader { final CellService service = CellService(); - final GridCell gridCell; + final GridCellIdentifier cellId; final ICellDataParser parser; - - @override - final IGridCellDataConfig config; + final bool reloadOnFieldChanged; GridCellDataLoader({ - required this.gridCell, + required this.cellId, required this.parser, - this.config = const GridCellDataConfig(), + this.reloadOnFieldChanged = false, }); - @override Future loadData() { - final fut = service.getCell( - gridId: gridCell.gridId, - fieldId: gridCell.field.id, - rowId: gridCell.rowId, - ); + final fut = service.getCell(cellId: cellId); return fut.then( (result) => result.fold((Cell cell) { try { diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_data_persistence.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_data_persistence.dart index 9e2f577d7c..4959adf3ed 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_data_persistence.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_data_persistence.dart @@ -1,25 +1,22 @@ part of 'cell_service.dart'; +/// Save the cell data to disk +/// You can extend this class to do custom operations. For example, the DateCellDataPersistence. abstract class IGridCellDataPersistence { Future> save(D data); } class CellDataPersistence implements IGridCellDataPersistence { - final GridCell gridCell; + final GridCellIdentifier cellId; CellDataPersistence({ - required this.gridCell, + required this.cellId, }); final CellService _cellService = CellService(); @override Future> save(String data) async { - final fut = _cellService.updateCell( - gridId: gridCell.gridId, - fieldId: gridCell.field.id, - rowId: gridCell.rowId, - data: data, - ); + final fut = _cellService.updateCell(cellId: cellId, data: data); return fut.then((result) { return result.fold( @@ -36,14 +33,14 @@ class CalendarData with _$CalendarData { } class DateCellDataPersistence implements IGridCellDataPersistence { - final GridCell gridCell; + final GridCellIdentifier cellId; DateCellDataPersistence({ - required this.gridCell, + required this.cellId, }); @override Future> save(CalendarData data) { - var payload = DateChangesetPayload.create()..cellIdentifier = _cellIdentifier(gridCell); + var payload = DateChangesetPayload.create()..cellIdentifier = _makeCellIdPayload(cellId); final date = (data.date.millisecondsSinceEpoch ~/ 1000).toString(); payload.date = date; @@ -61,9 +58,9 @@ class DateCellDataPersistence implements IGridCellDataPersistence } } -CellIdentifierPayload _cellIdentifier(GridCell gridCell) { +CellIdentifierPayload _makeCellIdPayload(GridCellIdentifier cellId) { return CellIdentifierPayload.create() - ..gridId = gridCell.gridId - ..fieldId = gridCell.field.id - ..rowId = gridCell.rowId; + ..gridId = cellId.gridId + ..fieldId = cellId.fieldId + ..rowId = cellId.rowId; } diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_field_notifier.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_field_notifier.dart index 1c043e2096..e4c909d501 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_field_notifier.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_field_notifier.dart @@ -8,6 +8,8 @@ abstract class GridFieldChangedNotifier { void dispose(); } +/// Grid's cell helper wrapper that enables each cell will get notified when the corresponding field was changed. +/// You Register an onFieldChanged callback to listen to the cell changes, and unregister if you don't want to listen. class GridCellFieldNotifier { /// fieldId: {objectId: callback} final Map>> _fieldListenerByFieldId = {}; @@ -27,7 +29,8 @@ class GridCellFieldNotifier { ); } - void addFieldListener(GridCellId cacheKey, VoidCallback onFieldChanged) { + /// + void register(GridCellCacheKey cacheKey, VoidCallback onFieldChanged) { var map = _fieldListenerByFieldId[cacheKey.fieldId]; if (map == null) { _fieldListenerByFieldId[cacheKey.fieldId] = {}; @@ -43,7 +46,7 @@ class GridCellFieldNotifier { } } - void removeFieldListener(GridCellId cacheKey, VoidCallback fn) { + void unregister(GridCellCacheKey cacheKey, VoidCallback fn) { var callbacks = _fieldListenerByFieldId[cacheKey.fieldId]?[cacheKey.rowId]; final index = callbacks?.indexWhere((callback) => callback == fn); if (index != null && index != -1) { 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 6c7791af92..d9924d38bb 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 @@ -18,11 +18,12 @@ import 'package:app_flowy/workspace/application/grid/cell/cell_listener.dart'; import 'package:app_flowy/workspace/application/grid/field/field_service.dart'; import 'dart:convert' show utf8; +import '../../field/type_option/type_option_service.dart'; import 'cell_field_notifier.dart'; part 'cell_service.freezed.dart'; part 'cell_data_loader.dart'; part 'context_builder.dart'; -part 'cache.dart'; +part 'cell_cache.dart'; part 'cell_data_persistence.dart'; // key: rowId @@ -31,44 +32,46 @@ class CellService { CellService(); Future> updateCell({ - required String gridId, - required String fieldId, - required String rowId, + required GridCellIdentifier cellId, required String data, }) { final payload = CellChangeset.create() - ..gridId = gridId - ..fieldId = fieldId - ..rowId = rowId + ..gridId = cellId.gridId + ..fieldId = cellId.fieldId + ..rowId = cellId.rowId ..content = data; return GridEventUpdateCell(payload).send(); } Future> getCell({ - required String gridId, - required String fieldId, - required String rowId, + required GridCellIdentifier cellId, }) { final payload = CellIdentifierPayload.create() - ..gridId = gridId - ..fieldId = fieldId - ..rowId = rowId; + ..gridId = cellId.gridId + ..fieldId = cellId.fieldId + ..rowId = cellId.rowId; return GridEventGetCell(payload).send(); } } +/// Id of the cell +/// We can locate the cell by using gridId + rowId + field.id. @freezed -class GridCell with _$GridCell { - const factory GridCell({ +class GridCellIdentifier with _$GridCellIdentifier { + const factory GridCellIdentifier({ required String gridId, required String rowId, required Field field, - }) = _GridCell; + }) = _GridCellIdentifier; // ignore: unused_element - const GridCell._(); + const GridCellIdentifier._(); - String cellId() { - return rowId + field.id + "${field.fieldType}"; + String get fieldId => field.id; + + FieldType get fieldType => field.fieldType; + + ValueKey key() { + return ValueKey(rowId + fieldId + "${field.fieldType}"); } } 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 f45520c602..124835cf94 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,115 +6,120 @@ typedef GridDateCellController = IGridCellController typedef GridURLCellController = IGridCellController; class GridCellControllerBuilder { - final GridCell _gridCell; - final GridCellsCache _cellCache; + final GridCellIdentifier _cellId; + final GridCellCache _cellCache; final GridFieldCache _fieldCache; - GridCellControllerBuilder( - {required GridCell gridCell, required GridCellsCache cellCache, required GridFieldCache fieldCache}) - : _cellCache = cellCache, + GridCellControllerBuilder({ + required GridCellIdentifier cellId, + required GridCellCache cellCache, + required GridFieldCache fieldCache, + }) : _cellCache = cellCache, _fieldCache = fieldCache, - _gridCell = gridCell; + _cellId = cellId; IGridCellController build() { final cellFieldNotifier = GridCellFieldNotifier(notifier: _GridFieldChangedNotifierImpl(_fieldCache)); - switch (_gridCell.field.fieldType) { + switch (_cellId.fieldType) { case FieldType.Checkbox: final cellDataLoader = GridCellDataLoader( - gridCell: _gridCell, + cellId: _cellId, parser: StringCellDataParser(), ); return GridCellController( - gridCell: _gridCell, + cellId: _cellId, cellCache: _cellCache, cellDataLoader: cellDataLoader, - cellFieldNotifier: cellFieldNotifier, - cellDataPersistence: CellDataPersistence(gridCell: _gridCell), + fieldNotifier: cellFieldNotifier, + cellDataPersistence: CellDataPersistence(cellId: _cellId), ); case FieldType.DateTime: final cellDataLoader = GridCellDataLoader( - gridCell: _gridCell, + cellId: _cellId, parser: DateCellDataParser(), - config: const GridCellDataConfig(reloadOnFieldChanged: true), + reloadOnFieldChanged: true, ); return GridDateCellController( - gridCell: _gridCell, + cellId: _cellId, cellCache: _cellCache, cellDataLoader: cellDataLoader, - cellFieldNotifier: cellFieldNotifier, - cellDataPersistence: DateCellDataPersistence(gridCell: _gridCell), + fieldNotifier: cellFieldNotifier, + cellDataPersistence: DateCellDataPersistence(cellId: _cellId), ); case FieldType.Number: final cellDataLoader = GridCellDataLoader( - gridCell: _gridCell, + cellId: _cellId, parser: StringCellDataParser(), - config: const GridCellDataConfig(reloadOnFieldChanged: true), ); return GridCellController( - gridCell: _gridCell, + cellId: _cellId, cellCache: _cellCache, cellDataLoader: cellDataLoader, - cellFieldNotifier: cellFieldNotifier, - cellDataPersistence: CellDataPersistence(gridCell: _gridCell), + fieldNotifier: cellFieldNotifier, + cellDataPersistence: CellDataPersistence(cellId: _cellId), ); case FieldType.RichText: final cellDataLoader = GridCellDataLoader( - gridCell: _gridCell, + cellId: _cellId, parser: StringCellDataParser(), ); return GridCellController( - gridCell: _gridCell, + cellId: _cellId, cellCache: _cellCache, cellDataLoader: cellDataLoader, - cellFieldNotifier: cellFieldNotifier, - cellDataPersistence: CellDataPersistence(gridCell: _gridCell), + fieldNotifier: cellFieldNotifier, + cellDataPersistence: CellDataPersistence(cellId: _cellId), ); case FieldType.MultiSelect: case FieldType.SingleSelect: final cellDataLoader = GridCellDataLoader( - gridCell: _gridCell, + cellId: _cellId, parser: SelectOptionCellDataParser(), - config: const GridCellDataConfig(reloadOnFieldChanged: true), + reloadOnFieldChanged: true, ); return GridSelectOptionCellController( - gridCell: _gridCell, + cellId: _cellId, cellCache: _cellCache, cellDataLoader: cellDataLoader, - cellFieldNotifier: cellFieldNotifier, - cellDataPersistence: CellDataPersistence(gridCell: _gridCell), + fieldNotifier: cellFieldNotifier, + cellDataPersistence: CellDataPersistence(cellId: _cellId), ); case FieldType.URL: final cellDataLoader = GridCellDataLoader( - gridCell: _gridCell, + cellId: _cellId, parser: URLCellDataParser(), ); return GridURLCellController( - gridCell: _gridCell, + cellId: _cellId, cellCache: _cellCache, cellDataLoader: cellDataLoader, - cellFieldNotifier: cellFieldNotifier, - cellDataPersistence: CellDataPersistence(gridCell: _gridCell), + fieldNotifier: cellFieldNotifier, + cellDataPersistence: CellDataPersistence(cellId: _cellId), ); } throw UnimplementedError; } } -// T: the type of the CellData -// D: the type of the data that will be saved to disk +/// IGridCellController is used to manipulate the cell and receive notifications. +/// * Read/Write cell data +/// * Listen on field/cell notifications. +/// +/// Generic T represents the type of the cell data. +/// Generic D represents the type of data that will be saved to the disk +/// // ignore: must_be_immutable class IGridCellController extends Equatable { - final GridCell gridCell; - final GridCellsCache _cellsCache; - final GridCellId _cacheKey; + final GridCellIdentifier cellId; + final GridCellCache _cellsCache; + final GridCellCacheKey _cacheKey; final FieldService _fieldService; - final GridCellFieldNotifier _cellFieldNotifier; - // final GridCellFieldNotifier _fieldNotifier; - final IGridCellDataLoader _cellDataLoader; + final GridCellFieldNotifier _fieldNotifier; + final GridCellDataLoader _cellDataLoader; final IGridCellDataPersistence _cellDataPersistence; late final CellListener _cellListener; @@ -124,42 +129,39 @@ class IGridCellController extends Equatable { VoidCallback? _onFieldChangedFn; Timer? _loadDataOperation; Timer? _saveDataOperation; - bool isDispose = false; + bool _isDispose = false; IGridCellController({ - required this.gridCell, - required GridCellsCache cellCache, - required GridCellFieldNotifier cellFieldNotifier, - required IGridCellDataLoader cellDataLoader, + required this.cellId, + required GridCellCache cellCache, + required GridCellFieldNotifier fieldNotifier, + required GridCellDataLoader cellDataLoader, required IGridCellDataPersistence cellDataPersistence, - // required GridFieldChangedNotifier notifierDelegate, }) : _cellsCache = cellCache, _cellDataLoader = cellDataLoader, _cellDataPersistence = cellDataPersistence, - _cellFieldNotifier = cellFieldNotifier, - _fieldService = FieldService(gridId: gridCell.gridId, fieldId: gridCell.field.id), - _cacheKey = GridCellId(rowId: gridCell.rowId, fieldId: gridCell.field.id); + _fieldNotifier = fieldNotifier, + _fieldService = FieldService(gridId: cellId.gridId, fieldId: cellId.field.id), + _cacheKey = GridCellCacheKey(rowId: cellId.rowId, fieldId: cellId.field.id); IGridCellController clone() { return IGridCellController( - gridCell: gridCell, + cellId: cellId, cellDataLoader: _cellDataLoader, cellCache: _cellsCache, - cellFieldNotifier: _cellFieldNotifier, + fieldNotifier: _fieldNotifier, cellDataPersistence: _cellDataPersistence); } - String get gridId => gridCell.gridId; + String get gridId => cellId.gridId; - String get rowId => gridCell.rowId; + String get rowId => cellId.rowId; - String get cellId => gridCell.rowId + gridCell.field.id; + String get fieldId => cellId.field.id; - String get fieldId => gridCell.field.id; + Field get field => cellId.field; - Field get field => gridCell.field; - - FieldType get fieldType => gridCell.field.fieldType; + FieldType get fieldType => cellId.field.fieldType; VoidCallback? startListening({required void Function(T?) onCellChanged, VoidCallback? onCellFieldChanged}) { if (isListening) { @@ -175,7 +177,7 @@ class IGridCellController extends Equatable { /// user input: 12 /// cell display: $12 _cellDataNotifier = ValueNotifier(_cellsCache.get(_cacheKey)); - _cellListener = CellListener(rowId: gridCell.rowId, fieldId: gridCell.field.id); + _cellListener = CellListener(rowId: cellId.rowId, fieldId: cellId.field.id); /// 1.Listen on user edit event and load the new cell data if needed. _cellListener.start(onCellChanged: (result) { @@ -191,16 +193,18 @@ class IGridCellController extends Equatable { onCellFieldChanged(); } - if (_cellDataLoader.config.reloadOnFieldChanged) { + if (_cellDataLoader.reloadOnFieldChanged) { _loadData(); } }; - _cellFieldNotifier.addFieldListener(_cacheKey, _onFieldChangedFn!); + _fieldNotifier.register(_cacheKey, _onFieldChangedFn!); /// Notify the listener, the cell data was changed. onCellChangedFn() => onCellChanged(_cellDataNotifier?.value); _cellDataNotifier?.addListener(onCellChangedFn); + + // Return the function pointer that can be used when calling removeListener. return onCellChangedFn; } @@ -208,22 +212,38 @@ class IGridCellController extends Equatable { _cellDataNotifier?.removeListener(fn); } - T? getCellData({bool loadIfNoCache = true}) { + /// Return the cell data. + /// The cell data will be read from the Cache first, and load from disk if it does not exist. + /// You can set [loadIfNotExist] to false (default is true) to disable loading the cell data. + T? getCellData({bool loadIfNotExist = true}) { final data = _cellsCache.get(_cacheKey); - if (data == null && loadIfNoCache) { + if (data == null && loadIfNotExist) { _loadData(); } return data; } - Future> getTypeOptionData() { - return _fieldService.getFieldTypeOptionData(fieldType: fieldType); + /// Return the FieldTypeOptionData that can be parsed into corresponding class using the [parser]. + /// [PD] is the type that the parser return. + Future> getFieldTypeOption(P parser) { + return _fieldService.getFieldTypeOptionData(fieldType: fieldType).then((result) { + return result.fold( + (data) => parser.fromBuffer(data.typeOptionData), + (err) => right(err), + ); + }); } + /// Save the cell data to disk + /// You can set [dedeplicate] to true (default is false) to reduce the save operation. + /// It's useful when you call this method when user editing the [TextField]. + /// The default debounce interval is 300 milliseconds. void saveCellData(D data, {bool deduplicate = false, void Function(Option)? resultCallback}) async { if (deduplicate) { _loadDataOperation?.cancel(); - _loadDataOperation = Timer(const Duration(milliseconds: 300), () async { + + _saveDataOperation?.cancel(); + _saveDataOperation = Timer(const Duration(milliseconds: 300), () async { final result = await _cellDataPersistence.save(data); if (resultCallback != null) { resultCallback(result); @@ -238,34 +258,36 @@ class IGridCellController extends Equatable { } void _loadData() { + _saveDataOperation?.cancel(); + _loadDataOperation?.cancel(); _loadDataOperation = Timer(const Duration(milliseconds: 10), () { _cellDataLoader.loadData().then((data) { _cellDataNotifier?.value = data; - _cellsCache.insert(_GridCellCacheItem(key: _cacheKey, object: data)); + _cellsCache.insert(_cacheKey, GridCell(object: data)); }); }); } void dispose() { - if (isDispose) { + if (_isDispose) { Log.error("$this should only dispose once"); return; } - isDispose = true; + _isDispose = true; _cellListener.stop(); _loadDataOperation?.cancel(); _saveDataOperation?.cancel(); _cellDataNotifier = null; if (_onFieldChangedFn != null) { - _cellFieldNotifier.removeFieldListener(_cacheKey, _onFieldChangedFn!); + _fieldNotifier.unregister(_cacheKey, _onFieldChangedFn!); _onFieldChangedFn = null; } } @override - List get props => [_cellsCache.get(_cacheKey) ?? "", cellId]; + List get props => [_cellsCache.get(_cacheKey) ?? "", cellId.rowId + cellId.field.id]; } class _GridFieldChangedNotifierImpl extends GridFieldChangedNotifier { diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_editor_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_editor_bloc.dart index 139d29141b..2daabe1a98 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_editor_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_editor_bloc.dart @@ -17,7 +17,7 @@ class SelectOptionCellEditorBloc extends Bloc( (event, emit) async { @@ -184,7 +184,7 @@ class SelectOptionEditorState with _$SelectOptionEditorState { }) = _SelectOptionEditorState; factory SelectOptionEditorState.initial(GridSelectOptionCellController context) { - final data = context.getCellData(loadIfNoCache: false); + final data = context.getCellData(loadIfNotExist: false); return SelectOptionEditorState( options: data?.options ?? [], allOptions: data?.options ?? [], diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_service.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_service.dart index f3458454d1..7b6fffa310 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_service.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_service.dart @@ -7,12 +7,12 @@ import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart'; import 'cell_service/cell_service.dart'; class SelectOptionService { - final GridCell gridCell; - SelectOptionService({required this.gridCell}); + final GridCellIdentifier cellId; + SelectOptionService({required this.cellId}); - String get gridId => gridCell.gridId; - String get fieldId => gridCell.field.id; - String get rowId => gridCell.rowId; + String get gridId => cellId.gridId; + String get fieldId => cellId.field.id; + String get rowId => cellId.rowId; Future> create({required String name}) { return TypeOptionService(gridId: gridId, fieldId: fieldId).newOption(name: name).then( diff --git a/frontend/app_flowy/lib/workspace/application/grid/field/field_editor_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/field/field_editor_bloc.dart index 2815d5519d..3779b4b418 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/field/field_editor_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/field/field_editor_bloc.dart @@ -9,7 +9,7 @@ class FieldEditorBloc extends Bloc { FieldEditorBloc({ required String gridId, required String fieldName, - required IFieldContextLoader fieldContextLoader, + required IFieldTypeOptionLoader fieldContextLoader, }) : super(FieldEditorState.initial(gridId, fieldName, fieldContextLoader)) { on( (event, emit) async { @@ -53,7 +53,7 @@ class FieldEditorState with _$FieldEditorState { required Option fieldContext, }) = _FieldEditorState; - factory FieldEditorState.initial(String gridId, String fieldName, IFieldContextLoader loader) => FieldEditorState( + factory FieldEditorState.initial(String gridId, String fieldName, IFieldTypeOptionLoader loader) => FieldEditorState( gridId: gridId, fieldContext: none(), errorText: '', diff --git a/frontend/app_flowy/lib/workspace/application/grid/field/field_service.dart b/frontend/app_flowy/lib/workspace/application/grid/field/field_service.dart index 99fef626c0..e8e09a725b 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/field/field_service.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/field/field_service.dart @@ -9,6 +9,10 @@ import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:protobuf/protobuf.dart'; part 'field_service.freezed.dart'; +/// FieldService consists of lots of event functions. We define the events in the backend(Rust), +/// you can find the corresponding event implementation in event_map.rs of the corresponding crate. +/// +/// You could check out the rust-lib/flowy-grid/event_map.rs for more information. class FieldService { final String gridId; final String fieldId; @@ -137,7 +141,7 @@ class GridFieldCellContext with _$GridFieldCellContext { }) = _GridFieldCellContext; } -abstract class IFieldContextLoader { +abstract class IFieldTypeOptionLoader { String get gridId; Future> load(); @@ -151,10 +155,10 @@ abstract class IFieldContextLoader { } } -class NewFieldContextLoader extends IFieldContextLoader { +class NewFieldTypeOptionLoader extends IFieldTypeOptionLoader { @override final String gridId; - NewFieldContextLoader({ + NewFieldTypeOptionLoader({ required this.gridId, }); @@ -168,12 +172,12 @@ class NewFieldContextLoader extends IFieldContextLoader { } } -class FieldContextLoader extends IFieldContextLoader { +class FieldTypeOptionLoader extends IFieldTypeOptionLoader { @override final String gridId; final Field field; - FieldContextLoader({ + FieldTypeOptionLoader({ required this.gridId, required this.field, }); @@ -191,14 +195,14 @@ class FieldContextLoader extends IFieldContextLoader { class GridFieldContext { final String gridId; - final IFieldContextLoader _loader; + final IFieldTypeOptionLoader _loader; late FieldTypeOptionData _data; ValueNotifier? _fieldNotifier; GridFieldContext({ required this.gridId, - required IFieldContextLoader loader, + required IFieldTypeOptionLoader loader, }) : _loader = loader; Future> loadData() async { diff --git a/frontend/app_flowy/lib/workspace/application/grid/field/type_option/date_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/field/type_option/date_bloc.dart index 73be8a427e..7258854ec6 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/field/type_option/date_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/field/type_option/date_bloc.dart @@ -8,7 +8,7 @@ part 'date_bloc.freezed.dart'; typedef DateTypeOptionContext = TypeOptionWidgetContext; -class DateTypeOptionDataParser extends TypeOptionWidgetDataParser { +class DateTypeOptionDataParser extends TypeOptionDataParser { @override DateTypeOption fromBuffer(List buffer) { return DateTypeOption.fromBuffer(buffer); diff --git a/frontend/app_flowy/lib/workspace/application/grid/field/type_option/multi_select_type_option.dart b/frontend/app_flowy/lib/workspace/application/grid/field/type_option/multi_select_type_option.dart index f36974f8e3..eae1765396 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/field/type_option/multi_select_type_option.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/field/type_option/multi_select_type_option.dart @@ -18,7 +18,7 @@ class MultiSelectTypeOptionContext extends TypeOptionWidgetContext Function(SelectOption) get deleteOption { @@ -71,7 +71,7 @@ class MultiSelectTypeOptionContext extends TypeOptionWidgetContext { +class MultiSelectTypeOptionWidgetDataParser extends TypeOptionDataParser { @override MultiSelectTypeOption fromBuffer(List buffer) { return MultiSelectTypeOption.fromBuffer(buffer); diff --git a/frontend/app_flowy/lib/workspace/application/grid/field/type_option/number_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/field/type_option/number_bloc.dart index ea51970b32..804ce3ee11 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/field/type_option/number_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/field/type_option/number_bloc.dart @@ -10,7 +10,7 @@ part 'number_bloc.freezed.dart'; typedef NumberTypeOptionContext = TypeOptionWidgetContext; -class NumberTypeOptionWidgetDataParser extends TypeOptionWidgetDataParser { +class NumberTypeOptionWidgetDataParser extends TypeOptionDataParser { @override NumberTypeOption fromBuffer(List buffer) { return NumberTypeOption.fromBuffer(buffer); diff --git a/frontend/app_flowy/lib/workspace/application/grid/field/type_option/single_select_type_option.dart b/frontend/app_flowy/lib/workspace/application/grid/field/type_option/single_select_type_option.dart index 0dc05e8e1f..16e686fb44 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/field/type_option/single_select_type_option.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/field/type_option/single_select_type_option.dart @@ -18,7 +18,7 @@ class SingleSelectTypeOptionContext extends TypeOptionWidgetContext Function(SelectOption) get deleteOption { @@ -71,7 +71,7 @@ class SingleSelectTypeOptionContext extends TypeOptionWidgetContext { +class SingleSelectTypeOptionWidgetDataParser extends TypeOptionDataParser { @override SingleSelectTypeOption fromBuffer(List buffer) { return SingleSelectTypeOption.fromBuffer(buffer); diff --git a/frontend/app_flowy/lib/workspace/application/grid/field/type_option/type_option_service.dart b/frontend/app_flowy/lib/workspace/application/grid/field/type_option/type_option_service.dart index 84faa3c35a..05ca9e9aa1 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/field/type_option/type_option_service.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/field/type_option/type_option_service.dart @@ -33,17 +33,17 @@ class TypeOptionService { } } -abstract class TypeOptionWidgetDataParser { +abstract class TypeOptionDataParser { T fromBuffer(List buffer); } class TypeOptionWidgetContext { T? _typeOptionObject; final GridFieldContext _fieldContext; - final TypeOptionWidgetDataParser dataBuilder; + final TypeOptionDataParser dataParser; TypeOptionWidgetContext({ - required this.dataBuilder, + required this.dataParser, required GridFieldContext fieldContext, }) : _fieldContext = fieldContext; @@ -56,7 +56,7 @@ class TypeOptionWidgetContext { return _typeOptionObject!; } - final T object = dataBuilder.fromBuffer(_fieldContext.typeOptionData); + final T object = dataParser.fromBuffer(_fieldContext.typeOptionData); _typeOptionObject = object; return object; } @@ -77,7 +77,7 @@ class TypeOptionContext2 { final Field field; final FieldService _fieldService; T? _data; - final TypeOptionWidgetDataParser dataBuilder; + final TypeOptionDataParser dataBuilder; TypeOptionContext2({ required this.gridId, 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 e6fff41317..7f120cf3d8 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/grid_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/grid_bloc.dart @@ -7,7 +7,7 @@ import 'package:flowy_sdk/protobuf/flowy-folder/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_service.dart'; +import 'block/block_cache.dart'; import 'grid_service.dart'; import 'row/row_service.dart'; import 'dart:collection'; @@ -20,7 +20,7 @@ class GridBloc extends Bloc { final GridFieldCache fieldCache; // key: the block id - final LinkedHashMap _blocks; + final LinkedHashMap _blocks; List get rows { final List rows = []; @@ -68,8 +68,8 @@ class GridBloc extends Bloc { return super.close(); } - GridRowsCache? getRowCache(String blockId, String rowId) { - final GridBlockCacheService? blockCache = _blocks[blockId]; + GridRowCache? getRowCache(String blockId, String rowId) { + final GridBlockCache? blockCache = _blocks[blockId]; return blockCache?.rowCache; } @@ -119,7 +119,7 @@ class GridBloc extends Bloc { return; } - final cache = GridBlockCacheService( + final cache = GridBlockCache( gridId: gridId, block: block, fieldCache: fieldCache, 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 3e9cbc2eee..4e742f9f42 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/grid_service.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/grid_service.dart @@ -61,13 +61,12 @@ typedef FieldsCallback = void Function(List); class GridFieldCache { final String gridId; - late final GridFieldsListener _fieldListener; + final GridFieldsListener _fieldListener; FieldsNotifier? _fieldNotifier = FieldsNotifier(); final Map _fieldsCallbackMap = {}; final Map _changesetCallbackMap = {}; - GridFieldCache({required this.gridId}) { - _fieldListener = GridFieldsListener(gridId: gridId); + GridFieldCache({required this.gridId}) : _fieldListener = GridFieldsListener(gridId: gridId) { _fieldListener.start(onFieldsChanged: (result) { result.fold( (changeset) { @@ -186,11 +185,11 @@ class GridFieldCache { } } -class GridRowCacheDelegateImpl extends GridRowCacheDelegate { +class GridRowCacheFieldNotifierImpl extends GridRowCacheFieldNotifier { final GridFieldCache _cache; FieldChangesetCallback? _onChangesetFn; FieldsCallback? _onFieldFn; - GridRowCacheDelegateImpl(GridFieldCache cache) : _cache = cache; + GridRowCacheFieldNotifierImpl(GridFieldCache cache) : _cache = cache; @override UnmodifiableListView get fields => _cache.unmodifiableFields; diff --git a/frontend/app_flowy/lib/workspace/application/grid/row/row_action_sheet_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/row/row_action_sheet_bloc.dart index 8754917223..7d570c9412 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/row/row_action_sheet_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/row/row_action_sheet_bloc.dart @@ -15,7 +15,7 @@ class RowActionSheetBloc extends Bloc : _rowService = RowService( gridId: rowData.gridId, blockId: rowData.blockId, - rowId: rowData.rowId, + rowId: rowData.id, ), super(RowActionSheetState.initial(rowData)) { on( 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 5af7c02a5d..69d2a95059 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,19 +11,19 @@ part 'row_bloc.freezed.dart'; class RowBloc extends Bloc { final RowService _rowService; - final GridRowsCache _rowCache; + final GridRowCache _rowCache; void Function()? _rowListenFn; RowBloc({ required GridRow rowData, - required GridRowsCache rowCache, + required GridRowCache rowCache, }) : _rowService = RowService( gridId: rowData.gridId, blockId: rowData.blockId, - rowId: rowData.rowId, + rowId: rowData.id, ), _rowCache = rowCache, - super(RowState.initial(rowData, rowCache.loadGridCells(rowData.rowId))) { + super(RowState.initial(rowData, rowCache.loadGridCells(rowData.id))) { on( (event, emit) async { await event.map( @@ -58,7 +58,7 @@ class RowBloc extends Bloc { Future _startListening() async { _rowListenFn = _rowCache.addListener( - rowId: state.rowData.rowId, + rowId: state.rowData.id, 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 0613b388a4..b75caf32cf 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 GridRowsCache _rowCache; + final GridRowCache _rowCache; void Function()? _rowListenFn; RowDetailBloc({ required this.rowData, - required GridRowsCache rowCache, + required GridRowCache rowCache, }) : _rowCache = rowCache, super(RowDetailState.initial()) { on( @@ -41,14 +41,14 @@ class RowDetailBloc extends Bloc { Future _startListening() async { _rowListenFn = _rowCache.addListener( - rowId: rowData.rowId, + rowId: rowData.id, onCellUpdated: (cellDatas, reason) => add(RowDetailEvent.didReceiveCellDatas(cellDatas.values.toList())), listenWhen: () => !isClosed, ); } Future _loadCellData() async { - final cellDataMap = _rowCache.loadGridCells(rowData.rowId); + final cellDataMap = _rowCache.loadGridCells(rowData.id); if (!isClosed) { add(RowDetailEvent.didReceiveCellDatas(cellDataMap.values.toList())); } @@ -58,13 +58,13 @@ class RowDetailBloc extends Bloc { @freezed class RowDetailEvent with _$RowDetailEvent { const factory RowDetailEvent.initial() = _Initial; - const factory RowDetailEvent.didReceiveCellDatas(List gridCells) = _DidReceiveCellDatas; + const factory RowDetailEvent.didReceiveCellDatas(List gridCells) = _DidReceiveCellDatas; } @freezed class RowDetailState with _$RowDetailState { const factory RowDetailState({ - required List gridCells, + required List gridCells, }) = _RowDetailState; factory RowDetailState.initial() => RowDetailState( 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 27749083b4..7296fd51a8 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,42 +14,53 @@ part 'row_service.freezed.dart'; typedef RowUpdateCallback = void Function(); -abstract class GridRowCacheDelegate { +abstract class GridRowCacheFieldNotifier { UnmodifiableListView get fields; void onFieldsChanged(VoidCallback callback); void onFieldChanged(void Function(Field) callback); void dispose(); } -class GridRowsCache { +/// Cache the rows in memory +/// Insert / delete / update row +/// +/// Read https://appflowy.gitbook.io/docs/essential-documentation/contribute-to-appflowy/architecture/frontend/grid for more information. + +class GridRowCache { final String gridId; final GridBlock block; - final _Notifier _notifier; + + /// _rows containers the current block's rows + /// Use List to reverse the order of the GridRow. List _rows = []; + + /// Use Map for faster access the raw row data. final HashMap _rowByRowId; - final GridRowCacheDelegate _delegate; - final GridCellsCache _cellCache; - List get rows => _rows; - GridCellsCache get cellCache => _cellCache; + final GridCellCache _cellCache; + final GridRowCacheFieldNotifier _fieldNotifier; + final _GridRowChangesetNotifier _rowChangeReasonNotifier; - GridRowsCache({ + UnmodifiableListView get rows => UnmodifiableListView(_rows); + GridCellCache get cellCache => _cellCache; + + GridRowCache({ required this.gridId, required this.block, - required GridRowCacheDelegate delegate, - }) : _cellCache = GridCellsCache(gridId: gridId), + required GridRowCacheFieldNotifier notifier, + }) : _cellCache = GridCellCache(gridId: gridId), _rowByRowId = HashMap(), - _notifier = _Notifier(), - _delegate = delegate { + _rowChangeReasonNotifier = _GridRowChangesetNotifier(), + _fieldNotifier = notifier { // - delegate.onFieldsChanged(() => _notifier.receive(const GridRowChangeReason.fieldDidChange())); - delegate.onFieldChanged((field) => _cellCache.remove(field.id)); + notifier.onFieldsChanged(() => _rowChangeReasonNotifier.receive(const GridRowChangeReason.fieldDidChange())); + notifier.onFieldChanged((field) => _cellCache.remove(field.id)); _rows = block.rowInfos.map((rowInfo) => buildGridRow(rowInfo.rowId, rowInfo.height.toDouble())).toList(); } Future dispose() async { - _delegate.dispose(); - _notifier.dispose(); + _fieldNotifier.dispose(); + _rowChangeReasonNotifier.dispose(); await _cellCache.dispose(); } @@ -73,14 +84,15 @@ class GridRowsCache { final Map deletedRowByRowId = {for (var rowId in deletedRows) rowId: rowId}; _rows.asMap().forEach((index, row) { - if (deletedRowByRowId[row.rowId] == null) { + if (deletedRowByRowId[row.id] == null) { newRows.add(row); } else { + _rowByRowId.remove(row.id); deletedIndex.add(DeletedIndex(index: index, row: row)); } }); _rows = newRows; - _notifier.receive(GridRowChangeReason.delete(deletedIndex)); + _rowChangeReasonNotifier.receive(GridRowChangeReason.delete(deletedIndex)); } void _insertRows(List insertRows) { @@ -89,17 +101,16 @@ class GridRowsCache { } InsertedIndexs insertIndexs = []; - final List newRows = _rows; for (final insertRow in insertRows) { final insertIndex = InsertedIndex( index: insertRow.index, rowId: insertRow.rowId, ); insertIndexs.add(insertIndex); - newRows.insert(insertRow.index, (buildGridRow(insertRow.rowId, insertRow.height.toDouble()))); + _rows.insert(insertRow.index, (buildGridRow(insertRow.rowId, insertRow.height.toDouble()))); } - _notifier.receive(GridRowChangeReason.insert(insertIndexs)); + _rowChangeReasonNotifier.receive(GridRowChangeReason.insert(insertIndexs)); } void _updateRows(List updatedRows) { @@ -108,20 +119,19 @@ class GridRowsCache { } final UpdatedIndexs updatedIndexs = UpdatedIndexs(); - final List newRows = _rows; for (final updatedRow in updatedRows) { final rowId = updatedRow.rowId; - final index = newRows.indexWhere((row) => row.rowId == rowId); + final index = _rows.indexWhere((row) => row.id == rowId); if (index != -1) { _rowByRowId[rowId] = updatedRow.row; - newRows.removeAt(index); - newRows.insert(index, buildGridRow(rowId, updatedRow.row.height.toDouble())); + _rows.removeAt(index); + _rows.insert(index, buildGridRow(rowId, updatedRow.row.height.toDouble())); updatedIndexs[rowId] = UpdatedIndex(index: index, rowId: rowId); } } - _notifier.receive(GridRowChangeReason.update(updatedIndexs)); + _rowChangeReasonNotifier.receive(GridRowChangeReason.update(updatedIndexs)); } void _hideRows(List hideRows) {} @@ -131,8 +141,8 @@ class GridRowsCache { void onRowsChanged( void Function(GridRowChangeReason) onRowChanged, ) { - _notifier.addListener(() { - onRowChanged(_notifier._reason); + _rowChangeReasonNotifier.addListener(() { + onRowChanged(_rowChangeReasonNotifier.reason); }); } @@ -151,12 +161,12 @@ class GridRowsCache { final row = _rowByRowId[rowId]; if (row != null) { final GridCellMap cellDataMap = _makeGridCells(rowId, row); - onCellUpdated(cellDataMap, _notifier._reason); + onCellUpdated(cellDataMap, _rowChangeReasonNotifier.reason); } } } - _notifier._reason.whenOrNull( + _rowChangeReasonNotifier.reason.whenOrNull( update: (indexs) { if (indexs[rowId] != null) notifyUpdate(); }, @@ -164,12 +174,12 @@ class GridRowsCache { ); } - _notifier.addListener(listenrHandler); + _rowChangeReasonNotifier.addListener(listenrHandler); return listenrHandler; } void removeRowListener(VoidCallback callback) { - _notifier.removeListener(callback); + _rowChangeReasonNotifier.removeListener(callback); } GridCellMap loadGridCells(String rowId) { @@ -195,9 +205,9 @@ class GridRowsCache { GridCellMap _makeGridCells(String rowId, Row? row) { var cellDataMap = GridCellMap.new(); - for (final field in _delegate.fields) { + for (final field in _fieldNotifier.fields) { if (field.visibility) { - cellDataMap[field.id] = GridCell( + cellDataMap[field.id] = GridCellIdentifier( rowId: rowId, gridId: gridId, field: field, @@ -215,19 +225,19 @@ class GridRowsCache { updatedRow.freeze(); _rowByRowId[updatedRow.id] = updatedRow; - final index = _rows.indexWhere((gridRow) => gridRow.rowId == updatedRow.id); + final index = _rows.indexWhere((gridRow) => gridRow.id == 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); + if (_rows[index].rawRow != updatedRow) { + final row = _rows.removeAt(index).copyWith(rawRow: updatedRow); _rows.insert(index, row); // Calculate the update index final UpdatedIndexs updatedIndexs = UpdatedIndexs(); - updatedIndexs[row.rowId] = UpdatedIndex(index: index, rowId: row.rowId); + updatedIndexs[row.id] = UpdatedIndex(index: index, rowId: row.id); // - _notifier.receive(GridRowChangeReason.update(updatedIndexs)); + _rowChangeReasonNotifier.receive(GridRowChangeReason.update(updatedIndexs)); } } } @@ -236,20 +246,20 @@ class GridRowsCache { return GridRow( gridId: gridId, blockId: block.id, - fields: _delegate.fields, - rowId: rowId, + fields: _fieldNotifier.fields, + id: rowId, height: rowHeight, ); } } -class _Notifier extends ChangeNotifier { - GridRowChangeReason _reason = const InitialListState(); +class _GridRowChangesetNotifier extends ChangeNotifier { + GridRowChangeReason reason = const InitialListState(); - _Notifier(); + _GridRowChangesetNotifier(); - void receive(GridRowChangeReason reason) { - _reason = reason; + void receive(GridRowChangeReason newReason) { + reason = newReason; reason.map( insert: (_) => notifyListeners(), delete: (_) => notifyListeners(), @@ -319,10 +329,10 @@ class GridRow with _$GridRow { const factory GridRow({ required String gridId, required String blockId, - required String rowId, + required String id, required UnmodifiableListView fields, required double height, - Row? data, + Row? rawRow, }) = _GridRow; } 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 6b980e468f..d7d2999dd1 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 @@ -227,7 +227,7 @@ class _GridRowsState extends State<_GridRows> { GridRow rowData, Animation animation, ) { - final rowCache = context.read().getRowCache(rowData.blockId, rowData.rowId); + final rowCache = context.read().getRowCache(rowData.blockId, rowData.id); final fieldCache = context.read().fieldCache; if (rowCache != null) { return SizeTransition( @@ -236,7 +236,7 @@ class _GridRowsState extends State<_GridRows> { rowData: rowData, rowCache: rowCache, fieldCache: fieldCache, - key: ValueKey(rowData.rowId), + key: ValueKey(rowData.id), ), ); } else { 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 a17384dd59..10efcbb0de 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 @@ -14,23 +14,21 @@ import 'text_cell.dart'; import 'url_cell/url_cell.dart'; class GridCellBuilder { - final GridCellsCache cellCache; + final GridCellCache cellCache; final GridFieldCache fieldCache; GridCellBuilder({ required this.cellCache, required this.fieldCache, }); - GridCellWidget build(GridCell cell, {GridCellStyle? style}) { - final key = ValueKey(cell.cellId()); - + GridCellWidget build(GridCellIdentifier cell, {GridCellStyle? style}) { final cellControllerBuilder = GridCellControllerBuilder( - gridCell: cell, + cellId: cell, cellCache: cellCache, fieldCache: fieldCache, ); - - switch (cell.field.fieldType) { + final key = cell.key(); + switch (cell.fieldType) { case FieldType.Checkbox: return CheckboxCell(cellControllerBuilder: cellControllerBuilder, key: key); case FieldType.DateTime: diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell/date_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell/date_cell.dart index efcb107e55..7436d0ac7f 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell/date_cell.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell/date_cell.dart @@ -80,7 +80,7 @@ class _DateCellState extends GridCellState { final calendar = DateCellEditor(onDismissed: () => widget.onCellEditing.value = false); calendar.show( context, - cellContext: bloc.cellContext.clone(), + cellController: bloc.cellContext.clone(), ); } diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell/date_editor.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell/date_editor.dart index 5dcd552fef..36ad22ec53 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell/date_editor.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell/date_editor.dart @@ -31,16 +31,16 @@ class DateCellEditor with FlowyOverlayDelegate { Future show( BuildContext context, { - required GridDateCellController cellContext, + required GridDateCellController cellController, }) async { DateCellEditor.remove(context); - final result = await cellContext.getTypeOptionData(); + final result = await cellController.getFieldTypeOption(DateTypeOptionDataParser()); result.fold( - (data) { + (dateTypeOption) { final calendar = _CellCalendarWidget( - cellContext: cellContext, - dateTypeOption: DateTypeOption.fromBuffer(data.typeOptionData), + cellContext: cellController, + dateTypeOption: dateTypeOption, ); FlowyOverlay.of(context).insertWithAnchor( diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/url_cell/cell_editor.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/url_cell/cell_editor.dart index f756bceb32..b6bc8daa21 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/url_cell/cell_editor.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/url_cell/cell_editor.dart @@ -7,9 +7,9 @@ import 'dart:async'; import 'package:flutter_bloc/flutter_bloc.dart'; class URLCellEditor extends StatefulWidget with FlowyOverlayDelegate { - final GridURLCellController cellContext; + final GridURLCellController cellController; final VoidCallback completed; - const URLCellEditor({required this.cellContext, required this.completed, Key? key}) : super(key: key); + const URLCellEditor({required this.cellController, required this.completed, Key? key}) : super(key: key); @override State createState() => _URLCellEditorState(); @@ -21,7 +21,7 @@ class URLCellEditor extends StatefulWidget with FlowyOverlayDelegate { ) { FlowyOverlay.of(context).remove(identifier()); final editor = URLCellEditor( - cellContext: cellContext, + cellController: cellContext, completed: completed, ); @@ -62,7 +62,7 @@ class _URLCellEditorState extends State { @override void initState() { - _cellBloc = URLCellEditorBloc(cellContext: widget.cellContext); + _cellBloc = URLCellEditorBloc(cellContext: widget.cellController); _cellBloc.add(const URLCellEditorEvent.initial()); _controller = TextEditingController(text: _cellBloc.state.content); diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/url_cell/url_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/url_cell/url_cell.dart index 0edb65a7dc..9cc14fbbda 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/url_cell/url_cell.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/url_cell/url_cell.dart @@ -187,7 +187,7 @@ class _CopyURLAccessory extends StatelessWidget with GridCellAccessory { @override void onTap() { - final content = cellContext.getCellData(loadIfNoCache: false)?.content ?? ""; + final content = cellContext.getCellData(loadIfNotExist: false)?.content ?? ""; Clipboard.setData(ClipboardData(text: content)); showMessageToast(LocaleKeys.grid_row_copyProperty.tr()); } diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_cell.dart index 9237c71be0..2ace435bf3 100755 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_cell.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_cell.dart @@ -65,7 +65,7 @@ class GridFieldCell extends StatelessWidget { FieldEditor( gridId: state.gridId, fieldName: field.name, - contextLoader: FieldContextLoader( + contextLoader: FieldTypeOptionLoader( gridId: state.gridId, field: field, ), diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_editor.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_editor.dart index 148727752c..d3345b5b6f 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_editor.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_editor.dart @@ -14,7 +14,7 @@ class FieldEditor extends StatelessWidget with FlowyOverlayDelegate { final String gridId; final String fieldName; - final IFieldContextLoader contextLoader; + final IFieldTypeOptionLoader contextLoader; const FieldEditor({ required this.gridId, required this.fieldName, diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/grid_header.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/grid_header.dart index cc968d15cb..c63bd37d2e 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/grid_header.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/grid_header.dart @@ -151,7 +151,7 @@ class CreateFieldButton extends StatelessWidget { onTap: () => FieldEditor( gridId: gridId, fieldName: "", - contextLoader: NewFieldContextLoader(gridId: gridId), + contextLoader: NewFieldTypeOptionLoader(gridId: gridId), ).show(context), leftIcon: svgWidget("home/add"), ); diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/builder.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/builder.dart index 872a501f90..f63edbac14 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/builder.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/builder.dart @@ -51,13 +51,13 @@ TypeOptionWidgetBuilder makeTypeOptionWidgetBuilder( case FieldType.Checkbox: final context = CheckboxTypeOptionContext( fieldContext: fieldContext, - dataBuilder: CheckboxTypeOptionWidgetDataParser(), + dataParser: CheckboxTypeOptionWidgetDataParser(), ); return CheckboxTypeOptionWidgetBuilder(context); case FieldType.DateTime: final context = DateTypeOptionContext( fieldContext: fieldContext, - dataBuilder: DateTypeOptionDataParser(), + dataParser: DateTypeOptionDataParser(), ); return DateTypeOptionWidgetBuilder( context, @@ -84,7 +84,7 @@ TypeOptionWidgetBuilder makeTypeOptionWidgetBuilder( case FieldType.Number: final context = NumberTypeOptionContext( fieldContext: fieldContext, - dataBuilder: NumberTypeOptionWidgetDataParser(), + dataParser: NumberTypeOptionWidgetDataParser(), ); return NumberTypeOptionWidgetBuilder( context, @@ -93,14 +93,14 @@ TypeOptionWidgetBuilder makeTypeOptionWidgetBuilder( case FieldType.RichText: final context = RichTextTypeOptionContext( fieldContext: fieldContext, - dataBuilder: RichTextTypeOptionWidgetDataParser(), + dataParser: RichTextTypeOptionWidgetDataParser(), ); return RichTextTypeOptionWidgetBuilder(context); case FieldType.URL: final context = URLTypeOptionContext( fieldContext: fieldContext, - dataBuilder: URLTypeOptionWidgetDataParser(), + dataParser: URLTypeOptionWidgetDataParser(), ); return URLTypeOptionWidgetBuilder(context); } diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/checkbox.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/checkbox.dart index 79622a6731..beca8acd09 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/checkbox.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/checkbox.dart @@ -5,7 +5,7 @@ import 'builder.dart'; typedef CheckboxTypeOptionContext = TypeOptionWidgetContext; -class CheckboxTypeOptionWidgetDataParser extends TypeOptionWidgetDataParser { +class CheckboxTypeOptionWidgetDataParser extends TypeOptionDataParser { @override CheckboxTypeOption fromBuffer(List buffer) { return CheckboxTypeOption.fromBuffer(buffer); diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/rich_text.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/rich_text.dart index f79fb2dd9d..2375918f11 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/rich_text.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/rich_text.dart @@ -5,7 +5,7 @@ import 'builder.dart'; typedef RichTextTypeOptionContext = TypeOptionWidgetContext; -class RichTextTypeOptionWidgetDataParser extends TypeOptionWidgetDataParser { +class RichTextTypeOptionWidgetDataParser extends TypeOptionDataParser { @override RichTextTypeOption fromBuffer(List buffer) { return RichTextTypeOption.fromBuffer(buffer); diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/url.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/url.dart index a55711bd05..97c0db0814 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/url.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/url.dart @@ -5,7 +5,7 @@ import 'builder.dart'; typedef URLTypeOptionContext = TypeOptionWidgetContext; -class URLTypeOptionWidgetDataParser extends TypeOptionWidgetDataParser { +class URLTypeOptionWidgetDataParser extends TypeOptionDataParser { @override URLTypeOption fromBuffer(List buffer) { return URLTypeOption.fromBuffer(buffer); 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 0e1c6a0041..b0c1cd2ea0 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,7 +16,7 @@ import 'row_detail.dart'; class GridRowWidget extends StatefulWidget { final GridRow rowData; - final GridRowsCache rowCache; + final GridRowCache rowCache; final GridCellBuilder cellBuilder; GridRowWidget({ @@ -183,12 +183,12 @@ class _RowCells extends StatelessWidget { List _makeCells(BuildContext context, GridCellMap gridCellMap) { return gridCellMap.values.map( - (gridCell) { - final GridCellWidget child = builder.build(gridCell); + (cellId) { + final GridCellWidget child = builder.build(cellId); accessoryBuilder(GridCellAccessoryBuildContext buildContext) { final builder = child.accessoryBuilder; List accessories = []; - if (gridCell.field.isPrimary) { + if (cellId.field.isPrimary) { accessories.add(PrimaryCellAccessory( onTapCallback: onExpand, isCellEditing: buildContext.isCellEditing, @@ -202,7 +202,7 @@ class _RowCells extends StatelessWidget { } return CellContainer( - width: gridCell.field.width.toDouble(), + width: cellId.field.width.toDouble(), child: child, rowStateNotifier: Provider.of(context, listen: false), accessoryBuilder: accessoryBuilder, 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 45dd63eb7f..b10eeecb2c 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 @@ -22,7 +22,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; class RowDetailPage extends StatefulWidget with FlowyOverlayDelegate { final GridRow rowData; - final GridRowsCache rowCache; + final GridRowCache rowCache; final GridCellBuilder cellBuilder; const RowDetailPage({ @@ -122,7 +122,7 @@ class _PropertyList extends StatelessWidget { itemCount: state.gridCells.length, itemBuilder: (BuildContext context, int index) { return _RowDetailCell( - gridCell: state.gridCells[index], + cellId: state.gridCells[index], cellBuilder: cellBuilder, ); }, @@ -137,10 +137,10 @@ class _PropertyList extends StatelessWidget { } class _RowDetailCell extends StatelessWidget { - final GridCell gridCell; + final GridCellIdentifier cellId; final GridCellBuilder cellBuilder; const _RowDetailCell({ - required this.gridCell, + required this.cellId, required this.cellBuilder, Key? key, }) : super(key: key); @@ -148,8 +148,8 @@ class _RowDetailCell extends StatelessWidget { @override Widget build(BuildContext context) { final theme = context.watch(); - final style = _customCellStyle(theme, gridCell.field.fieldType); - final cell = cellBuilder.build(gridCell, style: style); + final style = _customCellStyle(theme, cellId.fieldType); + final cell = cellBuilder.build(cellId, style: style); final gesture = GestureDetector( behavior: HitTestBehavior.translucent, @@ -169,7 +169,7 @@ class _RowDetailCell extends StatelessWidget { children: [ SizedBox( width: 150, - child: FieldCellButton(field: gridCell.field, onTap: () => _showFieldEditor(context)), + child: FieldCellButton(field: cellId.field, onTap: () => _showFieldEditor(context)), ), const HSpace(10), Expanded(child: gesture), @@ -181,11 +181,11 @@ class _RowDetailCell extends StatelessWidget { void _showFieldEditor(BuildContext context) { FieldEditor( - gridId: gridCell.gridId, - fieldName: gridCell.field.name, - contextLoader: FieldContextLoader( - gridId: gridCell.gridId, - field: gridCell.field, + gridId: cellId.gridId, + fieldName: cellId.field.name, + contextLoader: FieldTypeOptionLoader( + gridId: cellId.gridId, + field: cellId.field, ), ).show(context); } diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/toolbar/grid_property.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/toolbar/grid_property.dart index e19c90ecea..ac28ce9dbc 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/toolbar/grid_property.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/toolbar/grid_property.dart @@ -116,7 +116,7 @@ class _GridPropertyCell extends StatelessWidget { FieldEditor( gridId: gridId, fieldName: field.name, - contextLoader: FieldContextLoader(gridId: gridId, field: field), + contextLoader: FieldTypeOptionLoader(gridId: gridId, field: field), ).show(context, anchorDirection: AnchorDirection.bottomRight); }, );