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

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

View File

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

View File

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

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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