123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388 |
- 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;
- final String blockId;
- final String rowId;
- RowService(
- {required this.gridId, required this.blockId, required this.rowId});
- Future<Either<GridRowPB, FlowyError>> createRow() {
- CreateRowPayloadPB payload = CreateRowPayloadPB.create()
- ..gridId = gridId
- ..startRowId = rowId;
- return GridEventCreateRow(payload).send();
- }
- Future<Either<Unit, FlowyError>> moveRow(
- String rowId, int fromIndex, int toIndex) {
- final payload = MoveItemPayloadPB.create()
- ..gridId = gridId
- ..itemId = rowId
- ..ty = MoveItemTypePB.MoveRow
- ..fromIndex = fromIndex
- ..toIndex = toIndex;
- return GridEventMoveItem(payload).send();
- }
- Future<Either<OptionalRowPB, FlowyError>> getRow() {
- final payload = GridRowIdPB.create()
- ..gridId = gridId
- ..blockId = blockId
- ..rowId = rowId;
- return GridEventGetRow(payload).send();
- }
- Future<Either<Unit, FlowyError>> deleteRow() {
- final payload = GridRowIdPB.create()
- ..gridId = gridId
- ..blockId = blockId
- ..rowId = rowId;
- return GridEventDeleteRow(payload).send();
- }
- Future<Either<Unit, FlowyError>> duplicateRow() {
- final payload = GridRowIdPB.create()
- ..gridId = gridId
- ..blockId = blockId
- ..rowId = rowId;
- 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,
- });
- }
|