Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
07849778
Unverified
Commit
07849778
authored
Apr 23, 2021
by
Shi-Hao Hong
Committed by
GitHub
Apr 23, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
[State Restoration] Restorable `TimePickerDialog` widget, `RestorableTimeOfDay` (#80566)
parent
e6d4b8cf
Changes
4
Show whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
598 additions
and
125 deletions
+598
-125
time.dart
packages/flutter/lib/src/material/time.dart
+35
-0
time_picker.dart
packages/flutter/lib/src/material/time_picker.dart
+246
-100
time_picker_test.dart
packages/flutter/test/material/time_picker_test.dart
+178
-25
time_test.dart
packages/flutter/test/material/time_test.dart
+139
-0
No files found.
packages/flutter/lib/src/material/time.dart
View file @
07849778
...
@@ -3,6 +3,7 @@
...
@@ -3,6 +3,7 @@
// found in the LICENSE file.
// found in the LICENSE file.
import
'package:flutter/widgets.dart'
;
import
'package:flutter/widgets.dart'
;
import
'package:flutter/services.dart'
;
import
'debug.dart'
;
import
'debug.dart'
;
import
'material_localizations.dart'
;
import
'material_localizations.dart'
;
...
@@ -134,6 +135,40 @@ class TimeOfDay {
...
@@ -134,6 +135,40 @@ class TimeOfDay {
}
}
}
}
/// A [RestorableValue] that knows how to save and restore [TimeOfDay].
///
/// {@macro flutter.widgets.RestorableNum}.
class
RestorableTimeOfDay
extends
RestorableValue
<
TimeOfDay
>
{
/// Creates a [RestorableTimeOfDay].
///
/// {@macro flutter.widgets.RestorableNum.constructor}
RestorableTimeOfDay
(
TimeOfDay
defaultValue
)
:
_defaultValue
=
defaultValue
;
final
TimeOfDay
_defaultValue
;
@override
TimeOfDay
createDefaultValue
()
=>
_defaultValue
;
@override
void
didUpdateValue
(
TimeOfDay
?
oldValue
)
{
assert
(
debugIsSerializableForRestoration
(
value
.
hour
));
assert
(
debugIsSerializableForRestoration
(
value
.
minute
));
notifyListeners
();
}
@override
TimeOfDay
fromPrimitives
(
Object
?
data
)
{
final
List
<
Object
?>
timeData
=
data
!
as
List
<
Object
?>;
return
TimeOfDay
(
minute:
timeData
[
0
]!
as
int
,
hour:
timeData
[
1
]!
as
int
,
);
}
@override
Object
?
toPrimitives
()
=>
<
int
>[
value
.
minute
,
value
.
hour
];
}
/// Determines how the time picker invoked using [showTimePicker] formats and
/// Determines how the time picker invoked using [showTimePicker] formats and
/// lays out the time controls.
/// lays out the time controls.
///
///
...
...
packages/flutter/lib/src/material/time_picker.dart
View file @
07849778
...
@@ -1293,6 +1293,7 @@ class _TimePickerInput extends StatefulWidget {
...
@@ -1293,6 +1293,7 @@ class _TimePickerInput extends StatefulWidget {
required
this
.
autofocusHour
,
required
this
.
autofocusHour
,
required
this
.
autofocusMinute
,
required
this
.
autofocusMinute
,
required
this
.
onChanged
,
required
this
.
onChanged
,
this
.
restorationId
,
})
:
assert
(
initialSelectedTime
!=
null
),
})
:
assert
(
initialSelectedTime
!=
null
),
assert
(
onChanged
!=
null
),
assert
(
onChanged
!=
null
),
super
(
key:
key
);
super
(
key:
key
);
...
@@ -1309,19 +1310,32 @@ class _TimePickerInput extends StatefulWidget {
...
@@ -1309,19 +1310,32 @@ class _TimePickerInput extends StatefulWidget {
final
ValueChanged
<
TimeOfDay
>
onChanged
;
final
ValueChanged
<
TimeOfDay
>
onChanged
;
/// Restoration ID to save and restore the state of the time picker input
/// widget.
///
/// If it is non-null, the widget will persist and restore its state
///
/// The state of this widget is persisted in a [RestorationBucket] claimed
/// from the surrounding [RestorationScope] using the provided restoration ID.
final
String
?
restorationId
;
@override
@override
_TimePickerInputState
createState
()
=>
_TimePickerInputState
();
_TimePickerInputState
createState
()
=>
_TimePickerInputState
();
}
}
class
_TimePickerInputState
extends
State
<
_TimePickerInput
>
{
class
_TimePickerInputState
extends
State
<
_TimePickerInput
>
with
RestorationMixin
{
late
TimeOfDay
_selectedTime
;
late
final
RestorableTimeOfDay
_selectedTime
=
RestorableTimeOfDay
(
widget
.
initialSelectedTime
)
;
bool
hourHasError
=
false
;
final
RestorableBool
hourHasError
=
RestorableBool
(
false
)
;
bool
minuteHasError
=
false
;
final
RestorableBool
minuteHasError
=
RestorableBool
(
false
)
;
@override
@override
void
initState
()
{
String
?
get
restorationId
=>
widget
.
restorationId
;
super
.
initState
();
_selectedTime
=
widget
.
initialSelectedTime
;
@override
void
restoreState
(
RestorationBucket
?
oldBucket
,
bool
initialRestore
)
{
registerForRestoration
(
_selectedTime
,
'selected_time'
);
registerForRestoration
(
hourHasError
,
'hour_has_error'
);
registerForRestoration
(
minuteHasError
,
'minute_has_error'
);
}
}
int
?
_parseHour
(
String
?
value
)
{
int
?
_parseHour
(
String
?
value
)
{
...
@@ -1340,8 +1354,8 @@ class _TimePickerInputState extends State<_TimePickerInput> {
...
@@ -1340,8 +1354,8 @@ class _TimePickerInputState extends State<_TimePickerInput> {
}
}
}
else
{
}
else
{
if
(
newHour
>
0
&&
newHour
<
13
)
{
if
(
newHour
>
0
&&
newHour
<
13
)
{
if
((
_selectedTime
.
period
==
DayPeriod
.
pm
&&
newHour
!=
12
)
if
((
_selectedTime
.
value
.
period
==
DayPeriod
.
pm
&&
newHour
!=
12
)
||
(
_selectedTime
.
period
==
DayPeriod
.
am
&&
newHour
==
12
))
{
||
(
_selectedTime
.
value
.
period
==
DayPeriod
.
am
&&
newHour
==
12
))
{
newHour
=
(
newHour
+
TimeOfDay
.
hoursPerPeriod
)
%
TimeOfDay
.
hoursPerDay
;
newHour
=
(
newHour
+
TimeOfDay
.
hoursPerPeriod
)
%
TimeOfDay
.
hoursPerDay
;
}
}
return
newHour
;
return
newHour
;
...
@@ -1369,8 +1383,8 @@ class _TimePickerInputState extends State<_TimePickerInput> {
...
@@ -1369,8 +1383,8 @@ class _TimePickerInputState extends State<_TimePickerInput> {
void
_handleHourSavedSubmitted
(
String
?
value
)
{
void
_handleHourSavedSubmitted
(
String
?
value
)
{
final
int
?
newHour
=
_parseHour
(
value
);
final
int
?
newHour
=
_parseHour
(
value
);
if
(
newHour
!=
null
)
{
if
(
newHour
!=
null
)
{
_selectedTime
=
TimeOfDay
(
hour:
newHour
,
minute:
_selectedTim
e
.
minute
);
_selectedTime
.
value
=
TimeOfDay
(
hour:
newHour
,
minute:
_selectedTime
.
valu
e
.
minute
);
widget
.
onChanged
(
_selectedTime
);
widget
.
onChanged
(
_selectedTime
.
value
);
}
}
}
}
...
@@ -1385,20 +1399,20 @@ class _TimePickerInputState extends State<_TimePickerInput> {
...
@@ -1385,20 +1399,20 @@ class _TimePickerInputState extends State<_TimePickerInput> {
void
_handleMinuteSavedSubmitted
(
String
?
value
)
{
void
_handleMinuteSavedSubmitted
(
String
?
value
)
{
final
int
?
newMinute
=
_parseMinute
(
value
);
final
int
?
newMinute
=
_parseMinute
(
value
);
if
(
newMinute
!=
null
)
{
if
(
newMinute
!=
null
)
{
_selectedTime
=
TimeOfDay
(
hour:
_selectedTim
e
.
hour
,
minute:
int
.
parse
(
value
!));
_selectedTime
.
value
=
TimeOfDay
(
hour:
_selectedTime
.
valu
e
.
hour
,
minute:
int
.
parse
(
value
!));
widget
.
onChanged
(
_selectedTime
);
widget
.
onChanged
(
_selectedTime
.
value
);
}
}
}
}
void
_handleDayPeriodChanged
(
TimeOfDay
value
)
{
void
_handleDayPeriodChanged
(
TimeOfDay
value
)
{
_selectedTime
=
value
;
_selectedTime
.
value
=
value
;
widget
.
onChanged
(
_selectedTime
);
widget
.
onChanged
(
_selectedTime
.
value
);
}
}
String
?
_validateHour
(
String
?
value
)
{
String
?
_validateHour
(
String
?
value
)
{
final
int
?
newHour
=
_parseHour
(
value
);
final
int
?
newHour
=
_parseHour
(
value
);
setState
(()
{
setState
(()
{
hourHasError
=
newHour
==
null
;
hourHasError
.
value
=
newHour
==
null
;
});
});
// This is used as the validator for the [TextFormField].
// This is used as the validator for the [TextFormField].
// Returning an empty string allows the field to go into an error state.
// Returning an empty string allows the field to go into an error state.
...
@@ -1409,7 +1423,7 @@ class _TimePickerInputState extends State<_TimePickerInput> {
...
@@ -1409,7 +1423,7 @@ class _TimePickerInputState extends State<_TimePickerInput> {
String
?
_validateMinute
(
String
?
value
)
{
String
?
_validateMinute
(
String
?
value
)
{
final
int
?
newMinute
=
_parseMinute
(
value
);
final
int
?
newMinute
=
_parseMinute
(
value
);
setState
(()
{
setState
(()
{
minuteHasError
=
newMinute
==
null
;
minuteHasError
.
value
=
newMinute
==
null
;
});
});
// This is used as the validator for the [TextFormField].
// This is used as the validator for the [TextFormField].
// Returning an empty string allows the field to go into an error state.
// Returning an empty string allows the field to go into an error state.
...
@@ -1441,7 +1455,7 @@ class _TimePickerInputState extends State<_TimePickerInput> {
...
@@ -1441,7 +1455,7 @@ class _TimePickerInputState extends State<_TimePickerInput> {
children:
<
Widget
>[
children:
<
Widget
>[
if
(!
use24HourDials
&&
timeOfDayFormat
==
TimeOfDayFormat
.
a_space_h_colon_mm
)
...<
Widget
>[
if
(!
use24HourDials
&&
timeOfDayFormat
==
TimeOfDayFormat
.
a_space_h_colon_mm
)
...<
Widget
>[
_DayPeriodControl
(
_DayPeriodControl
(
selectedTime:
_selectedTime
,
selectedTime:
_selectedTime
.
value
,
orientation:
Orientation
.
portrait
,
orientation:
Orientation
.
portrait
,
onChanged:
_handleDayPeriodChanged
,
onChanged:
_handleDayPeriodChanged
,
),
),
...
@@ -1459,7 +1473,8 @@ class _TimePickerInputState extends State<_TimePickerInput> {
...
@@ -1459,7 +1473,8 @@ class _TimePickerInputState extends State<_TimePickerInput> {
children:
<
Widget
>[
children:
<
Widget
>[
const
SizedBox
(
height:
8.0
),
const
SizedBox
(
height:
8.0
),
_HourTextField
(
_HourTextField
(
selectedTime:
_selectedTime
,
restorationId:
'hour_text_field'
,
selectedTime:
_selectedTime
.
value
,
style:
hourMinuteStyle
,
style:
hourMinuteStyle
,
autofocus:
widget
.
autofocusHour
,
autofocus:
widget
.
autofocusHour
,
validator:
_validateHour
,
validator:
_validateHour
,
...
@@ -1467,7 +1482,7 @@ class _TimePickerInputState extends State<_TimePickerInput> {
...
@@ -1467,7 +1482,7 @@ class _TimePickerInputState extends State<_TimePickerInput> {
onChanged:
_handleHourChanged
,
onChanged:
_handleHourChanged
,
),
),
const
SizedBox
(
height:
8.0
),
const
SizedBox
(
height:
8.0
),
if
(!
hourHasError
&&
!
minuteHasError
)
if
(!
hourHasError
.
value
&&
!
minuteHasError
.
value
)
ExcludeSemantics
(
ExcludeSemantics
(
child:
Text
(
child:
Text
(
MaterialLocalizations
.
of
(
context
).
timePickerHourLabel
,
MaterialLocalizations
.
of
(
context
).
timePickerHourLabel
,
...
@@ -1490,14 +1505,15 @@ class _TimePickerInputState extends State<_TimePickerInput> {
...
@@ -1490,14 +1505,15 @@ class _TimePickerInputState extends State<_TimePickerInput> {
children:
<
Widget
>[
children:
<
Widget
>[
const
SizedBox
(
height:
8.0
),
const
SizedBox
(
height:
8.0
),
_MinuteTextField
(
_MinuteTextField
(
selectedTime:
_selectedTime
,
restorationId:
'minute_text_field'
,
selectedTime:
_selectedTime
.
value
,
style:
hourMinuteStyle
,
style:
hourMinuteStyle
,
autofocus:
widget
.
autofocusMinute
,
autofocus:
widget
.
autofocusMinute
,
validator:
_validateMinute
,
validator:
_validateMinute
,
onSavedSubmitted:
_handleMinuteSavedSubmitted
,
onSavedSubmitted:
_handleMinuteSavedSubmitted
,
),
),
const
SizedBox
(
height:
8.0
),
const
SizedBox
(
height:
8.0
),
if
(!
hourHasError
&&
!
minuteHasError
)
if
(!
hourHasError
.
value
&&
!
minuteHasError
.
value
)
ExcludeSemantics
(
ExcludeSemantics
(
child:
Text
(
child:
Text
(
MaterialLocalizations
.
of
(
context
).
timePickerMinuteLabel
,
MaterialLocalizations
.
of
(
context
).
timePickerMinuteLabel
,
...
@@ -1515,14 +1531,14 @@ class _TimePickerInputState extends State<_TimePickerInput> {
...
@@ -1515,14 +1531,14 @@ class _TimePickerInputState extends State<_TimePickerInput> {
if
(!
use24HourDials
&&
timeOfDayFormat
!=
TimeOfDayFormat
.
a_space_h_colon_mm
)
...<
Widget
>[
if
(!
use24HourDials
&&
timeOfDayFormat
!=
TimeOfDayFormat
.
a_space_h_colon_mm
)
...<
Widget
>[
const
SizedBox
(
width:
12.0
),
const
SizedBox
(
width:
12.0
),
_DayPeriodControl
(
_DayPeriodControl
(
selectedTime:
_selectedTime
,
selectedTime:
_selectedTime
.
value
,
orientation:
Orientation
.
portrait
,
orientation:
Orientation
.
portrait
,
onChanged:
_handleDayPeriodChanged
,
onChanged:
_handleDayPeriodChanged
,
),
),
],
],
],
],
),
),
if
(
hourHasError
||
minuteHasError
)
if
(
hourHasError
.
value
||
minuteHasError
.
value
)
Text
(
Text
(
MaterialLocalizations
.
of
(
context
).
invalidTimeLabel
,
MaterialLocalizations
.
of
(
context
).
invalidTimeLabel
,
style:
theme
.
textTheme
.
bodyText2
!.
copyWith
(
color:
theme
.
colorScheme
.
error
),
style:
theme
.
textTheme
.
bodyText2
!.
copyWith
(
color:
theme
.
colorScheme
.
error
),
...
@@ -1544,6 +1560,7 @@ class _HourTextField extends StatelessWidget {
...
@@ -1544,6 +1560,7 @@ class _HourTextField extends StatelessWidget {
required
this
.
validator
,
required
this
.
validator
,
required
this
.
onSavedSubmitted
,
required
this
.
onSavedSubmitted
,
required
this
.
onChanged
,
required
this
.
onChanged
,
this
.
restorationId
,
})
:
super
(
key:
key
);
})
:
super
(
key:
key
);
final
TimeOfDay
selectedTime
;
final
TimeOfDay
selectedTime
;
...
@@ -1552,10 +1569,12 @@ class _HourTextField extends StatelessWidget {
...
@@ -1552,10 +1569,12 @@ class _HourTextField extends StatelessWidget {
final
FormFieldValidator
<
String
>
validator
;
final
FormFieldValidator
<
String
>
validator
;
final
ValueChanged
<
String
?>
onSavedSubmitted
;
final
ValueChanged
<
String
?>
onSavedSubmitted
;
final
ValueChanged
<
String
>
onChanged
;
final
ValueChanged
<
String
>
onChanged
;
final
String
?
restorationId
;
@override
@override
Widget
build
(
BuildContext
context
)
{
Widget
build
(
BuildContext
context
)
{
return
_HourMinuteTextField
(
return
_HourMinuteTextField
(
restorationId:
restorationId
,
selectedTime:
selectedTime
,
selectedTime:
selectedTime
,
isHour:
true
,
isHour:
true
,
autofocus:
autofocus
,
autofocus:
autofocus
,
...
@@ -1576,6 +1595,7 @@ class _MinuteTextField extends StatelessWidget {
...
@@ -1576,6 +1595,7 @@ class _MinuteTextField extends StatelessWidget {
required
this
.
autofocus
,
required
this
.
autofocus
,
required
this
.
validator
,
required
this
.
validator
,
required
this
.
onSavedSubmitted
,
required
this
.
onSavedSubmitted
,
this
.
restorationId
,
})
:
super
(
key:
key
);
})
:
super
(
key:
key
);
final
TimeOfDay
selectedTime
;
final
TimeOfDay
selectedTime
;
...
@@ -1583,10 +1603,12 @@ class _MinuteTextField extends StatelessWidget {
...
@@ -1583,10 +1603,12 @@ class _MinuteTextField extends StatelessWidget {
final
bool
?
autofocus
;
final
bool
?
autofocus
;
final
FormFieldValidator
<
String
>
validator
;
final
FormFieldValidator
<
String
>
validator
;
final
ValueChanged
<
String
?>
onSavedSubmitted
;
final
ValueChanged
<
String
?>
onSavedSubmitted
;
final
String
?
restorationId
;
@override
@override
Widget
build
(
BuildContext
context
)
{
Widget
build
(
BuildContext
context
)
{
return
_HourMinuteTextField
(
return
_HourMinuteTextField
(
restorationId:
restorationId
,
selectedTime:
selectedTime
,
selectedTime:
selectedTime
,
isHour:
false
,
isHour:
false
,
autofocus:
autofocus
,
autofocus:
autofocus
,
...
@@ -1608,6 +1630,7 @@ class _HourMinuteTextField extends StatefulWidget {
...
@@ -1608,6 +1630,7 @@ class _HourMinuteTextField extends StatefulWidget {
required
this
.
semanticHintText
,
required
this
.
semanticHintText
,
required
this
.
validator
,
required
this
.
validator
,
required
this
.
onSavedSubmitted
,
required
this
.
onSavedSubmitted
,
this
.
restorationId
,
this
.
onChanged
,
this
.
onChanged
,
})
:
super
(
key:
key
);
})
:
super
(
key:
key
);
...
@@ -1619,13 +1642,15 @@ class _HourMinuteTextField extends StatefulWidget {
...
@@ -1619,13 +1642,15 @@ class _HourMinuteTextField extends StatefulWidget {
final
FormFieldValidator
<
String
>
validator
;
final
FormFieldValidator
<
String
>
validator
;
final
ValueChanged
<
String
?>
onSavedSubmitted
;
final
ValueChanged
<
String
?>
onSavedSubmitted
;
final
ValueChanged
<
String
>?
onChanged
;
final
ValueChanged
<
String
>?
onChanged
;
final
String
?
restorationId
;
@override
@override
_HourMinuteTextFieldState
createState
()
=>
_HourMinuteTextFieldState
();
_HourMinuteTextFieldState
createState
()
=>
_HourMinuteTextFieldState
();
}
}
class
_HourMinuteTextFieldState
extends
State
<
_HourMinuteTextField
>
{
class
_HourMinuteTextFieldState
extends
State
<
_HourMinuteTextField
>
with
RestorationMixin
{
TextEditingController
?
controller
;
final
RestorableTextEditingController
controller
=
RestorableTextEditingController
();
final
RestorableBool
controllerHasBeenSet
=
RestorableBool
(
false
);
late
FocusNode
focusNode
;
late
FocusNode
focusNode
;
@override
@override
...
@@ -1639,7 +1664,21 @@ class _HourMinuteTextFieldState extends State<_HourMinuteTextField> {
...
@@ -1639,7 +1664,21 @@ class _HourMinuteTextFieldState extends State<_HourMinuteTextField> {
@override
@override
void
didChangeDependencies
()
{
void
didChangeDependencies
()
{
super
.
didChangeDependencies
();
super
.
didChangeDependencies
();
controller
??=
TextEditingController
(
text:
_formattedValue
);
// Only set the text value if it has not been populated with a localized
// version yet.
if
(!
controllerHasBeenSet
.
value
)
{
controllerHasBeenSet
.
value
=
true
;
controller
.
value
.
text
=
_formattedValue
;
}
}
@override
String
?
get
restorationId
=>
widget
.
restorationId
;
@override
void
restoreState
(
RestorationBucket
?
oldBucket
,
bool
initialRestore
)
{
registerForRestoration
(
controller
,
'text_editing_controller'
);
registerForRestoration
(
controllerHasBeenSet
,
'has_controller_been_set'
);
}
}
String
get
_formattedValue
{
String
get
_formattedValue
{
...
@@ -1701,7 +1740,10 @@ class _HourMinuteTextFieldState extends State<_HourMinuteTextField> {
...
@@ -1701,7 +1740,10 @@ class _HourMinuteTextFieldState extends State<_HourMinuteTextField> {
height:
_kTimePickerHeaderControlHeight
,
height:
_kTimePickerHeaderControlHeight
,
child:
MediaQuery
(
child:
MediaQuery
(
data:
MediaQuery
.
of
(
context
).
copyWith
(
textScaleFactor:
1.0
),
data:
MediaQuery
.
of
(
context
).
copyWith
(
textScaleFactor:
1.0
),
child:
UnmanagedRestorationScope
(
bucket:
bucket
,
child:
TextFormField
(
child:
TextFormField
(
restorationId:
'hour_minute_text_form_field'
,
autofocus:
widget
.
autofocus
??
false
,
autofocus:
widget
.
autofocus
??
false
,
expands:
true
,
expands:
true
,
maxLines:
null
,
maxLines:
null
,
...
@@ -1712,15 +1754,16 @@ class _HourMinuteTextFieldState extends State<_HourMinuteTextField> {
...
@@ -1712,15 +1754,16 @@ class _HourMinuteTextFieldState extends State<_HourMinuteTextField> {
textAlign:
TextAlign
.
center
,
textAlign:
TextAlign
.
center
,
keyboardType:
TextInputType
.
number
,
keyboardType:
TextInputType
.
number
,
style:
widget
.
style
.
copyWith
(
color:
timePickerTheme
.
hourMinuteTextColor
??
colorScheme
.
onSurface
),
style:
widget
.
style
.
copyWith
(
color:
timePickerTheme
.
hourMinuteTextColor
??
colorScheme
.
onSurface
),
controller:
controller
,
controller:
controller
.
value
,
decoration:
inputDecoration
,
decoration:
inputDecoration
,
validator:
widget
.
validator
,
validator:
widget
.
validator
,
onEditingComplete:
()
=>
widget
.
onSavedSubmitted
(
controller
!
.
text
),
onEditingComplete:
()
=>
widget
.
onSavedSubmitted
(
controller
.
value
.
text
),
onSaved:
widget
.
onSavedSubmitted
,
onSaved:
widget
.
onSavedSubmitted
,
onFieldSubmitted:
widget
.
onSavedSubmitted
,
onFieldSubmitted:
widget
.
onSavedSubmitted
,
onChanged:
widget
.
onChanged
,
onChanged:
widget
.
onChanged
,
),
),
),
),
),
);
);
}
}
}
}
...
@@ -1731,16 +1774,17 @@ class _HourMinuteTextFieldState extends State<_HourMinuteTextField> {
...
@@ -1731,16 +1774,17 @@ class _HourMinuteTextFieldState extends State<_HourMinuteTextField> {
/// selected [TimeOfDay] if the user taps the "OK" button, or null if the user
/// selected [TimeOfDay] if the user taps the "OK" button, or null if the user
/// taps the "CANCEL" button. The selected time is reported by calling
/// taps the "CANCEL" button. The selected time is reported by calling
/// [Navigator.pop].
/// [Navigator.pop].
class
_
TimePickerDialog
extends
StatefulWidget
{
class
TimePickerDialog
extends
StatefulWidget
{
/// Creates a material time picker.
/// Creates a material time picker.
///
///
/// [initialTime] must not be null.
/// [initialTime] must not be null.
const
_
TimePickerDialog
({
const
TimePickerDialog
({
Key
?
key
,
Key
?
key
,
required
this
.
initialTime
,
required
this
.
initialTime
,
required
this
.
cancelText
,
this
.
cancelText
,
required
this
.
confirmText
,
this
.
confirmText
,
required
this
.
helpText
,
this
.
helpText
,
this
.
restorationId
,
this
.
initialEntryMode
=
TimePickerEntryMode
.
dial
,
this
.
initialEntryMode
=
TimePickerEntryMode
.
dial
,
})
:
assert
(
initialTime
!=
null
),
})
:
assert
(
initialTime
!=
null
),
super
(
key:
key
);
super
(
key:
key
);
...
@@ -1764,21 +1808,115 @@ class _TimePickerDialog extends StatefulWidget {
...
@@ -1764,21 +1808,115 @@ class _TimePickerDialog extends StatefulWidget {
/// Optionally provide your own help text to the header of the time picker.
/// Optionally provide your own help text to the header of the time picker.
final
String
?
helpText
;
final
String
?
helpText
;
/// Restoration ID to save and restore the state of the [TimePickerDialog].
///
/// If it is non-null, the time picker will persist and restore the
/// dialog's state.
///
/// 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
_TimePickerDialogState
createState
()
=>
_TimePickerDialogState
();
_TimePickerDialogState
createState
()
=>
_TimePickerDialogState
();
}
}
class
_TimePickerDialogState
extends
State
<
_TimePickerDialog
>
{
// A restorable [TimePickerEntryMode] value.
final
GlobalKey
<
FormState
>
_formKey
=
GlobalKey
<
FormState
>();
//
// This serializes each entry as a unique `int` value.
class
_RestorableTimePickerEntryMode
extends
RestorableValue
<
TimePickerEntryMode
>
{
_RestorableTimePickerEntryMode
(
TimePickerEntryMode
defaultValue
,
)
:
_defaultValue
=
defaultValue
;
final
TimePickerEntryMode
_defaultValue
;
@override
@override
void
initState
()
{
TimePickerEntryMode
createDefaultValue
()
=>
_defaultValue
;
super
.
initState
();
_selectedTime
=
widget
.
initialTime
;
@override
_entryMode
=
widget
.
initialEntryMode
;
void
didUpdateValue
(
TimePickerEntryMode
?
oldValue
)
{
_autoValidate
=
false
;
assert
(
debugIsSerializableForRestoration
(
value
.
index
));
notifyListeners
();
}
}
@override
TimePickerEntryMode
fromPrimitives
(
Object
?
data
)
=>
TimePickerEntryMode
.
values
[
data
!
as
int
];
@override
Object
?
toPrimitives
()
=>
value
.
index
;
}
// A restorable [_RestorableTimePickerEntryMode] value.
//
// This serializes each entry as a unique `int` value.
class
_RestorableTimePickerMode
extends
RestorableValue
<
_TimePickerMode
>
{
_RestorableTimePickerMode
(
_TimePickerMode
defaultValue
,
)
:
_defaultValue
=
defaultValue
;
final
_TimePickerMode
_defaultValue
;
@override
_TimePickerMode
createDefaultValue
()
=>
_defaultValue
;
@override
void
didUpdateValue
(
_TimePickerMode
?
oldValue
)
{
assert
(
debugIsSerializableForRestoration
(
value
.
index
));
notifyListeners
();
}
@override
_TimePickerMode
fromPrimitives
(
Object
?
data
)
=>
_TimePickerMode
.
values
[
data
!
as
int
];
@override
Object
?
toPrimitives
()
=>
value
.
index
;
}
// A restorable [_RestorableTimePickerEntryMode] value.
//
// This serializes each entry as a unique `int` value.
//
// This value can be null.
class
_RestorableTimePickerModeN
extends
RestorableValue
<
_TimePickerMode
?>
{
_RestorableTimePickerModeN
(
_TimePickerMode
?
defaultValue
,
)
:
_defaultValue
=
defaultValue
;
final
_TimePickerMode
?
_defaultValue
;
@override
_TimePickerMode
?
createDefaultValue
()
=>
_defaultValue
;
@override
void
didUpdateValue
(
_TimePickerMode
?
oldValue
)
{
assert
(
debugIsSerializableForRestoration
(
value
?.
index
));
notifyListeners
();
}
@override
_TimePickerMode
fromPrimitives
(
Object
?
data
)
=>
_TimePickerMode
.
values
[
data
!
as
int
];
@override
Object
?
toPrimitives
()
=>
value
?.
index
;
}
class
_TimePickerDialogState
extends
State
<
TimePickerDialog
>
with
RestorationMixin
{
final
GlobalKey
<
FormState
>
_formKey
=
GlobalKey
<
FormState
>();
late
final
_RestorableTimePickerEntryMode
_entryMode
=
_RestorableTimePickerEntryMode
(
widget
.
initialEntryMode
);
final
_RestorableTimePickerMode
_mode
=
_RestorableTimePickerMode
(
_TimePickerMode
.
hour
);
final
_RestorableTimePickerModeN
_lastModeAnnounced
=
_RestorableTimePickerModeN
(
null
);
final
RestorableBool
_autoValidate
=
RestorableBool
(
false
);
final
RestorableBoolN
_autofocusHour
=
RestorableBoolN
(
null
);
final
RestorableBoolN
_autofocusMinute
=
RestorableBoolN
(
null
);
final
RestorableBool
_announcedInitialTime
=
RestorableBool
(
false
);
@override
@override
void
didChangeDependencies
()
{
void
didChangeDependencies
()
{
super
.
didChangeDependencies
();
super
.
didChangeDependencies
();
...
@@ -1787,15 +1925,23 @@ class _TimePickerDialogState extends State<_TimePickerDialog> {
...
@@ -1787,15 +1925,23 @@ class _TimePickerDialogState extends State<_TimePickerDialog> {
_announceModeOnce
();
_announceModeOnce
();
}
}
late
TimePickerEntryMode
_entryMode
;
@override
_TimePickerMode
_mode
=
_TimePickerMode
.
hour
;
String
?
get
restorationId
=>
widget
.
restorationId
;
_TimePickerMode
?
_lastModeAnnounced
;
late
bool
_autoValidate
;
bool
?
_autofocusHour
;
bool
?
_autofocusMinute
;
TimeOfDay
get
selectedTime
=>
_selectedTime
;
@override
late
TimeOfDay
_selectedTime
;
void
restoreState
(
RestorationBucket
?
oldBucket
,
bool
initialRestore
)
{
registerForRestoration
(
_entryMode
,
'entry_mode'
);
registerForRestoration
(
_mode
,
'mode'
);
registerForRestoration
(
_lastModeAnnounced
,
'last_mode_announced'
);
registerForRestoration
(
_autoValidate
,
'autovalidate'
);
registerForRestoration
(
_autofocusHour
,
'autofocus_hour'
);
registerForRestoration
(
_autofocusMinute
,
'autofocus_minute'
);
registerForRestoration
(
_announcedInitialTime
,
'announced_initial_time'
);
registerForRestoration
(
_selectedTime
,
'selected_time'
);
}
RestorableTimeOfDay
get
selectedTime
=>
_selectedTime
;
late
final
RestorableTimeOfDay
_selectedTime
=
RestorableTimeOfDay
(
widget
.
initialTime
);
Timer
?
_vibrateTimer
;
Timer
?
_vibrateTimer
;
late
MaterialLocalizations
localizations
;
late
MaterialLocalizations
localizations
;
...
@@ -1821,35 +1967,35 @@ class _TimePickerDialogState extends State<_TimePickerDialog> {
...
@@ -1821,35 +1967,35 @@ class _TimePickerDialogState extends State<_TimePickerDialog> {
void
_handleModeChanged
(
_TimePickerMode
mode
)
{
void
_handleModeChanged
(
_TimePickerMode
mode
)
{
_vibrate
();
_vibrate
();
setState
(()
{
setState
(()
{
_mode
=
mode
;
_mode
.
value
=
mode
;
_announceModeOnce
();
_announceModeOnce
();
});
});
}
}
void
_handleEntryModeToggle
()
{
void
_handleEntryModeToggle
()
{
setState
(()
{
setState
(()
{
switch
(
_entryMode
)
{
switch
(
_entryMode
.
value
)
{
case
TimePickerEntryMode
.
dial
:
case
TimePickerEntryMode
.
dial
:
_autoValidate
=
false
;
_autoValidate
.
value
=
false
;
_entryMode
=
TimePickerEntryMode
.
input
;
_entryMode
.
value
=
TimePickerEntryMode
.
input
;
break
;
break
;
case
TimePickerEntryMode
.
input
:
case
TimePickerEntryMode
.
input
:
_formKey
.
currentState
!.
save
();
_formKey
.
currentState
!.
save
();
_autofocusHour
=
false
;
_autofocusHour
.
value
=
false
;
_autofocusMinute
=
false
;
_autofocusMinute
.
value
=
false
;
_entryMode
=
TimePickerEntryMode
.
dial
;
_entryMode
.
value
=
TimePickerEntryMode
.
dial
;
break
;
break
;
}
}
});
});
}
}
void
_announceModeOnce
()
{
void
_announceModeOnce
()
{
if
(
_lastModeAnnounced
==
_mod
e
)
{
if
(
_lastModeAnnounced
.
value
==
_mode
.
valu
e
)
{
// Already announced it.
// Already announced it.
return
;
return
;
}
}
switch
(
_mode
)
{
switch
(
_mode
.
value
)
{
case
_TimePickerMode
.
hour
:
case
_TimePickerMode
.
hour
:
_announceToAccessibility
(
context
,
localizations
.
timePickerHourModeAnnouncement
);
_announceToAccessibility
(
context
,
localizations
.
timePickerHourModeAnnouncement
);
break
;
break
;
...
@@ -1857,13 +2003,11 @@ class _TimePickerDialogState extends State<_TimePickerDialog> {
...
@@ -1857,13 +2003,11 @@ class _TimePickerDialogState extends State<_TimePickerDialog> {
_announceToAccessibility
(
context
,
localizations
.
timePickerMinuteModeAnnouncement
);
_announceToAccessibility
(
context
,
localizations
.
timePickerMinuteModeAnnouncement
);
break
;
break
;
}
}
_lastModeAnnounced
=
_mod
e
;
_lastModeAnnounced
.
value
=
_mode
.
valu
e
;
}
}
bool
_announcedInitialTime
=
false
;
void
_announceInitialTimeOnce
()
{
void
_announceInitialTimeOnce
()
{
if
(
_announcedInitialTime
)
if
(
_announcedInitialTime
.
value
)
return
;
return
;
final
MediaQueryData
media
=
MediaQuery
.
of
(
context
);
final
MediaQueryData
media
=
MediaQuery
.
of
(
context
);
...
@@ -1872,29 +2016,29 @@ class _TimePickerDialogState extends State<_TimePickerDialog> {
...
@@ -1872,29 +2016,29 @@ class _TimePickerDialogState extends State<_TimePickerDialog> {
context
,
context
,
localizations
.
formatTimeOfDay
(
widget
.
initialTime
,
alwaysUse24HourFormat:
media
.
alwaysUse24HourFormat
),
localizations
.
formatTimeOfDay
(
widget
.
initialTime
,
alwaysUse24HourFormat:
media
.
alwaysUse24HourFormat
),
);
);
_announcedInitialTime
=
true
;
_announcedInitialTime
.
value
=
true
;
}
}
void
_handleTimeChanged
(
TimeOfDay
value
)
{
void
_handleTimeChanged
(
TimeOfDay
value
)
{
_vibrate
();
_vibrate
();
setState
(()
{
setState
(()
{
_selectedTime
=
value
;
_selectedTime
.
value
=
value
;
});
});
}
}
void
_handleHourDoubleTapped
()
{
void
_handleHourDoubleTapped
()
{
_autofocusHour
=
true
;
_autofocusHour
.
value
=
true
;
_handleEntryModeToggle
();
_handleEntryModeToggle
();
}
}
void
_handleMinuteDoubleTapped
()
{
void
_handleMinuteDoubleTapped
()
{
_autofocusMinute
=
true
;
_autofocusMinute
.
value
=
true
;
_handleEntryModeToggle
();
_handleEntryModeToggle
();
}
}
void
_handleHourSelected
()
{
void
_handleHourSelected
()
{
setState
(()
{
setState
(()
{
_mode
=
_TimePickerMode
.
minute
;
_mode
.
value
=
_TimePickerMode
.
minute
;
});
});
}
}
...
@@ -1903,15 +2047,15 @@ class _TimePickerDialogState extends State<_TimePickerDialog> {
...
@@ -1903,15 +2047,15 @@ class _TimePickerDialogState extends State<_TimePickerDialog> {
}
}
void
_handleOk
()
{
void
_handleOk
()
{
if
(
_entryMode
==
TimePickerEntryMode
.
input
)
{
if
(
_entryMode
.
value
==
TimePickerEntryMode
.
input
)
{
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
,
_selectedTime
);
Navigator
.
pop
(
context
,
_selectedTime
.
value
);
}
}
Size
_dialogSize
(
BuildContext
context
)
{
Size
_dialogSize
(
BuildContext
context
)
{
...
@@ -1924,7 +2068,7 @@ class _TimePickerDialogState extends State<_TimePickerDialog> {
...
@@ -1924,7 +2068,7 @@ class _TimePickerDialogState extends State<_TimePickerDialog> {
final
double
timePickerWidth
;
final
double
timePickerWidth
;
final
double
timePickerHeight
;
final
double
timePickerHeight
;
switch
(
_entryMode
)
{
switch
(
_entryMode
.
value
)
{
case
TimePickerEntryMode
.
dial
:
case
TimePickerEntryMode
.
dial
:
switch
(
orientation
)
{
switch
(
orientation
)
{
case
Orientation
.
portrait
:
case
Orientation
.
portrait
:
...
@@ -1967,8 +2111,8 @@ class _TimePickerDialogState extends State<_TimePickerDialog> {
...
@@ -1967,8 +2111,8 @@ class _TimePickerDialogState extends State<_TimePickerDialog> {
theme
.
colorScheme
.
brightness
==
Brightness
.
dark
?
1.0
:
0.6
,
theme
.
colorScheme
.
brightness
==
Brightness
.
dark
?
1.0
:
0.6
,
),
),
onPressed:
_handleEntryModeToggle
,
onPressed:
_handleEntryModeToggle
,
icon:
Icon
(
_entryMode
==
TimePickerEntryMode
.
dial
?
Icons
.
keyboard
:
Icons
.
access_time
),
icon:
Icon
(
_entryMode
.
value
==
TimePickerEntryMode
.
dial
?
Icons
.
keyboard
:
Icons
.
access_time
),
tooltip:
_entryMode
==
TimePickerEntryMode
.
dial
tooltip:
_entryMode
.
value
==
TimePickerEntryMode
.
dial
?
MaterialLocalizations
.
of
(
context
).
inputTimeModeButtonLabel
?
MaterialLocalizations
.
of
(
context
).
inputTimeModeButtonLabel
:
MaterialLocalizations
.
of
(
context
).
dialModeButtonLabel
,
:
MaterialLocalizations
.
of
(
context
).
dialModeButtonLabel
,
),
),
...
@@ -1997,7 +2141,7 @@ class _TimePickerDialogState extends State<_TimePickerDialog> {
...
@@ -1997,7 +2141,7 @@ class _TimePickerDialogState extends State<_TimePickerDialog> {
);
);
final
Widget
picker
;
final
Widget
picker
;
switch
(
_entryMode
)
{
switch
(
_entryMode
.
value
)
{
case
TimePickerEntryMode
.
dial
:
case
TimePickerEntryMode
.
dial
:
final
Widget
dial
=
Padding
(
final
Widget
dial
=
Padding
(
padding:
orientation
==
Orientation
.
portrait
?
const
EdgeInsets
.
symmetric
(
horizontal:
36
,
vertical:
24
)
:
const
EdgeInsets
.
all
(
24
),
padding:
orientation
==
Orientation
.
portrait
?
const
EdgeInsets
.
symmetric
(
horizontal:
36
,
vertical:
24
)
:
const
EdgeInsets
.
all
(
24
),
...
@@ -2005,9 +2149,9 @@ class _TimePickerDialogState extends State<_TimePickerDialog> {
...
@@ -2005,9 +2149,9 @@ class _TimePickerDialogState extends State<_TimePickerDialog> {
child:
AspectRatio
(
child:
AspectRatio
(
aspectRatio:
1.0
,
aspectRatio:
1.0
,
child:
_Dial
(
child:
_Dial
(
mode:
_mode
,
mode:
_mode
.
value
,
use24HourDials:
use24HourDials
,
use24HourDials:
use24HourDials
,
selectedTime:
_selectedTime
,
selectedTime:
_selectedTime
.
value
,
onChanged:
_handleTimeChanged
,
onChanged:
_handleTimeChanged
,
onHourSelected:
_handleHourSelected
,
onHourSelected:
_handleHourSelected
,
),
),
...
@@ -2016,8 +2160,8 @@ class _TimePickerDialogState extends State<_TimePickerDialog> {
...
@@ -2016,8 +2160,8 @@ class _TimePickerDialogState extends State<_TimePickerDialog> {
);
);
final
Widget
header
=
_TimePickerHeader
(
final
Widget
header
=
_TimePickerHeader
(
selectedTime:
_selectedTime
,
selectedTime:
_selectedTime
.
value
,
mode:
_mode
,
mode:
_mode
.
value
,
orientation:
orientation
,
orientation:
orientation
,
onModeChanged:
_handleModeChanged
,
onModeChanged:
_handleModeChanged
,
onChanged:
_handleTimeChanged
,
onChanged:
_handleTimeChanged
,
...
@@ -2067,17 +2211,19 @@ class _TimePickerDialogState extends State<_TimePickerDialog> {
...
@@ -2067,17 +2211,19 @@ class _TimePickerDialogState extends State<_TimePickerDialog> {
case
TimePickerEntryMode
.
input
:
case
TimePickerEntryMode
.
input
:
picker
=
Form
(
picker
=
Form
(
key:
_formKey
,
key:
_formKey
,
autovalidate:
_autoValidate
,
autovalidate:
_autoValidate
.
value
,
child:
SingleChildScrollView
(
child:
SingleChildScrollView
(
restorationId:
'time_picker_scroll_view'
,
child:
Column
(
child:
Column
(
mainAxisSize:
MainAxisSize
.
min
,
mainAxisSize:
MainAxisSize
.
min
,
children:
<
Widget
>[
children:
<
Widget
>[
_TimePickerInput
(
_TimePickerInput
(
initialSelectedTime:
_selectedTime
,
initialSelectedTime:
_selectedTime
.
value
,
helpText:
widget
.
helpText
,
helpText:
widget
.
helpText
,
autofocusHour:
_autofocusHour
,
autofocusHour:
_autofocusHour
.
value
,
autofocusMinute:
_autofocusMinute
,
autofocusMinute:
_autofocusMinute
.
value
,
onChanged:
_handleTimeChanged
,
onChanged:
_handleTimeChanged
,
restorationId:
'time_picker_input'
,
),
),
actions
,
actions
,
],
],
...
@@ -2093,7 +2239,7 @@ class _TimePickerDialogState extends State<_TimePickerDialog> {
...
@@ -2093,7 +2239,7 @@ class _TimePickerDialogState extends State<_TimePickerDialog> {
backgroundColor:
TimePickerTheme
.
of
(
context
).
backgroundColor
??
theme
.
colorScheme
.
surface
,
backgroundColor:
TimePickerTheme
.
of
(
context
).
backgroundColor
??
theme
.
colorScheme
.
surface
,
insetPadding:
EdgeInsets
.
symmetric
(
insetPadding:
EdgeInsets
.
symmetric
(
horizontal:
16.0
,
horizontal:
16.0
,
vertical:
_entryMode
==
TimePickerEntryMode
.
input
?
0.0
:
24.0
,
vertical:
_entryMode
.
value
==
TimePickerEntryMode
.
input
?
0.0
:
24.0
,
),
),
child:
AnimatedContainer
(
child:
AnimatedContainer
(
width:
dialogSize
.
width
,
width:
dialogSize
.
width
,
...
@@ -2204,7 +2350,7 @@ Future<TimeOfDay?> showTimePicker({
...
@@ -2204,7 +2350,7 @@ Future<TimeOfDay?> showTimePicker({
assert
(
initialEntryMode
!=
null
);
assert
(
initialEntryMode
!=
null
);
assert
(
debugCheckHasMaterialLocalizations
(
context
));
assert
(
debugCheckHasMaterialLocalizations
(
context
));
final
Widget
dialog
=
_
TimePickerDialog
(
final
Widget
dialog
=
TimePickerDialog
(
initialTime:
initialTime
,
initialTime:
initialTime
,
initialEntryMode:
initialEntryMode
,
initialEntryMode:
initialEntryMode
,
cancelText:
cancelText
,
cancelText:
cancelText
,
...
...
packages/flutter/test/material/time_picker_test.dart
View file @
07849778
...
@@ -12,40 +12,88 @@ import 'feedback_tester.dart';
...
@@ -12,40 +12,88 @@ import 'feedback_tester.dart';
final
Finder
_hourControl
=
find
.
byWidgetPredicate
((
Widget
w
)
=>
'
${w.runtimeType}
'
==
'_HourControl'
);
final
Finder
_hourControl
=
find
.
byWidgetPredicate
((
Widget
w
)
=>
'
${w.runtimeType}
'
==
'_HourControl'
);
final
Finder
_minuteControl
=
find
.
byWidgetPredicate
((
Widget
widget
)
=>
'
${widget.runtimeType}
'
==
'_MinuteControl'
);
final
Finder
_minuteControl
=
find
.
byWidgetPredicate
((
Widget
widget
)
=>
'
${widget.runtimeType}
'
==
'_MinuteControl'
);
final
Finder
_timePickerDialog
=
find
.
byWidgetPredicate
((
Widget
widget
)
=>
'
${widget.runtimeType}
'
==
'
_
TimePickerDialog'
);
final
Finder
_timePickerDialog
=
find
.
byWidgetPredicate
((
Widget
widget
)
=>
'
${widget.runtimeType}
'
==
'TimePickerDialog'
);
class
_TimePickerLauncher
extends
State
less
Widget
{
class
_TimePickerLauncher
extends
State
ful
Widget
{
const
_TimePickerLauncher
({
const
_TimePickerLauncher
({
Key
?
key
,
Key
?
key
,
required
this
.
onChanged
,
required
this
.
onChanged
,
this
.
locale
,
this
.
entryMode
=
TimePickerEntryMode
.
dial
,
this
.
entryMode
=
TimePickerEntryMode
.
dial
,
this
.
restorationId
,
})
:
super
(
key:
key
);
})
:
super
(
key:
key
);
final
ValueChanged
<
TimeOfDay
?>
onChanged
;
final
ValueChanged
<
TimeOfDay
?>
onChanged
;
final
Locale
?
locale
;
final
TimePickerEntryMode
entryMode
;
final
TimePickerEntryMode
entryMode
;
final
String
?
restorationId
;
@override
_TimePickerLauncherState
createState
()
=>
_TimePickerLauncherState
();
}
class
_TimePickerLauncherState
extends
State
<
_TimePickerLauncher
>
with
RestorationMixin
{
@override
String
?
get
restorationId
=>
widget
.
restorationId
;
late
final
RestorableRouteFuture
<
TimeOfDay
?>
_restorableTimePickerRouteFuture
=
RestorableRouteFuture
<
TimeOfDay
?>(
onComplete:
_selectTime
,
onPresent:
(
NavigatorState
navigator
,
Object
?
arguments
)
{
return
navigator
.
restorablePush
(
_timePickerRoute
,
arguments:
<
String
,
int
>{
'entryMode'
:
widget
.
entryMode
.
index
,
},
);
},
);
static
Route
<
TimeOfDay
>
_timePickerRoute
(
BuildContext
context
,
Object
?
arguments
,
)
{
final
Map
<
dynamic
,
dynamic
>
args
=
arguments
!
as
Map
<
dynamic
,
dynamic
>;
final
TimePickerEntryMode
entryMode
=
TimePickerEntryMode
.
values
[
args
[
'entryMode'
]
as
int
];
return
DialogRoute
<
TimeOfDay
>(
context:
context
,
builder:
(
BuildContext
context
)
{
return
TimePickerDialog
(
restorationId:
'time_picker_dialog'
,
initialTime:
const
TimeOfDay
(
hour:
7
,
minute:
0
),
initialEntryMode:
entryMode
,
);
},
);
}
@override
void
restoreState
(
RestorationBucket
?
oldBucket
,
bool
initialRestore
)
{
registerForRestoration
(
_restorableTimePickerRouteFuture
,
'time_picker_route_future'
);
}
void
_selectTime
(
TimeOfDay
?
newSelectedTime
)
{
widget
.
onChanged
(
newSelectedTime
);
}
@override
@override
Widget
build
(
BuildContext
context
)
{
Widget
build
(
BuildContext
context
)
{
return
MaterialApp
(
return
Material
(
locale:
locale
,
home:
Material
(
child:
Center
(
child:
Center
(
child:
Builder
(
child:
Builder
(
builder:
(
BuildContext
context
)
{
builder:
(
BuildContext
context
)
{
return
ElevatedButton
(
return
ElevatedButton
(
child:
const
Text
(
'X'
),
child:
const
Text
(
'X'
),
onPressed:
()
async
{
onPressed:
()
async
{
onChanged
(
await
showTimePicker
(
if
(
widget
.
restorationId
==
null
)
{
widget
.
onChanged
(
await
showTimePicker
(
context:
context
,
context:
context
,
initialTime:
const
TimeOfDay
(
hour:
7
,
minute:
0
),
initialTime:
const
TimeOfDay
(
hour:
7
,
minute:
0
),
initialEntryMode:
entryMode
,
initialEntryMode:
widget
.
entryMode
,
));
));
}
else
{
_restorableTimePickerRouteFuture
.
present
();
}
},
},
);
);
}
},
),
),
),
),
),
);
);
...
@@ -56,8 +104,17 @@ Future<Offset?> startPicker(
...
@@ -56,8 +104,17 @@ Future<Offset?> startPicker(
WidgetTester
tester
,
WidgetTester
tester
,
ValueChanged
<
TimeOfDay
?>
onChanged
,
{
ValueChanged
<
TimeOfDay
?>
onChanged
,
{
TimePickerEntryMode
entryMode
=
TimePickerEntryMode
.
dial
,
TimePickerEntryMode
entryMode
=
TimePickerEntryMode
.
dial
,
String
?
restorationId
,
})
async
{
})
async
{
await
tester
.
pumpWidget
(
_TimePickerLauncher
(
onChanged:
onChanged
,
locale:
const
Locale
(
'en'
,
'US'
),
entryMode:
entryMode
));
await
tester
.
pumpWidget
(
MaterialApp
(
restorationScopeId:
'app'
,
locale:
const
Locale
(
'en'
,
'US'
),
home:
_TimePickerLauncher
(
onChanged:
onChanged
,
entryMode:
entryMode
,
restorationId:
restorationId
,
),
));
await
tester
.
tap
(
find
.
text
(
'X'
));
await
tester
.
tap
(
find
.
text
(
'X'
));
await
tester
.
pumpAndSettle
(
const
Duration
(
seconds:
1
));
await
tester
.
pumpAndSettle
(
const
Duration
(
seconds:
1
));
return
entryMode
==
TimePickerEntryMode
.
dial
?
tester
.
getCenter
(
find
.
byKey
(
const
ValueKey
<
String
>(
'time-picker-dial'
)))
:
null
;
return
entryMode
==
TimePickerEntryMode
.
dial
?
tester
.
getCenter
(
find
.
byKey
(
const
ValueKey
<
String
>(
'time-picker-dial'
)))
:
null
;
...
@@ -428,7 +485,7 @@ void _tests() {
...
@@ -428,7 +485,7 @@ void _tests() {
// Ensure we preserve day period as we roll over.
// Ensure we preserve day period as we roll over.
final
dynamic
pickerState
=
tester
.
state
(
_timePickerDialog
);
final
dynamic
pickerState
=
tester
.
state
(
_timePickerDialog
);
expect
(
pickerState
.
selectedTime
,
const
TimeOfDay
(
hour:
1
,
minute:
0
));
expect
(
pickerState
.
selectedTime
.
value
,
const
TimeOfDay
(
hour:
1
,
minute:
0
));
await
actAndExpect
(
await
actAndExpect
(
initialValue:
'1'
,
initialValue:
'1'
,
...
@@ -493,7 +550,7 @@ void _tests() {
...
@@ -493,7 +550,7 @@ void _tests() {
// Ensure we preserve hour period as we roll over.
// Ensure we preserve hour period as we roll over.
final
dynamic
pickerState
=
tester
.
state
(
_timePickerDialog
);
final
dynamic
pickerState
=
tester
.
state
(
_timePickerDialog
);
expect
(
pickerState
.
selectedTime
,
const
TimeOfDay
(
hour:
11
,
minute:
0
));
expect
(
pickerState
.
selectedTime
.
value
,
const
TimeOfDay
(
hour:
11
,
minute:
0
));
await
actAndExpect
(
await
actAndExpect
(
initialValue:
'00'
,
initialValue:
'00'
,
...
@@ -939,6 +996,102 @@ void _testsInput() {
...
@@ -939,6 +996,102 @@ void _testsInput() {
expect
(
hourFieldTop
,
separatorTop
);
expect
(
hourFieldTop
,
separatorTop
);
expect
(
minuteFieldTop
,
separatorTop
);
expect
(
minuteFieldTop
,
separatorTop
);
});
});
testWidgets
(
'Time Picker state restoration test - dial mode'
,
(
WidgetTester
tester
)
async
{
TimeOfDay
?
result
;
final
Offset
center
=
(
await
startPicker
(
tester
,
(
TimeOfDay
?
time
)
{
result
=
time
;
},
restorationId:
'restorable_time_picker'
,
))!;
final
Offset
hour6
=
Offset
(
center
.
dx
,
center
.
dy
+
50.0
);
// 6:00
final
Offset
min45
=
Offset
(
center
.
dx
-
50.0
,
center
.
dy
);
// 45 mins (or 9:00 hours)
await
tester
.
tapAt
(
hour6
);
await
tester
.
pump
(
const
Duration
(
milliseconds:
50
));
await
tester
.
restartAndRestore
();
await
tester
.
tapAt
(
min45
);
await
tester
.
pump
(
const
Duration
(
milliseconds:
50
));
final
TestRestorationData
restorationData
=
await
tester
.
getRestorationData
();
await
tester
.
restartAndRestore
();
// Setting to PM adds 12 hours (18:45)
await
tester
.
tap
(
find
.
text
(
'PM'
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
50
));
await
tester
.
restartAndRestore
();
await
finishPicker
(
tester
);
expect
(
result
,
equals
(
const
TimeOfDay
(
hour:
18
,
minute:
45
)));
// Test restoring from before PM was selected (6:45)
await
tester
.
restoreFrom
(
restorationData
);
await
finishPicker
(
tester
);
expect
(
result
,
equals
(
const
TimeOfDay
(
hour:
6
,
minute:
45
)));
});
testWidgets
(
'Time Picker state restoration test - input mode'
,
(
WidgetTester
tester
)
async
{
TimeOfDay
?
result
;
await
startPicker
(
tester
,
(
TimeOfDay
?
time
)
{
result
=
time
;
},
entryMode:
TimePickerEntryMode
.
input
,
restorationId:
'restorable_time_picker'
,
);
await
tester
.
enterText
(
find
.
byType
(
TextField
).
first
,
'9'
);
await
tester
.
pump
(
const
Duration
(
milliseconds:
50
));
await
tester
.
restartAndRestore
();
await
tester
.
enterText
(
find
.
byType
(
TextField
).
last
,
'12'
);
await
tester
.
pump
(
const
Duration
(
milliseconds:
50
));
final
TestRestorationData
restorationData
=
await
tester
.
getRestorationData
();
await
tester
.
restartAndRestore
();
// Setting to PM adds 12 hours (21:12)
await
tester
.
tap
(
find
.
text
(
'PM'
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
50
));
await
tester
.
restartAndRestore
();
await
finishPicker
(
tester
);
expect
(
result
,
equals
(
const
TimeOfDay
(
hour:
21
,
minute:
12
)));
// Restoring from before PM was set (9:12)
await
tester
.
restoreFrom
(
restorationData
);
await
finishPicker
(
tester
);
expect
(
result
,
equals
(
const
TimeOfDay
(
hour:
9
,
minute:
12
)));
});
testWidgets
(
'Time Picker state restoration test - switching modes'
,
(
WidgetTester
tester
)
async
{
TimeOfDay
?
result
;
final
Offset
center
=
(
await
startPicker
(
tester
,
(
TimeOfDay
?
time
)
{
result
=
time
;
},
restorationId:
'restorable_time_picker'
,
))!;
final
TestRestorationData
restorationData
=
await
tester
.
getRestorationData
();
// Switch to input mode from dial mode.
await
tester
.
tap
(
find
.
byIcon
(
Icons
.
keyboard
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
50
));
await
tester
.
restartAndRestore
();
// Select time using input mode controls.
await
tester
.
enterText
(
find
.
byType
(
TextField
).
first
,
'9'
);
await
tester
.
enterText
(
find
.
byType
(
TextField
).
last
,
'12'
);
await
tester
.
pump
(
const
Duration
(
milliseconds:
50
));
await
finishPicker
(
tester
);
expect
(
result
,
equals
(
const
TimeOfDay
(
hour:
9
,
minute:
12
)));
// Restoring from dial mode.
await
tester
.
restoreFrom
(
restorationData
);
final
Offset
hour6
=
Offset
(
center
.
dx
,
center
.
dy
+
50.0
);
// 6:00
final
Offset
min45
=
Offset
(
center
.
dx
-
50.0
,
center
.
dy
);
// 45 mins (or 9:00 hours)
await
tester
.
tapAt
(
hour6
);
await
tester
.
pump
(
const
Duration
(
milliseconds:
50
));
await
tester
.
restartAndRestore
();
await
tester
.
tapAt
(
min45
);
await
tester
.
pump
(
const
Duration
(
milliseconds:
50
));
await
finishPicker
(
tester
);
expect
(
result
,
equals
(
const
TimeOfDay
(
hour:
6
,
minute:
45
)));
});
}
}
final
Finder
findDialPaint
=
find
.
descendant
(
final
Finder
findDialPaint
=
find
.
descendant
(
...
...
packages/flutter/test/material/time_test.dart
View file @
07849778
...
@@ -26,4 +26,143 @@ void main() {
...
@@ -26,4 +26,143 @@ void main() {
expect
(
await
pumpTest
(
true
),
'07:00'
);
expect
(
await
pumpTest
(
true
),
'07:00'
);
});
});
});
});
group
(
'RestorableTimeOfDay tests'
,
()
{
testWidgets
(
'value is not accessible when not registered'
,
(
WidgetTester
tester
)
async
{
expect
(()
=>
RestorableTimeOfDay
(
const
TimeOfDay
(
hour:
20
,
minute:
4
)).
value
,
throwsAssertionError
);
});
testWidgets
(
'work when not in restoration scope'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
_RestorableWidget
());
final
_RestorableWidgetState
state
=
tester
.
state
(
find
.
byType
(
_RestorableWidget
));
// Initialized to default values.
expect
(
state
.
timeOfDay
.
value
,
const
TimeOfDay
(
hour:
10
,
minute:
5
));
// Modify values.
state
.
setProperties
(()
{
state
.
timeOfDay
.
value
=
const
TimeOfDay
(
hour:
2
,
minute:
2
);
});
await
tester
.
pump
();
expect
(
state
.
timeOfDay
.
value
,
const
TimeOfDay
(
hour:
2
,
minute:
2
));
});
testWidgets
(
'restart and restore'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
RootRestorationScope
(
restorationId:
'root-child'
,
child:
_RestorableWidget
(),
));
_RestorableWidgetState
state
=
tester
.
state
(
find
.
byType
(
_RestorableWidget
));
// Initialized to default values.
expect
(
state
.
timeOfDay
.
value
,
const
TimeOfDay
(
hour:
10
,
minute:
5
));
// Modify values.
state
.
setProperties
(()
{
state
.
timeOfDay
.
value
=
const
TimeOfDay
(
hour:
2
,
minute:
2
);
});
await
tester
.
pump
();
expect
(
state
.
timeOfDay
.
value
,
const
TimeOfDay
(
hour:
2
,
minute:
2
));
// Restores to previous values.
await
tester
.
restartAndRestore
();
final
_RestorableWidgetState
oldState
=
state
;
state
=
tester
.
state
(
find
.
byType
(
_RestorableWidget
));
expect
(
state
,
isNot
(
same
(
oldState
)));
expect
(
state
.
timeOfDay
.
value
,
const
TimeOfDay
(
hour:
2
,
minute:
2
));
});
testWidgets
(
'restore to older state'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
RootRestorationScope
(
restorationId:
'root-child'
,
child:
_RestorableWidget
(),
));
final
_RestorableWidgetState
state
=
tester
.
state
(
find
.
byType
(
_RestorableWidget
));
// Modify values.
state
.
setProperties
(()
{
state
.
timeOfDay
.
value
=
const
TimeOfDay
(
hour:
2
,
minute:
2
);
});
await
tester
.
pump
();
final
TestRestorationData
restorationData
=
await
tester
.
getRestorationData
();
// Modify values.
state
.
setProperties
(()
{
state
.
timeOfDay
.
value
=
const
TimeOfDay
(
hour:
4
,
minute:
4
);
});
await
tester
.
pump
();
// Restore to previous.
await
tester
.
restoreFrom
(
restorationData
);
expect
(
state
.
timeOfDay
.
value
,
const
TimeOfDay
(
hour:
2
,
minute:
2
));
// Restore to empty data will re-initialize to default values.
await
tester
.
restoreFrom
(
TestRestorationData
.
empty
);
expect
(
state
.
timeOfDay
.
value
,
const
TimeOfDay
(
hour:
10
,
minute:
5
));
});
testWidgets
(
'call notifiers when value changes'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
RootRestorationScope
(
restorationId:
'root-child'
,
child:
_RestorableWidget
(),
));
final
_RestorableWidgetState
state
=
tester
.
state
(
find
.
byType
(
_RestorableWidget
));
final
List
<
String
>
notifyLog
=
<
String
>[];
state
.
timeOfDay
.
addListener
(()
{
notifyLog
.
add
(
'hello world'
);
});
state
.
setProperties
(()
{
state
.
timeOfDay
.
value
=
const
TimeOfDay
(
hour:
2
,
minute:
2
);
});
expect
(
notifyLog
.
single
,
'hello world'
);
notifyLog
.
clear
();
await
tester
.
pump
();
// Does not notify when set to same value.
state
.
setProperties
(()
{
state
.
timeOfDay
.
value
=
const
TimeOfDay
(
hour:
2
,
minute:
2
);
});
expect
(
notifyLog
,
isEmpty
);
});
});
}
class
_RestorableWidget
extends
StatefulWidget
{
const
_RestorableWidget
({
Key
?
key
})
:
super
(
key:
key
);
@override
State
<
_RestorableWidget
>
createState
()
=>
_RestorableWidgetState
();
}
class
_RestorableWidgetState
extends
State
<
_RestorableWidget
>
with
RestorationMixin
{
final
RestorableTimeOfDay
timeOfDay
=
RestorableTimeOfDay
(
const
TimeOfDay
(
hour:
10
,
minute:
5
));
@override
void
restoreState
(
RestorationBucket
?
oldBucket
,
bool
initialRestore
)
{
registerForRestoration
(
timeOfDay
,
'time_of_day'
);
}
void
setProperties
(
VoidCallback
callback
)
{
setState
(
callback
);
}
@override
Widget
build
(
BuildContext
context
)
{
return
const
SizedBox
();
}
@override
String
get
restorationId
=>
'widget'
;
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment