row_cache.dart 9.8 KB

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