| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308 | import 'dart:collection';import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';import 'package:app_flowy/plugins/grid/application/field/field_controller.dart';import 'package:flowy_sdk/dispatch/dispatch.dart';import 'package:flowy_sdk/log.dart';import 'package:flowy_sdk/protobuf/flowy-grid/block_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';import 'row_list.dart';part 'row_cache.freezed.dart';typedef RowUpdateCallback = void Function();abstract class IGridRowFieldNotifier {  UnmodifiableListView<FieldInfo> get fields;  void onRowFieldsChanged(VoidCallback callback);  void onRowFieldChanged(void Function(FieldInfo) callback);  void onRowDispose();}/// 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 BlockPB block;  /// _rows containers the current block's rows  /// Use List to reverse the order of the GridRow.  final RowList _rowList = RowList();  final GridCellCache _cellCache;  final IGridRowFieldNotifier _fieldNotifier;  final _RowChangesetNotifier _rowChangeReasonNotifier;  UnmodifiableListView<RowInfo> get visibleRows {    var visibleRows = [..._rowList.rows];    return UnmodifiableListView(visibleRows);  }  GridCellCache get cellCache => _cellCache;  GridRowCache({    required this.gridId,    required this.block,    required IGridRowFieldNotifier notifier,  })  : _cellCache = GridCellCache(gridId: gridId),        _rowChangeReasonNotifier = _RowChangesetNotifier(),        _fieldNotifier = notifier {    //    notifier.onRowFieldsChanged(() => _rowChangeReasonNotifier        .receive(const RowsChangedReason.fieldDidChange()));    notifier.onRowFieldChanged(        (field) => _cellCache.removeCellWithFieldId(field.id));    for (final row in block.rows) {      final rowInfo = buildGridRow(row);      _rowList.add(rowInfo);    }  }  Future<void> dispose() async {    _fieldNotifier.onRowDispose();    _rowChangeReasonNotifier.dispose();    await _cellCache.dispose();  }  void applyChangesets(GridBlockChangesetPB changeset) {    _deleteRows(changeset.deletedRows);    _insertRows(changeset.insertedRows);    _updateRows(changeset.updatedRows);    _hideRows(changeset.invisibleRows);    _showRows(changeset.visibleRows);  }  void _deleteRows(List<String> deletedRowIds) {    for (final rowId in deletedRowIds) {      final deletedRow = _rowList.remove(rowId);      if (deletedRow != null) {        _rowChangeReasonNotifier.receive(RowsChangedReason.delete(deletedRow));      }    }  }  void _insertRows(List<InsertedRowPB> insertRows) {    for (final insertedRow in insertRows) {      final insertedIndex =          _rowList.insert(insertedRow.index, buildGridRow(insertedRow.row));      if (insertedIndex != null) {        _rowChangeReasonNotifier            .receive(RowsChangedReason.insert(insertedIndex));      }    }  }  void _updateRows(List<RowPB> updatedRows) {    if (updatedRows.isEmpty) return;    final updatedIndexs =        _rowList.updateRows(updatedRows, (rowPB) => buildGridRow(rowPB));    if (updatedIndexs.isNotEmpty) {      _rowChangeReasonNotifier.receive(RowsChangedReason.update(updatedIndexs));    }  }  void _hideRows(List<String> invisibleRows) {    for (final rowId in invisibleRows) {      final deletedRow = _rowList.remove(rowId);      if (deletedRow != null) {        _rowChangeReasonNotifier.receive(RowsChangedReason.delete(deletedRow));      }    }  }  void _showRows(List<InsertedRowPB> visibleRows) {    for (final insertedRow in visibleRows) {      final insertedIndex =          _rowList.insert(insertedRow.index, buildGridRow(insertedRow.row));      if (insertedIndex != null) {        _rowChangeReasonNotifier            .receive(RowsChangedReason.insert(insertedIndex));      }    }  }  void onRowsChanged(void Function(RowsChangedReason) onRowChanged) {    _rowChangeReasonNotifier.addListener(() {      onRowChanged(_rowChangeReasonNotifier.reason);    });  }  RowUpdateCallback addListener({    required String rowId,    void Function(GridCellMap, RowsChangedReason)? onCellUpdated,    bool Function()? listenWhen,  }) {    listenerHandler() async {      if (listenWhen != null && listenWhen() == false) {        return;      }      notifyUpdate() {        if (onCellUpdated != null) {          final rowInfo = _rowList.get(rowId);          if (rowInfo != null) {            final GridCellMap cellDataMap =                _makeGridCells(rowId, rowInfo.rowPB);            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 RowPB? data = _rowList.get(rowId)?.rowPB;    if (data == null) {      _loadRow(rowId);    }    return _makeGridCells(rowId, data);  }  Future<void> _loadRow(String rowId) async {    final payload = RowIdPB.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, RowPB? row) {    // ignore: prefer_collection_literals    var cellDataMap = GridCellMap();    for (final field in _fieldNotifier.fields) {      if (field.visibility) {        cellDataMap[field.id] = GridCellIdentifier(          rowId: rowId,          gridId: gridId,          fieldInfo: field,        );      }    }    return cellDataMap;  }  void _refreshRow(OptionalRowPB optionRow) {    if (!optionRow.hasRow()) {      return;    }    final updatedRow = optionRow.row;    updatedRow.freeze();    final rowInfo = _rowList.get(updatedRow.id);    final rowIndex = _rowList.indexOfRow(updatedRow.id);    if (rowInfo != null && rowIndex != null) {      final updatedRowInfo = rowInfo.copyWith(rowPB: updatedRow);      _rowList.remove(updatedRow.id);      _rowList.insert(rowIndex, updatedRowInfo);      final UpdatedIndexMap updatedIndexs = UpdatedIndexMap();      updatedIndexs[rowInfo.rowPB.id] = UpdatedIndex(        index: rowIndex,        rowId: updatedRowInfo.rowPB.id,      );      _rowChangeReasonNotifier.receive(RowsChangedReason.update(updatedIndexs));    }  }  RowInfo buildGridRow(RowPB rowPB) {    return RowInfo(      gridId: gridId,      fields: _fieldNotifier.fields,      rowPB: rowPB,    );  }}class _RowChangesetNotifier extends ChangeNotifier {  RowsChangedReason reason = const InitialListState();  _RowChangesetNotifier();  void receive(RowsChangedReason newReason) {    reason = newReason;    reason.map(      insert: (_) => notifyListeners(),      delete: (_) => notifyListeners(),      update: (_) => notifyListeners(),      fieldDidChange: (_) => notifyListeners(),      initial: (_) {},    );  }}@unfreezedclass RowInfo with _$RowInfo {  factory RowInfo({    required String gridId,    required UnmodifiableListView<FieldInfo> fields,    required RowPB rowPB,  }) = _RowInfo;}typedef InsertedIndexs = List<InsertedIndex>;typedef DeletedIndexs = List<DeletedIndex>;// key: id of the row// value: UpdatedIndextypedef UpdatedIndexMap = LinkedHashMap<String, UpdatedIndex>;@freezedclass RowsChangedReason with _$RowsChangedReason {  const factory RowsChangedReason.insert(InsertedIndex item) = _Insert;  const factory RowsChangedReason.delete(DeletedIndex item) = _Delete;  const factory RowsChangedReason.update(UpdatedIndexMap indexs) = _Update;  const factory RowsChangedReason.fieldDidChange() = _FieldDidChange;  const factory RowsChangedReason.initial() = InitialListState;}class InsertedIndex {  final int index;  final String rowId;  InsertedIndex({    required this.index,    required this.rowId,  });}class DeletedIndex {  final int index;  final RowInfo rowInfo;  DeletedIndex({    required this.index,    required this.rowInfo,  });}class UpdatedIndex {  final int index;  final String rowId;  UpdatedIndex({    required this.index,    required this.rowId,  });}
 |