diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/context_builder.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/context_builder.dart index 12ec88f219..c7faff5311 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/context_builder.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/context_builder.dart @@ -2,7 +2,7 @@ part of 'cell_service.dart'; typedef GridCellContext = _GridCellContext; typedef GridSelectOptionCellContext = _GridCellContext; -typedef GridDateCellContext = _GridCellContext; +typedef GridDateCellContext = _GridCellContext; class GridCellContextBuilder { final GridCellCache _cellCache; diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/data_persistence.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/data_persistence.dart index 2b882fdcbf..e09a528e44 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/data_persistence.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/data_persistence.dart @@ -31,23 +31,26 @@ class CellDataPersistence implements _GridCellDataPersistence { } @freezed -class DateCellPersistenceData with _$DateCellPersistenceData { - const factory DateCellPersistenceData({required DateTime date, String? time}) = _DateCellPersistenceData; +class DateCalData with _$DateCalData { + const factory DateCalData({required DateTime date, String? time}) = _DateCellPersistenceData; } -class DateCellDataPersistence implements _GridCellDataPersistence { +class DateCellDataPersistence implements _GridCellDataPersistence { final GridCell gridCell; DateCellDataPersistence({ required this.gridCell, }); @override - Future> save(DateCellPersistenceData data) { + Future> save(DateCalData data) { var payload = DateChangesetPayload.create()..cellIdentifier = _cellIdentifier(gridCell); final date = (data.date.millisecondsSinceEpoch ~/ 1000).toString(); payload.date = date; - payload.time = data.time ?? ""; + + if (data.time != null) { + payload.time = data.time!; + } return GridEventUpdateDateCell(payload).send().then((result) { return result.fold( diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/date_cal_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/date_cal_bloc.dart index 0e9b4af554..a0ba35f9d6 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/date_cal_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/date_cal_bloc.dart @@ -9,7 +9,7 @@ import 'dart:async'; import 'cell_service/cell_service.dart'; import 'package:dartz/dartz.dart'; import 'package:protobuf/protobuf.dart'; -// import 'package:fixnum/fixnum.dart' as $fixnum; +import 'package:fixnum/fixnum.dart' as $fixnum; part 'date_cal_bloc.freezed.dart'; class DateCalBloc extends Bloc { @@ -34,7 +34,11 @@ class DateCalBloc extends Bloc { setFocusedDay: (focusedDay) { emit(state.copyWith(focusedDay: focusedDay)); }, - didReceiveCellUpdate: (value) {}, + didReceiveCellUpdate: (DateCellData cellData) { + final dateData = dateDataFromCellData(cellData); + final time = dateData.foldRight("", (dateData, previous) => dateData.time); + emit(state.copyWith(dateData: dateData, time: time)); + }, setIncludeTime: (includeTime) async { await _updateTypeOption(emit, includeTime: includeTime); }, @@ -53,11 +57,8 @@ class DateCalBloc extends Bloc { } Future _updateDateData(Emitter emit, {DateTime? date, String? time}) { - final DateCellPersistenceData newDateData = state.dateData.fold( - () { - var newDateData = DateCellPersistenceData(date: date ?? DateTime.now()); - return newDateData.copyWith(time: time); - }, + final DateCalData newDateData = state.dateData.fold( + () => DateCalData(date: date ?? DateTime.now(), time: time), (dateData) { var newDateData = dateData; if (date != null && !isSameDay(newDateData.date, date)) { @@ -74,7 +75,7 @@ class DateCalBloc extends Bloc { return _saveDateData(emit, newDateData); } - Future _saveDateData(Emitter emit, DateCellPersistenceData newDateData) async { + Future _saveDateData(Emitter emit, DateCalData newDateData) async { if (state.dateData == Some(newDateData)) { return; } @@ -173,31 +174,48 @@ class DateCalState with _$DateCalState { required DateTypeOption dateTypeOption, required CalendarFormat format, required DateTime focusedDay, - required String time, required Option timeFormatError, - required Option dateData, + required Option dateData, + required String? time, }) = _DateCalState; factory DateCalState.initial( DateTypeOption dateTypeOption, DateCellData? cellData, ) { - Option dateData = none(); - final time = cellData?.time ?? ""; - if (cellData != null) { - // final timestamp = $fixnum.Int64.parseInt(cellData.timestamp).toInt(); - final timestamp = cellData.timestamp * 1000; - final selectedDay = DateTime.fromMillisecondsSinceEpoch(timestamp.toInt()); - dateData = Some(DateCellPersistenceData(date: selectedDay, time: time)); - } - + Option dateData = dateDataFromCellData(cellData); + final time = dateData.foldRight("", (dateData, previous) => dateData.time); return DateCalState( dateTypeOption: dateTypeOption, format: CalendarFormat.month, focusedDay: DateTime.now(), - dateData: dateData, time: time, + dateData: dateData, timeFormatError: none(), ); } } + +Option dateDataFromCellData(DateCellData? cellData) { + String? time = timeFromCellData(cellData); + Option dateData = none(); + if (cellData != null) { + final timestamp = cellData.timestamp * 1000; + final date = DateTime.fromMillisecondsSinceEpoch(timestamp.toInt()); + dateData = Some(DateCalData(date: date, time: time)); + } + return dateData; +} + +$fixnum.Int64 timestampFromDateTime(DateTime dateTime) { + final timestamp = (dateTime.millisecondsSinceEpoch ~/ 1000); + return $fixnum.Int64(timestamp); +} + +String? timeFromCellData(DateCellData? cellData) { + String? time; + if (cellData?.hasTime() ?? false) { + time = cellData?.time; + } + return time; +} diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell/calendar.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell/calendar.dart index 6217eaa53d..c19e8c3001 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell/calendar.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell/calendar.dart @@ -254,7 +254,11 @@ class _TimeTextFieldState extends State<_TimeTextField> { @override Widget build(BuildContext context) { final theme = context.watch(); - return BlocBuilder( + return BlocConsumer( + listener: (context, state) { + _controller.text = state.time ?? ""; + }, + listenWhen: (p, c) => p.time != c.time, builder: (context, state) { if (state.dateTypeOption.includeTime) { return Padding( diff --git a/frontend/rust-lib/flowy-grid/src/event_handler.rs b/frontend/rust-lib/flowy-grid/src/event_handler.rs index 9f59899063..6e34361961 100644 --- a/frontend/rust-lib/flowy-grid/src/event_handler.rs +++ b/frontend/rust-lib/flowy-grid/src/event_handler.rs @@ -289,7 +289,7 @@ pub(crate) async fn get_date_cell_data_handler( Some(field_meta) => { let cell_meta = editor.get_cell_meta(¶ms.row_id, ¶ms.field_id).await?; let type_option = DateTypeOption::from(&field_meta); - let date_cell_data = type_option.date_cell_data(&cell_meta)?; + let date_cell_data = type_option.make_date_cell_data(&cell_meta)?; data_result(date_cell_data) } } diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs index 84bb5a517b..84cfe0e4bf 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs @@ -5,14 +5,12 @@ use crate::services::row::{CellContentChangeset, CellDataOperation, DecodedCellD use bytes::Bytes; use chrono::format::strftime::StrftimeItems; use chrono::NaiveDateTime; -use diesel::types::Time; use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; use flowy_error::{internal_error, ErrorCode, FlowyError, FlowyResult}; use flowy_grid_data_model::entities::{ CellChangeset, CellMeta, FieldMeta, FieldType, TypeOptionDataDeserializer, TypeOptionDataEntry, }; use serde::{Deserialize, Serialize}; -use std::ops::Add; use std::str::FromStr; use strum_macros::EnumIter; @@ -79,7 +77,7 @@ impl DateTypeOption { } } - pub fn date_cell_data(&self, cell_meta: &Option) -> FlowyResult { + pub fn make_date_cell_data(&self, cell_meta: &Option) -> FlowyResult { if cell_meta.is_none() { return Ok(DateCellData::default()); } @@ -112,27 +110,28 @@ impl DateTypeOption { utc: &chrono::DateTime, time: &Option, ) -> FlowyResult { - let mut date_str = format!( - "{}", - utc.format_with_items(StrftimeItems::new(self.date_format.format_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)) + let date_str = format!( + "{}{}", + utc.format_with_items(StrftimeItems::new(self.date_format.format_str())), + &time_str + ); + + return match NaiveDateTime::parse_from_str(&date_str, &self.date_fmt(time)) { + Ok(native) => { + let utc = self.utc_date_time_from_native(native); + Ok(utc.timestamp()) + } + Err(_e) => { + let msg = format!("Parse {} failed", date_str); + Err(FlowyError::new(ErrorCode::InvalidDateTimeFormat, &msg)) + } + }; } } + + return Ok(utc.timestamp()); } } @@ -170,7 +169,7 @@ impl CellDataOperation for DateTypeOption { let timestamp = self.timestamp_from_utc_with_time(&utc, &time)?; DateCellDataSerde::new(timestamp, time, &self.time_format) } - _ => DateCellDataSerde::from_timestamp(date_timestamp, &self.time_format), + _ => DateCellDataSerde::from_timestamp(date_timestamp, Some(default_time_str(&self.time_format))), }, }; @@ -312,11 +311,8 @@ impl DateCellDataSerde { } } - fn from_timestamp(timestamp: i64, time_format: &TimeFormat) -> Self { - Self { - timestamp, - time: Some(default_time_str(time_format)), - } + pub(crate) fn from_timestamp(timestamp: i64, time: Option) -> Self { + Self { timestamp, time } } fn to_string(self) -> String { @@ -614,11 +610,7 @@ mod tests { } fn data(s: i64) -> String { - let json = serde_json::to_string(&DateCellDataSerde { - timestamp: s, - time: None, - }) - .unwrap(); + let json = serde_json::to_string(&DateCellDataSerde::from_timestamp(s, None)).unwrap(); TypeOptionCellData::new(&json, FieldType::DateTime).json() } } diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option.rs index cf1ca7380f..348114e122 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option.rs @@ -78,7 +78,8 @@ mod tests { // date let date_time_field_meta = FieldBuilder::from_field_type(&FieldType::DateTime).build(); - let data = TypeOptionCellData::new("1647251762", FieldType::DateTime).json(); + let json = serde_json::to_string(&DateCellDataSerde::from_timestamp(1647251762, None)).unwrap(); + let data = TypeOptionCellData::new(&json, FieldType::DateTime).json(); assert_eq!( type_option.decode_cell_data(data, &date_time_field_meta).content, "Mar 14,2022".to_owned() diff --git a/frontend/rust-lib/flowy-grid/tests/grid/grid_test.rs b/frontend/rust-lib/flowy-grid/tests/grid/grid_test.rs index 9f8ee392d5..a954694a16 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/grid_test.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/grid_test.rs @@ -2,8 +2,8 @@ use crate::grid::script::EditorScript::*; use crate::grid::script::*; use chrono::NaiveDateTime; use flowy_grid::services::field::{ - MultiSelectTypeOption, SelectOption, SelectOptionCellContentChangeset, SingleSelectTypeOption, - SELECTION_IDS_SEPARATOR, + DateCellContentChangeset, MultiSelectTypeOption, SelectOption, SelectOptionCellContentChangeset, + SingleSelectTypeOption, SELECTION_IDS_SEPARATOR, }; use flowy_grid::services::row::{decode_cell_data, CreateRowMetaBuilder}; use flowy_grid_data_model::entities::{ @@ -240,7 +240,9 @@ async fn grid_row_add_cells_test() { builder.add_cell(&field.id, "18,443".to_owned()).unwrap(); } FieldType::DateTime => { - builder.add_cell(&field.id, "1647251762".to_owned()).unwrap(); + builder + .add_cell(&field.id, make_date_cell_string("1647251762")) + .unwrap(); } FieldType::SingleSelect => { let type_option = SingleSelectTypeOption::from(field); @@ -278,10 +280,11 @@ async fn grid_row_add_date_cell_test() { date_field = Some(field.clone()); NaiveDateTime::from_timestamp(123, 0); // The data should not be empty - assert!(builder.add_cell(&field.id, "".to_owned()).is_err()); - - assert!(builder.add_cell(&field.id, "123".to_owned()).is_ok()); - assert!(builder.add_cell(&field.id, format!("{}", timestamp)).is_ok()); + assert!(builder.add_cell(&field.id, "".to_string()).is_err()); + assert!(builder.add_cell(&field.id, make_date_cell_string("123")).is_ok()); + assert!(builder + .add_cell(&field.id, make_date_cell_string(×tamp.to_string())) + .is_ok()); } } let context = builder.build(); @@ -315,7 +318,7 @@ async fn grid_cell_update() { let data = match field_meta.field_type { FieldType::RichText => "".to_string(), FieldType::Number => "123".to_string(), - FieldType::DateTime => "123".to_string(), + FieldType::DateTime => make_date_cell_string("123"), FieldType::SingleSelect => { let type_option = SingleSelectTypeOption::from(field_meta); SelectOptionCellContentChangeset::from_insert(&type_option.options.first().unwrap().id).to_str() @@ -363,3 +366,11 @@ async fn grid_cell_update() { test.run_scripts(scripts).await; } + +fn make_date_cell_string(s: &str) -> String { + serde_json::to_string(&DateCellContentChangeset { + date: Some(s.to_string()), + time: None, + }) + .unwrap() +}