row_cache.dart 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  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. visibleRows.retainWhere((element) => element.visible);
  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 _deleteRows(List<String> deletedRows) {
  68. if (deletedRows.isEmpty) return;
  69. final deletedIndex = _rowList.removeRows(deletedRows);
  70. if (deletedIndex.isNotEmpty) {
  71. _rowChangeReasonNotifier.receive(RowsChangedReason.delete(deletedIndex));
  72. }
  73. }
  74. void _insertRows(List<InsertedRowPB> insertRows) {
  75. if (insertRows.isEmpty) return;
  76. InsertedIndexs insertIndexs =
  77. _rowList.insertRows(insertRows, (rowPB) => buildGridRow(rowPB));
  78. if (insertIndexs.isNotEmpty) {
  79. _rowChangeReasonNotifier.receive(RowsChangedReason.insert(insertIndexs));
  80. }
  81. }
  82. void _updateRows(List<RowPB> updatedRows) {
  83. if (updatedRows.isEmpty) return;
  84. final updatedIndexs =
  85. _rowList.updateRows(updatedRows, (rowPB) => buildGridRow(rowPB));
  86. if (updatedIndexs.isNotEmpty) {
  87. _rowChangeReasonNotifier.receive(RowsChangedReason.update(updatedIndexs));
  88. }
  89. }
  90. void _hideRows(List<String> invisibleRows) {
  91. if (invisibleRows.isEmpty) return;
  92. final List<DeletedIndex> deletedRows = _rowList.removeRows(invisibleRows);
  93. if (deletedRows.isNotEmpty) {
  94. _rowChangeReasonNotifier.receive(RowsChangedReason.delete(deletedRows));
  95. }
  96. }
  97. void _showRows(List<InsertedRowPB> visibleRows) {
  98. if (visibleRows.isEmpty) return;
  99. final List<InsertedIndex> insertedRows =
  100. _rowList.insertRows(visibleRows, (rowPB) => buildGridRow(rowPB));
  101. if (insertedRows.isNotEmpty) {
  102. _rowChangeReasonNotifier.receive(RowsChangedReason.insert(insertedRows));
  103. }
  104. }
  105. void onRowsChanged(void Function(RowsChangedReason) onRowChanged) {
  106. _rowChangeReasonNotifier.addListener(() {
  107. onRowChanged(_rowChangeReasonNotifier.reason);
  108. });
  109. }
  110. RowUpdateCallback addListener({
  111. required String rowId,
  112. void Function(GridCellMap, RowsChangedReason)? onCellUpdated,
  113. bool Function()? listenWhen,
  114. }) {
  115. listenerHandler() async {
  116. if (listenWhen != null && listenWhen() == false) {
  117. return;
  118. }
  119. notifyUpdate() {
  120. if (onCellUpdated != null) {
  121. final rowInfo = _rowList.get(rowId);
  122. if (rowInfo != null) {
  123. final GridCellMap cellDataMap =
  124. _makeGridCells(rowId, rowInfo.rowPB);
  125. onCellUpdated(cellDataMap, _rowChangeReasonNotifier.reason);
  126. }
  127. }
  128. }
  129. _rowChangeReasonNotifier.reason.whenOrNull(
  130. update: (indexs) {
  131. if (indexs[rowId] != null) notifyUpdate();
  132. },
  133. fieldDidChange: () => notifyUpdate(),
  134. );
  135. }
  136. _rowChangeReasonNotifier.addListener(listenerHandler);
  137. return listenerHandler;
  138. }
  139. void removeRowListener(VoidCallback callback) {
  140. _rowChangeReasonNotifier.removeListener(callback);
  141. }
  142. GridCellMap loadGridCells(String rowId) {
  143. final RowPB? data = _rowList.get(rowId)?.rowPB;
  144. if (data == null) {
  145. _loadRow(rowId);
  146. }
  147. return _makeGridCells(rowId, data);
  148. }
  149. Future<void> _loadRow(String rowId) async {
  150. final payload = RowIdPB.create()
  151. ..gridId = gridId
  152. ..blockId = block.id
  153. ..rowId = rowId;
  154. final result = await GridEventGetRow(payload).send();
  155. result.fold(
  156. (optionRow) => _refreshRow(optionRow),
  157. (err) => Log.error(err),
  158. );
  159. }
  160. GridCellMap _makeGridCells(String rowId, RowPB? row) {
  161. // ignore: prefer_collection_literals
  162. var cellDataMap = GridCellMap();
  163. for (final field in _fieldNotifier.fields) {
  164. if (field.visibility) {
  165. cellDataMap[field.id] = GridCellIdentifier(
  166. rowId: rowId,
  167. gridId: gridId,
  168. fieldInfo: field,
  169. );
  170. }
  171. }
  172. return cellDataMap;
  173. }
  174. void _refreshRow(OptionalRowPB optionRow) {
  175. if (!optionRow.hasRow()) {
  176. return;
  177. }
  178. final updatedRow = optionRow.row;
  179. updatedRow.freeze();
  180. final rowInfo = _rowList.get(updatedRow.id);
  181. final rowIndex = _rowList.indexOfRow(updatedRow.id);
  182. if (rowInfo != null && rowIndex != null) {
  183. final updatedRowInfo = rowInfo.copyWith(rowPB: updatedRow);
  184. _rowList.remove(updatedRow.id);
  185. _rowList.insert(rowIndex, updatedRowInfo);
  186. final UpdatedIndexMap updatedIndexs = UpdatedIndexMap();
  187. updatedIndexs[rowInfo.rowPB.id] = UpdatedIndex(
  188. index: rowIndex,
  189. rowId: updatedRowInfo.rowPB.id,
  190. );
  191. _rowChangeReasonNotifier.receive(RowsChangedReason.update(updatedIndexs));
  192. }
  193. }
  194. RowInfo buildGridRow(RowPB rowPB) {
  195. return RowInfo(
  196. gridId: gridId,
  197. fields: _fieldNotifier.fields,
  198. rowPB: rowPB,
  199. visible: true,
  200. );
  201. }
  202. }
  203. class _RowChangesetNotifier extends ChangeNotifier {
  204. RowsChangedReason reason = const InitialListState();
  205. _RowChangesetNotifier();
  206. void receive(RowsChangedReason newReason) {
  207. reason = newReason;
  208. reason.map(
  209. insert: (_) => notifyListeners(),
  210. delete: (_) => notifyListeners(),
  211. update: (_) => notifyListeners(),
  212. fieldDidChange: (_) => notifyListeners(),
  213. initial: (_) {},
  214. );
  215. }
  216. }
  217. @unfreezed
  218. class RowInfo with _$RowInfo {
  219. factory RowInfo({
  220. required String gridId,
  221. required UnmodifiableListView<FieldInfo> fields,
  222. required RowPB rowPB,
  223. required bool visible,
  224. }) = _RowInfo;
  225. }
  226. typedef InsertedIndexs = List<InsertedIndex>;
  227. typedef DeletedIndexs = List<DeletedIndex>;
  228. // key: id of the row
  229. // value: UpdatedIndex
  230. typedef UpdatedIndexMap = LinkedHashMap<String, UpdatedIndex>;
  231. @freezed
  232. class RowsChangedReason with _$RowsChangedReason {
  233. const factory RowsChangedReason.insert(InsertedIndexs items) = _Insert;
  234. const factory RowsChangedReason.delete(DeletedIndexs items) = _Delete;
  235. const factory RowsChangedReason.update(UpdatedIndexMap indexs) = _Update;
  236. const factory RowsChangedReason.fieldDidChange() = _FieldDidChange;
  237. const factory RowsChangedReason.initial() = InitialListState;
  238. }
  239. class InsertedIndex {
  240. final int index;
  241. final String rowId;
  242. InsertedIndex({
  243. required this.index,
  244. required this.rowId,
  245. });
  246. }
  247. class DeletedIndex {
  248. final int index;
  249. final RowInfo rowInfo;
  250. DeletedIndex({
  251. required this.index,
  252. required this.rowInfo,
  253. });
  254. }
  255. class UpdatedIndex {
  256. final int index;
  257. final String rowId;
  258. UpdatedIndex({
  259. required this.index,
  260. required this.rowId,
  261. });
  262. }