import 'dart:collection'; import 'package:app_flowy/plugins/grid/application/field/grid_listener.dart'; import 'package:app_flowy/plugins/grid/application/grid_service.dart'; import 'package:app_flowy/plugins/grid/application/setting/setting_listener.dart'; import 'package:app_flowy/plugins/grid/application/setting/setting_service.dart'; import 'package:dartz/dartz.dart'; import 'package:flowy_sdk/log.dart'; import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/setting_entities.pb.dart'; import 'package:flutter/foundation.dart'; import '../row/row_cache.dart'; class _GridFieldNotifier extends ChangeNotifier { List _fieldContexts = []; set fieldContexts(List fieldContexts) { _fieldContexts = fieldContexts; notifyListeners(); } void notify() { notifyListeners(); } List get fieldContexts => _fieldContexts; } typedef OnChangeset = void Function(FieldChangesetPB); typedef OnReceiveFields = void Function(List); class GridFieldController { final String gridId; final GridFieldsListener _fieldListener; final SettingListener _settingListener; final Map _fieldCallbackMap = {}; final Map _changesetCallbackMap = {}; _GridFieldNotifier? _fieldNotifier = _GridFieldNotifier(); List _groupFieldIds = []; final GridFFIService _gridFFIService; final SettingFFIService _settingFFIService; List get fieldContexts => [..._fieldNotifier?.fieldContexts ?? []]; GridFieldController({required this.gridId}) : _fieldListener = GridFieldsListener(gridId: gridId), _gridFFIService = GridFFIService(gridId: gridId), _settingFFIService = SettingFFIService(viewId: gridId), _settingListener = SettingListener(gridId: gridId) { //Listen on field's changes _fieldListener.start(onFieldsChanged: (result) { result.fold( (changeset) { _deleteFields(changeset.deletedFields); _insertFields(changeset.insertedFields); _updateFields(changeset.updatedFields); for (final listener in _changesetCallbackMap.values) { listener(changeset); } }, (err) => Log.error(err), ); }); //Listen on setting changes _settingListener.start(onSettingUpdated: (result) { result.fold( (setting) => _updateFieldsWhenSettingChanged(setting), (r) => Log.error(r), ); }); _settingFFIService.getSetting().then((result) { result.fold( (setting) => _updateFieldsWhenSettingChanged(setting), (err) => Log.error(err), ); }); } void _updateFieldsWhenSettingChanged(GridSettingPB setting) { _groupFieldIds = setting.groupConfigurations.items .map((item) => item.groupFieldId) .toList(); _updateFieldContexts(); } void _updateFieldContexts() { if (_fieldNotifier != null) { for (var field in _fieldNotifier!.fieldContexts) { if (_groupFieldIds.contains(field.id)) { field._isGroupField = true; } else { field._isGroupField = false; } } _fieldNotifier?.notify(); } } Future dispose() async { await _fieldListener.stop(); _fieldNotifier?.dispose(); _fieldNotifier = null; } Future> loadFields( {required List fieldIds}) async { final result = await _gridFFIService.getFields(fieldIds: fieldIds); return Future( () => result.fold( (newFields) { _fieldNotifier?.fieldContexts = newFields.items .map((field) => GridFieldContext(field: field)) .toList(); _updateFieldContexts(); return left(unit); }, (err) => right(err), ), ); } void addListener({ OnReceiveFields? onFields, OnChangeset? onChangeset, bool Function()? listenWhen, }) { if (onChangeset != null) { callback(c) { if (listenWhen != null && listenWhen() == false) { return; } onChangeset(c); } _changesetCallbackMap[onChangeset] = callback; } if (onFields != null) { callback() { if (listenWhen != null && listenWhen() == false) { return; } onFields(fieldContexts); } _fieldCallbackMap[onFields] = callback; _fieldNotifier?.addListener(callback); } } void removeListener({ OnReceiveFields? onFieldsListener, OnChangeset? onChangesetListener, }) { if (onFieldsListener != null) { final callback = _fieldCallbackMap.remove(onFieldsListener); if (callback != null) { _fieldNotifier?.removeListener(callback); } } if (onChangesetListener != null) { _changesetCallbackMap.remove(onChangesetListener); } } void _deleteFields(List deletedFields) { if (deletedFields.isEmpty) { return; } final List newFields = fieldContexts; final Map deletedFieldMap = { for (var fieldOrder in deletedFields) fieldOrder.fieldId: fieldOrder }; newFields.retainWhere((field) => (deletedFieldMap[field.id] == null)); _fieldNotifier?.fieldContexts = newFields; } void _insertFields(List insertedFields) { if (insertedFields.isEmpty) { return; } final List newFields = fieldContexts; for (final indexField in insertedFields) { final gridField = GridFieldContext(field: indexField.field_1); if (newFields.length > indexField.index) { newFields.insert(indexField.index, gridField); } else { newFields.add(gridField); } } _fieldNotifier?.fieldContexts = newFields; } void _updateFields(List updatedFields) { if (updatedFields.isEmpty) { return; } final List newFields = fieldContexts; for (final updatedField in updatedFields) { final index = newFields.indexWhere((field) => field.id == updatedField.id); if (index != -1) { newFields.removeAt(index); final gridField = GridFieldContext(field: updatedField); newFields.insert(index, gridField); } } _fieldNotifier?.fieldContexts = newFields; } } class GridRowFieldNotifierImpl extends IGridRowFieldNotifier { final GridFieldController _cache; OnChangeset? _onChangesetFn; OnReceiveFields? _onFieldFn; GridRowFieldNotifierImpl(GridFieldController cache) : _cache = cache; @override UnmodifiableListView get fields => UnmodifiableListView(_cache.fieldContexts); @override void onRowFieldsChanged(VoidCallback callback) { _onFieldFn = (_) => callback(); _cache.addListener(onFields: _onFieldFn); } @override void onRowFieldChanged(void Function(FieldPB) callback) { _onChangesetFn = (FieldChangesetPB changeset) { for (final updatedField in changeset.updatedFields) { callback(updatedField); } }; _cache.addListener(onChangeset: _onChangesetFn); } @override void onRowDispose() { if (_onFieldFn != null) { _cache.removeListener(onFieldsListener: _onFieldFn!); _onFieldFn = null; } if (_onChangesetFn != null) { _cache.removeListener(onChangesetListener: _onChangesetFn!); _onChangesetFn = null; } } } class GridFieldContext { final FieldPB _field; bool _isGroupField = false; String get id => _field.id; FieldType get fieldType => _field.fieldType; bool get visibility => _field.visibility; double get width => _field.width.toDouble(); bool get isPrimary => _field.isPrimary; String get name => _field.name; FieldPB get field => _field; bool get isGroupField => _isGroupField; GridFieldContext({required FieldPB field}) : _field = field; }