mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
refactor: customize cell data persistence
This commit is contained in:
parent
19da42f210
commit
6fb163b296
@ -164,7 +164,7 @@ void _resolveGridDeps(GetIt getIt) {
|
||||
),
|
||||
);
|
||||
|
||||
getIt.registerFactoryParam<TextCellBloc, GridDefaultCellContext, void>(
|
||||
getIt.registerFactoryParam<TextCellBloc, GridCellContext, void>(
|
||||
(context, _) => TextCellBloc(
|
||||
cellContext: context,
|
||||
),
|
||||
@ -176,19 +176,19 @@ void _resolveGridDeps(GetIt getIt) {
|
||||
),
|
||||
);
|
||||
|
||||
getIt.registerFactoryParam<NumberCellBloc, GridDefaultCellContext, void>(
|
||||
getIt.registerFactoryParam<NumberCellBloc, GridCellContext, void>(
|
||||
(context, _) => NumberCellBloc(
|
||||
cellContext: context,
|
||||
),
|
||||
);
|
||||
|
||||
getIt.registerFactoryParam<DateCellBloc, GridDefaultCellContext, void>(
|
||||
getIt.registerFactoryParam<DateCellBloc, GridDateCellContext, void>(
|
||||
(context, _) => DateCellBloc(
|
||||
cellContext: context,
|
||||
),
|
||||
);
|
||||
|
||||
getIt.registerFactoryParam<CheckboxCellBloc, GridDefaultCellContext, void>(
|
||||
getIt.registerFactoryParam<CheckboxCellBloc, GridCellContext, void>(
|
||||
(cellData, _) => CheckboxCellBloc(
|
||||
service: CellService(),
|
||||
cellContext: cellData,
|
||||
|
@ -1,379 +0,0 @@
|
||||
import 'dart:async';
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:app_flowy/workspace/application/grid/cell/select_option_service.dart';
|
||||
import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flowy_sdk/dispatch/dispatch.dart';
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/cell_entities.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:app_flowy/workspace/application/grid/cell/cell_listener.dart';
|
||||
|
||||
part 'cell_service.freezed.dart';
|
||||
|
||||
typedef GridDefaultCellContext = GridCellContext<Cell>;
|
||||
typedef GridSelectOptionCellContext = GridCellContext<SelectOptionContext>;
|
||||
|
||||
class GridCellContextBuilder {
|
||||
final GridCellCache _cellCache;
|
||||
final GridCell _gridCell;
|
||||
GridCellContextBuilder({
|
||||
required GridCellCache cellCache,
|
||||
required GridCell gridCell,
|
||||
}) : _cellCache = cellCache,
|
||||
_gridCell = gridCell;
|
||||
|
||||
GridCellContext build() {
|
||||
switch (_gridCell.field.fieldType) {
|
||||
case FieldType.Checkbox:
|
||||
case FieldType.DateTime:
|
||||
case FieldType.Number:
|
||||
return GridDefaultCellContext(
|
||||
gridCell: _gridCell,
|
||||
cellCache: _cellCache,
|
||||
cellDataLoader: DefaultCellDataLoader(gridCell: _gridCell, reloadOnCellChanged: true),
|
||||
);
|
||||
case FieldType.RichText:
|
||||
return GridDefaultCellContext(
|
||||
gridCell: _gridCell,
|
||||
cellCache: _cellCache,
|
||||
cellDataLoader: DefaultCellDataLoader(gridCell: _gridCell),
|
||||
);
|
||||
case FieldType.MultiSelect:
|
||||
case FieldType.SingleSelect:
|
||||
return GridSelectOptionCellContext(
|
||||
gridCell: _gridCell,
|
||||
cellCache: _cellCache,
|
||||
cellDataLoader: SelectOptionCellDataLoader(gridCell: _gridCell),
|
||||
);
|
||||
default:
|
||||
throw UnimplementedError;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ignore: must_be_immutable
|
||||
class GridCellContext<T> extends Equatable {
|
||||
final GridCell gridCell;
|
||||
final GridCellCache cellCache;
|
||||
final GridCellCacheKey _cacheKey;
|
||||
final GridCellDataLoader<T> cellDataLoader;
|
||||
final CellService _cellService = CellService();
|
||||
final FieldService _fieldService;
|
||||
|
||||
late final CellListener _cellListener;
|
||||
late final ValueNotifier<T?> _cellDataNotifier;
|
||||
bool isListening = false;
|
||||
VoidCallback? _onFieldChangedFn;
|
||||
Timer? _delayOperation;
|
||||
|
||||
GridCellContext({
|
||||
required this.gridCell,
|
||||
required this.cellCache,
|
||||
required this.cellDataLoader,
|
||||
}) : _fieldService = FieldService(gridId: gridCell.gridId, fieldId: gridCell.field.id),
|
||||
_cacheKey = GridCellCacheKey(objectId: gridCell.rowId, fieldId: gridCell.field.id);
|
||||
|
||||
GridCellContext<T> clone() {
|
||||
return GridCellContext(
|
||||
gridCell: gridCell,
|
||||
cellDataLoader: cellDataLoader,
|
||||
cellCache: cellCache,
|
||||
);
|
||||
}
|
||||
|
||||
String get gridId => gridCell.gridId;
|
||||
|
||||
String get rowId => gridCell.rowId;
|
||||
|
||||
String get cellId => gridCell.rowId + gridCell.field.id;
|
||||
|
||||
String get fieldId => gridCell.field.id;
|
||||
|
||||
Field get field => gridCell.field;
|
||||
|
||||
FieldType get fieldType => gridCell.field.fieldType;
|
||||
|
||||
VoidCallback? startListening({required void Function(T) onCellChanged}) {
|
||||
if (isListening) {
|
||||
Log.error("Already started. It seems like you should call clone first");
|
||||
return null;
|
||||
}
|
||||
|
||||
isListening = true;
|
||||
_cellDataNotifier = ValueNotifier(cellCache.get(_cacheKey));
|
||||
_cellListener = CellListener(rowId: gridCell.rowId, fieldId: gridCell.field.id);
|
||||
_cellListener.start(onCellChanged: (result) {
|
||||
result.fold(
|
||||
(_) => _loadData(),
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
});
|
||||
|
||||
if (cellDataLoader.reloadOnFieldChanged) {
|
||||
_onFieldChangedFn = () {
|
||||
_loadData();
|
||||
};
|
||||
cellCache.addListener(_cacheKey, _onFieldChangedFn!);
|
||||
}
|
||||
|
||||
onCellChangedFn() {
|
||||
final value = _cellDataNotifier.value;
|
||||
if (value is T) {
|
||||
onCellChanged(value);
|
||||
}
|
||||
|
||||
if (cellDataLoader.reloadOnCellChanged) {
|
||||
_loadData();
|
||||
}
|
||||
}
|
||||
|
||||
_cellDataNotifier.addListener(onCellChangedFn);
|
||||
return onCellChangedFn;
|
||||
}
|
||||
|
||||
void removeListener(VoidCallback fn) {
|
||||
_cellDataNotifier.removeListener(fn);
|
||||
}
|
||||
|
||||
T? getCellData() {
|
||||
final data = cellCache.get(_cacheKey);
|
||||
if (data == null) {
|
||||
_loadData();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
Future<Either<List<int>, FlowyError>> getTypeOptionData() {
|
||||
return _fieldService.getTypeOptionData(fieldType: fieldType);
|
||||
}
|
||||
|
||||
void saveCellData(String data) {
|
||||
_cellService.updateCell(gridId: gridId, fieldId: field.id, rowId: rowId, data: data).then((result) {
|
||||
result.fold((l) => null, (err) => Log.error(err));
|
||||
});
|
||||
}
|
||||
|
||||
void _loadData() {
|
||||
_delayOperation?.cancel();
|
||||
_delayOperation = Timer(const Duration(milliseconds: 10), () {
|
||||
cellDataLoader.loadData().then((data) {
|
||||
_cellDataNotifier.value = data;
|
||||
cellCache.insert(GridCellCacheData(key: _cacheKey, object: data));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
_delayOperation?.cancel();
|
||||
|
||||
if (_onFieldChangedFn != null) {
|
||||
cellCache.removeListener(_cacheKey, _onFieldChangedFn!);
|
||||
_onFieldChangedFn = null;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object> get props => [cellCache.get(_cacheKey) ?? "", cellId];
|
||||
}
|
||||
|
||||
abstract class GridCellDataLoader<T> {
|
||||
Future<T?> loadData();
|
||||
|
||||
bool get reloadOnFieldChanged => true;
|
||||
bool get reloadOnCellChanged => false;
|
||||
}
|
||||
|
||||
abstract class GridCellDataConfig {
|
||||
bool get reloadOnFieldChanged => true;
|
||||
bool get reloadOnCellChanged => false;
|
||||
}
|
||||
|
||||
class DefaultCellDataLoader extends GridCellDataLoader<Cell> {
|
||||
final CellService service = CellService();
|
||||
final GridCell gridCell;
|
||||
@override
|
||||
final bool reloadOnCellChanged;
|
||||
|
||||
DefaultCellDataLoader({
|
||||
required this.gridCell,
|
||||
this.reloadOnCellChanged = false,
|
||||
});
|
||||
|
||||
@override
|
||||
Future<Cell?> loadData() {
|
||||
final fut = service.getCell(
|
||||
gridId: gridCell.gridId,
|
||||
fieldId: gridCell.field.id,
|
||||
rowId: gridCell.rowId,
|
||||
);
|
||||
return fut.then((result) {
|
||||
return result.fold((data) => data, (err) {
|
||||
Log.error(err);
|
||||
return null;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// key: rowId
|
||||
typedef GridCellMap = LinkedHashMap<String, GridCell>;
|
||||
|
||||
class GridCellCacheData {
|
||||
GridCellCacheKey key;
|
||||
dynamic object;
|
||||
GridCellCacheData({
|
||||
required this.key,
|
||||
required this.object,
|
||||
});
|
||||
}
|
||||
|
||||
class GridCellCacheKey {
|
||||
final String fieldId;
|
||||
final String objectId;
|
||||
GridCellCacheKey({
|
||||
required this.fieldId,
|
||||
required this.objectId,
|
||||
});
|
||||
}
|
||||
|
||||
abstract class GridCellFieldDelegate {
|
||||
void onFieldChanged(void Function(String) callback);
|
||||
void dispose();
|
||||
}
|
||||
|
||||
class GridCellCache {
|
||||
final String gridId;
|
||||
final GridCellFieldDelegate fieldDelegate;
|
||||
|
||||
/// fieldId: {objectId: callback}
|
||||
final Map<String, Map<String, List<VoidCallback>>> _listenerByFieldId = {};
|
||||
|
||||
/// fieldId: {cacheKey: cacheData}
|
||||
final Map<String, Map<String, dynamic>> _cellDataByFieldId = {};
|
||||
GridCellCache({
|
||||
required this.gridId,
|
||||
required this.fieldDelegate,
|
||||
}) {
|
||||
fieldDelegate.onFieldChanged((fieldId) {
|
||||
_cellDataByFieldId.remove(fieldId);
|
||||
final map = _listenerByFieldId[fieldId];
|
||||
if (map != null) {
|
||||
for (final callbacks in map.values) {
|
||||
for (final callback in callbacks) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void addListener(GridCellCacheKey cacheKey, VoidCallback callback) {
|
||||
var map = _listenerByFieldId[cacheKey.fieldId];
|
||||
if (map == null) {
|
||||
_listenerByFieldId[cacheKey.fieldId] = {};
|
||||
map = _listenerByFieldId[cacheKey.fieldId];
|
||||
map![cacheKey.objectId] = [callback];
|
||||
} else {
|
||||
var objects = map[cacheKey.objectId];
|
||||
if (objects == null) {
|
||||
map[cacheKey.objectId] = [callback];
|
||||
} else {
|
||||
objects.add(callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void removeListener(GridCellCacheKey cacheKey, VoidCallback fn) {
|
||||
var callbacks = _listenerByFieldId[cacheKey.fieldId]?[cacheKey.objectId];
|
||||
final index = callbacks?.indexWhere((callback) => callback == fn);
|
||||
if (index != null && index != -1) {
|
||||
callbacks?.removeAt(index);
|
||||
}
|
||||
}
|
||||
|
||||
void insert<T extends GridCellCacheData>(T item) {
|
||||
var map = _cellDataByFieldId[item.key.fieldId];
|
||||
if (map == null) {
|
||||
_cellDataByFieldId[item.key.fieldId] = {};
|
||||
map = _cellDataByFieldId[item.key.fieldId];
|
||||
}
|
||||
|
||||
map![item.key.objectId] = item.object;
|
||||
}
|
||||
|
||||
T? get<T>(GridCellCacheKey key) {
|
||||
final map = _cellDataByFieldId[key.fieldId];
|
||||
if (map == null) {
|
||||
return null;
|
||||
} else {
|
||||
final object = map[key.objectId];
|
||||
if (object is T) {
|
||||
return object;
|
||||
} else {
|
||||
if (object != null) {
|
||||
Log.error("Cache data type does not match the cache data type");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> dispose() async {
|
||||
fieldDelegate.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class CellService {
|
||||
CellService();
|
||||
|
||||
Future<Either<void, FlowyError>> updateCell({
|
||||
required String gridId,
|
||||
required String fieldId,
|
||||
required String rowId,
|
||||
required String data,
|
||||
}) {
|
||||
final payload = CellChangeset.create()
|
||||
..gridId = gridId
|
||||
..fieldId = fieldId
|
||||
..rowId = rowId
|
||||
..cellContentChangeset = data;
|
||||
return GridEventUpdateCell(payload).send();
|
||||
}
|
||||
|
||||
Future<Either<Cell, FlowyError>> getCell({
|
||||
required String gridId,
|
||||
required String fieldId,
|
||||
required String rowId,
|
||||
}) {
|
||||
final payload = CellIdentifierPayload.create()
|
||||
..gridId = gridId
|
||||
..fieldId = fieldId
|
||||
..rowId = rowId;
|
||||
return GridEventGetCell(payload).send();
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class GridCell with _$GridCell {
|
||||
const factory GridCell({
|
||||
required String gridId,
|
||||
required String rowId,
|
||||
required Field field,
|
||||
Cell? cell,
|
||||
}) = _GridCell;
|
||||
|
||||
// ignore: unused_element
|
||||
const GridCell._();
|
||||
|
||||
String cellId() {
|
||||
return rowId + field.id + "${field.fieldType}";
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
import 'dart:async';
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flowy_sdk/dispatch/dispatch.dart';
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/cell_entities.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
import 'package:app_flowy/workspace/application/grid/cell/cell_listener.dart';
|
||||
import 'package:app_flowy/workspace/application/grid/cell/select_option_service.dart';
|
||||
import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
|
||||
|
||||
part 'cell_service.freezed.dart';
|
||||
part 'data_loader.dart';
|
||||
part 'context_builder.dart';
|
||||
part 'data_cache.dart';
|
||||
part 'data_persistence.dart';
|
||||
|
||||
// key: rowId
|
||||
|
||||
class CellService {
|
||||
CellService();
|
||||
|
||||
Future<Either<void, FlowyError>> updateCell({
|
||||
required String gridId,
|
||||
required String fieldId,
|
||||
required String rowId,
|
||||
required String data,
|
||||
}) {
|
||||
final payload = CellChangeset.create()
|
||||
..gridId = gridId
|
||||
..fieldId = fieldId
|
||||
..rowId = rowId
|
||||
..cellContentChangeset = data;
|
||||
return GridEventUpdateCell(payload).send();
|
||||
}
|
||||
|
||||
Future<Either<Cell, FlowyError>> getCell({
|
||||
required String gridId,
|
||||
required String fieldId,
|
||||
required String rowId,
|
||||
}) {
|
||||
final payload = CellIdentifierPayload.create()
|
||||
..gridId = gridId
|
||||
..fieldId = fieldId
|
||||
..rowId = rowId;
|
||||
return GridEventGetCell(payload).send();
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class GridCell with _$GridCell {
|
||||
const factory GridCell({
|
||||
required String gridId,
|
||||
required String rowId,
|
||||
required Field field,
|
||||
Cell? cell,
|
||||
}) = _GridCell;
|
||||
|
||||
// ignore: unused_element
|
||||
const GridCell._();
|
||||
|
||||
String cellId() {
|
||||
return rowId + field.id + "${field.fieldType}";
|
||||
}
|
||||
}
|
@ -0,0 +1,182 @@
|
||||
part of 'cell_service.dart';
|
||||
|
||||
typedef GridCellContext = _GridCellContext<Cell, String>;
|
||||
typedef GridSelectOptionCellContext = _GridCellContext<SelectOptionContext, String>;
|
||||
typedef GridDateCellContext = _GridCellContext<Cell, DateCellPersistenceData>;
|
||||
|
||||
class GridCellContextBuilder {
|
||||
final GridCellCache _cellCache;
|
||||
final GridCell _gridCell;
|
||||
GridCellContextBuilder({
|
||||
required GridCellCache cellCache,
|
||||
required GridCell gridCell,
|
||||
}) : _cellCache = cellCache,
|
||||
_gridCell = gridCell;
|
||||
|
||||
_GridCellContext build() {
|
||||
switch (_gridCell.field.fieldType) {
|
||||
case FieldType.Checkbox:
|
||||
return GridCellContext(
|
||||
gridCell: _gridCell,
|
||||
cellCache: _cellCache,
|
||||
cellDataLoader: CellDataLoader(gridCell: _gridCell),
|
||||
cellDataPersistence: CellDataPersistence(gridCell: _gridCell),
|
||||
);
|
||||
case FieldType.DateTime:
|
||||
return GridDateCellContext(
|
||||
gridCell: _gridCell,
|
||||
cellCache: _cellCache,
|
||||
cellDataLoader: CellDataLoader(gridCell: _gridCell),
|
||||
cellDataPersistence: NumberCellDataPersistence(gridCell: _gridCell),
|
||||
);
|
||||
case FieldType.Number:
|
||||
return GridCellContext(
|
||||
gridCell: _gridCell,
|
||||
cellCache: _cellCache,
|
||||
cellDataLoader: CellDataLoader(gridCell: _gridCell, reloadOnCellChanged: true),
|
||||
cellDataPersistence: CellDataPersistence(gridCell: _gridCell),
|
||||
);
|
||||
case FieldType.RichText:
|
||||
return GridCellContext(
|
||||
gridCell: _gridCell,
|
||||
cellCache: _cellCache,
|
||||
cellDataLoader: CellDataLoader(gridCell: _gridCell),
|
||||
cellDataPersistence: CellDataPersistence(gridCell: _gridCell),
|
||||
);
|
||||
case FieldType.MultiSelect:
|
||||
case FieldType.SingleSelect:
|
||||
return GridSelectOptionCellContext(
|
||||
gridCell: _gridCell,
|
||||
cellCache: _cellCache,
|
||||
cellDataLoader: SelectOptionCellDataLoader(gridCell: _gridCell),
|
||||
cellDataPersistence: CellDataPersistence(gridCell: _gridCell),
|
||||
);
|
||||
default:
|
||||
throw UnimplementedError;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ignore: must_be_immutable
|
||||
class _GridCellContext<T, D> extends Equatable {
|
||||
final GridCell gridCell;
|
||||
final GridCellCache cellCache;
|
||||
final GridCellCacheKey _cacheKey;
|
||||
final _GridCellDataLoader<T> cellDataLoader;
|
||||
final _GridCellDataPersistence<D> cellDataPersistence;
|
||||
final FieldService _fieldService;
|
||||
|
||||
late final CellListener _cellListener;
|
||||
late final ValueNotifier<T?> _cellDataNotifier;
|
||||
bool isListening = false;
|
||||
VoidCallback? _onFieldChangedFn;
|
||||
Timer? _delayOperation;
|
||||
|
||||
_GridCellContext({
|
||||
required this.gridCell,
|
||||
required this.cellCache,
|
||||
required this.cellDataLoader,
|
||||
required this.cellDataPersistence,
|
||||
}) : _fieldService = FieldService(gridId: gridCell.gridId, fieldId: gridCell.field.id),
|
||||
_cacheKey = GridCellCacheKey(objectId: gridCell.rowId, fieldId: gridCell.field.id);
|
||||
|
||||
_GridCellContext<T, D> clone() {
|
||||
return _GridCellContext(
|
||||
gridCell: gridCell,
|
||||
cellDataLoader: cellDataLoader,
|
||||
cellCache: cellCache,
|
||||
cellDataPersistence: cellDataPersistence);
|
||||
}
|
||||
|
||||
String get gridId => gridCell.gridId;
|
||||
|
||||
String get rowId => gridCell.rowId;
|
||||
|
||||
String get cellId => gridCell.rowId + gridCell.field.id;
|
||||
|
||||
String get fieldId => gridCell.field.id;
|
||||
|
||||
Field get field => gridCell.field;
|
||||
|
||||
FieldType get fieldType => gridCell.field.fieldType;
|
||||
|
||||
VoidCallback? startListening({required void Function(T) onCellChanged}) {
|
||||
if (isListening) {
|
||||
Log.error("Already started. It seems like you should call clone first");
|
||||
return null;
|
||||
}
|
||||
|
||||
isListening = true;
|
||||
_cellDataNotifier = ValueNotifier(cellCache.get(_cacheKey));
|
||||
_cellListener = CellListener(rowId: gridCell.rowId, fieldId: gridCell.field.id);
|
||||
_cellListener.start(onCellChanged: (result) {
|
||||
result.fold(
|
||||
(_) => _loadData(),
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
});
|
||||
|
||||
if (cellDataLoader.config.reloadOnFieldChanged) {
|
||||
_onFieldChangedFn = () {
|
||||
_loadData();
|
||||
};
|
||||
cellCache.addListener(_cacheKey, _onFieldChangedFn!);
|
||||
}
|
||||
|
||||
onCellChangedFn() {
|
||||
final value = _cellDataNotifier.value;
|
||||
if (value is T) {
|
||||
onCellChanged(value);
|
||||
}
|
||||
|
||||
if (cellDataLoader.config.reloadOnCellChanged) {
|
||||
_loadData();
|
||||
}
|
||||
}
|
||||
|
||||
_cellDataNotifier.addListener(onCellChangedFn);
|
||||
return onCellChangedFn;
|
||||
}
|
||||
|
||||
void removeListener(VoidCallback fn) {
|
||||
_cellDataNotifier.removeListener(fn);
|
||||
}
|
||||
|
||||
T? getCellData() {
|
||||
final data = cellCache.get(_cacheKey);
|
||||
if (data == null) {
|
||||
_loadData();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
Future<Either<List<int>, FlowyError>> getTypeOptionData() {
|
||||
return _fieldService.getTypeOptionData(fieldType: fieldType);
|
||||
}
|
||||
|
||||
void saveCellData(D data) {
|
||||
cellDataPersistence.save(data);
|
||||
}
|
||||
|
||||
void _loadData() {
|
||||
_delayOperation?.cancel();
|
||||
_delayOperation = Timer(const Duration(milliseconds: 10), () {
|
||||
cellDataLoader.loadData().then((data) {
|
||||
_cellDataNotifier.value = data;
|
||||
cellCache.insert(GridCellCacheData(key: _cacheKey, object: data));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
_delayOperation?.cancel();
|
||||
|
||||
if (_onFieldChangedFn != null) {
|
||||
cellCache.removeListener(_cacheKey, _onFieldChangedFn!);
|
||||
_onFieldChangedFn = null;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object> get props => [cellCache.get(_cacheKey) ?? "", cellId];
|
||||
}
|
@ -0,0 +1,109 @@
|
||||
part of 'cell_service.dart';
|
||||
|
||||
typedef GridCellMap = LinkedHashMap<String, GridCell>;
|
||||
|
||||
class GridCellCacheData {
|
||||
GridCellCacheKey key;
|
||||
dynamic object;
|
||||
GridCellCacheData({
|
||||
required this.key,
|
||||
required this.object,
|
||||
});
|
||||
}
|
||||
|
||||
class GridCellCacheKey {
|
||||
final String fieldId;
|
||||
final String objectId;
|
||||
GridCellCacheKey({
|
||||
required this.fieldId,
|
||||
required this.objectId,
|
||||
});
|
||||
}
|
||||
|
||||
abstract class GridCellFieldDelegate {
|
||||
void onFieldChanged(void Function(String) callback);
|
||||
void dispose();
|
||||
}
|
||||
|
||||
class GridCellCache {
|
||||
final String gridId;
|
||||
final GridCellFieldDelegate fieldDelegate;
|
||||
|
||||
/// fieldId: {objectId: callback}
|
||||
final Map<String, Map<String, List<VoidCallback>>> _listenerByFieldId = {};
|
||||
|
||||
/// fieldId: {cacheKey: cacheData}
|
||||
final Map<String, Map<String, dynamic>> _cellDataByFieldId = {};
|
||||
GridCellCache({
|
||||
required this.gridId,
|
||||
required this.fieldDelegate,
|
||||
}) {
|
||||
fieldDelegate.onFieldChanged((fieldId) {
|
||||
_cellDataByFieldId.remove(fieldId);
|
||||
final map = _listenerByFieldId[fieldId];
|
||||
if (map != null) {
|
||||
for (final callbacks in map.values) {
|
||||
for (final callback in callbacks) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void addListener(GridCellCacheKey cacheKey, VoidCallback callback) {
|
||||
var map = _listenerByFieldId[cacheKey.fieldId];
|
||||
if (map == null) {
|
||||
_listenerByFieldId[cacheKey.fieldId] = {};
|
||||
map = _listenerByFieldId[cacheKey.fieldId];
|
||||
map![cacheKey.objectId] = [callback];
|
||||
} else {
|
||||
var objects = map[cacheKey.objectId];
|
||||
if (objects == null) {
|
||||
map[cacheKey.objectId] = [callback];
|
||||
} else {
|
||||
objects.add(callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void removeListener(GridCellCacheKey cacheKey, VoidCallback fn) {
|
||||
var callbacks = _listenerByFieldId[cacheKey.fieldId]?[cacheKey.objectId];
|
||||
final index = callbacks?.indexWhere((callback) => callback == fn);
|
||||
if (index != null && index != -1) {
|
||||
callbacks?.removeAt(index);
|
||||
}
|
||||
}
|
||||
|
||||
void insert<T extends GridCellCacheData>(T item) {
|
||||
var map = _cellDataByFieldId[item.key.fieldId];
|
||||
if (map == null) {
|
||||
_cellDataByFieldId[item.key.fieldId] = {};
|
||||
map = _cellDataByFieldId[item.key.fieldId];
|
||||
}
|
||||
|
||||
map![item.key.objectId] = item.object;
|
||||
}
|
||||
|
||||
T? get<T>(GridCellCacheKey key) {
|
||||
final map = _cellDataByFieldId[key.fieldId];
|
||||
if (map == null) {
|
||||
return null;
|
||||
} else {
|
||||
final object = map[key.objectId];
|
||||
if (object is T) {
|
||||
return object;
|
||||
} else {
|
||||
if (object != null) {
|
||||
Log.error("Cache data type does not match the cache data type");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> dispose() async {
|
||||
fieldDelegate.dispose();
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
part of 'cell_service.dart';
|
||||
|
||||
abstract class GridCellDataConfig {
|
||||
// The cell data will reload if it receives the field's change notification.
|
||||
bool get reloadOnFieldChanged;
|
||||
|
||||
// The cell data will reload if it receives the cell's change notification.
|
||||
// For example, the number cell should be reloaded after user input the number.
|
||||
// user input: 12
|
||||
// cell display: $12
|
||||
bool get reloadOnCellChanged;
|
||||
}
|
||||
|
||||
class DefaultCellDataConfig implements GridCellDataConfig {
|
||||
@override
|
||||
final bool reloadOnCellChanged;
|
||||
|
||||
@override
|
||||
final bool reloadOnFieldChanged;
|
||||
|
||||
DefaultCellDataConfig({
|
||||
this.reloadOnCellChanged = false,
|
||||
this.reloadOnFieldChanged = false,
|
||||
});
|
||||
}
|
||||
|
||||
abstract class _GridCellDataLoader<T> {
|
||||
Future<T?> loadData();
|
||||
|
||||
GridCellDataConfig get config;
|
||||
}
|
||||
|
||||
class CellDataLoader extends _GridCellDataLoader<Cell> {
|
||||
final CellService service = CellService();
|
||||
final GridCell gridCell;
|
||||
final GridCellDataConfig _config;
|
||||
|
||||
CellDataLoader({
|
||||
required this.gridCell,
|
||||
bool reloadOnCellChanged = false,
|
||||
}) : _config = DefaultCellDataConfig(reloadOnCellChanged: reloadOnCellChanged);
|
||||
|
||||
@override
|
||||
Future<Cell?> loadData() {
|
||||
final fut = service.getCell(
|
||||
gridId: gridCell.gridId,
|
||||
fieldId: gridCell.field.id,
|
||||
rowId: gridCell.rowId,
|
||||
);
|
||||
return fut.then((result) {
|
||||
return result.fold((data) => data, (err) {
|
||||
Log.error(err);
|
||||
return null;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
GridCellDataConfig get config => _config;
|
||||
}
|
||||
|
||||
class SelectOptionCellDataLoader extends _GridCellDataLoader<SelectOptionContext> {
|
||||
final SelectOptionService service;
|
||||
final GridCell gridCell;
|
||||
SelectOptionCellDataLoader({
|
||||
required this.gridCell,
|
||||
}) : service = SelectOptionService(gridCell: gridCell);
|
||||
@override
|
||||
Future<SelectOptionContext?> loadData() async {
|
||||
return service.getOpitonContext().then((result) {
|
||||
return result.fold(
|
||||
(data) => data,
|
||||
(err) {
|
||||
Log.error(err);
|
||||
return null;
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
GridCellDataConfig get config => DefaultCellDataConfig();
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
part of 'cell_service.dart';
|
||||
|
||||
abstract class _GridCellDataPersistence<D> {
|
||||
void save(D data);
|
||||
}
|
||||
|
||||
class CellDataPersistence implements _GridCellDataPersistence<String> {
|
||||
final GridCell gridCell;
|
||||
|
||||
CellDataPersistence({
|
||||
required this.gridCell,
|
||||
});
|
||||
final CellService _cellService = CellService();
|
||||
|
||||
@override
|
||||
void save(String data) {
|
||||
_cellService
|
||||
.updateCell(
|
||||
gridId: gridCell.gridId,
|
||||
fieldId: gridCell.field.id,
|
||||
rowId: gridCell.rowId,
|
||||
data: data,
|
||||
)
|
||||
.then((result) {
|
||||
result.fold((l) => null, (err) => Log.error(err));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class DateCellPersistenceData {
|
||||
final DateTime date;
|
||||
final String? time;
|
||||
DateCellPersistenceData({
|
||||
required this.date,
|
||||
this.time,
|
||||
});
|
||||
}
|
||||
|
||||
class NumberCellDataPersistence implements _GridCellDataPersistence<DateCellPersistenceData> {
|
||||
final GridCell gridCell;
|
||||
NumberCellDataPersistence({
|
||||
required this.gridCell,
|
||||
});
|
||||
|
||||
@override
|
||||
void save(DateCellPersistenceData data) {
|
||||
var payload = DateChangesetPayload.create()..cellIdentifier = _cellIdentifier(gridCell);
|
||||
|
||||
final date = (data.date.millisecondsSinceEpoch ~/ 1000).toString();
|
||||
payload.date = date;
|
||||
|
||||
if (data.time != null) {
|
||||
payload.time = data.time!;
|
||||
}
|
||||
|
||||
GridEventUpdateDateCell(payload).send().then((result) {
|
||||
result.fold((l) => null, (err) => Log.error(err));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
CellIdentifierPayload _cellIdentifier(GridCell gridCell) {
|
||||
return CellIdentifierPayload.create()
|
||||
..gridId = gridCell.gridId
|
||||
..fieldId = gridCell.field.id
|
||||
..rowId = gridCell.rowId;
|
||||
}
|
@ -2,12 +2,12 @@ import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Cell
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
import 'cell_service.dart';
|
||||
import 'cell_service/cell_service.dart';
|
||||
|
||||
part 'checkbox_cell_bloc.freezed.dart';
|
||||
|
||||
class CheckboxCellBloc extends Bloc<CheckboxCellEvent, CheckboxCellState> {
|
||||
final GridDefaultCellContext cellContext;
|
||||
final GridCellContext cellContext;
|
||||
void Function()? _onCellChangedFn;
|
||||
|
||||
CheckboxCellBloc({
|
||||
|
@ -7,14 +7,14 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:table_calendar/table_calendar.dart';
|
||||
import 'dart:async';
|
||||
import 'cell_service.dart';
|
||||
import 'cell_service/cell_service.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:fixnum/fixnum.dart' as $fixnum;
|
||||
import 'package:protobuf/protobuf.dart';
|
||||
part 'date_cal_bloc.freezed.dart';
|
||||
|
||||
class DateCalBloc extends Bloc<DateCalEvent, DateCalState> {
|
||||
final GridDefaultCellContext cellContext;
|
||||
final GridDateCellContext cellContext;
|
||||
void Function()? _onCellChangedFn;
|
||||
|
||||
DateCalBloc({
|
||||
@ -103,8 +103,7 @@ class DateCalBloc extends Bloc<DateCalEvent, DateCalState> {
|
||||
}
|
||||
|
||||
void _updateCellData(DateTime day) {
|
||||
final data = day.millisecondsSinceEpoch ~/ 1000;
|
||||
cellContext.saveCellData(data.toString());
|
||||
cellContext.saveCellData(DateCellPersistenceData(date: day));
|
||||
}
|
||||
|
||||
Future<void>? _updateTypeOption(
|
||||
|
@ -2,11 +2,11 @@ import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Cell
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
import 'cell_service.dart';
|
||||
import 'cell_service/cell_service.dart';
|
||||
part 'date_cell_bloc.freezed.dart';
|
||||
|
||||
class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
|
||||
final GridDefaultCellContext cellContext;
|
||||
final GridDateCellContext cellContext;
|
||||
void Function()? _onCellChangedFn;
|
||||
|
||||
DateCellBloc({required this.cellContext}) : super(DateCellState.initial(cellContext)) {
|
||||
@ -17,7 +17,7 @@ class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
|
||||
_startListening();
|
||||
},
|
||||
selectDay: (_SelectDay value) {
|
||||
_updateCellData(value.day);
|
||||
cellContext.saveCellData(value.data);
|
||||
},
|
||||
didReceiveCellUpdate: (_DidReceiveCellUpdate value) {
|
||||
emit(state.copyWith(
|
||||
@ -51,17 +51,12 @@ class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
void _updateCellData(DateTime day) {
|
||||
final data = day.millisecondsSinceEpoch ~/ 1000;
|
||||
cellContext.saveCellData(data.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class DateCellEvent with _$DateCellEvent {
|
||||
const factory DateCellEvent.initial() = _InitialCell;
|
||||
const factory DateCellEvent.selectDay(DateTime day) = _SelectDay;
|
||||
const factory DateCellEvent.selectDay(DateCellPersistenceData data) = _SelectDay;
|
||||
const factory DateCellEvent.didReceiveCellUpdate(Cell cell) = _DidReceiveCellUpdate;
|
||||
const factory DateCellEvent.didReceiveFieldUpdate(Field field) = _DidReceiveFieldUpdate;
|
||||
}
|
||||
@ -73,7 +68,7 @@ class DateCellState with _$DateCellState {
|
||||
required Field field,
|
||||
}) = _DateCellState;
|
||||
|
||||
factory DateCellState.initial(GridCellContext context) => DateCellState(
|
||||
factory DateCellState.initial(GridDateCellContext context) => DateCellState(
|
||||
field: context.field,
|
||||
content: context.getCellData()?.content ?? "",
|
||||
);
|
||||
|
@ -2,12 +2,12 @@ import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
import 'cell_service.dart';
|
||||
import 'cell_service/cell_service.dart';
|
||||
|
||||
part 'number_cell_bloc.freezed.dart';
|
||||
|
||||
class NumberCellBloc extends Bloc<NumberCellEvent, NumberCellState> {
|
||||
final GridDefaultCellContext cellContext;
|
||||
final GridCellContext cellContext;
|
||||
void Function()? _onCellChangedFn;
|
||||
|
||||
NumberCellBloc({
|
||||
@ -68,7 +68,7 @@ class NumberCellState with _$NumberCellState {
|
||||
required String content,
|
||||
}) = _NumberCellState;
|
||||
|
||||
factory NumberCellState.initial(GridDefaultCellContext context) {
|
||||
factory NumberCellState.initial(GridCellContext context) {
|
||||
final cell = context.getCellData();
|
||||
return NumberCellState(content: cell?.content ?? "");
|
||||
}
|
||||
|
@ -1,33 +1,10 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flowy_sdk/dispatch/dispatch.dart';
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/cell_entities.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
|
||||
|
||||
import 'package:app_flowy/workspace/application/grid/field/type_option/type_option_service.dart';
|
||||
|
||||
import 'cell_service.dart';
|
||||
|
||||
class SelectOptionCellDataLoader extends GridCellDataLoader<SelectOptionContext> {
|
||||
final SelectOptionService service;
|
||||
final GridCell gridCell;
|
||||
SelectOptionCellDataLoader({
|
||||
required this.gridCell,
|
||||
}) : service = SelectOptionService(gridCell: gridCell);
|
||||
@override
|
||||
Future<SelectOptionContext?> loadData() async {
|
||||
return service.getOpitonContext().then((result) {
|
||||
return result.fold(
|
||||
(data) => data,
|
||||
(err) {
|
||||
Log.error(err);
|
||||
return null;
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
import 'cell_service/cell_service.dart';
|
||||
|
||||
class SelectOptionService {
|
||||
final GridCell gridCell;
|
||||
|
@ -2,7 +2,7 @@ import 'dart:async';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:app_flowy/workspace/application/grid/cell/cell_service.dart';
|
||||
import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.dart';
|
||||
|
||||
part 'selection_cell_bloc.freezed.dart';
|
||||
|
||||
|
@ -4,7 +4,7 @@ import 'package:flowy_sdk/log.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:app_flowy/workspace/application/grid/cell/cell_service.dart';
|
||||
import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.dart';
|
||||
import 'select_option_service.dart';
|
||||
|
||||
part 'selection_editor_bloc.freezed.dart';
|
||||
|
@ -2,12 +2,12 @@ import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Cell
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
import 'cell_service.dart';
|
||||
import 'cell_service/cell_service.dart';
|
||||
|
||||
part 'text_cell_bloc.freezed.dart';
|
||||
|
||||
class TextCellBloc extends Bloc<TextCellEvent, TextCellState> {
|
||||
final GridDefaultCellContext cellContext;
|
||||
final GridCellContext cellContext;
|
||||
void Function()? _onCellChangedFn;
|
||||
TextCellBloc({
|
||||
required this.cellContext,
|
||||
@ -70,7 +70,7 @@ class TextCellState with _$TextCellState {
|
||||
required String content,
|
||||
}) = _TextCellState;
|
||||
|
||||
factory TextCellState.initial(GridDefaultCellContext context) => TextCellState(
|
||||
factory TextCellState.initial(GridCellContext context) => TextCellState(
|
||||
content: context.getCellData()?.content ?? "",
|
||||
);
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/protobuf.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'cell/cell_service.dart';
|
||||
import 'cell/cell_service/cell_service.dart';
|
||||
import 'grid_service.dart';
|
||||
import 'row/row_service.dart';
|
||||
|
||||
|
@ -8,7 +8,7 @@ import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
import 'cell/cell_service.dart';
|
||||
import 'cell/cell_service/cell_service.dart';
|
||||
import 'row/row_service.dart';
|
||||
|
||||
class GridService {
|
||||
|
@ -21,7 +21,7 @@ export 'cell/number_cell_bloc.dart';
|
||||
export 'cell/selection_cell_bloc.dart';
|
||||
export 'cell/date_cell_bloc.dart';
|
||||
export 'cell/checkbox_cell_bloc.dart';
|
||||
export 'cell/cell_service.dart';
|
||||
export 'cell/cell_service/cell_service.dart';
|
||||
|
||||
// Setting
|
||||
export 'setting/setting_bloc.dart';
|
||||
|
@ -1,5 +1,5 @@
|
||||
import 'dart:collection';
|
||||
import 'package:app_flowy/workspace/application/grid/cell/cell_service.dart';
|
||||
import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Field;
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
@ -1,4 +1,4 @@
|
||||
import 'package:app_flowy/workspace/application/grid/cell/cell_service.dart';
|
||||
import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
|
@ -1,6 +1,6 @@
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:app_flowy/workspace/application/grid/cell/cell_service.dart';
|
||||
import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flowy_sdk/dispatch/dispatch.dart';
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
|
@ -1,4 +1,4 @@
|
||||
import 'package:app_flowy/workspace/application/grid/cell/cell_service.dart';
|
||||
import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show FieldType;
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
@ -32,8 +32,8 @@ class CellCalendar with FlowyOverlayDelegate {
|
||||
|
||||
Future<void> show(
|
||||
BuildContext context, {
|
||||
required GridDefaultCellContext cellContext,
|
||||
required void Function(DateTime) onSelected,
|
||||
required GridDateCellContext cellContext,
|
||||
required void Function(DateCellPersistenceData) onSelected,
|
||||
}) async {
|
||||
CellCalendar.remove(context);
|
||||
|
||||
@ -88,10 +88,10 @@ class CellCalendar with FlowyOverlayDelegate {
|
||||
}
|
||||
|
||||
class _CellCalendarWidget extends StatelessWidget {
|
||||
final GridDefaultCellContext cellContext;
|
||||
final GridDateCellContext cellContext;
|
||||
final DateTypeOption dateTypeOption;
|
||||
final DateTime? selectedDay;
|
||||
final void Function(DateTime) onSelected;
|
||||
final void Function(DateCellPersistenceData) onSelected;
|
||||
|
||||
const _CellCalendarWidget({
|
||||
required this.onSelected,
|
||||
@ -113,7 +113,7 @@ class _CellCalendarWidget extends StatelessWidget {
|
||||
child: BlocConsumer<DateCalBloc, DateCalState>(
|
||||
listener: (context, state) {
|
||||
if (state.selectedDay != null) {
|
||||
onSelected(state.selectedDay!);
|
||||
onSelected(DateCellPersistenceData(date: state.selectedDay!));
|
||||
}
|
||||
},
|
||||
listenWhen: (p, c) => p.selectedDay != c.selectedDay,
|
||||
|
@ -1,5 +1,5 @@
|
||||
import 'dart:collection';
|
||||
import 'package:app_flowy/workspace/application/grid/cell/cell_service.dart';
|
||||
import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.dart';
|
||||
import 'package:app_flowy/workspace/application/grid/cell/selection_editor_bloc.dart';
|
||||
import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
|
||||
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/type_option/edit_option_pannel.dart';
|
||||
|
@ -1,4 +1,4 @@
|
||||
import 'package:app_flowy/workspace/application/grid/cell/cell_service.dart';
|
||||
import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.dart';
|
||||
import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
|
||||
import 'package:app_flowy/workspace/application/grid/row/row_detail_bloc.dart';
|
||||
import 'package:app_flowy/workspace/application/grid/row/row_service.dart';
|
||||
|
@ -5,6 +5,7 @@ import 'package:flowy_sdk/log.dart';
|
||||
import 'package:flowy_sdk/protobuf/dart-ffi/ffi_response.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/cell_entities.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/row_entities.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
|
||||
|
@ -79,14 +79,25 @@ impl CellDataOperation for DateTypeOption {
|
||||
fn apply_changeset<T: Into<CellContentChangeset>>(
|
||||
&self,
|
||||
changeset: T,
|
||||
_cell_meta: Option<CellMeta>,
|
||||
cell_meta: Option<CellMeta>,
|
||||
) -> Result<String, FlowyError> {
|
||||
let changeset = changeset.into();
|
||||
if changeset.parse::<f64>().is_err() || changeset.parse::<i64>().is_err() {
|
||||
return Err(FlowyError::internal().context(format!("Parse {} failed", changeset)));
|
||||
};
|
||||
let content_changeset: DateCellContentChangeset = serde_json::from_str(&changeset.into())?;
|
||||
match cell_meta {
|
||||
None => Ok(TypeOptionCellData::new("", self.field_type()).json()),
|
||||
Some(cell_meta) => {
|
||||
let s = match content_changeset.timestamp() {
|
||||
None => get_cell_data(&cell_meta),
|
||||
Some(timestamp) => timestamp.to_string(),
|
||||
};
|
||||
|
||||
Ok(TypeOptionCellData::new(changeset, self.field_type()).json())
|
||||
Ok(TypeOptionCellData::new(s, self.field_type()).json())
|
||||
|
||||
// let changeset = changeset.into();
|
||||
// if changeset.parse::<f64>().is_err() || changeset.parse::<i64>().is_err() {
|
||||
// return Err(FlowyError::internal().context(format!("Parse {} failed", changeset)));
|
||||
// };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -235,6 +246,31 @@ pub struct DateCellContentChangeset {
|
||||
pub time: Option<String>,
|
||||
}
|
||||
|
||||
impl DateCellContentChangeset {
|
||||
pub fn timestamp(self) -> Option<i64> {
|
||||
let mut timestamp = 0;
|
||||
if let Some(date) = self.date {
|
||||
match date.parse::<i64>() {
|
||||
Ok(date_timestamp) => {
|
||||
timestamp += date_timestamp;
|
||||
}
|
||||
Err(_) => {}
|
||||
}
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
|
||||
if let Some(time) = self.time {
|
||||
match time.parse::<i64>() {
|
||||
Ok(time_timestamp) => timestamp += time_timestamp,
|
||||
Err(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
return Some(timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<DateChangesetParams> for CellChangeset {
|
||||
fn from(params: DateChangesetParams) -> Self {
|
||||
let changeset = DateCellContentChangeset {
|
||||
|
Loading…
Reference in New Issue
Block a user