field_controller.dart 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534
  1. import 'dart:collection';
  2. import 'package:app_flowy/plugins/grid/application/field/grid_listener.dart';
  3. import 'package:app_flowy/plugins/grid/application/filter/filter_listener.dart';
  4. import 'package:app_flowy/plugins/grid/application/filter/filter_service.dart';
  5. import 'package:app_flowy/plugins/grid/application/grid_service.dart';
  6. import 'package:app_flowy/plugins/grid/application/setting/setting_listener.dart';
  7. import 'package:app_flowy/plugins/grid/application/setting/setting_service.dart';
  8. import 'package:app_flowy/plugins/grid/presentation/widgets/filter/filter_info.dart';
  9. import 'package:dartz/dartz.dart';
  10. import 'package:flowy_sdk/log.dart';
  11. import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
  12. import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
  13. import 'package:flowy_sdk/protobuf/flowy-grid/group.pb.dart';
  14. import 'package:flowy_sdk/protobuf/flowy-grid/setting_entities.pb.dart';
  15. import 'package:flowy_sdk/protobuf/flowy-grid/util.pb.dart';
  16. import 'package:flutter/foundation.dart';
  17. import '../row/row_cache.dart';
  18. class _GridFieldNotifier extends ChangeNotifier {
  19. List<FieldInfo> _fieldInfos = [];
  20. set fieldInfos(List<FieldInfo> fieldInfos) {
  21. _fieldInfos = fieldInfos;
  22. notifyListeners();
  23. }
  24. void notify() {
  25. notifyListeners();
  26. }
  27. List<FieldInfo> get fieldInfos => _fieldInfos;
  28. }
  29. class _GridFilterNotifier extends ChangeNotifier {
  30. List<FilterInfo> _filters = [];
  31. set filters(List<FilterInfo> filters) {
  32. _filters = filters;
  33. notifyListeners();
  34. }
  35. void notify() {
  36. notifyListeners();
  37. }
  38. List<FilterInfo> get filters => _filters;
  39. }
  40. typedef OnReceiveUpdateFields = void Function(List<FieldInfo>);
  41. typedef OnReceiveFields = void Function(List<FieldInfo>);
  42. typedef OnReceiveFilters = void Function(List<FilterInfo>);
  43. class GridFieldController {
  44. final String gridId;
  45. // Listeners
  46. final GridFieldsListener _fieldListener;
  47. final SettingListener _settingListener;
  48. final FiltersListener _filterListener;
  49. // FFI services
  50. final GridFFIService _gridFFIService;
  51. final SettingFFIService _settingFFIService;
  52. final FilterFFIService _filterFFIService;
  53. // Field callbacks
  54. final Map<OnReceiveFields, VoidCallback> _fieldCallbacks = {};
  55. _GridFieldNotifier? _fieldNotifier = _GridFieldNotifier();
  56. // Field updated callbacks
  57. final Map<OnReceiveUpdateFields, void Function(List<FieldInfo>)>
  58. _updatedFieldCallbacks = {};
  59. // Group callbacks
  60. final Map<String, GroupConfigurationPB> _groupConfigurationByFieldId = {};
  61. // Filter callbacks
  62. final Map<OnReceiveFilters, VoidCallback> _filterCallbacks = {};
  63. _GridFilterNotifier? _filterNotifier = _GridFilterNotifier();
  64. final Map<String, FilterPB> _filterPBByFieldId = {};
  65. // Getters
  66. List<FieldInfo> get fieldInfos => [..._fieldNotifier?.fieldInfos ?? []];
  67. List<FilterInfo> get filterInfos => [..._filterNotifier?.filters ?? []];
  68. FieldInfo? getField(String fieldId) {
  69. final fields = _fieldNotifier?.fieldInfos
  70. .where((element) => element.id == fieldId)
  71. .toList() ??
  72. [];
  73. if (fields.isEmpty) {
  74. return null;
  75. }
  76. assert(fields.length == 1);
  77. return fields.first;
  78. }
  79. FilterInfo? getFilter(String filterId) {
  80. final filters = _filterNotifier?.filters
  81. .where((element) => element.filter.id == filterId)
  82. .toList() ??
  83. [];
  84. if (filters.isEmpty) {
  85. return null;
  86. }
  87. assert(filters.length == 1);
  88. return filters.first;
  89. }
  90. GridFieldController({required this.gridId})
  91. : _fieldListener = GridFieldsListener(gridId: gridId),
  92. _settingListener = SettingListener(gridId: gridId),
  93. _filterListener = FiltersListener(viewId: gridId),
  94. _gridFFIService = GridFFIService(gridId: gridId),
  95. _filterFFIService = FilterFFIService(viewId: gridId),
  96. _settingFFIService = SettingFFIService(viewId: gridId) {
  97. //Listen on field's changes
  98. _listenOnFieldChanges();
  99. //Listen on setting changes
  100. _listenOnSettingChanges();
  101. //Listen on the fitler changes
  102. _listenOnFilterChanges();
  103. _settingFFIService.getSetting().then((result) {
  104. result.fold(
  105. (setting) => _updateSettingConfiguration(setting),
  106. (err) => Log.error(err),
  107. );
  108. });
  109. }
  110. void _listenOnFilterChanges() {
  111. //Listen on the fitler changes
  112. _filterListener.start(onFilterChanged: (result) {
  113. result.fold(
  114. (changeset) {
  115. final List<FilterInfo> filters = filterInfos;
  116. // Deletes the filters
  117. final deleteFilterIds =
  118. changeset.deleteFilters.map((e) => e.id).toList();
  119. if (deleteFilterIds.isNotEmpty) {
  120. filters.retainWhere(
  121. (element) => !deleteFilterIds.contains(element.filter.id),
  122. );
  123. }
  124. // Inserts the new filter if it's not exist
  125. for (final newFilter in changeset.insertFilters) {
  126. final filterIndex = filters
  127. .indexWhere((element) => element.filter.id == newFilter.id);
  128. if (filterIndex == -1) {
  129. final fieldInfo = _findFieldInfoForFilter(fieldInfos, newFilter);
  130. if (fieldInfo != null) {
  131. filters.add(FilterInfo(gridId, newFilter, fieldInfo));
  132. }
  133. }
  134. }
  135. for (final updatedFilter in changeset.updateFilters) {
  136. final filterIndex = filters.indexWhere(
  137. (element) => element.filter.id == updatedFilter.filterId,
  138. );
  139. // Remove the old filter
  140. if (filterIndex != -1) {
  141. filters.removeAt(filterIndex);
  142. _filterPBByFieldId.removeWhere(
  143. (key, value) => value.id == updatedFilter.filterId);
  144. }
  145. // Insert the filter if there is a fitler and its field info is
  146. // not null
  147. if (updatedFilter.hasFilter()) {
  148. final fieldInfo = _findFieldInfoForFilter(
  149. fieldInfos,
  150. updatedFilter.filter,
  151. );
  152. if (fieldInfo != null) {
  153. // Insert the filter with the position: filterIndex, otherwise,
  154. // append it to the end of the list.
  155. final filterInfo =
  156. FilterInfo(gridId, updatedFilter.filter, fieldInfo);
  157. if (filterIndex != -1) {
  158. filters.insert(filterIndex, filterInfo);
  159. } else {
  160. filters.add(filterInfo);
  161. }
  162. _filterPBByFieldId[fieldInfo.id] = updatedFilter.filter;
  163. }
  164. _updateFieldInfos();
  165. }
  166. }
  167. _filterNotifier?.filters = filters;
  168. },
  169. (err) => Log.error(err),
  170. );
  171. });
  172. }
  173. void _listenOnSettingChanges() {
  174. //Listen on setting changes
  175. _settingListener.start(onSettingUpdated: (result) {
  176. result.fold(
  177. (setting) => _updateSettingConfiguration(setting),
  178. (r) => Log.error(r),
  179. );
  180. });
  181. }
  182. void _listenOnFieldChanges() {
  183. //Listen on field's changes
  184. _fieldListener.start(onFieldsChanged: (result) {
  185. result.fold(
  186. (changeset) {
  187. _deleteFields(changeset.deletedFields);
  188. _insertFields(changeset.insertedFields);
  189. final updateFields = _updateFields(changeset.updatedFields);
  190. for (final listener in _updatedFieldCallbacks.values) {
  191. listener(updateFields);
  192. }
  193. },
  194. (err) => Log.error(err),
  195. );
  196. });
  197. }
  198. void _updateSettingConfiguration(GridSettingPB setting) {
  199. _groupConfigurationByFieldId.clear();
  200. for (final configuration in setting.groupConfigurations.items) {
  201. _groupConfigurationByFieldId[configuration.fieldId] = configuration;
  202. }
  203. for (final configuration in setting.filters.items) {
  204. _filterPBByFieldId[configuration.fieldId] = configuration;
  205. }
  206. _updateFieldInfos();
  207. }
  208. void _updateFieldInfos() {
  209. if (_fieldNotifier != null) {
  210. for (var field in _fieldNotifier!.fieldInfos) {
  211. field._isGroupField = _groupConfigurationByFieldId[field.id] != null;
  212. field._hasFilter = _filterPBByFieldId[field.id] != null;
  213. }
  214. _fieldNotifier?.notify();
  215. }
  216. }
  217. Future<void> dispose() async {
  218. await _fieldListener.stop();
  219. await _filterListener.stop();
  220. await _settingListener.stop();
  221. for (final callback in _fieldCallbacks.values) {
  222. _fieldNotifier?.removeListener(callback);
  223. }
  224. _fieldNotifier?.dispose();
  225. _fieldNotifier = null;
  226. for (final callback in _filterCallbacks.values) {
  227. _filterNotifier?.removeListener(callback);
  228. }
  229. _filterNotifier?.dispose();
  230. _filterNotifier = null;
  231. }
  232. Future<Either<Unit, FlowyError>> loadFields({
  233. required List<FieldIdPB> fieldIds,
  234. }) async {
  235. final result = await _gridFFIService.getFields(fieldIds: fieldIds);
  236. return Future(
  237. () => result.fold(
  238. (newFields) {
  239. _fieldNotifier?.fieldInfos =
  240. newFields.map((field) => FieldInfo(field: field)).toList();
  241. _loadFilters();
  242. _updateFieldInfos();
  243. return left(unit);
  244. },
  245. (err) => right(err),
  246. ),
  247. );
  248. }
  249. Future<Either<Unit, FlowyError>> _loadFilters() async {
  250. return _filterFFIService.getAllFilters().then((result) {
  251. return result.fold(
  252. (filterPBs) {
  253. final List<FilterInfo> filters = [];
  254. for (final filterPB in filterPBs) {
  255. final fieldInfo = _findFieldInfoForFilter(fieldInfos, filterPB);
  256. if (fieldInfo != null) {
  257. final filterInfo = FilterInfo(gridId, filterPB, fieldInfo);
  258. filters.add(filterInfo);
  259. }
  260. }
  261. _updateFieldInfos();
  262. _filterNotifier?.filters = filters;
  263. return left(unit);
  264. },
  265. (err) => right(err),
  266. );
  267. });
  268. }
  269. void addListener({
  270. OnReceiveFields? onFields,
  271. OnReceiveUpdateFields? onFieldsUpdated,
  272. OnReceiveFilters? onFilters,
  273. bool Function()? listenWhen,
  274. }) {
  275. if (onFieldsUpdated != null) {
  276. callback(List<FieldInfo> updateFields) {
  277. if (listenWhen != null && listenWhen() == false) {
  278. return;
  279. }
  280. onFieldsUpdated(updateFields);
  281. }
  282. _updatedFieldCallbacks[onFieldsUpdated] = callback;
  283. }
  284. if (onFields != null) {
  285. callback() {
  286. if (listenWhen != null && listenWhen() == false) {
  287. return;
  288. }
  289. onFields(fieldInfos);
  290. }
  291. _fieldCallbacks[onFields] = callback;
  292. _fieldNotifier?.addListener(callback);
  293. }
  294. if (onFilters != null) {
  295. callback() {
  296. if (listenWhen != null && listenWhen() == false) {
  297. return;
  298. }
  299. onFilters(filterInfos);
  300. }
  301. _filterCallbacks[onFilters] = callback;
  302. callback();
  303. _filterNotifier?.addListener(callback);
  304. }
  305. }
  306. void removeListener({
  307. OnReceiveFields? onFieldsListener,
  308. OnReceiveFilters? onFiltersListener,
  309. OnReceiveUpdateFields? onChangesetListener,
  310. }) {
  311. if (onFieldsListener != null) {
  312. final callback = _fieldCallbacks.remove(onFieldsListener);
  313. if (callback != null) {
  314. _fieldNotifier?.removeListener(callback);
  315. }
  316. }
  317. if (onFiltersListener != null) {
  318. final callback = _filterCallbacks.remove(onFiltersListener);
  319. if (callback != null) {
  320. _filterNotifier?.removeListener(callback);
  321. }
  322. }
  323. }
  324. void _deleteFields(List<FieldIdPB> deletedFields) {
  325. if (deletedFields.isEmpty) {
  326. return;
  327. }
  328. final List<FieldInfo> newFields = fieldInfos;
  329. final Map<String, FieldIdPB> deletedFieldMap = {
  330. for (var fieldOrder in deletedFields) fieldOrder.fieldId: fieldOrder
  331. };
  332. newFields.retainWhere((field) => (deletedFieldMap[field.id] == null));
  333. _fieldNotifier?.fieldInfos = newFields;
  334. }
  335. void _insertFields(List<IndexFieldPB> insertedFields) {
  336. if (insertedFields.isEmpty) {
  337. return;
  338. }
  339. final List<FieldInfo> newFields = fieldInfos;
  340. for (final indexField in insertedFields) {
  341. final gridField = FieldInfo(field: indexField.field_1);
  342. if (newFields.length > indexField.index) {
  343. newFields.insert(indexField.index, gridField);
  344. } else {
  345. newFields.add(gridField);
  346. }
  347. }
  348. _fieldNotifier?.fieldInfos = newFields;
  349. }
  350. List<FieldInfo> _updateFields(List<FieldPB> updatedFieldPBs) {
  351. if (updatedFieldPBs.isEmpty) {
  352. return [];
  353. }
  354. final List<FieldInfo> newFields = fieldInfos;
  355. final List<FieldInfo> updatedFields = [];
  356. for (final updatedFieldPB in updatedFieldPBs) {
  357. final index =
  358. newFields.indexWhere((field) => field.id == updatedFieldPB.id);
  359. if (index != -1) {
  360. newFields.removeAt(index);
  361. final fieldInfo = FieldInfo(field: updatedFieldPB);
  362. newFields.insert(index, fieldInfo);
  363. updatedFields.add(fieldInfo);
  364. }
  365. }
  366. if (updatedFields.isNotEmpty) {
  367. _fieldNotifier?.fieldInfos = newFields;
  368. }
  369. return updatedFields;
  370. }
  371. }
  372. class GridRowFieldNotifierImpl extends IGridRowFieldNotifier {
  373. final GridFieldController _cache;
  374. OnReceiveUpdateFields? _onChangesetFn;
  375. OnReceiveFields? _onFieldFn;
  376. GridRowFieldNotifierImpl(GridFieldController cache) : _cache = cache;
  377. @override
  378. UnmodifiableListView<FieldInfo> get fields =>
  379. UnmodifiableListView(_cache.fieldInfos);
  380. @override
  381. void onRowFieldsChanged(VoidCallback callback) {
  382. _onFieldFn = (_) => callback();
  383. _cache.addListener(onFields: _onFieldFn);
  384. }
  385. @override
  386. void onRowFieldChanged(void Function(FieldInfo) callback) {
  387. _onChangesetFn = (List<FieldInfo> fieldInfos) {
  388. for (final updatedField in fieldInfos) {
  389. callback(updatedField);
  390. }
  391. };
  392. _cache.addListener(onFieldsUpdated: _onChangesetFn);
  393. }
  394. @override
  395. void onRowDispose() {
  396. if (_onFieldFn != null) {
  397. _cache.removeListener(onFieldsListener: _onFieldFn!);
  398. _onFieldFn = null;
  399. }
  400. if (_onChangesetFn != null) {
  401. _cache.removeListener(onChangesetListener: _onChangesetFn!);
  402. _onChangesetFn = null;
  403. }
  404. }
  405. }
  406. FieldInfo? _findFieldInfoForFilter(
  407. List<FieldInfo> fieldInfos, FilterPB filter) {
  408. final fieldIndex = fieldInfos.indexWhere((element) {
  409. return element.id == filter.fieldId &&
  410. element.fieldType == filter.fieldType;
  411. });
  412. if (fieldIndex != -1) {
  413. return fieldInfos[fieldIndex];
  414. } else {
  415. return null;
  416. }
  417. }
  418. class FieldInfo {
  419. final FieldPB _field;
  420. bool _isGroupField = false;
  421. bool _hasFilter = false;
  422. String get id => _field.id;
  423. FieldType get fieldType => _field.fieldType;
  424. bool get visibility => _field.visibility;
  425. double get width => _field.width.toDouble();
  426. bool get isPrimary => _field.isPrimary;
  427. String get name => _field.name;
  428. FieldPB get field => _field;
  429. bool get isGroupField => _isGroupField;
  430. bool get hasFilter => _hasFilter;
  431. bool get canGroup {
  432. switch (_field.fieldType) {
  433. case FieldType.Checkbox:
  434. return true;
  435. case FieldType.DateTime:
  436. return false;
  437. case FieldType.MultiSelect:
  438. return true;
  439. case FieldType.Number:
  440. return false;
  441. case FieldType.RichText:
  442. return false;
  443. case FieldType.SingleSelect:
  444. return true;
  445. case FieldType.URL:
  446. return false;
  447. }
  448. return false;
  449. }
  450. bool get canCreateFilter {
  451. if (hasFilter) return false;
  452. if (_field.fieldType != FieldType.RichText) return false;
  453. return true;
  454. }
  455. FieldInfo({required FieldPB field}) : _field = field;
  456. }