|
@@ -1,283 +1,9 @@
|
|
|
-import 'dart:collection';
|
|
|
-import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
|
|
import 'package:dartz/dartz.dart';
|
|
|
import 'package:flowy_sdk/dispatch/dispatch.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/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 'package:freezed_annotation/freezed_annotation.dart';
|
|
|
-part 'row_service.freezed.dart';
|
|
|
-
|
|
|
-typedef RowUpdateCallback = void Function();
|
|
|
-
|
|
|
-abstract class GridRowCacheFieldNotifier {
|
|
|
- UnmodifiableListView<GridFieldPB> get fields;
|
|
|
- void onFieldsChanged(VoidCallback callback);
|
|
|
- void onFieldChanged(void Function(GridFieldPB) callback);
|
|
|
- void dispose();
|
|
|
-}
|
|
|
-
|
|
|
-/// Cache the rows in memory
|
|
|
-/// Insert / delete / update row
|
|
|
-///
|
|
|
-/// Read https://appflowy.gitbook.io/docs/essential-documentation/contribute-to-appflowy/architecture/frontend/grid for more information.
|
|
|
-
|
|
|
-class GridRowCache {
|
|
|
- final String gridId;
|
|
|
- final GridBlockPB block;
|
|
|
-
|
|
|
- /// _rows containers the current block's rows
|
|
|
- /// Use List to reverse the order of the GridRow.
|
|
|
- List<GridRowInfo> _rowInfos = [];
|
|
|
-
|
|
|
- /// Use Map for faster access the raw row data.
|
|
|
- final HashMap<String, GridRowPB> _rowByRowId;
|
|
|
-
|
|
|
- final GridCellCache _cellCache;
|
|
|
- final GridRowCacheFieldNotifier _fieldNotifier;
|
|
|
- final _GridRowChangesetNotifier _rowChangeReasonNotifier;
|
|
|
-
|
|
|
- UnmodifiableListView<GridRowInfo> get rows => UnmodifiableListView(_rowInfos);
|
|
|
- GridCellCache get cellCache => _cellCache;
|
|
|
-
|
|
|
- GridRowCache({
|
|
|
- required this.gridId,
|
|
|
- required this.block,
|
|
|
- required GridRowCacheFieldNotifier notifier,
|
|
|
- }) : _cellCache = GridCellCache(gridId: gridId),
|
|
|
- _rowByRowId = HashMap(),
|
|
|
- _rowChangeReasonNotifier = _GridRowChangesetNotifier(),
|
|
|
- _fieldNotifier = notifier {
|
|
|
- //
|
|
|
- notifier.onFieldsChanged(() => _rowChangeReasonNotifier
|
|
|
- .receive(const GridRowChangeReason.fieldDidChange()));
|
|
|
- notifier.onFieldChanged((field) => _cellCache.remove(field.id));
|
|
|
- _rowInfos = block.rows
|
|
|
- .map((rowInfo) => buildGridRow(rowInfo.id, rowInfo.height.toDouble()))
|
|
|
- .toList();
|
|
|
- }
|
|
|
-
|
|
|
- Future<void> dispose() async {
|
|
|
- _fieldNotifier.dispose();
|
|
|
- _rowChangeReasonNotifier.dispose();
|
|
|
- await _cellCache.dispose();
|
|
|
- }
|
|
|
-
|
|
|
- void applyChangesets(List<GridBlockChangesetPB> changesets) {
|
|
|
- for (final changeset in changesets) {
|
|
|
- _deleteRows(changeset.deletedRows);
|
|
|
- _insertRows(changeset.insertedRows);
|
|
|
- _updateRows(changeset.updatedRows);
|
|
|
- _hideRows(changeset.hideRows);
|
|
|
- _showRows(changeset.visibleRows);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- void _deleteRows(List<String> deletedRows) {
|
|
|
- if (deletedRows.isEmpty) {
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- final List<GridRowInfo> newRows = [];
|
|
|
- final DeletedIndexs deletedIndex = [];
|
|
|
- final Map<String, String> deletedRowByRowId = {
|
|
|
- for (var rowId in deletedRows) rowId: rowId
|
|
|
- };
|
|
|
-
|
|
|
- _rowInfos.asMap().forEach((index, row) {
|
|
|
- if (deletedRowByRowId[row.id] == null) {
|
|
|
- newRows.add(row);
|
|
|
- } else {
|
|
|
- _rowByRowId.remove(row.id);
|
|
|
- deletedIndex.add(DeletedIndex(index: index, row: row));
|
|
|
- }
|
|
|
- });
|
|
|
- _rowInfos = newRows;
|
|
|
- _rowChangeReasonNotifier.receive(GridRowChangeReason.delete(deletedIndex));
|
|
|
- }
|
|
|
-
|
|
|
- void _insertRows(List<InsertedRowPB> insertRows) {
|
|
|
- if (insertRows.isEmpty) {
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- InsertedIndexs insertIndexs = [];
|
|
|
- for (final insertRow in insertRows) {
|
|
|
- final insertIndex = InsertedIndex(
|
|
|
- index: insertRow.index,
|
|
|
- rowId: insertRow.rowId,
|
|
|
- );
|
|
|
- insertIndexs.add(insertIndex);
|
|
|
- _rowInfos.insert(insertRow.index,
|
|
|
- (buildGridRow(insertRow.rowId, insertRow.height.toDouble())));
|
|
|
- }
|
|
|
-
|
|
|
- _rowChangeReasonNotifier.receive(GridRowChangeReason.insert(insertIndexs));
|
|
|
- }
|
|
|
-
|
|
|
- void _updateRows(List<UpdatedRowPB> updatedRows) {
|
|
|
- if (updatedRows.isEmpty) {
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- final UpdatedIndexs updatedIndexs = UpdatedIndexs();
|
|
|
- for (final updatedRow in updatedRows) {
|
|
|
- final rowId = updatedRow.rowId;
|
|
|
- final index = _rowInfos.indexWhere((row) => row.id == rowId);
|
|
|
- if (index != -1) {
|
|
|
- _rowByRowId[rowId] = updatedRow.row;
|
|
|
-
|
|
|
- _rowInfos.removeAt(index);
|
|
|
- _rowInfos.insert(
|
|
|
- index, buildGridRow(rowId, updatedRow.row.height.toDouble()));
|
|
|
- updatedIndexs[rowId] = UpdatedIndex(index: index, rowId: rowId);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- _rowChangeReasonNotifier.receive(GridRowChangeReason.update(updatedIndexs));
|
|
|
- }
|
|
|
-
|
|
|
- void _hideRows(List<String> hideRows) {}
|
|
|
-
|
|
|
- void _showRows(List<String> visibleRows) {}
|
|
|
-
|
|
|
- void onRowsChanged(
|
|
|
- void Function(GridRowChangeReason) onRowChanged,
|
|
|
- ) {
|
|
|
- _rowChangeReasonNotifier.addListener(() {
|
|
|
- onRowChanged(_rowChangeReasonNotifier.reason);
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- RowUpdateCallback addListener({
|
|
|
- required String rowId,
|
|
|
- void Function(GridCellMap, GridRowChangeReason)? onCellUpdated,
|
|
|
- bool Function()? listenWhen,
|
|
|
- }) {
|
|
|
- listenerHandler() async {
|
|
|
- if (listenWhen != null && listenWhen() == false) {
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- notifyUpdate() {
|
|
|
- if (onCellUpdated != null) {
|
|
|
- final row = _rowByRowId[rowId];
|
|
|
- if (row != null) {
|
|
|
- final GridCellMap cellDataMap = _makeGridCells(rowId, row);
|
|
|
- onCellUpdated(cellDataMap, _rowChangeReasonNotifier.reason);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- _rowChangeReasonNotifier.reason.whenOrNull(
|
|
|
- update: (indexs) {
|
|
|
- if (indexs[rowId] != null) notifyUpdate();
|
|
|
- },
|
|
|
- fieldDidChange: () => notifyUpdate(),
|
|
|
- );
|
|
|
- }
|
|
|
-
|
|
|
- _rowChangeReasonNotifier.addListener(listenerHandler);
|
|
|
- return listenerHandler;
|
|
|
- }
|
|
|
-
|
|
|
- void removeRowListener(VoidCallback callback) {
|
|
|
- _rowChangeReasonNotifier.removeListener(callback);
|
|
|
- }
|
|
|
-
|
|
|
- GridCellMap loadGridCells(String rowId) {
|
|
|
- final GridRowPB? data = _rowByRowId[rowId];
|
|
|
- if (data == null) {
|
|
|
- _loadRow(rowId);
|
|
|
- }
|
|
|
- return _makeGridCells(rowId, data);
|
|
|
- }
|
|
|
-
|
|
|
- Future<void> _loadRow(String rowId) async {
|
|
|
- final payload = GridRowIdPB.create()
|
|
|
- ..gridId = gridId
|
|
|
- ..blockId = block.id
|
|
|
- ..rowId = rowId;
|
|
|
-
|
|
|
- final result = await GridEventGetRow(payload).send();
|
|
|
- result.fold(
|
|
|
- (optionRow) => _refreshRow(optionRow),
|
|
|
- (err) => Log.error(err),
|
|
|
- );
|
|
|
- }
|
|
|
-
|
|
|
- GridCellMap _makeGridCells(String rowId, GridRowPB? row) {
|
|
|
- var cellDataMap = GridCellMap.new();
|
|
|
- for (final field in _fieldNotifier.fields) {
|
|
|
- if (field.visibility) {
|
|
|
- cellDataMap[field.id] = GridCellIdentifier(
|
|
|
- rowId: rowId,
|
|
|
- gridId: gridId,
|
|
|
- field: field,
|
|
|
- );
|
|
|
- }
|
|
|
- }
|
|
|
- return cellDataMap;
|
|
|
- }
|
|
|
-
|
|
|
- void _refreshRow(OptionalRowPB optionRow) {
|
|
|
- if (!optionRow.hasRow()) {
|
|
|
- return;
|
|
|
- }
|
|
|
- final updatedRow = optionRow.row;
|
|
|
- updatedRow.freeze();
|
|
|
-
|
|
|
- _rowByRowId[updatedRow.id] = updatedRow;
|
|
|
- final index =
|
|
|
- _rowInfos.indexWhere((gridRow) => gridRow.id == updatedRow.id);
|
|
|
- if (index != -1) {
|
|
|
- // update the corresponding row in _rows if they are not the same
|
|
|
- if (_rowInfos[index].rawRow != updatedRow) {
|
|
|
- final row = _rowInfos.removeAt(index).copyWith(rawRow: updatedRow);
|
|
|
- _rowInfos.insert(index, row);
|
|
|
-
|
|
|
- // Calculate the update index
|
|
|
- final UpdatedIndexs updatedIndexs = UpdatedIndexs();
|
|
|
- updatedIndexs[row.id] = UpdatedIndex(index: index, rowId: row.id);
|
|
|
-
|
|
|
- //
|
|
|
- _rowChangeReasonNotifier
|
|
|
- .receive(GridRowChangeReason.update(updatedIndexs));
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- GridRowInfo buildGridRow(String rowId, double rowHeight) {
|
|
|
- return GridRowInfo(
|
|
|
- gridId: gridId,
|
|
|
- blockId: block.id,
|
|
|
- fields: _fieldNotifier.fields,
|
|
|
- id: rowId,
|
|
|
- height: rowHeight,
|
|
|
- );
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-class _GridRowChangesetNotifier extends ChangeNotifier {
|
|
|
- GridRowChangeReason reason = const InitialListState();
|
|
|
-
|
|
|
- _GridRowChangesetNotifier();
|
|
|
-
|
|
|
- void receive(GridRowChangeReason newReason) {
|
|
|
- reason = newReason;
|
|
|
- reason.map(
|
|
|
- insert: (_) => notifyListeners(),
|
|
|
- delete: (_) => notifyListeners(),
|
|
|
- update: (_) => notifyListeners(),
|
|
|
- fieldDidChange: (_) => notifyListeners(),
|
|
|
- initial: (_) {},
|
|
|
- );
|
|
|
- }
|
|
|
-}
|
|
|
|
|
|
class RowService {
|
|
|
final String gridId;
|
|
@@ -334,55 +60,3 @@ class RowService {
|
|
|
return GridEventDuplicateRow(payload).send();
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
-@freezed
|
|
|
-class GridRowInfo with _$GridRowInfo {
|
|
|
- const factory GridRowInfo({
|
|
|
- required String gridId,
|
|
|
- required String blockId,
|
|
|
- required String id,
|
|
|
- required UnmodifiableListView<GridFieldPB> fields,
|
|
|
- required double height,
|
|
|
- GridRowPB? rawRow,
|
|
|
- }) = _GridRowInfo;
|
|
|
-}
|
|
|
-
|
|
|
-typedef InsertedIndexs = List<InsertedIndex>;
|
|
|
-typedef DeletedIndexs = List<DeletedIndex>;
|
|
|
-typedef UpdatedIndexs = LinkedHashMap<String, UpdatedIndex>;
|
|
|
-
|
|
|
-@freezed
|
|
|
-class GridRowChangeReason with _$GridRowChangeReason {
|
|
|
- const factory GridRowChangeReason.insert(InsertedIndexs items) = _Insert;
|
|
|
- const factory GridRowChangeReason.delete(DeletedIndexs items) = _Delete;
|
|
|
- const factory GridRowChangeReason.update(UpdatedIndexs indexs) = _Update;
|
|
|
- const factory GridRowChangeReason.fieldDidChange() = _FieldDidChange;
|
|
|
- const factory GridRowChangeReason.initial() = InitialListState;
|
|
|
-}
|
|
|
-
|
|
|
-class InsertedIndex {
|
|
|
- final int index;
|
|
|
- final String rowId;
|
|
|
- InsertedIndex({
|
|
|
- required this.index,
|
|
|
- required this.rowId,
|
|
|
- });
|
|
|
-}
|
|
|
-
|
|
|
-class DeletedIndex {
|
|
|
- final int index;
|
|
|
- final GridRowInfo row;
|
|
|
- DeletedIndex({
|
|
|
- required this.index,
|
|
|
- required this.row,
|
|
|
- });
|
|
|
-}
|
|
|
-
|
|
|
-class UpdatedIndex {
|
|
|
- final int index;
|
|
|
- final String rowId;
|
|
|
- UpdatedIndex({
|
|
|
- required this.index,
|
|
|
- required this.rowId,
|
|
|
- });
|
|
|
-}
|