refactor: customize cell data persistence

This commit is contained in:
appflowy 2022-05-11 20:38:07 +08:00
parent 19da42f210
commit 6fb163b296
27 changed files with 594 additions and 451 deletions

View File

@ -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,

View File

@ -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}";
}
}

View File

@ -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}";
}
}

View File

@ -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];
}

View File

@ -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();
}
}

View File

@ -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();
}

View File

@ -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;
}

View File

@ -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({

View File

@ -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(

View File

@ -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 ?? "",
);

View File

@ -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 ?? "");
}

View File

@ -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;

View File

@ -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';

View File

@ -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';

View File

@ -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 ?? "",
);
}

View File

@ -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';

View File

@ -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 {

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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,

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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 {