row_cache.dart 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. import 'dart:collection';
  2. import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
  3. import 'package:flowy_sdk/dispatch/dispatch.dart';
  4. import 'package:flowy_sdk/log.dart';
  5. import 'package:flowy_sdk/protobuf/flowy-grid/block_entities.pb.dart';
  6. import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
  7. import 'package:flowy_sdk/protobuf/flowy-grid/row_entities.pb.dart';
  8. import 'package:flutter/foundation.dart';
  9. import 'package:freezed_annotation/freezed_annotation.dart';
  10. part 'row_cache.freezed.dart';
  11. typedef RowUpdateCallback = void Function();
  12. abstract class IGridRowFieldNotifier {
  13. UnmodifiableListView<FieldPB> get fields;
  14. void onRowFieldsChanged(VoidCallback callback);
  15. void onRowFieldChanged(void Function(FieldPB) callback);
  16. void onRowDispose();
  17. }
  18. /// Cache the rows in memory
  19. /// Insert / delete / update row
  20. ///
  21. /// Read https://appflowy.gitbook.io/docs/essential-documentation/contribute-to-appflowy/architecture/frontend/grid for more information.
  22. class GridRowCache {
  23. final String gridId;
  24. final BlockPB block;
  25. /// _rows containers the current block's rows
  26. /// Use List to reverse the order of the GridRow.
  27. List<RowInfo> _rowInfos = [];
  28. /// Use Map for faster access the raw row data.
  29. final HashMap<String, RowPB> _rowByRowId;
  30. final GridCellCache _cellCache;
  31. final IGridRowFieldNotifier _fieldNotifier;
  32. final _RowChangesetNotifier _rowChangeReasonNotifier;
  33. UnmodifiableListView<RowInfo> get rows => UnmodifiableListView(_rowInfos);
  34. GridCellCache get cellCache => _cellCache;
  35. GridRowCache({
  36. required this.gridId,
  37. required this.block,
  38. required IGridRowFieldNotifier notifier,
  39. }) : _cellCache = GridCellCache(gridId: gridId),
  40. _rowByRowId = HashMap(),
  41. _rowChangeReasonNotifier = _RowChangesetNotifier(),
  42. _fieldNotifier = notifier {
  43. //
  44. notifier.onRowFieldsChanged(() => _rowChangeReasonNotifier
  45. .receive(const RowChangeReason.fieldDidChange()));
  46. notifier.onRowFieldChanged((field) => _cellCache.remove(field.id));
  47. _rowInfos = block.rows
  48. .map((rowInfo) => buildGridRow(rowInfo.id, rowInfo.height.toDouble()))
  49. .toList();
  50. }
  51. Future<void> dispose() async {
  52. _fieldNotifier.onRowDispose();
  53. _rowChangeReasonNotifier.dispose();
  54. await _cellCache.dispose();
  55. }
  56. void applyChangesets(List<GridBlockChangesetPB> changesets) {
  57. for (final changeset in changesets) {
  58. _deleteRows(changeset.deletedRows);
  59. _insertRows(changeset.insertedRows);
  60. _updateRows(changeset.updatedRows);
  61. _hideRows(changeset.hideRows);
  62. _showRows(changeset.visibleRows);
  63. }
  64. }
  65. void _deleteRows(List<String> deletedRows) {
  66. if (deletedRows.isEmpty) {
  67. return;
  68. }
  69. final List<RowInfo> newRows = [];
  70. final DeletedIndexs deletedIndex = [];
  71. final Map<String, String> deletedRowByRowId = {
  72. for (var rowId in deletedRows) rowId: rowId
  73. };
  74. _rowInfos.asMap().forEach((index, row) {
  75. if (deletedRowByRowId[row.id] == null) {
  76. newRows.add(row);
  77. } else {
  78. _rowByRowId.remove(row.id);
  79. deletedIndex.add(DeletedIndex(index: index, row: row));
  80. }
  81. });
  82. _rowInfos = newRows;
  83. _rowChangeReasonNotifier.receive(RowChangeReason.delete(deletedIndex));
  84. }
  85. void _insertRows(List<InsertedRowPB> insertRows) {
  86. if (insertRows.isEmpty) {
  87. return;
  88. }
  89. InsertedIndexs insertIndexs = [];
  90. for (final insertRow in insertRows) {
  91. final insertIndex = InsertedIndex(
  92. index: insertRow.index,
  93. rowId: insertRow.rowId,
  94. );
  95. insertIndexs.add(insertIndex);
  96. _rowInfos.insert(insertRow.index,
  97. (buildGridRow(insertRow.rowId, insertRow.height.toDouble())));
  98. }
  99. _rowChangeReasonNotifier.receive(RowChangeReason.insert(insertIndexs));
  100. }
  101. void _updateRows(List<UpdatedRowPB> updatedRows) {
  102. if (updatedRows.isEmpty) {
  103. return;
  104. }
  105. final UpdatedIndexs updatedIndexs = UpdatedIndexs();
  106. for (final updatedRow in updatedRows) {
  107. final rowId = updatedRow.rowId;
  108. final index = _rowInfos.indexWhere((row) => row.id == rowId);
  109. if (index != -1) {
  110. _rowByRowId[rowId] = updatedRow.row;
  111. _rowInfos.removeAt(index);
  112. _rowInfos.insert(
  113. index, buildGridRow(rowId, updatedRow.row.height.toDouble()));
  114. updatedIndexs[rowId] = UpdatedIndex(index: index, rowId: rowId);
  115. }
  116. }
  117. _rowChangeReasonNotifier.receive(RowChangeReason.update(updatedIndexs));
  118. }
  119. void _hideRows(List<String> hideRows) {}
  120. void _showRows(List<String> visibleRows) {}
  121. void onRowsChanged(
  122. void Function(RowChangeReason) onRowChanged,
  123. ) {
  124. _rowChangeReasonNotifier.addListener(() {
  125. onRowChanged(_rowChangeReasonNotifier.reason);
  126. });
  127. }
  128. RowUpdateCallback addListener({
  129. required String rowId,
  130. void Function(GridCellMap, RowChangeReason)? onCellUpdated,
  131. bool Function()? listenWhen,
  132. }) {
  133. listenerHandler() async {
  134. if (listenWhen != null && listenWhen() == false) {
  135. return;
  136. }
  137. notifyUpdate() {
  138. if (onCellUpdated != null) {
  139. final row = _rowByRowId[rowId];
  140. if (row != null) {
  141. final GridCellMap cellDataMap = _makeGridCells(rowId, row);
  142. onCellUpdated(cellDataMap, _rowChangeReasonNotifier.reason);
  143. }
  144. }
  145. }
  146. _rowChangeReasonNotifier.reason.whenOrNull(
  147. update: (indexs) {
  148. if (indexs[rowId] != null) notifyUpdate();
  149. },
  150. fieldDidChange: () => notifyUpdate(),
  151. );
  152. }
  153. _rowChangeReasonNotifier.addListener(listenerHandler);
  154. return listenerHandler;
  155. }
  156. void removeRowListener(VoidCallback callback) {
  157. _rowChangeReasonNotifier.removeListener(callback);
  158. }
  159. GridCellMap loadGridCells(String rowId) {
  160. final RowPB? data = _rowByRowId[rowId];
  161. if (data == null) {
  162. _loadRow(rowId);
  163. }
  164. return _makeGridCells(rowId, data);
  165. }
  166. Future<void> _loadRow(String rowId) async {
  167. final payload = RowIdPB.create()
  168. ..gridId = gridId
  169. ..blockId = block.id
  170. ..rowId = rowId;
  171. final result = await GridEventGetRow(payload).send();
  172. result.fold(
  173. (optionRow) => _refreshRow(optionRow),
  174. (err) => Log.error(err),
  175. );
  176. }
  177. GridCellMap _makeGridCells(String rowId, RowPB? row) {
  178. var cellDataMap = GridCellMap.new();
  179. for (final field in _fieldNotifier.fields) {
  180. if (field.visibility) {
  181. cellDataMap[field.id] = GridCellIdentifier(
  182. rowId: rowId,
  183. gridId: gridId,
  184. field: field,
  185. );
  186. }
  187. }
  188. return cellDataMap;
  189. }
  190. void _refreshRow(OptionalRowPB optionRow) {
  191. if (!optionRow.hasRow()) {
  192. return;
  193. }
  194. final updatedRow = optionRow.row;
  195. updatedRow.freeze();
  196. _rowByRowId[updatedRow.id] = updatedRow;
  197. final index =
  198. _rowInfos.indexWhere((gridRow) => gridRow.id == updatedRow.id);
  199. if (index != -1) {
  200. // update the corresponding row in _rows if they are not the same
  201. if (_rowInfos[index].rawRow != updatedRow) {
  202. final row = _rowInfos.removeAt(index).copyWith(rawRow: updatedRow);
  203. _rowInfos.insert(index, row);
  204. // Calculate the update index
  205. final UpdatedIndexs updatedIndexs = UpdatedIndexs();
  206. updatedIndexs[row.id] = UpdatedIndex(index: index, rowId: row.id);
  207. //
  208. _rowChangeReasonNotifier.receive(RowChangeReason.update(updatedIndexs));
  209. }
  210. }
  211. }
  212. RowInfo buildGridRow(String rowId, double rowHeight) {
  213. return RowInfo(
  214. gridId: gridId,
  215. blockId: block.id,
  216. fields: _fieldNotifier.fields,
  217. id: rowId,
  218. height: rowHeight,
  219. );
  220. }
  221. }
  222. class _RowChangesetNotifier extends ChangeNotifier {
  223. RowChangeReason reason = const InitialListState();
  224. _RowChangesetNotifier();
  225. void receive(RowChangeReason newReason) {
  226. reason = newReason;
  227. reason.map(
  228. insert: (_) => notifyListeners(),
  229. delete: (_) => notifyListeners(),
  230. update: (_) => notifyListeners(),
  231. fieldDidChange: (_) => notifyListeners(),
  232. initial: (_) {},
  233. );
  234. }
  235. }
  236. @freezed
  237. class RowInfo with _$RowInfo {
  238. const factory RowInfo({
  239. required String gridId,
  240. required String blockId,
  241. required String id,
  242. required UnmodifiableListView<FieldPB> fields,
  243. required double height,
  244. RowPB? rawRow,
  245. }) = _RowInfo;
  246. }
  247. typedef InsertedIndexs = List<InsertedIndex>;
  248. typedef DeletedIndexs = List<DeletedIndex>;
  249. typedef UpdatedIndexs = LinkedHashMap<String, UpdatedIndex>;
  250. @freezed
  251. class RowChangeReason with _$RowChangeReason {
  252. const factory RowChangeReason.insert(InsertedIndexs items) = _Insert;
  253. const factory RowChangeReason.delete(DeletedIndexs items) = _Delete;
  254. const factory RowChangeReason.update(UpdatedIndexs indexs) = _Update;
  255. const factory RowChangeReason.fieldDidChange() = _FieldDidChange;
  256. const factory RowChangeReason.initial() = InitialListState;
  257. }
  258. class InsertedIndex {
  259. final int index;
  260. final String rowId;
  261. InsertedIndex({
  262. required this.index,
  263. required this.rowId,
  264. });
  265. }
  266. class DeletedIndex {
  267. final int index;
  268. final RowInfo row;
  269. DeletedIndex({
  270. required this.index,
  271. required this.row,
  272. });
  273. }
  274. class UpdatedIndex {
  275. final int index;
  276. final String rowId;
  277. UpdatedIndex({
  278. required this.index,
  279. required this.rowId,
  280. });
  281. }