row_cache.dart 9.8 KB

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