fix: twelve hour parsing issue (#4721)

* fix: twelve hour time format fixed

* fix: time format for 12hour

* feat: auto capitalize am-pm in formatter

* fix: time field placeholder based on user time format

* chore: bugfix + improvements

---------

Co-authored-by: Mathias Mogensen <mathias@appflowy.io>
Co-authored-by: Mathias Mogensen <42929161+Xazin@users.noreply.github.com>
This commit is contained in:
Ansah Mohammad 2024-04-08 07:03:18 +05:30 committed by GitHub
parent 9536cde789
commit 47d321b8c2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 119 additions and 78 deletions

View File

@ -258,11 +258,11 @@ class _MentionDateBlockState extends State<MentionDateBlock> {
}
DateTime _parseTime(String timeStr, UserTimeFormatPB timeFormat) {
final twelveHourFormat = DateFormat('HH:mm a');
final twelveHourFormat = DateFormat('hh:mm a');
final twentyFourHourFormat = DateFormat('HH:mm');
try {
if (timeFormat == TimeFormatPB.TwelveHour) {
if (timeFormat == UserTimeFormatPB.TwelveHour) {
return twelveHourFormat.parseStrict(timeStr);
}

View File

@ -174,85 +174,92 @@ class _AppFlowyDatePickerState extends State<AppFlowyDatePicker> {
}
Widget buildDesktopPicker() {
return Padding(
padding: const EdgeInsets.only(top: 18.0, bottom: 12.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
StartTextField(
includeTime: widget.includeTime,
timeFormat: widget.timeFormat,
timeHintText: widget.timeHintText,
parseEndTimeError: widget.parseEndTimeError,
parseTimeError: widget.parseTimeError,
timeStr: widget.timeStr,
popoverMutex: widget.popoverMutex,
onSubmitted: widget.onStartTimeSubmitted,
),
EndTextField(
includeTime: widget.includeTime,
timeFormat: widget.timeFormat,
isRange: widget.isRange,
endTimeStr: widget.endTimeStr,
popoverMutex: widget.popoverMutex,
onSubmitted: widget.onEndTimeSubmitted,
),
DatePicker(
isRange: widget.isRange,
onDaySelected: (selectedDay, focusedDay) {
widget.onDaySelected?.call(selectedDay, focusedDay);
if (widget.rebuildOnDaySelected) {
setState(() => _selectedDay = selectedDay);
}
},
onRangeSelected: widget.onRangeSelected,
selectedDay:
widget.rebuildOnDaySelected ? _selectedDay : widget.selectedDay,
firstDay: widget.firstDay,
lastDay: widget.lastDay,
startDay: widget.startDay,
endDay: widget.endDay,
onCalendarCreated: widget.onCalendarCreated,
onPageChanged: widget.onPageChanged,
),
const TypeOptionSeparator(spacing: 12.0),
if (widget.enableRanges && widget.onIsRangeChanged != null) ...[
EndTimeButton(
// GestureDetector is a workaround to stop popover from closing
// when clicking on the date picker.
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {},
child: Padding(
padding: const EdgeInsets.only(top: 18.0, bottom: 12.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
StartTextField(
includeTime: widget.includeTime,
timeFormat: widget.timeFormat,
timeHintText: widget.timeHintText,
parseEndTimeError: widget.parseEndTimeError,
parseTimeError: widget.parseTimeError,
timeStr: widget.timeStr,
popoverMutex: widget.popoverMutex,
onSubmitted: widget.onStartTimeSubmitted,
),
EndTextField(
includeTime: widget.includeTime,
timeFormat: widget.timeFormat,
isRange: widget.isRange,
onChanged: widget.onIsRangeChanged!,
endTimeStr: widget.endTimeStr,
popoverMutex: widget.popoverMutex,
onSubmitted: widget.onEndTimeSubmitted,
),
const VSpace(4.0),
],
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12.0),
child: IncludeTimeButton(
value: widget.includeTime,
onChanged: widget.onIncludeTimeChanged,
DatePicker(
isRange: widget.isRange,
onDaySelected: (selectedDay, focusedDay) {
widget.onDaySelected?.call(selectedDay, focusedDay);
if (widget.rebuildOnDaySelected) {
setState(() => _selectedDay = selectedDay);
}
},
onRangeSelected: widget.onRangeSelected,
selectedDay: widget.rebuildOnDaySelected
? _selectedDay
: widget.selectedDay,
firstDay: widget.firstDay,
lastDay: widget.lastDay,
startDay: widget.startDay,
endDay: widget.endDay,
onCalendarCreated: widget.onCalendarCreated,
onPageChanged: widget.onPageChanged,
),
const TypeOptionSeparator(spacing: 12.0),
if (widget.enableRanges && widget.onIsRangeChanged != null) ...[
EndTimeButton(
isRange: widget.isRange,
onChanged: widget.onIsRangeChanged!,
),
const VSpace(4.0),
],
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12.0),
child: IncludeTimeButton(
value: widget.includeTime,
onChanged: widget.onIncludeTimeChanged,
),
),
),
const _GroupSeparator(),
ReminderSelector(
mutex: widget.popoverMutex,
hasTime: widget.includeTime,
timeFormat: widget.timeFormat,
selectedOption: _selectedReminderOption,
onOptionSelected: (option) {
setState(() => _selectedReminderOption = option);
widget.onReminderSelected?.call(option);
},
),
if (widget.options?.isNotEmpty ?? false) ...[
const _GroupSeparator(),
ListView.separated(
shrinkWrap: true,
itemCount: widget.options!.length,
separatorBuilder: (_, __) => const _GroupSeparator(),
itemBuilder: (_, index) =>
_renderGroupOptions(widget.options![index].options),
ReminderSelector(
mutex: widget.popoverMutex,
hasTime: widget.includeTime,
timeFormat: widget.timeFormat,
selectedOption: _selectedReminderOption,
onOptionSelected: (option) {
setState(() => _selectedReminderOption = option);
widget.onReminderSelected?.call(option);
},
),
if (widget.options?.isNotEmpty ?? false) ...[
const _GroupSeparator(),
ListView.separated(
shrinkWrap: true,
itemCount: widget.options!.length,
separatorBuilder: (_, __) => const _GroupSeparator(),
itemBuilder: (_, index) =>
_renderGroupOptions(widget.options![index].options),
),
],
],
],
),
),
);
}

View File

@ -4,6 +4,7 @@ import 'package:flutter/services.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/date_entities.pbenum.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:flowy_infra_ui/style_widget/text_field.dart';
import 'package:intl/intl.dart';
const _maxLengthTwelveHour = 8;
const _maxLengthTwentyFourHour = 5;
@ -55,6 +56,13 @@ class _TimeTextFieldState extends State<TimeTextField> {
text = widget.endTimeStr!;
}
if (widget.timeFormat == TimeFormatPB.TwelveHour) {
final twentyFourHourFormat = DateFormat('HH:mm');
final twelveHourFormat = DateFormat('hh:mm a');
final date = twentyFourHourFormat.parse(text);
text = twelveHourFormat.format(date);
}
_focusNode.addListener(_focusNodeListener);
widget.popoverMutex?.listenOnPopoverChanged(_popoverListener);
}
@ -86,6 +94,7 @@ class _TimeTextFieldState extends State<TimeTextField> {
padding: const EdgeInsets.symmetric(horizontal: 18.0),
child: FlowyTextField(
text: text,
keyboardType: TextInputType.datetime,
focusNode: _focusNode,
autoFocus: false,
controller: _textController,
@ -97,7 +106,16 @@ class _TimeTextFieldState extends State<TimeTextField> {
? _maxLengthTwelveHour
: _maxLengthTwentyFourHour,
showCounter: false,
inputFormatters: [TimeInputFormatter(widget.timeFormat)],
inputFormatters: [
if (widget.timeFormat == TimeFormatPB.TwelveHour) ...[
// Allow for AM/PM if time format is 12-hour
FilteringTextInputFormatter.allow(RegExp('[0-9:aApPmM ]')),
] else ...[
// Default allow for hh:mm format
FilteringTextInputFormatter.allow(RegExp('[0-9:]')),
],
TimeInputFormatter(widget.timeFormat),
],
onSubmitted: widget.onSubmitted,
),
);
@ -134,7 +152,20 @@ class TimeInputFormatter extends TextInputFormatter {
return _formatText(newText, spacePosition, ' ');
}
return newValue;
if (timeFormat == TimeFormatPB.TwentyFourHour &&
newValue.text.length == 5) {
final prefix = newValue.text.substring(0, 3);
final suffix = newValue.text.length > 5 ? newValue.text.substring(6) : '';
final minutes = int.tryParse(newValue.text.substring(3, 5));
if (minutes == null || minutes <= 0) {
return newValue.copyWith(text: '${prefix}00$suffix'.toUpperCase());
} else if (minutes > 59) {
return newValue.copyWith(text: '${prefix}59$suffix'.toUpperCase());
}
}
return newValue.copyWith(text: newText.toUpperCase());
}
TextEditingValue _formatText(String text, int index, String separator) {

View File

@ -1,9 +1,10 @@
import 'dart:async';
import 'package:flowy_infra/size.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flowy_infra/size.dart';
class FlowyTextField extends StatefulWidget {
final String? hintText;
final String? text;
@ -31,6 +32,7 @@ class FlowyTextField extends StatefulWidget {
final TextStyle? hintStyle;
final InputDecoration? decoration;
final TextAlignVertical? textAlignVertical;
final TextInputAction? textInputAction;
final TextInputType? keyboardType;
final List<TextInputFormatter>? inputFormatters;
@ -62,6 +64,7 @@ class FlowyTextField extends StatefulWidget {
this.hintStyle,
this.decoration,
this.textAlignVertical,
this.textInputAction,
this.keyboardType = TextInputType.multiline,
this.inputFormatters,
});