Unverified Commit 594ce21d authored by Shi-Hao Hong's avatar Shi-Hao Hong Committed by GitHub

[State Restoration] Material Date Picker (#77879)

parent 640ae7a3
...@@ -102,6 +102,122 @@ const double _inputFormLandscapeHeight = 108.0; ...@@ -102,6 +102,122 @@ const double _inputFormLandscapeHeight = 108.0;
/// [DatePickerMode.day] mode. It defaults to [DatePickerMode.day], and /// [DatePickerMode.day] mode. It defaults to [DatePickerMode.day], and
/// must be non-null. /// must be non-null.
/// ///
/// ### State Restoration
///
/// Using this method will not enable state restoration for the date picker.
/// In order to enable state restoration for a date picker, use
/// [Navigator.restorablePush] or [Navigator.restorablePushNamed] with
/// [DatePickerDialog].
///
/// For more information about state restoration, see [RestorationManager].
///
/// {@tool sample --template=freeform}
///
/// This sample demonstrates how to create a restorable Material date picker.
/// This is accomplished by enabling state restoration by specifying
/// [MaterialApp.restorationScopeId] and using [Navigator.restorablePush] to
/// push [DatePickerDialog] when the button is tapped.
///
/// {@macro flutter.widgets.RestorationManager}
///
/// ```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 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 {
/// @override
/// String get restorationId => 'scaffold_state';
///
/// final RestorableDateTime _selectedDate = RestorableDateTime(DateTime(2021, 7, 25));
/// late final RestorableRouteFuture<DateTime?> _restorableDatePickerRouteFuture = RestorableRouteFuture<DateTime?>(
/// onComplete: _selectDate,
/// onPresent: (NavigatorState navigator, Object? arguments) {
/// return navigator.restorablePush(
/// _datePickerRoute,
/// arguments: _selectedDate.value.millisecondsSinceEpoch,
/// );
/// },
/// );
///
/// static Route<DateTime> _datePickerRoute(
/// BuildContext context,
/// Object? arguments,
/// ) {
/// return DialogRoute<DateTime>(
/// context: context,
/// builder: (BuildContext context) {
/// return DatePickerDialog(
/// restorationId: 'date_picker_dialog',
/// initialEntryMode: DatePickerEntryMode.calendarOnly,
/// initialDate: DateTime.fromMillisecondsSinceEpoch(arguments! as int),
/// firstDate: DateTime(2021, 1, 1),
/// lastDate: DateTime(2022, 1, 1),
/// );
/// },
/// );
/// }
///
/// @override
/// void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
/// registerForRestoration(_selectedDate, 'selected_date');
/// registerForRestoration(_restorableDatePickerRouteFuture, 'date_picker_route_future');
/// }
///
/// void _selectDate(DateTime? newSelectedDate) {
/// if (newSelectedDate != null) {
/// setState(() {
/// _selectedDate.value = newSelectedDate;
/// ScaffoldMessenger.of(context).showSnackBar(SnackBar(
/// content: Text(
/// 'Selected: ${_selectedDate.value.day}/${_selectedDate.value.month}/${_selectedDate.value.year}'),
/// ));
/// });
/// }
/// }
///
/// @override
/// Widget build(BuildContext context) {
/// return Scaffold(
/// body: Center(
/// child: OutlinedButton(
/// onPressed: () {
/// _restorableDatePickerRouteFuture.present();
/// },
/// child: const Text('Open Date Picker'),
/// ),
/// ),
/// );
/// }
/// }
/// ```
///
/// {@end-tool}
///
/// See also: /// See also:
/// ///
/// * [showDateRangePicker], which shows a material design date range picker /// * [showDateRangePicker], which shows a material design date range picker
...@@ -159,7 +275,7 @@ Future<DateTime?> showDatePicker({ ...@@ -159,7 +275,7 @@ Future<DateTime?> showDatePicker({
assert(initialDatePickerMode != null); assert(initialDatePickerMode != null);
assert(debugCheckHasMaterialLocalizations(context)); assert(debugCheckHasMaterialLocalizations(context));
Widget dialog = _DatePickerDialog( Widget dialog = DatePickerDialog(
initialDate: initialDate, initialDate: initialDate,
firstDate: firstDate, firstDate: firstDate,
lastDate: lastDate, lastDate: lastDate,
...@@ -201,8 +317,18 @@ Future<DateTime?> showDatePicker({ ...@@ -201,8 +317,18 @@ Future<DateTime?> showDatePicker({
); );
} }
class _DatePickerDialog extends StatefulWidget { /// A Material-style date picker dialog.
_DatePickerDialog({ ///
/// It is used internally by [showDatePicker] or can be directly pushed
/// onto the [Navigator] stack to enable state restoration. See
/// [showDatePicker] for a state restoration app example.
///
/// See also:
///
/// * [showDatePicker], which is a way to display the date picker.
class DatePickerDialog extends StatefulWidget {
/// A Material-style date picker dialog.
DatePickerDialog({
Key? key, Key? key,
required DateTime initialDate, required DateTime initialDate,
required DateTime firstDate, required DateTime firstDate,
...@@ -218,6 +344,7 @@ class _DatePickerDialog extends StatefulWidget { ...@@ -218,6 +344,7 @@ class _DatePickerDialog extends StatefulWidget {
this.errorInvalidText, this.errorInvalidText,
this.fieldHintText, this.fieldHintText,
this.fieldLabelText, this.fieldLabelText,
this.restorationId,
}) : assert(initialDate != null), }) : assert(initialDate != null),
assert(firstDate != null), assert(firstDate != null),
assert(lastDate != null), assert(lastDate != null),
...@@ -258,6 +385,10 @@ class _DatePickerDialog extends StatefulWidget { ...@@ -258,6 +385,10 @@ class _DatePickerDialog extends StatefulWidget {
/// The [DateTime] representing today. It will be highlighted in the day grid. /// The [DateTime] representing today. It will be highlighted in the day grid.
final DateTime currentDate; final DateTime currentDate;
/// The initial mode of date entry method for the date picker dialog.
///
/// See [DatePickerEntryMode] for more details on the different data entry
/// modes available.
final DatePickerEntryMode initialEntryMode; final DatePickerEntryMode initialEntryMode;
/// Function to provide full control over which [DateTime] can be selected. /// Function to provide full control over which [DateTime] can be selected.
...@@ -277,44 +408,73 @@ class _DatePickerDialog extends StatefulWidget { ...@@ -277,44 +408,73 @@ class _DatePickerDialog extends StatefulWidget {
/// The initial display of the calendar picker. /// The initial display of the calendar picker.
final DatePickerMode initialCalendarMode; final DatePickerMode initialCalendarMode;
/// The error text displayed if the entered date is not in the correct format.
final String? errorFormatText; final String? errorFormatText;
/// The error text displayed if the date is not valid.
///
/// A date is not valid if it is earlier than [firstDate], later than
/// [lastDate], or doesn't pass the [selectableDayPredicate].
final String? errorInvalidText; final String? errorInvalidText;
/// The hint text displayed in the [TextField].
///
/// If this is null, it will default to the date format string. For example,
/// 'mm/dd/yyyy' for en_US.
final String? fieldHintText; final String? fieldHintText;
/// The label text displayed in the [TextField].
///
/// If this is null, it will default to the words representing the date format
/// string. For example, 'Month, Day, Year' for en_US.
final String? fieldLabelText; final String? fieldLabelText;
/// Restoration ID to save and restore the state of the [DatePickerDialog].
///
/// If it is non-null, the date picker will persist and restore the
/// date 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
_DatePickerDialogState createState() => _DatePickerDialogState(); _DatePickerDialogState createState() => _DatePickerDialogState();
} }
class _DatePickerDialogState extends State<_DatePickerDialog> { class _DatePickerDialogState extends State<DatePickerDialog> with RestorationMixin {
late final RestorableDateTime _selectedDate = RestorableDateTime(widget.initialDate);
late final _RestorableDatePickerEntryMode _entryMode = _RestorableDatePickerEntryMode(widget.initialEntryMode);
final RestorableBool _autoValidate = RestorableBool(false);
late DatePickerEntryMode _entryMode; @override
late DateTime _selectedDate; String? get restorationId => widget.restorationId;
late bool _autoValidate;
final GlobalKey _calendarPickerKey = GlobalKey();
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
@override @override
void initState() { void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
super.initState(); registerForRestoration(_selectedDate, 'selected_date');
_entryMode = widget.initialEntryMode; registerForRestoration(_autoValidate, 'autovalidate');
_selectedDate = widget.initialDate; registerForRestoration(_entryMode, 'calendar_entry_mode');
_autoValidate = false;
} }
final GlobalKey _calendarPickerKey = GlobalKey();
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
void _handleOk() { void _handleOk() {
if (_entryMode == DatePickerEntryMode.input || _entryMode == DatePickerEntryMode.inputOnly) { if (_entryMode.value == DatePickerEntryMode.input || _entryMode.value == DatePickerEntryMode.inputOnly) {
final FormState form = _formKey.currentState!; final FormState form = _formKey.currentState!;
if (!form.validate()) { if (!form.validate()) {
setState(() => _autoValidate = true); setState(() => _autoValidate.value = true);
return; return;
} }
form.save(); form.save();
} }
Navigator.pop(context, _selectedDate); Navigator.pop(context, _selectedDate.value);
} }
void _handleCancel() { void _handleCancel() {
...@@ -323,14 +483,14 @@ class _DatePickerDialogState extends State<_DatePickerDialog> { ...@@ -323,14 +483,14 @@ class _DatePickerDialogState extends State<_DatePickerDialog> {
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:
_formKey.currentState!.save(); _formKey.currentState!.save();
_entryMode = DatePickerEntryMode.calendar; _entryMode.value = DatePickerEntryMode.calendar;
break; break;
case DatePickerEntryMode.calendarOnly: case DatePickerEntryMode.calendarOnly:
case DatePickerEntryMode.inputOnly: case DatePickerEntryMode.inputOnly:
...@@ -341,12 +501,14 @@ class _DatePickerDialogState extends State<_DatePickerDialog> { ...@@ -341,12 +501,14 @@ class _DatePickerDialogState extends State<_DatePickerDialog> {
} }
void _handleDateChanged(DateTime date) { void _handleDateChanged(DateTime date) {
setState(() => _selectedDate = date); setState(() {
_selectedDate.value = date;
});
} }
Size _dialogSize(BuildContext context) { Size _dialogSize(BuildContext context) {
final Orientation orientation = MediaQuery.of(context).orientation; final Orientation orientation = MediaQuery.of(context).orientation;
switch (_entryMode) { switch (_entryMode.value) {
case DatePickerEntryMode.calendar: case DatePickerEntryMode.calendar:
case DatePickerEntryMode.calendarOnly: case DatePickerEntryMode.calendarOnly:
switch (orientation) { switch (orientation) {
...@@ -382,7 +544,7 @@ class _DatePickerDialogState extends State<_DatePickerDialog> { ...@@ -382,7 +544,7 @@ class _DatePickerDialogState extends State<_DatePickerDialog> {
// layout issues. // layout issues.
final double textScaleFactor = math.min(MediaQuery.of(context).textScaleFactor, 1.3); final double textScaleFactor = math.min(MediaQuery.of(context).textScaleFactor, 1.3);
final String dateText = localizations.formatMediumDate(_selectedDate); final String dateText = localizations.formatMediumDate(_selectedDate.value);
final Color onPrimarySurface = colorScheme.brightness == Brightness.light final Color onPrimarySurface = colorScheme.brightness == Brightness.light
? colorScheme.onPrimary ? colorScheme.onPrimary
: colorScheme.onSurface; : colorScheme.onSurface;
...@@ -412,7 +574,7 @@ class _DatePickerDialogState extends State<_DatePickerDialog> { ...@@ -412,7 +574,7 @@ class _DatePickerDialogState extends State<_DatePickerDialog> {
CalendarDatePicker calendarDatePicker() { CalendarDatePicker calendarDatePicker() {
return CalendarDatePicker( return CalendarDatePicker(
key: _calendarPickerKey, key: _calendarPickerKey,
initialDate: _selectedDate, initialDate: _selectedDate.value,
firstDate: widget.firstDate, firstDate: widget.firstDate,
lastDate: widget.lastDate, lastDate: widget.lastDate,
currentDate: widget.currentDate, currentDate: widget.currentDate,
...@@ -425,7 +587,7 @@ class _DatePickerDialogState extends State<_DatePickerDialog> { ...@@ -425,7 +587,7 @@ class _DatePickerDialogState extends State<_DatePickerDialog> {
Form inputDatePicker() { Form inputDatePicker() {
return Form( return Form(
key: _formKey, key: _formKey,
autovalidate: _autoValidate, autovalidate: _autoValidate.value,
child: Container( child: Container(
padding: const EdgeInsets.symmetric(horizontal: 24), padding: const EdgeInsets.symmetric(horizontal: 24),
height: orientation == Orientation.portrait ? _inputFormPortraitHeight : _inputFormLandscapeHeight, height: orientation == Orientation.portrait ? _inputFormPortraitHeight : _inputFormLandscapeHeight,
...@@ -435,7 +597,7 @@ class _DatePickerDialogState extends State<_DatePickerDialog> { ...@@ -435,7 +597,7 @@ class _DatePickerDialogState extends State<_DatePickerDialog> {
children: <Widget>[ children: <Widget>[
const Spacer(), const Spacer(),
InputDatePickerFormField( InputDatePickerFormField(
initialDate: _selectedDate, initialDate: _selectedDate.value,
firstDate: widget.firstDate, firstDate: widget.firstDate,
lastDate: widget.lastDate, lastDate: widget.lastDate,
onDateSubmitted: _handleDateChanged, onDateSubmitted: _handleDateChanged,
...@@ -457,7 +619,7 @@ class _DatePickerDialogState extends State<_DatePickerDialog> { ...@@ -457,7 +619,7 @@ class _DatePickerDialogState extends State<_DatePickerDialog> {
final Widget picker; final Widget picker;
final Widget? entryModeButton; final Widget? entryModeButton;
switch (_entryMode) { switch (_entryMode.value) {
case DatePickerEntryMode.calendar: case DatePickerEntryMode.calendar:
picker = calendarDatePicker(); picker = calendarDatePicker();
entryModeButton = IconButton( entryModeButton = IconButton(
...@@ -549,6 +711,32 @@ class _DatePickerDialogState extends State<_DatePickerDialog> { ...@@ -549,6 +711,32 @@ class _DatePickerDialogState extends State<_DatePickerDialog> {
} }
} }
// A restorable [DatePickerEntryMode] value.
//
// This serializes each entry as a unique `int` value.
class _RestorableDatePickerEntryMode extends RestorableValue<DatePickerEntryMode> {
_RestorableDatePickerEntryMode(
DatePickerEntryMode defaultValue,
) : _defaultValue = defaultValue;
final DatePickerEntryMode _defaultValue;
@override
DatePickerEntryMode createDefaultValue() => _defaultValue;
@override
void didUpdateValue(DatePickerEntryMode? oldValue) {
assert(debugIsSerializableForRestoration(value.index));
notifyListeners();
}
@override
DatePickerEntryMode fromPrimitives(Object? data) => DatePickerEntryMode.values[data! as int];
@override
Object? toPrimitives() => value.index;
}
/// Re-usable widget that displays the selected date (in large font) and the /// Re-usable widget that displays the selected date (in large font) and the
/// help text above it. /// help text above it.
/// ///
......
...@@ -358,6 +358,33 @@ class RestorableStringN extends _RestorablePrimitiveValueN<String?> { ...@@ -358,6 +358,33 @@ class RestorableStringN extends _RestorablePrimitiveValueN<String?> {
RestorableStringN(String? defaultValue) : super(defaultValue); RestorableStringN(String? defaultValue) : super(defaultValue);
} }
/// A [RestorableValue] that knows how to save and restore [DateTime].
///
/// {@macro flutter.widgets.RestorableNum}.
class RestorableDateTime extends RestorableValue<DateTime> {
/// Creates a [RestorableDateTime].
///
/// {@macro flutter.widgets.RestorableNum.constructor}
RestorableDateTime(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) => DateTime.fromMillisecondsSinceEpoch(data! as int);
@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].
/// ///
......
...@@ -198,7 +198,7 @@ void main() { ...@@ -198,7 +198,7 @@ void main() {
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(find.text('Invalid format.'), findsOneWidget); expect(find.text('Invalid format.'), findsOneWidget);
// Toggle to calender mode and then back to input mode // Toggle to calendar mode and then back to input mode
await tester.tap(find.byIcon(Icons.calendar_today)); await tester.tap(find.byIcon(Icons.calendar_today));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
await tester.tap(find.byIcon(Icons.edit)); await tester.tap(find.byIcon(Icons.edit));
...@@ -1125,6 +1125,187 @@ void main() { ...@@ -1125,6 +1125,187 @@ void main() {
expect(tester.takeException(), isNull); expect(tester.takeException(), isNull);
}); });
}); });
testWidgets('DatePickerDialog is state restorable', (WidgetTester tester) async {
await tester.pumpWidget(
const MaterialApp(
restorationScopeId: 'app',
home: _RestorableDatePickerDialogTestWidget(),
),
);
// The date picker should be closed.
expect(find.byType(DatePickerDialog), findsNothing);
expect(find.text('25/7/2021'), findsOneWidget);
// Open the date picker.
await tester.tap(find.text('X'));
await tester.pumpAndSettle();
expect(find.byType(DatePickerDialog), findsOneWidget);
final TestRestorationData restorationData = await tester.getRestorationData();
await tester.restartAndRestore();
// The date picker should be open after restoring.
expect(find.byType(DatePickerDialog), findsOneWidget);
// Tap on the barrier.
await tester.tapAt(const Offset(10.0, 10.0));
await tester.pumpAndSettle();
// The date picker should be closed, the text value updated to the
// newly selected date.
expect(find.byType(DatePickerDialog), findsNothing);
expect(find.text('25/7/2021'), findsOneWidget);
// The date picker should be open after restoring.
await tester.restoreFrom(restorationData);
expect(find.byType(DatePickerDialog), findsOneWidget);
// Select a different date and close the date picker.
await tester.tap(find.text('30'));
await tester.pumpAndSettle();
await tester.tap(find.text('OK'));
await tester.pumpAndSettle();
// The date picker should be closed, the text value updated to the
// newly selected date.
expect(find.byType(DatePickerDialog), findsNothing);
expect(find.text('30/7/2021'), findsOneWidget);
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/33615
testWidgets('DatePickerDialog state restoration - DatePickerEntryMode', (WidgetTester tester) async {
await tester.pumpWidget(
const MaterialApp(
restorationScopeId: 'app',
home: _RestorableDatePickerDialogTestWidget(
datePickerEntryMode: DatePickerEntryMode.calendarOnly,
),
),
);
// The date picker should be closed.
expect(find.byType(DatePickerDialog), findsNothing);
expect(find.text('25/7/2021'), findsOneWidget);
// Open the date picker.
await tester.tap(find.text('X'));
await tester.pumpAndSettle();
expect(find.byType(DatePickerDialog), 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 picker should be open after restoring.
expect(find.byType(DatePickerDialog), 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.tapAt(const Offset(10.0, 10.0));
await tester.pumpAndSettle();
// The date picker should be closed, the text value updated to the
// newly selected date.
expect(find.byType(DatePickerDialog), findsNothing);
expect(find.text('25/7/2021'), findsOneWidget);
// The date picker should be open after restoring.
await tester.restoreFrom(restorationData);
expect(find.byType(DatePickerDialog), 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 _RestorableDatePickerDialogTestWidget extends StatefulWidget {
const _RestorableDatePickerDialogTestWidget({
Key? key,
this.datePickerEntryMode = DatePickerEntryMode.calendar,
}) : super(key: key);
final DatePickerEntryMode datePickerEntryMode;
@override
_RestorableDatePickerDialogTestWidgetState createState() => _RestorableDatePickerDialogTestWidgetState();
}
class _RestorableDatePickerDialogTestWidgetState extends State<_RestorableDatePickerDialogTestWidget> with RestorationMixin {
@override
String? get restorationId => 'scaffold_state';
final RestorableDateTime _selectedDate = RestorableDateTime(DateTime(2021, 7, 25));
late final RestorableRouteFuture<DateTime?> _restorableDatePickerRouteFuture = RestorableRouteFuture<DateTime?>(
onComplete: _selectDate,
onPresent: (NavigatorState navigator, Object? arguments) {
return navigator.restorablePush(
_datePickerRoute,
arguments: <String, dynamic>{
'selectedDate': _selectedDate.value.millisecondsSinceEpoch,
'datePickerEntryMode': widget.datePickerEntryMode.index,
}
);
},
);
@override
void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
registerForRestoration(_selectedDate, 'selected_date');
registerForRestoration(_restorableDatePickerRouteFuture, 'date_picker_route_future');
}
void _selectDate(DateTime? newSelectedDate) {
if (newSelectedDate != null) {
setState(() { _selectedDate.value = newSelectedDate; });
}
}
static Route<DateTime> _datePickerRoute(
BuildContext context,
Object? arguments,
) {
return DialogRoute<DateTime>(
context: context,
builder: (BuildContext context) {
final Map<dynamic, dynamic> args = arguments! as Map<dynamic, dynamic>;
return DatePickerDialog(
restorationId: 'date_picker_dialog',
initialEntryMode: DatePickerEntryMode.values[args['datePickerEntryMode'] as int],
initialDate: DateTime.fromMillisecondsSinceEpoch(args['selectedDate'] as int),
firstDate: DateTime(2021, 1, 1),
lastDate: DateTime(2022, 1, 1),
);
},
);
}
@override
Widget build(BuildContext context) {
final DateTime selectedDateTime = _selectedDate.value;
// Example: "25/7/1994"
final String selectedDateTimeString = '${selectedDateTime.day}/${selectedDateTime.month}/${selectedDateTime.year}';
return Scaffold(
body: Center(
child: Column(
children: <Widget>[
OutlinedButton(
onPressed: () {
_restorableDatePickerRouteFuture.present();
},
child: const Text('X'),
),
Text(selectedDateTimeString),
],
),
),
);
}
} }
class _DatePickerObserver extends NavigatorObserver { class _DatePickerObserver extends NavigatorObserver {
......
...@@ -18,6 +18,7 @@ void main() { ...@@ -18,6 +18,7 @@ void main() {
expect(() => RestorableStringN('hello').value, throwsAssertionError); expect(() => RestorableStringN('hello').value, throwsAssertionError);
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(() => _TestRestorableValue().value, throwsAssertionError); expect(() => _TestRestorableValue().value, throwsAssertionError);
}); });
...@@ -34,6 +35,7 @@ void main() { ...@@ -34,6 +35,7 @@ void main() {
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.controllerValue.value.text, 'FooBar');
expect(state.dateTimeValue.value, DateTime(2021, 3, 16));
expect(state.objectValue.value, 55); expect(state.objectValue.value, 55);
// Modify values. // Modify values.
...@@ -44,6 +46,7 @@ void main() { ...@@ -44,6 +46,7 @@ void main() {
state.stringValue.value = 'guten tag'; state.stringValue.value = 'guten tag';
state.boolValue.value = true; state.boolValue.value = true;
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();
...@@ -54,6 +57,7 @@ void main() { ...@@ -54,6 +57,7 @@ void main() {
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.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);
}); });
...@@ -74,6 +78,7 @@ void main() { ...@@ -74,6 +78,7 @@ void main() {
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.controllerValue.value.text, 'FooBar');
expect(state.dateTimeValue.value, DateTime(2021, 3, 16));
expect(state.objectValue.value, 55); expect(state.objectValue.value, 55);
// Modify values. // Modify values.
...@@ -84,6 +89,7 @@ void main() { ...@@ -84,6 +89,7 @@ void main() {
state.stringValue.value = 'guten tag'; state.stringValue.value = 'guten tag';
state.boolValue.value = true; state.boolValue.value = true;
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();
...@@ -94,6 +100,7 @@ void main() { ...@@ -94,6 +100,7 @@ void main() {
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.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);
...@@ -109,6 +116,7 @@ void main() { ...@@ -109,6 +116,7 @@ void main() {
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.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);
}); });
...@@ -135,6 +143,7 @@ void main() { ...@@ -135,6 +143,7 @@ void main() {
state.nullableStringValue.value = 'hullo'; state.nullableStringValue.value = 'hullo';
state.nullableBoolValue.value = false; state.nullableBoolValue.value = false;
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();
...@@ -155,6 +164,7 @@ void main() { ...@@ -155,6 +164,7 @@ void main() {
state.nullableStringValue.value = 'ni hao'; state.nullableStringValue.value = 'ni hao';
state.nullableBoolValue.value = null; state.nullableBoolValue.value = null;
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();
...@@ -174,6 +184,7 @@ void main() { ...@@ -174,6 +184,7 @@ void main() {
expect(state.nullableStringValue.value, 'hullo'); expect(state.nullableStringValue.value, 'hullo');
expect(state.nullableBoolValue.value, false); expect(state.nullableBoolValue.value, false);
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)));
...@@ -191,6 +202,7 @@ void main() { ...@@ -191,6 +202,7 @@ void main() {
expect(state.nullableStringValue.value, null); expect(state.nullableStringValue.value, null);
expect(state.nullableBoolValue.value, null); expect(state.nullableBoolValue.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);
}); });
...@@ -390,6 +402,7 @@ class _RestorableWidgetState extends State<_RestorableWidget> with RestorationMi ...@@ -390,6 +402,7 @@ class _RestorableWidgetState extends State<_RestorableWidget> with RestorationMi
final RestorableStringN nullableStringValue = RestorableStringN(null); final RestorableStringN nullableStringValue = RestorableStringN(null);
final RestorableBoolN nullableBoolValue = RestorableBoolN(null); final RestorableBoolN nullableBoolValue = RestorableBoolN(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
...@@ -405,6 +418,7 @@ class _RestorableWidgetState extends State<_RestorableWidget> with RestorationMi ...@@ -405,6 +418,7 @@ class _RestorableWidgetState extends State<_RestorableWidget> with RestorationMi
registerForRestoration(nullableStringValue, 'nullableString'); registerForRestoration(nullableStringValue, 'nullableString');
registerForRestoration(nullableBoolValue, 'nullableBool'); registerForRestoration(nullableBoolValue, 'nullableBool');
registerForRestoration(controllerValue, 'controller'); registerForRestoration(controllerValue, 'controller');
registerForRestoration(dateTimeValue, 'dateTime');
registerForRestoration(objectValue, 'object'); registerForRestoration(objectValue, 'object');
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment