date_field/lib/date_field.dart

326 lines
10 KiB
Dart
Raw Normal View History

2020-03-01 22:16:40 +00:00
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
2021-01-22 18:38:04 +00:00
final DateTime _kDefaultFirstSelectableDate = DateTime(1900);
final DateTime _kDefaultLastSelectableDate = DateTime(2100);
const double _kCupertinoDatePickerHeight = 216;
2020-07-19 14:49:06 +00:00
/// A [FormField] that contains a [DateTimeField].
2020-03-01 22:41:41 +00:00
///
2020-07-19 14:49:06 +00:00
/// This is a convenience widget that wraps a [DateTimeField] widget in a
2020-03-01 22:41:41 +00:00
/// [FormField].
///
/// A [Form] ancestor is not required. The [Form] simply makes it easier to
/// save, reset, or validate multiple fields at once. To use without a [Form],
/// pass a [GlobalKey] to the constructor and use [GlobalKey.currentState] to
/// save or reset the form field.
2020-07-19 14:49:06 +00:00
class DateTimeFormField extends FormField<DateTime> {
DateTimeFormField({
2021-03-11 18:43:23 +00:00
Key? key,
FormFieldSetter<DateTime>? onSaved,
FormFieldValidator<DateTime>? validator,
DateTime? initialValue,
AutovalidateMode? autovalidateMode,
2020-07-19 14:49:06 +00:00
bool enabled = true,
2021-03-11 18:43:23 +00:00
TextStyle? dateTextStyle,
DateFormat? dateFormat,
DateTime? firstDate,
DateTime? lastDate,
ValueChanged<DateTime>? onDateSelected,
InputDecoration? decoration,
DatePickerEntryMode initialEntryMode = DatePickerEntryMode.calendar,
2021-01-22 18:38:04 +00:00
DatePickerMode initialDatePickerMode = DatePickerMode.day,
DateTimeFieldPickerMode mode = DateTimeFieldPickerMode.dateAndTime,
2020-07-19 14:49:06 +00:00
}) : super(
2020-09-10 14:04:14 +00:00
key: key,
initialValue: initialValue,
onSaved: onSaved,
validator: validator,
2021-01-22 18:38:04 +00:00
autovalidateMode: autovalidateMode,
2020-09-10 14:04:14 +00:00
enabled: enabled,
builder: (FormFieldState<DateTime> field) {
2021-01-22 18:38:04 +00:00
// Theme defaults are applied inside the _InputDropdown widget
final InputDecoration _decorationWithThemeDefaults =
decoration ?? const InputDecoration();
final InputDecoration effectiveDecoration =
_decorationWithThemeDefaults.copyWith(
errorText: field.errorText);
2020-09-10 14:04:14 +00:00
void onChangedHandler(DateTime value) {
if (onDateSelected != null) {
onDateSelected(value);
}
field.didChange(value);
}
2020-09-10 14:03:04 +00:00
2020-09-10 14:04:14 +00:00
return DateTimeField(
firstDate: firstDate,
lastDate: lastDate,
2021-01-22 18:38:04 +00:00
decoration: effectiveDecoration,
2020-09-10 14:04:14 +00:00
initialDatePickerMode: initialDatePickerMode,
dateFormat: dateFormat,
onDateSelected: onChangedHandler,
2021-03-11 18:43:23 +00:00
selectedDate: field.value,
2020-09-10 14:04:14 +00:00
enabled: enabled,
mode: mode,
initialEntryMode: initialEntryMode,
2021-01-22 18:38:04 +00:00
dateTextStyle: dateTextStyle,
2020-09-10 14:04:14 +00:00
);
},
);
2020-03-01 22:16:40 +00:00
@override
_DateFormFieldState createState() => _DateFormFieldState();
2020-03-01 22:16:40 +00:00
}
class _DateFormFieldState extends FormFieldState<DateTime> {}
2020-07-19 14:49:06 +00:00
/// [DateTimeField]
2020-03-01 22:16:40 +00:00
///
2020-07-19 14:49:06 +00:00
/// Shows an [_InputDropdown] that'll trigger [DateTimeField._selectDate] whenever the user
2020-03-01 22:41:41 +00:00
/// clicks on it ! The date picker is **platform responsive** (ios date picker style for ios, ...)
2020-07-19 14:49:06 +00:00
class DateTimeField extends StatelessWidget {
DateTimeField({
2021-03-11 18:43:23 +00:00
Key? key,
required this.onDateSelected,
required this.selectedDate,
2020-03-01 22:16:40 +00:00
this.initialDatePickerMode = DatePickerMode.day,
this.decoration,
this.enabled = true,
2021-01-22 18:38:04 +00:00
this.mode = DateTimeFieldPickerMode.dateAndTime,
this.initialEntryMode = DatePickerEntryMode.calendar,
2021-01-22 18:38:04 +00:00
this.dateTextStyle,
2021-03-11 18:43:23 +00:00
DateTime? firstDate,
DateTime? lastDate,
DateFormat? dateFormat,
2020-07-19 14:49:06 +00:00
}) : dateFormat = dateFormat ?? getDateFormatFromDateFieldPickerMode(mode),
2021-01-22 18:38:04 +00:00
firstDate = firstDate ?? _kDefaultFirstSelectableDate,
lastDate = lastDate ?? _kDefaultLastSelectableDate,
2020-07-19 14:49:06 +00:00
super(key: key);
DateTimeField.time({
2021-03-11 18:43:23 +00:00
Key? key,
2020-07-19 14:49:06 +00:00
this.onDateSelected,
this.selectedDate,
this.decoration,
this.enabled,
2021-01-22 18:38:04 +00:00
this.dateTextStyle,
this.initialEntryMode = DatePickerEntryMode.calendar,
2021-03-11 18:43:23 +00:00
DateTime? firstDate,
DateTime? lastDate,
2020-07-19 14:49:06 +00:00
}) : initialDatePickerMode = null,
2021-01-22 18:38:04 +00:00
mode = DateTimeFieldPickerMode.time,
2020-07-19 14:49:06 +00:00
dateFormat = DateFormat.jm(),
firstDate = firstDate ?? DateTime(2000),
lastDate = lastDate ?? DateTime(2001),
super(key: key);
2020-03-01 22:16:40 +00:00
/// Callback for whenever the user selects a [DateTime]
2021-03-11 18:43:23 +00:00
final ValueChanged<DateTime>? onDateSelected;
2020-03-01 22:16:40 +00:00
/// The current selected date to display inside the field
2021-03-11 18:43:23 +00:00
final DateTime? selectedDate;
2020-03-01 22:16:40 +00:00
2021-01-22 18:38:04 +00:00
/// The first date that the user can select (default is 1900)
2020-03-01 22:16:40 +00:00
final DateTime firstDate;
2021-01-22 18:38:04 +00:00
/// The last date that the user can select (default is 2100)
2020-03-01 22:16:40 +00:00
final DateTime lastDate;
/// Let you choose the [DatePickerMode] for the date picker! (default is [DatePickerMode.day]
2021-03-11 18:43:23 +00:00
final DatePickerMode? initialDatePickerMode;
2020-03-01 22:16:40 +00:00
2021-01-22 18:38:04 +00:00
/// Custom [InputDecoration] for the [InputDecorator] widget
2021-03-11 18:43:23 +00:00
final InputDecoration? decoration;
2020-03-01 22:16:40 +00:00
2021-01-22 18:38:04 +00:00
/// How to display the [DateTime] for the user (default is [DateFormat.yMMMD])
2020-03-01 22:16:40 +00:00
final DateFormat dateFormat;
2021-01-22 18:38:04 +00:00
/// Whether the field is usable. If false the user won't be able to select any date
2021-03-11 18:43:23 +00:00
final bool? enabled;
2020-07-19 14:49:06 +00:00
/// Whether to ask the user to pick only the date, the time or both.
2021-01-22 18:38:04 +00:00
final DateTimeFieldPickerMode mode;
2020-07-19 14:49:06 +00:00
2021-01-22 18:38:04 +00:00
/// [TextStyle] of the selected date inside the field.
2021-03-11 18:43:23 +00:00
final TextStyle? dateTextStyle;
2020-09-10 14:03:04 +00:00
/// The initial entry mode for the material date picker dialog
final DatePickerEntryMode initialEntryMode;
2020-03-01 22:16:40 +00:00
/// Shows a dialog asking the user to pick a date !
Future<void> _selectDate(BuildContext context) async {
2021-01-22 18:38:04 +00:00
final DateTime initialDateTime = selectedDate ?? DateTime.now();
2020-07-19 14:49:06 +00:00
2020-07-14 14:44:57 +00:00
if (Theme.of(context).platform == TargetPlatform.iOS) {
showModalBottomSheet<void>(
2020-03-01 22:16:40 +00:00
context: context,
builder: (BuildContext builder) {
return Container(
2021-01-22 18:38:04 +00:00
height: _kCupertinoDatePickerHeight,
2020-03-01 22:16:40 +00:00
child: CupertinoDatePicker(
2020-07-19 14:49:06 +00:00
mode: _cupertinoModeFromPickerMode(mode),
2021-03-11 18:43:23 +00:00
onDateTimeChanged: onDateSelected!,
2020-07-19 14:49:06 +00:00
initialDateTime: initialDateTime,
2020-03-01 22:16:40 +00:00
minimumDate: firstDate,
maximumDate: lastDate,
),
);
},
);
2020-07-15 16:55:02 +00:00
} else {
2020-07-19 14:49:06 +00:00
DateTime _selectedDateTime = initialDateTime;
2021-01-22 18:38:04 +00:00
if ([DateTimeFieldPickerMode.dateAndTime, DateTimeFieldPickerMode.date]
2020-07-19 14:49:06 +00:00
.contains(mode)) {
2021-03-11 18:43:23 +00:00
final DateTime? _selectedDate = await showDatePicker(
2021-01-22 18:38:04 +00:00
context: context,
2021-03-11 18:43:23 +00:00
initialDatePickerMode: initialDatePickerMode!,
2021-01-22 18:38:04 +00:00
initialDate: initialDateTime,
initialEntryMode: initialEntryMode,
2021-01-22 18:38:04 +00:00
firstDate: firstDate,
lastDate: lastDate,
);
2020-07-19 14:49:06 +00:00
if (_selectedDate != null) {
_selectedDateTime = _selectedDate;
2021-01-22 18:38:04 +00:00
} else {
return;
2020-07-19 14:49:06 +00:00
}
}
2021-01-22 18:38:04 +00:00
if ([DateTimeFieldPickerMode.dateAndTime, DateTimeFieldPickerMode.time]
2020-07-19 14:49:06 +00:00
.contains(mode)) {
2021-03-11 18:43:23 +00:00
final TimeOfDay? _selectedTime = await showTimePicker(
2020-07-19 14:49:06 +00:00
initialTime: TimeOfDay.fromDateTime(initialDateTime),
2020-03-01 22:16:40 +00:00
context: context,
2020-07-19 14:49:06 +00:00
);
2020-03-01 22:16:40 +00:00
2020-07-19 14:49:06 +00:00
if (_selectedTime != null) {
_selectedDateTime = DateTime(
_selectedDateTime.year,
_selectedDateTime.month,
_selectedDateTime.day,
_selectedTime.hour,
_selectedTime.minute,
);
2020-07-19 14:49:06 +00:00
}
2020-03-01 22:16:40 +00:00
}
2020-07-19 14:49:06 +00:00
2021-03-11 18:43:23 +00:00
onDateSelected!(_selectedDateTime);
2020-03-01 22:16:40 +00:00
}
}
@override
Widget build(BuildContext context) {
2021-03-11 18:43:23 +00:00
String? text;
2020-03-01 22:16:40 +00:00
2021-03-11 18:43:23 +00:00
if (selectedDate != null) text = dateFormat.format(selectedDate!);
2020-03-01 22:16:40 +00:00
2021-03-11 18:43:23 +00:00
TextStyle? textStyle;
2021-01-22 18:38:04 +00:00
if (text == null) {
2021-03-11 18:43:23 +00:00
textStyle = decoration!.hintStyle ??
2021-01-22 18:38:04 +00:00
Theme.of(context).inputDecorationTheme.hintStyle;
} else {
textStyle = dateTextStyle ?? dateTextStyle;
}
2021-03-11 18:43:23 +00:00
final bool shouldDisplayLabelText = (text ?? decoration!.hintText) != null;
2021-01-22 18:38:04 +00:00
2021-03-11 18:43:23 +00:00
InputDecoration? effectiveDecoration = decoration;
2021-01-22 18:38:04 +00:00
if (!shouldDisplayLabelText) {
2021-03-11 18:43:23 +00:00
effectiveDecoration = effectiveDecoration!.copyWith(labelText: '');
2021-01-22 18:38:04 +00:00
}
2020-03-01 22:16:40 +00:00
return _InputDropdown(
2021-03-11 18:43:23 +00:00
text: text ??
decoration!.hintText ??
decoration!.labelText ??
'Select date',
2020-09-10 14:03:04 +00:00
textStyle: textStyle,
2021-01-22 18:38:04 +00:00
decoration: effectiveDecoration,
2021-03-11 18:43:23 +00:00
onPressed: enabled! ? () => _selectDate(context) : null,
2020-03-01 22:16:40 +00:00
);
}
}
2020-07-19 14:49:06 +00:00
/// Those values are used by the [DateTimeField] widget to determine whether to ask
/// the user for the time, the date or both.
2021-01-22 18:38:04 +00:00
enum DateTimeFieldPickerMode { time, date, dateAndTime }
2020-07-19 14:49:06 +00:00
/// Returns the [CupertinoDatePickerMode] corresponding to the selected
2021-01-22 18:38:04 +00:00
/// [DateTimeFieldPickerMode]. This exists to prevent redundancy in the [DateTimeField]
2020-07-19 14:49:06 +00:00
/// widget parameters.
2021-01-22 18:38:04 +00:00
CupertinoDatePickerMode _cupertinoModeFromPickerMode(
DateTimeFieldPickerMode mode) {
2020-07-19 14:49:06 +00:00
switch (mode) {
2021-01-22 18:38:04 +00:00
case DateTimeFieldPickerMode.time:
2020-07-19 14:49:06 +00:00
return CupertinoDatePickerMode.time;
2021-01-22 18:38:04 +00:00
case DateTimeFieldPickerMode.date:
2020-07-19 14:49:06 +00:00
return CupertinoDatePickerMode.date;
default:
return CupertinoDatePickerMode.dateAndTime;
}
}
2021-01-22 18:38:04 +00:00
/// Returns the corresponding default [DateFormat] for the selected [DateTimeFieldPickerMode]
DateFormat getDateFormatFromDateFieldPickerMode(DateTimeFieldPickerMode mode) {
2020-07-19 14:49:06 +00:00
switch (mode) {
2021-01-22 18:38:04 +00:00
case DateTimeFieldPickerMode.time:
2020-07-19 14:49:06 +00:00
return DateFormat.jm();
2021-01-22 18:38:04 +00:00
case DateTimeFieldPickerMode.date:
2020-07-19 14:49:06 +00:00
return DateFormat.yMMMMd();
default:
return DateFormat.yMd().add_jm();
}
}
2020-03-01 22:16:40 +00:00
///
/// [_InputDropdown]
///
/// Shows a field with a dropdown arrow !
/// It does not show any popup menu, it'll just trigger onPressed whenever the
/// user does click on it !
2020-09-10 14:03:04 +00:00
class _InputDropdown extends StatelessWidget {
2020-07-15 16:55:02 +00:00
const _InputDropdown({
2021-03-11 18:43:23 +00:00
Key? key,
required this.text,
2020-07-15 16:55:02 +00:00
this.decoration,
this.textStyle,
this.onPressed,
2021-03-11 18:43:23 +00:00
}) : super(key: key);
2020-03-01 22:16:40 +00:00
/// The text that should be displayed inside the field
final String text;
2021-01-22 18:38:04 +00:00
/// Custom [InputDecoration] for the [InputDecorator] widget
2021-03-11 18:43:23 +00:00
final InputDecoration? decoration;
2020-03-01 22:16:40 +00:00
/// TextStyle for the field
2021-03-11 18:43:23 +00:00
final TextStyle? textStyle;
2020-03-01 22:16:40 +00:00
/// Callbacks triggered whenever the user presses on the field!
2021-03-11 18:43:23 +00:00
final VoidCallback? onPressed;
2020-03-01 22:16:40 +00:00
@override
Widget build(BuildContext context) {
2021-01-22 18:38:04 +00:00
final InputDecoration effectiveDecoration = decoration ??
const InputDecoration(
suffixIcon: Icon(Icons.arrow_drop_down),
2021-01-22 18:38:04 +00:00
).applyDefaults(Theme.of(context).inputDecorationTheme);
2021-01-23 10:57:52 +00:00
return GestureDetector(
onTap: onPressed,
child: InputDecorator(
decoration: effectiveDecoration,
child: Text(text, style: textStyle),
),
2020-03-01 22:16:40 +00:00
);
}
}