mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: initial date text input
This commit is contained in:
parent
5daf9d23f5
commit
6a14ba8047
@ -1,3 +1,4 @@
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/date_entities.pbenum.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/date_time.pbenum.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
|
||||
@ -7,6 +8,28 @@ const _isoFmt = 'y-M-d';
|
||||
const _friendlyFmt = 'MMM d, y';
|
||||
const _dmyFmt = 'd/M/y';
|
||||
|
||||
extension FormatDate on DateFormatPB {
|
||||
String formatDate(
|
||||
DateTime date,
|
||||
bool includeTime, [
|
||||
UserTimeFormatPB? timeFormat,
|
||||
]) {
|
||||
final global = _toGlobalFormat();
|
||||
return global.formatDate(date, includeTime, timeFormat);
|
||||
}
|
||||
|
||||
UserDateFormatPB _toGlobalFormat() {
|
||||
return switch (this) {
|
||||
DateFormatPB.DayMonthYear => UserDateFormatPB.DayMonthYear,
|
||||
DateFormatPB.Friendly => UserDateFormatPB.Friendly,
|
||||
DateFormatPB.ISO => UserDateFormatPB.ISO,
|
||||
DateFormatPB.Local => UserDateFormatPB.Locally,
|
||||
DateFormatPB.US => UserDateFormatPB.US,
|
||||
_ => UserDateFormatPB.Friendly,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
extension DateFormatter on UserDateFormatPB {
|
||||
DateFormat get toFormat => DateFormat(_toFormat[this] ?? _friendlyFmt);
|
||||
|
||||
|
@ -3,10 +3,9 @@ import 'package:flutter/material.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/common/type_option_separator.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/header/type_option/date/date_time_format.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/date_picker/widgets/date_picker.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/date_picker/widgets/end_text_field.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/date_picker/widgets/date_time_input.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/date_picker/widgets/end_time_button.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/date_picker/widgets/reminder_selector.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/date_picker/widgets/start_text_field.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/date_entities.pbenum.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
@ -179,24 +178,61 @@ class _AppFlowyDatePickerState extends State<AppFlowyDatePicker> {
|
||||
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,
|
||||
// Start Date and Time input
|
||||
DateTimeInput(
|
||||
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,
|
||||
isTimeEnabled: widget.includeTime,
|
||||
dateOptions: DateOptions(
|
||||
dateFormat: widget.dateFormat,
|
||||
date: widget.startDay ?? widget.selectedDay,
|
||||
),
|
||||
timeOptions: widget.includeTime
|
||||
? TimeOptions(
|
||||
timeFormat: widget.timeFormat,
|
||||
timeStr: widget.timeStr,
|
||||
parseTimeError: widget.parseTimeError,
|
||||
onSubmitted: widget.onStartTimeSubmitted,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
// End Date and Time input
|
||||
if (widget.isRange) ...[
|
||||
const VSpace(6),
|
||||
DateTimeInput(
|
||||
popoverMutex: widget.popoverMutex,
|
||||
isTimeEnabled: widget.includeTime,
|
||||
dateOptions: DateOptions(
|
||||
dateFormat: widget.dateFormat,
|
||||
date: widget.endDay,
|
||||
),
|
||||
timeOptions: widget.includeTime
|
||||
? TimeOptions(
|
||||
timeFormat: widget.timeFormat,
|
||||
timeStr: widget.endTimeStr,
|
||||
parseTimeError: widget.parseEndTimeError,
|
||||
onSubmitted: widget.onEndTimeSubmitted,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
],
|
||||
// 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) {
|
||||
|
@ -0,0 +1,225 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/workspace/application/settings/date_time/date_format_ext.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/date_entities.pbenum.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:flowy_infra/size.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text_field.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
|
||||
class TimeOptions {
|
||||
const TimeOptions({
|
||||
required this.timeFormat,
|
||||
this.timeHintText,
|
||||
this.parseTimeError,
|
||||
this.timeStr,
|
||||
this.onSubmitted,
|
||||
});
|
||||
|
||||
final TimeFormatPB timeFormat;
|
||||
final String? timeHintText;
|
||||
final String? parseTimeError;
|
||||
final String? timeStr;
|
||||
final Function(String timeStr)? onSubmitted;
|
||||
}
|
||||
|
||||
class DateOptions {
|
||||
DateOptions({
|
||||
required this.dateFormat,
|
||||
required this.date,
|
||||
});
|
||||
|
||||
final DateFormatPB dateFormat;
|
||||
final DateTime? date;
|
||||
}
|
||||
|
||||
class DateTimeInput extends StatelessWidget {
|
||||
const DateTimeInput({
|
||||
super.key,
|
||||
this.popoverMutex,
|
||||
this.isTimeEnabled = true,
|
||||
required this.dateOptions,
|
||||
this.timeOptions,
|
||||
}) : assert(!isTimeEnabled || timeOptions != null);
|
||||
|
||||
final PopoverMutex? popoverMutex;
|
||||
final bool isTimeEnabled;
|
||||
|
||||
final DateOptions dateOptions;
|
||||
final TimeOptions? timeOptions;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final inputDecoration = _defaultInputDecoration(context);
|
||||
final leftBorder = _inputBorderFromSide(context, true);
|
||||
final rightBorder = _inputBorderFromSide(context, false);
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 18.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Theme.of(context).colorScheme.outline),
|
||||
borderRadius: Corners.s8Border,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Flexible(
|
||||
child: _DatePart(
|
||||
inputDecoration: isTimeEnabled
|
||||
? inputDecoration.copyWith(
|
||||
enabledBorder: leftBorder,
|
||||
focusedBorder: leftBorder,
|
||||
)
|
||||
: inputDecoration,
|
||||
options: dateOptions,
|
||||
),
|
||||
),
|
||||
if (isTimeEnabled && timeOptions != null) ...[
|
||||
SizedBox(
|
||||
height: 18,
|
||||
child: VerticalDivider(
|
||||
width: 4,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: _TimePart(
|
||||
inputDecoration: isTimeEnabled
|
||||
? inputDecoration.copyWith(
|
||||
enabledBorder: rightBorder,
|
||||
focusedBorder: rightBorder,
|
||||
)
|
||||
: inputDecoration,
|
||||
options: timeOptions!,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
if (timeOptions?.parseTimeError?.isNotEmpty ?? false) ...[
|
||||
const VSpace(4),
|
||||
Text(
|
||||
timeOptions!.parseTimeError!,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall!
|
||||
.copyWith(color: Theme.of(context).colorScheme.error),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
InputBorder _inputBorderFromSide(BuildContext context, bool isLeft) =>
|
||||
OutlineInputBorder(
|
||||
borderSide: BorderSide.none,
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: isLeft ? Corners.s8Radius : Radius.zero,
|
||||
bottomLeft: isLeft ? Corners.s8Radius : Radius.zero,
|
||||
topRight: !isLeft ? Corners.s8Radius : Radius.zero,
|
||||
bottomRight: !isLeft ? Corners.s8Radius : Radius.zero,
|
||||
),
|
||||
);
|
||||
|
||||
InputDecoration _defaultInputDecoration(BuildContext context) =>
|
||||
InputDecoration(
|
||||
constraints: const BoxConstraints(maxHeight: 32),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: Theme.of(context).colorScheme.outline),
|
||||
borderRadius: Corners.s8Border,
|
||||
),
|
||||
isDense: false,
|
||||
errorStyle: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall!
|
||||
.copyWith(color: Theme.of(context).colorScheme.error),
|
||||
hintStyle: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall!
|
||||
.copyWith(color: Theme.of(context).hintColor),
|
||||
suffixText: "",
|
||||
counterText: "",
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: Theme.of(context).colorScheme.outline),
|
||||
borderRadius: Corners.s8Border,
|
||||
),
|
||||
errorBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: Theme.of(context).colorScheme.error),
|
||||
borderRadius: Corners.s8Border,
|
||||
),
|
||||
focusedErrorBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: Theme.of(context).colorScheme.error),
|
||||
borderRadius: Corners.s8Border,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class _DatePart extends StatefulWidget {
|
||||
const _DatePart({
|
||||
required this.inputDecoration,
|
||||
required this.options,
|
||||
});
|
||||
|
||||
final InputDecoration inputDecoration;
|
||||
final DateOptions options;
|
||||
|
||||
@override
|
||||
State<_DatePart> createState() => _DatePartState();
|
||||
}
|
||||
|
||||
class _DatePartState extends State<_DatePart> {
|
||||
late final TextEditingController _controller;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final dateStr = widget.options.date != null
|
||||
? widget.options.dateFormat.formatDate(
|
||||
widget.options.date!,
|
||||
false,
|
||||
)
|
||||
: "";
|
||||
|
||||
_controller = TextEditingController(text: dateStr);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FlowyTextField(
|
||||
controller: _controller,
|
||||
decoration: widget.inputDecoration,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _TimePart extends StatefulWidget {
|
||||
const _TimePart({
|
||||
required this.inputDecoration,
|
||||
required this.options,
|
||||
});
|
||||
|
||||
final InputDecoration inputDecoration;
|
||||
final TimeOptions options;
|
||||
|
||||
@override
|
||||
State<_TimePart> createState() => _TimePartState();
|
||||
}
|
||||
|
||||
class _TimePartState extends State<_TimePart> {
|
||||
late final _controller = TextEditingController(text: widget.options.timeStr);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FlowyTextField(
|
||||
controller: _controller,
|
||||
decoration: widget.inputDecoration,
|
||||
onSubmitted: widget.options.onSubmitted,
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user