row_cache.dart 9.8 KB

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