|
@@ -1,118 +1,16 @@
|
|
|
-part of 'cell_service.dart';
|
|
|
-
|
|
|
-typedef TextCellController = CellController<String, String>;
|
|
|
-typedef CheckboxCellController = CellController<String, String>;
|
|
|
-typedef NumberCellController = CellController<String, String>;
|
|
|
-typedef SelectOptionCellController
|
|
|
- = CellController<SelectOptionCellDataPB, String>;
|
|
|
-typedef ChecklistCellController
|
|
|
- = CellController<SelectOptionCellDataPB, String>;
|
|
|
-typedef DateCellController = CellController<DateCellDataPB, CalendarData>;
|
|
|
-typedef URLCellController = CellController<URLCellDataPB, String>;
|
|
|
-
|
|
|
-abstract class CellControllerBuilderDelegate {
|
|
|
- CellFieldNotifier buildFieldNotifier();
|
|
|
-}
|
|
|
-
|
|
|
-class CellControllerBuilder {
|
|
|
- final CellIdentifier _cellId;
|
|
|
- final CellCache _cellCache;
|
|
|
- final CellControllerBuilderDelegate delegate;
|
|
|
-
|
|
|
- CellControllerBuilder({
|
|
|
- required this.delegate,
|
|
|
- required CellIdentifier cellId,
|
|
|
- required CellCache cellCache,
|
|
|
- }) : _cellCache = cellCache,
|
|
|
- _cellId = cellId;
|
|
|
-
|
|
|
- CellController build() {
|
|
|
- final cellFieldNotifier = delegate.buildFieldNotifier();
|
|
|
- switch (_cellId.fieldType) {
|
|
|
- case FieldType.Checkbox:
|
|
|
- final cellDataLoader = CellDataLoader(
|
|
|
- cellId: _cellId,
|
|
|
- parser: StringCellDataParser(),
|
|
|
- );
|
|
|
- return TextCellController(
|
|
|
- cellId: _cellId,
|
|
|
- cellCache: _cellCache,
|
|
|
- cellDataLoader: cellDataLoader,
|
|
|
- fieldNotifier: cellFieldNotifier,
|
|
|
- cellDataPersistence: TextCellDataPersistence(cellId: _cellId),
|
|
|
- );
|
|
|
- case FieldType.DateTime:
|
|
|
- final cellDataLoader = CellDataLoader(
|
|
|
- cellId: _cellId,
|
|
|
- parser: DateCellDataParser(),
|
|
|
- reloadOnFieldChanged: true,
|
|
|
- );
|
|
|
-
|
|
|
- return DateCellController(
|
|
|
- cellId: _cellId,
|
|
|
- cellCache: _cellCache,
|
|
|
- cellDataLoader: cellDataLoader,
|
|
|
- fieldNotifier: cellFieldNotifier,
|
|
|
- cellDataPersistence: DateCellDataPersistence(cellId: _cellId),
|
|
|
- );
|
|
|
- case FieldType.Number:
|
|
|
- final cellDataLoader = CellDataLoader(
|
|
|
- cellId: _cellId,
|
|
|
- parser: StringCellDataParser(),
|
|
|
- reloadOnFieldChanged: true,
|
|
|
- );
|
|
|
- return NumberCellController(
|
|
|
- cellId: _cellId,
|
|
|
- cellCache: _cellCache,
|
|
|
- cellDataLoader: cellDataLoader,
|
|
|
- fieldNotifier: cellFieldNotifier,
|
|
|
- cellDataPersistence: TextCellDataPersistence(cellId: _cellId),
|
|
|
- );
|
|
|
- case FieldType.RichText:
|
|
|
- final cellDataLoader = CellDataLoader(
|
|
|
- cellId: _cellId,
|
|
|
- parser: StringCellDataParser(),
|
|
|
- );
|
|
|
- return TextCellController(
|
|
|
- cellId: _cellId,
|
|
|
- cellCache: _cellCache,
|
|
|
- cellDataLoader: cellDataLoader,
|
|
|
- fieldNotifier: cellFieldNotifier,
|
|
|
- cellDataPersistence: TextCellDataPersistence(cellId: _cellId),
|
|
|
- );
|
|
|
- case FieldType.MultiSelect:
|
|
|
- case FieldType.SingleSelect:
|
|
|
- case FieldType.Checklist:
|
|
|
- final cellDataLoader = CellDataLoader(
|
|
|
- cellId: _cellId,
|
|
|
- parser: SelectOptionCellDataParser(),
|
|
|
- reloadOnFieldChanged: true,
|
|
|
- );
|
|
|
-
|
|
|
- return SelectOptionCellController(
|
|
|
- cellId: _cellId,
|
|
|
- cellCache: _cellCache,
|
|
|
- cellDataLoader: cellDataLoader,
|
|
|
- fieldNotifier: cellFieldNotifier,
|
|
|
- cellDataPersistence: TextCellDataPersistence(cellId: _cellId),
|
|
|
- );
|
|
|
-
|
|
|
- case FieldType.URL:
|
|
|
- final cellDataLoader = CellDataLoader(
|
|
|
- cellId: _cellId,
|
|
|
- parser: URLCellDataParser(),
|
|
|
- );
|
|
|
- return URLCellController(
|
|
|
- cellId: _cellId,
|
|
|
- cellCache: _cellCache,
|
|
|
- cellDataLoader: cellDataLoader,
|
|
|
- fieldNotifier: cellFieldNotifier,
|
|
|
- cellDataPersistence: TextCellDataPersistence(cellId: _cellId),
|
|
|
- );
|
|
|
- }
|
|
|
- throw UnimplementedError;
|
|
|
- }
|
|
|
-}
|
|
|
+import 'dart:async';
|
|
|
+import 'package:appflowy/plugins/database_view/application/field/field_listener.dart';
|
|
|
+import 'package:appflowy_backend/log.dart';
|
|
|
+import 'package:appflowy_backend/protobuf/flowy-database/field_entities.pbenum.dart';
|
|
|
+import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
|
|
+import 'package:dartz/dartz.dart';
|
|
|
+import 'package:equatable/equatable.dart';
|
|
|
+import 'package:flutter/foundation.dart';
|
|
|
+import '../field/field_controller.dart';
|
|
|
+import '../field/field_service.dart';
|
|
|
+import '../field/type_option/type_option_context.dart';
|
|
|
+import 'cell_listener.dart';
|
|
|
+import 'cell_service.dart';
|
|
|
|
|
|
/// IGridCellController is used to manipulate the cell and receive notifications.
|
|
|
/// * Read/Write cell data
|
|
@@ -124,32 +22,39 @@ class CellControllerBuilder {
|
|
|
// ignore: must_be_immutable
|
|
|
class CellController<T, D> extends Equatable {
|
|
|
final CellIdentifier cellId;
|
|
|
- final CellCache _cellsCache;
|
|
|
+ final CellCache _cellCache;
|
|
|
final CellCacheKey _cacheKey;
|
|
|
final FieldBackendService _fieldBackendSvc;
|
|
|
- final CellFieldNotifier _fieldNotifier;
|
|
|
+ final SingleFieldListener _fieldListener;
|
|
|
final CellDataLoader<T> _cellDataLoader;
|
|
|
final CellDataPersistence<D> _cellDataPersistence;
|
|
|
|
|
|
CellListener? _cellListener;
|
|
|
CellDataNotifier<T?>? _cellDataNotifier;
|
|
|
|
|
|
- bool isListening = false;
|
|
|
- VoidCallback? _onFieldChangedFn;
|
|
|
+ VoidCallback? _onCellFieldChanged;
|
|
|
Timer? _loadDataOperation;
|
|
|
Timer? _saveDataOperation;
|
|
|
- bool _isDispose = false;
|
|
|
+
|
|
|
+ String get viewId => cellId.viewId;
|
|
|
+
|
|
|
+ String get rowId => cellId.rowId;
|
|
|
+
|
|
|
+ String get fieldId => cellId.fieldInfo.id;
|
|
|
+
|
|
|
+ FieldInfo get fieldInfo => cellId.fieldInfo;
|
|
|
+
|
|
|
+ FieldType get fieldType => cellId.fieldInfo.fieldType;
|
|
|
|
|
|
CellController({
|
|
|
required this.cellId,
|
|
|
required CellCache cellCache,
|
|
|
- required CellFieldNotifier fieldNotifier,
|
|
|
required CellDataLoader<T> cellDataLoader,
|
|
|
required CellDataPersistence<D> cellDataPersistence,
|
|
|
- }) : _cellsCache = cellCache,
|
|
|
+ }) : _cellCache = cellCache,
|
|
|
_cellDataLoader = cellDataLoader,
|
|
|
_cellDataPersistence = cellDataPersistence,
|
|
|
- _fieldNotifier = fieldNotifier,
|
|
|
+ _fieldListener = SingleFieldListener(fieldId: cellId.fieldId),
|
|
|
_fieldBackendSvc = FieldBackendService(
|
|
|
viewId: cellId.viewId,
|
|
|
fieldId: cellId.fieldInfo.id,
|
|
@@ -157,46 +62,12 @@ class CellController<T, D> extends Equatable {
|
|
|
_cacheKey = CellCacheKey(
|
|
|
rowId: cellId.rowId,
|
|
|
fieldId: cellId.fieldInfo.id,
|
|
|
- );
|
|
|
-
|
|
|
- String get viewId => cellId.viewId;
|
|
|
-
|
|
|
- String get rowId => cellId.rowId;
|
|
|
-
|
|
|
- String get fieldId => cellId.fieldInfo.id;
|
|
|
-
|
|
|
- FieldInfo get fieldInfo => cellId.fieldInfo;
|
|
|
-
|
|
|
- FieldType get fieldType => cellId.fieldInfo.fieldType;
|
|
|
-
|
|
|
- /// Listen on the cell content or field changes
|
|
|
- ///
|
|
|
- /// An optional [listenWhenOnCellChanged] can be implemented for more
|
|
|
- /// granular control over when [listener] is called.
|
|
|
- /// [listenWhenOnCellChanged] will be invoked on each [onCellChanged]
|
|
|
- /// get called.
|
|
|
- /// [listenWhenOnCellChanged] takes the previous `value` and current
|
|
|
- /// `value` and must return a [bool] which determines whether or not
|
|
|
- /// the [onCellChanged] function will be invoked.
|
|
|
- /// [onCellChanged] is optional and if omitted, it will default to `true`.
|
|
|
- ///
|
|
|
- VoidCallback? startListening({
|
|
|
- required void Function(T?) onCellChanged,
|
|
|
- bool Function(T? oldValue, T? newValue)? listenWhenOnCellChanged,
|
|
|
- VoidCallback? onCellFieldChanged,
|
|
|
- }) {
|
|
|
- if (isListening) {
|
|
|
- Log.error("Already started. It seems like you should call clone first");
|
|
|
- return null;
|
|
|
- }
|
|
|
- isListening = true;
|
|
|
-
|
|
|
- _cellDataNotifier = CellDataNotifier(
|
|
|
- value: _cellsCache.get(_cacheKey),
|
|
|
- listenWhen: listenWhenOnCellChanged,
|
|
|
+ ) {
|
|
|
+ _cellDataNotifier = CellDataNotifier(value: _cellCache.get(_cacheKey));
|
|
|
+ _cellListener = CellListener(
|
|
|
+ rowId: cellId.rowId,
|
|
|
+ fieldId: cellId.fieldInfo.id,
|
|
|
);
|
|
|
- _cellListener =
|
|
|
- CellListener(rowId: cellId.rowId, fieldId: cellId.fieldInfo.id);
|
|
|
|
|
|
/// 1.Listen on user edit event and load the new cell data if needed.
|
|
|
/// For example:
|
|
@@ -205,7 +76,7 @@ class CellController<T, D> extends Equatable {
|
|
|
_cellListener?.start(onCellChanged: (result) {
|
|
|
result.fold(
|
|
|
(_) {
|
|
|
- _cellsCache.remove(_cacheKey);
|
|
|
+ _cellCache.remove(_cacheKey);
|
|
|
_loadData();
|
|
|
},
|
|
|
(err) => Log.error(err),
|
|
@@ -213,20 +84,25 @@ class CellController<T, D> extends Equatable {
|
|
|
});
|
|
|
|
|
|
/// 2.Listen on the field event and load the cell data if needed.
|
|
|
- _onFieldChangedFn = () {
|
|
|
- if (onCellFieldChanged != null) {
|
|
|
- onCellFieldChanged();
|
|
|
- }
|
|
|
-
|
|
|
- /// reloadOnFieldChanged should be true if you need to load the data when the corresponding field is changed
|
|
|
- /// For example:
|
|
|
- /// ¥12 -> $12
|
|
|
- if (_cellDataLoader.reloadOnFieldChanged) {
|
|
|
- _loadData();
|
|
|
- }
|
|
|
- };
|
|
|
+ _fieldListener.start(onFieldChanged: (result) {
|
|
|
+ result.fold((fieldPB) {
|
|
|
+ /// reloadOnFieldChanged should be true if you need to load the data when the corresponding field is changed
|
|
|
+ /// For example:
|
|
|
+ /// ¥12 -> $12
|
|
|
+ if (_cellDataLoader.reloadOnFieldChanged) {
|
|
|
+ _loadData();
|
|
|
+ }
|
|
|
+ _onCellFieldChanged?.call();
|
|
|
+ }, (err) => Log.error(err));
|
|
|
+ });
|
|
|
+ }
|
|
|
|
|
|
- _fieldNotifier.register(_cacheKey, _onFieldChangedFn!);
|
|
|
+ /// Listen on the cell content or field changes
|
|
|
+ VoidCallback? startListening({
|
|
|
+ required void Function(T?) onCellChanged,
|
|
|
+ VoidCallback? onCellFieldChanged,
|
|
|
+ }) {
|
|
|
+ _onCellFieldChanged = onCellFieldChanged;
|
|
|
|
|
|
/// Notify the listener, the cell data was changed.
|
|
|
onCellChangedFn() => onCellChanged(_cellDataNotifier?.value);
|
|
@@ -244,7 +120,7 @@ class CellController<T, D> extends Equatable {
|
|
|
/// The cell data will be read from the Cache first, and load from disk if it does not exist.
|
|
|
/// You can set [loadIfNotExist] to false (default is true) to disable loading the cell data.
|
|
|
T? getCellData({bool loadIfNotExist = true}) {
|
|
|
- final data = _cellsCache.get(_cacheKey);
|
|
|
+ final data = _cellCache.get(_cacheKey);
|
|
|
if (data == null && loadIfNotExist) {
|
|
|
_loadData();
|
|
|
}
|
|
@@ -294,9 +170,9 @@ class CellController<T, D> extends Equatable {
|
|
|
_loadDataOperation = Timer(const Duration(milliseconds: 10), () {
|
|
|
_cellDataLoader.loadData().then((data) {
|
|
|
if (data != null) {
|
|
|
- _cellsCache.insert(_cacheKey, GridBaseCell(object: data));
|
|
|
+ _cellCache.insert(_cacheKey, GridBaseCell(object: data));
|
|
|
} else {
|
|
|
- _cellsCache.remove(_cacheKey);
|
|
|
+ _cellCache.remove(_cacheKey);
|
|
|
}
|
|
|
|
|
|
_cellDataNotifier?.value = data;
|
|
@@ -305,54 +181,17 @@ class CellController<T, D> extends Equatable {
|
|
|
}
|
|
|
|
|
|
Future<void> dispose() async {
|
|
|
- if (_isDispose) {
|
|
|
- Log.error("$this should only dispose once");
|
|
|
- return;
|
|
|
- }
|
|
|
- _isDispose = true;
|
|
|
await _cellListener?.stop();
|
|
|
_loadDataOperation?.cancel();
|
|
|
_saveDataOperation?.cancel();
|
|
|
_cellDataNotifier?.dispose();
|
|
|
+ await _fieldListener.stop();
|
|
|
_cellDataNotifier = null;
|
|
|
-
|
|
|
- if (_onFieldChangedFn != null) {
|
|
|
- _fieldNotifier.unregister(_cacheKey, _onFieldChangedFn!);
|
|
|
- await _fieldNotifier.dispose();
|
|
|
- _onFieldChangedFn = null;
|
|
|
- }
|
|
|
}
|
|
|
|
|
|
@override
|
|
|
List<Object> get props =>
|
|
|
- [_cellsCache.get(_cacheKey) ?? "", cellId.rowId + cellId.fieldInfo.id];
|
|
|
-}
|
|
|
-
|
|
|
-class GridCellFieldNotifierImpl extends ICellFieldNotifier {
|
|
|
- final FieldController _fieldController;
|
|
|
- OnReceiveUpdateFields? _onChangesetFn;
|
|
|
-
|
|
|
- GridCellFieldNotifierImpl(FieldController cache) : _fieldController = cache;
|
|
|
-
|
|
|
- @override
|
|
|
- void onCellDispose() {
|
|
|
- if (_onChangesetFn != null) {
|
|
|
- _fieldController.removeListener(onChangesetListener: _onChangesetFn!);
|
|
|
- _onChangesetFn = null;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- @override
|
|
|
- void onCellFieldChanged(void Function(FieldInfo) callback) {
|
|
|
- _onChangesetFn = (List<FieldInfo> filedInfos) {
|
|
|
- for (final field in filedInfos) {
|
|
|
- callback(field);
|
|
|
- }
|
|
|
- };
|
|
|
- _fieldController.addListener(
|
|
|
- onReceiveFields: _onChangesetFn,
|
|
|
- );
|
|
|
- }
|
|
|
+ [_cellCache.get(_cacheKey) ?? "", cellId.rowId + cellId.fieldInfo.id];
|
|
|
}
|
|
|
|
|
|
class CellDataNotifier<T> extends ChangeNotifier {
|