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 da1238e758..91e8559a6f 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 @@ -1,5 +1,6 @@ import 'package:app_flowy/workspace/application/grid/field/field_service.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' show Cell; import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -16,13 +17,17 @@ class DateCalBloc extends Bloc { final GridDefaultCellContext cellContext; void Function()? _onCellChangedFn; - DateCalBloc({required this.cellContext}) : super(DateCalState.initial(cellContext)) { + DateCalBloc({ + required DateTypeOption dateTypeOption, + required DateTime? selectedDay, + required this.cellContext, + }) : super(DateCalState.initial(dateTypeOption, selectedDay)) { on( (event, emit) async { await event.map( initial: (_Initial value) async { _startListening(); - await _loadDateTypeOption(emit); + // await _loadDateTypeOption(emit); }, selectDay: (_SelectDay value) { if (!isSameDay(state.selectedDay, value.day)) { @@ -46,6 +51,7 @@ class DateCalBloc extends Bloc { setTimeFormat: (_TimeFormat value) async { await _updateTypeOption(emit, timeFormat: value.timeFormat); }, + setTime: (_Time value) {}, ); }, ); @@ -71,12 +77,12 @@ class DateCalBloc extends Bloc { ); } + // ignore: unused_element Future _loadDateTypeOption(Emitter emit) async { final result = await cellContext.getTypeOptionData(); result.fold( (data) { final typeOptionData = DateTypeOption.fromBuffer(data); - DateTime? selectedDay; final cellData = cellContext.getCellData()?.data; @@ -86,7 +92,7 @@ class DateCalBloc extends Bloc { } emit(state.copyWith( - dateTypeOption: some(typeOptionData), + dateTypeOption: typeOptionData, selectedDay: selectedDay, )); }, @@ -105,35 +111,31 @@ class DateCalBloc extends Bloc { TimeFormat? timeFormat, bool? includeTime, }) async { - final newDateTypeOption = state.dateTypeOption.fold(() => null, (dateTypeOption) { - dateTypeOption.freeze(); - return dateTypeOption.rebuild((typeOption) { - if (dateFormat != null) { - typeOption.dateFormat = dateFormat; - } + state.dateTypeOption.freeze(); + final newDateTypeOption = state.dateTypeOption.rebuild((typeOption) { + if (dateFormat != null) { + typeOption.dateFormat = dateFormat; + } - if (timeFormat != null) { - typeOption.timeFormat = timeFormat; - } + if (timeFormat != null) { + typeOption.timeFormat = timeFormat; + } - if (includeTime != null) { - typeOption.includeTime = includeTime; - } - }); + if (includeTime != null) { + typeOption.includeTime = includeTime; + } }); - if (newDateTypeOption != null) { - final result = await FieldService.updateFieldTypeOption( - gridId: cellContext.gridId, - fieldId: cellContext.field.id, - typeOptionData: newDateTypeOption.writeToBuffer(), - ); + final result = await FieldService.updateFieldTypeOption( + gridId: cellContext.gridId, + fieldId: cellContext.field.id, + typeOptionData: newDateTypeOption.writeToBuffer(), + ); - result.fold( - (l) => emit(state.copyWith(dateTypeOption: Some(newDateTypeOption))), - (err) => Log.error(err), - ); - } + result.fold( + (l) => emit(state.copyWith(dateTypeOption: newDateTypeOption)), + (err) => Log.error(err), + ); } } @@ -146,23 +148,31 @@ class DateCalEvent with _$DateCalEvent { const factory DateCalEvent.setTimeFormat(TimeFormat timeFormat) = _TimeFormat; const factory DateCalEvent.setDateFormat(DateFormat dateFormat) = _DateFormat; const factory DateCalEvent.setIncludeTime(bool includeTime) = _IncludeTime; + const factory DateCalEvent.setTime(String time) = _Time; const factory DateCalEvent.didReceiveCellUpdate(Cell cell) = _DidReceiveCellUpdate; } @freezed class DateCalState with _$DateCalState { const factory DateCalState({ - required Option dateTypeOption, + required DateTypeOption dateTypeOption, required CalendarFormat format, required DateTime focusedDay, - required Option time, + required String time, + required Option inputTimeError, DateTime? selectedDay, }) = _DateCalState; - factory DateCalState.initial(GridCellContext context) => DateCalState( - dateTypeOption: none(), + factory DateCalState.initial( + DateTypeOption dateTypeOption, + DateTime? selectedDay, + ) => + DateCalState( + dateTypeOption: dateTypeOption, format: CalendarFormat.month, focusedDay: DateTime.now(), - time: none(), + selectedDay: selectedDay, + time: "", + inputTimeError: none(), ); } 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 68875d4ce3..d30be1887a 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 @@ -8,13 +8,15 @@ import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/style_widget/button.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; +import 'package:flowy_infra_ui/widget/rounded_input_field.dart'; import 'package:flowy_infra_ui/widget/spacing.dart'; +import 'package:flowy_sdk/log.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:table_calendar/table_calendar.dart'; import 'package:app_flowy/workspace/application/grid/prelude.dart'; -import 'package:dartz/dartz.dart' show Option; +import 'package:fixnum/fixnum.dart' as $fixnum; final kToday = DateTime.now(); final kFirstDay = DateTime(kToday.year, kToday.month - 3, kToday.day); @@ -35,22 +37,38 @@ class CellCalendar with FlowyOverlayDelegate { }) async { CellCalendar.remove(context); - final calendar = _CellCalendarWidget( - onSelected: onSelected, - includeTime: false, - cellContext: cellContext, - ); + final result = await cellContext.getTypeOptionData(); + result.fold( + (data) { + final typeOptionData = DateTypeOption.fromBuffer(data); + DateTime? selectedDay; + final cellData = cellContext.getCellData()?.data; - FlowyOverlay.of(context).insertWithAnchor( - widget: OverlayContainer( - child: calendar, - constraints: BoxConstraints.loose(const Size(320, 500)), - ), - identifier: CellCalendar.identifier(), - anchorContext: context, - anchorDirection: AnchorDirection.leftWithCenterAligned, - style: FlowyOverlayStyle(blur: false), - delegate: this, + if (cellData != null) { + final timestamp = $fixnum.Int64.parseInt(cellData).toInt(); + selectedDay = DateTime.fromMillisecondsSinceEpoch(timestamp * 1000); + } + + final calendar = _CellCalendarWidget( + onSelected: onSelected, + cellContext: cellContext, + dateTypeOption: typeOptionData, + selectedDay: selectedDay, + ); + + FlowyOverlay.of(context).insertWithAnchor( + widget: OverlayContainer( + child: calendar, + constraints: BoxConstraints.loose(const Size(320, 500)), + ), + identifier: CellCalendar.identifier(), + anchorContext: context, + anchorDirection: AnchorDirection.leftWithCenterAligned, + style: FlowyOverlayStyle(blur: false), + delegate: this, + ); + }, + (err) => Log.error(err), ); } @@ -70,14 +88,16 @@ class CellCalendar with FlowyOverlayDelegate { } class _CellCalendarWidget extends StatelessWidget { - final bool includeTime; final GridDefaultCellContext cellContext; + final DateTypeOption dateTypeOption; + final DateTime? selectedDay; final void Function(DateTime) onSelected; const _CellCalendarWidget({ required this.onSelected, - required this.includeTime, required this.cellContext, + required this.dateTypeOption, + this.selectedDay, Key? key, }) : super(key: key); @@ -85,7 +105,11 @@ class _CellCalendarWidget extends StatelessWidget { Widget build(BuildContext context) { final theme = context.watch(); return BlocProvider( - create: (context) => DateCalBloc(cellContext: cellContext)..add(const DateCalEvent.initial()), + create: (context) => DateCalBloc( + dateTypeOption: dateTypeOption, + selectedDay: selectedDay, + cellContext: cellContext, + )..add(const DateCalEvent.initial()), child: BlocConsumer( listener: (context, state) { if (state.selectedDay != null) { @@ -101,12 +125,11 @@ class _CellCalendarWidget extends StatelessWidget { const VSpace(10), ]); - state.dateTypeOption.foldRight(null, (dateTypeOption, _) { + if (state.dateTypeOption.includeTime) { children.addAll([ const _TimeTextField(), - const VSpace(10), ]); - }); + } children.addAll([ Divider(height: 1, color: theme.shader5), @@ -189,7 +212,7 @@ class _IncludeTimeButton extends StatelessWidget { Widget build(BuildContext context) { final theme = context.watch(); return BlocSelector( - selector: (state) => state.dateTypeOption.foldRight(false, (option, _) => option.includeTime), + selector: (state) => state.dateTypeOption.includeTime, builder: (context, includeTime) { return SizedBox( height: 50, @@ -219,7 +242,19 @@ class _TimeTextField extends StatelessWidget { @override Widget build(BuildContext context) { - return Container(); + final theme = context.watch(); + return Padding( + padding: kMargin, + child: RoundedInputField( + height: 40, + style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500), + normalBorderColor: theme.shader4, + errorBorderColor: theme.red, + cursorColor: theme.main1, + errorText: context.read().state.inputTimeError.fold(() => "", (error) => error.toString()), + onEditingComplete: (value) => context.read().add(DateCalEvent.setTime(value)), + ), + ); } } @@ -230,29 +265,33 @@ class _DateTypeOptionButton extends StatelessWidget { Widget build(BuildContext context) { final theme = context.watch(); final title = LocaleKeys.grid_field_dateFormat.tr() + " &" + LocaleKeys.grid_field_timeFormat.tr(); - return BlocSelector>( + return BlocSelector( selector: (state) => state.dateTypeOption, builder: (context, dateTypeOption) { return FlowyButton( text: FlowyText.medium(title, fontSize: 12), hoverColor: theme.hover, margin: kMargin, - onTap: () { - dateTypeOption.fold(() => null, (dateTypeOption) { - final setting = _CalDateTimeSetting(dateTypeOption: dateTypeOption); - setting.show(context); - }); - }, + onTap: () => _showTimeSetting(dateTypeOption, context), rightIcon: svgWidget("grid/more", color: theme.iconColor), ); }, ); } + + void _showTimeSetting(DateTypeOption dateTypeOption, BuildContext context) { + final setting = _CalDateTimeSetting( + dateTypeOption: dateTypeOption, + onEvent: (event) => context.read().add(event), + ); + setting.show(context); + } } class _CalDateTimeSetting extends StatefulWidget { final DateTypeOption dateTypeOption; - const _CalDateTimeSetting({required this.dateTypeOption, Key? key}) : super(key: key); + final Function(DateCalEvent) onEvent; + const _CalDateTimeSetting({required this.dateTypeOption, required this.onEvent, Key? key}) : super(key: key); @override State<_CalDateTimeSetting> createState() => _CalDateTimeSettingState(); @@ -283,9 +322,7 @@ class _CalDateTimeSettingState extends State<_CalDateTimeSetting> { DateFormatButton(onTap: () { final list = DateFormatList( selectedFormat: widget.dateTypeOption.dateFormat, - onSelected: (format) { - context.read().add(DateCalEvent.setDateFormat(format)); - }, + onSelected: (format) => widget.onEvent(DateCalEvent.setDateFormat(format)), ); _showOverlay(context, list); }), @@ -294,9 +331,7 @@ class _CalDateTimeSettingState extends State<_CalDateTimeSetting> { onTap: () { final list = TimeFormatList( selectedFormat: widget.dateTypeOption.timeFormat, - onSelected: (format) { - context.read().add(DateCalEvent.setTimeFormat(format)); - }, + onSelected: (format) => widget.onEvent(DateCalEvent.setTimeFormat(format)), ); _showOverlay(context, list); }, diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/common/text_field.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/common/text_field.dart index 5a06dbfd46..b5860b1fbb 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/common/text_field.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/common/text_field.dart @@ -53,7 +53,7 @@ class _InputTextFieldState extends State { widget.onChanged!(text); } }, - onEditingComplete: () { + onEditingComplete: (_) { if (widget.onDone != null) { widget.onDone!(_controller.text); } diff --git a/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/rounded_input_field.dart b/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/rounded_input_field.dart index 19b3accc59..66e40a380b 100644 --- a/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/rounded_input_field.dart +++ b/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/rounded_input_field.dart @@ -15,7 +15,7 @@ class RoundedInputField extends StatefulWidget { final String errorText; final TextStyle style; final ValueChanged? onChanged; - final VoidCallback? onEditingComplete; + final Function(String)? onEditingComplete; final String? initialValue; final EdgeInsets margin; final EdgeInsets padding; @@ -90,7 +90,11 @@ class _RoundedInputFieldState extends State { } setState(() {}); }, - onEditingComplete: widget.onEditingComplete, + onEditingComplete: () { + if (widget.onEditingComplete != null) { + widget.onEditingComplete!(inputText); + } + }, cursorColor: widget.cursorColor, obscureText: obscuteText, style: widget.style,