mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
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:
parent
9536cde789
commit
47d321b8c2
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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),
|
||||
),
|
||||
],
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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,
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user