row_cache.dart 9.3 KB

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