mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: rewrite date logic (#2390)
* chore: remove unused fields * chore: rewrite date logic * chore: apply suggestions from Alex * chore: add space in date format * chore: re-add error handling in apply-changeset
This commit is contained in:
@ -29,7 +29,7 @@ class TextCellDataPersistence implements CellDataPersistence<String> {
|
||||
@freezed
|
||||
class DateCellData with _$DateCellData {
|
||||
const factory DateCellData({
|
||||
required DateTime date,
|
||||
DateTime? dateTime,
|
||||
String? time,
|
||||
required bool includeTime,
|
||||
}) = _DateCellData;
|
||||
@ -45,19 +45,14 @@ class DateCellDataPersistence implements CellDataPersistence<DateCellData> {
|
||||
Future<Option<FlowyError>> save(DateCellData data) {
|
||||
var payload = DateChangesetPB.create()..cellPath = _makeCellPath(cellId);
|
||||
|
||||
// This is a bit of a hack. This converts the data.date which is in
|
||||
// UTC to Local but actually changes the timestamp instead of just
|
||||
// changing the isUtc flag
|
||||
final dateTime = DateTime(data.date.year, data.date.month, data.date.day);
|
||||
|
||||
final date = (dateTime.millisecondsSinceEpoch ~/ 1000).toString();
|
||||
payload.date = date;
|
||||
payload.isUtc = data.date.isUtc;
|
||||
payload.includeTime = data.includeTime;
|
||||
|
||||
if (data.dateTime != null) {
|
||||
final date = (data.dateTime!.millisecondsSinceEpoch ~/ 1000).toString();
|
||||
payload.date = date;
|
||||
}
|
||||
if (data.time != null) {
|
||||
payload.time = data.time!;
|
||||
}
|
||||
payload.includeTime = data.includeTime;
|
||||
|
||||
return DatabaseEventUpdateDateCell(payload).send().then((result) {
|
||||
return result.fold(
|
||||
|
@ -342,10 +342,9 @@ class RowDataBuilder {
|
||||
_cellDataByFieldId[fieldInfo.field.id] = num.toString();
|
||||
}
|
||||
|
||||
/// The date should use the UTC timezone. Becuase the backend uses UTC timezone to format the time string.
|
||||
void insertDate(FieldInfo fieldInfo, DateTime date) {
|
||||
assert(fieldInfo.fieldType == FieldType.DateTime);
|
||||
final timestamp = (date.toUtc().millisecondsSinceEpoch ~/ 1000);
|
||||
final timestamp = date.millisecondsSinceEpoch ~/ 1000;
|
||||
_cellDataByFieldId[fieldInfo.field.id] = timestamp.toString();
|
||||
}
|
||||
|
||||
|
@ -53,10 +53,6 @@ class DateTypeOptionBloc
|
||||
if (timeFormat != null) {
|
||||
typeOption.timeFormat = timeFormat;
|
||||
}
|
||||
|
||||
if (includeTime != null) {
|
||||
typeOption.includeTime = includeTime;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,6 @@ 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 'package:dartz/dartz.dart';
|
||||
import 'package:protobuf/protobuf.dart';
|
||||
|
||||
part 'date_cal_bloc.freezed.dart';
|
||||
@ -31,45 +30,39 @@ class DateCellCalendarBloc
|
||||
(event, emit) async {
|
||||
await event.when(
|
||||
initial: () async => _startListening(),
|
||||
selectDay: (date) async {
|
||||
await _updateDateData(emit, date: date, time: state.time);
|
||||
},
|
||||
setCalFormat: (format) {
|
||||
emit(state.copyWith(format: format));
|
||||
},
|
||||
setFocusedDay: (focusedDay) {
|
||||
emit(state.copyWith(focusedDay: focusedDay));
|
||||
},
|
||||
didReceiveCellUpdate: (DateCellDataPB? cellData) {
|
||||
final dateCellData = calDataFromCellData(cellData);
|
||||
final time = dateCellData.foldRight(
|
||||
"",
|
||||
(dateData, previous) => dateData.time ?? '',
|
||||
final dateData = _dateDataFromCellData(cellData);
|
||||
emit(
|
||||
state.copyWith(
|
||||
dateTime: dateData.dateTime,
|
||||
time: dateData.time,
|
||||
includeTime: dateData.includeTime,
|
||||
),
|
||||
);
|
||||
emit(state.copyWith(dateCellData: dateCellData, time: time));
|
||||
},
|
||||
didReceiveTimeFormatError: (String? timeFormatError) {
|
||||
emit(state.copyWith(timeFormatError: timeFormatError));
|
||||
},
|
||||
selectDay: (date) async {
|
||||
await _updateDateData(emit, date: date);
|
||||
},
|
||||
setIncludeTime: (includeTime) async {
|
||||
await _updateDateData(emit, includeTime: includeTime);
|
||||
},
|
||||
setTime: (time) async {
|
||||
await _updateDateData(emit, time: time);
|
||||
},
|
||||
setDateFormat: (dateFormat) async {
|
||||
await _updateTypeOption(emit, dateFormat: dateFormat);
|
||||
},
|
||||
setTimeFormat: (timeFormat) async {
|
||||
await _updateTypeOption(emit, timeFormat: timeFormat);
|
||||
},
|
||||
setTime: (time) async {
|
||||
if (state.dateCellData.isSome()) {
|
||||
await _updateDateData(emit, time: time);
|
||||
}
|
||||
setCalFormat: (format) {
|
||||
emit(state.copyWith(format: format));
|
||||
},
|
||||
didUpdateCalData:
|
||||
(Option<DateCellData> data, Option<String> timeFormatError) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
dateCellData: data,
|
||||
timeFormatError: timeFormatError,
|
||||
),
|
||||
);
|
||||
setFocusedDay: (focusedDay) {
|
||||
emit(state.copyWith(focusedDay: focusedDay));
|
||||
},
|
||||
);
|
||||
},
|
||||
@ -81,65 +74,44 @@ class DateCellCalendarBloc
|
||||
DateTime? date,
|
||||
String? time,
|
||||
bool? includeTime,
|
||||
}) {
|
||||
final DateCellData newDateData = state.dateCellData.fold(
|
||||
() => DateCellData(
|
||||
date: date ?? DateTime.now(),
|
||||
time: time,
|
||||
includeTime: includeTime ?? false,
|
||||
),
|
||||
(dateData) {
|
||||
var newDateData = dateData;
|
||||
if (date != null && !isSameDay(newDateData.date, date)) {
|
||||
newDateData = newDateData.copyWith(date: date);
|
||||
}
|
||||
}) async {
|
||||
// make sure date and time are not updated together from the UI
|
||||
assert(
|
||||
date == null && time == null ||
|
||||
date == null && time != null ||
|
||||
date != null && time == null,
|
||||
);
|
||||
String? newTime = time ?? state.time;
|
||||
|
||||
if (newDateData.time != time) {
|
||||
newDateData = newDateData.copyWith(time: time);
|
||||
}
|
||||
DateTime? newDate = date;
|
||||
if (time != null && time.isNotEmpty) {
|
||||
newDate = state.dateTime ?? DateTime.now();
|
||||
}
|
||||
|
||||
if (includeTime != null && newDateData.includeTime != includeTime) {
|
||||
newDateData = newDateData.copyWith(includeTime: includeTime);
|
||||
}
|
||||
|
||||
return newDateData;
|
||||
},
|
||||
final DateCellData newDateData = DateCellData(
|
||||
dateTime: newDate,
|
||||
time: newTime,
|
||||
includeTime: includeTime ?? state.includeTime,
|
||||
);
|
||||
|
||||
return _saveDateData(emit, newDateData);
|
||||
}
|
||||
|
||||
Future<void> _saveDateData(
|
||||
Emitter<DateCellCalendarState> emit,
|
||||
DateCellData newCalData,
|
||||
) async {
|
||||
if (state.dateCellData == Some(newCalData)) {
|
||||
return;
|
||||
}
|
||||
|
||||
updateCalData(
|
||||
Option<DateCellData> dateCellData,
|
||||
Option<String> timeFormatError,
|
||||
) {
|
||||
if (!isClosed) {
|
||||
add(
|
||||
DateCellCalendarEvent.didUpdateCalData(
|
||||
dateCellData,
|
||||
timeFormatError,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
cellController.saveCellData(
|
||||
newCalData,
|
||||
newDateData,
|
||||
onFinish: (result) {
|
||||
result.fold(
|
||||
() => updateCalData(Some(newCalData), none()),
|
||||
() {
|
||||
if (!isClosed && state.timeFormatError != null) {
|
||||
add(const DateCellCalendarEvent.didReceiveTimeFormatError(null));
|
||||
}
|
||||
},
|
||||
(err) {
|
||||
switch (ErrorCode.valueOf(err.code)!) {
|
||||
case ErrorCode.InvalidDateTimeFormat:
|
||||
updateCalData(state.dateCellData, Some(timeFormatPrompt(err)));
|
||||
if (isClosed) return;
|
||||
add(
|
||||
DateCellCalendarEvent.didReceiveTimeFormatError(
|
||||
timeFormatPrompt(err),
|
||||
),
|
||||
);
|
||||
break;
|
||||
default:
|
||||
Log.error(err);
|
||||
@ -221,25 +193,33 @@ class DateCellCalendarBloc
|
||||
|
||||
@freezed
|
||||
class DateCellCalendarEvent with _$DateCellCalendarEvent {
|
||||
// initial event
|
||||
const factory DateCellCalendarEvent.initial() = _Initial;
|
||||
const factory DateCellCalendarEvent.selectDay(DateTime day) = _SelectDay;
|
||||
|
||||
// notification that cell is updated in the backend
|
||||
const factory DateCellCalendarEvent.didReceiveCellUpdate(
|
||||
DateCellDataPB? data,
|
||||
) = _DidReceiveCellUpdate;
|
||||
const factory DateCellCalendarEvent.didReceiveTimeFormatError(
|
||||
String? timeformatError,
|
||||
) = _DidReceiveTimeFormatError;
|
||||
|
||||
// table calendar's UI settings
|
||||
const factory DateCellCalendarEvent.setFocusedDay(DateTime day) = _FocusedDay;
|
||||
const factory DateCellCalendarEvent.setCalFormat(CalendarFormat format) =
|
||||
_CalendarFormat;
|
||||
const factory DateCellCalendarEvent.setFocusedDay(DateTime day) = _FocusedDay;
|
||||
|
||||
// date cell data is modified
|
||||
const factory DateCellCalendarEvent.selectDay(DateTime day) = _SelectDay;
|
||||
const factory DateCellCalendarEvent.setTime(String time) = _Time;
|
||||
const factory DateCellCalendarEvent.setIncludeTime(bool includeTime) =
|
||||
_IncludeTime;
|
||||
|
||||
// date field type options are modified
|
||||
const factory DateCellCalendarEvent.setTimeFormat(TimeFormatPB timeFormat) =
|
||||
_TimeFormat;
|
||||
const factory DateCellCalendarEvent.setDateFormat(DateFormatPB dateFormat) =
|
||||
_DateFormat;
|
||||
const factory DateCellCalendarEvent.setIncludeTime(bool includeTime) =
|
||||
_IncludeTime;
|
||||
const factory DateCellCalendarEvent.setTime(String time) = _Time;
|
||||
const factory DateCellCalendarEvent.didReceiveCellUpdate(
|
||||
DateCellDataPB? data,
|
||||
) = _DidReceiveCellUpdate;
|
||||
const factory DateCellCalendarEvent.didUpdateCalData(
|
||||
Option<DateCellData> data,
|
||||
Option<String> timeFormatError,
|
||||
) = _DidUpdateCalData;
|
||||
}
|
||||
|
||||
@freezed
|
||||
@ -248,9 +228,10 @@ class DateCellCalendarState with _$DateCellCalendarState {
|
||||
required DateTypeOptionPB dateTypeOptionPB,
|
||||
required CalendarFormat format,
|
||||
required DateTime focusedDay,
|
||||
required Option<String> timeFormatError,
|
||||
required Option<DateCellData> dateCellData,
|
||||
required DateTime? dateTime,
|
||||
required String? time,
|
||||
required bool includeTime,
|
||||
required String? timeFormatError,
|
||||
required String timeHintText,
|
||||
}) = _DateCellCalendarState;
|
||||
|
||||
@ -258,16 +239,15 @@ class DateCellCalendarState with _$DateCellCalendarState {
|
||||
DateTypeOptionPB dateTypeOptionPB,
|
||||
DateCellDataPB? cellData,
|
||||
) {
|
||||
Option<DateCellData> dateCellData = calDataFromCellData(cellData);
|
||||
final time =
|
||||
dateCellData.foldRight("", (dateData, previous) => dateData.time ?? '');
|
||||
final dateData = _dateDataFromCellData(cellData);
|
||||
return DateCellCalendarState(
|
||||
dateTypeOptionPB: dateTypeOptionPB,
|
||||
format: CalendarFormat.month,
|
||||
focusedDay: DateTime.now(),
|
||||
time: time,
|
||||
dateCellData: dateCellData,
|
||||
timeFormatError: none(),
|
||||
dateTime: dateData.dateTime,
|
||||
time: dateData.time,
|
||||
includeTime: dateData.includeTime,
|
||||
timeFormatError: null,
|
||||
timeHintText: _timeHintText(dateTypeOptionPB),
|
||||
);
|
||||
}
|
||||
@ -284,27 +264,21 @@ String _timeHintText(DateTypeOptionPB typeOption) {
|
||||
}
|
||||
}
|
||||
|
||||
Option<DateCellData> calDataFromCellData(DateCellDataPB? cellData) {
|
||||
String? time = timeFromCellData(cellData);
|
||||
Option<DateCellData> dateData = none();
|
||||
if (cellData != null) {
|
||||
DateCellData _dateDataFromCellData(DateCellDataPB? cellData) {
|
||||
// a null DateCellDataPB may be returned, indicating that all the fields are
|
||||
// at their default values: empty strings and false booleans
|
||||
if (cellData == null) {
|
||||
return const DateCellData(includeTime: false);
|
||||
}
|
||||
|
||||
DateTime? dateTime;
|
||||
String? time;
|
||||
if (cellData.hasTimestamp()) {
|
||||
final timestamp = cellData.timestamp * 1000;
|
||||
final date = DateTime.fromMillisecondsSinceEpoch(timestamp.toInt());
|
||||
dateData = Some(
|
||||
DateCellData(
|
||||
date: date,
|
||||
time: time,
|
||||
includeTime: cellData.includeTime,
|
||||
),
|
||||
);
|
||||
dateTime = DateTime.fromMillisecondsSinceEpoch(timestamp.toInt());
|
||||
time = cellData.time;
|
||||
}
|
||||
return dateData;
|
||||
}
|
||||
bool includeTime = cellData.includeTime;
|
||||
|
||||
String? timeFromCellData(DateCellDataPB? cellData) {
|
||||
if (cellData == null || !cellData.hasTime()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return cellData.time;
|
||||
return DateCellData(dateTime: dateTime, time: time, includeTime: includeTime);
|
||||
}
|
||||
|
@ -79,7 +79,11 @@ class DateCellState with _$DateCellState {
|
||||
String _dateStrFromCellData(DateCellDataPB? cellData) {
|
||||
String dateStr = "";
|
||||
if (cellData != null) {
|
||||
dateStr = "${cellData.date} ${cellData.time}";
|
||||
if (cellData.includeTime) {
|
||||
dateStr = "${cellData.date} ${cellData.time}";
|
||||
} else {
|
||||
dateStr = cellData.date;
|
||||
}
|
||||
}
|
||||
return dateStr;
|
||||
}
|
||||
|
@ -10,8 +10,8 @@ import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra/size.dart';
|
||||
import 'package:flowy_infra/time/duration.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/widget/rounded_input_field.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pbserver.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@ -89,41 +89,35 @@ class _CellCalendarWidget extends StatefulWidget {
|
||||
|
||||
class _CellCalendarWidgetState extends State<_CellCalendarWidget> {
|
||||
late PopoverMutex popoverMutex;
|
||||
late DateCellCalendarBloc bloc;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
popoverMutex = PopoverMutex();
|
||||
|
||||
bloc = DateCellCalendarBloc(
|
||||
dateTypeOptionPB: widget.dateTypeOptionPB,
|
||||
cellData: widget.cellContext.getCellData(),
|
||||
cellController: widget.cellContext,
|
||||
)..add(const DateCellCalendarEvent.initial());
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider.value(
|
||||
value: bloc,
|
||||
return BlocProvider(
|
||||
create: (context) => DateCellCalendarBloc(
|
||||
dateTypeOptionPB: widget.dateTypeOptionPB,
|
||||
cellData: widget.cellContext.getCellData(),
|
||||
cellController: widget.cellContext,
|
||||
)..add(const DateCellCalendarEvent.initial()),
|
||||
child: BlocBuilder<DateCellCalendarBloc, DateCellCalendarState>(
|
||||
buildWhen: (p, c) => p != c,
|
||||
builder: (context, state) {
|
||||
bool includeTime = state.dateCellData
|
||||
.fold(() => false, (dateData) => dateData.includeTime);
|
||||
List<Widget> children = [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: _buildCalendar(context),
|
||||
),
|
||||
if (includeTime) ...[
|
||||
const VSpace(12.0),
|
||||
_TimeTextField(
|
||||
bloc: context.read<DateCellCalendarBloc>(),
|
||||
popoverMutex: popoverMutex,
|
||||
),
|
||||
],
|
||||
AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
child: state.includeTime
|
||||
? _TimeTextField(popoverMutex: popoverMutex)
|
||||
: const SizedBox(),
|
||||
),
|
||||
const TypeOptionSeparator(spacing: 12.0),
|
||||
const _IncludeTimeButton(),
|
||||
const TypeOptionSeparator(spacing: 12.0),
|
||||
@ -144,7 +138,6 @@ class _CellCalendarWidgetState extends State<_CellCalendarWidget> {
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
bloc.close();
|
||||
popoverMutex.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
@ -208,16 +201,11 @@ class _CellCalendarWidgetState extends State<_CellCalendarWidget> {
|
||||
outsideTextStyle:
|
||||
textStyle.textColor(Theme.of(context).disabledColor),
|
||||
),
|
||||
selectedDayPredicate: (day) {
|
||||
return state.dateCellData.fold(
|
||||
() => false,
|
||||
(dateData) => isSameDay(dateData.date, day),
|
||||
);
|
||||
},
|
||||
selectedDayPredicate: (day) => isSameDay(state.dateTime, day),
|
||||
onDaySelected: (selectedDay, focusedDay) {
|
||||
context
|
||||
.read<DateCellCalendarBloc>()
|
||||
.add(DateCellCalendarEvent.selectDay(selectedDay));
|
||||
context.read<DateCellCalendarBloc>().add(
|
||||
DateCellCalendarEvent.selectDay(selectedDay.toLocal().date),
|
||||
);
|
||||
},
|
||||
onFormatChanged: (format) {
|
||||
context
|
||||
@ -241,10 +229,7 @@ class _IncludeTimeButton extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocSelector<DateCellCalendarBloc, DateCellCalendarState, bool>(
|
||||
selector: (state) => state.dateCellData.fold(
|
||||
() => false,
|
||||
(dateData) => dateData.includeTime,
|
||||
),
|
||||
selector: (state) => state.includeTime,
|
||||
builder: (context, includeTime) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
@ -258,7 +243,7 @@ class _IncludeTimeButton extends StatelessWidget {
|
||||
"grid/clock",
|
||||
color: Theme.of(context).iconTheme.color,
|
||||
),
|
||||
const HSpace(4),
|
||||
const HSpace(6),
|
||||
FlowyText.medium(LocaleKeys.grid_field_includeTime.tr()),
|
||||
const Spacer(),
|
||||
Toggle(
|
||||
@ -280,11 +265,9 @@ class _IncludeTimeButton extends StatelessWidget {
|
||||
}
|
||||
|
||||
class _TimeTextField extends StatefulWidget {
|
||||
final DateCellCalendarBloc bloc;
|
||||
final PopoverMutex popoverMutex;
|
||||
|
||||
const _TimeTextField({
|
||||
required this.bloc,
|
||||
required this.popoverMutex,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
@ -295,18 +278,10 @@ class _TimeTextField extends StatefulWidget {
|
||||
|
||||
class _TimeTextFieldState extends State<_TimeTextField> {
|
||||
late final FocusNode _focusNode;
|
||||
late final TextEditingController _controller;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_focusNode = FocusNode();
|
||||
_controller = TextEditingController(text: widget.bloc.state.time);
|
||||
|
||||
_focusNode.addListener(() {
|
||||
if (mounted) {
|
||||
widget.bloc.add(DateCellCalendarEvent.setTime(_controller.text));
|
||||
}
|
||||
});
|
||||
|
||||
_focusNode.addListener(() {
|
||||
if (_focusNode.hasFocus) {
|
||||
@ -325,38 +300,31 @@ class _TimeTextFieldState extends State<_TimeTextField> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
_controller.text = widget.bloc.state.time ?? "";
|
||||
_controller.selection =
|
||||
TextSelection.collapsed(offset: _controller.text.length);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: Padding(
|
||||
padding: GridSize.typeOptionContentInsets,
|
||||
child: RoundedInputField(
|
||||
height: GridSize.popoverItemHeight,
|
||||
focusNode: _focusNode,
|
||||
autoFocus: true,
|
||||
hintText: widget.bloc.state.timeHintText,
|
||||
controller: _controller,
|
||||
style: Theme.of(context).textTheme.bodyMedium!,
|
||||
normalBorderColor: Theme.of(context).colorScheme.outline,
|
||||
errorBorderColor: Theme.of(context).colorScheme.error,
|
||||
focusBorderColor: Theme.of(context).colorScheme.primary,
|
||||
cursorColor: Theme.of(context).colorScheme.primary,
|
||||
errorText: widget.bloc.state.timeFormatError
|
||||
.fold(() => "", (error) => error),
|
||||
onEditingComplete: (value) =>
|
||||
widget.bloc.add(DateCellCalendarEvent.setTime(value)),
|
||||
),
|
||||
),
|
||||
return BlocBuilder<DateCellCalendarBloc, DateCellCalendarState>(
|
||||
builder: (context, state) {
|
||||
return Column(
|
||||
children: [
|
||||
const VSpace(12),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: FlowyTextField(
|
||||
text: state.time ?? "",
|
||||
focusNode: _focusNode,
|
||||
submitOnLeave: true,
|
||||
hintText: state.timeHintText,
|
||||
errorText: state.timeFormatError,
|
||||
onSubmitted: (timeString) {
|
||||
context
|
||||
.read<DateCellCalendarBloc>()
|
||||
.add(DateCellCalendarEvent.setTime(timeString));
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_focusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class _DateTypeOptionButton extends StatelessWidget {
|
||||
|
Reference in New Issue
Block a user