Adding support for time

This commit is contained in:
Gaspard Merten 2020-07-19 16:49:06 +02:00
parent 97da6f5955
commit c836bc8329
3 changed files with 380 additions and 59 deletions

View file

@ -32,3 +32,17 @@
## 0.2.2 ## 0.2.2
* Auto-formatting with dart-fm to meet pub.dev requirements * Auto-formatting with dart-fm to meet pub.dev requirements
## 0.3.0
Breaking changes:
* No more const constructor.
Deprecated:
* DateField and DateFormField are now deprecated and will be removed in the next version, please consider switching to
DateTimeField and DateTimeFormField.
Improvements:
* Adding support for time. Now you can ask the user for a time, a date or both.
* Improving performances by setting default value in the constructor.
* Adding .time constructor for the DateField widget only.

View file

@ -29,7 +29,7 @@ class _HomeWidgetState extends State<HomeWidget> {
children: <Widget>[ children: <Widget>[
Column( Column(
children: <Widget>[ children: <Widget>[
DateField( DateTimeField(
selectedDate: selectedDate, selectedDate: selectedDate,
onDateSelected: (DateTime date) { onDateSelected: (DateTime date) {

View file

@ -2,18 +2,18 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
/// A [FormField] that contains a [DateField]. /// A [FormField] that contains a [DateTimeField].
/// ///
/// This is a convenience widget that wraps a [DateField] widget in a /// This is a convenience widget that wraps a [DateTimeField] widget in a
/// [FormField]. /// [FormField].
/// ///
/// A [Form] ancestor is not required. The [Form] simply makes it easier to /// 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], /// save, reset, or validate multiple fields at once. To use without a [Form],
/// pass a [GlobalKey] to the constructor and use [GlobalKey.currentState] to /// pass a [GlobalKey] to the constructor and use [GlobalKey.currentState] to
/// save or reset the form field. /// save or reset the form field.
class DateFormField extends FormField<DateTime> { class DateTimeFormField extends FormField<DateTime> {
DateFormField( DateTimeFormField({
{Key key, Key key,
FormFieldSetter<DateTime> onSaved, FormFieldSetter<DateTime> onSaved,
FormFieldValidator<DateTime> validator, FormFieldValidator<DateTime> validator,
DateTime initialValue, DateTime initialValue,
@ -25,8 +25,9 @@ class DateFormField extends FormField<DateTime> {
this.label = 'Select date', this.label = 'Select date',
this.dateFormat, this.dateFormat,
this.decoration, this.decoration,
this.initialDatePickerMode = DatePickerMode.day}) this.initialDatePickerMode = DatePickerMode.day,
: super( this.mode = DateFieldPickerMode.date,
}) : super(
key: key, key: key,
initialValue: initialValue, initialValue: initialValue,
onSaved: onSaved, onSaved: onSaved,
@ -43,7 +44,7 @@ class DateFormField extends FormField<DateTime> {
field.didChange(value); field.didChange(value);
} }
return DateField( return DateTimeField(
label: label, label: label,
firstDate: firstDate, firstDate: firstDate,
lastDate: lastDate, lastDate: lastDate,
@ -54,7 +55,7 @@ class DateFormField extends FormField<DateTime> {
onDateSelected: onChangedHandler, onDateSelected: onChangedHandler,
selectedDate: state.value, selectedDate: state.value,
enabled: enabled, enabled: enabled,
); mode: mode);
}, },
); );
@ -80,30 +81,55 @@ class DateFormField extends FormField<DateTime> {
/// (optional) Let you choose the [DatePickerMode] for the date picker! (default is [DatePickerMode.day] /// (optional) Let you choose the [DatePickerMode] for the date picker! (default is [DatePickerMode.day]
final DatePickerMode initialDatePickerMode; final DatePickerMode initialDatePickerMode;
/// Whether to ask the user to pick only the date, the time or both.
final DateFieldPickerMode mode;
@override @override
_DateFormFieldState createState() => _DateFormFieldState(); _DateFormFieldState createState() => _DateFormFieldState();
} }
class _DateFormFieldState extends FormFieldState<DateTime> {} class _DateFormFieldState extends FormFieldState<DateTime> {}
/// [DateField] /// [DateTimeField]
/// ///
/// Shows an [_InputDropdown] that'll trigger [DateField._selectDate] whenever the user /// Shows an [_InputDropdown] that'll trigger [DateTimeField._selectDate] whenever the user
/// clicks on it ! The date picker is **platform responsive** (ios date picker style for ios, ...) /// clicks on it ! The date picker is **platform responsive** (ios date picker style for ios, ...)
class DateField extends StatelessWidget { class DateTimeField extends StatelessWidget {
/// Default constructor /// Default constructor
const DateField({ DateTimeField({
Key key,
@required this.onDateSelected, @required this.onDateSelected,
@required this.selectedDate, @required this.selectedDate,
this.firstDate,
this.lastDate,
this.initialDatePickerMode = DatePickerMode.day, this.initialDatePickerMode = DatePickerMode.day,
this.decoration, this.decoration,
this.errorText, this.errorText,
this.dateFormat,
this.label = 'Select date', this.label = 'Select date',
this.enabled = true, this.enabled = true,
}); this.mode = DateFieldPickerMode.dateAndTime,
DateTime firstDate,
DateTime lastDate,
DateFormat dateFormat,
}) : dateFormat = dateFormat ?? getDateFormatFromDateFieldPickerMode(mode),
firstDate = firstDate ?? DateTime(1900),
lastDate = lastDate ?? DateTime(2100),
super(key: key);
DateTimeField.time({
Key key,
this.onDateSelected,
this.selectedDate,
this.label,
this.errorText,
this.decoration,
this.enabled,
DateTime firstDate,
DateTime lastDate,
}) : initialDatePickerMode = null,
mode = DateFieldPickerMode.time,
dateFormat = DateFormat.jm(),
firstDate = firstDate ?? DateTime(2000),
lastDate = lastDate ?? DateTime(2001),
super(key: key);
/// Callback for whenever the user selects a [DateTime] /// Callback for whenever the user selects a [DateTime]
final ValueChanged<DateTime> onDateSelected; final ValueChanged<DateTime> onDateSelected;
@ -135,18 +161,23 @@ class DateField extends StatelessWidget {
/// (optional) Whether the field is usable. If false the user won't be able to select any date /// (optional) Whether the field is usable. If false the user won't be able to select any date
final bool enabled; final bool enabled;
/// Whether to ask the user to pick only the date, the time or both.
final DateFieldPickerMode mode;
/// Shows a dialog asking the user to pick a date ! /// Shows a dialog asking the user to pick a date !
Future<void> _selectDate(BuildContext context) async { Future<void> _selectDate(BuildContext context) async {
final DateTime initialDateTime = selectedDate ?? lastDate ?? DateTime.now();
if (Theme.of(context).platform == TargetPlatform.iOS) { if (Theme.of(context).platform == TargetPlatform.iOS) {
showModalBottomSheet<void>( showModalBottomSheet<void>(
context: context, context: context,
builder: (BuildContext builder) { builder: (BuildContext builder) {
return Container( return Container(
height: MediaQuery.of(context).size.height / 4, height: 216,
child: CupertinoDatePicker( child: CupertinoDatePicker(
mode: CupertinoDatePickerMode.date, mode: _cupertinoModeFromPickerMode(mode),
onDateTimeChanged: onDateSelected, onDateTimeChanged: onDateSelected,
initialDateTime: selectedDate ?? lastDate ?? DateTime.now(), initialDateTime: initialDateTime,
minimumDate: firstDate, minimumDate: firstDate,
maximumDate: lastDate, maximumDate: lastDate,
), ),
@ -154,47 +185,96 @@ class DateField extends StatelessWidget {
}, },
); );
} else { } else {
DateTime _selectedDateTime = initialDateTime;
if ([DateFieldPickerMode.dateAndTime, DateFieldPickerMode.date]
.contains(mode)) {
final DateTime _selectedDate = await showDatePicker( final DateTime _selectedDate = await showDatePicker(
context: context, context: context,
initialDatePickerMode: initialDatePickerMode, initialDatePickerMode: initialDatePickerMode,
initialDate: selectedDate ?? lastDate ?? DateTime.now(), initialDate: initialDateTime,
firstDate: firstDate ?? DateTime(1900), firstDate: firstDate,
lastDate: lastDate ?? DateTime(2100)); lastDate: lastDate);
if (_selectedDate != null) { if (_selectedDate != null) {
onDateSelected(_selectedDate); _selectedDateTime = _selectedDate;
} }
} }
if ([DateFieldPickerMode.dateAndTime, DateFieldPickerMode.time]
.contains(mode)) {
final TimeOfDay _selectedTime = await showTimePicker(
initialTime: TimeOfDay.fromDateTime(initialDateTime),
context: context,
);
if (_selectedTime != null) {
_selectedDateTime = DateTime(
_selectedDateTime.year,
_selectedDateTime.month,
_selectedDateTime.day,
_selectedTime.hour,
_selectedTime.minute);
}
}
onDateSelected(_selectedDateTime);
}
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
String text; String text;
if (selectedDate != null) if (selectedDate != null) text = dateFormat.format(selectedDate);
text = (dateFormat ?? DateFormat.yMMMd()).format(selectedDate);
return _InputDropdown( return _InputDropdown(
text: text ?? label, text: text ?? label,
label: text == null ? null : label, label: text == null ? null : label,
errorText: errorText, errorText: errorText,
decoration: decoration, decoration: decoration,
onPressed: enabled onPressed: enabled ? () => _selectDate(context) : null,
? () {
_selectDate(context);
}
: null,
); );
} }
} }
/// Those values are used by the [DateTimeField] widget to determine whether to ask
/// the user for the time, the date or both.
enum DateFieldPickerMode { time, date, dateAndTime }
/// Returns the [CupertinoDatePickerMode] corresponding to the selected
/// [DateFieldPickerMode]. This exists to prevent redundancy in the [DateTimeField]
/// widget parameters.
CupertinoDatePickerMode _cupertinoModeFromPickerMode(DateFieldPickerMode mode) {
switch (mode) {
case DateFieldPickerMode.time:
return CupertinoDatePickerMode.time;
case DateFieldPickerMode.date:
return CupertinoDatePickerMode.date;
default:
return CupertinoDatePickerMode.dateAndTime;
}
}
/// Returns the corresponding default [DateFormat] for the selected [DateFieldPickerMode]
DateFormat getDateFormatFromDateFieldPickerMode(DateFieldPickerMode mode) {
switch (mode) {
case DateFieldPickerMode.time:
return DateFormat.jm();
case DateFieldPickerMode.date:
return DateFormat.yMMMMd();
default:
return DateFormat.yMd().add_jm();
}
}
/// ///
/// [_InputDropdown] /// [_InputDropdown]
/// ///
/// Shows a field with a dropdown arrow ! /// Shows a field with a dropdown arrow !
/// It does not show any popup menu, it'll just trigger onPressed whenever the /// It does not show any popup menu, it'll just trigger onPressed whenever the
/// user does click on it ! /// user does click on it !
class _InputDropdown extends StatelessWidget { class _InputDropdown extends StatelessWidget {
const _InputDropdown({ const _InputDropdown({
Key key, Key key,
@required this.text, @required this.text,
@ -254,3 +334,230 @@ class _InputDropdown extends StatelessWidget {
); );
} }
} }
/// Deprecated! Use [DateTimeFormField] instead
@Deprecated('This widget will be removed in the next version of the date_field package, consider switching to DateTimeFormField')
class DateFormField extends FormField<DateTime> {
DateFormField({
Key key,
FormFieldSetter<DateTime> onSaved,
FormFieldValidator<DateTime> validator,
DateTime initialValue,
bool autovalidate = false,
bool enabled = true,
this.onDateSelected,
this.firstDate,
this.lastDate,
this.label = 'Select date',
this.dateFormat,
this.decoration,
this.initialDatePickerMode = DatePickerMode.day,
this.mode = DateFieldPickerMode.date,
}) : super(
key: key,
initialValue: initialValue,
onSaved: onSaved,
validator: validator,
autovalidate: autovalidate,
enabled: enabled,
builder: (FormFieldState<DateTime> field) {
final _DateFormFieldState state = field;
void onChangedHandler(DateTime value) {
if (onDateSelected != null) {
onDateSelected(value);
}
field.didChange(value);
}
return DateTimeField(
label: label,
firstDate: firstDate,
lastDate: lastDate,
decoration: decoration,
initialDatePickerMode: initialDatePickerMode,
dateFormat: dateFormat,
errorText: state.errorText,
onDateSelected: onChangedHandler,
selectedDate: state.value,
enabled: enabled,
mode: mode);
},
);
/// (optional) A callback that will be triggered whenever a new
/// DateTime is selected
final ValueChanged<DateTime> onDateSelected;
/// (optional) The first date that the user can select (default is 1900)
final DateTime firstDate;
/// (optional) The last date that the user can select (default is 2100)
final DateTime lastDate;
/// (optional) The label to display for the field (default is 'Select date')
final String label;
/// (optional) Custom [InputDecoration] for the [InputDecorator] widget
final InputDecoration decoration;
/// (optional) How to display the [DateTime] for the user (default is [DateFormat.yMMMD])
final DateFormat dateFormat;
/// (optional) Let you choose the [DatePickerMode] for the date picker! (default is [DatePickerMode.day]
final DatePickerMode initialDatePickerMode;
/// Whether to ask the user to pick only the date, the time or both.
final DateFieldPickerMode mode;
@override
_DateFormFieldState createState() => _DateFormFieldState();
}
/// Deprecated! Use [DateField] instead
@Deprecated('This widget will be removed in the next version of the date_field package, consider switching to DateTimeField')
class DateField extends StatelessWidget {
/// Default constructor
DateField({
Key key,
@required this.onDateSelected,
@required this.selectedDate,
this.initialDatePickerMode = DatePickerMode.day,
this.decoration,
this.errorText,
this.label = 'Select date',
this.enabled = true,
this.mode = DateFieldPickerMode.dateAndTime,
DateTime firstDate,
DateTime lastDate,
DateFormat dateFormat,
}) : dateFormat = dateFormat ?? getDateFormatFromDateFieldPickerMode(mode),
firstDate = firstDate ?? DateTime(1900),
lastDate = lastDate ?? DateTime(2100),
super(key: key);
DateField.time({
Key key,
this.onDateSelected,
this.selectedDate,
this.label,
this.errorText,
this.decoration,
this.enabled,
DateTime firstDate,
DateTime lastDate,
}) : initialDatePickerMode = null,
mode = DateFieldPickerMode.time,
dateFormat = DateFormat.jm(),
firstDate = firstDate ?? DateTime(2000),
lastDate = lastDate ?? DateTime(2001),
super(key: key);
/// Callback for whenever the user selects a [DateTime]
final ValueChanged<DateTime> onDateSelected;
/// The current selected date to display inside the field
final DateTime selectedDate;
/// (optional) The first date that the user can select (default is 1900)
final DateTime firstDate;
/// (optional) The last date that the user can select (default is 2100)
final DateTime lastDate;
/// Let you choose the [DatePickerMode] for the date picker! (default is [DatePickerMode.day]
final DatePickerMode initialDatePickerMode;
/// The label to display for the field (default is 'Select date')
final String label;
/// (optional) The error text that should be displayed under the field
final String errorText;
/// (optional) Custom [InputDecoration] for the [InputDecorator] widget
final InputDecoration decoration;
/// (optional) How to display the [DateTime] for the user (default is [DateFormat.yMMMD])
final DateFormat dateFormat;
/// (optional) Whether the field is usable. If false the user won't be able to select any date
final bool enabled;
/// Whether to ask the user to pick only the date, the time or both.
final DateFieldPickerMode mode;
/// Shows a dialog asking the user to pick a date !
Future<void> _selectDate(BuildContext context) async {
final DateTime initialDateTime = selectedDate ?? lastDate ?? DateTime.now();
if (Theme.of(context).platform == TargetPlatform.iOS) {
showModalBottomSheet<void>(
context: context,
builder: (BuildContext builder) {
return Container(
height: 216,
child: CupertinoDatePicker(
mode: _cupertinoModeFromPickerMode(mode),
onDateTimeChanged: onDateSelected,
initialDateTime: initialDateTime,
minimumDate: firstDate,
maximumDate: lastDate,
),
);
},
);
} else {
DateTime _selectedDateTime = initialDateTime;
if ([DateFieldPickerMode.dateAndTime, DateFieldPickerMode.date]
.contains(mode)) {
final DateTime _selectedDate = await showDatePicker(
context: context,
initialDatePickerMode: initialDatePickerMode,
initialDate: initialDateTime,
firstDate: firstDate,
lastDate: lastDate);
if (_selectedDate != null) {
_selectedDateTime = _selectedDate;
}
}
if ([DateFieldPickerMode.dateAndTime, DateFieldPickerMode.time]
.contains(mode)) {
final TimeOfDay _selectedTime = await showTimePicker(
initialTime: TimeOfDay.fromDateTime(initialDateTime),
context: context,
);
if (_selectedTime != null) {
_selectedDateTime = DateTime(
_selectedDateTime.year,
_selectedDateTime.month,
_selectedDateTime.day,
_selectedTime.hour,
_selectedTime.minute);
}
}
onDateSelected(_selectedDateTime);
}
}
@override
Widget build(BuildContext context) {
String text;
if (selectedDate != null) text = dateFormat.format(selectedDate);
return _InputDropdown(
text: text ?? label,
label: text == null ? null : label,
errorText: errorText,
decoration: decoration,
onPressed: enabled ? () => _selectDate(context) : null,
);
}
}