mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
Merge pull request #584 from AppFlowy-IO/refactor/grid_block_cache
refactor: grid block cache
This commit is contained in:
commit
6fc7f63a8b
@ -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) {
|
||||||
|
@ -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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
@ -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(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
if (listenWhen != null && listenWhen() == false) {
|
bool Function()? listenWhen,
|
||||||
return;
|
}) {
|
||||||
|
if (onChangeset != null) {
|
||||||
|
fn(c) {
|
||||||
|
if (listenWhen != null && listenWhen() == false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
onChangeset(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (onChanged != null) {
|
_changesetCallbackMap[onChangeset] = fn;
|
||||||
onChanged(fields);
|
}
|
||||||
|
|
||||||
|
if (onFields != null) {
|
||||||
|
fn() {
|
||||||
|
if (listenWhen != null && listenWhen() == false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
onFields(fields);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (listener != null) {
|
_fieldsCallbackMap[onFields] = fn;
|
||||||
listener();
|
_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);
|
if (onChangsetListener != null) {
|
||||||
return f;
|
_changesetCallbackMap.remove(onChangsetListener);
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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,
|
return;
|
||||||
}) {
|
}
|
||||||
_rowsNotifier.addListener(() {
|
|
||||||
if (onChanged == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (listenWhen != null && listenWhen() == false) {
|
final List<GridRow> newRows = [];
|
||||||
return;
|
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,
|
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) {
|
||||||
if (row != null) {
|
final row = _rowByRowId[rowId];
|
||||||
final GridCellMap cellDataMap = _makeGridCells(rowId, row);
|
if (row != null) {
|
||||||
onUpdated(cellDataMap, _rowsNotifier._changeReason);
|
final GridCellMap cellDataMap = _makeGridCells(rowId, row);
|
||||||
|
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 {
|
||||||
|
@ -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,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
@ -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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user