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

refactor: grid block cache
This commit is contained in:
Nathan.fooo 2022-07-03 16:50:17 +08:00 committed by GitHub
commit 6fc7f63a8b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 376 additions and 373 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -12,7 +12,7 @@ import 'select_option_cell/select_option_cell.dart';
import 'text_cell.dart'; import 'text_cell.dart';
import 'url_cell/url_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 key = ValueKey(gridCell.cellId());
final cellContextBuilder = GridCellContextBuilder(gridCell: gridCell, cellCache: cellCache); final cellContextBuilder = GridCellContextBuilder(gridCell: gridCell, cellCache: cellCache);

View File

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

View File

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

View File

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