field_controller.dart 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542
  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. _filterPBByFieldId.removeWhere(
  124. (key, value) => deleteFilterIds.contains(value.id));
  125. }
  126. // Inserts the new filter if it's not exist
  127. for (final newFilter in changeset.insertFilters) {
  128. final filterIndex = filters
  129. .indexWhere((element) => element.filter.id == newFilter.id);
  130. if (filterIndex == -1) {
  131. final fieldInfo = _findFieldInfoForFilter(fieldInfos, newFilter);
  132. if (fieldInfo != null) {
  133. _filterPBByFieldId[fieldInfo.id] = newFilter;
  134. filters.add(FilterInfo(gridId, newFilter, fieldInfo));
  135. }
  136. }
  137. }
  138. for (final updatedFilter in changeset.updateFilters) {
  139. final filterIndex = filters.indexWhere(
  140. (element) => element.filter.id == updatedFilter.filterId,
  141. );
  142. // Remove the old filter
  143. if (filterIndex != -1) {
  144. filters.removeAt(filterIndex);
  145. _filterPBByFieldId.removeWhere(
  146. (key, value) => value.id == updatedFilter.filterId);
  147. }
  148. // Insert the filter if there is a fitler and its field info is
  149. // not null
  150. if (updatedFilter.hasFilter()) {
  151. final fieldInfo = _findFieldInfoForFilter(
  152. fieldInfos,
  153. updatedFilter.filter,
  154. );
  155. if (fieldInfo != null) {
  156. // Insert the filter with the position: filterIndex, otherwise,
  157. // append it to the end of the list.
  158. final filterInfo =
  159. FilterInfo(gridId, updatedFilter.filter, fieldInfo);
  160. if (filterIndex != -1) {
  161. filters.insert(filterIndex, filterInfo);
  162. } else {
  163. filters.add(filterInfo);
  164. }
  165. _filterPBByFieldId[fieldInfo.id] = updatedFilter.filter;
  166. }
  167. }
  168. }
  169. _updateFieldInfos();
  170. _filterNotifier?.filters = filters;
  171. },
  172. (err) => Log.error(err),
  173. );
  174. });
  175. }
  176. void _listenOnSettingChanges() {
  177. //Listen on setting changes
  178. _settingListener.start(onSettingUpdated: (result) {
  179. result.fold(
  180. (setting) => _updateSettingConfiguration(setting),
  181. (r) => Log.error(r),
  182. );
  183. });
  184. }
  185. void _listenOnFieldChanges() {
  186. //Listen on field's changes
  187. _fieldListener.start(onFieldsChanged: (result) {
  188. result.fold(
  189. (changeset) {
  190. _deleteFields(changeset.deletedFields);
  191. _insertFields(changeset.insertedFields);
  192. final updateFields = _updateFields(changeset.updatedFields);
  193. for (final listener in _updatedFieldCallbacks.values) {
  194. listener(updateFields);
  195. }
  196. },
  197. (err) => Log.error(err),
  198. );
  199. });
  200. }
  201. void _updateSettingConfiguration(GridSettingPB setting) {
  202. _groupConfigurationByFieldId.clear();
  203. for (final configuration in setting.groupConfigurations.items) {
  204. _groupConfigurationByFieldId[configuration.fieldId] = configuration;
  205. }
  206. for (final configuration in setting.filters.items) {
  207. _filterPBByFieldId[configuration.fieldId] = configuration;
  208. }
  209. _updateFieldInfos();
  210. }
  211. void _updateFieldInfos() {
  212. if (_fieldNotifier != null) {
  213. for (var field in _fieldNotifier!.fieldInfos) {
  214. field._isGroupField = _groupConfigurationByFieldId[field.id] != null;
  215. field._hasFilter = _filterPBByFieldId[field.id] != null;
  216. }
  217. _fieldNotifier?.notify();
  218. }
  219. }
  220. Future<void> dispose() async {
  221. await _fieldListener.stop();
  222. await _filterListener.stop();
  223. await _settingListener.stop();
  224. for (final callback in _fieldCallbacks.values) {
  225. _fieldNotifier?.removeListener(callback);
  226. }
  227. _fieldNotifier?.dispose();
  228. _fieldNotifier = null;
  229. for (final callback in _filterCallbacks.values) {
  230. _filterNotifier?.removeListener(callback);
  231. }
  232. _filterNotifier?.dispose();
  233. _filterNotifier = null;
  234. }
  235. Future<Either<Unit, FlowyError>> loadFields({
  236. required List<FieldIdPB> fieldIds,
  237. }) async {
  238. final result = await _gridFFIService.getFields(fieldIds: fieldIds);
  239. return Future(
  240. () => result.fold(
  241. (newFields) {
  242. _fieldNotifier?.fieldInfos =
  243. newFields.map((field) => FieldInfo(field: field)).toList();
  244. _loadFilters();
  245. _updateFieldInfos();
  246. return left(unit);
  247. },
  248. (err) => right(err),
  249. ),
  250. );
  251. }
  252. Future<Either<Unit, FlowyError>> _loadFilters() async {
  253. return _filterFFIService.getAllFilters().then((result) {
  254. return result.fold(
  255. (filterPBs) {
  256. final List<FilterInfo> filters = [];
  257. for (final filterPB in filterPBs) {
  258. final fieldInfo = _findFieldInfoForFilter(fieldInfos, filterPB);
  259. if (fieldInfo != null) {
  260. final filterInfo = FilterInfo(gridId, filterPB, fieldInfo);
  261. filters.add(filterInfo);
  262. }
  263. }
  264. _updateFieldInfos();
  265. _filterNotifier?.filters = filters;
  266. return left(unit);
  267. },
  268. (err) => right(err),
  269. );
  270. });
  271. }
  272. void addListener({
  273. OnReceiveFields? onFields,
  274. OnReceiveUpdateFields? onFieldsUpdated,
  275. OnReceiveFilters? onFilters,
  276. bool Function()? listenWhen,
  277. }) {
  278. if (onFieldsUpdated != null) {
  279. callback(List<FieldInfo> updateFields) {
  280. if (listenWhen != null && listenWhen() == false) {
  281. return;
  282. }
  283. onFieldsUpdated(updateFields);
  284. }
  285. _updatedFieldCallbacks[onFieldsUpdated] = callback;
  286. }
  287. if (onFields != null) {
  288. callback() {
  289. if (listenWhen != null && listenWhen() == false) {
  290. return;
  291. }
  292. onFields(fieldInfos);
  293. }
  294. _fieldCallbacks[onFields] = callback;
  295. _fieldNotifier?.addListener(callback);
  296. }
  297. if (onFilters != null) {
  298. callback() {
  299. if (listenWhen != null && listenWhen() == false) {
  300. return;
  301. }
  302. onFilters(filterInfos);
  303. }
  304. _filterCallbacks[onFilters] = callback;
  305. _filterNotifier?.addListener(callback);
  306. }
  307. }
  308. void removeListener({
  309. OnReceiveFields? onFieldsListener,
  310. OnReceiveFilters? onFiltersListener,
  311. OnReceiveUpdateFields? onChangesetListener,
  312. }) {
  313. if (onFieldsListener != null) {
  314. final callback = _fieldCallbacks.remove(onFieldsListener);
  315. if (callback != null) {
  316. _fieldNotifier?.removeListener(callback);
  317. }
  318. }
  319. if (onFiltersListener != null) {
  320. final callback = _filterCallbacks.remove(onFiltersListener);
  321. if (callback != null) {
  322. _filterNotifier?.removeListener(callback);
  323. }
  324. }
  325. }
  326. void _deleteFields(List<FieldIdPB> deletedFields) {
  327. if (deletedFields.isEmpty) {
  328. return;
  329. }
  330. final List<FieldInfo> newFields = fieldInfos;
  331. final Map<String, FieldIdPB> deletedFieldMap = {
  332. for (var fieldOrder in deletedFields) fieldOrder.fieldId: fieldOrder
  333. };
  334. newFields.retainWhere((field) => (deletedFieldMap[field.id] == null));
  335. _fieldNotifier?.fieldInfos = newFields;
  336. }
  337. void _insertFields(List<IndexFieldPB> insertedFields) {
  338. if (insertedFields.isEmpty) {
  339. return;
  340. }
  341. final List<FieldInfo> newFields = fieldInfos;
  342. for (final indexField in insertedFields) {
  343. final gridField = FieldInfo(field: indexField.field_1);
  344. if (newFields.length > indexField.index) {
  345. newFields.insert(indexField.index, gridField);
  346. } else {
  347. newFields.add(gridField);
  348. }
  349. }
  350. _fieldNotifier?.fieldInfos = newFields;
  351. }
  352. List<FieldInfo> _updateFields(List<FieldPB> updatedFieldPBs) {
  353. if (updatedFieldPBs.isEmpty) {
  354. return [];
  355. }
  356. final List<FieldInfo> newFields = fieldInfos;
  357. final List<FieldInfo> updatedFields = [];
  358. for (final updatedFieldPB in updatedFieldPBs) {
  359. final index =
  360. newFields.indexWhere((field) => field.id == updatedFieldPB.id);
  361. if (index != -1) {
  362. newFields.removeAt(index);
  363. final fieldInfo = FieldInfo(field: updatedFieldPB);
  364. newFields.insert(index, fieldInfo);
  365. updatedFields.add(fieldInfo);
  366. }
  367. }
  368. if (updatedFields.isNotEmpty) {
  369. _fieldNotifier?.fieldInfos = newFields;
  370. }
  371. return updatedFields;
  372. }
  373. }
  374. class GridRowFieldNotifierImpl extends IGridRowFieldNotifier {
  375. final GridFieldController _cache;
  376. OnReceiveUpdateFields? _onChangesetFn;
  377. OnReceiveFields? _onFieldFn;
  378. GridRowFieldNotifierImpl(GridFieldController cache) : _cache = cache;
  379. @override
  380. UnmodifiableListView<FieldInfo> get fields =>
  381. UnmodifiableListView(_cache.fieldInfos);
  382. @override
  383. void onRowFieldsChanged(VoidCallback callback) {
  384. _onFieldFn = (_) => callback();
  385. _cache.addListener(onFields: _onFieldFn);
  386. }
  387. @override
  388. void onRowFieldChanged(void Function(FieldInfo) callback) {
  389. _onChangesetFn = (List<FieldInfo> fieldInfos) {
  390. for (final updatedField in fieldInfos) {
  391. callback(updatedField);
  392. }
  393. };
  394. _cache.addListener(onFieldsUpdated: _onChangesetFn);
  395. }
  396. @override
  397. void onRowDispose() {
  398. if (_onFieldFn != null) {
  399. _cache.removeListener(onFieldsListener: _onFieldFn!);
  400. _onFieldFn = null;
  401. }
  402. if (_onChangesetFn != null) {
  403. _cache.removeListener(onChangesetListener: _onChangesetFn!);
  404. _onChangesetFn = null;
  405. }
  406. }
  407. }
  408. FieldInfo? _findFieldInfoForFilter(
  409. List<FieldInfo> fieldInfos, FilterPB filter) {
  410. final fieldIndex = fieldInfos.indexWhere((element) {
  411. return element.id == filter.fieldId &&
  412. element.fieldType == filter.fieldType;
  413. });
  414. if (fieldIndex != -1) {
  415. return fieldInfos[fieldIndex];
  416. } else {
  417. return null;
  418. }
  419. }
  420. class FieldInfo {
  421. final FieldPB _field;
  422. bool _isGroupField = false;
  423. bool _hasFilter = false;
  424. String get id => _field.id;
  425. FieldType get fieldType => _field.fieldType;
  426. bool get visibility => _field.visibility;
  427. double get width => _field.width.toDouble();
  428. bool get isPrimary => _field.isPrimary;
  429. String get name => _field.name;
  430. FieldPB get field => _field;
  431. bool get isGroupField => _isGroupField;
  432. bool get hasFilter => _hasFilter;
  433. bool get canGroup {
  434. switch (_field.fieldType) {
  435. case FieldType.Checkbox:
  436. return true;
  437. case FieldType.DateTime:
  438. return false;
  439. case FieldType.MultiSelect:
  440. return true;
  441. case FieldType.Number:
  442. return false;
  443. case FieldType.RichText:
  444. return false;
  445. case FieldType.SingleSelect:
  446. return true;
  447. case FieldType.URL:
  448. return false;
  449. }
  450. return false;
  451. }
  452. bool get canCreateFilter {
  453. if (hasFilter) return false;
  454. switch (_field.fieldType) {
  455. case FieldType.Checkbox:
  456. case FieldType.MultiSelect:
  457. case FieldType.RichText:
  458. case FieldType.SingleSelect:
  459. return true;
  460. default:
  461. return false;
  462. }
  463. }
  464. FieldInfo({required FieldPB field}) : _field = field;
  465. }