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) {
|
DateTime _parseTime(String timeStr, UserTimeFormatPB timeFormat) {
|
||||||
final twelveHourFormat = DateFormat('HH:mm a');
|
final twelveHourFormat = DateFormat('hh:mm a');
|
||||||
final twentyFourHourFormat = DateFormat('HH:mm');
|
final twentyFourHourFormat = DateFormat('HH:mm');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (timeFormat == TimeFormatPB.TwelveHour) {
|
if (timeFormat == UserTimeFormatPB.TwelveHour) {
|
||||||
return twelveHourFormat.parseStrict(timeStr);
|
return twelveHourFormat.parseStrict(timeStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,85 +174,92 @@ class _AppFlowyDatePickerState extends State<AppFlowyDatePicker> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget buildDesktopPicker() {
|
Widget buildDesktopPicker() {
|
||||||
return Padding(
|
// GestureDetector is a workaround to stop popover from closing
|
||||||
padding: const EdgeInsets.only(top: 18.0, bottom: 12.0),
|
// when clicking on the date picker.
|
||||||
child: Column(
|
return GestureDetector(
|
||||||
mainAxisSize: MainAxisSize.min,
|
behavior: HitTestBehavior.opaque,
|
||||||
children: [
|
onTap: () {},
|
||||||
StartTextField(
|
child: Padding(
|
||||||
includeTime: widget.includeTime,
|
padding: const EdgeInsets.only(top: 18.0, bottom: 12.0),
|
||||||
timeFormat: widget.timeFormat,
|
child: Column(
|
||||||
timeHintText: widget.timeHintText,
|
mainAxisSize: MainAxisSize.min,
|
||||||
parseEndTimeError: widget.parseEndTimeError,
|
children: [
|
||||||
parseTimeError: widget.parseTimeError,
|
StartTextField(
|
||||||
timeStr: widget.timeStr,
|
includeTime: widget.includeTime,
|
||||||
popoverMutex: widget.popoverMutex,
|
timeFormat: widget.timeFormat,
|
||||||
onSubmitted: widget.onStartTimeSubmitted,
|
timeHintText: widget.timeHintText,
|
||||||
),
|
parseEndTimeError: widget.parseEndTimeError,
|
||||||
EndTextField(
|
parseTimeError: widget.parseTimeError,
|
||||||
includeTime: widget.includeTime,
|
timeStr: widget.timeStr,
|
||||||
timeFormat: widget.timeFormat,
|
popoverMutex: widget.popoverMutex,
|
||||||
isRange: widget.isRange,
|
onSubmitted: widget.onStartTimeSubmitted,
|
||||||
endTimeStr: widget.endTimeStr,
|
),
|
||||||
popoverMutex: widget.popoverMutex,
|
EndTextField(
|
||||||
onSubmitted: widget.onEndTimeSubmitted,
|
includeTime: widget.includeTime,
|
||||||
),
|
timeFormat: widget.timeFormat,
|
||||||
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,
|
isRange: widget.isRange,
|
||||||
onChanged: widget.onIsRangeChanged!,
|
endTimeStr: widget.endTimeStr,
|
||||||
|
popoverMutex: widget.popoverMutex,
|
||||||
|
onSubmitted: widget.onEndTimeSubmitted,
|
||||||
),
|
),
|
||||||
const VSpace(4.0),
|
DatePicker(
|
||||||
],
|
isRange: widget.isRange,
|
||||||
Padding(
|
onDaySelected: (selectedDay, focusedDay) {
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
widget.onDaySelected?.call(selectedDay, focusedDay);
|
||||||
child: IncludeTimeButton(
|
|
||||||
value: widget.includeTime,
|
if (widget.rebuildOnDaySelected) {
|
||||||
onChanged: widget.onIncludeTimeChanged,
|
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(),
|
const _GroupSeparator(),
|
||||||
ListView.separated(
|
ReminderSelector(
|
||||||
shrinkWrap: true,
|
mutex: widget.popoverMutex,
|
||||||
itemCount: widget.options!.length,
|
hasTime: widget.includeTime,
|
||||||
separatorBuilder: (_, __) => const _GroupSeparator(),
|
timeFormat: widget.timeFormat,
|
||||||
itemBuilder: (_, index) =>
|
selectedOption: _selectedReminderOption,
|
||||||
_renderGroupOptions(widget.options![index].options),
|
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_backend/protobuf/flowy-database2/date_entities.pbenum.dart';
|
||||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/text_field.dart';
|
import 'package:flowy_infra_ui/style_widget/text_field.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
const _maxLengthTwelveHour = 8;
|
const _maxLengthTwelveHour = 8;
|
||||||
const _maxLengthTwentyFourHour = 5;
|
const _maxLengthTwentyFourHour = 5;
|
||||||
@ -55,6 +56,13 @@ class _TimeTextFieldState extends State<TimeTextField> {
|
|||||||
text = widget.endTimeStr!;
|
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);
|
_focusNode.addListener(_focusNodeListener);
|
||||||
widget.popoverMutex?.listenOnPopoverChanged(_popoverListener);
|
widget.popoverMutex?.listenOnPopoverChanged(_popoverListener);
|
||||||
}
|
}
|
||||||
@ -86,6 +94,7 @@ class _TimeTextFieldState extends State<TimeTextField> {
|
|||||||
padding: const EdgeInsets.symmetric(horizontal: 18.0),
|
padding: const EdgeInsets.symmetric(horizontal: 18.0),
|
||||||
child: FlowyTextField(
|
child: FlowyTextField(
|
||||||
text: text,
|
text: text,
|
||||||
|
keyboardType: TextInputType.datetime,
|
||||||
focusNode: _focusNode,
|
focusNode: _focusNode,
|
||||||
autoFocus: false,
|
autoFocus: false,
|
||||||
controller: _textController,
|
controller: _textController,
|
||||||
@ -97,7 +106,16 @@ class _TimeTextFieldState extends State<TimeTextField> {
|
|||||||
? _maxLengthTwelveHour
|
? _maxLengthTwelveHour
|
||||||
: _maxLengthTwentyFourHour,
|
: _maxLengthTwentyFourHour,
|
||||||
showCounter: false,
|
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,
|
onSubmitted: widget.onSubmitted,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -134,7 +152,20 @@ class TimeInputFormatter extends TextInputFormatter {
|
|||||||
return _formatText(newText, spacePosition, ' ');
|
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) {
|
TextEditingValue _formatText(String text, int index, String separator) {
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flowy_infra/size.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
import 'package:flowy_infra/size.dart';
|
||||||
|
|
||||||
class FlowyTextField extends StatefulWidget {
|
class FlowyTextField extends StatefulWidget {
|
||||||
final String? hintText;
|
final String? hintText;
|
||||||
final String? text;
|
final String? text;
|
||||||
@ -31,6 +32,7 @@ class FlowyTextField extends StatefulWidget {
|
|||||||
final TextStyle? hintStyle;
|
final TextStyle? hintStyle;
|
||||||
final InputDecoration? decoration;
|
final InputDecoration? decoration;
|
||||||
final TextAlignVertical? textAlignVertical;
|
final TextAlignVertical? textAlignVertical;
|
||||||
|
final TextInputAction? textInputAction;
|
||||||
final TextInputType? keyboardType;
|
final TextInputType? keyboardType;
|
||||||
final List<TextInputFormatter>? inputFormatters;
|
final List<TextInputFormatter>? inputFormatters;
|
||||||
|
|
||||||
@ -62,6 +64,7 @@ class FlowyTextField extends StatefulWidget {
|
|||||||
this.hintStyle,
|
this.hintStyle,
|
||||||
this.decoration,
|
this.decoration,
|
||||||
this.textAlignVertical,
|
this.textAlignVertical,
|
||||||
|
this.textInputAction,
|
||||||
this.keyboardType = TextInputType.multiline,
|
this.keyboardType = TextInputType.multiline,
|
||||||
this.inputFormatters,
|
this.inputFormatters,
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user