chore: update data & time format

This commit is contained in:
appflowy 2022-05-13 22:58:49 +08:00
parent efbac6c92d
commit c6edd1a6da
14 changed files with 283 additions and 214 deletions

View File

@ -27,7 +27,7 @@ class GridCellContextBuilder {
gridCell: _gridCell, gridCell: _gridCell,
cellCache: _cellCache, cellCache: _cellCache,
cellDataLoader: DateCellDataLoader(gridCell: _gridCell), cellDataLoader: DateCellDataLoader(gridCell: _gridCell),
cellDataPersistence: NumberCellDataPersistence(gridCell: _gridCell), cellDataPersistence: DateCellDataPersistence(gridCell: _gridCell),
); );
case FieldType.Number: case FieldType.Number:
return GridCellContext( return GridCellContext(
@ -120,7 +120,7 @@ class _GridCellContext<T, D> extends Equatable {
_onFieldChangedFn = () { _onFieldChangedFn = () {
_loadData(); _loadData();
}; };
cellCache.addListener(_cacheKey, _onFieldChangedFn!); cellCache.addFieldListener(_cacheKey, _onFieldChangedFn!);
} }
onCellChangedFn() { onCellChangedFn() {
@ -172,7 +172,7 @@ class _GridCellContext<T, D> extends Equatable {
_delayOperation?.cancel(); _delayOperation?.cancel();
if (_onFieldChangedFn != null) { if (_onFieldChangedFn != null) {
cellCache.removeListener(_cacheKey, _onFieldChangedFn!); cellCache.removeFieldListener(_cacheKey, _onFieldChangedFn!);
_onFieldChangedFn = null; _onFieldChangedFn = null;
} }
} }

View File

@ -30,7 +30,7 @@ class GridCellCache {
final GridCellFieldDelegate fieldDelegate; final GridCellFieldDelegate fieldDelegate;
/// fieldId: {objectId: callback} /// fieldId: {objectId: callback}
final Map<String, Map<String, List<VoidCallback>>> _listenerByFieldId = {}; final Map<String, Map<String, List<VoidCallback>>> _fieldListenerByFieldId = {};
/// fieldId: {cacheKey: cacheData} /// fieldId: {cacheKey: cacheData}
final Map<String, Map<String, dynamic>> _cellDataByFieldId = {}; final Map<String, Map<String, dynamic>> _cellDataByFieldId = {};
@ -40,7 +40,7 @@ class GridCellCache {
}) { }) {
fieldDelegate.onFieldChanged((fieldId) { fieldDelegate.onFieldChanged((fieldId) {
_cellDataByFieldId.remove(fieldId); _cellDataByFieldId.remove(fieldId);
final map = _listenerByFieldId[fieldId]; final map = _fieldListenerByFieldId[fieldId];
if (map != null) { if (map != null) {
for (final callbacks in map.values) { for (final callbacks in map.values) {
for (final callback in callbacks) { for (final callback in callbacks) {
@ -51,24 +51,24 @@ class GridCellCache {
}); });
} }
void addListener(GridCellCacheKey cacheKey, VoidCallback callback) { void addFieldListener(GridCellCacheKey cacheKey, VoidCallback onFieldChanged) {
var map = _listenerByFieldId[cacheKey.fieldId]; var map = _fieldListenerByFieldId[cacheKey.fieldId];
if (map == null) { if (map == null) {
_listenerByFieldId[cacheKey.fieldId] = {}; _fieldListenerByFieldId[cacheKey.fieldId] = {};
map = _listenerByFieldId[cacheKey.fieldId]; map = _fieldListenerByFieldId[cacheKey.fieldId];
map![cacheKey.objectId] = [callback]; map![cacheKey.objectId] = [onFieldChanged];
} else { } else {
var objects = map[cacheKey.objectId]; var objects = map[cacheKey.objectId];
if (objects == null) { if (objects == null) {
map[cacheKey.objectId] = [callback]; map[cacheKey.objectId] = [onFieldChanged];
} else { } else {
objects.add(callback); objects.add(onFieldChanged);
} }
} }
} }
void removeListener(GridCellCacheKey cacheKey, VoidCallback fn) { void removeFieldListener(GridCellCacheKey cacheKey, VoidCallback fn) {
var callbacks = _listenerByFieldId[cacheKey.fieldId]?[cacheKey.objectId]; var callbacks = _fieldListenerByFieldId[cacheKey.fieldId]?[cacheKey.objectId];
final index = callbacks?.indexWhere((callback) => callback == fn); final index = callbacks?.indexWhere((callback) => callback == fn);
if (index != null && index != -1) { if (index != null && index != -1) {
callbacks?.removeAt(index); callbacks?.removeAt(index);

View File

@ -61,12 +61,13 @@ class CellDataLoader extends _GridCellDataLoader<Cell> {
class DateCellDataLoader extends _GridCellDataLoader<DateCellData> { class DateCellDataLoader extends _GridCellDataLoader<DateCellData> {
final GridCell gridCell; final GridCell gridCell;
final GridCellDataConfig _config;
DateCellDataLoader({ DateCellDataLoader({
required this.gridCell, required this.gridCell,
}); }) : _config = DefaultCellDataConfig(reloadOnFieldChanged: true);
@override @override
GridCellDataConfig get config => DefaultCellDataConfig(); GridCellDataConfig get config => _config;
@override @override
Future<DateCellData?> loadData() { Future<DateCellData?> loadData() {

View File

@ -35,9 +35,9 @@ class DateCellPersistenceData with _$DateCellPersistenceData {
const factory DateCellPersistenceData({required DateTime date, String? time}) = _DateCellPersistenceData; const factory DateCellPersistenceData({required DateTime date, String? time}) = _DateCellPersistenceData;
} }
class NumberCellDataPersistence implements _GridCellDataPersistence<DateCellPersistenceData> { class DateCellDataPersistence implements _GridCellDataPersistence<DateCellPersistenceData> {
final GridCell gridCell; final GridCell gridCell;
NumberCellDataPersistence({ DateCellDataPersistence({
required this.gridCell, required this.gridCell,
}); });
@ -47,10 +47,7 @@ class NumberCellDataPersistence implements _GridCellDataPersistence<DateCellPers
final date = (data.date.millisecondsSinceEpoch ~/ 1000).toString(); final date = (data.date.millisecondsSinceEpoch ~/ 1000).toString();
payload.date = date; payload.date = date;
payload.time = data.time ?? "";
if (data.time != null) {
payload.time = data.time!;
}
return GridEventUpdateDateCell(payload).send().then((result) { return GridEventUpdateDateCell(payload).send().then((result) {
return result.fold( return result.fold(

View File

@ -1,6 +1,6 @@
import 'package:app_flowy/workspace/application/grid/field/field_service.dart'; import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
import 'package:flowy_sdk/log.dart'; import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-error-code/code.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
@ -25,8 +25,8 @@ class DateCalBloc extends Bloc<DateCalEvent, DateCalState> {
(event, emit) async { (event, emit) async {
await event.when( await event.when(
initial: () async => _startListening(), initial: () async => _startListening(),
selectDay: (date) { selectDay: (date) async {
_updateDateData(emit, date: date); await _updateDateData(emit, date: date, time: state.time);
}, },
setCalFormat: (format) { setCalFormat: (format) {
emit(state.copyWith(format: format)); emit(state.copyWith(format: format));
@ -44,22 +44,19 @@ class DateCalBloc extends Bloc<DateCalEvent, DateCalState> {
setTimeFormat: (timeFormat) async { setTimeFormat: (timeFormat) async {
await _updateTypeOption(emit, timeFormat: timeFormat); await _updateTypeOption(emit, timeFormat: timeFormat);
}, },
setTime: (time) { setTime: (time) async {
_updateDateData(emit, time: time); await _updateDateData(emit, time: time);
}, },
); );
}, },
); );
} }
void _updateDateData(Emitter<DateCalState> emit, {DateTime? date, String? time}) { Future<void> _updateDateData(Emitter<DateCalState> emit, {DateTime? date, String? time}) {
state.dateData.fold( final DateCellPersistenceData newDateData = state.dateData.fold(
() { () {
var newDateData = DateCellPersistenceData(date: date ?? DateTime.now()); var newDateData = DateCellPersistenceData(date: date ?? DateTime.now());
if (time != null) { return newDateData.copyWith(time: time);
newDateData = newDateData.copyWith(time: time);
}
emit(state.copyWith(dateData: Some(newDateData)));
}, },
(dateData) { (dateData) {
var newDateData = dateData; var newDateData = dateData;
@ -70,9 +67,34 @@ class DateCalBloc extends Bloc<DateCalEvent, DateCalState> {
if (newDateData.time != time) { if (newDateData.time != time) {
newDateData = newDateData.copyWith(time: time); newDateData = newDateData.copyWith(time: time);
} }
return newDateData;
},
);
if (newDateData != dateData) { return _saveDateData(emit, newDateData);
emit(state.copyWith(dateData: Some(newDateData))); }
Future<void> _saveDateData(Emitter<DateCalState> emit, DateCellPersistenceData newDateData) async {
if (state.dateData == Some(newDateData)) {
return;
}
final result = await cellContext.saveCellData(newDateData);
result.fold(
() => emit(state.copyWith(
dateData: Some(newDateData),
timeFormatError: none(),
)),
(err) {
switch (ErrorCode.valueOf(err.code)!) {
case ErrorCode.InvalidDateTimeFormat:
emit(state.copyWith(
dateData: Some(newDateData),
timeFormatError: Some(err.toString()),
));
break;
default:
Log.error(err);
} }
}, },
); );
@ -152,7 +174,7 @@ class DateCalState with _$DateCalState {
required CalendarFormat format, required CalendarFormat format,
required DateTime focusedDay, required DateTime focusedDay,
required String time, required String time,
required Option<FlowyError> inputTimeError, required Option<String> timeFormatError,
required Option<DateCellPersistenceData> dateData, required Option<DateCellPersistenceData> dateData,
}) = _DateCalState; }) = _DateCalState;
@ -175,7 +197,7 @@ class DateCalState with _$DateCalState {
focusedDay: DateTime.now(), focusedDay: DateTime.now(),
dateData: dateData, dateData: dateData,
time: time, time: time,
inputTimeError: none(), timeFormatError: none(),
); );
} }
} }

View File

@ -16,7 +16,6 @@ class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
(event, emit) async { (event, emit) async {
event.when( event.when(
initial: () => _startListening(), initial: () => _startListening(),
selectDate: (DateCellPersistenceData value) => cellContext.saveCellData(value),
didReceiveCellUpdate: (DateCellData value) => emit(state.copyWith(data: Some(value))), didReceiveCellUpdate: (DateCellData value) => emit(state.copyWith(data: Some(value))),
didReceiveFieldUpdate: (Field value) => emit(state.copyWith(field: value)), didReceiveFieldUpdate: (Field value) => emit(state.copyWith(field: value)),
); );
@ -48,7 +47,6 @@ class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
@freezed @freezed
class DateCellEvent with _$DateCellEvent { class DateCellEvent with _$DateCellEvent {
const factory DateCellEvent.initial() = _InitialCell; const factory DateCellEvent.initial() = _InitialCell;
const factory DateCellEvent.selectDate(DateCellPersistenceData data) = _SelectDay;
const factory DateCellEvent.didReceiveCellUpdate(DateCellData data) = _DidReceiveCellUpdate; const factory DateCellEvent.didReceiveCellUpdate(DateCellData data) = _DidReceiveCellUpdate;
const factory DateCellEvent.didReceiveFieldUpdate(Field field) = _DidReceiveFieldUpdate; const factory DateCellEvent.didReceiveFieldUpdate(Field field) = _DidReceiveFieldUpdate;
} }

View File

@ -32,7 +32,6 @@ class CellCalendar with FlowyOverlayDelegate {
Future<void> show( Future<void> show(
BuildContext context, { BuildContext context, {
required GridDateCellContext cellContext, required GridDateCellContext cellContext,
required void Function(DateCellPersistenceData) onSelected,
}) async { }) async {
CellCalendar.remove(context); CellCalendar.remove(context);
@ -49,7 +48,6 @@ class CellCalendar with FlowyOverlayDelegate {
// } // }
final calendar = _CellCalendarWidget( final calendar = _CellCalendarWidget(
onSelected: onSelected,
cellContext: cellContext, cellContext: cellContext,
dateTypeOption: typeOptionData, dateTypeOption: typeOptionData,
); );
@ -88,10 +86,8 @@ class CellCalendar with FlowyOverlayDelegate {
class _CellCalendarWidget extends StatelessWidget { class _CellCalendarWidget extends StatelessWidget {
final GridDateCellContext cellContext; final GridDateCellContext cellContext;
final DateTypeOption dateTypeOption; final DateTypeOption dateTypeOption;
final void Function(DateCellPersistenceData) onSelected;
const _CellCalendarWidget({ const _CellCalendarWidget({
required this.onSelected,
required this.cellContext, required this.cellContext,
required this.dateTypeOption, required this.dateTypeOption,
Key? key, Key? key,
@ -108,40 +104,16 @@ class _CellCalendarWidget extends StatelessWidget {
cellContext: cellContext, cellContext: cellContext,
)..add(const DateCalEvent.initial()); )..add(const DateCalEvent.initial());
}, },
child: BlocConsumer<DateCalBloc, DateCalState>( child: BlocBuilder<DateCalBloc, DateCalState>(
listener: (context, state) { buildWhen: (p, c) => false,
state.dateData.fold(
() => null,
(dateData) => onSelected(dateData),
);
},
listenWhen: (p, c) => p.dateData != c.dateData,
builder: (context, state) { builder: (context, state) {
List<Widget> children = []; List<Widget> children = [
_buildCalendar(theme, context),
children.addAll([ _TimeTextField(bloc: context.read<DateCalBloc>()),
_buildCalendar(state, theme, context),
const VSpace(10),
]);
if (state.dateTypeOption.includeTime) {
children.addAll([
_TimeTextField(
text: state.time,
errorText: state.inputTimeError.fold(() => "", (error) => error.toString()),
onEditingComplete: (text) {
context.read<DateCalBloc>().add(DateCalEvent.setTime(text));
},
),
]);
}
children.addAll([
Divider(height: 1, color: theme.shader5), Divider(height: 1, color: theme.shader5),
const _IncludeTimeButton(), const _IncludeTimeButton(),
]); const _DateTypeOptionButton()
];
children.add(const _DateTypeOptionButton());
return ListView.separated( return ListView.separated(
shrinkWrap: true, shrinkWrap: true,
@ -159,7 +131,9 @@ class _CellCalendarWidget extends StatelessWidget {
); );
} }
TableCalendar<dynamic> _buildCalendar(DateCalState state, AppTheme theme, BuildContext context) { Widget _buildCalendar(AppTheme theme, BuildContext context) {
return BlocBuilder<DateCalBloc, DateCalState>(
builder: (context, state) {
return TableCalendar( return TableCalendar(
firstDay: kFirstDay, firstDay: kFirstDay,
lastDay: kLastDay, lastDay: kLastDay,
@ -210,6 +184,8 @@ class _CellCalendarWidget extends StatelessWidget {
context.read<DateCalBloc>().add(DateCalEvent.setFocusedDay(focusedDay)); context.read<DateCalBloc>().add(DateCalEvent.setFocusedDay(focusedDay));
}, },
); );
},
);
} }
} }
@ -246,14 +222,10 @@ class _IncludeTimeButton extends StatelessWidget {
} }
class _TimeTextField extends StatefulWidget { class _TimeTextField extends StatefulWidget {
final String errorText; final DateCalBloc bloc;
final String text;
final void Function(String) onEditingComplete;
const _TimeTextField({ const _TimeTextField({
required this.bloc,
Key? key, Key? key,
required this.text,
required this.errorText,
required this.onEditingComplete,
}) : super(key: key); }) : super(key: key);
@override @override
@ -267,18 +239,24 @@ class _TimeTextFieldState extends State<_TimeTextField> {
@override @override
void initState() { void initState() {
_focusNode = FocusNode(); _focusNode = FocusNode();
_controller = TextEditingController(text: widget.text); _controller = TextEditingController(text: widget.bloc.state.time);
if (widget.bloc.state.dateTypeOption.includeTime) {
_focusNode.addListener(() { _focusNode.addListener(() {
if (mounted) { if (mounted) {
widget.onEditingComplete(_controller.text); widget.bloc.add(DateCalEvent.setTime(_controller.text));
} }
}); });
}
super.initState(); super.initState();
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = context.watch<AppTheme>(); final theme = context.watch<AppTheme>();
return BlocBuilder<DateCalBloc, DateCalState>(
builder: (context, state) {
if (state.dateTypeOption.includeTime) {
return Padding( return Padding(
padding: kMargin, padding: kMargin,
child: RoundedInputField( child: RoundedInputField(
@ -288,13 +266,19 @@ class _TimeTextFieldState extends State<_TimeTextField> {
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500), style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
normalBorderColor: theme.shader4, normalBorderColor: theme.shader4,
errorBorderColor: theme.red, errorBorderColor: theme.red,
focusBorderColor: theme.main1,
cursorColor: theme.main1, cursorColor: theme.main1,
errorText: widget.errorText, errorText: state.timeFormatError.fold(() => "", (error) => error),
onEditingComplete: (value) { onEditingComplete: (value) {
widget.onEditingComplete(value); widget.bloc.add(DateCalEvent.setTime(value));
}, },
), ),
); );
} else {
return const SizedBox();
}
},
);
} }
@override @override

View File

@ -81,7 +81,6 @@ class _DateCellState extends State<DateCell> {
calendar.show( calendar.show(
context, context,
cellContext: bloc.cellContext.clone(), cellContext: bloc.cellContext.clone(),
onSelected: (data) => bloc.add(DateCellEvent.selectDate(data)),
); );
} }

View File

@ -52,7 +52,8 @@ class ErrorCode extends $pb.ProtobufEnum {
static const ErrorCode FieldNotExists = ErrorCode._(443, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'FieldNotExists'); static const ErrorCode FieldNotExists = ErrorCode._(443, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'FieldNotExists');
static const ErrorCode FieldInvalidOperation = ErrorCode._(444, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'FieldInvalidOperation'); static const ErrorCode FieldInvalidOperation = ErrorCode._(444, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'FieldInvalidOperation');
static const ErrorCode TypeOptionDataIsEmpty = ErrorCode._(450, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'TypeOptionDataIsEmpty'); static const ErrorCode TypeOptionDataIsEmpty = ErrorCode._(450, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'TypeOptionDataIsEmpty');
static const ErrorCode InvalidData = ErrorCode._(500, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'InvalidData'); static const ErrorCode InvalidDateTimeFormat = ErrorCode._(500, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'InvalidDateTimeFormat');
static const ErrorCode InvalidData = ErrorCode._(1000, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'InvalidData');
static const $core.List<ErrorCode> values = <ErrorCode> [ static const $core.List<ErrorCode> values = <ErrorCode> [
Internal, Internal,
@ -97,6 +98,7 @@ class ErrorCode extends $pb.ProtobufEnum {
FieldNotExists, FieldNotExists,
FieldInvalidOperation, FieldInvalidOperation,
TypeOptionDataIsEmpty, TypeOptionDataIsEmpty,
InvalidDateTimeFormat,
InvalidData, InvalidData,
]; ];

View File

@ -54,9 +54,10 @@ const ErrorCode$json = const {
const {'1': 'FieldNotExists', '2': 443}, const {'1': 'FieldNotExists', '2': 443},
const {'1': 'FieldInvalidOperation', '2': 444}, const {'1': 'FieldInvalidOperation', '2': 444},
const {'1': 'TypeOptionDataIsEmpty', '2': 450}, const {'1': 'TypeOptionDataIsEmpty', '2': 450},
const {'1': 'InvalidData', '2': 500}, const {'1': 'InvalidDateTimeFormat', '2': 500},
const {'1': 'InvalidData', '2': 1000},
], ],
}; };
/// Descriptor for `ErrorCode`. Decode as a `google.protobuf.EnumDescriptorProto`. /// Descriptor for `ErrorCode`. Decode as a `google.protobuf.EnumDescriptorProto`.
final $typed_data.Uint8List errorCodeDescriptor = $convert.base64Decode('CglFcnJvckNvZGUSDAoISW50ZXJuYWwQABIUChBVc2VyVW5hdXRob3JpemVkEAISEgoOUmVjb3JkTm90Rm91bmQQAxIRCg1Vc2VySWRJc0VtcHR5EAQSGAoUV29ya3NwYWNlTmFtZUludmFsaWQQZBIWChJXb3Jrc3BhY2VJZEludmFsaWQQZRIYChRBcHBDb2xvclN0eWxlSW52YWxpZBBmEhgKFFdvcmtzcGFjZURlc2NUb29Mb25nEGcSGAoUV29ya3NwYWNlTmFtZVRvb0xvbmcQaBIQCgxBcHBJZEludmFsaWQQbhISCg5BcHBOYW1lSW52YWxpZBBvEhMKD1ZpZXdOYW1lSW52YWxpZBB4EhgKFFZpZXdUaHVtYm5haWxJbnZhbGlkEHkSEQoNVmlld0lkSW52YWxpZBB6EhMKD1ZpZXdEZXNjVG9vTG9uZxB7EhMKD1ZpZXdEYXRhSW52YWxpZBB8EhMKD1ZpZXdOYW1lVG9vTG9uZxB9EhEKDENvbm5lY3RFcnJvchDIARIRCgxFbWFpbElzRW1wdHkQrAISFwoSRW1haWxGb3JtYXRJbnZhbGlkEK0CEhcKEkVtYWlsQWxyZWFkeUV4aXN0cxCuAhIUCg9QYXNzd29yZElzRW1wdHkQrwISFAoPUGFzc3dvcmRUb29Mb25nELACEiUKIFBhc3N3b3JkQ29udGFpbnNGb3JiaWRDaGFyYWN0ZXJzELECEhoKFVBhc3N3b3JkRm9ybWF0SW52YWxpZBCyAhIVChBQYXNzd29yZE5vdE1hdGNoELMCEhQKD1VzZXJOYW1lVG9vTG9uZxC0AhInCiJVc2VyTmFtZUNvbnRhaW5Gb3JiaWRkZW5DaGFyYWN0ZXJzELUCEhQKD1VzZXJOYW1lSXNFbXB0eRC2AhISCg1Vc2VySWRJbnZhbGlkELcCEhEKDFVzZXJOb3RFeGlzdBC4AhIQCgtUZXh0VG9vTG9uZxCQAxISCg1HcmlkSWRJc0VtcHR5EJoDEhMKDkJsb2NrSWRJc0VtcHR5EKQDEhEKDFJvd0lkSXNFbXB0eRCuAxIUCg9PcHRpb25JZElzRW1wdHkQrwMSEwoORmllbGRJZElzRW1wdHkQuAMSFgoRRmllbGREb2VzTm90RXhpc3QQuQMSHAoXU2VsZWN0T3B0aW9uTmFtZUlzRW1wdHkQugMSEwoORmllbGROb3RFeGlzdHMQuwMSGgoVRmllbGRJbnZhbGlkT3BlcmF0aW9uELwDEhoKFVR5cGVPcHRpb25EYXRhSXNFbXB0eRDCAxIQCgtJbnZhbGlkRGF0YRD0Aw=='); final $typed_data.Uint8List errorCodeDescriptor = $convert.base64Decode('CglFcnJvckNvZGUSDAoISW50ZXJuYWwQABIUChBVc2VyVW5hdXRob3JpemVkEAISEgoOUmVjb3JkTm90Rm91bmQQAxIRCg1Vc2VySWRJc0VtcHR5EAQSGAoUV29ya3NwYWNlTmFtZUludmFsaWQQZBIWChJXb3Jrc3BhY2VJZEludmFsaWQQZRIYChRBcHBDb2xvclN0eWxlSW52YWxpZBBmEhgKFFdvcmtzcGFjZURlc2NUb29Mb25nEGcSGAoUV29ya3NwYWNlTmFtZVRvb0xvbmcQaBIQCgxBcHBJZEludmFsaWQQbhISCg5BcHBOYW1lSW52YWxpZBBvEhMKD1ZpZXdOYW1lSW52YWxpZBB4EhgKFFZpZXdUaHVtYm5haWxJbnZhbGlkEHkSEQoNVmlld0lkSW52YWxpZBB6EhMKD1ZpZXdEZXNjVG9vTG9uZxB7EhMKD1ZpZXdEYXRhSW52YWxpZBB8EhMKD1ZpZXdOYW1lVG9vTG9uZxB9EhEKDENvbm5lY3RFcnJvchDIARIRCgxFbWFpbElzRW1wdHkQrAISFwoSRW1haWxGb3JtYXRJbnZhbGlkEK0CEhcKEkVtYWlsQWxyZWFkeUV4aXN0cxCuAhIUCg9QYXNzd29yZElzRW1wdHkQrwISFAoPUGFzc3dvcmRUb29Mb25nELACEiUKIFBhc3N3b3JkQ29udGFpbnNGb3JiaWRDaGFyYWN0ZXJzELECEhoKFVBhc3N3b3JkRm9ybWF0SW52YWxpZBCyAhIVChBQYXNzd29yZE5vdE1hdGNoELMCEhQKD1VzZXJOYW1lVG9vTG9uZxC0AhInCiJVc2VyTmFtZUNvbnRhaW5Gb3JiaWRkZW5DaGFyYWN0ZXJzELUCEhQKD1VzZXJOYW1lSXNFbXB0eRC2AhISCg1Vc2VySWRJbnZhbGlkELcCEhEKDFVzZXJOb3RFeGlzdBC4AhIQCgtUZXh0VG9vTG9uZxCQAxISCg1HcmlkSWRJc0VtcHR5EJoDEhMKDkJsb2NrSWRJc0VtcHR5EKQDEhEKDFJvd0lkSXNFbXB0eRCuAxIUCg9PcHRpb25JZElzRW1wdHkQrwMSEwoORmllbGRJZElzRW1wdHkQuAMSFgoRRmllbGREb2VzTm90RXhpc3QQuQMSHAoXU2VsZWN0T3B0aW9uTmFtZUlzRW1wdHkQugMSEwoORmllbGROb3RFeGlzdHMQuwMSGgoVRmllbGRJbnZhbGlkT3BlcmF0aW9uELwDEhoKFVR5cGVPcHRpb25EYXRhSXNFbXB0eRDCAxIaChVJbnZhbGlkRGF0ZVRpbWVGb3JtYXQQ9AMSEAoLSW52YWxpZERhdGEQ6Ac=');

View File

@ -5,6 +5,7 @@ use crate::services::row::{CellContentChangeset, CellDataOperation, DecodedCellD
use bytes::Bytes; use bytes::Bytes;
use chrono::format::strftime::StrftimeItems; use chrono::format::strftime::StrftimeItems;
use chrono::NaiveDateTime; use chrono::NaiveDateTime;
use diesel::types::Time;
use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
use flowy_error::{internal_error, ErrorCode, FlowyError, FlowyResult}; use flowy_error::{internal_error, ErrorCode, FlowyError, FlowyResult};
use flowy_grid_data_model::entities::{ use flowy_grid_data_model::entities::{
@ -30,37 +31,28 @@ pub struct DateTypeOption {
impl_type_option!(DateTypeOption, FieldType::DateTime); impl_type_option!(DateTypeOption, FieldType::DateTime);
impl DateTypeOption { impl DateTypeOption {
fn today_desc_from_timestamp(&self, timestamp: i64) -> String { fn today_desc_from_timestamp(&self, timestamp: i64, time: &Option<String>) -> String {
let native = chrono::NaiveDateTime::from_timestamp(timestamp, 0); let native = chrono::NaiveDateTime::from_timestamp(timestamp, 0);
self.today_desc_from_native(native) self.today_desc_from_native(native, time)
} }
#[allow(dead_code)] #[allow(dead_code)]
fn today_desc_from_str(&self, s: String) -> String { fn today_desc_from_str(&self, s: String, time: &Option<String>) -> String {
match NaiveDateTime::parse_from_str(&s, &self.fmt_str()) { match NaiveDateTime::parse_from_str(&s, &self.date_fmt(time)) {
Ok(native) => self.today_desc_from_native(native), Ok(native) => self.today_desc_from_native(native, time),
Err(_) => "".to_owned(), Err(_) => "".to_owned(),
} }
} }
fn today_desc_from_native(&self, native: chrono::NaiveDateTime) -> String { fn today_desc_from_native(&self, native: chrono::NaiveDateTime, time: &Option<String>) -> String {
let utc = self.utc_date_time_from_native(native); let utc = self.utc_date_time_from_native(native);
// let china_timezone = FixedOffset::east(8 * 3600); // let china_timezone = FixedOffset::east(8 * 3600);
// let a = utc.with_timezone(&china_timezone); // let a = utc.with_timezone(&china_timezone);
let output = format!("{}", utc.format_with_items(StrftimeItems::new(&self.fmt_str()))); let fmt = self.date_fmt(time);
let output = format!("{}", utc.format_with_items(StrftimeItems::new(&fmt)));
output output
} }
fn timestamp_from_str(&self, s: &str) -> FlowyResult<i64> {
match NaiveDateTime::parse_from_str(s, &self.fmt_str()) {
Ok(native) => {
let utc = self.utc_date_time_from_native(native);
Ok(utc.timestamp())
}
Err(_) => Err(ErrorCode::InvalidData.into()),
}
}
fn utc_date_time_from_timestamp(&self, timestamp: i64) -> chrono::DateTime<chrono::Utc> { fn utc_date_time_from_timestamp(&self, timestamp: i64) -> chrono::DateTime<chrono::Utc> {
let native = NaiveDateTime::from_timestamp(timestamp, 0); let native = NaiveDateTime::from_timestamp(timestamp, 0);
self.utc_date_time_from_native(native) self.utc_date_time_from_native(native)
@ -70,9 +62,18 @@ impl DateTypeOption {
chrono::DateTime::<chrono::Utc>::from_utc(naive, chrono::Utc) chrono::DateTime::<chrono::Utc>::from_utc(naive, chrono::Utc)
} }
fn fmt_str(&self) -> String { fn date_fmt(&self, time: &Option<String>) -> String {
if self.include_time { if self.include_time {
match time.as_ref() {
None => self.date_format.format_str().to_string(),
Some(time_str) => {
if time_str.is_empty() {
self.date_format.format_str().to_string()
} else {
format!("{} {}", self.date_format.format_str(), self.time_format.format_str()) format!("{} {}", self.date_format.format_str(), self.time_format.format_str())
}
}
}
} else { } else {
self.date_format.format_str().to_string() self.date_format.format_str().to_string()
} }
@ -90,29 +91,48 @@ impl DateTypeOption {
} }
let serde_cell_data = DateCellDataSerde::from_str(&result.unwrap().data)?; let serde_cell_data = DateCellDataSerde::from_str(&result.unwrap().data)?;
let time = serde_cell_data.time; let date = self.decode_cell_data_from_timestamp(&serde_cell_data).content;
let time = serde_cell_data.time.unwrap_or("".to_owned());
let timestamp = serde_cell_data.timestamp; let timestamp = serde_cell_data.timestamp;
let date = self.decode_cell_data_from_timestamp(serde_cell_data.timestamp).content;
return Ok(DateCellData { date, time, timestamp }); return Ok(DateCellData { date, time, timestamp });
} }
fn decode_cell_data_from_timestamp(&self, timestamp: i64) -> DecodedCellData { fn decode_cell_data_from_timestamp(&self, serde_cell_data: &DateCellDataSerde) -> DecodedCellData {
if timestamp == 0 { if serde_cell_data.timestamp == 0 {
return DecodedCellData::default(); return DecodedCellData::default();
} }
let cell_content = self.today_desc_from_timestamp(timestamp); let cell_content = self.today_desc_from_timestamp(serde_cell_data.timestamp, &serde_cell_data.time);
return DecodedCellData::new(timestamp.to_string(), cell_content); return DecodedCellData::new(serde_cell_data.timestamp.to_string(), cell_content);
} }
fn timestamp_from_utc_with_time(&self, utc: &chrono::DateTime<chrono::Utc>, time: &str) -> FlowyResult<i64> { fn timestamp_from_utc_with_time(
&self,
utc: &chrono::DateTime<chrono::Utc>,
time: &Option<String>,
) -> FlowyResult<i64> {
let mut date_str = format!( let mut date_str = format!(
"{}", "{}",
utc.format_with_items(StrftimeItems::new(self.date_format.format_str())) utc.format_with_items(StrftimeItems::new(self.date_format.format_str()))
); );
date_str = date_str.add(&time);
self.timestamp_from_str(&date_str) if let Some(time_str) = time.as_ref() {
if !time_str.is_empty() {
date_str = date_str.add(&time_str);
}
}
let fmt = self.date_fmt(time);
match NaiveDateTime::parse_from_str(&date_str, &fmt) {
Ok(native) => {
let utc = self.utc_date_time_from_native(native);
Ok(utc.timestamp())
}
Err(_e) => {
let msg = format!("Parse {} with format: {} failed", date_str, fmt);
Err(FlowyError::new(ErrorCode::InvalidDateTimeFormat, &msg))
}
}
} }
} }
@ -127,7 +147,7 @@ impl CellDataOperation for DateTypeOption {
return DecodedCellData::default(); return DecodedCellData::default();
} }
return match DateCellDataSerde::from_str(&type_option_cell_data.data) { return match DateCellDataSerde::from_str(&type_option_cell_data.data) {
Ok(serde_cell_data) => self.decode_cell_data_from_timestamp(serde_cell_data.timestamp), Ok(serde_cell_data) => self.decode_cell_data_from_timestamp(&serde_cell_data),
Err(_) => DecodedCellData::default(), Err(_) => DecodedCellData::default(),
}; };
} }
@ -145,12 +165,12 @@ impl CellDataOperation for DateTypeOption {
None => DateCellDataSerde::default(), None => DateCellDataSerde::default(),
Some(date_timestamp) => match (self.include_time, content_changeset.time) { Some(date_timestamp) => match (self.include_time, content_changeset.time) {
(true, Some(time)) => { (true, Some(time)) => {
let time = time.to_uppercase(); let time = Some(time.trim().to_uppercase());
let utc = self.utc_date_time_from_timestamp(date_timestamp); let utc = self.utc_date_time_from_timestamp(date_timestamp);
let timestamp = self.timestamp_from_utc_with_time(&utc, &time)?; let timestamp = self.timestamp_from_utc_with_time(&utc, &time)?;
DateCellDataSerde { timestamp, time } DateCellDataSerde::new(timestamp, time, &self.time_format)
} }
_ => DateCellDataSerde::from_timestamp(date_timestamp), _ => DateCellDataSerde::from_timestamp(date_timestamp, &self.time_format),
}, },
}; };
@ -281,14 +301,21 @@ pub struct DateCellData {
#[derive(Default, Serialize, Deserialize)] #[derive(Default, Serialize, Deserialize)]
pub struct DateCellDataSerde { pub struct DateCellDataSerde {
pub timestamp: i64, pub timestamp: i64,
pub time: String, pub time: Option<String>,
} }
impl DateCellDataSerde { impl DateCellDataSerde {
fn from_timestamp(timestamp: i64) -> Self { fn new(timestamp: i64, time: Option<String>, time_format: &TimeFormat) -> Self {
Self { Self {
timestamp, timestamp,
time: "".to_string(), time: Some(time.unwrap_or(default_time_str(time_format))),
}
}
fn from_timestamp(timestamp: i64, time_format: &TimeFormat) -> Self {
Self {
timestamp,
time: Some(default_time_str(time_format)),
} }
} }
@ -301,6 +328,13 @@ impl DateCellDataSerde {
} }
} }
fn default_time_str(time_format: &TimeFormat) -> String {
match time_format {
TimeFormat::TwelveHour => "12:00 AM".to_string(),
TimeFormat::TwentyFourHour => "00:00".to_string(),
}
}
#[derive(Clone, Debug, Default, ProtoBuf)] #[derive(Clone, Debug, Default, ProtoBuf)]
pub struct DateChangesetPayload { pub struct DateChangesetPayload {
#[pb(index = 1)] #[pb(index = 1)]
@ -439,7 +473,7 @@ mod tests {
TimeFormat::TwentyFourHour => { TimeFormat::TwentyFourHour => {
assert_eq!( assert_eq!(
"Mar 14,2022".to_owned(), "Mar 14,2022".to_owned(),
type_option.today_desc_from_timestamp(1647251762) type_option.today_desc_from_timestamp(1647251762, &None)
); );
assert_eq!( assert_eq!(
"Mar 14,2022".to_owned(), "Mar 14,2022".to_owned(),
@ -449,7 +483,7 @@ mod tests {
TimeFormat::TwelveHour => { TimeFormat::TwelveHour => {
assert_eq!( assert_eq!(
"Mar 14,2022".to_owned(), "Mar 14,2022".to_owned(),
type_option.today_desc_from_timestamp(1647251762) type_option.today_desc_from_timestamp(1647251762, &None)
); );
assert_eq!( assert_eq!(
"Mar 14,2022".to_owned(), "Mar 14,2022".to_owned(),
@ -469,24 +503,47 @@ mod tests {
type_option.include_time = true; type_option.include_time = true;
match time_format { match time_format {
TimeFormat::TwentyFourHour => { TimeFormat::TwentyFourHour => {
assert_eq!( let changeset = DateCellContentChangeset {
"May 27,2022 00:00".to_owned(), date: Some(1653609600.to_string()),
type_option.today_desc_from_timestamp(1653609600) time: None,
); };
assert_eq!( let result = type_option.apply_changeset(changeset, None).unwrap();
"May 27,2022 00:00".to_owned(), let content = type_option.decode_cell_data(result, &field_meta).content;
type_option.decode_cell_data(data(1653609600), &field_meta).content assert_eq!("May 27,2022 00:00".to_owned(), content);
);
let changeset = DateCellContentChangeset {
date: Some(1653609600.to_string()),
time: Some("23:00".to_owned()),
};
let result = type_option.apply_changeset(changeset, None).unwrap();
let content = type_option.decode_cell_data(result, &field_meta).content;
assert_eq!("May 27,2022 23:00".to_owned(), content);
} }
TimeFormat::TwelveHour => { TimeFormat::TwelveHour => {
assert_eq!( let changeset = DateCellContentChangeset {
"May 27,2022 12:00 AM".to_owned(), date: Some(1653609600.to_string()),
type_option.today_desc_from_timestamp(1653609600) time: None,
); };
assert_eq!( let result = type_option.apply_changeset(changeset, None).unwrap();
"May 27,2022 12:00 AM".to_owned(), let content = type_option.decode_cell_data(result, &field_meta).content;
type_option.decode_cell_data(data(1653609600), &field_meta).content assert_eq!("May 27,2022 12:00 AM".to_owned(), content);
);
let changeset = DateCellContentChangeset {
date: Some(1653609600.to_string()),
time: Some("".to_owned()),
};
let result = type_option.apply_changeset(changeset, None).unwrap();
let content = type_option.decode_cell_data(result, &field_meta).content;
assert_eq!("May 27,2022".to_owned(), content);
let changeset = DateCellContentChangeset {
date: Some(1653609600.to_string()),
time: Some("11:23 pm".to_owned()),
};
let result = type_option.apply_changeset(changeset, None).unwrap();
let content = type_option.decode_cell_data(result, &field_meta).content;
assert_eq!("May 27,2022 11:23 PM".to_owned(), content);
} }
} }
} }
@ -559,7 +616,7 @@ mod tests {
fn data(s: i64) -> String { fn data(s: i64) -> String {
let json = serde_json::to_string(&DateCellDataSerde { let json = serde_json::to_string(&DateCellDataSerde {
timestamp: s, timestamp: s,
time: "".to_string(), time: None,
}) })
.unwrap(); .unwrap();
TypeOptionCellData::new(&json, FieldType::DateTime).json() TypeOptionCellData::new(&json, FieldType::DateTime).json()

View File

@ -111,8 +111,11 @@ pub enum ErrorCode {
#[display(fmt = "Field's type option data should not be empty")] #[display(fmt = "Field's type option data should not be empty")]
TypeOptionDataIsEmpty = 450, TypeOptionDataIsEmpty = 450,
#[display(fmt = "Invalid date time format")]
InvalidDateTimeFormat = 500,
#[display(fmt = "Invalid data")] #[display(fmt = "Invalid data")]
InvalidData = 500, InvalidData = 1000,
} }
impl ErrorCode { impl ErrorCode {

View File

@ -67,7 +67,8 @@ pub enum ErrorCode {
FieldNotExists = 443, FieldNotExists = 443,
FieldInvalidOperation = 444, FieldInvalidOperation = 444,
TypeOptionDataIsEmpty = 450, TypeOptionDataIsEmpty = 450,
InvalidData = 500, InvalidDateTimeFormat = 500,
InvalidData = 1000,
} }
impl ::protobuf::ProtobufEnum for ErrorCode { impl ::protobuf::ProtobufEnum for ErrorCode {
@ -119,7 +120,8 @@ impl ::protobuf::ProtobufEnum for ErrorCode {
443 => ::std::option::Option::Some(ErrorCode::FieldNotExists), 443 => ::std::option::Option::Some(ErrorCode::FieldNotExists),
444 => ::std::option::Option::Some(ErrorCode::FieldInvalidOperation), 444 => ::std::option::Option::Some(ErrorCode::FieldInvalidOperation),
450 => ::std::option::Option::Some(ErrorCode::TypeOptionDataIsEmpty), 450 => ::std::option::Option::Some(ErrorCode::TypeOptionDataIsEmpty),
500 => ::std::option::Option::Some(ErrorCode::InvalidData), 500 => ::std::option::Option::Some(ErrorCode::InvalidDateTimeFormat),
1000 => ::std::option::Option::Some(ErrorCode::InvalidData),
_ => ::std::option::Option::None _ => ::std::option::Option::None
} }
} }
@ -168,6 +170,7 @@ impl ::protobuf::ProtobufEnum for ErrorCode {
ErrorCode::FieldNotExists, ErrorCode::FieldNotExists,
ErrorCode::FieldInvalidOperation, ErrorCode::FieldInvalidOperation,
ErrorCode::TypeOptionDataIsEmpty, ErrorCode::TypeOptionDataIsEmpty,
ErrorCode::InvalidDateTimeFormat,
ErrorCode::InvalidData, ErrorCode::InvalidData,
]; ];
values values
@ -197,7 +200,7 @@ impl ::protobuf::reflect::ProtobufValue for ErrorCode {
} }
static file_descriptor_proto_data: &'static [u8] = b"\ static file_descriptor_proto_data: &'static [u8] = b"\
\n\ncode.proto*\xe5\x07\n\tErrorCode\x12\x0c\n\x08Internal\x10\0\x12\x14\ \n\ncode.proto*\x81\x08\n\tErrorCode\x12\x0c\n\x08Internal\x10\0\x12\x14\
\n\x10UserUnauthorized\x10\x02\x12\x12\n\x0eRecordNotFound\x10\x03\x12\ \n\x10UserUnauthorized\x10\x02\x12\x12\n\x0eRecordNotFound\x10\x03\x12\
\x11\n\rUserIdIsEmpty\x10\x04\x12\x18\n\x14WorkspaceNameInvalid\x10d\x12\ \x11\n\rUserIdIsEmpty\x10\x04\x12\x18\n\x14WorkspaceNameInvalid\x10d\x12\
\x16\n\x12WorkspaceIdInvalid\x10e\x12\x18\n\x14AppColorStyleInvalid\x10f\ \x16\n\x12WorkspaceIdInvalid\x10e\x12\x18\n\x14AppColorStyleInvalid\x10f\
@ -220,8 +223,9 @@ static file_descriptor_proto_data: &'static [u8] = b"\
\x12\x13\n\x0eFieldIdIsEmpty\x10\xb8\x03\x12\x16\n\x11FieldDoesNotExist\ \x12\x13\n\x0eFieldIdIsEmpty\x10\xb8\x03\x12\x16\n\x11FieldDoesNotExist\
\x10\xb9\x03\x12\x1c\n\x17SelectOptionNameIsEmpty\x10\xba\x03\x12\x13\n\ \x10\xb9\x03\x12\x1c\n\x17SelectOptionNameIsEmpty\x10\xba\x03\x12\x13\n\
\x0eFieldNotExists\x10\xbb\x03\x12\x1a\n\x15FieldInvalidOperation\x10\ \x0eFieldNotExists\x10\xbb\x03\x12\x1a\n\x15FieldInvalidOperation\x10\
\xbc\x03\x12\x1a\n\x15TypeOptionDataIsEmpty\x10\xc2\x03\x12\x10\n\x0bInv\ \xbc\x03\x12\x1a\n\x15TypeOptionDataIsEmpty\x10\xc2\x03\x12\x1a\n\x15Inv\
alidData\x10\xf4\x03b\x06proto3\ alidDateTimeFormat\x10\xf4\x03\x12\x10\n\x0bInvalidData\x10\xe8\x07b\x06\
proto3\
"; ";
static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT; static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

View File

@ -43,5 +43,6 @@ enum ErrorCode {
FieldNotExists = 443; FieldNotExists = 443;
FieldInvalidOperation = 444; FieldInvalidOperation = 444;
TypeOptionDataIsEmpty = 450; TypeOptionDataIsEmpty = 450;
InvalidData = 500; InvalidDateTimeFormat = 500;
InvalidData = 1000;
} }