row_cache.dart 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  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/row_entities.pb.dart';
  8. import 'package:flutter/foundation.dart';
  9. import 'package:freezed_annotation/freezed_annotation.dart';
  10. import 'row_list.dart';
  11. part 'row_cache.freezed.dart';
  12. typedef RowUpdateCallback = void Function();
  13. abstract class IGridRowFieldNotifier {
  14. UnmodifiableListView<FieldInfo> get fields;
  15. void onRowFieldsChanged(VoidCallback callback);
  16. void onRowFieldChanged(void Function(FieldInfo) 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. final RowList _rowList = RowList();
  29. final GridCellCache _cellCache;
  30. final IGridRowFieldNotifier _fieldNotifier;
  31. final _RowChangesetNotifier _rowChangeReasonNotifier;
  32. UnmodifiableListView<RowInfo> get visibleRows {
  33. var visibleRows = [..._rowList.rows];
  34. return UnmodifiableListView(visibleRows);
  35. }
  36. GridCellCache get cellCache => _cellCache;
  37. GridRowCache({
  38. required this.gridId,
  39. required this.block,
  40. required IGridRowFieldNotifier notifier,
  41. }) : _cellCache = GridCellCache(gridId: gridId),
  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. for (final row in block.rows) {
  50. final rowInfo = buildGridRow(row);
  51. _rowList.add(rowInfo);
  52. }
  53. }
  54. Future<void> dispose() async {
  55. _fieldNotifier.onRowDispose();
  56. _rowChangeReasonNotifier.dispose();
  57. await _cellCache.dispose();
  58. }
  59. void applyChangesets(GridBlockChangesetPB changeset) {
  60. _deleteRows(changeset.deletedRows);
  61. _insertRows(changeset.insertedRows);
  62. _updateRows(changeset.updatedRows);
  63. _hideRows(changeset.invisibleRows);
  64. _showRows(changeset.visibleRows);
  65. }
  66. void _deleteRows(List<String> deletedRowIds) {
  67. for (final rowId in deletedRowIds) {
  68. final deletedRow = _rowList.remove(rowId);
  69. if (deletedRow != null) {
  70. _rowChangeReasonNotifier.receive(RowsChangedReason.delete(deletedRow));
  71. }
  72. }
  73. }
  74. void _insertRows(List<InsertedRowPB> insertRows) {
  75. for (final insertedRow in insertRows) {
  76. final insertedIndex =
  77. _rowList.insert(insertedRow.index, buildGridRow(insertedRow.row));
  78. if (insertedIndex != null) {
  79. _rowChangeReasonNotifier
  80. .receive(RowsChangedReason.insert(insertedIndex));
  81. }
  82. }
  83. }
  84. void _updateRows(List<UpdatedRowPB> updatedRows) {
  85. if (updatedRows.isEmpty) return;
  86. List<RowPB> rowPBs = [];
  87. for (final updatedRow in updatedRows) {
  88. for (final fieldId in updatedRow.fieldIds) {
  89. final key = GridCellCacheKey(
  90. fieldId: fieldId,
  91. rowId: updatedRow.row.id,
  92. );
  93. _cellCache.remove(key);
  94. }
  95. rowPBs.add(updatedRow.row);
  96. }
  97. final updatedIndexs =
  98. _rowList.updateRows(rowPBs, (rowPB) => buildGridRow(rowPB));
  99. if (updatedIndexs.isNotEmpty) {
  100. _rowChangeReasonNotifier.receive(RowsChangedReason.update(updatedIndexs));
  101. }
  102. }
  103. void _hideRows(List<String> invisibleRows) {
  104. for (final rowId in invisibleRows) {
  105. final deletedRow = _rowList.remove(rowId);
  106. if (deletedRow != null) {
  107. _rowChangeReasonNotifier.receive(RowsChangedReason.delete(deletedRow));
  108. }
  109. }
  110. }
  111. void _showRows(List<InsertedRowPB> visibleRows) {
  112. for (final insertedRow in visibleRows) {
  113. final insertedIndex =
  114. _rowList.insert(insertedRow.index, buildGridRow(insertedRow.row));
  115. if (insertedIndex != null) {
  116. _rowChangeReasonNotifier
  117. .receive(RowsChangedReason.insert(insertedIndex));
  118. }
  119. }
  120. }
  121. void onRowsChanged(void Function(RowsChangedReason) onRowChanged) {
  122. _rowChangeReasonNotifier.addListener(() {
  123. onRowChanged(_rowChangeReasonNotifier.reason);
  124. });
  125. }
  126. RowUpdateCallback addListener({
  127. required String rowId,
  128. void Function(GridCellMap, RowsChangedReason)? onCellUpdated,
  129. bool Function()? listenWhen,
  130. }) {
  131. listenerHandler() async {
  132. if (listenWhen != null && listenWhen() == false) {
  133. return;
  134. }
  135. notifyUpdate() {
  136. if (onCellUpdated != null) {
  137. final rowInfo = _rowList.get(rowId);
  138. if (rowInfo != null) {
  139. final GridCellMap cellDataMap =
  140. _makeGridCells(rowId, rowInfo.rowPB);
  141. onCellUpdated(cellDataMap, _rowChangeReasonNotifier.reason);
  142. }
  143. }
  144. }
  145. _rowChangeReasonNotifier.reason.whenOrNull(
  146. update: (indexs) {
  147. if (indexs[rowId] != null) notifyUpdate();
  148. },
  149. fieldDidChange: () => notifyUpdate(),
  150. );
  151. }
  152. _rowChangeReasonNotifier.addListener(listenerHandler);
  153. return listenerHandler;
  154. }
  155. void removeRowListener(VoidCallback callback) {
  156. _rowChangeReasonNotifier.removeListener(callback);
  157. }
  158. GridCellMap loadGridCells(String rowId) {
  159. final RowPB? data = _rowList.get(rowId)?.rowPB;
  160. if (data == null) {
  161. _loadRow(rowId);
  162. }
  163. return _makeGridCells(rowId, data);
  164. }
  165. Future<void> _loadRow(String rowId) async {
  166. final payload = RowIdPB.create()
  167. ..gridId = gridId
  168. ..blockId = block.id
  169. ..rowId = rowId;
  170. final result = await GridEventGetRow(payload).send();
  171. result.fold(
  172. (optionRow) => _refreshRow(optionRow),
  173. (err) => Log.error(err),
  174. );
  175. }
  176. GridCellMap _makeGridCells(String rowId, RowPB? row) {
  177. // ignore: prefer_collection_literals
  178. var cellDataMap = GridCellMap();
  179. for (final field in _fieldNotifier.fields) {
  180. if (field.visibility) {
  181. cellDataMap[field.id] = GridCellIdentifier(
  182. rowId: rowId,
  183. gridId: gridId,
  184. fieldInfo: 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. final rowInfo = _rowList.get(updatedRow.id);
  197. final rowIndex = _rowList.indexOfRow(updatedRow.id);
  198. if (rowInfo != null && rowIndex != null) {
  199. final updatedRowInfo = rowInfo.copyWith(rowPB: updatedRow);
  200. _rowList.remove(updatedRow.id);
  201. _rowList.insert(rowIndex, updatedRowInfo);
  202. final UpdatedIndexMap updatedIndexs = UpdatedIndexMap();
  203. updatedIndexs[rowInfo.rowPB.id] = UpdatedIndex(
  204. index: rowIndex,
  205. rowId: updatedRowInfo.rowPB.id,
  206. );
  207. _rowChangeReasonNotifier.receive(RowsChangedReason.update(updatedIndexs));
  208. }
  209. }
  210. RowInfo buildGridRow(RowPB rowPB) {
  211. return RowInfo(
  212. gridId: gridId,
  213. fields: _fieldNotifier.fields,
  214. rowPB: rowPB,
  215. );
  216. }
  217. }
  218. class _RowChangesetNotifier extends ChangeNotifier {
  219. RowsChangedReason reason = const InitialListState();
  220. _RowChangesetNotifier();
  221. void receive(RowsChangedReason newReason) {
  222. reason = newReason;
  223. reason.map(
  224. insert: (_) => notifyListeners(),
  225. delete: (_) => notifyListeners(),
  226. update: (_) => notifyListeners(),
  227. fieldDidChange: (_) => notifyListeners(),
  228. initial: (_) {},
  229. );
  230. }
  231. }
  232. @unfreezed
  233. class RowInfo with _$RowInfo {
  234. factory RowInfo({
  235. required String gridId,
  236. required UnmodifiableListView<FieldInfo> fields,
  237. required RowPB rowPB,
  238. }) = _RowInfo;
  239. }
  240. typedef InsertedIndexs = List<InsertedIndex>;
  241. typedef DeletedIndexs = List<DeletedIndex>;
  242. // key: id of the row
  243. // value: UpdatedIndex
  244. typedef UpdatedIndexMap = LinkedHashMap<String, UpdatedIndex>;
  245. @freezed
  246. class RowsChangedReason with _$RowsChangedReason {
  247. const factory RowsChangedReason.insert(InsertedIndex item) = _Insert;
  248. const factory RowsChangedReason.delete(DeletedIndex item) = _Delete;
  249. const factory RowsChangedReason.update(UpdatedIndexMap indexs) = _Update;
  250. const factory RowsChangedReason.fieldDidChange() = _FieldDidChange;
  251. const factory RowsChangedReason.initial() = InitialListState;
  252. }
  253. class InsertedIndex {
  254. final int index;
  255. final String rowId;
  256. InsertedIndex({
  257. required this.index,
  258. required this.rowId,
  259. });
  260. }
  261. class DeletedIndex {
  262. final int index;
  263. final RowInfo rowInfo;
  264. DeletedIndex({
  265. required this.index,
  266. required this.rowInfo,
  267. });
  268. }
  269. class UpdatedIndex {
  270. final int index;
  271. final String rowId;
  272. UpdatedIndex({
  273. required this.index,
  274. required this.rowId,
  275. });
  276. }