[State Restoration] Material DateRangePicker, adds some general state restoration tests (#78506)
This commit is contained in:
parent
2977a346fe
commit
e58ee0fbc7
@ -901,14 +901,14 @@ class _DatePickerHeader extends StatelessWidget {
|
|||||||
/// returned.
|
/// returned.
|
||||||
///
|
///
|
||||||
/// If [initialDateRange] is non-null, then it will be used as the initially
|
/// If [initialDateRange] is non-null, then it will be used as the initially
|
||||||
/// selected date range. If it is provided, [initialDateRange.start] must be
|
/// selected date range. If it is provided, `initialDateRange.start` must be
|
||||||
/// before or on [initialDateRange.end].
|
/// before or on `initialDateRange.end`.
|
||||||
///
|
///
|
||||||
/// The [firstDate] is the earliest allowable date. The [lastDate] is the latest
|
/// The [firstDate] is the earliest allowable date. The [lastDate] is the latest
|
||||||
/// allowable date. Both must be non-null.
|
/// allowable date. Both must be non-null.
|
||||||
///
|
///
|
||||||
/// If an initial date range is provided, [initialDateRange.start]
|
/// If an initial date range is provided, `initialDateRange.start`
|
||||||
/// and [initialDateRange.end] must both fall between or on [firstDate] and
|
/// and `initialDateRange.end` must both fall between or on [firstDate] and
|
||||||
/// [lastDate]. For all of these [DateTime] values, only their dates are
|
/// [lastDate]. For all of these [DateTime] values, only their dates are
|
||||||
/// considered. Their time fields are ignored.
|
/// considered. Their time fields are ignored.
|
||||||
///
|
///
|
||||||
@ -958,6 +958,133 @@ class _DatePickerHeader extends StatelessWidget {
|
|||||||
/// The [builder] parameter can be used to wrap the dialog widget
|
/// The [builder] parameter can be used to wrap the dialog widget
|
||||||
/// to add inherited widgets like [Theme].
|
/// to add inherited widgets like [Theme].
|
||||||
///
|
///
|
||||||
|
/// ### State Restoration
|
||||||
|
///
|
||||||
|
/// Using this method will not enable state restoration for the date range picker.
|
||||||
|
/// In order to enable state restoration for a date range picker, use
|
||||||
|
/// [Navigator.restorablePush] or [Navigator.restorablePushNamed] with
|
||||||
|
/// [DateRangePickerDialog].
|
||||||
|
///
|
||||||
|
/// For more information about state restoration, see [RestorationManager].
|
||||||
|
///
|
||||||
|
/// {@macro flutter.widgets.RestorationManager}
|
||||||
|
///
|
||||||
|
/// {@tool sample --template=freeform}
|
||||||
|
///
|
||||||
|
/// This sample demonstrates how to create a restorable Material date range picker.
|
||||||
|
/// This is accomplished by enabling state restoration by specifying
|
||||||
|
/// [MaterialApp.restorationScopeId] and using [Navigator.restorablePush] to
|
||||||
|
/// push [DateRangePickerDialog] when the button is tapped.
|
||||||
|
///
|
||||||
|
/// ```dart imports
|
||||||
|
/// import 'package:flutter/material.dart';
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// void main() {
|
||||||
|
/// runApp(const MyApp());
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// class MyApp extends StatelessWidget {
|
||||||
|
/// const MyApp({Key? key}) : super(key: key);
|
||||||
|
///
|
||||||
|
/// @override
|
||||||
|
/// Widget build(BuildContext context) {
|
||||||
|
/// return const MaterialApp(
|
||||||
|
/// restorationScopeId: 'app',
|
||||||
|
/// title: 'Restorable Date Range Picker Demo',
|
||||||
|
/// home: MyHomePage(),
|
||||||
|
/// );
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// class MyHomePage extends StatefulWidget {
|
||||||
|
/// const MyHomePage({Key? key}) : super(key: key);
|
||||||
|
///
|
||||||
|
/// @override
|
||||||
|
/// _MyHomePageState createState() => _MyHomePageState();
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// class _MyHomePageState extends State<MyHomePage> with RestorationMixin {
|
||||||
|
/// final RestorableDateTimeN _startDate = RestorableDateTimeN(DateTime(2021, 1, 1));
|
||||||
|
/// final RestorableDateTimeN _endDate = RestorableDateTimeN(DateTime(2021, 1, 5));
|
||||||
|
/// late final RestorableRouteFuture<DateTimeRange?> _restorableDateRangePickerRouteFuture = RestorableRouteFuture<DateTimeRange?>(
|
||||||
|
/// onComplete: _selectDateRange,
|
||||||
|
/// onPresent: (NavigatorState navigator, Object? arguments) {
|
||||||
|
/// return navigator.restorablePush(
|
||||||
|
/// _dateRangePickerRoute,
|
||||||
|
/// arguments: <String, dynamic>{
|
||||||
|
/// 'initialStartDate': _startDate.value?.millisecondsSinceEpoch,
|
||||||
|
/// 'initialEndDate': _endDate.value?.millisecondsSinceEpoch,
|
||||||
|
/// }
|
||||||
|
/// );
|
||||||
|
/// },
|
||||||
|
/// );
|
||||||
|
///
|
||||||
|
/// void _selectDateRange(DateTimeRange? newSelectedDate) {
|
||||||
|
/// if (newSelectedDate != null) {
|
||||||
|
/// setState(() {
|
||||||
|
/// _startDate.value = newSelectedDate.start;
|
||||||
|
/// _endDate.value = newSelectedDate.end;
|
||||||
|
/// });
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// @override
|
||||||
|
/// String get restorationId => 'scaffold_state';
|
||||||
|
///
|
||||||
|
/// @override
|
||||||
|
/// void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
|
||||||
|
/// registerForRestoration(_startDate, 'start_date');
|
||||||
|
/// registerForRestoration(_endDate, 'end_date');
|
||||||
|
/// registerForRestoration(_restorableDateRangePickerRouteFuture, 'date_picker_route_future');
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// static Route<DateTimeRange?> _dateRangePickerRoute(
|
||||||
|
/// BuildContext context,
|
||||||
|
/// Object? arguments,
|
||||||
|
/// ) {
|
||||||
|
/// return DialogRoute<DateTimeRange?>(
|
||||||
|
/// context: context,
|
||||||
|
/// builder: (BuildContext context) {
|
||||||
|
/// return DateRangePickerDialog(
|
||||||
|
/// restorationId: 'date_picker_dialog',
|
||||||
|
/// initialDateRange: _initialDateTimeRange(arguments! as Map<dynamic, dynamic>),
|
||||||
|
/// firstDate: DateTime(2021, 1, 1),
|
||||||
|
/// currentDate: DateTime(2021, 1, 25),
|
||||||
|
/// lastDate: DateTime(2022, 1, 1),
|
||||||
|
/// );
|
||||||
|
/// },
|
||||||
|
/// );
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// static DateTimeRange? _initialDateTimeRange(Map<dynamic, dynamic> arguments) {
|
||||||
|
/// if (arguments['initialStartDate'] != null && arguments['initialEndDate'] != null) {
|
||||||
|
/// return DateTimeRange(
|
||||||
|
/// start: DateTime.fromMillisecondsSinceEpoch(arguments['initialStartDate'] as int),
|
||||||
|
/// end: DateTime.fromMillisecondsSinceEpoch(arguments['initialEndDate'] as int),
|
||||||
|
/// );
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// return null;
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// @override
|
||||||
|
/// Widget build(BuildContext context) {
|
||||||
|
/// return Scaffold(
|
||||||
|
/// body: Center(
|
||||||
|
/// child: OutlinedButton(
|
||||||
|
/// onPressed: () { _restorableDateRangePickerRouteFuture.present(); },
|
||||||
|
/// child: const Text('Open Date Range Picker'),
|
||||||
|
/// ),
|
||||||
|
/// ),
|
||||||
|
/// );
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// {@end-tool}
|
||||||
|
///
|
||||||
/// See also:
|
/// See also:
|
||||||
///
|
///
|
||||||
/// * [showDatePicker], which shows a material design date picker used to
|
/// * [showDatePicker], which shows a material design date picker used to
|
||||||
@ -1027,7 +1154,7 @@ Future<DateTimeRange?> showDateRangePicker({
|
|||||||
assert(useRootNavigator != null);
|
assert(useRootNavigator != null);
|
||||||
assert(debugCheckHasMaterialLocalizations(context));
|
assert(debugCheckHasMaterialLocalizations(context));
|
||||||
|
|
||||||
Widget dialog = _DateRangePickerDialog(
|
Widget dialog = DateRangePickerDialog(
|
||||||
initialDateRange: initialDateRange,
|
initialDateRange: initialDateRange,
|
||||||
firstDate: firstDate,
|
firstDate: firstDate,
|
||||||
lastDate: lastDate,
|
lastDate: lastDate,
|
||||||
@ -1100,8 +1227,18 @@ String _formatRangeEndDate(MaterialLocalizations localizations, DateTime? startD
|
|||||||
: localizations.formatShortDate(endDate);
|
: localizations.formatShortDate(endDate);
|
||||||
}
|
}
|
||||||
|
|
||||||
class _DateRangePickerDialog extends StatefulWidget {
|
/// A Material-style date range picker dialog.
|
||||||
const _DateRangePickerDialog({
|
///
|
||||||
|
/// It is used internally by [showDateRangePicker] or can be directly pushed
|
||||||
|
/// onto the [Navigator] stack to enable state restoration. See
|
||||||
|
/// [showDateRangePicker] for a state restoration app example.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [showDateRangePicker], which is a way to display the date picker.
|
||||||
|
class DateRangePickerDialog extends StatefulWidget {
|
||||||
|
/// A Material-style date range picker dialog.
|
||||||
|
const DateRangePickerDialog({
|
||||||
Key? key,
|
Key? key,
|
||||||
this.initialDateRange,
|
this.initialDateRange,
|
||||||
required this.firstDate,
|
required this.firstDate,
|
||||||
@ -1119,58 +1256,161 @@ class _DateRangePickerDialog extends StatefulWidget {
|
|||||||
this.fieldEndHintText,
|
this.fieldEndHintText,
|
||||||
this.fieldStartLabelText,
|
this.fieldStartLabelText,
|
||||||
this.fieldEndLabelText,
|
this.fieldEndLabelText,
|
||||||
|
this.restorationId,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
|
/// The date range that the date range picker starts with when it opens.
|
||||||
|
///
|
||||||
|
/// If an initial date range is provided, `initialDateRange.start`
|
||||||
|
/// and `initialDateRange.end` must both fall between or on [firstDate] and
|
||||||
|
/// [lastDate]. For all of these [DateTime] values, only their dates are
|
||||||
|
/// considered. Their time fields are ignored.
|
||||||
|
///
|
||||||
|
/// If [initialDateRange] is non-null, then it will be used as the initially
|
||||||
|
/// selected date range. If it is provided, `initialDateRange.start` must be
|
||||||
|
/// before or on `initialDateRange.end`.
|
||||||
final DateTimeRange? initialDateRange;
|
final DateTimeRange? initialDateRange;
|
||||||
|
|
||||||
|
/// The earliest allowable date on the date range.
|
||||||
final DateTime firstDate;
|
final DateTime firstDate;
|
||||||
|
|
||||||
|
/// The latest allowable date on the date range.
|
||||||
final DateTime lastDate;
|
final DateTime lastDate;
|
||||||
|
|
||||||
|
/// The [currentDate] represents the current day (i.e. today).
|
||||||
|
///
|
||||||
|
/// This date will be highlighted in the day grid.
|
||||||
|
///
|
||||||
|
/// If `null`, the date of `DateTime.now()` will be used.
|
||||||
final DateTime? currentDate;
|
final DateTime? currentDate;
|
||||||
|
|
||||||
|
/// The initial date range picker entry mode.
|
||||||
|
///
|
||||||
|
/// The date range has two main modes: [DatePickerEntryMode.calendar] (a
|
||||||
|
/// scrollable calendar month grid) or [DatePickerEntryMode.input] (two text
|
||||||
|
/// input fields) mode.
|
||||||
|
///
|
||||||
|
/// It defaults to [DatePickerEntryMode.calendar] and must be non-null.
|
||||||
final DatePickerEntryMode initialEntryMode;
|
final DatePickerEntryMode initialEntryMode;
|
||||||
|
|
||||||
|
/// The label on the cancel button for the text input mode.
|
||||||
|
///
|
||||||
|
/// If null, the localized value of
|
||||||
|
/// [MaterialLocalizations.cancelButtonLabel] is used.
|
||||||
final String? cancelText;
|
final String? cancelText;
|
||||||
|
|
||||||
|
/// The label on the "OK" button for the text input mode.
|
||||||
|
///
|
||||||
|
/// If null, the localized value of
|
||||||
|
/// [MaterialLocalizations.okButtonLabel] is used.
|
||||||
final String? confirmText;
|
final String? confirmText;
|
||||||
|
|
||||||
|
/// The label on the save button for the fullscreen calendar mode.
|
||||||
|
///
|
||||||
|
/// If null, the localized value of
|
||||||
|
/// [MaterialLocalizations.saveButtonLabel] is used.
|
||||||
final String? saveText;
|
final String? saveText;
|
||||||
|
|
||||||
|
/// The label displayed at the top of the dialog.
|
||||||
|
///
|
||||||
|
/// If null, the localized value of
|
||||||
|
/// [MaterialLocalizations.dateRangePickerHelpText] is used.
|
||||||
final String? helpText;
|
final String? helpText;
|
||||||
|
|
||||||
|
/// The message used when the date range is invalid (e.g. start date is after
|
||||||
|
/// end date).
|
||||||
|
///
|
||||||
|
/// If null, the localized value of
|
||||||
|
/// [MaterialLocalizations.invalidDateRangeLabel] is used.
|
||||||
final String? errorInvalidRangeText;
|
final String? errorInvalidRangeText;
|
||||||
|
|
||||||
|
/// The message used when an input text isn't in a proper date format.
|
||||||
|
///
|
||||||
|
/// If null, the localized value of
|
||||||
|
/// [MaterialLocalizations.invalidDateFormatLabel] is used.
|
||||||
final String? errorFormatText;
|
final String? errorFormatText;
|
||||||
|
|
||||||
|
/// The message used when an input text isn't a selectable date.
|
||||||
|
///
|
||||||
|
/// If null, the localized value of
|
||||||
|
/// [MaterialLocalizations.dateOutOfRangeLabel] is used.
|
||||||
final String? errorInvalidText;
|
final String? errorInvalidText;
|
||||||
|
|
||||||
|
/// The text used to prompt the user when no text has been entered in the
|
||||||
|
/// start field.
|
||||||
|
///
|
||||||
|
/// If null, the localized value of
|
||||||
|
/// [MaterialLocalizations.dateHelpText] is used.
|
||||||
final String? fieldStartHintText;
|
final String? fieldStartHintText;
|
||||||
|
|
||||||
|
/// The text used to prompt the user when no text has been entered in the
|
||||||
|
/// end field.
|
||||||
|
///
|
||||||
|
/// If null, the localized value of [MaterialLocalizations.dateHelpText] is
|
||||||
|
/// used.
|
||||||
final String? fieldEndHintText;
|
final String? fieldEndHintText;
|
||||||
|
|
||||||
|
/// The label for the start date text input field.
|
||||||
|
///
|
||||||
|
/// If null, the localized value of [MaterialLocalizations.dateRangeStartLabel]
|
||||||
|
/// is used.
|
||||||
final String? fieldStartLabelText;
|
final String? fieldStartLabelText;
|
||||||
|
|
||||||
|
/// The label for the end date text input field.
|
||||||
|
///
|
||||||
|
/// If null, the localized value of [MaterialLocalizations.dateRangeEndLabel]
|
||||||
|
/// is used.
|
||||||
final String? fieldEndLabelText;
|
final String? fieldEndLabelText;
|
||||||
|
|
||||||
|
/// Restoration ID to save and restore the state of the [DateRangePickerDialog].
|
||||||
|
///
|
||||||
|
/// If it is non-null, the date range picker will persist and restore the
|
||||||
|
/// date range selected on the dialog.
|
||||||
|
///
|
||||||
|
/// The state of this widget is persisted in a [RestorationBucket] claimed
|
||||||
|
/// from the surrounding [RestorationScope] using the provided restoration ID.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [RestorationManager], which explains how state restoration works in
|
||||||
|
/// Flutter.
|
||||||
|
final String? restorationId;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_DateRangePickerDialogState createState() => _DateRangePickerDialogState();
|
_DateRangePickerDialogState createState() => _DateRangePickerDialogState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _DateRangePickerDialogState extends State<_DateRangePickerDialog> {
|
class _DateRangePickerDialogState extends State<DateRangePickerDialog> with RestorationMixin {
|
||||||
late DatePickerEntryMode _entryMode;
|
late final _RestorableDatePickerEntryMode _entryMode = _RestorableDatePickerEntryMode(widget.initialEntryMode);
|
||||||
DateTime? _selectedStart;
|
late final RestorableDateTimeN _selectedStart = RestorableDateTimeN(widget.initialDateRange?.start);
|
||||||
DateTime? _selectedEnd;
|
late final RestorableDateTimeN _selectedEnd = RestorableDateTimeN(widget.initialDateRange?.end);
|
||||||
late bool _autoValidate;
|
final RestorableBool _autoValidate = RestorableBool(false);
|
||||||
final GlobalKey _calendarPickerKey = GlobalKey();
|
final GlobalKey _calendarPickerKey = GlobalKey();
|
||||||
final GlobalKey<_InputDateRangePickerState> _inputPickerKey = GlobalKey<_InputDateRangePickerState>();
|
final GlobalKey<_InputDateRangePickerState> _inputPickerKey = GlobalKey<_InputDateRangePickerState>();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
String? get restorationId => widget.restorationId;
|
||||||
super.initState();
|
|
||||||
_selectedStart = widget.initialDateRange?.start;
|
@override
|
||||||
_selectedEnd = widget.initialDateRange?.end;
|
void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
|
||||||
_entryMode = widget.initialEntryMode;
|
registerForRestoration(_entryMode, 'entry_mode');
|
||||||
_autoValidate = false;
|
registerForRestoration(_selectedStart, 'selected_start');
|
||||||
|
registerForRestoration(_selectedEnd, 'selected_end');
|
||||||
|
registerForRestoration(_autoValidate, 'autovalidate');
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleOk() {
|
void _handleOk() {
|
||||||
if (_entryMode == DatePickerEntryMode.input || _entryMode == DatePickerEntryMode.inputOnly) {
|
if (_entryMode.value == DatePickerEntryMode.input || _entryMode.value == DatePickerEntryMode.inputOnly) {
|
||||||
final _InputDateRangePickerState picker = _inputPickerKey.currentState!;
|
final _InputDateRangePickerState picker = _inputPickerKey.currentState!;
|
||||||
if (!picker.validate()) {
|
if (!picker.validate()) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_autoValidate = true;
|
_autoValidate.value = true;
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
final DateTimeRange? selectedRange = _hasSelectedDateRange
|
final DateTimeRange? selectedRange = _hasSelectedDateRange
|
||||||
? DateTimeRange(start: _selectedStart!, end: _selectedEnd!)
|
? DateTimeRange(start: _selectedStart.value!, end: _selectedEnd.value!)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
Navigator.pop(context, selectedRange);
|
Navigator.pop(context, selectedRange);
|
||||||
@ -1182,29 +1422,29 @@ class _DateRangePickerDialogState extends State<_DateRangePickerDialog> {
|
|||||||
|
|
||||||
void _handleEntryModeToggle() {
|
void _handleEntryModeToggle() {
|
||||||
setState(() {
|
setState(() {
|
||||||
switch (_entryMode) {
|
switch (_entryMode.value) {
|
||||||
case DatePickerEntryMode.calendar:
|
case DatePickerEntryMode.calendar:
|
||||||
_autoValidate = false;
|
_autoValidate.value = false;
|
||||||
_entryMode = DatePickerEntryMode.input;
|
_entryMode.value = DatePickerEntryMode.input;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DatePickerEntryMode.input:
|
case DatePickerEntryMode.input:
|
||||||
// Validate the range dates
|
// Validate the range dates
|
||||||
if (_selectedStart != null &&
|
if (_selectedStart.value != null &&
|
||||||
(_selectedStart!.isBefore(widget.firstDate) || _selectedStart!.isAfter(widget.lastDate))) {
|
(_selectedStart.value!.isBefore(widget.firstDate) || _selectedStart.value!.isAfter(widget.lastDate))) {
|
||||||
_selectedStart = null;
|
_selectedStart.value = null;
|
||||||
// With no valid start date, having an end date makes no sense for the UI.
|
// With no valid start date, having an end date makes no sense for the UI.
|
||||||
_selectedEnd = null;
|
_selectedEnd.value = null;
|
||||||
}
|
}
|
||||||
if (_selectedEnd != null &&
|
if (_selectedEnd.value != null &&
|
||||||
(_selectedEnd!.isBefore(widget.firstDate) || _selectedEnd!.isAfter(widget.lastDate))) {
|
(_selectedEnd.value!.isBefore(widget.firstDate) || _selectedEnd.value!.isAfter(widget.lastDate))) {
|
||||||
_selectedEnd = null;
|
_selectedEnd.value = null;
|
||||||
}
|
}
|
||||||
// If invalid range (start after end), then just use the start date
|
// If invalid range (start after end), then just use the start date
|
||||||
if (_selectedStart != null && _selectedEnd != null && _selectedStart!.isAfter(_selectedEnd!)) {
|
if (_selectedStart.value != null && _selectedEnd.value != null && _selectedStart.value!.isAfter(_selectedEnd.value!)) {
|
||||||
_selectedEnd = null;
|
_selectedEnd.value = null;
|
||||||
}
|
}
|
||||||
_entryMode = DatePickerEntryMode.calendar;
|
_entryMode.value = DatePickerEntryMode.calendar;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DatePickerEntryMode.calendarOnly:
|
case DatePickerEntryMode.calendarOnly:
|
||||||
@ -1216,14 +1456,14 @@ class _DateRangePickerDialogState extends State<_DateRangePickerDialog> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _handleStartDateChanged(DateTime? date) {
|
void _handleStartDateChanged(DateTime? date) {
|
||||||
setState(() => _selectedStart = date);
|
setState(() => _selectedStart.value = date);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleEndDateChanged(DateTime? date) {
|
void _handleEndDateChanged(DateTime? date) {
|
||||||
setState(() => _selectedEnd = date);
|
setState(() => _selectedEnd.value = date);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool get _hasSelectedDateRange => _selectedStart != null && _selectedEnd != null;
|
bool get _hasSelectedDateRange => _selectedStart.value != null && _selectedEnd.value != null;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -1242,15 +1482,15 @@ class _DateRangePickerDialogState extends State<_DateRangePickerDialog> {
|
|||||||
final double elevation;
|
final double elevation;
|
||||||
final EdgeInsets insetPadding;
|
final EdgeInsets insetPadding;
|
||||||
final bool showEntryModeButton =
|
final bool showEntryModeButton =
|
||||||
_entryMode == DatePickerEntryMode.calendar ||
|
_entryMode.value == DatePickerEntryMode.calendar ||
|
||||||
_entryMode == DatePickerEntryMode.input;
|
_entryMode.value == DatePickerEntryMode.input;
|
||||||
switch (_entryMode) {
|
switch (_entryMode.value) {
|
||||||
case DatePickerEntryMode.calendar:
|
case DatePickerEntryMode.calendar:
|
||||||
case DatePickerEntryMode.calendarOnly:
|
case DatePickerEntryMode.calendarOnly:
|
||||||
contents = _CalendarRangePickerDialog(
|
contents = _CalendarRangePickerDialog(
|
||||||
key: _calendarPickerKey,
|
key: _calendarPickerKey,
|
||||||
selectedStartDate: _selectedStart,
|
selectedStartDate: _selectedStart.value,
|
||||||
selectedEndDate: _selectedEnd,
|
selectedEndDate: _selectedEnd.value,
|
||||||
firstDate: widget.firstDate,
|
firstDate: widget.firstDate,
|
||||||
lastDate: widget.lastDate,
|
lastDate: widget.lastDate,
|
||||||
currentDate: widget.currentDate,
|
currentDate: widget.currentDate,
|
||||||
@ -1279,8 +1519,8 @@ class _DateRangePickerDialogState extends State<_DateRangePickerDialog> {
|
|||||||
case DatePickerEntryMode.input:
|
case DatePickerEntryMode.input:
|
||||||
case DatePickerEntryMode.inputOnly:
|
case DatePickerEntryMode.inputOnly:
|
||||||
contents = _InputDateRangePickerDialog(
|
contents = _InputDateRangePickerDialog(
|
||||||
selectedStartDate: _selectedStart,
|
selectedStartDate: _selectedStart.value,
|
||||||
selectedEndDate: _selectedEnd,
|
selectedEndDate: _selectedEnd.value,
|
||||||
currentDate: widget.currentDate,
|
currentDate: widget.currentDate,
|
||||||
picker: Container(
|
picker: Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
@ -1292,14 +1532,14 @@ class _DateRangePickerDialogState extends State<_DateRangePickerDialog> {
|
|||||||
const Spacer(),
|
const Spacer(),
|
||||||
_InputDateRangePicker(
|
_InputDateRangePicker(
|
||||||
key: _inputPickerKey,
|
key: _inputPickerKey,
|
||||||
initialStartDate: _selectedStart,
|
initialStartDate: _selectedStart.value,
|
||||||
initialEndDate: _selectedEnd,
|
initialEndDate: _selectedEnd.value,
|
||||||
firstDate: widget.firstDate,
|
firstDate: widget.firstDate,
|
||||||
lastDate: widget.lastDate,
|
lastDate: widget.lastDate,
|
||||||
onStartDateChanged: _handleStartDateChanged,
|
onStartDateChanged: _handleStartDateChanged,
|
||||||
onEndDateChanged: _handleEndDateChanged,
|
onEndDateChanged: _handleEndDateChanged,
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
autovalidate: _autoValidate,
|
autovalidate: _autoValidate.value,
|
||||||
helpText: widget.helpText,
|
helpText: widget.helpText,
|
||||||
errorInvalidRangeText: widget.errorInvalidRangeText,
|
errorInvalidRangeText: widget.errorInvalidRangeText,
|
||||||
errorFormatText: widget.errorFormatText,
|
errorFormatText: widget.errorFormatText,
|
||||||
|
@ -385,6 +385,34 @@ class RestorableDateTime extends RestorableValue<DateTime> {
|
|||||||
Object? toPrimitives() => value.millisecondsSinceEpoch;
|
Object? toPrimitives() => value.millisecondsSinceEpoch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A [RestorableValue] that knows how to save and restore [DateTime] that is
|
||||||
|
/// nullable.
|
||||||
|
///
|
||||||
|
/// {@macro flutter.widgets.RestorableNum}.
|
||||||
|
class RestorableDateTimeN extends RestorableValue<DateTime?> {
|
||||||
|
/// Creates a [RestorableDateTime].
|
||||||
|
///
|
||||||
|
/// {@macro flutter.widgets.RestorableNum.constructor}
|
||||||
|
RestorableDateTimeN(DateTime? defaultValue) : _defaultValue = defaultValue;
|
||||||
|
|
||||||
|
final DateTime? _defaultValue;
|
||||||
|
|
||||||
|
@override
|
||||||
|
DateTime? createDefaultValue() => _defaultValue;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateValue(DateTime? oldValue) {
|
||||||
|
assert(debugIsSerializableForRestoration(value?.millisecondsSinceEpoch));
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
DateTime? fromPrimitives(Object? data) => data != null ? DateTime.fromMillisecondsSinceEpoch(data as int) : null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Object? toPrimitives() => value?.millisecondsSinceEpoch;
|
||||||
|
}
|
||||||
|
|
||||||
/// A base class for creating a [RestorableProperty] that stores and restores a
|
/// A base class for creating a [RestorableProperty] that stores and restores a
|
||||||
/// [Listenable].
|
/// [Listenable].
|
||||||
///
|
///
|
||||||
|
@ -1162,9 +1162,14 @@ void main() {
|
|||||||
await tester.restoreFrom(restorationData);
|
await tester.restoreFrom(restorationData);
|
||||||
expect(find.byType(DatePickerDialog), findsOneWidget);
|
expect(find.byType(DatePickerDialog), findsOneWidget);
|
||||||
|
|
||||||
// Select a different date and close the date picker.
|
// Select a different date.
|
||||||
await tester.tap(find.text('30'));
|
await tester.tap(find.text('30'));
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
// Restart after the new selection. It should remain selected.
|
||||||
|
await tester.restartAndRestore();
|
||||||
|
|
||||||
|
// Close the date picker.
|
||||||
await tester.tap(find.text('OK'));
|
await tester.tap(find.text('OK'));
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
@ -1210,8 +1215,8 @@ void main() {
|
|||||||
await tester.tapAt(const Offset(10.0, 10.0));
|
await tester.tapAt(const Offset(10.0, 10.0));
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
// The date picker should be closed, the text value updated to the
|
// The date picker should be closed, the text value should be the same
|
||||||
// newly selected date.
|
// as before.
|
||||||
expect(find.byType(DatePickerDialog), findsNothing);
|
expect(find.byType(DatePickerDialog), findsNothing);
|
||||||
expect(find.text('25/7/2021'), findsOneWidget);
|
expect(find.text('25/7/2021'), findsOneWidget);
|
||||||
|
|
||||||
|
@ -853,4 +853,198 @@ void main() {
|
|||||||
_testInputDecorator(tester.widget(borderContainers.last), border, Colors.transparent);
|
_testInputDecorator(tester.widget(borderContainers.last), border, Colors.transparent);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('DatePickerDialog is state restorable', (WidgetTester tester) async {
|
||||||
|
await tester.pumpWidget(
|
||||||
|
const MaterialApp(
|
||||||
|
restorationScopeId: 'app',
|
||||||
|
home: _RestorableDateRangePickerDialogTestWidget(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// The date range picker should be closed.
|
||||||
|
expect(find.byType(DateRangePickerDialog), findsNothing);
|
||||||
|
expect(find.text('1/1/2021 to 5/1/2021'), findsOneWidget);
|
||||||
|
|
||||||
|
// Open the date range picker.
|
||||||
|
await tester.tap(find.text('X'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(find.byType(DateRangePickerDialog), findsOneWidget);
|
||||||
|
|
||||||
|
final TestRestorationData restorationData = await tester.getRestorationData();
|
||||||
|
await tester.restartAndRestore();
|
||||||
|
|
||||||
|
// The date range picker should be open after restoring.
|
||||||
|
expect(find.byType(DateRangePickerDialog), findsOneWidget);
|
||||||
|
|
||||||
|
// Close the date range picker.
|
||||||
|
await tester.tap(find.byIcon(Icons.close));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
// The date range picker should be closed, the text value updated to the
|
||||||
|
// newly selected date.
|
||||||
|
expect(find.byType(DateRangePickerDialog), findsNothing);
|
||||||
|
expect(find.text('1/1/2021 to 5/1/2021'), findsOneWidget);
|
||||||
|
|
||||||
|
// The date range picker should be open after restoring.
|
||||||
|
await tester.restoreFrom(restorationData);
|
||||||
|
expect(find.byType(DateRangePickerDialog), findsOneWidget);
|
||||||
|
|
||||||
|
// // Select a different date and close the date range picker.
|
||||||
|
await tester.tap(find.text('12').first);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
await tester.tap(find.text('14').first);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
// Restart after the new selection. It should remain selected.
|
||||||
|
await tester.restartAndRestore();
|
||||||
|
|
||||||
|
// Close the date range picker.
|
||||||
|
await tester.tap(find.text('SAVE'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
// The date range picker should be closed, the text value updated to the
|
||||||
|
// newly selected date.
|
||||||
|
expect(find.byType(DateRangePickerDialog), findsNothing);
|
||||||
|
expect(find.text('12/1/2021 to 14/1/2021'), findsOneWidget);
|
||||||
|
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/33615
|
||||||
|
|
||||||
|
testWidgets('DateRangePickerDialog state restoration - DatePickerEntryMode', (WidgetTester tester) async {
|
||||||
|
await tester.pumpWidget(
|
||||||
|
const MaterialApp(
|
||||||
|
restorationScopeId: 'app',
|
||||||
|
home: _RestorableDateRangePickerDialogTestWidget(
|
||||||
|
datePickerEntryMode: DatePickerEntryMode.calendarOnly,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// The date range picker should be closed.
|
||||||
|
expect(find.byType(DateRangePickerDialog), findsNothing);
|
||||||
|
expect(find.text('1/1/2021 to 5/1/2021'), findsOneWidget);
|
||||||
|
|
||||||
|
// Open the date range picker.
|
||||||
|
await tester.tap(find.text('X'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(find.byType(DateRangePickerDialog), findsOneWidget);
|
||||||
|
|
||||||
|
// Only in calendar mode and cannot switch out.
|
||||||
|
expect(find.byType(TextField), findsNothing);
|
||||||
|
expect(find.byIcon(Icons.edit), findsNothing);
|
||||||
|
|
||||||
|
final TestRestorationData restorationData = await tester.getRestorationData();
|
||||||
|
await tester.restartAndRestore();
|
||||||
|
|
||||||
|
// The date range picker should be open after restoring.
|
||||||
|
expect(find.byType(DateRangePickerDialog), findsOneWidget);
|
||||||
|
// Only in calendar mode and cannot switch out.
|
||||||
|
expect(find.byType(TextField), findsNothing);
|
||||||
|
expect(find.byIcon(Icons.edit), findsNothing);
|
||||||
|
|
||||||
|
// Tap on the barrier.
|
||||||
|
await tester.tap(find.byIcon(Icons.close));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
// The date range picker should be closed, the text value should be the same
|
||||||
|
// as before.
|
||||||
|
expect(find.byType(DateRangePickerDialog), findsNothing);
|
||||||
|
expect(find.text('1/1/2021 to 5/1/2021'), findsOneWidget);
|
||||||
|
|
||||||
|
// The date range picker should be open after restoring.
|
||||||
|
await tester.restoreFrom(restorationData);
|
||||||
|
expect(find.byType(DateRangePickerDialog), findsOneWidget);
|
||||||
|
// Only in calendar mode and cannot switch out.
|
||||||
|
expect(find.byType(TextField), findsNothing);
|
||||||
|
expect(find.byIcon(Icons.edit), findsNothing);
|
||||||
|
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/33615
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RestorableDateRangePickerDialogTestWidget extends StatefulWidget {
|
||||||
|
const _RestorableDateRangePickerDialogTestWidget({
|
||||||
|
Key? key,
|
||||||
|
this.datePickerEntryMode = DatePickerEntryMode.calendar,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final DatePickerEntryMode datePickerEntryMode;
|
||||||
|
|
||||||
|
@override
|
||||||
|
_RestorableDateRangePickerDialogTestWidgetState createState() => _RestorableDateRangePickerDialogTestWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RestorableDateRangePickerDialogTestWidgetState extends State<_RestorableDateRangePickerDialogTestWidget> with RestorationMixin {
|
||||||
|
@override
|
||||||
|
String? get restorationId => 'scaffold_state';
|
||||||
|
|
||||||
|
final RestorableDateTimeN _startDate = RestorableDateTimeN(DateTime(2021, 1, 1));
|
||||||
|
final RestorableDateTimeN _endDate = RestorableDateTimeN(DateTime(2021, 1, 5));
|
||||||
|
late final RestorableRouteFuture<DateTimeRange?> _restorableDateRangePickerRouteFuture = RestorableRouteFuture<DateTimeRange?>(
|
||||||
|
onComplete: _selectDateRange,
|
||||||
|
onPresent: (NavigatorState navigator, Object? arguments) {
|
||||||
|
return navigator.restorablePush(
|
||||||
|
_dateRangePickerRoute,
|
||||||
|
arguments: <String, dynamic>{
|
||||||
|
'datePickerEntryMode': widget.datePickerEntryMode.index,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
|
||||||
|
registerForRestoration(_startDate, 'start_date');
|
||||||
|
registerForRestoration(_endDate, 'end_date');
|
||||||
|
registerForRestoration(_restorableDateRangePickerRouteFuture, 'date_picker_route_future');
|
||||||
|
}
|
||||||
|
|
||||||
|
void _selectDateRange(DateTimeRange? newSelectedDate) {
|
||||||
|
if (newSelectedDate != null) {
|
||||||
|
setState(() {
|
||||||
|
_startDate.value = newSelectedDate.start;
|
||||||
|
_endDate.value = newSelectedDate.end;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Route<DateTimeRange?> _dateRangePickerRoute(
|
||||||
|
BuildContext context,
|
||||||
|
Object? arguments,
|
||||||
|
) {
|
||||||
|
return DialogRoute<DateTimeRange?>(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
final Map<dynamic, dynamic> args = arguments! as Map<dynamic, dynamic>;
|
||||||
|
return DateRangePickerDialog(
|
||||||
|
restorationId: 'date_picker_dialog',
|
||||||
|
initialEntryMode: DatePickerEntryMode.values[args['datePickerEntryMode'] as int],
|
||||||
|
firstDate: DateTime(2021, 1, 1),
|
||||||
|
currentDate: DateTime(2021, 1, 25),
|
||||||
|
lastDate: DateTime(2022, 1, 1),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final DateTime? startDateTime = _startDate.value;
|
||||||
|
final DateTime? endDateTime = _endDate.value;
|
||||||
|
// Example: "25/7/1994"
|
||||||
|
final String startDateTimeString = '${startDateTime?.day}/${startDateTime?.month}/${startDateTime?.year}';
|
||||||
|
final String endDateTimeString = '${endDateTime?.day}/${endDateTime?.month}/${endDateTime?.year}';
|
||||||
|
return Scaffold(
|
||||||
|
body: Center(
|
||||||
|
child: Column(
|
||||||
|
children: <Widget>[
|
||||||
|
OutlinedButton(
|
||||||
|
onPressed: () {
|
||||||
|
_restorableDateRangePickerRouteFuture.present();
|
||||||
|
},
|
||||||
|
child: const Text('X'),
|
||||||
|
),
|
||||||
|
Text('$startDateTimeString to $endDateTimeString'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ void main() {
|
|||||||
expect(() => RestorableBoolN(true).value, throwsAssertionError);
|
expect(() => RestorableBoolN(true).value, throwsAssertionError);
|
||||||
expect(() => RestorableTextEditingController().value, throwsAssertionError);
|
expect(() => RestorableTextEditingController().value, throwsAssertionError);
|
||||||
expect(() => RestorableDateTime(DateTime(2020, 4, 3)).value, throwsAssertionError);
|
expect(() => RestorableDateTime(DateTime(2020, 4, 3)).value, throwsAssertionError);
|
||||||
|
expect(() => RestorableDateTimeN(DateTime(2020, 4, 3)).value, throwsAssertionError);
|
||||||
expect(() => _TestRestorableValue().value, throwsAssertionError);
|
expect(() => _TestRestorableValue().value, throwsAssertionError);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -34,8 +35,14 @@ void main() {
|
|||||||
expect(state.intValue.value, 42);
|
expect(state.intValue.value, 42);
|
||||||
expect(state.stringValue.value, 'hello world');
|
expect(state.stringValue.value, 'hello world');
|
||||||
expect(state.boolValue.value, false);
|
expect(state.boolValue.value, false);
|
||||||
expect(state.controllerValue.value.text, 'FooBar');
|
|
||||||
expect(state.dateTimeValue.value, DateTime(2021, 3, 16));
|
expect(state.dateTimeValue.value, DateTime(2021, 3, 16));
|
||||||
|
expect(state.nullableNumValue.value, null);
|
||||||
|
expect(state.nullableDoubleValue.value, null);
|
||||||
|
expect(state.nullableIntValue.value, null);
|
||||||
|
expect(state.nullableStringValue.value, null);
|
||||||
|
expect(state.nullableBoolValue.value, null);
|
||||||
|
expect(state.nullableDateTimeValue.value, null);
|
||||||
|
expect(state.controllerValue.value.text, 'FooBar');
|
||||||
expect(state.objectValue.value, 55);
|
expect(state.objectValue.value, 55);
|
||||||
|
|
||||||
// Modify values.
|
// Modify values.
|
||||||
@ -45,8 +52,14 @@ void main() {
|
|||||||
state.intValue.value = 10;
|
state.intValue.value = 10;
|
||||||
state.stringValue.value = 'guten tag';
|
state.stringValue.value = 'guten tag';
|
||||||
state.boolValue.value = true;
|
state.boolValue.value = true;
|
||||||
state.controllerValue.value.text = 'blabla';
|
|
||||||
state.dateTimeValue.value = DateTime(2020, 7, 4);
|
state.dateTimeValue.value = DateTime(2020, 7, 4);
|
||||||
|
state.nullableNumValue.value = 5.0;
|
||||||
|
state.nullableDoubleValue.value = 2.0;
|
||||||
|
state.nullableIntValue.value = 1;
|
||||||
|
state.nullableStringValue.value = 'hullo';
|
||||||
|
state.nullableBoolValue.value = false;
|
||||||
|
state.nullableDateTimeValue.value = DateTime(2020, 4, 4);
|
||||||
|
state.controllerValue.value.text = 'blabla';
|
||||||
state.objectValue.value = 53;
|
state.objectValue.value = 53;
|
||||||
});
|
});
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
@ -56,8 +69,14 @@ void main() {
|
|||||||
expect(state.intValue.value, 10);
|
expect(state.intValue.value, 10);
|
||||||
expect(state.stringValue.value, 'guten tag');
|
expect(state.stringValue.value, 'guten tag');
|
||||||
expect(state.boolValue.value, true);
|
expect(state.boolValue.value, true);
|
||||||
expect(state.controllerValue.value.text, 'blabla');
|
|
||||||
expect(state.dateTimeValue.value, DateTime(2020, 7, 4));
|
expect(state.dateTimeValue.value, DateTime(2020, 7, 4));
|
||||||
|
expect(state.nullableNumValue.value, 5.0);
|
||||||
|
expect(state.nullableDoubleValue.value, 2.0);
|
||||||
|
expect(state.nullableIntValue.value, 1);
|
||||||
|
expect(state.nullableStringValue.value, 'hullo');
|
||||||
|
expect(state.nullableBoolValue.value, false);
|
||||||
|
expect(state.nullableDateTimeValue.value, DateTime(2020, 4, 4));
|
||||||
|
expect(state.controllerValue.value.text, 'blabla');
|
||||||
expect(state.objectValue.value, 53);
|
expect(state.objectValue.value, 53);
|
||||||
expect(find.text('guten tag'), findsOneWidget);
|
expect(find.text('guten tag'), findsOneWidget);
|
||||||
});
|
});
|
||||||
@ -77,8 +96,14 @@ void main() {
|
|||||||
expect(state.intValue.value, 42);
|
expect(state.intValue.value, 42);
|
||||||
expect(state.stringValue.value, 'hello world');
|
expect(state.stringValue.value, 'hello world');
|
||||||
expect(state.boolValue.value, false);
|
expect(state.boolValue.value, false);
|
||||||
expect(state.controllerValue.value.text, 'FooBar');
|
|
||||||
expect(state.dateTimeValue.value, DateTime(2021, 3, 16));
|
expect(state.dateTimeValue.value, DateTime(2021, 3, 16));
|
||||||
|
expect(state.nullableNumValue.value, null);
|
||||||
|
expect(state.nullableDoubleValue.value, null);
|
||||||
|
expect(state.nullableIntValue.value, null);
|
||||||
|
expect(state.nullableStringValue.value, null);
|
||||||
|
expect(state.nullableBoolValue.value, null);
|
||||||
|
expect(state.nullableDateTimeValue.value, null);
|
||||||
|
expect(state.controllerValue.value.text, 'FooBar');
|
||||||
expect(state.objectValue.value, 55);
|
expect(state.objectValue.value, 55);
|
||||||
|
|
||||||
// Modify values.
|
// Modify values.
|
||||||
@ -88,8 +113,14 @@ void main() {
|
|||||||
state.intValue.value = 10;
|
state.intValue.value = 10;
|
||||||
state.stringValue.value = 'guten tag';
|
state.stringValue.value = 'guten tag';
|
||||||
state.boolValue.value = true;
|
state.boolValue.value = true;
|
||||||
state.controllerValue.value.text = 'blabla';
|
|
||||||
state.dateTimeValue.value = DateTime(2020, 7, 4);
|
state.dateTimeValue.value = DateTime(2020, 7, 4);
|
||||||
|
state.nullableNumValue.value = 5.0;
|
||||||
|
state.nullableDoubleValue.value = 2.0;
|
||||||
|
state.nullableIntValue.value = 1;
|
||||||
|
state.nullableStringValue.value = 'hullo';
|
||||||
|
state.nullableBoolValue.value = false;
|
||||||
|
state.nullableDateTimeValue.value = DateTime(2020, 4, 4);
|
||||||
|
state.controllerValue.value.text = 'blabla';
|
||||||
state.objectValue.value = 53;
|
state.objectValue.value = 53;
|
||||||
});
|
});
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
@ -99,8 +130,14 @@ void main() {
|
|||||||
expect(state.intValue.value, 10);
|
expect(state.intValue.value, 10);
|
||||||
expect(state.stringValue.value, 'guten tag');
|
expect(state.stringValue.value, 'guten tag');
|
||||||
expect(state.boolValue.value, true);
|
expect(state.boolValue.value, true);
|
||||||
expect(state.controllerValue.value.text, 'blabla');
|
|
||||||
expect(state.dateTimeValue.value, DateTime(2020, 7, 4));
|
expect(state.dateTimeValue.value, DateTime(2020, 7, 4));
|
||||||
|
expect(state.nullableNumValue.value, 5.0);
|
||||||
|
expect(state.nullableDoubleValue.value, 2.0);
|
||||||
|
expect(state.nullableIntValue.value, 1);
|
||||||
|
expect(state.nullableStringValue.value, 'hullo');
|
||||||
|
expect(state.nullableBoolValue.value, false);
|
||||||
|
expect(state.nullableDateTimeValue.value, DateTime(2020, 4, 4));
|
||||||
|
expect(state.controllerValue.value.text, 'blabla');
|
||||||
expect(state.objectValue.value, 53);
|
expect(state.objectValue.value, 53);
|
||||||
expect(find.text('guten tag'), findsOneWidget);
|
expect(find.text('guten tag'), findsOneWidget);
|
||||||
|
|
||||||
@ -115,8 +152,14 @@ void main() {
|
|||||||
expect(state.intValue.value, 10);
|
expect(state.intValue.value, 10);
|
||||||
expect(state.stringValue.value, 'guten tag');
|
expect(state.stringValue.value, 'guten tag');
|
||||||
expect(state.boolValue.value, true);
|
expect(state.boolValue.value, true);
|
||||||
expect(state.controllerValue.value.text, 'blabla');
|
|
||||||
expect(state.dateTimeValue.value, DateTime(2020, 7, 4));
|
expect(state.dateTimeValue.value, DateTime(2020, 7, 4));
|
||||||
|
expect(state.nullableNumValue.value, 5.0);
|
||||||
|
expect(state.nullableDoubleValue.value, 2.0);
|
||||||
|
expect(state.nullableIntValue.value, 1);
|
||||||
|
expect(state.nullableStringValue.value, 'hullo');
|
||||||
|
expect(state.nullableBoolValue.value, false);
|
||||||
|
expect(state.nullableDateTimeValue.value, DateTime(2020, 4, 4));
|
||||||
|
expect(state.controllerValue.value.text, 'blabla');
|
||||||
expect(state.objectValue.value, 53);
|
expect(state.objectValue.value, 53);
|
||||||
expect(find.text('guten tag'), findsOneWidget);
|
expect(find.text('guten tag'), findsOneWidget);
|
||||||
});
|
});
|
||||||
@ -137,13 +180,14 @@ void main() {
|
|||||||
state.intValue.value = 10;
|
state.intValue.value = 10;
|
||||||
state.stringValue.value = 'guten tag';
|
state.stringValue.value = 'guten tag';
|
||||||
state.boolValue.value = true;
|
state.boolValue.value = true;
|
||||||
|
state.dateTimeValue.value = DateTime(2020, 7, 4);
|
||||||
state.nullableNumValue.value = 5.0;
|
state.nullableNumValue.value = 5.0;
|
||||||
state.nullableDoubleValue.value = 2.0;
|
state.nullableDoubleValue.value = 2.0;
|
||||||
state.nullableIntValue.value = 1;
|
state.nullableIntValue.value = 1;
|
||||||
state.nullableStringValue.value = 'hullo';
|
state.nullableStringValue.value = 'hullo';
|
||||||
state.nullableBoolValue.value = false;
|
state.nullableBoolValue.value = false;
|
||||||
|
state.nullableDateTimeValue.value = DateTime(2020, 4, 4);
|
||||||
state.controllerValue.value.text = 'blabla';
|
state.controllerValue.value.text = 'blabla';
|
||||||
state.dateTimeValue.value = DateTime(2020, 7, 4);
|
|
||||||
state.objectValue.value = 53;
|
state.objectValue.value = 53;
|
||||||
});
|
});
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
@ -158,13 +202,14 @@ void main() {
|
|||||||
state.intValue.value = 20;
|
state.intValue.value = 20;
|
||||||
state.stringValue.value = 'ciao';
|
state.stringValue.value = 'ciao';
|
||||||
state.boolValue.value = false;
|
state.boolValue.value = false;
|
||||||
|
state.dateTimeValue.value = DateTime(2020, 3, 2);
|
||||||
state.nullableNumValue.value = 20.0;
|
state.nullableNumValue.value = 20.0;
|
||||||
state.nullableDoubleValue.value = 20.0;
|
state.nullableDoubleValue.value = 20.0;
|
||||||
state.nullableIntValue.value = 20;
|
state.nullableIntValue.value = 20;
|
||||||
state.nullableStringValue.value = 'ni hao';
|
state.nullableStringValue.value = 'ni hao';
|
||||||
state.nullableBoolValue.value = null;
|
state.nullableBoolValue.value = null;
|
||||||
|
state.nullableDateTimeValue.value = DateTime(2020, 5, 5);
|
||||||
state.controllerValue.value.text = 'blub';
|
state.controllerValue.value.text = 'blub';
|
||||||
state.dateTimeValue.value = DateTime(2020, 3, 2);
|
|
||||||
state.objectValue.value = 20;
|
state.objectValue.value = 20;
|
||||||
});
|
});
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
@ -178,13 +223,14 @@ void main() {
|
|||||||
expect(state.intValue.value, 10);
|
expect(state.intValue.value, 10);
|
||||||
expect(state.stringValue.value, 'guten tag');
|
expect(state.stringValue.value, 'guten tag');
|
||||||
expect(state.boolValue.value, true);
|
expect(state.boolValue.value, true);
|
||||||
|
expect(state.dateTimeValue.value, DateTime(2020, 7, 4));
|
||||||
expect(state.nullableNumValue.value, 5.0);
|
expect(state.nullableNumValue.value, 5.0);
|
||||||
expect(state.nullableDoubleValue.value, 2.0);
|
expect(state.nullableDoubleValue.value, 2.0);
|
||||||
expect(state.nullableIntValue.value, 1);
|
expect(state.nullableIntValue.value, 1);
|
||||||
expect(state.nullableStringValue.value, 'hullo');
|
expect(state.nullableStringValue.value, 'hullo');
|
||||||
expect(state.nullableBoolValue.value, false);
|
expect(state.nullableBoolValue.value, false);
|
||||||
|
expect(state.nullableDateTimeValue.value, DateTime(2020, 4, 4));
|
||||||
expect(state.controllerValue.value.text, 'blabla');
|
expect(state.controllerValue.value.text, 'blabla');
|
||||||
expect(state.dateTimeValue.value, DateTime(2020, 7, 4));
|
|
||||||
expect(state.objectValue.value, 53);
|
expect(state.objectValue.value, 53);
|
||||||
expect(find.text('guten tag'), findsOneWidget);
|
expect(find.text('guten tag'), findsOneWidget);
|
||||||
expect(state.controllerValue.value, isNot(same(controller)));
|
expect(state.controllerValue.value, isNot(same(controller)));
|
||||||
@ -196,13 +242,14 @@ void main() {
|
|||||||
expect(state.intValue.value, 42);
|
expect(state.intValue.value, 42);
|
||||||
expect(state.stringValue.value, 'hello world');
|
expect(state.stringValue.value, 'hello world');
|
||||||
expect(state.boolValue.value, false);
|
expect(state.boolValue.value, false);
|
||||||
|
expect(state.dateTimeValue.value, DateTime(2021, 3, 16));
|
||||||
expect(state.nullableNumValue.value, null);
|
expect(state.nullableNumValue.value, null);
|
||||||
expect(state.nullableDoubleValue.value, null);
|
expect(state.nullableDoubleValue.value, null);
|
||||||
expect(state.nullableIntValue.value, null);
|
expect(state.nullableIntValue.value, null);
|
||||||
expect(state.nullableStringValue.value, null);
|
expect(state.nullableStringValue.value, null);
|
||||||
expect(state.nullableBoolValue.value, null);
|
expect(state.nullableBoolValue.value, null);
|
||||||
|
expect(state.nullableDateTimeValue.value, null);
|
||||||
expect(state.controllerValue.value.text, 'FooBar');
|
expect(state.controllerValue.value.text, 'FooBar');
|
||||||
expect(state.dateTimeValue.value, DateTime(2021, 3, 16));
|
|
||||||
expect(state.objectValue.value, 55);
|
expect(state.objectValue.value, 55);
|
||||||
expect(find.text('hello world'), findsOneWidget);
|
expect(find.text('hello world'), findsOneWidget);
|
||||||
});
|
});
|
||||||
@ -217,6 +264,7 @@ void main() {
|
|||||||
final _RestorableWidgetState state = tester.state(find.byType(_RestorableWidget));
|
final _RestorableWidgetState state = tester.state(find.byType(_RestorableWidget));
|
||||||
|
|
||||||
final List<String> notifyLog = <String>[];
|
final List<String> notifyLog = <String>[];
|
||||||
|
|
||||||
state.numValue.addListener(() {
|
state.numValue.addListener(() {
|
||||||
notifyLog.add('num');
|
notifyLog.add('num');
|
||||||
});
|
});
|
||||||
@ -232,6 +280,27 @@ void main() {
|
|||||||
state.boolValue.addListener(() {
|
state.boolValue.addListener(() {
|
||||||
notifyLog.add('bool');
|
notifyLog.add('bool');
|
||||||
});
|
});
|
||||||
|
state.dateTimeValue.addListener(() {
|
||||||
|
notifyLog.add('date-time');
|
||||||
|
});
|
||||||
|
state.nullableNumValue.addListener(() {
|
||||||
|
notifyLog.add('nullable-num');
|
||||||
|
});
|
||||||
|
state.nullableDoubleValue.addListener(() {
|
||||||
|
notifyLog.add('nullable-double');
|
||||||
|
});
|
||||||
|
state.nullableIntValue.addListener(() {
|
||||||
|
notifyLog.add('nullable-int');
|
||||||
|
});
|
||||||
|
state.nullableStringValue.addListener(() {
|
||||||
|
notifyLog.add('nullable-string');
|
||||||
|
});
|
||||||
|
state.nullableBoolValue.addListener(() {
|
||||||
|
notifyLog.add('nullable-bool');
|
||||||
|
});
|
||||||
|
state.nullableDateTimeValue.addListener(() {
|
||||||
|
notifyLog.add('nullable-date-time');
|
||||||
|
});
|
||||||
state.controllerValue.addListener(() {
|
state.controllerValue.addListener(() {
|
||||||
notifyLog.add('controller');
|
notifyLog.add('controller');
|
||||||
});
|
});
|
||||||
@ -269,6 +338,48 @@ void main() {
|
|||||||
expect(notifyLog.single, 'bool');
|
expect(notifyLog.single, 'bool');
|
||||||
notifyLog.clear();
|
notifyLog.clear();
|
||||||
|
|
||||||
|
state.setProperties(() {
|
||||||
|
state.dateTimeValue.value = DateTime(2020, 7, 4);
|
||||||
|
});
|
||||||
|
expect(notifyLog.single, 'date-time');
|
||||||
|
notifyLog.clear();
|
||||||
|
|
||||||
|
state.setProperties(() {
|
||||||
|
state.nullableNumValue.value = 42.2;
|
||||||
|
});
|
||||||
|
expect(notifyLog.single, 'nullable-num');
|
||||||
|
notifyLog.clear();
|
||||||
|
|
||||||
|
state.setProperties(() {
|
||||||
|
state.nullableDoubleValue.value = 42.2;
|
||||||
|
});
|
||||||
|
expect(notifyLog.single, 'nullable-double');
|
||||||
|
notifyLog.clear();
|
||||||
|
|
||||||
|
state.setProperties(() {
|
||||||
|
state.nullableIntValue.value = 45;
|
||||||
|
});
|
||||||
|
expect(notifyLog.single, 'nullable-int');
|
||||||
|
notifyLog.clear();
|
||||||
|
|
||||||
|
state.setProperties(() {
|
||||||
|
state.nullableStringValue.value = 'bar';
|
||||||
|
});
|
||||||
|
expect(notifyLog.single, 'nullable-string');
|
||||||
|
notifyLog.clear();
|
||||||
|
|
||||||
|
state.setProperties(() {
|
||||||
|
state.nullableBoolValue.value = true;
|
||||||
|
});
|
||||||
|
expect(notifyLog.single, 'nullable-bool');
|
||||||
|
notifyLog.clear();
|
||||||
|
|
||||||
|
state.setProperties(() {
|
||||||
|
state.nullableDateTimeValue.value = DateTime(2020, 4, 4);
|
||||||
|
});
|
||||||
|
expect(notifyLog.single, 'nullable-date-time');
|
||||||
|
notifyLog.clear();
|
||||||
|
|
||||||
state.setProperties(() {
|
state.setProperties(() {
|
||||||
state.controllerValue.value.text = 'foo';
|
state.controllerValue.value.text = 'foo';
|
||||||
});
|
});
|
||||||
@ -291,8 +402,17 @@ void main() {
|
|||||||
state.intValue.value = 45;
|
state.intValue.value = 45;
|
||||||
state.stringValue.value = 'bar';
|
state.stringValue.value = 'bar';
|
||||||
state.boolValue.value = true;
|
state.boolValue.value = true;
|
||||||
|
state.dateTimeValue.value = DateTime(2020, 7, 4);
|
||||||
|
state.nullableNumValue.value = 42.2;
|
||||||
|
state.nullableDoubleValue.value = 42.2;
|
||||||
|
state.nullableIntValue.value = 45;
|
||||||
|
state.nullableStringValue.value = 'bar';
|
||||||
|
state.nullableBoolValue.value = true;
|
||||||
|
state.nullableDateTimeValue.value = DateTime(2020, 4, 4);
|
||||||
state.controllerValue.value.text = 'foo';
|
state.controllerValue.value.text = 'foo';
|
||||||
|
state.objectValue.value = 42;
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(notifyLog, isEmpty);
|
expect(notifyLog, isEmpty);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -396,13 +516,14 @@ class _RestorableWidgetState extends State<_RestorableWidget> with RestorationMi
|
|||||||
final RestorableInt intValue = RestorableInt(42);
|
final RestorableInt intValue = RestorableInt(42);
|
||||||
final RestorableString stringValue = RestorableString('hello world');
|
final RestorableString stringValue = RestorableString('hello world');
|
||||||
final RestorableBool boolValue = RestorableBool(false);
|
final RestorableBool boolValue = RestorableBool(false);
|
||||||
|
final RestorableDateTime dateTimeValue = RestorableDateTime(DateTime(2021, 3, 16));
|
||||||
final RestorableNumN<num?> nullableNumValue = RestorableNumN<num?>(null);
|
final RestorableNumN<num?> nullableNumValue = RestorableNumN<num?>(null);
|
||||||
final RestorableDoubleN nullableDoubleValue = RestorableDoubleN(null);
|
final RestorableDoubleN nullableDoubleValue = RestorableDoubleN(null);
|
||||||
final RestorableIntN nullableIntValue = RestorableIntN(null);
|
final RestorableIntN nullableIntValue = RestorableIntN(null);
|
||||||
final RestorableStringN nullableStringValue = RestorableStringN(null);
|
final RestorableStringN nullableStringValue = RestorableStringN(null);
|
||||||
final RestorableBoolN nullableBoolValue = RestorableBoolN(null);
|
final RestorableBoolN nullableBoolValue = RestorableBoolN(null);
|
||||||
|
final RestorableDateTimeN nullableDateTimeValue = RestorableDateTimeN(null);
|
||||||
final RestorableTextEditingController controllerValue = RestorableTextEditingController(text: 'FooBar');
|
final RestorableTextEditingController controllerValue = RestorableTextEditingController(text: 'FooBar');
|
||||||
final RestorableDateTime dateTimeValue = RestorableDateTime(DateTime(2021, 3, 16));
|
|
||||||
final _TestRestorableValue objectValue = _TestRestorableValue();
|
final _TestRestorableValue objectValue = _TestRestorableValue();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -412,13 +533,14 @@ class _RestorableWidgetState extends State<_RestorableWidget> with RestorationMi
|
|||||||
registerForRestoration(intValue, 'int');
|
registerForRestoration(intValue, 'int');
|
||||||
registerForRestoration(stringValue, 'string');
|
registerForRestoration(stringValue, 'string');
|
||||||
registerForRestoration(boolValue, 'bool');
|
registerForRestoration(boolValue, 'bool');
|
||||||
|
registerForRestoration(dateTimeValue, 'dateTime');
|
||||||
registerForRestoration(nullableNumValue, 'nullableNum');
|
registerForRestoration(nullableNumValue, 'nullableNum');
|
||||||
registerForRestoration(nullableDoubleValue, 'nullableDouble');
|
registerForRestoration(nullableDoubleValue, 'nullableDouble');
|
||||||
registerForRestoration(nullableIntValue, 'nullableInt');
|
registerForRestoration(nullableIntValue, 'nullableInt');
|
||||||
registerForRestoration(nullableStringValue, 'nullableString');
|
registerForRestoration(nullableStringValue, 'nullableString');
|
||||||
registerForRestoration(nullableBoolValue, 'nullableBool');
|
registerForRestoration(nullableBoolValue, 'nullableBool');
|
||||||
|
registerForRestoration(nullableDateTimeValue, 'nullableDateTime');
|
||||||
registerForRestoration(controllerValue, 'controller');
|
registerForRestoration(controllerValue, 'controller');
|
||||||
registerForRestoration(dateTimeValue, 'dateTime');
|
|
||||||
registerForRestoration(objectValue, 'object');
|
registerForRestoration(objectValue, 'object');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user