row_cache.dart 9.3 KB

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