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
e676024d
Unverified
Commit
e676024d
authored
Jun 23, 2020
by
rami-a
Committed by
GitHub
Jun 23, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
[Material] Redesign Time Picker (#59191)
parent
2cd205bb
Changes
8
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
2395 additions
and
1270 deletions
+2395
-1270
material.dart
packages/flutter/lib/material.dart
+1
-0
theme_data.dart
packages/flutter/lib/src/material/theme_data.dart
+15
-0
time_picker.dart
packages/flutter/lib/src/material/time_picker.dart
+1152
-927
time_picker_theme.dart
packages/flutter/lib/src/material/time_picker_theme.dart
+381
-0
theme_data_test.dart
packages/flutter/test/material/theme_data_test.dart
+4
-0
time_picker_test.dart
packages/flutter/test/material/time_picker_test.dart
+275
-274
time_picker_theme_test.dart
packages/flutter/test/material/time_picker_theme_test.dart
+426
-0
time_picker_test.dart
...flutter_localizations/test/material/time_picker_test.dart
+141
-69
No files found.
packages/flutter/lib/material.dart
View file @
e676024d
...
@@ -124,6 +124,7 @@ export 'src/material/theme.dart';
...
@@ -124,6 +124,7 @@ export 'src/material/theme.dart';
export
'src/material/theme_data.dart'
;
export
'src/material/theme_data.dart'
;
export
'src/material/time.dart'
;
export
'src/material/time.dart'
;
export
'src/material/time_picker.dart'
;
export
'src/material/time_picker.dart'
;
export
'src/material/time_picker_theme.dart'
;
export
'src/material/toggle_buttons.dart'
;
export
'src/material/toggle_buttons.dart'
;
export
'src/material/toggle_buttons_theme.dart'
;
export
'src/material/toggle_buttons_theme.dart'
;
export
'src/material/toggleable.dart'
;
export
'src/material/toggleable.dart'
;
...
...
packages/flutter/lib/src/material/theme_data.dart
View file @
e676024d
...
@@ -35,6 +35,7 @@ import 'slider_theme.dart';
...
@@ -35,6 +35,7 @@ import 'slider_theme.dart';
import
'snack_bar_theme.dart'
;
import
'snack_bar_theme.dart'
;
import
'tab_bar_theme.dart'
;
import
'tab_bar_theme.dart'
;
import
'text_theme.dart'
;
import
'text_theme.dart'
;
import
'time_picker_theme.dart'
;
import
'toggle_buttons_theme.dart'
;
import
'toggle_buttons_theme.dart'
;
import
'tooltip_theme.dart'
;
import
'tooltip_theme.dart'
;
import
'typography.dart'
;
import
'typography.dart'
;
...
@@ -269,6 +270,7 @@ class ThemeData with Diagnosticable {
...
@@ -269,6 +270,7 @@ class ThemeData with Diagnosticable {
DividerThemeData
dividerTheme
,
DividerThemeData
dividerTheme
,
ButtonBarThemeData
buttonBarTheme
,
ButtonBarThemeData
buttonBarTheme
,
BottomNavigationBarThemeData
bottomNavigationBarTheme
,
BottomNavigationBarThemeData
bottomNavigationBarTheme
,
TimePickerThemeData
timePickerTheme
,
bool
fixTextFieldOutlineLabel
,
bool
fixTextFieldOutlineLabel
,
})
{
})
{
assert
(
colorScheme
?.
brightness
==
null
||
brightness
==
null
||
colorScheme
.
brightness
==
brightness
);
assert
(
colorScheme
?.
brightness
==
null
||
brightness
==
null
||
colorScheme
.
brightness
==
brightness
);
...
@@ -380,6 +382,7 @@ class ThemeData with Diagnosticable {
...
@@ -380,6 +382,7 @@ class ThemeData with Diagnosticable {
dividerTheme
??=
const
DividerThemeData
();
dividerTheme
??=
const
DividerThemeData
();
buttonBarTheme
??=
const
ButtonBarThemeData
();
buttonBarTheme
??=
const
ButtonBarThemeData
();
bottomNavigationBarTheme
??=
const
BottomNavigationBarThemeData
();
bottomNavigationBarTheme
??=
const
BottomNavigationBarThemeData
();
timePickerTheme
??=
const
TimePickerThemeData
();
fixTextFieldOutlineLabel
??=
false
;
fixTextFieldOutlineLabel
??=
false
;
...
@@ -448,6 +451,7 @@ class ThemeData with Diagnosticable {
...
@@ -448,6 +451,7 @@ class ThemeData with Diagnosticable {
dividerTheme:
dividerTheme
,
dividerTheme:
dividerTheme
,
buttonBarTheme:
buttonBarTheme
,
buttonBarTheme:
buttonBarTheme
,
bottomNavigationBarTheme:
bottomNavigationBarTheme
,
bottomNavigationBarTheme:
bottomNavigationBarTheme
,
timePickerTheme:
timePickerTheme
,
fixTextFieldOutlineLabel:
fixTextFieldOutlineLabel
,
fixTextFieldOutlineLabel:
fixTextFieldOutlineLabel
,
);
);
}
}
...
@@ -527,6 +531,7 @@ class ThemeData with Diagnosticable {
...
@@ -527,6 +531,7 @@ class ThemeData with Diagnosticable {
@required
this
.
dividerTheme
,
@required
this
.
dividerTheme
,
@required
this
.
buttonBarTheme
,
@required
this
.
buttonBarTheme
,
@required
this
.
bottomNavigationBarTheme
,
@required
this
.
bottomNavigationBarTheme
,
@required
this
.
timePickerTheme
,
@required
this
.
fixTextFieldOutlineLabel
,
@required
this
.
fixTextFieldOutlineLabel
,
})
:
assert
(
visualDensity
!=
null
),
})
:
assert
(
visualDensity
!=
null
),
assert
(
primaryColor
!=
null
),
assert
(
primaryColor
!=
null
),
...
@@ -589,6 +594,7 @@ class ThemeData with Diagnosticable {
...
@@ -589,6 +594,7 @@ class ThemeData with Diagnosticable {
assert
(
dividerTheme
!=
null
),
assert
(
dividerTheme
!=
null
),
assert
(
buttonBarTheme
!=
null
),
assert
(
buttonBarTheme
!=
null
),
assert
(
bottomNavigationBarTheme
!=
null
),
assert
(
bottomNavigationBarTheme
!=
null
),
assert
(
timePickerTheme
!=
null
),
assert
(
fixTextFieldOutlineLabel
!=
null
);
assert
(
fixTextFieldOutlineLabel
!=
null
);
/// Create a [ThemeData] based on the colors in the given [colorScheme] and
/// Create a [ThemeData] based on the colors in the given [colorScheme] and
...
@@ -1036,6 +1042,9 @@ class ThemeData with Diagnosticable {
...
@@ -1036,6 +1042,9 @@ class ThemeData with Diagnosticable {
/// widgets.
/// widgets.
final
BottomNavigationBarThemeData
bottomNavigationBarTheme
;
final
BottomNavigationBarThemeData
bottomNavigationBarTheme
;
/// A theme for customizing the appearance and layout of time picker widgets.
final
TimePickerThemeData
timePickerTheme
;
/// A temporary flag to allow apps to opt-in to a
/// A temporary flag to allow apps to opt-in to a
/// [small fix](https://github.com/flutter/flutter/issues/54028) for the Y
/// [small fix](https://github.com/flutter/flutter/issues/54028) for the Y
/// coordinate of the floating label in a [TextField] [OutlineInputBorder].
/// coordinate of the floating label in a [TextField] [OutlineInputBorder].
...
@@ -1117,6 +1126,7 @@ class ThemeData with Diagnosticable {
...
@@ -1117,6 +1126,7 @@ class ThemeData with Diagnosticable {
DividerThemeData
dividerTheme
,
DividerThemeData
dividerTheme
,
ButtonBarThemeData
buttonBarTheme
,
ButtonBarThemeData
buttonBarTheme
,
BottomNavigationBarThemeData
bottomNavigationBarTheme
,
BottomNavigationBarThemeData
bottomNavigationBarTheme
,
TimePickerThemeData
timePickerTheme
,
bool
fixTextFieldOutlineLabel
,
bool
fixTextFieldOutlineLabel
,
})
{
})
{
cupertinoOverrideTheme
=
cupertinoOverrideTheme
?.
noDefault
();
cupertinoOverrideTheme
=
cupertinoOverrideTheme
?.
noDefault
();
...
@@ -1185,6 +1195,7 @@ class ThemeData with Diagnosticable {
...
@@ -1185,6 +1195,7 @@ class ThemeData with Diagnosticable {
dividerTheme:
dividerTheme
??
this
.
dividerTheme
,
dividerTheme:
dividerTheme
??
this
.
dividerTheme
,
buttonBarTheme:
buttonBarTheme
??
this
.
buttonBarTheme
,
buttonBarTheme:
buttonBarTheme
??
this
.
buttonBarTheme
,
bottomNavigationBarTheme:
bottomNavigationBarTheme
??
this
.
bottomNavigationBarTheme
,
bottomNavigationBarTheme:
bottomNavigationBarTheme
??
this
.
bottomNavigationBarTheme
,
timePickerTheme:
timePickerTheme
??
this
.
timePickerTheme
,
fixTextFieldOutlineLabel:
fixTextFieldOutlineLabel
??
this
.
fixTextFieldOutlineLabel
,
fixTextFieldOutlineLabel:
fixTextFieldOutlineLabel
??
this
.
fixTextFieldOutlineLabel
,
);
);
}
}
...
@@ -1331,6 +1342,7 @@ class ThemeData with Diagnosticable {
...
@@ -1331,6 +1342,7 @@ class ThemeData with Diagnosticable {
dividerTheme:
DividerThemeData
.
lerp
(
a
.
dividerTheme
,
b
.
dividerTheme
,
t
),
dividerTheme:
DividerThemeData
.
lerp
(
a
.
dividerTheme
,
b
.
dividerTheme
,
t
),
buttonBarTheme:
ButtonBarThemeData
.
lerp
(
a
.
buttonBarTheme
,
b
.
buttonBarTheme
,
t
),
buttonBarTheme:
ButtonBarThemeData
.
lerp
(
a
.
buttonBarTheme
,
b
.
buttonBarTheme
,
t
),
bottomNavigationBarTheme:
BottomNavigationBarThemeData
.
lerp
(
a
.
bottomNavigationBarTheme
,
b
.
bottomNavigationBarTheme
,
t
),
bottomNavigationBarTheme:
BottomNavigationBarThemeData
.
lerp
(
a
.
bottomNavigationBarTheme
,
b
.
bottomNavigationBarTheme
,
t
),
timePickerTheme:
TimePickerThemeData
.
lerp
(
a
.
timePickerTheme
,
b
.
timePickerTheme
,
t
),
fixTextFieldOutlineLabel:
t
<
0.5
?
a
.
fixTextFieldOutlineLabel
:
b
.
fixTextFieldOutlineLabel
,
fixTextFieldOutlineLabel:
t
<
0.5
?
a
.
fixTextFieldOutlineLabel
:
b
.
fixTextFieldOutlineLabel
,
);
);
}
}
...
@@ -1405,6 +1417,7 @@ class ThemeData with Diagnosticable {
...
@@ -1405,6 +1417,7 @@ class ThemeData with Diagnosticable {
&&
other
.
dividerTheme
==
dividerTheme
&&
other
.
dividerTheme
==
dividerTheme
&&
other
.
buttonBarTheme
==
buttonBarTheme
&&
other
.
buttonBarTheme
==
buttonBarTheme
&&
other
.
bottomNavigationBarTheme
==
bottomNavigationBarTheme
&&
other
.
bottomNavigationBarTheme
==
bottomNavigationBarTheme
&&
other
.
timePickerTheme
==
timePickerTheme
&&
other
.
fixTextFieldOutlineLabel
==
fixTextFieldOutlineLabel
;
&&
other
.
fixTextFieldOutlineLabel
==
fixTextFieldOutlineLabel
;
}
}
...
@@ -1478,6 +1491,7 @@ class ThemeData with Diagnosticable {
...
@@ -1478,6 +1491,7 @@ class ThemeData with Diagnosticable {
dividerTheme
,
dividerTheme
,
buttonBarTheme
,
buttonBarTheme
,
bottomNavigationBarTheme
,
bottomNavigationBarTheme
,
timePickerTheme
,
fixTextFieldOutlineLabel
,
fixTextFieldOutlineLabel
,
];
];
return
hashList
(
values
);
return
hashList
(
values
);
...
@@ -1547,6 +1561,7 @@ class ThemeData with Diagnosticable {
...
@@ -1547,6 +1561,7 @@ class ThemeData with Diagnosticable {
properties
.
add
(
DiagnosticsProperty
<
MaterialBannerThemeData
>(
'bannerTheme'
,
bannerTheme
,
defaultValue:
defaultData
.
bannerTheme
,
level:
DiagnosticLevel
.
debug
));
properties
.
add
(
DiagnosticsProperty
<
MaterialBannerThemeData
>(
'bannerTheme'
,
bannerTheme
,
defaultValue:
defaultData
.
bannerTheme
,
level:
DiagnosticLevel
.
debug
));
properties
.
add
(
DiagnosticsProperty
<
DividerThemeData
>(
'dividerTheme'
,
dividerTheme
,
defaultValue:
defaultData
.
dividerTheme
,
level:
DiagnosticLevel
.
debug
));
properties
.
add
(
DiagnosticsProperty
<
DividerThemeData
>(
'dividerTheme'
,
dividerTheme
,
defaultValue:
defaultData
.
dividerTheme
,
level:
DiagnosticLevel
.
debug
));
properties
.
add
(
DiagnosticsProperty
<
ButtonBarThemeData
>(
'buttonBarTheme'
,
buttonBarTheme
,
defaultValue:
defaultData
.
buttonBarTheme
,
level:
DiagnosticLevel
.
debug
));
properties
.
add
(
DiagnosticsProperty
<
ButtonBarThemeData
>(
'buttonBarTheme'
,
buttonBarTheme
,
defaultValue:
defaultData
.
buttonBarTheme
,
level:
DiagnosticLevel
.
debug
));
properties
.
add
(
DiagnosticsProperty
<
TimePickerThemeData
>(
'timePickerTheme'
,
timePickerTheme
,
defaultValue:
defaultData
.
timePickerTheme
,
level:
DiagnosticLevel
.
debug
));
properties
.
add
(
DiagnosticsProperty
<
BottomNavigationBarThemeData
>(
'bottomNavigationBarTheme'
,
bottomNavigationBarTheme
,
defaultValue:
defaultData
.
bottomNavigationBarTheme
,
level:
DiagnosticLevel
.
debug
));
properties
.
add
(
DiagnosticsProperty
<
BottomNavigationBarThemeData
>(
'bottomNavigationBarTheme'
,
bottomNavigationBarTheme
,
defaultValue:
defaultData
.
bottomNavigationBarTheme
,
level:
DiagnosticLevel
.
debug
));
}
}
}
}
...
...
packages/flutter/lib/src/material/time_picker.dart
View file @
e676024d
...
@@ -12,325 +12,283 @@ import 'package:flutter/services.dart';
...
@@ -12,325 +12,283 @@ import 'package:flutter/services.dart';
import
'package:flutter/widgets.dart'
;
import
'package:flutter/widgets.dart'
;
import
'button_bar.dart'
;
import
'button_bar.dart'
;
import
'button_theme.dart'
;
import
'color_scheme.dart'
;
import
'colors.dart'
;
import
'colors.dart'
;
import
'constants.dart'
;
import
'curves.dart'
;
import
'debug.dart'
;
import
'debug.dart'
;
import
'dialog.dart'
;
import
'dialog.dart'
;
import
'feedback.dart'
;
import
'feedback.dart'
;
import
'flat_button.dart'
;
import
'flat_button.dart'
;
import
'icon_button.dart'
;
import
'icons.dart'
;
import
'ink_well.dart'
;
import
'ink_well.dart'
;
import
'input_border.dart'
;
import
'input_decorator.dart'
;
import
'material.dart'
;
import
'material.dart'
;
import
'material_localizations.dart'
;
import
'material_localizations.dart'
;
import
'material_state.dart'
;
import
'text_form_field.dart'
;
import
'text_theme.dart'
;
import
'text_theme.dart'
;
import
'theme.dart'
;
import
'theme.dart'
;
import
'theme_data.dart'
;
import
'theme_data.dart'
;
import
'time.dart'
;
import
'time.dart'
;
import
'time_picker_theme.dart'
;
// Examples can assume:
// Examples can assume:
// BuildContext context;
// BuildContext context;
const
Duration
_kDialogSizeAnimationDuration
=
Duration
(
milliseconds:
200
);
const
Duration
_kDialAnimateDuration
=
Duration
(
milliseconds:
200
);
const
Duration
_kDialAnimateDuration
=
Duration
(
milliseconds:
200
);
const
double
_kTwoPi
=
2
*
math
.
pi
;
const
double
_kTwoPi
=
2
*
math
.
pi
;
const
Duration
_kVibrateCommitDelay
=
Duration
(
milliseconds:
100
);
const
Duration
_kVibrateCommitDelay
=
Duration
(
milliseconds:
100
);
enum
_TimePickerMode
{
hour
,
minute
}
enum
_TimePickerMode
{
hour
,
minute
}
const
double
_kTimePickerHeaderPortraitHeight
=
96.0
;
const
double
_kTimePickerHeaderLandscapeWidth
=
264.0
;
const
double
_kTimePickerHeaderLandscapeWidth
=
168.0
;
const
double
_kTimePickerHeaderControlHeight
=
80.0
;
const
double
_kTimePickerWidthPortrait
=
328.0
;
const
double
_kTimePickerWidthPortrait
=
328.0
;
const
double
_kTimePickerWidthLandscape
=
5
12
.0
;
const
double
_kTimePickerWidthLandscape
=
5
28
.0
;
const
double
_kTimePickerHeightInput
=
226.0
;
const
double
_kTimePickerHeightPortrait
=
496.0
;
const
double
_kTimePickerHeightPortrait
=
496.0
;
const
double
_kTimePickerHeightLandscape
=
316.0
;
const
double
_kTimePickerHeightLandscape
=
316.0
;
const
double
_kTimePickerHeightPortraitCollapsed
=
484.0
;
const
double
_kTimePickerHeightPortraitCollapsed
=
484.0
;
const
double
_kTimePickerHeightLandscapeCollapsed
=
304.0
;
const
double
_kTimePickerHeightLandscapeCollapsed
=
304.0
;
const
BoxConstraints
_kMinTappableRegion
=
BoxConstraints
(
minWidth:
48
,
minHeight:
48
);
const
BorderRadius
_kDefaultBorderRadius
=
BorderRadius
.
all
(
Radius
.
circular
(
4.0
));
const
ShapeBorder
_kDefaultShape
=
RoundedRectangleBorder
(
borderRadius:
_kDefaultBorderRadius
);
enum
_TimePickerHeaderId
{
/// Interactive input mode of the time picker dialog.
hour
,
///
colon
,
/// In [TimePickerEntryMode.dial] mode, a clock dial is displayed and
minute
,
/// the user taps or drags the time they wish to select. In
period
,
// AM/PM picker
/// TimePickerEntryMode.input] mode, [TextField]s are displayed and the user
dot
,
/// types in the time they wish to select.
hString
,
// French Canadian "h" literal
enum
TimePickerEntryMode
{
/// Tapping/dragging on a clock dial.
dial
,
/// Text input.
input
,
}
}
/// Provides properties for rendering time picker header fragments.
/// Provides properties for rendering time picker header fragments.
@immutable
@immutable
class
_TimePickerFragmentContext
{
class
_TimePickerFragmentContext
{
const
_TimePickerFragmentContext
({
const
_TimePickerFragmentContext
({
@required
this
.
headerTextTheme
,
@required
this
.
textDirection
,
@required
this
.
selectedTime
,
@required
this
.
selectedTime
,
@required
this
.
mode
,
@required
this
.
mode
,
@required
this
.
activeColor
,
@required
this
.
activeStyle
,
@required
this
.
inactiveColor
,
@required
this
.
inactiveStyle
,
@required
this
.
onTimeChange
,
@required
this
.
onTimeChange
,
@required
this
.
onModeChange
,
@required
this
.
onModeChange
,
@required
this
.
targetPlatform
,
@required
this
.
use24HourDials
,
@required
this
.
use24HourDials
,
})
:
assert
(
headerTextTheme
!=
null
),
})
:
assert
(
selectedTime
!=
null
),
assert
(
textDirection
!=
null
),
assert
(
selectedTime
!=
null
),
assert
(
mode
!=
null
),
assert
(
mode
!=
null
),
assert
(
activeColor
!=
null
),
assert
(
activeStyle
!=
null
),
assert
(
inactiveColor
!=
null
),
assert
(
inactiveStyle
!=
null
),
assert
(
onTimeChange
!=
null
),
assert
(
onTimeChange
!=
null
),
assert
(
onModeChange
!=
null
),
assert
(
onModeChange
!=
null
),
assert
(
targetPlatform
!=
null
),
assert
(
use24HourDials
!=
null
);
assert
(
use24HourDials
!=
null
);
final
TextTheme
headerTextTheme
;
final
TextDirection
textDirection
;
final
TimeOfDay
selectedTime
;
final
TimeOfDay
selectedTime
;
final
_TimePickerMode
mode
;
final
_TimePickerMode
mode
;
final
Color
activeColor
;
final
TextStyle
activeStyle
;
final
Color
inactiveColor
;
final
TextStyle
inactiveStyle
;
final
ValueChanged
<
TimeOfDay
>
onTimeChange
;
final
ValueChanged
<
TimeOfDay
>
onTimeChange
;
final
ValueChanged
<
_TimePickerMode
>
onModeChange
;
final
ValueChanged
<
_TimePickerMode
>
onModeChange
;
final
TargetPlatform
targetPlatform
;
final
bool
use24HourDials
;
final
bool
use24HourDials
;
}
}
/// Contains the [widget] and layout properties of an atom of time information,
class
_TimePickerHeader
extends
StatelessWidget
{
/// such as am/pm indicator, hour, minute and string literals appearing in the
const
_TimePickerHeader
({
/// formatted time string.
@required
this
.
selectedTime
,
class
_TimePickerHeaderFragment
{
@required
this
.
mode
,
const
_TimePickerHeaderFragment
({
@required
this
.
layoutId
,
@required
this
.
widget
,
this
.
startMargin
=
0.0
,
})
:
assert
(
layoutId
!=
null
),
assert
(
widget
!=
null
),
assert
(
startMargin
!=
null
);
/// Identifier used by the custom layout to refer to the widget.
final
_TimePickerHeaderId
layoutId
;
/// The widget that renders a piece of time information.
final
Widget
widget
;
/// Horizontal distance from the fragment appearing at the start of this
/// fragment.
///
/// This value contributes to the total horizontal width of all fragments
/// appearing on the same line, unless it is the first fragment on the line,
/// in which case this value is ignored.
final
double
startMargin
;
}
/// An unbreakable part of the time picker header.
///
/// When the picker is laid out vertically, [fragments] of the piece are laid
/// out on the same line, with each piece getting its own line.
class
_TimePickerHeaderPiece
{
/// Creates a time picker header piece.
///
/// All arguments must be non-null. If the piece does not contain a pivot
/// fragment, use the value -1 as a convention.
const
_TimePickerHeaderPiece
(
this
.
pivotIndex
,
this
.
fragments
,
{
this
.
bottomMargin
=
0.0
})
:
assert
(
pivotIndex
!=
null
),
assert
(
fragments
!=
null
),
assert
(
bottomMargin
!=
null
);
/// Index into the [fragments] list, pointing at the fragment that's centered
/// horizontally.
final
int
pivotIndex
;
/// Fragments this piece is made of.
final
List
<
_TimePickerHeaderFragment
>
fragments
;
/// Vertical distance between this piece and the next piece.
///
/// This property applies only when the header is laid out vertically.
final
double
bottomMargin
;
}
/// Describes how the time picker header must be formatted.
///
/// A [_TimePickerHeaderFormat] is made of multiple [_TimePickerHeaderPiece]s.
/// A piece is made of multiple [_TimePickerHeaderFragment]s. A fragment has a
/// widget used to render some time information and contains some layout
/// properties.
///
/// ## Layout rules
///
/// Pieces are laid out such that all fragments inside the same piece are laid
/// out horizontally. Pieces are laid out horizontally if portrait orientation,
/// and vertically in landscape orientation.
///
/// One of the pieces is identified as a _centerpiece_. It is a piece that is
/// positioned in the center of the header, with all other pieces positioned
/// to the left or right of it.
class
_TimePickerHeaderFormat
{
const
_TimePickerHeaderFormat
(
this
.
centerpieceIndex
,
this
.
pieces
)
:
assert
(
centerpieceIndex
!=
null
),
assert
(
pieces
!=
null
);
/// Index into the [pieces] list pointing at the piece that contains the
/// pivot fragment.
final
int
centerpieceIndex
;
/// Pieces that constitute a time picker header.
final
List
<
_TimePickerHeaderPiece
>
pieces
;
}
/// Displays the am/pm fragment and provides controls for switching between am
/// and pm.
class
_DayPeriodControl
extends
StatelessWidget
{
const
_DayPeriodControl
({
@required
this
.
fragmentContext
,
@required
this
.
orientation
,
@required
this
.
orientation
,
});
@required
this
.
onModeChanged
,
@required
this
.
onChanged
,
@required
this
.
use24HourDials
,
@required
this
.
helpText
,
})
:
assert
(
selectedTime
!=
null
),
assert
(
mode
!=
null
),
assert
(
orientation
!=
null
),
assert
(
use24HourDials
!=
null
);
final
_TimePickerFragmentContext
fragmentContext
;
final
TimeOfDay
selectedTime
;
final
_TimePickerMode
mode
;
final
Orientation
orientation
;
final
Orientation
orientation
;
final
ValueChanged
<
_TimePickerMode
>
onModeChanged
;
final
ValueChanged
<
TimeOfDay
>
onChanged
;
final
bool
use24HourDials
;
final
String
helpText
;
void
_togglePeriod
()
{
void
_handleChangeMode
(
_TimePickerMode
value
)
{
final
int
newHour
=
(
fragmentContext
.
selectedTime
.
hour
+
TimeOfDay
.
hoursPerPeriod
)
%
TimeOfDay
.
hoursPerDay
;
if
(
value
!=
mode
)
final
TimeOfDay
newTime
=
fragmentContext
.
selectedTime
.
replacing
(
hour:
newHour
);
onModeChanged
(
value
);
fragmentContext
.
onTimeChange
(
newTime
);
}
void
_setAm
(
BuildContext
context
)
{
if
(
fragmentContext
.
selectedTime
.
period
==
DayPeriod
.
am
)
{
return
;
}
switch
(
fragmentContext
.
targetPlatform
)
{
case
TargetPlatform
.
android
:
case
TargetPlatform
.
fuchsia
:
case
TargetPlatform
.
linux
:
case
TargetPlatform
.
windows
:
_announceToAccessibility
(
context
,
MaterialLocalizations
.
of
(
context
).
anteMeridiemAbbreviation
);
break
;
case
TargetPlatform
.
iOS
:
case
TargetPlatform
.
macOS
:
break
;
}
_togglePeriod
();
}
void
_setPm
(
BuildContext
context
)
{
if
(
fragmentContext
.
selectedTime
.
period
==
DayPeriod
.
pm
)
{
return
;
}
switch
(
fragmentContext
.
targetPlatform
)
{
case
TargetPlatform
.
android
:
case
TargetPlatform
.
fuchsia
:
case
TargetPlatform
.
linux
:
case
TargetPlatform
.
windows
:
_announceToAccessibility
(
context
,
MaterialLocalizations
.
of
(
context
).
postMeridiemAbbreviation
);
break
;
case
TargetPlatform
.
iOS
:
case
TargetPlatform
.
macOS
:
break
;
}
_togglePeriod
();
}
}
@override
@override
Widget
build
(
BuildContext
context
)
{
Widget
build
(
BuildContext
context
)
{
final
MaterialLocalizations
materialLocalizations
=
MaterialLocalizations
.
of
(
context
);
assert
(
debugCheckHasMediaQuery
(
context
));
final
TextTheme
headerTextTheme
=
fragmentContext
.
headerTextTheme
;
final
ThemeData
themeData
=
Theme
.
of
(
context
);
final
TimeOfDay
selectedTime
=
fragmentContext
.
selectedTime
;
final
TimeOfDayFormat
timeOfDayFormat
=
MaterialLocalizations
.
of
(
context
).
timeOfDayFormat
(
final
Color
activeColor
=
fragmentContext
.
activeColor
;
alwaysUse24HourFormat:
MediaQuery
.
of
(
context
).
alwaysUse24HourFormat
,
final
Color
inactiveColor
=
fragmentContext
.
inactiveColor
;
final
bool
amSelected
=
selectedTime
.
period
==
DayPeriod
.
am
;
final
TextStyle
amStyle
=
headerTextTheme
.
subtitle1
.
copyWith
(
color:
amSelected
?
activeColor:
inactiveColor
);
);
final
TextStyle
pmStyle
=
headerTextTheme
.
subtitle1
.
copyWith
(
color:
!
amSelected
?
activeColor:
inactiveColor
final
_TimePickerFragmentContext
fragmentContext
=
_TimePickerFragmentContext
(
selectedTime:
selectedTime
,
mode:
mode
,
onTimeChange:
onChanged
,
onModeChange:
_handleChangeMode
,
use24HourDials:
use24HourDials
,
);
);
final
bool
layoutPortrait
=
orientation
==
Orientation
.
portrait
;
final
double
buttonTextScaleFactor
=
math
.
min
(
MediaQuery
.
of
(
context
).
textScaleFactor
,
2.0
);
EdgeInsets
padding
;
double
width
;
Widget
controls
;
final
Widget
amButton
=
ConstrainedBox
(
switch
(
orientation
)
{
constraints:
_kMinTappableRegion
,
case
Orientation
.
portrait
:
child:
Material
(
// Keep width null because in portrait we don't cap the width.
type:
MaterialType
.
transparency
,
padding
=
const
EdgeInsets
.
symmetric
(
horizontal:
24.0
);
child:
InkWell
(
controls
=
Column
(
onTap:
Feedback
.
wrapForTap
(()
=>
_setAm
(
context
),
context
),
children:
<
Widget
>[
child:
Padding
(
const
SizedBox
(
height:
16.0
),
padding:
layoutPortrait
?
const
EdgeInsets
.
only
(
bottom:
2.0
)
:
const
EdgeInsets
.
only
(
right:
4.0
),
Container
(
child:
Align
(
height:
kMinInteractiveDimension
*
2
,
alignment:
layoutPortrait
?
Alignment
.
bottomCenter
:
Alignment
.
centerRight
,
child:
Row
(
widthFactor:
1
,
children:
<
Widget
>[
heightFactor:
1
,
if
(!
use24HourDials
&&
timeOfDayFormat
==
TimeOfDayFormat
.
a_space_h_colon_mm
)
...<
Widget
>[
child:
Semantics
(
_DayPeriodControl
(
selected:
amSelected
,
selectedTime:
selectedTime
,
child:
Text
(
orientation:
orientation
,
materialLocalizations
.
anteMeridiemAbbreviation
,
onChanged:
onChanged
,
style:
amStyle
,
),
textScaleFactor:
buttonTextScaleFactor
,
const
SizedBox
(
width:
12.0
),
),
],
Expanded
(
child:
_HourControl
(
fragmentContext:
fragmentContext
)),
_StringFragment
(
timeOfDayFormat:
timeOfDayFormat
),
Expanded
(
child:
_MinuteControl
(
fragmentContext:
fragmentContext
)),
if
(!
use24HourDials
&&
timeOfDayFormat
!=
TimeOfDayFormat
.
a_space_h_colon_mm
)
...<
Widget
>[
const
SizedBox
(
width:
12.0
),
_DayPeriodControl
(
selectedTime:
selectedTime
,
orientation:
orientation
,
onChanged:
onChanged
,
),
]
],
),
),
),
),
],
);
break
;
case
Orientation
.
landscape
:
width
=
_kTimePickerHeaderLandscapeWidth
;
padding
=
const
EdgeInsets
.
symmetric
(
horizontal:
24.0
);
controls
=
Expanded
(
child:
Column
(
mainAxisAlignment:
MainAxisAlignment
.
center
,
children:
<
Widget
>[
if
(!
use24HourDials
&&
timeOfDayFormat
==
TimeOfDayFormat
.
a_space_h_colon_mm
)
_DayPeriodControl
(
selectedTime:
selectedTime
,
orientation:
orientation
,
onChanged:
onChanged
,
),
Container
(
height:
kMinInteractiveDimension
*
2
,
child:
Row
(
children:
<
Widget
>[
Expanded
(
child:
_HourControl
(
fragmentContext:
fragmentContext
)),
_StringFragment
(
timeOfDayFormat:
timeOfDayFormat
),
Expanded
(
child:
_MinuteControl
(
fragmentContext:
fragmentContext
)),
],
),
),
if
(!
use24HourDials
&&
timeOfDayFormat
!=
TimeOfDayFormat
.
a_space_h_colon_mm
)
_DayPeriodControl
(
selectedTime:
selectedTime
,
orientation:
orientation
,
onChanged:
onChanged
,
),
],
),
),
),
);
break
;
}
return
Container
(
width:
width
,
padding:
padding
,
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
<
Widget
>[
const
SizedBox
(
height:
16.0
),
Text
(
// TODO(rami-a): localize 'SELECT TIME.'
helpText
??
'SELECT TIME'
,
style:
TimePickerTheme
.
of
(
context
).
helpTextStyle
??
themeData
.
textTheme
.
overline
,
),
controls
,
],
),
),
);
);
}
}
class
_HourMinuteControl
extends
StatelessWidget
{
const
_HourMinuteControl
({
@required
this
.
text
,
@required
this
.
onTap
,
@required
this
.
isSelected
,
})
:
assert
(
text
!=
null
),
assert
(
onTap
!=
null
),
assert
(
isSelected
!=
null
);
final
String
text
;
final
GestureTapCallback
onTap
;
final
bool
isSelected
;
final
Widget
pmButton
=
ConstrainedBox
(
@override
constraints:
_kMinTappableRegion
,
Widget
build
(
BuildContext
context
)
{
final
ThemeData
themeData
=
Theme
.
of
(
context
);
final
TimePickerThemeData
timePickerTheme
=
TimePickerTheme
.
of
(
context
);
final
bool
isDark
=
themeData
.
colorScheme
.
brightness
==
Brightness
.
dark
;
final
Color
textColor
=
timePickerTheme
.
hourMinuteTextColor
??
MaterialStateColor
.
resolveWith
((
Set
<
MaterialState
>
states
)
{
return
states
.
contains
(
MaterialState
.
selected
)
?
themeData
.
colorScheme
.
primary
:
themeData
.
colorScheme
.
onSurface
;
});
final
Color
backgroundColor
=
timePickerTheme
.
hourMinuteColor
??
MaterialStateColor
.
resolveWith
((
Set
<
MaterialState
>
states
)
{
return
states
.
contains
(
MaterialState
.
selected
)
?
themeData
.
colorScheme
.
primary
.
withOpacity
(
isDark
?
0.24
:
0.12
)
:
themeData
.
colorScheme
.
onSurface
.
withOpacity
(
0.12
);
});
final
TextStyle
style
=
timePickerTheme
.
hourMinuteTextStyle
??
themeData
.
textTheme
.
headline2
;
final
ShapeBorder
shape
=
timePickerTheme
.
hourMinuteShape
??
_kDefaultShape
;
final
Set
<
MaterialState
>
states
=
isSelected
?
<
MaterialState
>{
MaterialState
.
selected
}
:
<
MaterialState
>{};
return
Container
(
height:
_kTimePickerHeaderControlHeight
,
child:
Material
(
child:
Material
(
type:
MaterialType
.
transparency
,
color:
MaterialStateProperty
.
resolveAs
(
backgroundColor
,
states
),
textStyle:
pmStyle
,
clipBehavior:
Clip
.
antiAlias
,
shape:
shape
,
child:
InkWell
(
child:
InkWell
(
onTap:
Feedback
.
wrapForTap
(()
=>
_setPm
(
context
),
context
),
onTap:
onTap
,
child:
Padding
(
child:
Center
(
padding:
layoutPortrait
?
const
EdgeInsets
.
only
(
top:
2.0
)
:
const
EdgeInsets
.
only
(
left:
4.0
),
child:
Text
(
child:
Align
(
text
,
alignment:
orientation
==
Orientation
.
portrait
?
Alignment
.
topCenter
:
Alignment
.
centerLeft
,
style:
style
.
copyWith
(
color:
MaterialStateProperty
.
resolveAs
(
textColor
,
states
)),
widthFactor:
1
,
textScaleFactor:
1.0
,
heightFactor:
1
,
child:
Semantics
(
selected:
!
amSelected
,
child:
Text
(
materialLocalizations
.
postMeridiemAbbreviation
,
style:
pmStyle
,
textScaleFactor:
buttonTextScaleFactor
,
),
),
),
),
),
),
),
),
),
),
);
);
switch
(
orientation
)
{
case
Orientation
.
portrait
:
return
Column
(
mainAxisSize:
MainAxisSize
.
min
,
children:
<
Widget
>[
amButton
,
pmButton
,
],
);
case
Orientation
.
landscape
:
return
Row
(
mainAxisSize:
MainAxisSize
.
min
,
children:
<
Widget
>[
amButton
,
pmButton
,
],
);
}
return
null
;
}
}
}
}
/// Displays the hour fragment.
/// Displays the hour fragment.
///
///
/// When tapped changes time picker dial mode to [_TimePickerMode.hour].
/// When tapped changes time picker dial mode to [_TimePickerMode.hour].
...
@@ -346,9 +304,6 @@ class _HourControl extends StatelessWidget {
...
@@ -346,9 +304,6 @@ class _HourControl extends StatelessWidget {
assert
(
debugCheckHasMediaQuery
(
context
));
assert
(
debugCheckHasMediaQuery
(
context
));
final
bool
alwaysUse24HourFormat
=
MediaQuery
.
of
(
context
).
alwaysUse24HourFormat
;
final
bool
alwaysUse24HourFormat
=
MediaQuery
.
of
(
context
).
alwaysUse24HourFormat
;
final
MaterialLocalizations
localizations
=
MaterialLocalizations
.
of
(
context
);
final
MaterialLocalizations
localizations
=
MaterialLocalizations
.
of
(
context
);
final
TextStyle
hourStyle
=
fragmentContext
.
mode
==
_TimePickerMode
.
hour
?
fragmentContext
.
activeStyle
:
fragmentContext
.
inactiveStyle
;
final
String
formattedHour
=
localizations
.
formatHour
(
final
String
formattedHour
=
localizations
.
formatHour
(
fragmentContext
.
selectedTime
,
fragmentContext
.
selectedTime
,
alwaysUse24HourFormat:
alwaysUse24HourFormat
,
alwaysUse24HourFormat:
alwaysUse24HourFormat
,
...
@@ -393,20 +348,10 @@ class _HourControl extends StatelessWidget {
...
@@ -393,20 +348,10 @@ class _HourControl extends StatelessWidget {
onDecrease:
()
{
onDecrease:
()
{
fragmentContext
.
onTimeChange
(
previousHour
);
fragmentContext
.
onTimeChange
(
previousHour
);
},
},
child:
ConstrainedBox
(
child:
_HourMinuteControl
(
constraints:
_kMinTappableRegion
,
isSelected:
fragmentContext
.
mode
==
_TimePickerMode
.
hour
,
child:
Material
(
text:
formattedHour
,
type:
MaterialType
.
transparency
,
onTap:
Feedback
.
wrapForTap
(()
=>
fragmentContext
.
onModeChange
(
_TimePickerMode
.
hour
),
context
),
child:
InkWell
(
onTap:
Feedback
.
wrapForTap
(()
=>
fragmentContext
.
onModeChange
(
_TimePickerMode
.
hour
),
context
),
child:
Text
(
formattedHour
,
style:
hourStyle
,
textAlign:
TextAlign
.
end
,
textScaleFactor:
1.0
,
),
),
),
),
),
);
);
}
}
...
@@ -415,17 +360,44 @@ class _HourControl extends StatelessWidget {
...
@@ -415,17 +360,44 @@ class _HourControl extends StatelessWidget {
/// A passive fragment showing a string value.
/// A passive fragment showing a string value.
class
_StringFragment
extends
StatelessWidget
{
class
_StringFragment
extends
StatelessWidget
{
const
_StringFragment
({
const
_StringFragment
({
@required
this
.
fragmentContext
,
@required
this
.
timeOfDayFormat
,
@required
this
.
value
,
});
});
final
_TimePickerFragmentContext
fragmentContext
;
final
TimeOfDayFormat
timeOfDayFormat
;
final
String
value
;
String
_stringFragmentValue
(
TimeOfDayFormat
timeOfDayFormat
)
{
switch
(
timeOfDayFormat
)
{
case
TimeOfDayFormat
.
h_colon_mm_space_a
:
case
TimeOfDayFormat
.
a_space_h_colon_mm
:
case
TimeOfDayFormat
.
H_colon_mm
:
case
TimeOfDayFormat
.
HH_colon_mm
:
return
':'
;
case
TimeOfDayFormat
.
HH_dot_mm
:
return
'.'
;
case
TimeOfDayFormat
.
frenchCanadian
:
return
'h'
;
}
return
''
;
}
@override
@override
Widget
build
(
BuildContext
context
)
{
Widget
build
(
BuildContext
context
)
{
final
ThemeData
theme
=
Theme
.
of
(
context
);
final
TimePickerThemeData
timePickerTheme
=
TimePickerTheme
.
of
(
context
);
final
TextStyle
hourMinuteStyle
=
timePickerTheme
.
hourMinuteTextStyle
??
theme
.
textTheme
.
headline2
;
final
Color
textColor
=
timePickerTheme
.
hourMinuteTextColor
??
theme
.
colorScheme
.
onSurface
;
return
ExcludeSemantics
(
return
ExcludeSemantics
(
child:
Text
(
value
,
style:
fragmentContext
.
inactiveStyle
,
textScaleFactor:
1.0
),
child:
Padding
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
6.0
),
child:
Center
(
child:
Text
(
_stringFragmentValue
(
timeOfDayFormat
),
style:
hourMinuteStyle
.
apply
(
color:
MaterialStateProperty
.
resolveAs
(
textColor
,
<
MaterialState
>{})),
textScaleFactor:
1.0
,
),
),
),
);
);
}
}
}
}
...
@@ -443,9 +415,6 @@ class _MinuteControl extends StatelessWidget {
...
@@ -443,9 +415,6 @@ class _MinuteControl extends StatelessWidget {
@override
@override
Widget
build
(
BuildContext
context
)
{
Widget
build
(
BuildContext
context
)
{
final
MaterialLocalizations
localizations
=
MaterialLocalizations
.
of
(
context
);
final
MaterialLocalizations
localizations
=
MaterialLocalizations
.
of
(
context
);
final
TextStyle
minuteStyle
=
fragmentContext
.
mode
==
_TimePickerMode
.
minute
?
fragmentContext
.
activeStyle
:
fragmentContext
.
inactiveStyle
;
final
String
formattedMinute
=
localizations
.
formatMinute
(
fragmentContext
.
selectedTime
);
final
String
formattedMinute
=
localizations
.
formatMinute
(
fragmentContext
.
selectedTime
);
final
TimeOfDay
nextMinute
=
fragmentContext
.
selectedTime
.
replacing
(
final
TimeOfDay
nextMinute
=
fragmentContext
.
selectedTime
.
replacing
(
minute:
(
fragmentContext
.
selectedTime
.
minute
+
1
)
%
TimeOfDay
.
minutesPerHour
,
minute:
(
fragmentContext
.
selectedTime
.
minute
+
1
)
%
TimeOfDay
.
minutesPerHour
,
...
@@ -468,414 +437,337 @@ class _MinuteControl extends StatelessWidget {
...
@@ -468,414 +437,337 @@ class _MinuteControl extends StatelessWidget {
onDecrease:
()
{
onDecrease:
()
{
fragmentContext
.
onTimeChange
(
previousMinute
);
fragmentContext
.
onTimeChange
(
previousMinute
);
},
},
child:
ConstrainedBox
(
child:
_HourMinuteControl
(
constraints:
_kMinTappableRegion
,
isSelected:
fragmentContext
.
mode
==
_TimePickerMode
.
minute
,
child:
Material
(
text:
formattedMinute
,
type:
MaterialType
.
transparency
,
onTap:
Feedback
.
wrapForTap
(()
=>
fragmentContext
.
onModeChange
(
_TimePickerMode
.
minute
),
context
),
child:
InkWell
(
onTap:
Feedback
.
wrapForTap
(()
=>
fragmentContext
.
onModeChange
(
_TimePickerMode
.
minute
),
context
),
child:
Text
(
formattedMinute
,
style:
minuteStyle
,
textAlign:
TextAlign
.
start
,
textScaleFactor:
1.0
),
),
),
),
),
);
);
}
}
}
}
/// Provides time picker header layout configuration for the given
/// [timeOfDayFormat] passing [context] to each widget in the
/// configuration.
///
/// The [timeOfDayFormat] and [context] arguments must not be null.
_TimePickerHeaderFormat
_buildHeaderFormat
(
TimeOfDayFormat
timeOfDayFormat
,
_TimePickerFragmentContext
context
,
Orientation
orientation
,
)
{
// Creates an hour fragment.
_TimePickerHeaderFragment
hour
()
{
return
_TimePickerHeaderFragment
(
layoutId:
_TimePickerHeaderId
.
hour
,
widget:
_HourControl
(
fragmentContext:
context
),
);
}
// Creates a minute fragment.
/// Displays the am/pm fragment and provides controls for switching between am
_TimePickerHeaderFragment
minute
()
{
/// and pm.
return
_TimePickerHeaderFragment
(
class
_DayPeriodControl
extends
StatelessWidget
{
layoutId:
_TimePickerHeaderId
.
minute
,
const
_DayPeriodControl
({
widget:
_MinuteControl
(
fragmentContext:
context
),
@required
this
.
selectedTime
,
);
@required
this
.
onChanged
,
}
@required
this
.
orientation
,
});
// Creates a string fragment.
final
TimeOfDay
selectedTime
;
_TimePickerHeaderFragment
string
(
_TimePickerHeaderId
layoutId
,
String
value
)
{
final
Orientation
orientation
;
return
_TimePickerHeaderFragment
(
final
ValueChanged
<
TimeOfDay
>
onChanged
;
layoutId:
layoutId
,
widget:
_StringFragment
(
fragmentContext:
context
,
value:
value
,
),
);
}
// Creates an am/pm fragment.
void
_togglePeriod
()
{
_TimePickerHeaderFragment
dayPeriod
()
{
final
int
newHour
=
(
selectedTime
.
hour
+
TimeOfDay
.
hoursPerPeriod
)
%
TimeOfDay
.
hoursPerDay
;
return
_TimePickerHeaderFragment
(
final
TimeOfDay
newTime
=
selectedTime
.
replacing
(
hour:
newHour
);
layoutId:
_TimePickerHeaderId
.
period
,
onChanged
(
newTime
);
widget:
_DayPeriodControl
(
fragmentContext:
context
,
orientation:
orientation
),
);
}
}
// Convenience function for creating a time header format with up to two pieces.
void
_setAm
(
BuildContext
context
)
{
_TimePickerHeaderFormat
format
(
if
(
selectedTime
.
period
==
DayPeriod
.
am
)
{
_TimePickerHeaderPiece
piece1
,
[
return
;
_TimePickerHeaderPiece
piece2
,
}
])
{
switch
(
Theme
.
of
(
context
).
platform
)
{
final
List
<
_TimePickerHeaderPiece
>
pieces
=
<
_TimePickerHeaderPiece
>[];
case
TargetPlatform
.
android
:
switch
(
context
.
textDirection
)
{
case
TargetPlatform
.
fuchsia
:
case
TextDirection
.
ltr
:
case
TargetPlatform
.
linux
:
pieces
.
add
(
piece1
);
case
TargetPlatform
.
windows
:
if
(
piece2
!=
null
)
_announceToAccessibility
(
context
,
MaterialLocalizations
.
of
(
context
).
anteMeridiemAbbreviation
);
pieces
.
add
(
piece2
);
break
;
break
;
case
TextDirection
.
rtl
:
case
TargetPlatform
.
iOS
:
if
(
piece2
!=
null
)
case
TargetPlatform
.
macOS
:
pieces
.
add
(
piece2
);
pieces
.
add
(
piece1
);
break
;
break
;
}
}
int
centerpieceIndex
;
_togglePeriod
();
for
(
int
i
=
0
;
i
<
pieces
.
length
;
i
+=
1
)
{
}
if
(
pieces
[
i
].
pivotIndex
>=
0
)
{
centerpieceIndex
=
i
;
void
_setPm
(
BuildContext
context
)
{
}
if
(
selectedTime
.
period
==
DayPeriod
.
pm
)
{
return
;
}
}
assert
(
centerpieceIndex
!=
null
);
switch
(
Theme
.
of
(
context
).
platform
)
{
return
_TimePickerHeaderFormat
(
centerpieceIndex
,
pieces
);
case
TargetPlatform
.
android
:
}
case
TargetPlatform
.
fuchsia
:
case
TargetPlatform
.
linux
:
// Convenience function for creating a time header piece with up to three fragments.
case
TargetPlatform
.
windows
:
_TimePickerHeaderPiece
piece
({
_announceToAccessibility
(
context
,
MaterialLocalizations
.
of
(
context
).
postMeridiemAbbreviation
);
int
pivotIndex
=
-
1
,
break
;
double
bottomMargin
=
0.0
,
case
TargetPlatform
.
iOS
:
_TimePickerHeaderFragment
fragment1
,
case
TargetPlatform
.
macOS
:
_TimePickerHeaderFragment
fragment2
,
break
;
_TimePickerHeaderFragment
fragment3
,
}
})
{
_togglePeriod
();
final
List
<
_TimePickerHeaderFragment
>
fragments
=
<
_TimePickerHeaderFragment
>[
fragment1
,
if
(
fragment2
!=
null
)
...<
_TimePickerHeaderFragment
>[
fragment2
,
if
(
fragment3
!=
null
)
fragment3
,
],
];
return
_TimePickerHeaderPiece
(
pivotIndex
,
fragments
,
bottomMargin:
bottomMargin
);
}
}
switch
(
timeOfDayFormat
)
{
@override
case
TimeOfDayFormat
.
h_colon_mm_space_a
:
Widget
build
(
BuildContext
context
)
{
return
format
(
final
MaterialLocalizations
materialLocalizations
=
MaterialLocalizations
.
of
(
context
);
piece
(
final
ColorScheme
colorScheme
=
Theme
.
of
(
context
).
colorScheme
;
pivotIndex:
1
,
final
TimePickerThemeData
timePickerTheme
=
TimePickerTheme
.
of
(
context
);
fragment1:
hour
(),
final
bool
isDark
=
colorScheme
.
brightness
==
Brightness
.
dark
;
fragment2:
string
(
_TimePickerHeaderId
.
colon
,
':'
),
final
Color
textColor
=
timePickerTheme
.
dayPeriodTextColor
fragment3:
minute
(),
??
MaterialStateColor
.
resolveWith
((
Set
<
MaterialState
>
states
)
{
),
return
states
.
contains
(
MaterialState
.
selected
)
piece
(
?
colorScheme
.
primary
fragment1:
dayPeriod
(),
:
colorScheme
.
onSurface
.
withOpacity
(
0.60
);
),
});
);
final
Color
backgroundColor
=
timePickerTheme
.
dayPeriodColor
case
TimeOfDayFormat
.
H_colon_mm
:
??
MaterialStateColor
.
resolveWith
((
Set
<
MaterialState
>
states
)
{
return
format
(
piece
(
// The unselected day period should match the overall picker dialog
pivotIndex:
1
,
// color. Making it transparent enables that without being redundant
fragment1:
hour
(),
// and allows the optional elevation overlay for dark mode to be
fragment2:
string
(
_TimePickerHeaderId
.
colon
,
':'
),
// visible.
fragment3:
minute
(),
return
states
.
contains
(
MaterialState
.
selected
)
));
?
colorScheme
.
primary
.
withOpacity
(
isDark
?
0.24
:
0.12
)
case
TimeOfDayFormat
.
HH_dot_mm
:
:
Colors
.
transparent
;
return
format
(
piece
(
});
pivotIndex:
1
,
final
bool
amSelected
=
selectedTime
.
period
==
DayPeriod
.
am
;
fragment1:
hour
(),
final
Set
<
MaterialState
>
amStates
=
amSelected
?
<
MaterialState
>{
MaterialState
.
selected
}
:
<
MaterialState
>{};
fragment2:
string
(
_TimePickerHeaderId
.
dot
,
'.'
),
final
bool
pmSelected
=
!
amSelected
;
fragment3:
minute
(),
final
Set
<
MaterialState
>
pmStates
=
pmSelected
?
<
MaterialState
>{
MaterialState
.
selected
}
:
<
MaterialState
>{};
));
final
TextStyle
textStyle
=
timePickerTheme
.
dayPeriodTextStyle
??
Theme
.
of
(
context
).
textTheme
.
subtitle1
;
case
TimeOfDayFormat
.
a_space_h_colon_mm
:
final
TextStyle
amStyle
=
textStyle
.
copyWith
(
return
format
(
color:
MaterialStateProperty
.
resolveAs
(
textColor
,
amStates
),
piece
(
);
fragment1:
dayPeriod
(),
final
TextStyle
pmStyle
=
textStyle
.
copyWith
(
),
color:
MaterialStateProperty
.
resolveAs
(
textColor
,
pmStates
),
piece
(
);
pivotIndex:
1
,
OutlinedBorder
shape
=
timePickerTheme
.
dayPeriodShape
??
fragment1:
hour
(),
const
RoundedRectangleBorder
(
borderRadius:
_kDefaultBorderRadius
);
fragment2:
string
(
_TimePickerHeaderId
.
colon
,
':'
),
final
BorderSide
borderSide
=
timePickerTheme
.
dayPeriodBorderSide
??
BorderSide
(
fragment3:
minute
(),
color:
Color
.
alphaBlend
(
colorScheme
.
onBackground
.
withOpacity
(
0.38
),
colorScheme
.
surface
),
),
);
);
// Apply the custom borderSide.
case
TimeOfDayFormat
.
frenchCanadian
:
shape
=
shape
.
copyWith
(
return
format
(
piece
(
side:
borderSide
,
pivotIndex:
1
,
);
fragment1:
hour
(),
fragment2:
string
(
_TimePickerHeaderId
.
hString
,
'h'
),
fragment3:
minute
(),
));
case
TimeOfDayFormat
.
HH_colon_mm
:
return
format
(
piece
(
pivotIndex:
1
,
fragment1:
hour
(),
fragment2:
string
(
_TimePickerHeaderId
.
colon
,
':'
),
fragment3:
minute
(),
));
}
return
null
;
}
class
_TimePickerHeaderLayout
extends
MultiChildLayoutDelegate
{
final
double
buttonTextScaleFactor
=
math
.
min
(
MediaQuery
.
of
(
context
).
textScaleFactor
,
2.0
);
_TimePickerHeaderLayout
(
this
.
orientation
,
this
.
format
)
:
assert
(
orientation
!=
null
),
assert
(
format
!=
null
);
final
Orientation
orientation
;
final
Widget
amButton
=
Material
(
final
_TimePickerHeaderFormat
format
;
color:
MaterialStateProperty
.
resolveAs
(
backgroundColor
,
amStates
),
child:
InkWell
(
onTap:
Feedback
.
wrapForTap
(()
=>
_setAm
(
context
),
context
),
child:
Semantics
(
selected:
amSelected
,
child:
Center
(
child:
Text
(
materialLocalizations
.
anteMeridiemAbbreviation
,
style:
amStyle
,
textScaleFactor:
buttonTextScaleFactor
,
),
),
),
),
);
@override
final
Widget
pmButton
=
Material
(
void
performLayout
(
Size
size
)
{
color:
MaterialStateProperty
.
resolveAs
(
backgroundColor
,
pmStates
),
final
BoxConstraints
constraints
=
BoxConstraints
.
loose
(
size
);
child:
InkWell
(
onTap:
Feedback
.
wrapForTap
(()
=>
_setPm
(
context
),
context
),
child:
Semantics
(
selected:
pmSelected
,
child:
Center
(
child:
Text
(
materialLocalizations
.
postMeridiemAbbreviation
,
style:
pmStyle
,
textScaleFactor:
buttonTextScaleFactor
,
),
),
),
),
);
Widget
result
;
switch
(
orientation
)
{
switch
(
orientation
)
{
case
Orientation
.
portrait
:
case
Orientation
.
portrait
:
_layoutHorizontally
(
size
,
constraints
);
const
double
width
=
52.0
;
result
=
_DayPeriodInputPadding
(
minSize:
const
Size
(
width
,
kMinInteractiveDimension
*
2
),
orientation:
orientation
,
child:
Container
(
width:
width
,
height:
_kTimePickerHeaderControlHeight
,
child:
Material
(
clipBehavior:
Clip
.
antiAlias
,
color:
Colors
.
transparent
,
shape:
shape
,
child:
Column
(
children:
<
Widget
>[
Expanded
(
child:
amButton
),
Container
(
decoration:
BoxDecoration
(
border:
Border
(
top:
borderSide
),
),
height:
1
,
),
Expanded
(
child:
pmButton
),
],
),
),
),
);
break
;
break
;
case
Orientation
.
landscape
:
case
Orientation
.
landscape
:
_layoutVertically
(
size
,
constraints
);
result
=
_DayPeriodInputPadding
(
minSize:
const
Size
(
0.0
,
kMinInteractiveDimension
),
orientation:
orientation
,
child:
Container
(
height:
40.0
,
child:
Material
(
clipBehavior:
Clip
.
antiAlias
,
color:
Colors
.
transparent
,
shape:
shape
,
child:
Row
(
children:
<
Widget
>[
Expanded
(
child:
amButton
),
Container
(
decoration:
BoxDecoration
(
border:
Border
(
left:
borderSide
),
),
width:
1
,
),
Expanded
(
child:
pmButton
),
],
),
),
),
);
break
;
break
;
}
}
return
result
;
}
}
}
void
_layoutHorizontally
(
Size
size
,
BoxConstraints
constraints
)
{
/// A widget to pad the area around the [_DayPeriodControl]'s inner [Material].
final
List
<
_TimePickerHeaderFragment
>
fragmentsFlattened
=
<
_TimePickerHeaderFragment
>[];
class
_DayPeriodInputPadding
extends
SingleChildRenderObjectWidget
{
final
Map
<
_TimePickerHeaderId
,
Size
>
childSizes
=
<
_TimePickerHeaderId
,
Size
>{};
const
_DayPeriodInputPadding
({
int
pivotIndex
=
0
;
Key
key
,
for
(
int
pieceIndex
=
0
;
pieceIndex
<
format
.
pieces
.
length
;
pieceIndex
+=
1
)
{
Widget
child
,
final
_TimePickerHeaderPiece
piece
=
format
.
pieces
[
pieceIndex
];
this
.
minSize
,
for
(
final
_TimePickerHeaderFragment
fragment
in
piece
.
fragments
)
{
this
.
orientation
,
childSizes
[
fragment
.
layoutId
]
=
layoutChild
(
fragment
.
layoutId
,
constraints
);
})
:
super
(
key:
key
,
child:
child
);
fragmentsFlattened
.
add
(
fragment
);
}
if
(
pieceIndex
==
format
.
centerpieceIndex
)
final
Size
minSize
;
pivotIndex
+=
format
.
pieces
[
format
.
centerpieceIndex
].
pivotIndex
;
final
Orientation
orientation
;
else
if
(
pieceIndex
<
format
.
centerpieceIndex
)
pivotIndex
+=
piece
.
fragments
.
length
;
}
_positionPivoted
(
size
.
width
,
size
.
height
/
2.0
,
childSizes
,
fragmentsFlattened
,
pivotIndex
);
@override
RenderObject
createRenderObject
(
BuildContext
context
)
{
return
_RenderInputPadding
(
minSize
,
orientation
);
}
}
void
_layoutVertically
(
Size
size
,
BoxConstraints
constraints
)
{
@override
final
Map
<
_TimePickerHeaderId
,
Size
>
childSizes
=
<
_TimePickerHeaderId
,
Size
>{};
void
updateRenderObject
(
BuildContext
context
,
covariant
_RenderInputPadding
renderObject
)
{
final
List
<
double
>
pieceHeights
=
<
double
>[];
renderObject
.
minSize
=
minSize
;
double
height
=
0.0
;
}
double
margin
=
0.0
;
}
for
(
final
_TimePickerHeaderPiece
piece
in
format
.
pieces
)
{
double
pieceHeight
=
0.0
;
for
(
final
_TimePickerHeaderFragment
fragment
in
piece
.
fragments
)
{
final
Size
childSize
=
childSizes
[
fragment
.
layoutId
]
=
layoutChild
(
fragment
.
layoutId
,
constraints
);
pieceHeight
=
math
.
max
(
pieceHeight
,
childSize
.
height
);
}
pieceHeights
.
add
(
pieceHeight
);
height
+=
pieceHeight
+
margin
;
// Delay application of margin until next piece because margin of the
// bottom-most piece should not contribute to the size.
margin
=
piece
.
bottomMargin
;
}
final
_TimePickerHeaderPiece
centerpiece
=
format
.
pieces
[
format
.
centerpieceIndex
];
class
_RenderInputPadding
extends
RenderShiftedBox
{
double
y
=
(
size
.
height
-
height
)
/
2.0
;
_RenderInputPadding
(
this
.
_minSize
,
this
.
orientation
,
[
RenderBox
child
])
:
super
(
child
);
for
(
int
pieceIndex
=
0
;
pieceIndex
<
format
.
pieces
.
length
;
pieceIndex
+=
1
)
{
final
double
pieceVerticalCenter
=
y
+
pieceHeights
[
pieceIndex
]
/
2.0
;
if
(
pieceIndex
!=
format
.
centerpieceIndex
)
_positionPiece
(
size
.
width
,
pieceVerticalCenter
,
childSizes
,
format
.
pieces
[
pieceIndex
].
fragments
);
else
_positionPivoted
(
size
.
width
,
pieceVerticalCenter
,
childSizes
,
centerpiece
.
fragments
,
centerpiece
.
pivotIndex
);
y
+=
pieceHeights
[
pieceIndex
]
+
format
.
pieces
[
pieceIndex
].
bottomMargin
;
final
Orientation
orientation
;
}
Size
get
minSize
=>
_minSize
;
Size
_minSize
;
set
minSize
(
Size
value
)
{
if
(
_minSize
==
value
)
return
;
_minSize
=
value
;
markNeedsLayout
();
}
}
void
_positionPivoted
(
double
width
,
double
y
,
Map
<
_TimePickerHeaderId
,
Size
>
childSizes
,
List
<
_TimePickerHeaderFragment
>
fragments
,
int
pivotIndex
)
{
@override
double
tailWidth
=
childSizes
[
fragments
[
pivotIndex
].
layoutId
].
width
/
2.0
;
double
computeMinIntrinsicWidth
(
double
height
)
{
for
(
final
_TimePickerHeaderFragment
fragment
in
fragments
.
skip
(
pivotIndex
+
1
)
)
{
if
(
child
!=
null
)
{
tailWidth
+=
childSizes
[
fragment
.
layoutId
].
width
+
fragment
.
startMargin
;
return
math
.
max
(
child
.
getMinIntrinsicWidth
(
height
),
minSize
.
width
)
;
}
}
return
0.0
;
}
double
x
=
width
/
2.0
+
tailWidth
;
@override
x
=
math
.
min
(
x
,
width
);
double
computeMinIntrinsicHeight
(
double
width
)
{
for
(
int
i
=
fragments
.
length
-
1
;
i
>=
0
;
i
-=
1
)
{
if
(
child
!=
null
)
{
final
_TimePickerHeaderFragment
fragment
=
fragments
[
i
];
return
math
.
max
(
child
.
getMinIntrinsicHeight
(
width
),
minSize
.
height
);
final
Size
childSize
=
childSizes
[
fragment
.
layoutId
];
x
-=
childSize
.
width
;
positionChild
(
fragment
.
layoutId
,
Offset
(
x
,
y
-
childSize
.
height
/
2.0
));
x
-=
fragment
.
startMargin
;
}
}
return
0.0
;
}
}
void
_positionPiece
(
double
width
,
double
centeredAroundY
,
Map
<
_TimePickerHeaderId
,
Size
>
childSizes
,
List
<
_TimePickerHeaderFragment
>
fragments
)
{
@override
double
pieceWidth
=
0.0
;
double
computeMaxIntrinsicWidth
(
double
height
)
{
double
nextMargin
=
0.0
;
if
(
child
!=
null
)
{
for
(
final
_TimePickerHeaderFragment
fragment
in
fragments
)
{
return
math
.
max
(
child
.
getMaxIntrinsicWidth
(
height
),
minSize
.
width
);
final
Size
childSize
=
childSizes
[
fragment
.
layoutId
];
pieceWidth
+=
childSize
.
width
+
nextMargin
;
// Delay application of margin until next element because margin of the
// left-most fragment should not contribute to the size.
nextMargin
=
fragment
.
startMargin
;
}
double
x
=
(
width
+
pieceWidth
)
/
2.0
;
for
(
int
i
=
fragments
.
length
-
1
;
i
>=
0
;
i
-=
1
)
{
final
_TimePickerHeaderFragment
fragment
=
fragments
[
i
];
final
Size
childSize
=
childSizes
[
fragment
.
layoutId
];
x
-=
childSize
.
width
;
positionChild
(
fragment
.
layoutId
,
Offset
(
x
,
centeredAroundY
-
childSize
.
height
/
2.0
));
x
-=
fragment
.
startMargin
;
}
}
return
0.0
;
}
}
@override
@override
bool
shouldRelayout
(
_TimePickerHeaderLayout
oldDelegate
)
=>
orientation
!=
oldDelegate
.
orientation
||
format
!=
oldDelegate
.
format
;
double
computeMaxIntrinsicHeight
(
double
width
)
{
}
if
(
child
!=
null
)
{
return
math
.
max
(
child
.
getMaxIntrinsicHeight
(
width
),
minSize
.
height
);
class
_TimePickerHeader
extends
StatelessWidget
{
}
const
_TimePickerHeader
({
return
0.0
;
@required
this
.
selectedTime
,
@required
this
.
mode
,
@required
this
.
orientation
,
@required
this
.
onModeChanged
,
@required
this
.
onChanged
,
@required
this
.
use24HourDials
,
})
:
assert
(
selectedTime
!=
null
),
assert
(
mode
!=
null
),
assert
(
orientation
!=
null
),
assert
(
use24HourDials
!=
null
);
final
TimeOfDay
selectedTime
;
final
_TimePickerMode
mode
;
final
Orientation
orientation
;
final
ValueChanged
<
_TimePickerMode
>
onModeChanged
;
final
ValueChanged
<
TimeOfDay
>
onChanged
;
final
bool
use24HourDials
;
void
_handleChangeMode
(
_TimePickerMode
value
)
{
if
(
value
!=
mode
)
onModeChanged
(
value
);
}
}
TextStyle
_getBaseHeaderStyle
(
TextTheme
headerTextTheme
)
{
@override
// These font sizes aren't listed in the spec explicitly. I worked them out
void
performLayout
()
{
// by measuring the text using a screen ruler and comparing them to the
if
(
child
!=
null
)
{
// screen shots of the time picker in the spec.
child
.
layout
(
constraints
,
parentUsesSize:
true
);
assert
(
orientation
!=
null
);
final
double
width
=
math
.
max
(
child
.
size
.
width
,
minSize
.
width
);
switch
(
orientation
)
{
final
double
height
=
math
.
max
(
child
.
size
.
height
,
minSize
.
height
);
case
Orientation
.
portrait
:
size
=
constraints
.
constrain
(
Size
(
width
,
height
));
return
headerTextTheme
.
headline2
.
copyWith
(
fontSize:
60.0
);
final
BoxParentData
childParentData
=
child
.
parentData
as
BoxParentData
;
case
Orientation
.
landscape
:
childParentData
.
offset
=
Alignment
.
center
.
alongOffset
(
size
-
child
.
size
as
Offset
);
return
headerTextTheme
.
headline3
.
copyWith
(
fontSize:
50.0
);
}
else
{
size
=
Size
.
zero
;
}
}
return
null
;
}
}
@override
@override
Widget
build
(
BuildContext
context
)
{
bool
hitTest
(
BoxHitTestResult
result
,
{
Offset
position
})
{
assert
(
debugCheckHasMediaQuery
(
context
));
if
(
super
.
hitTest
(
result
,
position:
position
))
{
final
ThemeData
themeData
=
Theme
.
of
(
context
);
return
true
;
final
MediaQueryData
media
=
MediaQuery
.
of
(
context
);
}
final
TimeOfDayFormat
timeOfDayFormat
=
MaterialLocalizations
.
of
(
context
)
.
timeOfDayFormat
(
alwaysUse24HourFormat:
media
.
alwaysUse24HourFormat
);
EdgeInsets
padding
;
if
(
position
.
dx
<
0.0
||
double
height
;
position
.
dx
>
math
.
max
(
child
.
size
.
width
,
minSize
.
width
)
||
double
width
;
position
.
dy
<
0.0
||
position
.
dy
>
math
.
max
(
child
.
size
.
height
,
minSize
.
height
))
{
return
false
;
}
assert
(
orientation
!=
null
);
Offset
newPosition
=
child
.
size
.
center
(
Offset
.
zero
);
switch
(
orientation
)
{
switch
(
orientation
)
{
case
Orientation
.
portrait
:
case
Orientation
.
portrait
:
height
=
_kTimePickerHeaderPortraitHeight
;
if
(
position
.
dy
>
newPosition
.
dy
)
{
padding
=
const
EdgeInsets
.
symmetric
(
horizontal:
24.0
);
newPosition
+=
const
Offset
(
0.0
,
1.0
);
}
else
{
newPosition
+=
const
Offset
(
0.0
,
-
1.0
);
}
break
;
break
;
case
Orientation
.
landscape
:
case
Orientation
.
landscape
:
width
=
_kTimePickerHeaderLandscapeWidth
;
if
(
position
.
dx
>
newPosition
.
dx
)
{
padding
=
const
EdgeInsets
.
symmetric
(
horizontal:
16.0
);
newPosition
+=
const
Offset
(
1.0
,
0.0
);
}
else
{
newPosition
+=
const
Offset
(-
1.0
,
0.0
);
}
break
;
break
;
}
}
Color
backgroundColor
;
switch
(
themeData
.
brightness
)
{
case
Brightness
.
light
:
backgroundColor
=
themeData
.
primaryColor
;
break
;
case
Brightness
.
dark
:
backgroundColor
=
themeData
.
backgroundColor
;
break
;
}
Color
activeColor
;
return
result
.
addWithRawTransform
(
Color
inactiveColor
;
transform:
MatrixUtils
.
forceToPoint
(
newPosition
),
switch
(
themeData
.
primaryColorBrightness
)
{
position:
newPosition
,
case
Brightness
.
light
:
hitTest:
(
BoxHitTestResult
result
,
Offset
position
)
{
activeColor
=
Colors
.
black87
;
assert
(
position
==
newPosition
);
inactiveColor
=
Colors
.
black54
;
return
child
.
hitTest
(
result
,
position:
newPosition
);
break
;
},
case
Brightness
.
dark
:
activeColor
=
Colors
.
white
;
inactiveColor
=
Colors
.
white70
;
break
;
}
final
TextTheme
headerTextTheme
=
themeData
.
primaryTextTheme
;
final
TextStyle
baseHeaderStyle
=
_getBaseHeaderStyle
(
headerTextTheme
);
final
_TimePickerFragmentContext
fragmentContext
=
_TimePickerFragmentContext
(
headerTextTheme:
headerTextTheme
,
textDirection:
Directionality
.
of
(
context
),
selectedTime:
selectedTime
,
mode:
mode
,
activeColor:
activeColor
,
activeStyle:
baseHeaderStyle
.
copyWith
(
color:
activeColor
),
inactiveColor:
inactiveColor
,
inactiveStyle:
baseHeaderStyle
.
copyWith
(
color:
inactiveColor
),
onTimeChange:
onChanged
,
onModeChange:
_handleChangeMode
,
targetPlatform:
themeData
.
platform
,
use24HourDials:
use24HourDials
,
);
final
_TimePickerHeaderFormat
format
=
_buildHeaderFormat
(
timeOfDayFormat
,
fragmentContext
,
orientation
);
return
Container
(
width:
width
,
height:
height
,
padding:
padding
,
color:
backgroundColor
,
child:
CustomMultiChildLayout
(
delegate:
_TimePickerHeaderLayout
(
orientation
,
format
),
children:
format
.
pieces
.
expand
<
_TimePickerHeaderFragment
>((
_TimePickerHeaderPiece
piece
)
=>
piece
.
fragments
)
.
map
<
Widget
>((
_TimePickerHeaderFragment
fragment
)
{
return
LayoutId
(
id:
fragment
.
layoutId
,
child:
fragment
.
widget
,
);
})
.
toList
(),
),
);
);
}
}
}
}
enum
_DialRing
{
outer
,
inner
,
}
class
_TappableLabel
{
class
_TappableLabel
{
_TappableLabel
({
_TappableLabel
({
@required
this
.
value
,
@required
this
.
value
,
...
@@ -895,29 +787,27 @@ class _TappableLabel {
...
@@ -895,29 +787,27 @@ class _TappableLabel {
class
_DialPainter
extends
CustomPainter
{
class
_DialPainter
extends
CustomPainter
{
_DialPainter
({
_DialPainter
({
@required
this
.
primaryOuterLabels
,
@required
this
.
primaryLabels
,
@required
this
.
primaryInnerLabels
,
@required
this
.
secondaryLabels
,
@required
this
.
secondaryOuterLabels
,
@required
this
.
secondaryInnerLabels
,
@required
this
.
backgroundColor
,
@required
this
.
backgroundColor
,
@required
this
.
accentColor
,
@required
this
.
accentColor
,
@required
this
.
dotColor
,
@required
this
.
theta
,
@required
this
.
theta
,
@required
this
.
activeRing
,
@required
this
.
textDirection
,
@required
this
.
textDirection
,
@required
this
.
selectedValue
,
@required
this
.
selectedValue
,
})
:
super
(
repaint:
PaintingBinding
.
instance
.
systemFonts
);
})
:
super
(
repaint:
PaintingBinding
.
instance
.
systemFonts
);
final
List
<
_TappableLabel
>
primaryOuterLabels
;
final
List
<
_TappableLabel
>
primaryLabels
;
final
List
<
_TappableLabel
>
primaryInnerLabels
;
final
List
<
_TappableLabel
>
secondaryLabels
;
final
List
<
_TappableLabel
>
secondaryOuterLabels
;
final
List
<
_TappableLabel
>
secondaryInnerLabels
;
final
Color
backgroundColor
;
final
Color
backgroundColor
;
final
Color
accentColor
;
final
Color
accentColor
;
final
Color
dotColor
;
final
double
theta
;
final
double
theta
;
final
_DialRing
activeRing
;
final
TextDirection
textDirection
;
final
TextDirection
textDirection
;
final
int
selectedValue
;
final
int
selectedValue
;
static
const
double
_labelPadding
=
28.0
;
@override
@override
void
paint
(
Canvas
canvas
,
Size
size
)
{
void
paint
(
Canvas
canvas
,
Size
size
)
{
final
double
radius
=
size
.
shortestSide
/
2.0
;
final
double
radius
=
size
.
shortestSide
/
2.0
;
...
@@ -925,147 +815,62 @@ class _DialPainter extends CustomPainter {
...
@@ -925,147 +815,62 @@ class _DialPainter extends CustomPainter {
final
Offset
centerPoint
=
center
;
final
Offset
centerPoint
=
center
;
canvas
.
drawCircle
(
centerPoint
,
radius
,
Paint
()..
color
=
backgroundColor
);
canvas
.
drawCircle
(
centerPoint
,
radius
,
Paint
()..
color
=
backgroundColor
);
const
double
labelPadding
=
24.0
;
final
double
labelRadius
=
radius
-
_labelPadding
;
final
double
outerLabelRadius
=
radius
-
labelPadding
;
Offset
getOffsetForTheta
(
double
theta
)
{
final
double
innerLabelRadius
=
radius
-
labelPadding
*
2.5
;
return
center
+
Offset
(
labelRadius
*
math
.
cos
(
theta
),
-
labelRadius
*
math
.
sin
(
theta
));
Offset
getOffsetForTheta
(
double
theta
,
_DialRing
ring
)
{
double
labelRadius
;
switch
(
ring
)
{
case
_DialRing
.
outer
:
labelRadius
=
outerLabelRadius
;
break
;
case
_DialRing
.
inner
:
labelRadius
=
innerLabelRadius
;
break
;
}
return
center
+
Offset
(
labelRadius
*
math
.
cos
(
theta
),
-
labelRadius
*
math
.
sin
(
theta
));
}
}
void
paintLabels
(
List
<
_TappableLabel
>
labels
,
_DialRing
ring
)
{
void
paintLabels
(
List
<
_TappableLabel
>
labels
)
{
if
(
labels
==
null
)
if
(
labels
==
null
)
return
;
return
;
final
double
labelThetaIncrement
=
-
_kTwoPi
/
labels
.
length
;
final
double
labelThetaIncrement
=
-
_kTwoPi
/
labels
.
length
;
double
labelTheta
=
math
.
pi
/
2.0
;
double
labelTheta
=
math
.
pi
/
2.0
;
for
(
final
_TappableLabel
label
in
labels
)
{
for
(
final
_TappableLabel
label
in
labels
)
{
final
TextPainter
labelPainter
=
label
.
painter
;
final
TextPainter
labelPainter
=
label
.
painter
;
final
Offset
labelOffset
=
Offset
(-
labelPainter
.
width
/
2.0
,
-
labelPainter
.
height
/
2.0
);
final
Offset
labelOffset
=
Offset
(-
labelPainter
.
width
/
2.0
,
-
labelPainter
.
height
/
2.0
);
labelPainter
.
paint
(
canvas
,
getOffsetForTheta
(
labelTheta
,
ring
)
+
labelOffset
);
labelPainter
.
paint
(
canvas
,
getOffsetForTheta
(
labelTheta
)
+
labelOffset
);
labelTheta
+=
labelThetaIncrement
;
}
}
paintLabels
(
primaryOuterLabels
,
_DialRing
.
outer
);
paintLabels
(
primaryInnerLabels
,
_DialRing
.
inner
);
final
Paint
selectorPaint
=
Paint
()
..
color
=
accentColor
;
final
Offset
focusedPoint
=
getOffsetForTheta
(
theta
,
activeRing
);
const
double
focusedRadius
=
labelPadding
-
4.0
;
canvas
.
drawCircle
(
centerPoint
,
4.0
,
selectorPaint
);
canvas
.
drawCircle
(
focusedPoint
,
focusedRadius
,
selectorPaint
);
selectorPaint
.
strokeWidth
=
2.0
;
canvas
.
drawLine
(
centerPoint
,
focusedPoint
,
selectorPaint
);
final
Rect
focusedRect
=
Rect
.
fromCircle
(
center:
focusedPoint
,
radius:
focusedRadius
,
);
canvas
..
save
()
..
clipPath
(
Path
()..
addOval
(
focusedRect
));
paintLabels
(
secondaryOuterLabels
,
_DialRing
.
outer
);
paintLabels
(
secondaryInnerLabels
,
_DialRing
.
inner
);
canvas
.
restore
();
}
static
const
double
_semanticNodeSizeScale
=
1.5
;
@override
SemanticsBuilderCallback
get
semanticsBuilder
=>
_buildSemantics
;
/// Creates semantics nodes for the hour/minute labels painted on the dial.
///
/// The nodes are positioned on top of the text and their size is
/// [_semanticNodeSizeScale] bigger than those of the text boxes to provide
/// bigger tap area.
List
<
CustomPainterSemantics
>
_buildSemantics
(
Size
size
)
{
final
double
radius
=
size
.
shortestSide
/
2.0
;
final
Offset
center
=
Offset
(
size
.
width
/
2.0
,
size
.
height
/
2.0
);
const
double
labelPadding
=
24.0
;
final
double
outerLabelRadius
=
radius
-
labelPadding
;
final
double
innerLabelRadius
=
radius
-
labelPadding
*
2.5
;
Offset
getOffsetForTheta
(
double
theta
,
_DialRing
ring
)
{
double
labelRadius
;
switch
(
ring
)
{
case
_DialRing
.
outer
:
labelRadius
=
outerLabelRadius
;
break
;
case
_DialRing
.
inner
:
labelRadius
=
innerLabelRadius
;
break
;
}
return
center
+
Offset
(
labelRadius
*
math
.
cos
(
theta
),
-
labelRadius
*
math
.
sin
(
theta
));
}
final
List
<
CustomPainterSemantics
>
nodes
=
<
CustomPainterSemantics
>[];
void
paintLabels
(
List
<
_TappableLabel
>
labels
,
_DialRing
ring
)
{
if
(
labels
==
null
)
return
;
final
double
labelThetaIncrement
=
-
_kTwoPi
/
labels
.
length
;
final
double
ordinalOffset
=
ring
==
_DialRing
.
inner
?
12.0
:
0.0
;
double
labelTheta
=
math
.
pi
/
2.0
;
for
(
int
i
=
0
;
i
<
labels
.
length
;
i
++)
{
final
_TappableLabel
label
=
labels
[
i
];
final
TextPainter
labelPainter
=
label
.
painter
;
final
double
width
=
labelPainter
.
width
*
_semanticNodeSizeScale
;
final
double
height
=
labelPainter
.
height
*
_semanticNodeSizeScale
;
final
Offset
nodeOffset
=
getOffsetForTheta
(
labelTheta
,
ring
)
+
Offset
(-
width
/
2.0
,
-
height
/
2.0
);
final
TextSpan
textSpan
=
labelPainter
.
text
as
TextSpan
;
final
CustomPainterSemantics
node
=
CustomPainterSemantics
(
rect:
Rect
.
fromLTRB
(
nodeOffset
.
dx
-
24.0
+
width
/
2
,
nodeOffset
.
dy
-
24.0
+
height
/
2
,
nodeOffset
.
dx
+
24.0
+
width
/
2
,
nodeOffset
.
dy
+
24.0
+
height
/
2
,
),
properties:
SemanticsProperties
(
sortKey:
OrdinalSortKey
(
i
.
toDouble
()
+
ordinalOffset
),
selected:
label
.
value
==
selectedValue
,
value:
textSpan
?.
text
,
textDirection:
textDirection
,
onTap:
label
.
onTap
,
),
tags:
const
<
SemanticsTag
>{
// Used by tests to find this node.
SemanticsTag
(
'dial-label'
),
},
);
nodes
.
add
(
node
);
labelTheta
+=
labelThetaIncrement
;
labelTheta
+=
labelThetaIncrement
;
}
}
}
}
paintLabels
(
primaryOuterLabels
,
_DialRing
.
outer
);
paintLabels
(
primaryLabels
);
paintLabels
(
primaryInnerLabels
,
_DialRing
.
inner
);
final
Paint
selectorPaint
=
Paint
()
..
color
=
accentColor
;
final
Offset
focusedPoint
=
getOffsetForTheta
(
theta
);
const
double
focusedRadius
=
_labelPadding
-
4.0
;
canvas
.
drawCircle
(
centerPoint
,
4.0
,
selectorPaint
);
canvas
.
drawCircle
(
focusedPoint
,
focusedRadius
,
selectorPaint
);
selectorPaint
.
strokeWidth
=
2.0
;
canvas
.
drawLine
(
centerPoint
,
focusedPoint
,
selectorPaint
);
// Add a dot inside the selector but only when it isn't over the labels.
// This checks that the selector's theta is between two labels. A remainder
// between 0.1 and 0.45 indicates that the selector is roughly not above any
// labels. The values were derived by manually testing the dial.
final
double
labelThetaIncrement
=
-
_kTwoPi
/
primaryLabels
.
length
;
if
(
theta
%
labelThetaIncrement
>
0.1
&&
theta
%
labelThetaIncrement
<
0.45
)
{
canvas
.
drawCircle
(
focusedPoint
,
2.0
,
selectorPaint
..
color
=
dotColor
);
}
return
nodes
;
final
Rect
focusedRect
=
Rect
.
fromCircle
(
center:
focusedPoint
,
radius:
focusedRadius
,
);
canvas
..
save
()
..
clipPath
(
Path
()..
addOval
(
focusedRect
));
paintLabels
(
secondaryLabels
);
canvas
.
restore
();
}
}
@override
@override
bool
shouldRepaint
(
_DialPainter
oldPainter
)
{
bool
shouldRepaint
(
_DialPainter
oldPainter
)
{
return
oldPainter
.
primaryOuterLabels
!=
primaryOuterLabels
return
oldPainter
.
primaryLabels
!=
primaryLabels
||
oldPainter
.
primaryInnerLabels
!=
primaryInnerLabels
||
oldPainter
.
secondaryLabels
!=
secondaryLabels
||
oldPainter
.
secondaryOuterLabels
!=
secondaryOuterLabels
||
oldPainter
.
secondaryInnerLabels
!=
secondaryInnerLabels
||
oldPainter
.
backgroundColor
!=
backgroundColor
||
oldPainter
.
backgroundColor
!=
backgroundColor
||
oldPainter
.
accentColor
!=
accentColor
||
oldPainter
.
accentColor
!=
accentColor
||
oldPainter
.
theta
!=
theta
||
oldPainter
.
theta
!=
theta
;
||
oldPainter
.
activeRing
!=
activeRing
;
}
}
}
}
...
@@ -1094,14 +899,13 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin {
...
@@ -1094,14 +899,13 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin {
@override
@override
void
initState
()
{
void
initState
()
{
super
.
initState
();
super
.
initState
();
_updateDialRingFromWidget
();
_thetaController
=
AnimationController
(
_thetaController
=
AnimationController
(
duration:
_kDialAnimateDuration
,
duration:
_kDialAnimateDuration
,
vsync:
this
,
vsync:
this
,
);
);
_thetaTween
=
Tween
<
double
>(
begin:
_getThetaForTime
(
widget
.
selectedTime
));
_thetaTween
=
Tween
<
double
>(
begin:
_getThetaForTime
(
widget
.
selectedTime
));
_theta
=
_thetaController
_theta
=
_thetaController
.
drive
(
CurveTween
(
curve:
Curves
.
fastOutSlowIn
))
.
drive
(
CurveTween
(
curve:
standardEasing
))
.
drive
(
_thetaTween
)
.
drive
(
_thetaTween
)
..
addListener
(()
=>
setState
(()
{
/* _theta.value has changed */
}));
..
addListener
(()
=>
setState
(()
{
/* _theta.value has changed */
}));
}
}
...
@@ -1122,23 +926,12 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin {
...
@@ -1122,23 +926,12 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin {
@override
@override
void
didUpdateWidget
(
_Dial
oldWidget
)
{
void
didUpdateWidget
(
_Dial
oldWidget
)
{
super
.
didUpdateWidget
(
oldWidget
);
super
.
didUpdateWidget
(
oldWidget
);
_updateDialRingFromWidget
();
if
(
widget
.
mode
!=
oldWidget
.
mode
||
widget
.
selectedTime
!=
oldWidget
.
selectedTime
)
{
if
(
widget
.
mode
!=
oldWidget
.
mode
||
widget
.
selectedTime
!=
oldWidget
.
selectedTime
)
{
if
(!
_dragging
)
if
(!
_dragging
)
_animateTo
(
_getThetaForTime
(
widget
.
selectedTime
));
_animateTo
(
_getThetaForTime
(
widget
.
selectedTime
));
}
}
}
}
void
_updateDialRingFromWidget
()
{
if
(
widget
.
mode
==
_TimePickerMode
.
hour
&&
widget
.
use24HourDials
)
{
_activeRing
=
widget
.
selectedTime
.
hour
>=
1
&&
widget
.
selectedTime
.
hour
<=
12
?
_DialRing
.
inner
:
_DialRing
.
outer
;
}
else
{
_activeRing
=
_DialRing
.
outer
;
}
}
@override
@override
void
dispose
()
{
void
dispose
()
{
_thetaController
.
dispose
();
_thetaController
.
dispose
();
...
@@ -1167,36 +960,36 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin {
...
@@ -1167,36 +960,36 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin {
}
}
double
_getThetaForTime
(
TimeOfDay
time
)
{
double
_getThetaForTime
(
TimeOfDay
time
)
{
final
int
hoursFactor
=
widget
.
use24HourDials
?
TimeOfDay
.
hoursPerDay
:
TimeOfDay
.
hoursPerPeriod
;
final
double
fraction
=
widget
.
mode
==
_TimePickerMode
.
hour
final
double
fraction
=
widget
.
mode
==
_TimePickerMode
.
hour
?
(
time
.
hour
/
TimeOfDay
.
hoursPerPeriod
)
%
TimeOfDay
.
hoursPerPeriod
?
(
time
.
hour
/
hoursFactor
)
%
hoursFactor
:
(
time
.
minute
/
TimeOfDay
.
minutesPerHour
)
%
TimeOfDay
.
minutesPerHour
;
:
(
time
.
minute
/
TimeOfDay
.
minutesPerHour
)
%
TimeOfDay
.
minutesPerHour
;
return
(
math
.
pi
/
2.0
-
fraction
*
_kTwoPi
)
%
_kTwoPi
;
return
(
math
.
pi
/
2.0
-
fraction
*
_kTwoPi
)
%
_kTwoPi
;
}
}
TimeOfDay
_getTimeForTheta
(
double
theta
)
{
TimeOfDay
_getTimeForTheta
(
double
theta
,
{
bool
roundMinutes
=
false
}
)
{
final
double
fraction
=
(
0.25
-
(
theta
%
_kTwoPi
)
/
_kTwoPi
)
%
1.0
;
final
double
fraction
=
(
0.25
-
(
theta
%
_kTwoPi
)
/
_kTwoPi
)
%
1.0
;
if
(
widget
.
mode
==
_TimePickerMode
.
hour
)
{
if
(
widget
.
mode
==
_TimePickerMode
.
hour
)
{
int
newHour
=
(
fraction
*
TimeOfDay
.
hoursPerPeriod
).
round
()
%
TimeOfDay
.
hoursPerPeriod
;
int
newHour
;
if
(
widget
.
use24HourDials
)
{
if
(
widget
.
use24HourDials
)
{
if
(
_activeRing
==
_DialRing
.
outer
)
{
newHour
=
(
fraction
*
TimeOfDay
.
hoursPerDay
).
round
()
%
TimeOfDay
.
hoursPerDay
;
if
(
newHour
!=
0
)
newHour
=
(
newHour
+
TimeOfDay
.
hoursPerPeriod
)
%
TimeOfDay
.
hoursPerDay
;
}
else
if
(
newHour
==
0
)
{
newHour
=
TimeOfDay
.
hoursPerPeriod
;
}
}
else
{
}
else
{
newHour
=
(
fraction
*
TimeOfDay
.
hoursPerPeriod
).
round
()
%
TimeOfDay
.
hoursPerPeriod
;
newHour
=
newHour
+
widget
.
selectedTime
.
periodOffset
;
newHour
=
newHour
+
widget
.
selectedTime
.
periodOffset
;
}
}
return
widget
.
selectedTime
.
replacing
(
hour:
newHour
);
return
widget
.
selectedTime
.
replacing
(
hour:
newHour
);
}
else
{
}
else
{
return
widget
.
selectedTime
.
replacing
(
int
minute
=
(
fraction
*
TimeOfDay
.
minutesPerHour
).
round
()
%
TimeOfDay
.
minutesPerHour
;
minute:
(
fraction
*
TimeOfDay
.
minutesPerHour
).
round
()
%
TimeOfDay
.
minutesPerHour
if
(
roundMinutes
)
{
);
// Round the minutes to nearest 5 minute interval.
minute
=
((
minute
+
2
)
~/
5
)
*
5
%
TimeOfDay
.
minutesPerHour
;
}
return
widget
.
selectedTime
.
replacing
(
minute:
minute
);
}
}
}
}
TimeOfDay
_notifyOnChangedIfNeeded
()
{
TimeOfDay
_notifyOnChangedIfNeeded
(
{
bool
roundMinutes
=
false
}
)
{
final
TimeOfDay
current
=
_getTimeForTheta
(
_theta
.
value
);
final
TimeOfDay
current
=
_getTimeForTheta
(
_theta
.
value
,
roundMinutes:
roundMinutes
);
if
(
widget
.
onChanged
==
null
)
if
(
widget
.
onChanged
==
null
)
return
current
;
return
current
;
if
(
current
!=
widget
.
selectedTime
)
if
(
current
!=
widget
.
selectedTime
)
...
@@ -1204,27 +997,21 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin {
...
@@ -1204,27 +997,21 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin {
return
current
;
return
current
;
}
}
void
_updateThetaForPan
()
{
void
_updateThetaForPan
(
{
bool
roundMinutes
=
false
}
)
{
setState
(()
{
setState
(()
{
final
Offset
offset
=
_position
-
_center
;
final
Offset
offset
=
_position
-
_center
;
final
double
angle
=
(
math
.
atan2
(
offset
.
dx
,
offset
.
dy
)
-
math
.
pi
/
2.0
)
%
_kTwoPi
;
double
angle
=
(
math
.
atan2
(
offset
.
dx
,
offset
.
dy
)
-
math
.
pi
/
2.0
)
%
_kTwoPi
;
if
(
roundMinutes
)
{
angle
=
_getThetaForTime
(
_getTimeForTheta
(
angle
,
roundMinutes:
roundMinutes
));
}
_thetaTween
_thetaTween
..
begin
=
angle
..
begin
=
angle
..
end
=
angle
;
// The controller doesn't animate during the pan gesture.
..
end
=
angle
;
// The controller doesn't animate during the pan gesture.
final
RenderBox
box
=
context
.
findRenderObject
()
as
RenderBox
;
final
double
radius
=
box
.
size
.
shortestSide
/
2.0
;
if
(
widget
.
mode
==
_TimePickerMode
.
hour
&&
widget
.
use24HourDials
)
{
if
(
offset
.
distance
*
1.5
<
radius
)
_activeRing
=
_DialRing
.
inner
;
else
_activeRing
=
_DialRing
.
outer
;
}
});
});
}
}
Offset
_position
;
Offset
_position
;
Offset
_center
;
Offset
_center
;
_DialRing
_activeRing
=
_DialRing
.
outer
;
void
_handlePanStart
(
DragStartDetails
details
)
{
void
_handlePanStart
(
DragStartDetails
details
)
{
assert
(!
_dragging
);
assert
(!
_dragging
);
...
@@ -1259,8 +1046,8 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin {
...
@@ -1259,8 +1046,8 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin {
final
RenderBox
box
=
context
.
findRenderObject
()
as
RenderBox
;
final
RenderBox
box
=
context
.
findRenderObject
()
as
RenderBox
;
_position
=
box
.
globalToLocal
(
details
.
globalPosition
);
_position
=
box
.
globalToLocal
(
details
.
globalPosition
);
_center
=
box
.
size
.
center
(
Offset
.
zero
);
_center
=
box
.
size
.
center
(
Offset
.
zero
);
_updateThetaForPan
();
_updateThetaForPan
(
roundMinutes:
true
);
final
TimeOfDay
newTime
=
_notifyOnChangedIfNeeded
();
final
TimeOfDay
newTime
=
_notifyOnChangedIfNeeded
(
roundMinutes:
true
);
if
(
widget
.
mode
==
_TimePickerMode
.
hour
)
{
if
(
widget
.
mode
==
_TimePickerMode
.
hour
)
{
if
(
widget
.
use24HourDials
)
{
if
(
widget
.
use24HourDials
)
{
_announceToAccessibility
(
context
,
localizations
.
formatDecimal
(
newTime
.
hour
));
_announceToAccessibility
(
context
,
localizations
.
formatDecimal
(
newTime
.
hour
));
...
@@ -1273,7 +1060,7 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin {
...
@@ -1273,7 +1060,7 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin {
}
else
{
}
else
{
_announceToAccessibility
(
context
,
localizations
.
formatDecimal
(
newTime
.
minute
));
_announceToAccessibility
(
context
,
localizations
.
formatDecimal
(
newTime
.
minute
));
}
}
_animateTo
(
_getThetaForTime
(
_getTimeForTheta
(
_theta
.
value
)));
_animateTo
(
_getThetaForTime
(
_getTimeForTheta
(
_theta
.
value
,
roundMinutes:
true
)));
_dragging
=
false
;
_dragging
=
false
;
_position
=
null
;
_position
=
null
;
_center
=
null
;
_center
=
null
;
...
@@ -1283,12 +1070,8 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin {
...
@@ -1283,12 +1070,8 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin {
_announceToAccessibility
(
context
,
localizations
.
formatDecimal
(
hour
));
_announceToAccessibility
(
context
,
localizations
.
formatDecimal
(
hour
));
TimeOfDay
time
;
TimeOfDay
time
;
if
(
widget
.
mode
==
_TimePickerMode
.
hour
&&
widget
.
use24HourDials
)
{
if
(
widget
.
mode
==
_TimePickerMode
.
hour
&&
widget
.
use24HourDials
)
{
_activeRing
=
hour
>=
1
&&
hour
<=
12
?
_DialRing
.
inner
:
_DialRing
.
outer
;
time
=
TimeOfDay
(
hour:
hour
,
minute:
widget
.
selectedTime
.
minute
);
time
=
TimeOfDay
(
hour:
hour
,
minute:
widget
.
selectedTime
.
minute
);
}
else
{
}
else
{
_activeRing
=
_DialRing
.
outer
;
if
(
widget
.
selectedTime
.
period
==
DayPeriod
.
am
)
{
if
(
widget
.
selectedTime
.
period
==
DayPeriod
.
am
)
{
time
=
TimeOfDay
(
hour:
hour
,
minute:
widget
.
selectedTime
.
minute
);
time
=
TimeOfDay
(
hour:
hour
,
minute:
widget
.
selectedTime
.
minute
);
}
else
{
}
else
{
...
@@ -1330,19 +1113,19 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin {
...
@@ -1330,19 +1113,19 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin {
TimeOfDay
(
hour:
11
,
minute:
0
),
TimeOfDay
(
hour:
11
,
minute:
0
),
];
];
static
const
List
<
TimeOfDay
>
_
pm
Hours
=
<
TimeOfDay
>[
static
const
List
<
TimeOfDay
>
_
twentyFour
Hours
=
<
TimeOfDay
>[
TimeOfDay
(
hour:
0
,
minute:
0
),
TimeOfDay
(
hour:
0
,
minute:
0
),
TimeOfDay
(
hour:
13
,
minute:
0
),
TimeOfDay
(
hour:
2
,
minute:
0
),
TimeOfDay
(
hour:
4
,
minute:
0
),
TimeOfDay
(
hour:
6
,
minute:
0
),
TimeOfDay
(
hour:
8
,
minute:
0
),
TimeOfDay
(
hour:
10
,
minute:
0
),
TimeOfDay
(
hour:
12
,
minute:
0
),
TimeOfDay
(
hour:
14
,
minute:
0
),
TimeOfDay
(
hour:
14
,
minute:
0
),
TimeOfDay
(
hour:
15
,
minute:
0
),
TimeOfDay
(
hour:
16
,
minute:
0
),
TimeOfDay
(
hour:
16
,
minute:
0
),
TimeOfDay
(
hour:
17
,
minute:
0
),
TimeOfDay
(
hour:
18
,
minute:
0
),
TimeOfDay
(
hour:
18
,
minute:
0
),
TimeOfDay
(
hour:
19
,
minute:
0
),
TimeOfDay
(
hour:
20
,
minute:
0
),
TimeOfDay
(
hour:
20
,
minute:
0
),
TimeOfDay
(
hour:
21
,
minute:
0
),
TimeOfDay
(
hour:
22
,
minute:
0
),
TimeOfDay
(
hour:
22
,
minute:
0
),
TimeOfDay
(
hour:
23
,
minute:
0
),
];
];
_TappableLabel
_buildTappableLabel
(
TextTheme
textTheme
,
int
value
,
String
label
,
VoidCallback
onTap
)
{
_TappableLabel
_buildTappableLabel
(
TextTheme
textTheme
,
int
value
,
String
label
,
VoidCallback
onTap
)
{
...
@@ -1359,20 +1142,8 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin {
...
@@ -1359,20 +1142,8 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin {
);
);
}
}
List
<
_TappableLabel
>
_build24HourInnerRing
(
TextTheme
textTheme
)
=>
<
_TappableLabel
>[
List
<
_TappableLabel
>
_build24HourRing
(
TextTheme
textTheme
)
=>
<
_TappableLabel
>[
for
(
final
TimeOfDay
timeOfDay
in
_amHours
)
for
(
final
TimeOfDay
timeOfDay
in
_twentyFourHours
)
_buildTappableLabel
(
textTheme
,
timeOfDay
.
hour
,
localizations
.
formatHour
(
timeOfDay
,
alwaysUse24HourFormat:
media
.
alwaysUse24HourFormat
),
()
{
_selectHour
(
timeOfDay
.
hour
);
},
),
];
List
<
_TappableLabel
>
_build24HourOuterRing
(
TextTheme
textTheme
)
=>
<
_TappableLabel
>[
for
(
final
TimeOfDay
timeOfDay
in
_pmHours
)
_buildTappableLabel
(
_buildTappableLabel
(
textTheme
,
textTheme
,
timeOfDay
.
hour
,
timeOfDay
.
hour
,
...
@@ -1383,7 +1154,7 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin {
...
@@ -1383,7 +1154,7 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin {
),
),
];
];
List
<
_TappableLabel
>
_build12Hour
Outer
Ring
(
TextTheme
textTheme
)
=>
<
_TappableLabel
>[
List
<
_TappableLabel
>
_build12HourRing
(
TextTheme
textTheme
)
=>
<
_TappableLabel
>[
for
(
final
TimeOfDay
timeOfDay
in
_amHours
)
for
(
final
TimeOfDay
timeOfDay
in
_amHours
)
_buildTappableLabel
(
_buildTappableLabel
(
textTheme
,
textTheme
,
...
@@ -1426,42 +1197,29 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin {
...
@@ -1426,42 +1197,29 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin {
@override
@override
Widget
build
(
BuildContext
context
)
{
Widget
build
(
BuildContext
context
)
{
Color
backgroundColor
;
switch
(
themeData
.
brightness
)
{
case
Brightness
.
light
:
backgroundColor
=
Colors
.
grey
[
200
];
break
;
case
Brightness
.
dark
:
backgroundColor
=
themeData
.
backgroundColor
;
break
;
}
final
ThemeData
theme
=
Theme
.
of
(
context
);
final
ThemeData
theme
=
Theme
.
of
(
context
);
List
<
_TappableLabel
>
primaryOuterLabels
;
final
TimePickerThemeData
pickerTheme
=
TimePickerTheme
.
of
(
context
);
List
<
_TappableLabel
>
primaryInnerLabels
;
final
Color
backgroundColor
=
pickerTheme
.
dialBackgroundColor
??
themeData
.
colorScheme
.
onBackground
.
withOpacity
(
0.12
);
List
<
_TappableLabel
>
secondaryOuterLabels
;
final
Color
accentColor
=
pickerTheme
.
dialHandColor
??
themeData
.
colorScheme
.
primary
;
List
<
_TappableLabel
>
secondaryInnerLabels
;
List
<
_TappableLabel
>
primaryLabels
;
List
<
_TappableLabel
>
secondaryLabels
;
int
selectedDialValue
;
int
selectedDialValue
;
switch
(
widget
.
mode
)
{
switch
(
widget
.
mode
)
{
case
_TimePickerMode
.
hour
:
case
_TimePickerMode
.
hour
:
if
(
widget
.
use24HourDials
)
{
if
(
widget
.
use24HourDials
)
{
selectedDialValue
=
widget
.
selectedTime
.
hour
;
selectedDialValue
=
widget
.
selectedTime
.
hour
;
primaryOuterLabels
=
_build24HourOuterRing
(
theme
.
textTheme
);
primaryLabels
=
_build24HourRing
(
theme
.
textTheme
);
secondaryOuterLabels
=
_build24HourOuterRing
(
theme
.
accentTextTheme
);
secondaryLabels
=
_build24HourRing
(
theme
.
accentTextTheme
);
primaryInnerLabels
=
_build24HourInnerRing
(
theme
.
textTheme
);
secondaryInnerLabels
=
_build24HourInnerRing
(
theme
.
accentTextTheme
);
}
else
{
}
else
{
selectedDialValue
=
widget
.
selectedTime
.
hourOfPeriod
;
selectedDialValue
=
widget
.
selectedTime
.
hourOfPeriod
;
primary
OuterLabels
=
_build12HourOute
rRing
(
theme
.
textTheme
);
primary
Labels
=
_build12Hou
rRing
(
theme
.
textTheme
);
secondary
OuterLabels
=
_build12HourOute
rRing
(
theme
.
accentTextTheme
);
secondary
Labels
=
_build12Hou
rRing
(
theme
.
accentTextTheme
);
}
}
break
;
break
;
case
_TimePickerMode
.
minute
:
case
_TimePickerMode
.
minute
:
selectedDialValue
=
widget
.
selectedTime
.
minute
;
selectedDialValue
=
widget
.
selectedTime
.
minute
;
primaryOuterLabels
=
_buildMinutes
(
theme
.
textTheme
);
primaryLabels
=
_buildMinutes
(
theme
.
textTheme
);
primaryInnerLabels
=
null
;
secondaryLabels
=
_buildMinutes
(
theme
.
accentTextTheme
);
secondaryOuterLabels
=
_buildMinutes
(
theme
.
accentTextTheme
);
secondaryInnerLabels
=
null
;
break
;
break
;
}
}
...
@@ -1475,14 +1233,12 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin {
...
@@ -1475,14 +1233,12 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin {
key:
const
ValueKey
<
String
>(
'time-picker-dial'
),
key:
const
ValueKey
<
String
>(
'time-picker-dial'
),
painter:
_DialPainter
(
painter:
_DialPainter
(
selectedValue:
selectedDialValue
,
selectedValue:
selectedDialValue
,
primaryOuterLabels:
primaryOuterLabels
,
primaryLabels:
primaryLabels
,
primaryInnerLabels:
primaryInnerLabels
,
secondaryLabels:
secondaryLabels
,
secondaryOuterLabels:
secondaryOuterLabels
,
secondaryInnerLabels:
secondaryInnerLabels
,
backgroundColor:
backgroundColor
,
backgroundColor:
backgroundColor
,
accentColor:
themeData
.
accentColor
,
accentColor:
accentColor
,
dotColor:
theme
.
colorScheme
.
surface
,
theta:
_theta
.
value
,
theta:
_theta
.
value
,
activeRing:
_activeRing
,
textDirection:
Directionality
.
of
(
context
),
textDirection:
Directionality
.
of
(
context
),
),
),
),
),
...
@@ -1490,6 +1246,342 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin {
...
@@ -1490,6 +1246,342 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin {
}
}
}
}
class
_TimePickerInput
extends
StatefulWidget
{
const
_TimePickerInput
({
Key
key
,
@required
this
.
initialSelectedTime
,
@required
this
.
helpText
,
@required
this
.
onChanged
,
})
:
assert
(
initialSelectedTime
!=
null
),
assert
(
onChanged
!=
null
),
super
(
key:
key
);
/// The time initially selected when the dialog is shown.
final
TimeOfDay
initialSelectedTime
;
/// Optionally provide your own help text to the time picker.
final
String
helpText
;
final
ValueChanged
<
TimeOfDay
>
onChanged
;
@override
_TimePickerInputState
createState
()
=>
_TimePickerInputState
();
}
class
_TimePickerInputState
extends
State
<
_TimePickerInput
>
{
TimeOfDay
_selectedTime
;
bool
hourHasError
=
false
;
bool
minuteHasError
=
false
;
@override
void
initState
()
{
super
.
initState
();
_selectedTime
=
widget
.
initialSelectedTime
;
}
int
_parseHour
(
String
value
)
{
if
(
value
==
null
)
{
return
null
;
}
int
newHour
=
int
.
tryParse
(
value
);
if
(
newHour
==
null
)
{
return
null
;
}
if
(
MediaQuery
.
of
(
context
).
alwaysUse24HourFormat
)
{
if
(
newHour
>=
0
&&
newHour
<
24
)
{
return
newHour
;
}
}
else
{
if
(
newHour
>
0
&&
newHour
<
13
)
{
if
((
_selectedTime
.
period
==
DayPeriod
.
pm
&&
newHour
!=
12
)
||
(
_selectedTime
.
period
==
DayPeriod
.
am
&&
newHour
==
12
))
{
newHour
=
(
newHour
+
TimeOfDay
.
hoursPerPeriod
)
%
TimeOfDay
.
hoursPerDay
;
}
return
newHour
;
}
}
return
null
;
}
int
_parseMinute
(
String
value
)
{
if
(
value
==
null
)
{
return
null
;
}
final
int
newMinute
=
int
.
tryParse
(
value
);
if
(
newMinute
==
null
)
{
return
null
;
}
if
(
newMinute
>=
0
&&
newMinute
<
60
)
{
return
newMinute
;
}
return
null
;
}
void
_handleHourSavedSubmitted
(
String
value
)
{
final
int
newHour
=
_parseHour
(
value
);
if
(
newHour
!=
null
)
{
_selectedTime
=
TimeOfDay
(
hour:
newHour
,
minute:
_selectedTime
.
minute
);
widget
.
onChanged
(
_selectedTime
);
}
}
void
_handleHourChanged
(
String
value
)
{
final
int
newHour
=
_parseHour
(
value
);
if
(
newHour
!=
null
&&
value
.
length
==
2
)
{
// If a valid hour is typed, move focus to the minute TextField.
FocusScope
.
of
(
context
).
nextFocus
();
}
}
void
_handleMinuteSavedSubmitted
(
String
value
)
{
final
int
newMinute
=
_parseMinute
(
value
);
if
(
newMinute
!=
null
)
{
_selectedTime
=
TimeOfDay
(
hour:
_selectedTime
.
hour
,
minute:
int
.
parse
(
value
));
widget
.
onChanged
(
_selectedTime
);
}
}
void
_handleDayPeriodChanged
(
TimeOfDay
value
)
{
_selectedTime
=
value
;
widget
.
onChanged
(
_selectedTime
);
}
String
_validateHour
(
String
value
)
{
final
int
newHour
=
_parseHour
(
value
);
setState
(()
{
hourHasError
=
newHour
==
null
;
});
// This is used as the validator for the [TextFormField].
// Returning an empty string allows the field to go into an error state.
// Returning null means no error in the validation of the entered text.
return
newHour
==
null
?
''
:
null
;
}
String
_validateMinute
(
String
value
)
{
final
int
newMinute
=
_parseMinute
(
value
);
setState
(()
{
minuteHasError
=
newMinute
==
null
;
});
// This is used as the validator for the [TextFormField].
// Returning an empty string allows the field to go into an error state.
// Returning null means no error in the validation of the entered text.
return
newMinute
==
null
?
''
:
null
;
}
@override
Widget
build
(
BuildContext
context
)
{
assert
(
debugCheckHasMediaQuery
(
context
));
final
MediaQueryData
media
=
MediaQuery
.
of
(
context
);
final
TimeOfDayFormat
timeOfDayFormat
=
MaterialLocalizations
.
of
(
context
).
timeOfDayFormat
(
alwaysUse24HourFormat:
media
.
alwaysUse24HourFormat
);
final
bool
use24HourDials
=
hourFormat
(
of:
timeOfDayFormat
)
!=
HourFormat
.
h
;
final
ThemeData
theme
=
Theme
.
of
(
context
);
final
TextStyle
hourMinuteStyle
=
TimePickerTheme
.
of
(
context
).
hourMinuteTextStyle
??
theme
.
textTheme
.
headline2
;
return
Padding
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
24.0
,
vertical:
16.0
),
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
<
Widget
>[
Text
(
// TODO(rami-a): localize 'ENTER TIME'
widget
.
helpText
??
'ENTER TIME'
,
style:
TimePickerTheme
.
of
(
context
).
helpTextStyle
??
theme
.
textTheme
.
overline
,
),
const
SizedBox
(
height:
16.0
),
Row
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
<
Widget
>[
if
(!
use24HourDials
&&
timeOfDayFormat
==
TimeOfDayFormat
.
a_space_h_colon_mm
)
...<
Widget
>[
_DayPeriodControl
(
selectedTime:
_selectedTime
,
orientation:
Orientation
.
portrait
,
onChanged:
_handleDayPeriodChanged
,
),
const
SizedBox
(
width:
12.0
),
],
Expanded
(
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
<
Widget
>[
const
SizedBox
(
height:
8.0
),
_HourMinuteTextField
(
selectedTime:
_selectedTime
,
isHour:
true
,
style:
hourMinuteStyle
,
validator:
_validateHour
,
onSavedSubmitted:
_handleHourSavedSubmitted
,
onChanged:
_handleHourChanged
,
),
const
SizedBox
(
height:
8.0
),
if
(!
hourHasError
&&
!
minuteHasError
)
ExcludeSemantics
(
// TODO(rami-a): localize 'Hour'
child:
Text
(
'Hour'
,
style:
theme
.
textTheme
.
caption
,
maxLines:
1
,
overflow:
TextOverflow
.
ellipsis
),
),
],
)),
Padding
(
padding:
const
EdgeInsets
.
only
(
top:
20.0
),
child:
_StringFragment
(
timeOfDayFormat:
timeOfDayFormat
),
),
Expanded
(
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
<
Widget
>[
const
SizedBox
(
height:
8.0
),
_HourMinuteTextField
(
selectedTime:
_selectedTime
,
isHour:
false
,
style:
hourMinuteStyle
,
validator:
_validateMinute
,
onSavedSubmitted:
_handleMinuteSavedSubmitted
,
),
const
SizedBox
(
height:
8.0
),
if
(!
hourHasError
&&
!
minuteHasError
)
ExcludeSemantics
(
// TODO(rami-a): localize 'Minute'
child:
Text
(
'Minute'
,
style:
theme
.
textTheme
.
caption
,
maxLines:
1
,
overflow:
TextOverflow
.
ellipsis
),
),
],
)),
if
(!
use24HourDials
&&
timeOfDayFormat
!=
TimeOfDayFormat
.
a_space_h_colon_mm
)
...<
Widget
>[
const
SizedBox
(
width:
12.0
),
_DayPeriodControl
(
selectedTime:
_selectedTime
,
orientation:
Orientation
.
portrait
,
onChanged:
_handleDayPeriodChanged
,
),
],
],
),
if
(
hourHasError
||
minuteHasError
)
Text
(
// TODO(rami-a): localize 'Enter a valid time'
'Enter a valid time'
,
style:
theme
.
textTheme
.
bodyText2
.
copyWith
(
color:
theme
.
colorScheme
.
error
),
)
else
const
SizedBox
(
height:
2.0
),
],
),
);
}
}
class
_HourMinuteTextField
extends
StatefulWidget
{
const
_HourMinuteTextField
({
Key
key
,
@required
this
.
selectedTime
,
@required
this
.
isHour
,
@required
this
.
style
,
@required
this
.
validator
,
@required
this
.
onSavedSubmitted
,
this
.
onChanged
,
})
:
super
(
key:
key
);
final
TimeOfDay
selectedTime
;
final
bool
isHour
;
final
TextStyle
style
;
final
FormFieldValidator
<
String
>
validator
;
final
ValueChanged
<
String
>
onSavedSubmitted
;
final
ValueChanged
<
String
>
onChanged
;
@override
_HourMinuteTextFieldState
createState
()
=>
_HourMinuteTextFieldState
();
}
class
_HourMinuteTextFieldState
extends
State
<
_HourMinuteTextField
>
{
TextEditingController
controller
;
FocusNode
focusNode
;
@override
void
initState
()
{
super
.
initState
();
focusNode
=
FocusNode
()..
addListener
(()
{
setState
(()
{
});
// Rebuild.
});
}
@override
void
didChangeDependencies
()
{
super
.
didChangeDependencies
();
controller
??=
TextEditingController
(
text:
_formattedValue
);
}
String
get
_formattedValue
{
final
bool
alwaysUse24HourFormat
=
MediaQuery
.
of
(
context
).
alwaysUse24HourFormat
;
final
MaterialLocalizations
localizations
=
MaterialLocalizations
.
of
(
context
);
return
!
widget
.
isHour
?
localizations
.
formatMinute
(
widget
.
selectedTime
)
:
localizations
.
formatHour
(
widget
.
selectedTime
,
alwaysUse24HourFormat:
alwaysUse24HourFormat
,
);
}
@override
Widget
build
(
BuildContext
context
)
{
final
ThemeData
theme
=
Theme
.
of
(
context
);
final
ColorScheme
colorScheme
=
theme
.
colorScheme
;
final
InputDecorationTheme
inputDecorationTheme
=
TimePickerTheme
.
of
(
context
).
inputDecorationTheme
;
InputDecoration
inputDecoration
;
if
(
inputDecorationTheme
!=
null
)
{
inputDecoration
=
const
InputDecoration
().
applyDefaults
(
inputDecorationTheme
);
}
else
{
inputDecoration
=
InputDecoration
(
contentPadding:
const
EdgeInsetsDirectional
.
only
(
bottom:
16.0
,
start:
3.0
),
filled:
true
,
fillColor:
focusNode
.
hasFocus
?
colorScheme
.
surface
:
colorScheme
.
onSurface
.
withOpacity
(
0.12
),
enabledBorder:
const
OutlineInputBorder
(
borderSide:
BorderSide
(
color:
Colors
.
transparent
),
),
errorBorder:
OutlineInputBorder
(
borderSide:
BorderSide
(
color:
colorScheme
.
error
,
width:
2.0
),
),
focusedBorder:
OutlineInputBorder
(
borderSide:
BorderSide
(
color:
colorScheme
.
primary
,
width:
2.0
),
),
focusedErrorBorder:
OutlineInputBorder
(
borderSide:
BorderSide
(
color:
colorScheme
.
error
,
width:
2.0
),
),
hintStyle:
widget
.
style
.
copyWith
(
color:
colorScheme
.
onSurface
.
withOpacity
(
0.36
)),
// TODO(rami-a): Remove this logic once https://github.com/flutter/flutter/issues/54104 is fixed.
errorStyle:
const
TextStyle
(
fontSize:
0.0
,
height:
0.0
),
// Prevent the error text from appearing.
);
}
inputDecoration
=
inputDecoration
.
copyWith
(
// Remove the hint text when focused because the centered cursor appears
// odd above the hint text.
hintText:
focusNode
.
hasFocus
?
null
:
_formattedValue
,
);
return
Column
(
children:
<
Widget
>[
SizedBox
(
height:
_kTimePickerHeaderControlHeight
,
child:
MediaQuery
(
data:
MediaQuery
.
of
(
context
).
copyWith
(
textScaleFactor:
1.0
),
child:
TextFormField
(
focusNode:
focusNode
,
textAlign:
TextAlign
.
center
,
keyboardType:
TextInputType
.
number
,
style:
widget
.
style
.
copyWith
(
color:
colorScheme
.
onSurface
),
controller:
controller
,
decoration:
inputDecoration
,
validator:
widget
.
validator
,
onEditingComplete:
()
=>
widget
.
onSavedSubmitted
(
controller
.
text
),
onSaved:
widget
.
onSavedSubmitted
,
onFieldSubmitted:
widget
.
onSavedSubmitted
,
onChanged:
widget
.
onChanged
,
),
),
),
],
);
}
}
/// A material design time picker designed to appear inside a popup dialog.
/// A material design time picker designed to appear inside a popup dialog.
///
///
/// Pass this widget to [showDialog]. The value returned by [showDialog] is the
/// Pass this widget to [showDialog]. The value returned by [showDialog] is the
...
@@ -1503,21 +1595,45 @@ class _TimePickerDialog extends StatefulWidget {
...
@@ -1503,21 +1595,45 @@ class _TimePickerDialog extends StatefulWidget {
const
_TimePickerDialog
({
const
_TimePickerDialog
({
Key
key
,
Key
key
,
@required
this
.
initialTime
,
@required
this
.
initialTime
,
@required
this
.
cancelText
,
@required
this
.
confirmText
,
@required
this
.
helpText
,
this
.
initialEntryMode
=
TimePickerEntryMode
.
dial
,
})
:
assert
(
initialTime
!=
null
),
})
:
assert
(
initialTime
!=
null
),
super
(
key:
key
);
super
(
key:
key
);
/// The time initially selected when the dialog is shown.
/// The time initially selected when the dialog is shown.
final
TimeOfDay
initialTime
;
final
TimeOfDay
initialTime
;
/// The entry mode for the picker. Whether it's text input or a dial.
final
TimePickerEntryMode
initialEntryMode
;
/// Optionally provide your own text for the cancel button.
///
/// If null, the button uses [MaterialLocalizations.cancelButtonLabel].
final
String
cancelText
;
/// Optionally provide your own text for the confirm button.
///
/// If null, the button uses [MaterialLocalizations.okButtonLabel].
final
String
confirmText
;
/// Optionally provide your own help text to the header of the time picker.
final
String
helpText
;
@override
@override
_TimePickerDialogState
createState
()
=>
_TimePickerDialogState
();
_TimePickerDialogState
createState
()
=>
_TimePickerDialogState
();
}
}
class
_TimePickerDialogState
extends
State
<
_TimePickerDialog
>
{
class
_TimePickerDialogState
extends
State
<
_TimePickerDialog
>
{
final
GlobalKey
<
FormState
>
_formKey
=
GlobalKey
<
FormState
>();
@override
@override
void
initState
()
{
void
initState
()
{
super
.
initState
();
super
.
initState
();
_selectedTime
=
widget
.
initialTime
;
_selectedTime
=
widget
.
initialTime
;
_entryMode
=
widget
.
initialEntryMode
;
_autoValidate
=
false
;
}
}
@override
@override
...
@@ -1528,8 +1644,10 @@ class _TimePickerDialogState extends State<_TimePickerDialog> {
...
@@ -1528,8 +1644,10 @@ class _TimePickerDialogState extends State<_TimePickerDialog> {
_announceModeOnce
();
_announceModeOnce
();
}
}
TimePickerEntryMode
_entryMode
;
_TimePickerMode
_mode
=
_TimePickerMode
.
hour
;
_TimePickerMode
_mode
=
_TimePickerMode
.
hour
;
_TimePickerMode
_lastModeAnnounced
;
_TimePickerMode
_lastModeAnnounced
;
bool
_autoValidate
;
TimeOfDay
get
selectedTime
=>
_selectedTime
;
TimeOfDay
get
selectedTime
=>
_selectedTime
;
TimeOfDay
_selectedTime
;
TimeOfDay
_selectedTime
;
...
@@ -1563,6 +1681,21 @@ class _TimePickerDialogState extends State<_TimePickerDialog> {
...
@@ -1563,6 +1681,21 @@ class _TimePickerDialogState extends State<_TimePickerDialog> {
});
});
}
}
void
_handleEntryModeToggle
()
{
setState
(()
{
switch
(
_entryMode
)
{
case
TimePickerEntryMode
.
dial
:
_autoValidate
=
false
;
_entryMode
=
TimePickerEntryMode
.
input
;
break
;
case
TimePickerEntryMode
.
input
:
_formKey
.
currentState
.
save
();
_entryMode
=
TimePickerEntryMode
.
dial
;
break
;
}
});
}
void
_announceModeOnce
()
{
void
_announceModeOnce
()
{
if
(
_lastModeAnnounced
==
_mode
)
{
if
(
_lastModeAnnounced
==
_mode
)
{
// Already announced it.
// Already announced it.
...
@@ -1613,9 +1746,52 @@ class _TimePickerDialogState extends State<_TimePickerDialog> {
...
@@ -1613,9 +1746,52 @@ class _TimePickerDialogState extends State<_TimePickerDialog> {
}
}
void
_handleOk
()
{
void
_handleOk
()
{
if
(
_entryMode
==
TimePickerEntryMode
.
input
)
{
final
FormState
form
=
_formKey
.
currentState
;
if
(!
form
.
validate
())
{
setState
(()
{
_autoValidate
=
true
;
});
return
;
}
form
.
save
();
}
Navigator
.
pop
(
context
,
_selectedTime
);
Navigator
.
pop
(
context
,
_selectedTime
);
}
}
Size
_dialogSize
(
BuildContext
context
)
{
final
Orientation
orientation
=
MediaQuery
.
of
(
context
).
orientation
;
final
ThemeData
theme
=
Theme
.
of
(
context
);
// Constrain the textScaleFactor to prevent layout issues. Since only some
// parts of the time picker scale up with textScaleFactor, we cap the factor
// to 1.1 as that provides enough space to reasonably fit all the content.
final
double
textScaleFactor
=
math
.
min
(
MediaQuery
.
of
(
context
).
textScaleFactor
,
1.1
);
double
timePickerWidth
;
double
timePickerHeight
;
switch
(
_entryMode
)
{
case
TimePickerEntryMode
.
dial
:
switch
(
orientation
)
{
case
Orientation
.
portrait
:
timePickerWidth
=
_kTimePickerWidthPortrait
;
timePickerHeight
=
theme
.
materialTapTargetSize
==
MaterialTapTargetSize
.
padded
?
_kTimePickerHeightPortrait
:
_kTimePickerHeightPortraitCollapsed
;
break
;
case
Orientation
.
landscape
:
timePickerWidth
=
_kTimePickerWidthLandscape
*
textScaleFactor
;
timePickerHeight
=
theme
.
materialTapTargetSize
==
MaterialTapTargetSize
.
padded
?
_kTimePickerHeightLandscape
:
_kTimePickerHeightLandscapeCollapsed
;
break
;
}
break
;
case
TimePickerEntryMode
.
input
:
timePickerWidth
=
_kTimePickerWidthPortrait
;
timePickerHeight
=
_kTimePickerHeightInput
;
break
;
}
return
Size
(
timePickerWidth
,
timePickerHeight
*
textScaleFactor
);
}
@override
@override
Widget
build
(
BuildContext
context
)
{
Widget
build
(
BuildContext
context
)
{
assert
(
debugCheckHasMediaQuery
(
context
));
assert
(
debugCheckHasMediaQuery
(
context
));
...
@@ -1623,113 +1799,144 @@ class _TimePickerDialogState extends State<_TimePickerDialog> {
...
@@ -1623,113 +1799,144 @@ class _TimePickerDialogState extends State<_TimePickerDialog> {
final
TimeOfDayFormat
timeOfDayFormat
=
localizations
.
timeOfDayFormat
(
alwaysUse24HourFormat:
media
.
alwaysUse24HourFormat
);
final
TimeOfDayFormat
timeOfDayFormat
=
localizations
.
timeOfDayFormat
(
alwaysUse24HourFormat:
media
.
alwaysUse24HourFormat
);
final
bool
use24HourDials
=
hourFormat
(
of:
timeOfDayFormat
)
!=
HourFormat
.
h
;
final
bool
use24HourDials
=
hourFormat
(
of:
timeOfDayFormat
)
!=
HourFormat
.
h
;
final
ThemeData
theme
=
Theme
.
of
(
context
);
final
ThemeData
theme
=
Theme
.
of
(
context
);
final
ShapeBorder
shape
=
TimePickerTheme
.
of
(
context
).
shape
??
_kDefaultShape
;
final
Orientation
orientation
=
media
.
orientation
;
final
Widget
picker
=
Padding
(
final
Widget
actions
=
Row
(
padding:
const
EdgeInsets
.
all
(
16.0
),
child:
AspectRatio
(
aspectRatio:
1.0
,
child:
_Dial
(
mode:
_mode
,
use24HourDials:
use24HourDials
,
selectedTime:
_selectedTime
,
onChanged:
_handleTimeChanged
,
onHourSelected:
_handleHourSelected
,
),
),
);
final
Widget
actions
=
ButtonBar
(
children:
<
Widget
>[
children:
<
Widget
>[
FlatButton
(
const
SizedBox
(
width:
10.0
),
child:
Text
(
localizations
.
cancelButtonLabel
),
IconButton
(
onPressed:
_handleCancel
,
color:
TimePickerTheme
.
of
(
context
).
entryModeIconColor
??
theme
.
colorScheme
.
onSurface
.
withOpacity
(
theme
.
colorScheme
.
brightness
==
Brightness
.
dark
?
1.0
:
0.6
,
),
onPressed:
_handleEntryModeToggle
,
icon:
Icon
(
_entryMode
==
TimePickerEntryMode
.
dial
?
Icons
.
keyboard
:
Icons
.
access_time
),
// TODO(rami-a): Localize these strings.
tooltip:
_entryMode
==
TimePickerEntryMode
.
dial
?
'Switch to text input mode'
:
'Switch to dial picker mode'
,
),
),
FlatButton
(
Expanded
(
child:
Text
(
localizations
.
okButtonLabel
),
// TODO(rami-a): Move away from ButtonBar to avoid https://github.com/flutter/flutter/issues/53378.
onPressed:
_handleOk
,
child:
ButtonBar
(
layoutBehavior:
ButtonBarLayoutBehavior
.
constrained
,
children:
<
Widget
>[
FlatButton
(
onPressed:
_handleCancel
,
child:
Text
(
widget
.
cancelText
??
localizations
.
cancelButtonLabel
),
),
FlatButton
(
onPressed:
_handleOk
,
child:
Text
(
widget
.
confirmText
??
localizations
.
okButtonLabel
),
),
],
),
),
),
],
],
);
);
final
Dialog
dialog
=
Dialog
(
Widget
picker
;
child:
OrientationBuilder
(
switch
(
_entryMode
)
{
builder:
(
BuildContext
context
,
Orientation
orientation
)
{
case
TimePickerEntryMode
.
dial
:
final
Widget
header
=
_TimePickerHeader
(
final
Widget
dial
=
Padding
(
selectedTime:
_selectedTime
,
padding:
orientation
==
Orientation
.
portrait
?
const
EdgeInsets
.
symmetric
(
horizontal:
36
,
vertical:
24
)
:
const
EdgeInsets
.
all
(
24
),
mode:
_mode
,
child:
ExcludeSemantics
(
orientation:
orientation
,
child:
AspectRatio
(
onModeChanged:
_handleModeChanged
,
aspectRatio:
1.0
,
onChanged:
_handleTimeChanged
,
child:
_Dial
(
use24HourDials:
use24HourDials
,
mode:
_mode
,
);
use24HourDials:
use24HourDials
,
selectedTime:
_selectedTime
,
final
Widget
pickerAndActions
=
Container
(
onChanged:
_handleTimeChanged
,
color:
theme
.
dialogBackgroundColor
,
onHourSelected:
_handleHourSelected
,
),
),
),
);
final
Widget
header
=
_TimePickerHeader
(
selectedTime:
_selectedTime
,
mode:
_mode
,
orientation:
orientation
,
onModeChanged:
_handleModeChanged
,
onChanged:
_handleTimeChanged
,
use24HourDials:
use24HourDials
,
helpText:
widget
.
helpText
,
);
switch
(
orientation
)
{
case
Orientation
.
portrait
:
picker
=
Column
(
mainAxisSize:
MainAxisSize
.
min
,
crossAxisAlignment:
CrossAxisAlignment
.
stretch
,
children:
<
Widget
>[
header
,
Expanded
(
child:
Column
(
mainAxisSize:
MainAxisSize
.
min
,
children:
<
Widget
>[
// Dial grows and shrinks with the available space.
Expanded
(
child:
dial
),
actions
,
],
),
),
],
);
break
;
case
Orientation
.
landscape
:
picker
=
Column
(
children:
<
Widget
>[
Expanded
(
child:
Row
(
children:
<
Widget
>[
header
,
Expanded
(
child:
dial
),
],
),
),
actions
,
],
);
break
;
}
break
;
case
TimePickerEntryMode
.
input
:
picker
=
Form
(
key:
_formKey
,
autovalidate:
_autoValidate
,
child:
SingleChildScrollView
(
child:
Column
(
child:
Column
(
mainAxisSize:
MainAxisSize
.
min
,
mainAxisSize:
MainAxisSize
.
min
,
children:
<
Widget
>[
children:
<
Widget
>[
Expanded
(
child:
picker
),
// picker grows and shrinks with the available space
_TimePickerInput
(
initialSelectedTime:
_selectedTime
,
helpText:
widget
.
helpText
,
onChanged:
_handleTimeChanged
,
),
actions
,
actions
,
],
],
),
),
);
),
);
double
timePickerHeightPortrait
;
break
;
double
timePickerHeightLandscape
;
}
switch
(
theme
.
materialTapTargetSize
)
{
case
MaterialTapTargetSize
.
padded
:
timePickerHeightPortrait
=
_kTimePickerHeightPortrait
;
timePickerHeightLandscape
=
_kTimePickerHeightLandscape
;
break
;
case
MaterialTapTargetSize
.
shrinkWrap
:
timePickerHeightPortrait
=
_kTimePickerHeightPortraitCollapsed
;
timePickerHeightLandscape
=
_kTimePickerHeightLandscapeCollapsed
;
break
;
}
assert
(
orientation
!=
null
);
switch
(
orientation
)
{
case
Orientation
.
portrait
:
return
SizedBox
(
width:
_kTimePickerWidthPortrait
,
height:
timePickerHeightPortrait
,
child:
Column
(
mainAxisSize:
MainAxisSize
.
min
,
crossAxisAlignment:
CrossAxisAlignment
.
stretch
,
children:
<
Widget
>[
header
,
Expanded
(
child:
pickerAndActions
,
),
],
),
);
case
Orientation
.
landscape
:
return
SizedBox
(
width:
_kTimePickerWidthLandscape
,
height:
timePickerHeightLandscape
,
child:
Row
(
mainAxisSize:
MainAxisSize
.
min
,
crossAxisAlignment:
CrossAxisAlignment
.
stretch
,
children:
<
Widget
>[
header
,
Flexible
(
child:
pickerAndActions
,
),
],
),
);
}
return
null
;
}
),
);
return
Theme
(
final
Size
dialogSize
=
_dialogSize
(
context
);
data:
theme
.
copyWith
(
return
Dialog
(
dialogBackgroundColor:
Colors
.
transparent
,
shape:
shape
,
backgroundColor:
TimePickerTheme
.
of
(
context
).
backgroundColor
??
theme
.
colorScheme
.
surface
,
insetPadding:
EdgeInsets
.
symmetric
(
horizontal:
16.0
,
vertical:
_entryMode
==
TimePickerEntryMode
.
input
?
0.0
:
24.0
,
),
child:
AnimatedContainer
(
width:
dialogSize
.
width
,
height:
dialogSize
.
height
,
duration:
_kDialogSizeAnimationDuration
,
curve:
Curves
.
easeIn
,
child:
picker
,
),
),
child:
dialog
,
);
);
}
}
...
@@ -1764,6 +1971,13 @@ class _TimePickerDialogState extends State<_TimePickerDialog> {
...
@@ -1764,6 +1971,13 @@ class _TimePickerDialogState extends State<_TimePickerDialog> {
/// to add inherited widgets like [Localizations.override],
/// to add inherited widgets like [Localizations.override],
/// [Directionality], or [MediaQuery].
/// [Directionality], or [MediaQuery].
///
///
/// The [entryMode] parameter can be used to
/// determine the initial time entry selection of the picker (either a clock
/// dial or text input).
///
/// Optional strings for the [helpText], [cancelText], and [confirmText] can be
/// provided to override the default values.
///
/// {@tool snippet}
/// {@tool snippet}
/// Show a dialog with the text direction overridden to be [TextDirection.rtl].
/// Show a dialog with the text direction overridden to be [TextDirection.rtl].
///
///
...
@@ -1807,14 +2021,25 @@ Future<TimeOfDay> showTimePicker({
...
@@ -1807,14 +2021,25 @@ Future<TimeOfDay> showTimePicker({
@required
TimeOfDay
initialTime
,
@required
TimeOfDay
initialTime
,
TransitionBuilder
builder
,
TransitionBuilder
builder
,
bool
useRootNavigator
=
true
,
bool
useRootNavigator
=
true
,
TimePickerEntryMode
initialEntryMode
=
TimePickerEntryMode
.
dial
,
String
cancelText
,
String
confirmText
,
String
helpText
,
RouteSettings
routeSettings
,
RouteSettings
routeSettings
,
})
async
{
})
async
{
assert
(
context
!=
null
);
assert
(
context
!=
null
);
assert
(
initialTime
!=
null
);
assert
(
initialTime
!=
null
);
assert
(
useRootNavigator
!=
null
);
assert
(
useRootNavigator
!=
null
);
assert
(
initialEntryMode
!=
null
);
assert
(
debugCheckHasMaterialLocalizations
(
context
));
assert
(
debugCheckHasMaterialLocalizations
(
context
));
final
Widget
dialog
=
_TimePickerDialog
(
initialTime:
initialTime
);
final
Widget
dialog
=
_TimePickerDialog
(
initialTime:
initialTime
,
initialEntryMode:
initialEntryMode
,
cancelText:
cancelText
,
confirmText:
confirmText
,
helpText:
helpText
,
);
return
await
showDialog
<
TimeOfDay
>(
return
await
showDialog
<
TimeOfDay
>(
context:
context
,
context:
context
,
useRootNavigator:
useRootNavigator
,
useRootNavigator:
useRootNavigator
,
...
...
packages/flutter/lib/src/material/time_picker_theme.dart
0 → 100644
View file @
e676024d
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// @dart = 2.8
import
'package:flutter/foundation.dart'
;
import
'package:flutter/widgets.dart'
;
import
'input_decorator.dart'
;
import
'theme.dart'
;
/// Defines the visual properties of the widget displayed with [showTimePicker].
///
/// Descendant widgets obtain the current [TimePickerThemeData] object using
/// `TimePickerTheme.of(context)`. Instances of [TimePickerThemeData]
/// can be customized with [TimePickerThemeData.copyWith].
///
/// Typically a [TimePickerThemeData] is specified as part of the overall
/// [Theme] with [ThemeData.timePickerTheme].
///
/// All [TimePickerThemeData] properties are `null` by default. When null,
/// [showTimePicker] will provide its own defaults.
///
/// See also:
///
/// * [ThemeData], which describes the overall theme information for the
/// application.
/// * [TimePickerTheme], which describes the actual configuration of a time
/// picker theme.
@immutable
class
TimePickerThemeData
with
Diagnosticable
{
/// Creates a theme that can be used for [TimePickerTheme] or
/// [ThemeData.timePickerTheme].
const
TimePickerThemeData
({
this
.
backgroundColor
,
this
.
hourMinuteTextColor
,
this
.
hourMinuteColor
,
this
.
dayPeriodTextColor
,
this
.
dayPeriodColor
,
this
.
dialHandColor
,
this
.
dialBackgroundColor
,
this
.
entryModeIconColor
,
this
.
hourMinuteTextStyle
,
this
.
dayPeriodTextStyle
,
this
.
helpTextStyle
,
this
.
shape
,
this
.
hourMinuteShape
,
this
.
dayPeriodShape
,
this
.
dayPeriodBorderSide
,
this
.
inputDecorationTheme
,
});
/// The background color of a time picker.
///
/// If this is null, the time picker defaults to the overall theme's
/// [ColorScheme.background].
final
Color
backgroundColor
;
/// The color of the header text that represents hours and minutes.
///
/// If [hourMinuteTextColor] is a [MaterialStateColor], then the effective
/// text color can depend on the [MaterialState.selected] state, i.e. if the
/// text is selected or not.
///
/// By default the overall theme's [ColorScheme.primary] color is used when
/// the text is selected and [ColorScheme.onSurface] when it's not selected.
final
Color
hourMinuteTextColor
;
/// The background color of the hour and minutes header segments.
///
/// If [hourMinuteColor] is a [MaterialStateColor], then the effective
/// background color can depend on the [MaterialState.selected] state, i.e.
/// if the segment is selected or not.
///
/// By default, if the segment is selected, the overall theme's
/// `ColorScheme.primary.withOpacity(0.12)` is used when the overall theme's
/// brightness is [Brightness.light] and
/// `ColorScheme.primary.withOpacity(0.24)` is used when the overall theme's
/// brightness is [Brightness.dark].
/// If the segment is not selected, the overall theme's
/// `ColorScheme.onSurface.withOpacity(0.12)` is used.
final
Color
hourMinuteColor
;
/// The color of the day period text that represents AM/PM.
///
/// If [dayPeriodTextColor] is a [MaterialStateColor], then the effective
/// text color can depend on the [MaterialState.selected] state, i.e. if the
/// text is selected or not.
///
/// By default the overall theme's [ColorScheme.primary] color is used when
/// the text is selected and `ColorScheme.onSurface.withOpacity(0.60)` when
/// it's not selected.
final
Color
dayPeriodTextColor
;
/// The background color of the AM/PM toggle.
///
/// If [dayPeriodColor] is a [MaterialStateColor], then the effective
/// background color can depend on the [MaterialState.selected] state, i.e.
/// if the segment is selected or not.
///
/// By default, if the segment is selected, the overall theme's
/// `ColorScheme.primary.withOpacity(0.12)` is used when the overall theme's
/// brightness is [Brightness.light] and
/// `ColorScheme.primary.withOpacity(0.24)` is used when the overall theme's
/// brightness is [Brightness.dark].
/// If the segment is not selected, [Colors.transparent] is used to allow the
/// [Dialog]'s color to be used.
final
Color
dayPeriodColor
;
/// The color of the time picker dial's hand.
///
/// If this is null, the time picker defaults to the overall theme's
/// [ColorScheme.primary].
final
Color
dialHandColor
;
/// The background color of the time picker dial.
///
/// If this is null, the time picker defaults to the overall theme's
/// [ColorScheme.primary].
final
Color
dialBackgroundColor
;
/// The color of the entry mode [IconButton].
///
/// If this is null, the time picker defaults to
/// ```
/// Theme.of(context).colorScheme.onSurface.withOpacity(
/// Theme.of(context).colorScheme.brightness == Brightness.dark ? 1.0 : 0.6,
/// )
/// ```
final
Color
entryModeIconColor
;
/// Used to configure the [TextStyle]s for the hour/minute controls.
///
/// If this is null, the time picker defaults to the overall theme's
/// [TextTheme.headline3].
final
TextStyle
hourMinuteTextStyle
;
/// Used to configure the [TextStyle]s for the day period control.
///
/// If this is null, the time picker defaults to the overall theme's
/// [TextTheme.subtitle1].
final
TextStyle
dayPeriodTextStyle
;
/// Used to configure the [TextStyle]s for the helper text in the header.
///
/// If this is null, the time picker defaults to the overall theme's
/// [TextTheme.overline].
final
TextStyle
helpTextStyle
;
/// The shape of the [Dialog] that the time picker is presented in.
///
/// If this is null, the time picker defaults to
/// `RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4.0)))`.
final
ShapeBorder
shape
;
/// The shape of the hour and minute controls that the time picker uses.
///
/// If this is null, the time picker defaults to
/// `RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4.0)))`.
final
ShapeBorder
hourMinuteShape
;
/// The shape of the day period that the time picker uses.
///
/// If this is null, the time picker defaults to:
/// ```
/// RoundedRectangleBorder(
/// borderRadius: BorderRadius.all(Radius.circular(4.0)),
/// side: BorderSide(),
/// )
/// ```
final
OutlinedBorder
dayPeriodShape
;
/// The color and weight of the day period's outline.
///
/// If this is null, the time picker defaults to:
/// ```
/// BorderSide(
/// color: Color.alphaBlend(colorScheme.onBackground.withOpacity(0.38), colorScheme.surface),
/// )
/// ```
final
BorderSide
dayPeriodBorderSide
;
/// The input decoration theme for the [TextField]s in the time picker.
///
/// If this is null, the time picker provides its own defaults.
final
InputDecorationTheme
inputDecorationTheme
;
/// Creates a copy of this object with the given fields replaced with the
/// new values.
TimePickerThemeData
copyWith
({
Color
backgroundColor
,
Color
hourMinuteTextColor
,
Color
hourMinuteColor
,
Color
hourMinuteUnselectedTextColor
,
Color
hourMinuteUnselectedColor
,
Color
dayPeriodTextColor
,
Color
dayPeriodColor
,
Color
dialHandColor
,
Color
dialBackgroundColor
,
Color
entryModeIconColor
,
TextStyle
hourMinuteTextStyle
,
TextStyle
dayPeriodTextStyle
,
TextStyle
helpTextStyle
,
ShapeBorder
shape
,
ShapeBorder
hourMinuteShape
,
OutlinedBorder
dayPeriodShape
,
BorderSide
dayPeriodBorderSide
,
InputDecorationTheme
inputDecorationTheme
,
})
{
return
TimePickerThemeData
(
backgroundColor:
backgroundColor
??
this
.
backgroundColor
,
hourMinuteTextColor:
hourMinuteTextColor
??
this
.
hourMinuteTextColor
,
hourMinuteColor:
hourMinuteColor
??
this
.
hourMinuteColor
,
dayPeriodTextColor:
dayPeriodTextColor
??
this
.
dayPeriodTextColor
,
dayPeriodColor:
dayPeriodColor
??
this
.
dayPeriodColor
,
dialHandColor:
dialHandColor
??
this
.
dialHandColor
,
dialBackgroundColor:
dialBackgroundColor
??
this
.
dialBackgroundColor
,
entryModeIconColor:
entryModeIconColor
??
this
.
entryModeIconColor
,
hourMinuteTextStyle:
hourMinuteTextStyle
??
this
.
hourMinuteTextStyle
,
dayPeriodTextStyle:
dayPeriodTextStyle
??
this
.
dayPeriodTextStyle
,
helpTextStyle:
helpTextStyle
??
this
.
helpTextStyle
,
shape:
shape
??
this
.
shape
,
hourMinuteShape:
hourMinuteShape
??
this
.
hourMinuteShape
,
dayPeriodShape:
dayPeriodShape
??
this
.
dayPeriodShape
,
dayPeriodBorderSide:
dayPeriodBorderSide
??
this
.
dayPeriodBorderSide
,
inputDecorationTheme:
inputDecorationTheme
??
this
.
inputDecorationTheme
,
);
}
/// Linearly interpolate between two time picker themes.
///
/// The argument `t` must not be null.
///
/// {@macro dart.ui.shadow.lerp}
static
TimePickerThemeData
lerp
(
TimePickerThemeData
a
,
TimePickerThemeData
b
,
double
t
)
{
assert
(
t
!=
null
);
// Workaround since BorderSide's lerp does not allow for null arguments.
BorderSide
lerpedBorderSide
;
if
(
a
?.
dayPeriodBorderSide
==
null
&&
b
?.
dayPeriodBorderSide
==
null
)
{
lerpedBorderSide
=
null
;
}
else
if
(
a
?.
dayPeriodBorderSide
==
null
)
{
lerpedBorderSide
=
b
?.
dayPeriodBorderSide
;
}
else
if
(
b
?.
dayPeriodBorderSide
==
null
)
{
lerpedBorderSide
=
a
?.
dayPeriodBorderSide
;
}
else
{
lerpedBorderSide
=
BorderSide
.
lerp
(
a
?.
dayPeriodBorderSide
,
b
?.
dayPeriodBorderSide
,
t
);
}
return
TimePickerThemeData
(
backgroundColor:
Color
.
lerp
(
a
?.
backgroundColor
,
b
?.
backgroundColor
,
t
),
hourMinuteTextColor:
Color
.
lerp
(
a
?.
hourMinuteTextColor
,
b
?.
hourMinuteTextColor
,
t
),
hourMinuteColor:
Color
.
lerp
(
a
?.
hourMinuteColor
,
b
?.
hourMinuteColor
,
t
),
dayPeriodTextColor:
Color
.
lerp
(
a
?.
dayPeriodTextColor
,
b
?.
dayPeriodTextColor
,
t
),
dayPeriodColor:
Color
.
lerp
(
a
?.
dayPeriodColor
,
b
?.
dayPeriodColor
,
t
),
dialHandColor:
Color
.
lerp
(
a
?.
dialHandColor
,
b
?.
dialHandColor
,
t
),
dialBackgroundColor:
Color
.
lerp
(
a
?.
dialBackgroundColor
,
b
?.
dialBackgroundColor
,
t
),
entryModeIconColor:
Color
.
lerp
(
a
?.
entryModeIconColor
,
b
?.
entryModeIconColor
,
t
),
hourMinuteTextStyle:
TextStyle
.
lerp
(
a
?.
hourMinuteTextStyle
,
b
?.
hourMinuteTextStyle
,
t
),
dayPeriodTextStyle:
TextStyle
.
lerp
(
a
?.
dayPeriodTextStyle
,
b
?.
dayPeriodTextStyle
,
t
),
helpTextStyle:
TextStyle
.
lerp
(
a
?.
helpTextStyle
,
b
?.
helpTextStyle
,
t
),
shape:
ShapeBorder
.
lerp
(
a
?.
shape
,
b
?.
shape
,
t
),
hourMinuteShape:
ShapeBorder
.
lerp
(
a
?.
hourMinuteShape
,
b
?.
hourMinuteShape
,
t
),
dayPeriodShape:
ShapeBorder
.
lerp
(
a
?.
dayPeriodShape
,
b
?.
dayPeriodShape
,
t
)
as
OutlinedBorder
,
dayPeriodBorderSide:
lerpedBorderSide
,
inputDecorationTheme:
t
<
0.5
?
a
.
inputDecorationTheme
:
b
.
inputDecorationTheme
,
);
}
@override
int
get
hashCode
{
return
hashValues
(
backgroundColor
,
hourMinuteTextColor
,
hourMinuteColor
,
dayPeriodTextColor
,
dayPeriodColor
,
dialHandColor
,
dialBackgroundColor
,
entryModeIconColor
,
hourMinuteTextStyle
,
dayPeriodTextStyle
,
helpTextStyle
,
shape
,
hourMinuteShape
,
dayPeriodShape
,
dayPeriodBorderSide
,
inputDecorationTheme
,
);
}
@override
bool
operator
==(
Object
other
)
{
if
(
identical
(
this
,
other
))
return
true
;
if
(
other
.
runtimeType
!=
runtimeType
)
return
false
;
return
other
is
TimePickerThemeData
&&
other
.
backgroundColor
==
backgroundColor
&&
other
.
hourMinuteTextColor
==
hourMinuteTextColor
&&
other
.
hourMinuteColor
==
hourMinuteColor
&&
other
.
dayPeriodTextColor
==
dayPeriodTextColor
&&
other
.
dayPeriodColor
==
dayPeriodColor
&&
other
.
dialHandColor
==
dialHandColor
&&
other
.
dialBackgroundColor
==
dialBackgroundColor
&&
other
.
entryModeIconColor
==
entryModeIconColor
&&
other
.
hourMinuteTextStyle
==
hourMinuteTextStyle
&&
other
.
dayPeriodTextStyle
==
dayPeriodTextStyle
&&
other
.
helpTextStyle
==
helpTextStyle
&&
other
.
shape
==
shape
&&
other
.
hourMinuteShape
==
hourMinuteShape
&&
other
.
dayPeriodShape
==
dayPeriodShape
&&
other
.
dayPeriodBorderSide
==
dayPeriodBorderSide
&&
other
.
inputDecorationTheme
==
inputDecorationTheme
;
}
@override
void
debugFillProperties
(
DiagnosticPropertiesBuilder
properties
)
{
super
.
debugFillProperties
(
properties
);
properties
.
add
(
ColorProperty
(
'backgroundColor'
,
backgroundColor
,
defaultValue:
null
));
properties
.
add
(
ColorProperty
(
'hourMinuteTextColor'
,
hourMinuteTextColor
,
defaultValue:
null
));
properties
.
add
(
ColorProperty
(
'hourMinuteColor'
,
hourMinuteColor
,
defaultValue:
null
));
properties
.
add
(
ColorProperty
(
'dayPeriodTextColor'
,
dayPeriodTextColor
,
defaultValue:
null
));
properties
.
add
(
ColorProperty
(
'dayPeriodColor'
,
dayPeriodColor
,
defaultValue:
null
));
properties
.
add
(
ColorProperty
(
'dialHandColor'
,
dialHandColor
,
defaultValue:
null
));
properties
.
add
(
ColorProperty
(
'dialBackgroundColor'
,
dialBackgroundColor
,
defaultValue:
null
));
properties
.
add
(
ColorProperty
(
'entryModeIconColor'
,
entryModeIconColor
,
defaultValue:
null
));
properties
.
add
(
DiagnosticsProperty
<
TextStyle
>(
'hourMinuteTextStyle'
,
hourMinuteTextStyle
,
defaultValue:
null
));
properties
.
add
(
DiagnosticsProperty
<
TextStyle
>(
'dayPeriodTextStyle'
,
dayPeriodTextStyle
,
defaultValue:
null
));
properties
.
add
(
DiagnosticsProperty
<
TextStyle
>(
'helpTextStyle'
,
helpTextStyle
,
defaultValue:
null
));
properties
.
add
(
DiagnosticsProperty
<
ShapeBorder
>(
'shape'
,
shape
,
defaultValue:
null
));
properties
.
add
(
DiagnosticsProperty
<
ShapeBorder
>(
'hourMinuteShape'
,
hourMinuteShape
,
defaultValue:
null
));
properties
.
add
(
DiagnosticsProperty
<
ShapeBorder
>(
'dayPeriodShape'
,
dayPeriodShape
,
defaultValue:
null
));
properties
.
add
(
DiagnosticsProperty
<
BorderSide
>(
'dayPeriodBorderSide'
,
dayPeriodBorderSide
,
defaultValue:
null
));
properties
.
add
(
DiagnosticsProperty
<
InputDecorationTheme
>(
'inputDecorationTheme'
,
inputDecorationTheme
,
defaultValue:
null
));
}
}
/// An inherited widget that defines the configuration for time pickers
/// displayed using [showTimePicker] in this widget's subtree.
///
/// Values specified here are used for time picker properties that are not
/// given an explicit non-null value.
class
TimePickerTheme
extends
InheritedTheme
{
/// Creates a time picker theme that controls the configurations for
/// time pickers displayed in its widget subtree.
const
TimePickerTheme
({
Key
key
,
@required
this
.
data
,
Widget
child
,
})
:
assert
(
data
!=
null
),
super
(
key:
key
,
child:
child
);
/// The properties for descendant time picker widgets.
final
TimePickerThemeData
data
;
/// The [data] value of the closest [TimePickerTheme] ancestor.
///
/// If there is no ancestor, it returns [ThemeData.timePickerTheme].
/// Applications can assume that the returned value will not be null.
///
/// Typical usage is as follows:
///
/// ```dart
/// TimePickerThemeData theme = TimePickerTheme.of(context);
/// ```
static
TimePickerThemeData
of
(
BuildContext
context
)
{
final
TimePickerTheme
timePickerTheme
=
context
.
dependOnInheritedWidgetOfExactType
<
TimePickerTheme
>();
return
timePickerTheme
?.
data
??
Theme
.
of
(
context
).
timePickerTheme
;
}
@override
Widget
wrap
(
BuildContext
context
,
Widget
child
)
{
final
TimePickerTheme
ancestorTheme
=
context
.
findAncestorWidgetOfExactType
<
TimePickerTheme
>();
return
identical
(
this
,
ancestorTheme
)
?
child
:
TimePickerTheme
(
data:
data
,
child:
child
);
}
@override
bool
updateShouldNotify
(
TimePickerTheme
oldWidget
)
=>
data
!=
oldWidget
.
data
;
}
packages/flutter/test/material/theme_data_test.dart
View file @
e676024d
...
@@ -282,6 +282,7 @@ void main() {
...
@@ -282,6 +282,7 @@ void main() {
dividerTheme:
const
DividerThemeData
(
color:
Colors
.
black
),
dividerTheme:
const
DividerThemeData
(
color:
Colors
.
black
),
buttonBarTheme:
const
ButtonBarThemeData
(
alignment:
MainAxisAlignment
.
start
),
buttonBarTheme:
const
ButtonBarThemeData
(
alignment:
MainAxisAlignment
.
start
),
bottomNavigationBarTheme:
const
BottomNavigationBarThemeData
(
type:
BottomNavigationBarType
.
fixed
),
bottomNavigationBarTheme:
const
BottomNavigationBarThemeData
(
type:
BottomNavigationBarType
.
fixed
),
timePickerTheme:
const
TimePickerThemeData
(
backgroundColor:
Colors
.
black
),
fixTextFieldOutlineLabel:
false
,
fixTextFieldOutlineLabel:
false
,
);
);
...
@@ -363,6 +364,7 @@ void main() {
...
@@ -363,6 +364,7 @@ void main() {
dividerTheme:
const
DividerThemeData
(
color:
Colors
.
white
),
dividerTheme:
const
DividerThemeData
(
color:
Colors
.
white
),
buttonBarTheme:
const
ButtonBarThemeData
(
alignment:
MainAxisAlignment
.
end
),
buttonBarTheme:
const
ButtonBarThemeData
(
alignment:
MainAxisAlignment
.
end
),
bottomNavigationBarTheme:
const
BottomNavigationBarThemeData
(
type:
BottomNavigationBarType
.
shifting
),
bottomNavigationBarTheme:
const
BottomNavigationBarThemeData
(
type:
BottomNavigationBarType
.
shifting
),
timePickerTheme:
const
TimePickerThemeData
(
backgroundColor:
Colors
.
white
),
fixTextFieldOutlineLabel:
true
,
fixTextFieldOutlineLabel:
true
,
);
);
...
@@ -430,6 +432,7 @@ void main() {
...
@@ -430,6 +432,7 @@ void main() {
dividerTheme:
otherTheme
.
dividerTheme
,
dividerTheme:
otherTheme
.
dividerTheme
,
buttonBarTheme:
otherTheme
.
buttonBarTheme
,
buttonBarTheme:
otherTheme
.
buttonBarTheme
,
bottomNavigationBarTheme:
otherTheme
.
bottomNavigationBarTheme
,
bottomNavigationBarTheme:
otherTheme
.
bottomNavigationBarTheme
,
timePickerTheme:
otherTheme
.
timePickerTheme
,
fixTextFieldOutlineLabel:
otherTheme
.
fixTextFieldOutlineLabel
,
fixTextFieldOutlineLabel:
otherTheme
.
fixTextFieldOutlineLabel
,
);
);
...
@@ -499,6 +502,7 @@ void main() {
...
@@ -499,6 +502,7 @@ void main() {
expect
(
themeDataCopy
.
dividerTheme
,
equals
(
otherTheme
.
dividerTheme
));
expect
(
themeDataCopy
.
dividerTheme
,
equals
(
otherTheme
.
dividerTheme
));
expect
(
themeDataCopy
.
buttonBarTheme
,
equals
(
otherTheme
.
buttonBarTheme
));
expect
(
themeDataCopy
.
buttonBarTheme
,
equals
(
otherTheme
.
buttonBarTheme
));
expect
(
themeDataCopy
.
bottomNavigationBarTheme
,
equals
(
otherTheme
.
bottomNavigationBarTheme
));
expect
(
themeDataCopy
.
bottomNavigationBarTheme
,
equals
(
otherTheme
.
bottomNavigationBarTheme
));
expect
(
themeDataCopy
.
timePickerTheme
,
equals
(
otherTheme
.
timePickerTheme
));
expect
(
themeDataCopy
.
fixTextFieldOutlineLabel
,
equals
(
otherTheme
.
fixTextFieldOutlineLabel
));
expect
(
themeDataCopy
.
fixTextFieldOutlineLabel
,
equals
(
otherTheme
.
fixTextFieldOutlineLabel
));
});
});
...
...
packages/flutter/test/material/time_picker_test.dart
View file @
e676024d
...
@@ -6,15 +6,12 @@
...
@@ -6,15 +6,12 @@
@TestOn
(
'!chrome'
)
// entire file needs triage.
@TestOn
(
'!chrome'
)
// entire file needs triage.
import
'dart:async'
;
import
'dart:async'
;
import
'dart:ui'
as
ui
;
import
'package:flutter/material.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter/widgets.dart'
;
import
'package:flutter/widgets.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
import
'../rendering/mock_canvas.dart'
;
import
'../rendering/recording_canvas.dart'
;
import
'../widgets/semantics_tester.dart'
;
import
'../widgets/semantics_tester.dart'
;
import
'feedback_tester.dart'
;
import
'feedback_tester.dart'
;
...
@@ -23,10 +20,16 @@ final Finder _minuteControl = find.byWidgetPredicate((Widget widget) => '${widge
...
@@ -23,10 +20,16 @@ final Finder _minuteControl = find.byWidgetPredicate((Widget widget) => '${widge
final
Finder
_timePickerDialog
=
find
.
byWidgetPredicate
((
Widget
widget
)
=>
'
${widget.runtimeType}
'
==
'_TimePickerDialog'
);
final
Finder
_timePickerDialog
=
find
.
byWidgetPredicate
((
Widget
widget
)
=>
'
${widget.runtimeType}
'
==
'_TimePickerDialog'
);
class
_TimePickerLauncher
extends
StatelessWidget
{
class
_TimePickerLauncher
extends
StatelessWidget
{
const
_TimePickerLauncher
({
Key
key
,
this
.
onChanged
,
this
.
locale
})
:
super
(
key:
key
);
const
_TimePickerLauncher
({
Key
key
,
this
.
onChanged
,
this
.
locale
,
this
.
entryMode
=
TimePickerEntryMode
.
dial
,
})
:
super
(
key:
key
);
final
ValueChanged
<
TimeOfDay
>
onChanged
;
final
ValueChanged
<
TimeOfDay
>
onChanged
;
final
Locale
locale
;
final
Locale
locale
;
final
TimePickerEntryMode
entryMode
;
@override
@override
Widget
build
(
BuildContext
context
)
{
Widget
build
(
BuildContext
context
)
{
...
@@ -35,17 +38,18 @@ class _TimePickerLauncher extends StatelessWidget {
...
@@ -35,17 +38,18 @@ class _TimePickerLauncher extends StatelessWidget {
home:
Material
(
home:
Material
(
child:
Center
(
child:
Center
(
child:
Builder
(
child:
Builder
(
builder:
(
BuildContext
context
)
{
builder:
(
BuildContext
context
)
{
return
RaisedButton
(
return
RaisedButton
(
child:
const
Text
(
'X'
),
child:
const
Text
(
'X'
),
onPressed:
()
async
{
onPressed:
()
async
{
onChanged
(
await
showTimePicker
(
onChanged
(
await
showTimePicker
(
context:
context
,
context:
context
,
initialTime:
const
TimeOfDay
(
hour:
7
,
minute:
0
),
initialTime:
const
TimeOfDay
(
hour:
7
,
minute:
0
),
));
initialEntryMode:
entryMode
,
},
));
);
},
}
);
}
),
),
),
),
),
),
...
@@ -53,11 +57,15 @@ class _TimePickerLauncher extends StatelessWidget {
...
@@ -53,11 +57,15 @@ class _TimePickerLauncher extends StatelessWidget {
}
}
}
}
Future
<
Offset
>
startPicker
(
WidgetTester
tester
,
ValueChanged
<
TimeOfDay
>
onChanged
)
async
{
Future
<
Offset
>
startPicker
(
await
tester
.
pumpWidget
(
_TimePickerLauncher
(
onChanged:
onChanged
,
locale:
const
Locale
(
'en'
,
'US'
)));
WidgetTester
tester
,
ValueChanged
<
TimeOfDay
>
onChanged
,
{
TimePickerEntryMode
entryMode
=
TimePickerEntryMode
.
dial
,
})
async
{
await
tester
.
pumpWidget
(
_TimePickerLauncher
(
onChanged:
onChanged
,
locale:
const
Locale
(
'en'
,
'US'
),
entryMode:
entryMode
));
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
tester
.
getCenter
(
find
.
byKey
(
const
ValueKey
<
String
>(
'time-picker-dial'
)))
;
return
entryMode
==
TimePickerEntryMode
.
dial
?
tester
.
getCenter
(
find
.
byKey
(
const
ValueKey
<
String
>(
'time-picker-dial'
)))
:
null
;
}
}
Future
<
void
>
finishPicker
(
WidgetTester
tester
)
async
{
Future
<
void
>
finishPicker
(
WidgetTester
tester
)
async
{
...
@@ -67,9 +75,13 @@ Future<void> finishPicker(WidgetTester tester) async {
...
@@ -67,9 +75,13 @@ Future<void> finishPicker(WidgetTester tester) async {
}
}
void
main
(
)
{
void
main
(
)
{
group
(
'Time picker'
,
()
{
group
(
'Time picker
- Dial
'
,
()
{
_tests
();
_tests
();
});
});
group
(
'Time picker - Input'
,
()
{
_testsInput
();
});
}
}
void
_tests
(
)
{
void
_tests
(
)
{
...
@@ -170,6 +182,34 @@ void _tests() {
...
@@ -170,6 +182,34 @@ void _tests() {
expect
(
result
,
equals
(
const
TimeOfDay
(
hour:
9
,
minute:
15
)));
expect
(
result
,
equals
(
const
TimeOfDay
(
hour:
9
,
minute:
15
)));
});
});
testWidgets
(
'tap-select rounds down to nearest 5 minute increment'
,
(
WidgetTester
tester
)
async
{
TimeOfDay
result
;
final
Offset
center
=
await
startPicker
(
tester
,
(
TimeOfDay
time
)
{
result
=
time
;
});
final
Offset
hour6
=
Offset
(
center
.
dx
,
center
.
dy
+
50.0
);
// 6:00
final
Offset
min46
=
Offset
(
center
.
dx
-
50.0
,
center
.
dy
-
5
);
// 46 mins
await
tester
.
tapAt
(
hour6
);
await
tester
.
pump
(
const
Duration
(
milliseconds:
50
));
await
tester
.
tapAt
(
min46
);
await
finishPicker
(
tester
);
expect
(
result
,
equals
(
const
TimeOfDay
(
hour:
6
,
minute:
45
)));
});
testWidgets
(
'tap-select rounds up to nearest 5 minute increment'
,
(
WidgetTester
tester
)
async
{
TimeOfDay
result
;
final
Offset
center
=
await
startPicker
(
tester
,
(
TimeOfDay
time
)
{
result
=
time
;
});
final
Offset
hour6
=
Offset
(
center
.
dx
,
center
.
dy
+
50.0
);
// 6:00
final
Offset
min48
=
Offset
(
center
.
dx
-
50.0
,
center
.
dy
-
15
);
// 48 mins
await
tester
.
tapAt
(
hour6
);
await
tester
.
pump
(
const
Duration
(
milliseconds:
50
));
await
tester
.
tapAt
(
min48
);
await
finishPicker
(
tester
);
expect
(
result
,
equals
(
const
TimeOfDay
(
hour:
6
,
minute:
50
)));
});
group
(
'haptic feedback'
,
()
{
group
(
'haptic feedback'
,
()
{
const
Duration
kFastFeedbackInterval
=
Duration
(
milliseconds:
10
);
const
Duration
kFastFeedbackInterval
=
Duration
(
milliseconds:
10
);
const
Duration
kSlowFeedbackInterval
=
Duration
(
milliseconds:
200
);
const
Duration
kSlowFeedbackInterval
=
Duration
(
milliseconds:
200
);
...
@@ -256,64 +296,18 @@ void _tests() {
...
@@ -256,64 +296,18 @@ void _tests() {
});
});
const
List
<
String
>
labels12To11
=
<
String
>[
'12'
,
'1'
,
'2'
,
'3'
,
'4'
,
'5'
,
'6'
,
'7'
,
'8'
,
'9'
,
'10'
,
'11'
];
const
List
<
String
>
labels12To11
=
<
String
>[
'12'
,
'1'
,
'2'
,
'3'
,
'4'
,
'5'
,
'6'
,
'7'
,
'8'
,
'9'
,
'10'
,
'11'
];
const
List
<
String
>
labels12To11TwoDigit
=
<
String
>[
'12'
,
'01'
,
'02'
,
'03'
,
'04'
,
'05'
,
'06'
,
'07'
,
'08'
,
'09'
,
'10'
,
'11'
];
const
List
<
String
>
labels00To22
=
<
String
>[
'00'
,
'02'
,
'04'
,
'06'
,
'08'
,
'10'
,
'12'
,
'14'
,
'16'
,
'18'
,
'20'
,
'22'
];
const
List
<
String
>
labels00To23
=
<
String
>[
'00'
,
'13'
,
'14'
,
'15'
,
'16'
,
'17'
,
'18'
,
'19'
,
'20'
,
'21'
,
'22'
,
'23'
];
Future
<
void
>
mediaQueryBoilerplate
(
WidgetTester
tester
,
bool
alwaysUse24HourFormat
,
{
TimeOfDay
initialTime
=
const
TimeOfDay
(
hour:
7
,
minute:
0
),
double
textScaleFactor
=
1.0
,
})
async
{
await
tester
.
pumpWidget
(
Localizations
(
locale:
const
Locale
(
'en'
,
'US'
),
delegates:
const
<
LocalizationsDelegate
<
dynamic
>>[
DefaultMaterialLocalizations
.
delegate
,
DefaultWidgetsLocalizations
.
delegate
,
],
child:
MediaQuery
(
data:
MediaQueryData
(
alwaysUse24HourFormat:
alwaysUse24HourFormat
,
textScaleFactor:
textScaleFactor
,
),
child:
Material
(
child:
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
Navigator
(
onGenerateRoute:
(
RouteSettings
settings
)
{
return
MaterialPageRoute
<
void
>(
builder:
(
BuildContext
context
)
{
return
FlatButton
(
onPressed:
()
{
showTimePicker
(
context:
context
,
initialTime:
initialTime
);
},
child:
const
Text
(
'X'
),
);
});
},
),
),
),
),
),
);
await
tester
.
tap
(
find
.
text
(
'X'
));
await
tester
.
pumpAndSettle
();
}
testWidgets
(
'respects MediaQueryData.alwaysUse24HourFormat == false'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
'respects MediaQueryData.alwaysUse24HourFormat == false'
,
(
WidgetTester
tester
)
async
{
await
mediaQueryBoilerplate
(
tester
,
false
);
await
mediaQueryBoilerplate
(
tester
,
false
);
final
CustomPaint
dialPaint
=
tester
.
widget
(
findDialPaint
);
final
CustomPaint
dialPaint
=
tester
.
widget
(
findDialPaint
);
final
dynamic
dialPainter
=
dialPaint
.
painter
;
final
dynamic
dialPainter
=
dialPaint
.
painter
;
final
List
<
dynamic
>
primaryOuterLabels
=
dialPainter
.
primaryOuterLabels
as
List
<
dynamic
>;
final
List
<
dynamic
>
primaryLabels
=
dialPainter
.
primaryLabels
as
List
<
dynamic
>;
expect
(
primaryOuterLabels
.
map
<
String
>((
dynamic
tp
)
=>
tp
.
painter
.
text
.
text
as
String
),
labels12To11
);
expect
(
primaryLabels
.
map
<
String
>((
dynamic
tp
)
=>
tp
.
painter
.
text
.
text
as
String
),
labels12To11
);
expect
(
dialPainter
.
primaryInnerLabels
,
null
);
final
List
<
dynamic
>
secondaryOuterLabels
=
dialPainter
.
secondaryOuterLabels
as
List
<
dynamic
>;
final
List
<
dynamic
>
secondaryLabels
=
dialPainter
.
secondaryLabels
as
List
<
dynamic
>;
expect
(
secondaryOuterLabels
.
map
<
String
>((
dynamic
tp
)
=>
tp
.
painter
.
text
.
text
as
String
),
labels12To11
);
expect
(
secondaryLabels
.
map
<
String
>((
dynamic
tp
)
=>
tp
.
painter
.
text
.
text
as
String
),
labels12To11
);
expect
(
dialPainter
.
secondaryInnerLabels
,
null
);
});
});
testWidgets
(
'respects MediaQueryData.alwaysUse24HourFormat == true'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
'respects MediaQueryData.alwaysUse24HourFormat == true'
,
(
WidgetTester
tester
)
async
{
...
@@ -321,15 +315,11 @@ void _tests() {
...
@@ -321,15 +315,11 @@ void _tests() {
final
CustomPaint
dialPaint
=
tester
.
widget
(
findDialPaint
);
final
CustomPaint
dialPaint
=
tester
.
widget
(
findDialPaint
);
final
dynamic
dialPainter
=
dialPaint
.
painter
;
final
dynamic
dialPainter
=
dialPaint
.
painter
;
final
List
<
dynamic
>
primaryOuterLabels
=
dialPainter
.
primaryOuterLabels
as
List
<
dynamic
>;
final
List
<
dynamic
>
primaryLabels
=
dialPainter
.
primaryLabels
as
List
<
dynamic
>;
expect
(
primaryOuterLabels
.
map
<
String
>((
dynamic
tp
)
=>
tp
.
painter
.
text
.
text
as
String
),
labels00To23
);
expect
(
primaryLabels
.
map
<
String
>((
dynamic
tp
)
=>
tp
.
painter
.
text
.
text
as
String
),
labels00To22
);
final
List
<
dynamic
>
primaryInnerLabels
=
dialPainter
.
primaryInnerLabels
as
List
<
dynamic
>;
expect
(
primaryInnerLabels
.
map
<
String
>((
dynamic
tp
)
=>
tp
.
painter
.
text
.
text
as
String
),
labels12To11TwoDigit
);
final
List
<
dynamic
>
secondaryLabels
=
dialPainter
.
secondaryLabels
as
List
<
dynamic
>;
expect
(
secondaryLabels
.
map
<
String
>((
dynamic
tp
)
=>
tp
.
painter
.
text
.
text
as
String
),
labels00To22
);
final
List
<
dynamic
>
secondaryOuterLabels
=
dialPainter
.
secondaryOuterLabels
as
List
<
dynamic
>;
expect
(
secondaryOuterLabels
.
map
<
String
>((
dynamic
tp
)
=>
tp
.
painter
.
text
.
text
as
String
),
labels00To23
);
final
List
<
dynamic
>
secondaryInnerLabels
=
dialPainter
.
secondaryInnerLabels
as
List
<
dynamic
>;
expect
(
secondaryInnerLabels
.
map
<
String
>((
dynamic
tp
)
=>
tp
.
painter
.
text
.
text
as
String
),
labels12To11TwoDigit
);
});
});
testWidgets
(
'provides semantics information for AM/PM indicator'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
'provides semantics information for AM/PM indicator'
,
(
WidgetTester
tester
)
async
{
...
@@ -347,10 +337,10 @@ void _tests() {
...
@@ -347,10 +337,10 @@ void _tests() {
await
mediaQueryBoilerplate
(
tester
,
true
);
await
mediaQueryBoilerplate
(
tester
,
true
);
expect
(
semantics
,
isNot
(
includesNodeWith
(
label:
':'
)));
expect
(
semantics
,
isNot
(
includesNodeWith
(
label:
':'
)));
expect
(
semantics
.
nodesWith
(
value:
'00'
),
hasLength
(
2
),
expect
(
semantics
.
nodesWith
(
value:
'00'
),
hasLength
(
1
),
reason:
'00 appears once in the header
, then again in the dial
'
);
reason:
'00 appears once in the header'
);
expect
(
semantics
.
nodesWith
(
value:
'07'
),
hasLength
(
2
),
expect
(
semantics
.
nodesWith
(
value:
'07'
),
hasLength
(
1
),
reason:
'07 appears once in the header
, then again in the dial
'
);
reason:
'07 appears once in the header'
);
expect
(
semantics
,
includesNodeWith
(
label:
'CANCEL'
));
expect
(
semantics
,
includesNodeWith
(
label:
'CANCEL'
));
expect
(
semantics
,
includesNodeWith
(
label:
'OK'
));
expect
(
semantics
,
includesNodeWith
(
label:
'OK'
));
...
@@ -361,82 +351,6 @@ void _tests() {
...
@@ -361,82 +351,6 @@ void _tests() {
semantics
.
dispose
();
semantics
.
dispose
();
});
});
testWidgets
(
'provides semantics information for hours'
,
(
WidgetTester
tester
)
async
{
final
SemanticsTester
semantics
=
SemanticsTester
(
tester
);
await
mediaQueryBoilerplate
(
tester
,
true
);
final
CustomPaint
dialPaint
=
tester
.
widget
(
find
.
byKey
(
const
ValueKey
<
String
>(
'time-picker-dial'
)));
final
CustomPainter
dialPainter
=
dialPaint
.
painter
;
final
_CustomPainterSemanticsTester
painterTester
=
_CustomPainterSemanticsTester
(
tester
,
dialPainter
,
semantics
);
painterTester
.
addLabel
(
'00'
,
86.0
,
0.0
,
134.0
,
48.0
);
painterTester
.
addLabel
(
'13'
,
129.0
,
11.5
,
177.0
,
59.5
);
painterTester
.
addLabel
(
'14'
,
160.5
,
43.0
,
208.5
,
91.0
);
painterTester
.
addLabel
(
'15'
,
172.0
,
86.0
,
220.0
,
134.0
);
painterTester
.
addLabel
(
'16'
,
160.5
,
129.0
,
208.5
,
177.0
);
painterTester
.
addLabel
(
'17'
,
129.0
,
160.5
,
177.0
,
208.5
);
painterTester
.
addLabel
(
'18'
,
86.0
,
172.0
,
134.0
,
220.0
);
painterTester
.
addLabel
(
'19'
,
43.0
,
160.5
,
91.0
,
208.5
);
painterTester
.
addLabel
(
'20'
,
11.5
,
129.0
,
59.5
,
177.0
);
painterTester
.
addLabel
(
'21'
,
0.0
,
86.0
,
48.0
,
134.0
);
painterTester
.
addLabel
(
'22'
,
11.5
,
43.0
,
59.5
,
91.0
);
painterTester
.
addLabel
(
'23'
,
43.0
,
11.5
,
91.0
,
59.5
);
painterTester
.
addLabel
(
'12'
,
86.0
,
36.0
,
134.0
,
84.0
);
painterTester
.
addLabel
(
'01'
,
111.0
,
42.7
,
159.0
,
90.7
);
painterTester
.
addLabel
(
'02'
,
129.3
,
61.0
,
177.3
,
109.0
);
painterTester
.
addLabel
(
'03'
,
136.0
,
86.0
,
184.0
,
134.0
);
painterTester
.
addLabel
(
'04'
,
129.3
,
111.0
,
177.3
,
159.0
);
painterTester
.
addLabel
(
'05'
,
111.0
,
129.3
,
159.0
,
177.3
);
painterTester
.
addLabel
(
'06'
,
86.0
,
136.0
,
134.0
,
184.0
);
painterTester
.
addLabel
(
'07'
,
61.0
,
129.3
,
109.0
,
177.3
);
painterTester
.
addLabel
(
'08'
,
42.7
,
111.0
,
90.7
,
159.0
);
painterTester
.
addLabel
(
'09'
,
36.0
,
86.0
,
84.0
,
134.0
);
painterTester
.
addLabel
(
'10'
,
42.7
,
61.0
,
90.7
,
109.0
);
painterTester
.
addLabel
(
'11'
,
61.0
,
42.7
,
109.0
,
90.7
);
painterTester
.
assertExpectations
();
semantics
.
dispose
();
});
testWidgets
(
'provides semantics information for minutes'
,
(
WidgetTester
tester
)
async
{
final
SemanticsTester
semantics
=
SemanticsTester
(
tester
);
await
mediaQueryBoilerplate
(
tester
,
true
);
await
tester
.
tap
(
_minuteControl
);
await
tester
.
pumpAndSettle
();
final
CustomPaint
dialPaint
=
tester
.
widget
(
find
.
byKey
(
const
ValueKey
<
String
>(
'time-picker-dial'
)));
final
CustomPainter
dialPainter
=
dialPaint
.
painter
;
final
_CustomPainterSemanticsTester
painterTester
=
_CustomPainterSemanticsTester
(
tester
,
dialPainter
,
semantics
);
painterTester
.
addLabel
(
'00'
,
86.0
,
0.0
,
134.0
,
48.0
);
painterTester
.
addLabel
(
'05'
,
129.0
,
11.5
,
177.0
,
59.5
);
painterTester
.
addLabel
(
'10'
,
160.5
,
43.0
,
208.5
,
91.0
);
painterTester
.
addLabel
(
'15'
,
172.0
,
86.0
,
220.0
,
134.0
);
painterTester
.
addLabel
(
'20'
,
160.5
,
129.0
,
208.5
,
177.0
);
painterTester
.
addLabel
(
'25'
,
129.0
,
160.5
,
177.0
,
208.5
);
painterTester
.
addLabel
(
'30'
,
86.0
,
172.0
,
134.0
,
220.0
);
painterTester
.
addLabel
(
'35'
,
43.0
,
160.5
,
91.0
,
208.5
);
painterTester
.
addLabel
(
'40'
,
11.5
,
129.0
,
59.5
,
177.0
);
painterTester
.
addLabel
(
'45'
,
0.0
,
86.0
,
48.0
,
134.0
);
painterTester
.
addLabel
(
'50'
,
11.5
,
43.0
,
59.5
,
91.0
);
painterTester
.
addLabel
(
'55'
,
43.0
,
11.5
,
91.0
,
59.5
);
painterTester
.
assertExpectations
();
semantics
.
dispose
();
});
testWidgets
(
'picks the right dial ring from widget configuration'
,
(
WidgetTester
tester
)
async
{
await
mediaQueryBoilerplate
(
tester
,
true
,
initialTime:
const
TimeOfDay
(
hour:
12
,
minute:
0
));
dynamic
dialPaint
=
tester
.
widget
(
findDialPaint
);
expect
(
'
${dialPaint.painter.activeRing}
'
,
'_DialRing.inner'
);
await
tester
.
pumpWidget
(
Container
());
// make sure previous state isn't reused
await
mediaQueryBoilerplate
(
tester
,
true
,
initialTime:
const
TimeOfDay
(
hour:
0
,
minute:
0
));
dialPaint
=
tester
.
widget
(
findDialPaint
);
expect
(
'
${dialPaint.painter.activeRing}
'
,
'_DialRing.outer'
);
});
testWidgets
(
'can increment and decrement hours'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
'can increment and decrement hours'
,
(
WidgetTester
tester
)
async
{
final
SemanticsTester
semantics
=
SemanticsTester
(
tester
);
final
SemanticsTester
semantics
=
SemanticsTester
(
tester
);
...
@@ -550,21 +464,15 @@ void _tests() {
...
@@ -550,21 +464,15 @@ void _tests() {
});
});
testWidgets
(
'header touch regions are large enough'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
'header touch regions are large enough'
,
(
WidgetTester
tester
)
async
{
// Ensure picker is displayed in portrait mode.
tester
.
binding
.
window
.
physicalSizeTestValue
=
const
Size
(
400
,
800
);
tester
.
binding
.
window
.
devicePixelRatioTestValue
=
1
;
await
mediaQueryBoilerplate
(
tester
,
false
);
await
mediaQueryBoilerplate
(
tester
,
false
);
final
Size
amSize
=
tester
.
getSize
(
find
.
ancestor
(
final
Size
dayPeriodControlSize
=
tester
.
getSize
(
find
.
byWidgetPredicate
((
Widget
w
)
=>
'
${w.runtimeType}
'
==
'_DayPeriodControl'
));
of:
find
.
text
(
'AM'
),
expect
(
dayPeriodControlSize
.
width
,
greaterThanOrEqualTo
(
48.0
));
matching:
find
.
byType
(
InkWell
),
// Height should be double the minimum size to account for both AM/PM stacked.
));
expect
(
dayPeriodControlSize
.
height
,
greaterThanOrEqualTo
(
48.0
*
2
));
expect
(
amSize
.
width
,
greaterThanOrEqualTo
(
48.0
));
expect
(
amSize
.
height
,
greaterThanOrEqualTo
(
48.0
));
final
Size
pmSize
=
tester
.
getSize
(
find
.
ancestor
(
of:
find
.
text
(
'PM'
),
matching:
find
.
byType
(
InkWell
),
));
expect
(
pmSize
.
width
,
greaterThanOrEqualTo
(
48.0
));
expect
(
pmSize
.
height
,
greaterThanOrEqualTo
(
48.0
));
final
Size
hourSize
=
tester
.
getSize
(
find
.
ancestor
(
final
Size
hourSize
=
tester
.
getSize
(
find
.
ancestor
(
of:
find
.
text
(
'7'
),
of:
find
.
text
(
'7'
),
...
@@ -579,6 +487,9 @@ void _tests() {
...
@@ -579,6 +487,9 @@ void _tests() {
));
));
expect
(
minuteSize
.
width
,
greaterThanOrEqualTo
(
48.0
));
expect
(
minuteSize
.
width
,
greaterThanOrEqualTo
(
48.0
));
expect
(
minuteSize
.
height
,
greaterThanOrEqualTo
(
48.0
));
expect
(
minuteSize
.
height
,
greaterThanOrEqualTo
(
48.0
));
tester
.
binding
.
window
.
physicalSizeTestValue
=
null
;
tester
.
binding
.
window
.
devicePixelRatioTestValue
=
null
;
});
});
testWidgets
(
'builder parameter'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
'builder parameter'
,
(
WidgetTester
tester
)
async
{
...
@@ -696,126 +607,166 @@ void _tests() {
...
@@ -696,126 +607,166 @@ void _tests() {
expect
(
nestedObserver
.
pickerCount
,
1
);
expect
(
nestedObserver
.
pickerCount
,
1
);
});
});
testWidgets
(
'text scale affects certain elements and not others'
,
testWidgets
(
'optional text parameters are utilized'
,
(
WidgetTester
tester
)
async
{
(
WidgetTester
tester
)
async
{
const
String
cancelText
=
'Custom Cancel'
;
await
mediaQueryBoilerplate
(
const
String
confirmText
=
'Custom OK'
;
tester
,
const
String
helperText
=
'Custom Help'
;
false
,
await
tester
.
pumpWidget
(
MaterialApp
(
textScaleFactor:
1.0
,
home:
Material
(
initialTime:
const
TimeOfDay
(
hour:
7
,
minute:
41
),
child:
Center
(
);
child:
Builder
(
builder:
(
BuildContext
context
)
{
return
RaisedButton
(
child:
const
Text
(
'X'
),
onPressed:
()
async
{
await
showTimePicker
(
context:
context
,
initialTime:
const
TimeOfDay
(
hour:
7
,
minute:
0
),
cancelText:
cancelText
,
confirmText:
confirmText
,
helpText:
helperText
,
);
},
);
}
),
),
)
));
// Open the picker.
await
tester
.
tap
(
find
.
text
(
'X'
));
await
tester
.
tap
(
find
.
text
(
'X'
));
await
tester
.
pumpAndSettle
();
await
tester
.
pumpAndSettle
(
const
Duration
(
seconds:
1
)
);
final
double
minutesDisplayHeight
=
tester
.
getSize
(
find
.
text
(
'41'
)).
height
;
expect
(
find
.
text
(
cancelText
),
findsOneWidget
);
final
double
amHeight
=
tester
.
getSize
(
find
.
text
(
'AM'
)).
height
;
expect
(
find
.
text
(
confirmText
),
findsOneWidget
);
expect
(
find
.
text
(
helperText
),
findsOneWidget
);
});
await
tester
.
tap
(
find
.
text
(
'OK'
));
// dismiss the dialog
// TODO(rami-a): Re-enable and fix test.
await
tester
.
pumpAndSettle
();
testWidgets
(
'text scale affects certain elements and not others'
,
(
WidgetTester
tester
)
async
{
await
mediaQueryBoilerplate
(
tester
,
false
,
textScaleFactor:
1.0
,
initialTime:
const
TimeOfDay
(
hour:
7
,
minute:
41
),
);
await
tester
.
tap
(
find
.
text
(
'X'
));
await
tester
.
pumpAndSettle
();
final
double
minutesDisplayHeight
=
tester
.
getSize
(
find
.
text
(
'41'
)).
height
;
final
double
amHeight
=
tester
.
getSize
(
find
.
text
(
'AM'
)).
height
;
await
tester
.
tap
(
find
.
text
(
'OK'
));
// dismiss the dialog
await
tester
.
pumpAndSettle
();
// Verify that the time display is not affected by text scale.
await
mediaQueryBoilerplate
(
tester
,
false
,
textScaleFactor:
2.0
,
initialTime:
const
TimeOfDay
(
hour:
7
,
minute:
41
),
);
await
tester
.
tap
(
find
.
text
(
'X'
));
await
tester
.
pumpAndSettle
();
final
double
amHeight2x
=
tester
.
getSize
(
find
.
text
(
'AM'
)).
height
;
expect
(
tester
.
getSize
(
find
.
text
(
'41'
)).
height
,
equals
(
minutesDisplayHeight
));
expect
(
amHeight2x
,
greaterThanOrEqualTo
(
amHeight
*
2
));
await
tester
.
tap
(
find
.
text
(
'OK'
));
// dismiss the dialog
await
tester
.
pumpAndSettle
();
// Verify that text scale for AM/PM is at most 2x.
await
mediaQueryBoilerplate
(
tester
,
false
,
textScaleFactor:
3.0
,
initialTime:
const
TimeOfDay
(
hour:
7
,
minute:
41
),
);
await
tester
.
tap
(
find
.
text
(
'X'
));
await
tester
.
pumpAndSettle
();
expect
(
tester
.
getSize
(
find
.
text
(
'41'
)).
height
,
equals
(
minutesDisplayHeight
));
expect
(
tester
.
getSize
(
find
.
text
(
'AM'
)).
height
,
equals
(
amHeight2x
));
});
}
// Verify that the time display is not affected by text scale.
void
_testsInput
(
)
{
await
mediaQueryBoilerplate
(
testWidgets
(
'Initial entry mode is used'
,
(
WidgetTester
tester
)
async
{
tester
,
await
mediaQueryBoilerplate
(
tester
,
true
,
entryMode:
TimePickerEntryMode
.
input
);
false
,
expect
(
find
.
byType
(
TextField
),
findsNWidgets
(
2
));
textScaleFactor:
2.0
,
});
initialTime:
const
TimeOfDay
(
hour:
7
,
minute:
41
),
);
await
tester
.
tap
(
find
.
text
(
'X'
));
await
tester
.
pumpAndSettle
();
expect
(
tester
.
getSize
(
find
.
text
(
'41'
)).
height
,
equals
(
minutesDisplayHeight
));
testWidgets
(
'Initial time is the default'
,
(
WidgetTester
tester
)
async
{
expect
(
tester
.
getSize
(
find
.
text
(
'AM'
)).
height
,
equals
(
amHeight
*
2
));
TimeOfDay
result
;
await
startPicker
(
tester
,
(
TimeOfDay
time
)
{
result
=
time
;
},
entryMode:
TimePickerEntryMode
.
input
);
await
finishPicker
(
tester
);
expect
(
result
,
equals
(
const
TimeOfDay
(
hour:
7
,
minute:
0
)));
});
await
tester
.
tap
(
find
.
text
(
'OK'
));
// dismiss the dialog
testWidgets
(
'Help text is used - Input'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpAndSettle
();
const
String
helpText
=
'help'
;
await
mediaQueryBoilerplate
(
tester
,
true
,
entryMode:
TimePickerEntryMode
.
input
,
helpText:
helpText
);
expect
(
find
.
text
(
helpText
),
findsOneWidget
);
});
// Verify that text scale for AM/PM is at most 2x.
testWidgets
(
'Can toggle to dial entry mode'
,
(
WidgetTester
tester
)
async
{
await
mediaQueryBoilerplate
(
await
mediaQueryBoilerplate
(
tester
,
true
,
entryMode:
TimePickerEntryMode
.
input
);
tester
,
await
tester
.
tap
(
find
.
byIcon
(
Icons
.
access_time
));
false
,
textScaleFactor:
3.0
,
initialTime:
const
TimeOfDay
(
hour:
7
,
minute:
41
),
);
await
tester
.
tap
(
find
.
text
(
'X'
));
await
tester
.
pumpAndSettle
();
await
tester
.
pumpAndSettle
();
expect
(
find
.
byType
(
TextField
),
findsNothing
);
expect
(
tester
.
getSize
(
find
.
text
(
'41'
)).
height
,
equals
(
minutesDisplayHeight
));
expect
(
tester
.
getSize
(
find
.
text
(
'AM'
)).
height
,
equals
(
amHeight
*
2
));
});
});
}
final
Finder
findDialPaint
=
find
.
descendant
(
of:
find
.
byWidgetPredicate
((
Widget
w
)
=>
'
${w.runtimeType}
'
==
'_Dial'
),
matching:
find
.
byWidgetPredicate
((
Widget
w
)
=>
w
is
CustomPaint
),
);
class
_SemanticsNodeExpectation
{
_SemanticsNodeExpectation
(
this
.
label
,
this
.
left
,
this
.
top
,
this
.
right
,
this
.
bottom
);
final
String
label
;
testWidgets
(
'Entered text returns time'
,
(
WidgetTester
tester
)
async
{
final
double
left
;
TimeOfDay
result
;
final
double
top
;
await
startPicker
(
tester
,
(
TimeOfDay
time
)
{
result
=
time
;
},
entryMode:
TimePickerEntryMode
.
input
);
final
double
right
;
await
tester
.
enterText
(
find
.
byType
(
TextField
).
first
,
'9'
);
final
double
bottom
;
await
tester
.
enterText
(
find
.
byType
(
TextField
).
last
,
'12'
);
}
await
finishPicker
(
tester
);
expect
(
result
,
equals
(
const
TimeOfDay
(
hour:
9
,
minute:
12
)));
});
class
_CustomPainterSemanticsTester
{
testWidgets
(
'Toggle to dial mode keeps selected time'
,
(
WidgetTester
tester
)
async
{
_CustomPainterSemanticsTester
(
this
.
tester
,
this
.
painter
,
this
.
semantics
);
TimeOfDay
result
;
await
startPicker
(
tester
,
(
TimeOfDay
time
)
{
result
=
time
;
},
entryMode:
TimePickerEntryMode
.
input
);
await
tester
.
enterText
(
find
.
byType
(
TextField
).
first
,
'8'
);
await
tester
.
enterText
(
find
.
byType
(
TextField
).
last
,
'15'
);
await
tester
.
tap
(
find
.
byIcon
(
Icons
.
access_time
));
await
finishPicker
(
tester
);
expect
(
result
,
equals
(
const
TimeOfDay
(
hour:
8
,
minute:
15
)));
});
final
WidgetTester
tester
;
testWidgets
(
'Invalid text prevents dismissing'
,
(
WidgetTester
tester
)
async
{
final
CustomPainter
painter
;
TimeOfDay
result
;
final
SemanticsTester
semantics
;
await
startPicker
(
tester
,
(
TimeOfDay
time
)
{
result
=
time
;
},
entryMode:
TimePickerEntryMode
.
input
);
final
PaintPattern
expectedLabels
=
paints
;
final
List
<
_SemanticsNodeExpectation
>
expectedNodes
=
<
_SemanticsNodeExpectation
>[];
void
addLabel
(
String
label
,
double
left
,
double
top
,
double
right
,
double
bottom
)
{
// Invalid hour.
expectedNodes
.
add
(
_SemanticsNodeExpectation
(
label
,
left
,
top
,
right
,
bottom
));
await
tester
.
enterText
(
find
.
byType
(
TextField
).
first
,
'88'
);
}
await
tester
.
enterText
(
find
.
byType
(
TextField
).
last
,
'15'
);
await
finishPicker
(
tester
);
expect
(
result
,
null
);
void
assertExpectations
()
{
// Invalid minute.
final
TestRecordingCanvas
canvasRecording
=
TestRecordingCanvas
();
await
tester
.
enterText
(
find
.
byType
(
TextField
).
first
,
'8'
);
painter
.
paint
(
canvasRecording
,
const
Size
(
220.0
,
220.0
));
await
tester
.
enterText
(
find
.
byType
(
TextField
).
last
,
'150'
);
final
List
<
ui
.
Paragraph
>
paragraphs
=
canvasRecording
.
invocations
await
finishPicker
(
tester
);
.
where
((
RecordedInvocation
recordedInvocation
)
{
expect
(
result
,
null
);
return
recordedInvocation
.
invocation
.
memberName
==
#drawParagraph
;
})
.
map
<
ui
.
Paragraph
>((
RecordedInvocation
recordedInvocation
)
{
return
recordedInvocation
.
invocation
.
positionalArguments
.
first
as
ui
.
Paragraph
;
})
.
toList
();
final
PaintPattern
expectedLabels
=
paints
;
int
i
=
0
;
for
(
final
_SemanticsNodeExpectation
expectation
in
expectedNodes
)
{
expect
(
semantics
,
includesNodeWith
(
value:
expectation
.
label
));
final
Iterable
<
SemanticsNode
>
dialLabelNodes
=
semantics
.
nodesWith
(
value:
expectation
.
label
)
.
where
((
SemanticsNode
node
)
=>
node
.
tags
?.
contains
(
const
SemanticsTag
(
'dial-label'
))
??
false
);
expect
(
dialLabelNodes
,
hasLength
(
1
),
reason:
'Expected exactly one label
${expectation.label}
'
);
final
Rect
rect
=
Rect
.
fromLTRB
(
expectation
.
left
,
expectation
.
top
,
expectation
.
right
,
expectation
.
bottom
);
expect
(
dialLabelNodes
.
single
.
rect
,
within
(
distance:
1.0
,
from:
rect
),
reason:
'This is checking the node rectangle for label
${expectation.label}
'
);
final
ui
.
Paragraph
paragraph
=
paragraphs
[
i
++];
// The label text paragraph and the semantics node share the same center,
// but have different sizes.
final
Offset
center
=
dialLabelNodes
.
single
.
rect
.
center
;
final
Offset
topLeft
=
center
.
translate
(
-
paragraph
.
width
/
2.0
,
-
paragraph
.
height
/
2.0
,
);
expectedLabels
.
paragraph
(
await
tester
.
enterText
(
find
.
byType
(
TextField
).
first
,
'8'
);
paragraph:
paragraph
,
await
tester
.
enterText
(
find
.
byType
(
TextField
).
last
,
'15'
);
offset:
within
<
Offset
>(
distance:
1.0
,
from:
topLeft
),
await
finishPicker
(
tester
);
);
expect
(
result
,
equals
(
const
TimeOfDay
(
hour:
8
,
minute:
15
)));
}
});
expect
(
tester
.
renderObject
(
findDialPaint
),
expectedLabels
);
}
}
}
final
Finder
findDialPaint
=
find
.
descendant
(
of:
find
.
byWidgetPredicate
((
Widget
w
)
=>
'
${w.runtimeType}
'
==
'_Dial'
),
matching:
find
.
byWidgetPredicate
((
Widget
w
)
=>
w
is
CustomPaint
),
);
class
PickerObserver
extends
NavigatorObserver
{
class
PickerObserver
extends
NavigatorObserver
{
int
pickerCount
=
0
;
int
pickerCount
=
0
;
...
@@ -827,3 +778,53 @@ class PickerObserver extends NavigatorObserver {
...
@@ -827,3 +778,53 @@ class PickerObserver extends NavigatorObserver {
super
.
didPush
(
route
,
previousRoute
);
super
.
didPush
(
route
,
previousRoute
);
}
}
}
}
Future
<
void
>
mediaQueryBoilerplate
(
WidgetTester
tester
,
bool
alwaysUse24HourFormat
,
{
TimeOfDay
initialTime
=
const
TimeOfDay
(
hour:
7
,
minute:
0
),
double
textScaleFactor
=
1.0
,
TimePickerEntryMode
entryMode
=
TimePickerEntryMode
.
dial
,
String
helpText
,
})
async
{
await
tester
.
pumpWidget
(
Localizations
(
locale:
const
Locale
(
'en'
,
'US'
),
delegates:
const
<
LocalizationsDelegate
<
dynamic
>>[
DefaultMaterialLocalizations
.
delegate
,
DefaultWidgetsLocalizations
.
delegate
,
],
child:
MediaQuery
(
data:
MediaQueryData
(
alwaysUse24HourFormat:
alwaysUse24HourFormat
,
textScaleFactor:
textScaleFactor
,
),
child:
Material
(
child:
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
Navigator
(
onGenerateRoute:
(
RouteSettings
settings
)
{
return
MaterialPageRoute
<
void
>(
builder:
(
BuildContext
context
)
{
return
FlatButton
(
onPressed:
()
{
showTimePicker
(
context:
context
,
initialTime:
initialTime
,
initialEntryMode:
entryMode
,
helpText:
helpText
,
);
},
child:
const
Text
(
'X'
),
);
});
},
),
),
),
),
),
);
await
tester
.
tap
(
find
.
text
(
'X'
));
await
tester
.
pumpAndSettle
();
}
packages/flutter/test/material/time_picker_theme_test.dart
0 → 100644
View file @
e676024d
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'package:flutter/material.dart'
;
import
'package:flutter/painting.dart'
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
import
'../rendering/mock_canvas.dart'
;
void
main
(
)
{
test
(
'TimePickerThemeData copyWith, ==, hashCode basics'
,
()
{
expect
(
const
TimePickerThemeData
(),
const
TimePickerThemeData
().
copyWith
());
expect
(
const
TimePickerThemeData
().
hashCode
,
const
TimePickerThemeData
().
copyWith
().
hashCode
);
});
test
(
'TimePickerThemeData null fields by default'
,
()
{
const
TimePickerThemeData
timePickerTheme
=
TimePickerThemeData
();
expect
(
timePickerTheme
.
backgroundColor
,
null
);
expect
(
timePickerTheme
.
hourMinuteTextColor
,
null
);
expect
(
timePickerTheme
.
hourMinuteColor
,
null
);
expect
(
timePickerTheme
.
dayPeriodTextColor
,
null
);
expect
(
timePickerTheme
.
dayPeriodColor
,
null
);
expect
(
timePickerTheme
.
dialHandColor
,
null
);
expect
(
timePickerTheme
.
dialBackgroundColor
,
null
);
expect
(
timePickerTheme
.
dialHandColor
,
null
);
expect
(
timePickerTheme
.
dialBackgroundColor
,
null
);
expect
(
timePickerTheme
.
entryModeIconColor
,
null
);
expect
(
timePickerTheme
.
hourMinuteTextStyle
,
null
);
expect
(
timePickerTheme
.
dayPeriodTextStyle
,
null
);
expect
(
timePickerTheme
.
helpTextStyle
,
null
);
expect
(
timePickerTheme
.
shape
,
null
);
expect
(
timePickerTheme
.
hourMinuteShape
,
null
);
expect
(
timePickerTheme
.
dayPeriodShape
,
null
);
expect
(
timePickerTheme
.
dayPeriodBorderSide
,
null
);
expect
(
timePickerTheme
.
inputDecorationTheme
,
null
);
});
testWidgets
(
'Default TimePickerThemeData debugFillProperties'
,
(
WidgetTester
tester
)
async
{
final
DiagnosticPropertiesBuilder
builder
=
DiagnosticPropertiesBuilder
();
const
TimePickerThemeData
().
debugFillProperties
(
builder
);
final
List
<
String
>
description
=
builder
.
properties
.
where
((
DiagnosticsNode
node
)
=>
!
node
.
isFiltered
(
DiagnosticLevel
.
info
))
.
map
((
DiagnosticsNode
node
)
=>
node
.
toString
())
.
toList
();
expect
(
description
,
<
String
>[]);
});
testWidgets
(
'TimePickerThemeData implements debugFillProperties'
,
(
WidgetTester
tester
)
async
{
final
DiagnosticPropertiesBuilder
builder
=
DiagnosticPropertiesBuilder
();
const
TimePickerThemeData
(
backgroundColor:
Color
(
0xFFFFFFFF
),
hourMinuteTextColor:
Color
(
0xFFFFFFFF
),
hourMinuteColor:
Color
(
0xFFFFFFFF
),
dayPeriodTextColor:
Color
(
0xFFFFFFFF
),
dayPeriodColor:
Color
(
0xFFFFFFFF
),
dialHandColor:
Color
(
0xFFFFFFFF
),
dialBackgroundColor:
Color
(
0xFFFFFFFF
),
entryModeIconColor:
Color
(
0xFFFFFFFF
),
hourMinuteTextStyle:
TextStyle
(),
dayPeriodTextStyle:
TextStyle
(),
helpTextStyle:
TextStyle
(),
shape:
RoundedRectangleBorder
(),
hourMinuteShape:
RoundedRectangleBorder
(),
dayPeriodShape:
RoundedRectangleBorder
(),
dayPeriodBorderSide:
BorderSide
(),
).
debugFillProperties
(
builder
);
final
List
<
String
>
description
=
builder
.
properties
.
where
((
DiagnosticsNode
node
)
=>
!
node
.
isFiltered
(
DiagnosticLevel
.
info
))
.
map
((
DiagnosticsNode
node
)
=>
node
.
toString
())
.
toList
();
expect
(
description
,
<
String
>[
'backgroundColor: Color(0xffffffff)'
,
'hourMinuteTextColor: Color(0xffffffff)'
,
'hourMinuteColor: Color(0xffffffff)'
,
'dayPeriodTextColor: Color(0xffffffff)'
,
'dayPeriodColor: Color(0xffffffff)'
,
'dialHandColor: Color(0xffffffff)'
,
'dialBackgroundColor: Color(0xffffffff)'
,
'entryModeIconColor: Color(0xffffffff)'
,
'hourMinuteTextStyle: TextStyle(<all styles inherited>)'
,
'dayPeriodTextStyle: TextStyle(<all styles inherited>)'
,
'helpTextStyle: TextStyle(<all styles inherited>)'
,
'shape: RoundedRectangleBorder(BorderSide(Color(0xff000000), 0.0, BorderStyle.none), BorderRadius.zero)'
,
'hourMinuteShape: RoundedRectangleBorder(BorderSide(Color(0xff000000), 0.0, BorderStyle.none), BorderRadius.zero)'
,
'dayPeriodShape: RoundedRectangleBorder(BorderSide(Color(0xff000000), 0.0, BorderStyle.none), BorderRadius.zero)'
,
'dayPeriodBorderSide: BorderSide(Color(0xff000000), 1.0, BorderStyle.solid)'
,
]);
});
testWidgets
(
'Passing no TimePickerThemeData uses defaults'
,
(
WidgetTester
tester
)
async
{
final
ThemeData
defaultTheme
=
ThemeData
.
fallback
();
await
tester
.
pumpWidget
(
const
_TimePickerLauncher
());
await
tester
.
tap
(
find
.
text
(
'X'
));
await
tester
.
pumpAndSettle
(
const
Duration
(
seconds:
1
));
final
Material
dialogMaterial
=
_dialogMaterial
(
tester
);
expect
(
dialogMaterial
.
color
,
defaultTheme
.
colorScheme
.
surface
);
expect
(
dialogMaterial
.
shape
,
const
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
all
(
Radius
.
circular
(
4.0
))));
final
RenderBox
dial
=
tester
.
firstRenderObject
<
RenderBox
>(
find
.
byType
(
CustomPaint
));
expect
(
dial
,
paints
..
circle
(
color:
defaultTheme
.
colorScheme
.
onBackground
.
withOpacity
(
0.12
))
// Dial background color.
..
circle
(
color:
Color
(
defaultTheme
.
colorScheme
.
primary
.
value
)),
// Dial hand color.
);
final
RenderParagraph
hourText
=
_textRenderParagraph
(
tester
,
'7'
);
expect
(
hourText
.
text
.
style
,
Typography
.
material2014
().
englishLike
.
headline2
.
merge
(
Typography
.
material2014
().
black
.
headline2
)
.
copyWith
(
color:
defaultTheme
.
colorScheme
.
primary
),
);
final
RenderParagraph
minuteText
=
_textRenderParagraph
(
tester
,
'15'
);
expect
(
minuteText
.
text
.
style
,
Typography
.
material2014
().
englishLike
.
headline2
.
merge
(
Typography
.
material2014
().
black
.
headline2
)
.
copyWith
(
color:
defaultTheme
.
colorScheme
.
onSurface
),
);
final
RenderParagraph
amText
=
_textRenderParagraph
(
tester
,
'AM'
);
expect
(
amText
.
text
.
style
,
Typography
.
material2014
().
englishLike
.
subtitle1
.
merge
(
Typography
.
material2014
().
black
.
subtitle1
)
.
copyWith
(
color:
defaultTheme
.
colorScheme
.
primary
),
);
final
RenderParagraph
pmText
=
_textRenderParagraph
(
tester
,
'PM'
);
expect
(
pmText
.
text
.
style
,
Typography
.
material2014
().
englishLike
.
subtitle1
.
merge
(
Typography
.
material2014
().
black
.
subtitle1
)
.
copyWith
(
color:
defaultTheme
.
colorScheme
.
onSurface
.
withOpacity
(
0.6
)),
);
final
RenderParagraph
helperText
=
_textRenderParagraph
(
tester
,
'SELECT TIME'
);
expect
(
helperText
.
text
.
style
,
Typography
.
material2014
().
englishLike
.
overline
.
merge
(
Typography
.
material2014
().
black
.
overline
),
);
final
Material
hourMaterial
=
_textMaterial
(
tester
,
'7'
);
expect
(
hourMaterial
.
color
,
defaultTheme
.
colorScheme
.
primary
.
withOpacity
(
0.12
));
expect
(
hourMaterial
.
shape
,
const
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
all
(
Radius
.
circular
(
4.0
))));
final
Material
minuteMaterial
=
_textMaterial
(
tester
,
'15'
);
expect
(
minuteMaterial
.
color
,
defaultTheme
.
colorScheme
.
onSurface
.
withOpacity
(
0.12
));
expect
(
minuteMaterial
.
shape
,
const
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
all
(
Radius
.
circular
(
4.0
))));
final
Material
amMaterial
=
_textMaterial
(
tester
,
'AM'
);
expect
(
amMaterial
.
color
,
defaultTheme
.
colorScheme
.
primary
.
withOpacity
(
0.12
));
final
Material
pmMaterial
=
_textMaterial
(
tester
,
'PM'
);
expect
(
pmMaterial
.
color
,
Colors
.
transparent
);
final
Color
expectedBorderColor
=
Color
.
alphaBlend
(
defaultTheme
.
colorScheme
.
onBackground
.
withOpacity
(
0.38
),
defaultTheme
.
colorScheme
.
surface
,
);
final
Material
dayPeriodMaterial
=
_dayPeriodMaterial
(
tester
);
expect
(
dayPeriodMaterial
.
shape
,
RoundedRectangleBorder
(
borderRadius:
const
BorderRadius
.
all
(
Radius
.
circular
(
4.0
)),
side:
BorderSide
(
color:
expectedBorderColor
),
),
);
final
Container
dayPeriodDivider
=
_dayPeriodDivider
(
tester
);
expect
(
dayPeriodDivider
.
decoration
,
BoxDecoration
(
border:
Border
(
left:
BorderSide
(
color:
expectedBorderColor
))),
);
final
IconButton
entryModeIconButton
=
_entryModeIconButton
(
tester
);
expect
(
entryModeIconButton
.
color
,
defaultTheme
.
colorScheme
.
onSurface
.
withOpacity
(
0.6
),
);
});
testWidgets
(
'Passing no TimePickerThemeData uses defaults - input mode'
,
(
WidgetTester
tester
)
async
{
final
ThemeData
defaultTheme
=
ThemeData
.
fallback
();
await
tester
.
pumpWidget
(
const
_TimePickerLauncher
(
entryMode:
TimePickerEntryMode
.
input
));
await
tester
.
tap
(
find
.
text
(
'X'
));
await
tester
.
pumpAndSettle
(
const
Duration
(
seconds:
1
));
final
InputDecoration
hourDecoration
=
_textField
(
tester
,
'7'
).
decoration
;
expect
(
hourDecoration
.
filled
,
true
);
expect
(
hourDecoration
.
fillColor
,
defaultTheme
.
colorScheme
.
onSurface
.
withOpacity
(
0.12
));
expect
(
hourDecoration
.
enabledBorder
,
const
OutlineInputBorder
(
borderSide:
BorderSide
(
color:
Colors
.
transparent
)));
expect
(
hourDecoration
.
errorBorder
,
OutlineInputBorder
(
borderSide:
BorderSide
(
color:
defaultTheme
.
colorScheme
.
error
,
width:
2
)));
expect
(
hourDecoration
.
focusedBorder
,
OutlineInputBorder
(
borderSide:
BorderSide
(
color:
defaultTheme
.
colorScheme
.
primary
,
width:
2
)));
expect
(
hourDecoration
.
focusedErrorBorder
,
OutlineInputBorder
(
borderSide:
BorderSide
(
color:
defaultTheme
.
colorScheme
.
error
,
width:
2
)));
expect
(
hourDecoration
.
hintStyle
,
Typography
.
material2014
().
englishLike
.
headline2
.
merge
(
defaultTheme
.
textTheme
.
headline2
.
copyWith
(
color:
defaultTheme
.
colorScheme
.
onSurface
.
withOpacity
(
0.36
))),
);
});
testWidgets
(
'Time picker uses values from TimePickerThemeData'
,
(
WidgetTester
tester
)
async
{
final
TimePickerThemeData
timePickerTheme
=
_timePickerTheme
();
final
ThemeData
theme
=
ThemeData
(
timePickerTheme:
timePickerTheme
);
await
tester
.
pumpWidget
(
_TimePickerLauncher
(
themeData:
theme
,));
await
tester
.
tap
(
find
.
text
(
'X'
));
await
tester
.
pumpAndSettle
(
const
Duration
(
seconds:
1
));
final
Material
dialogMaterial
=
_dialogMaterial
(
tester
);
expect
(
dialogMaterial
.
color
,
timePickerTheme
.
backgroundColor
);
expect
(
dialogMaterial
.
shape
,
timePickerTheme
.
shape
);
final
RenderBox
dial
=
tester
.
firstRenderObject
<
RenderBox
>(
find
.
byType
(
CustomPaint
));
expect
(
dial
,
paints
..
circle
(
color:
Color
(
timePickerTheme
.
dialBackgroundColor
.
value
))
// Dial background color.
..
circle
(
color:
Color
(
timePickerTheme
.
dialHandColor
.
value
)),
// Dial hand color.
);
final
RenderParagraph
hourText
=
_textRenderParagraph
(
tester
,
'7'
);
expect
(
hourText
.
text
.
style
,
Typography
.
material2014
().
englishLike
.
bodyText2
.
merge
(
Typography
.
material2014
().
black
.
bodyText2
)
.
merge
(
timePickerTheme
.
hourMinuteTextStyle
)
.
copyWith
(
color:
_selectedColor
),
);
final
RenderParagraph
minuteText
=
_textRenderParagraph
(
tester
,
'15'
);
expect
(
minuteText
.
text
.
style
,
Typography
.
material2014
().
englishLike
.
bodyText2
.
merge
(
Typography
.
material2014
().
black
.
bodyText2
)
.
merge
(
timePickerTheme
.
hourMinuteTextStyle
)
.
copyWith
(
color:
_unselectedColor
),
);
final
RenderParagraph
amText
=
_textRenderParagraph
(
tester
,
'AM'
);
expect
(
amText
.
text
.
style
,
Typography
.
material2014
().
englishLike
.
subtitle1
.
merge
(
Typography
.
material2014
().
black
.
subtitle1
)
.
merge
(
timePickerTheme
.
dayPeriodTextStyle
)
.
copyWith
(
color:
_selectedColor
),
);
final
RenderParagraph
pmText
=
_textRenderParagraph
(
tester
,
'PM'
);
expect
(
pmText
.
text
.
style
,
Typography
.
material2014
().
englishLike
.
subtitle1
.
merge
(
Typography
.
material2014
().
black
.
subtitle1
)
.
merge
(
timePickerTheme
.
dayPeriodTextStyle
)
.
copyWith
(
color:
_unselectedColor
),
);
final
RenderParagraph
helperText
=
_textRenderParagraph
(
tester
,
'SELECT TIME'
);
expect
(
helperText
.
text
.
style
,
Typography
.
material2014
().
englishLike
.
bodyText2
.
merge
(
Typography
.
material2014
().
black
.
bodyText2
)
.
merge
(
timePickerTheme
.
helpTextStyle
),
);
final
Material
hourMaterial
=
_textMaterial
(
tester
,
'7'
);
expect
(
hourMaterial
.
color
,
_selectedColor
);
expect
(
hourMaterial
.
shape
,
timePickerTheme
.
hourMinuteShape
);
final
Material
minuteMaterial
=
_textMaterial
(
tester
,
'15'
);
expect
(
minuteMaterial
.
color
,
_unselectedColor
);
expect
(
minuteMaterial
.
shape
,
timePickerTheme
.
hourMinuteShape
);
final
Material
amMaterial
=
_textMaterial
(
tester
,
'AM'
);
expect
(
amMaterial
.
color
,
_selectedColor
);
final
Material
pmMaterial
=
_textMaterial
(
tester
,
'PM'
);
expect
(
pmMaterial
.
color
,
_unselectedColor
);
final
Material
dayPeriodMaterial
=
_dayPeriodMaterial
(
tester
);
expect
(
dayPeriodMaterial
.
shape
,
timePickerTheme
.
dayPeriodShape
.
copyWith
(
side:
timePickerTheme
.
dayPeriodBorderSide
),
);
final
Container
dayPeriodDivider
=
_dayPeriodDivider
(
tester
);
expect
(
dayPeriodDivider
.
decoration
,
BoxDecoration
(
border:
Border
(
left:
timePickerTheme
.
dayPeriodBorderSide
)),
);
final
IconButton
entryModeIconButton
=
_entryModeIconButton
(
tester
);
expect
(
entryModeIconButton
.
color
,
timePickerTheme
.
entryModeIconColor
,
);
});
testWidgets
(
'Time picker uses values from TimePickerThemeData - input mode'
,
(
WidgetTester
tester
)
async
{
final
TimePickerThemeData
timePickerTheme
=
_timePickerTheme
();
final
ThemeData
theme
=
ThemeData
(
timePickerTheme:
timePickerTheme
);
await
tester
.
pumpWidget
(
_TimePickerLauncher
(
themeData:
theme
,
entryMode:
TimePickerEntryMode
.
input
));
await
tester
.
tap
(
find
.
text
(
'X'
));
await
tester
.
pumpAndSettle
(
const
Duration
(
seconds:
1
));
final
InputDecoration
hourDecoration
=
_textField
(
tester
,
'7'
).
decoration
;
expect
(
hourDecoration
.
filled
,
timePickerTheme
.
inputDecorationTheme
.
filled
);
expect
(
hourDecoration
.
fillColor
,
timePickerTheme
.
inputDecorationTheme
.
fillColor
);
expect
(
hourDecoration
.
enabledBorder
,
timePickerTheme
.
inputDecorationTheme
.
enabledBorder
);
expect
(
hourDecoration
.
errorBorder
,
timePickerTheme
.
inputDecorationTheme
.
errorBorder
);
expect
(
hourDecoration
.
focusedBorder
,
timePickerTheme
.
inputDecorationTheme
.
focusedBorder
);
expect
(
hourDecoration
.
focusedErrorBorder
,
timePickerTheme
.
inputDecorationTheme
.
focusedErrorBorder
);
expect
(
hourDecoration
.
hintStyle
,
timePickerTheme
.
inputDecorationTheme
.
hintStyle
);
});
}
final
Color
_selectedColor
=
Colors
.
green
[
100
];
final
Color
_unselectedColor
=
Colors
.
green
[
200
];
TimePickerThemeData
_timePickerTheme
(
)
{
Color
getColor
(
Set
<
MaterialState
>
states
)
{
return
states
.
contains
(
MaterialState
.
selected
)
?
_selectedColor
:
_unselectedColor
;
}
final
MaterialStateColor
materialStateColor
=
MaterialStateColor
.
resolveWith
(
getColor
);
return
TimePickerThemeData
(
backgroundColor:
Colors
.
orange
,
hourMinuteTextColor:
materialStateColor
,
hourMinuteColor:
materialStateColor
,
dayPeriodTextColor:
materialStateColor
,
dayPeriodColor:
materialStateColor
,
dialHandColor:
Colors
.
brown
,
dialBackgroundColor:
Colors
.
pinkAccent
,
entryModeIconColor:
Colors
.
red
,
hourMinuteTextStyle:
const
TextStyle
(
fontSize:
8.0
),
dayPeriodTextStyle:
const
TextStyle
(
fontSize:
8.0
),
helpTextStyle:
const
TextStyle
(
fontSize:
8.0
),
shape:
const
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
all
(
Radius
.
circular
(
16.0
))),
hourMinuteShape:
const
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
all
(
Radius
.
circular
(
16.0
))),
dayPeriodShape:
const
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
all
(
Radius
.
circular
(
16.0
))),
dayPeriodBorderSide:
const
BorderSide
(
color:
Colors
.
blueAccent
),
inputDecorationTheme:
const
InputDecorationTheme
(
filled:
true
,
fillColor:
Colors
.
purple
,
enabledBorder:
OutlineInputBorder
(
borderSide:
BorderSide
(
color:
Colors
.
blue
)),
errorBorder:
OutlineInputBorder
(
borderSide:
BorderSide
(
color:
Colors
.
green
)),
focusedBorder:
OutlineInputBorder
(
borderSide:
BorderSide
(
color:
Colors
.
yellow
)),
focusedErrorBorder:
OutlineInputBorder
(
borderSide:
BorderSide
(
color:
Colors
.
red
)),
hintStyle:
TextStyle
(
fontSize:
8
),
),
);
}
class
_TimePickerLauncher
extends
StatelessWidget
{
const
_TimePickerLauncher
({
Key
key
,
this
.
themeData
,
this
.
entryMode
=
TimePickerEntryMode
.
dial
,
})
:
super
(
key:
key
);
final
ThemeData
themeData
;
final
TimePickerEntryMode
entryMode
;
@override
Widget
build
(
BuildContext
context
)
{
return
MaterialApp
(
theme:
themeData
,
home:
Material
(
child:
Center
(
child:
Builder
(
builder:
(
BuildContext
context
)
{
return
RaisedButton
(
child:
const
Text
(
'X'
),
onPressed:
()
async
{
await
showTimePicker
(
context:
context
,
initialEntryMode:
entryMode
,
initialTime:
const
TimeOfDay
(
hour:
7
,
minute:
15
),
);
},
);
}
),
),
),
);
}
}
Material
_dialogMaterial
(
WidgetTester
tester
)
{
return
tester
.
widget
<
Material
>(
find
.
descendant
(
of:
find
.
byType
(
Dialog
),
matching:
find
.
byType
(
Material
)).
first
);
}
Material
_textMaterial
(
WidgetTester
tester
,
String
text
)
{
return
tester
.
widget
<
Material
>(
find
.
ancestor
(
of:
find
.
text
(
text
),
matching:
find
.
byType
(
Material
)).
first
);
}
TextField
_textField
(
WidgetTester
tester
,
String
text
)
{
return
tester
.
widget
<
TextField
>(
find
.
ancestor
(
of:
find
.
text
(
text
),
matching:
find
.
byType
(
TextField
)).
first
);
}
Material
_dayPeriodMaterial
(
WidgetTester
tester
)
{
return
tester
.
widget
<
Material
>(
find
.
descendant
(
of:
find
.
byWidgetPredicate
((
Widget
w
)
=>
'
${w.runtimeType}
'
==
'_DayPeriodControl'
),
matching:
find
.
byType
(
Material
)).
first
);
}
Container
_dayPeriodDivider
(
WidgetTester
tester
)
{
return
tester
.
widget
<
Container
>(
find
.
descendant
(
of:
find
.
byWidgetPredicate
((
Widget
w
)
=>
'
${w.runtimeType}
'
==
'_DayPeriodControl'
),
matching:
find
.
byType
(
Container
)).
at
(
1
));
}
IconButton
_entryModeIconButton
(
WidgetTester
tester
)
{
return
tester
.
widget
<
IconButton
>(
find
.
descendant
(
of:
find
.
byType
(
Dialog
),
matching:
find
.
byType
(
IconButton
)).
first
);
}
RenderParagraph
_textRenderParagraph
(
WidgetTester
tester
,
String
text
)
{
return
tester
.
element
<
StatelessElement
>(
find
.
text
(
text
).
first
).
renderObject
as
RenderParagraph
;
}
\ No newline at end of file
packages/flutter_localizations/test/material/time_picker_test.dart
View file @
e676024d
...
@@ -43,7 +43,7 @@ class _TimePickerLauncher extends StatelessWidget {
...
@@ -43,7 +43,7 @@ class _TimePickerLauncher extends StatelessWidget {
Future
<
Offset
>
startPicker
(
Future
<
Offset
>
startPicker
(
WidgetTester
tester
,
WidgetTester
tester
,
ValueChanged
<
TimeOfDay
>
onChanged
,
{
ValueChanged
<
TimeOfDay
>
onChanged
,
{
Locale
locale
=
const
Locale
(
'en'
,
'US'
),
Locale
locale
=
const
Locale
(
'en'
,
'US'
),
})
async
{
})
async
{
await
tester
.
pumpWidget
(
_TimePickerLauncher
(
onChanged:
onChanged
,
locale:
locale
,));
await
tester
.
pumpWidget
(
_TimePickerLauncher
(
onChanged:
onChanged
,
locale:
locale
,));
await
tester
.
tap
(
find
.
text
(
'X'
));
await
tester
.
tap
(
find
.
text
(
'X'
));
...
@@ -58,66 +58,151 @@ Future<void> finishPicker(WidgetTester tester) async {
...
@@ -58,66 +58,151 @@ Future<void> finishPicker(WidgetTester tester) async {
}
}
void
main
(
)
{
void
main
(
)
{
testWidgets
(
'can localize the header in all known formats'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
'can localize the header in all known formats - portrait'
,
(
WidgetTester
tester
)
async
{
// Ensure picker is displayed in portrait mode.
tester
.
binding
.
window
.
physicalSizeTestValue
=
const
Size
(
400
,
800
);
tester
.
binding
.
window
.
devicePixelRatioTestValue
=
1
;
final
Finder
stringFragmentTextFinder
=
find
.
descendant
(
of:
find
.
byWidgetPredicate
((
Widget
w
)
=>
'
${w.runtimeType}
'
==
'_StringFragment'
),
matching:
find
.
byType
(
Text
),
).
first
;
final
Finder
hourControlFinder
=
find
.
byWidgetPredicate
((
Widget
w
)
=>
'
${w.runtimeType}
'
==
'_HourControl'
);
final
Finder
minuteControlFinder
=
find
.
byWidgetPredicate
((
Widget
w
)
=>
'
${w.runtimeType}
'
==
'_MinuteControl'
);
final
Finder
dayPeriodControlFinder
=
find
.
byWidgetPredicate
((
Widget
w
)
=>
'
${w.runtimeType}
'
==
'_DayPeriodControl'
);
// TODO(yjbanov): also test `HH.mm` (in_ID), `a h:mm` (ko_KR) and `HH:mm น.` (th_TH) when we have .arb files for them
// TODO(yjbanov): also test `HH.mm` (in_ID), `a h:mm` (ko_KR) and `HH:mm น.` (th_TH) when we have .arb files for them
final
Map
<
Locale
,
List
<
String
>>
locales
=
<
Locale
,
List
<
String
>>{
final
List
<
Locale
>
locales
=
<
Locale
>[
const
Locale
(
'en'
,
'US'
)
:
const
<
String
>[
'hour'
,
'string :'
,
'minute'
,
'period'
]
,
//'h:mm a'
const
Locale
(
'en'
,
'US'
),
//'h:mm a'
const
Locale
(
'en'
,
'GB'
)
:
const
<
String
>[
'hour'
,
'string :'
,
'minute'
]
,
//'HH:mm'
const
Locale
(
'en'
,
'GB'
),
//'HH:mm'
const
Locale
(
'es'
,
'ES'
)
:
const
<
String
>[
'hour'
,
'string :'
,
'minute'
]
,
//'H:mm'
const
Locale
(
'es'
,
'ES'
),
//'H:mm'
const
Locale
(
'fr'
,
'CA'
)
:
const
<
String
>[
'hour'
,
'string h'
,
'minute'
]
,
//'HH \'h\' mm'
const
Locale
(
'fr'
,
'CA'
),
//'HH \'h\' mm'
const
Locale
(
'zh'
,
'ZH'
)
:
const
<
String
>[
'period'
,
'hour'
,
'string :'
,
'minute'
]
,
//'ah:mm'
const
Locale
(
'zh'
,
'ZH'
),
//'ah:mm'
}
;
]
;
for
(
final
Locale
locale
in
locales
.
keys
)
{
for
(
final
Locale
locale
in
locales
)
{
final
Offset
center
=
await
startPicker
(
tester
,
(
TimeOfDay
time
)
{
},
locale:
locale
);
final
Offset
center
=
await
startPicker
(
tester
,
(
TimeOfDay
time
)
{
},
locale:
locale
);
final
List
<
String
>
actual
=
<
String
>[];
final
Text
stringFragmentText
=
tester
.
widget
(
stringFragmentTextFinder
);
tester
.
element
(
find
.
byType
(
CustomMultiChildLayout
)).
visitChildren
((
Element
child
)
{
final
double
hourLeftOffset
=
tester
.
getTopLeft
(
hourControlFinder
).
dx
;
final
LayoutId
layout
=
child
.
widget
as
LayoutId
;
final
double
minuteLeftOffset
=
tester
.
getTopLeft
(
minuteControlFinder
).
dx
;
final
String
fragmentType
=
'
${layout.child.runtimeType}
'
;
final
double
stringFragmentLeftOffset
=
tester
.
getTopLeft
(
stringFragmentTextFinder
).
dx
;
final
dynamic
widget
=
layout
.
child
;
if
(
fragmentType
==
'_MinuteControl'
)
{
if
(
locale
==
const
Locale
(
'en'
,
'US'
))
{
actual
.
add
(
'minute'
);
final
double
dayPeriodLeftOffset
=
tester
.
getTopLeft
(
dayPeriodControlFinder
).
dx
;
}
else
if
(
fragmentType
==
'_DayPeriodControl'
)
{
expect
(
stringFragmentText
.
data
,
':'
);
actual
.
add
(
'period'
);
expect
(
hourLeftOffset
,
lessThan
(
stringFragmentLeftOffset
));
}
else
if
(
fragmentType
==
'_HourControl'
)
{
expect
(
stringFragmentLeftOffset
,
lessThan
(
minuteLeftOffset
));
actual
.
add
(
'hour'
);
expect
(
minuteLeftOffset
,
lessThan
(
dayPeriodLeftOffset
));
}
else
if
(
fragmentType
==
'_StringFragment'
)
{
}
else
if
(
locale
==
const
Locale
(
'en'
,
'GB'
))
{
actual
.
add
(
'string
${widget.value}
'
);
expect
(
stringFragmentText
.
data
,
':'
);
}
else
{
expect
(
hourLeftOffset
,
lessThan
(
stringFragmentLeftOffset
));
fail
(
'Unsupported fragment type:
$fragmentType
'
);
expect
(
stringFragmentLeftOffset
,
lessThan
(
minuteLeftOffset
));
}
expect
(
dayPeriodControlFinder
,
findsNothing
);
});
}
else
if
(
locale
==
const
Locale
(
'es'
,
'ES'
))
{
expect
(
actual
,
locales
[
locale
]);
expect
(
stringFragmentText
.
data
,
':'
);
expect
(
hourLeftOffset
,
lessThan
(
stringFragmentLeftOffset
));
expect
(
stringFragmentLeftOffset
,
lessThan
(
minuteLeftOffset
));
expect
(
dayPeriodControlFinder
,
findsNothing
);
}
else
if
(
locale
==
const
Locale
(
'fr'
,
'CA'
))
{
expect
(
stringFragmentText
.
data
,
'h'
);
expect
(
hourLeftOffset
,
lessThan
(
stringFragmentLeftOffset
));
expect
(
stringFragmentLeftOffset
,
lessThan
(
minuteLeftOffset
));
expect
(
dayPeriodControlFinder
,
findsNothing
);
}
else
if
(
locale
==
const
Locale
(
'zh'
,
'ZH'
))
{
final
double
dayPeriodLeftOffset
=
tester
.
getTopLeft
(
dayPeriodControlFinder
).
dx
;
expect
(
stringFragmentText
.
data
,
':'
);
expect
(
dayPeriodLeftOffset
,
lessThan
(
hourLeftOffset
));
expect
(
hourLeftOffset
,
lessThan
(
stringFragmentLeftOffset
));
expect
(
stringFragmentLeftOffset
,
lessThan
(
minuteLeftOffset
));
}
await
tester
.
tapAt
(
Offset
(
center
.
dx
,
center
.
dy
-
50.0
));
await
tester
.
tapAt
(
Offset
(
center
.
dx
,
center
.
dy
-
50.0
));
await
finishPicker
(
tester
);
await
finishPicker
(
tester
);
}
}
tester
.
binding
.
window
.
physicalSizeTestValue
=
null
;
tester
.
binding
.
window
.
devicePixelRatioTestValue
=
null
;
});
});
testWidgets
(
'uses single-ring 12-hour dial for h hour format'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
'can localize the header in all known formats - landscape'
,
(
WidgetTester
tester
)
async
{
// Tap along the segment stretching from the center to the edge at
// Ensure picker is displayed in landscape mode.
// 12:00 AM position. Because there's only one ring, no matter where you
tester
.
binding
.
window
.
physicalSizeTestValue
=
const
Size
(
800
,
400
);
// tap the time will be the same. See the 24-hour dial test that behaves
tester
.
binding
.
window
.
devicePixelRatioTestValue
=
1
;
// differently.
for
(
int
i
=
1
;
i
<
10
;
i
++)
{
final
Finder
stringFragmentTextFinder
=
find
.
descendant
(
TimeOfDay
result
;
of:
find
.
byWidgetPredicate
((
Widget
w
)
=>
'
${w.runtimeType}
'
==
'_StringFragment'
),
final
Offset
center
=
await
startPicker
(
tester
,
(
TimeOfDay
time
)
{
result
=
time
;
});
matching:
find
.
byType
(
Text
),
final
Size
size
=
tester
.
getSize
(
find
.
byKey
(
const
Key
(
'time-picker-dial'
)));
).
first
;
final
double
dy
=
(
size
.
height
/
2.0
/
10
)
*
i
;
final
Finder
hourControlFinder
=
find
.
byWidgetPredicate
((
Widget
w
)
=>
'
${w.runtimeType}
'
==
'_HourControl'
);
await
tester
.
tapAt
(
Offset
(
center
.
dx
,
center
.
dy
-
dy
));
final
Finder
minuteControlFinder
=
find
.
byWidgetPredicate
((
Widget
w
)
=>
'
${w.runtimeType}
'
==
'_MinuteControl'
);
final
Finder
dayPeriodControlFinder
=
find
.
byWidgetPredicate
((
Widget
w
)
=>
'
${w.runtimeType}
'
==
'_DayPeriodControl'
);
// TODO(yjbanov): also test `HH.mm` (in_ID), `a h:mm` (ko_KR) and `HH:mm น.` (th_TH) when we have .arb files for them
final
List
<
Locale
>
locales
=
<
Locale
>[
const
Locale
(
'en'
,
'US'
),
//'h:mm a'
const
Locale
(
'en'
,
'GB'
),
//'HH:mm'
const
Locale
(
'es'
,
'ES'
),
//'H:mm'
const
Locale
(
'fr'
,
'CA'
),
//'HH \'h\' mm'
const
Locale
(
'zh'
,
'ZH'
),
//'ah:mm'
];
for
(
final
Locale
locale
in
locales
)
{
final
Offset
center
=
await
startPicker
(
tester
,
(
TimeOfDay
time
)
{
},
locale:
locale
);
final
Text
stringFragmentText
=
tester
.
widget
(
stringFragmentTextFinder
);
final
double
hourLeftOffset
=
tester
.
getTopLeft
(
hourControlFinder
).
dx
;
final
double
hourTopOffset
=
tester
.
getTopLeft
(
hourControlFinder
).
dy
;
final
double
minuteLeftOffset
=
tester
.
getTopLeft
(
minuteControlFinder
).
dx
;
final
double
stringFragmentLeftOffset
=
tester
.
getTopLeft
(
stringFragmentTextFinder
).
dx
;
if
(
locale
==
const
Locale
(
'en'
,
'US'
))
{
final
double
dayPeriodLeftOffset
=
tester
.
getTopLeft
(
dayPeriodControlFinder
).
dx
;
final
double
dayPeriodTopOffset
=
tester
.
getTopLeft
(
dayPeriodControlFinder
).
dy
;
expect
(
stringFragmentText
.
data
,
':'
);
expect
(
hourLeftOffset
,
lessThan
(
stringFragmentLeftOffset
));
expect
(
stringFragmentLeftOffset
,
lessThan
(
minuteLeftOffset
));
expect
(
hourLeftOffset
,
dayPeriodLeftOffset
);
expect
(
hourTopOffset
,
lessThan
(
dayPeriodTopOffset
));
}
else
if
(
locale
==
const
Locale
(
'en'
,
'GB'
))
{
expect
(
stringFragmentText
.
data
,
':'
);
expect
(
hourLeftOffset
,
lessThan
(
stringFragmentLeftOffset
));
expect
(
stringFragmentLeftOffset
,
lessThan
(
minuteLeftOffset
));
expect
(
dayPeriodControlFinder
,
findsNothing
);
}
else
if
(
locale
==
const
Locale
(
'es'
,
'ES'
))
{
expect
(
stringFragmentText
.
data
,
':'
);
expect
(
hourLeftOffset
,
lessThan
(
stringFragmentLeftOffset
));
expect
(
stringFragmentLeftOffset
,
lessThan
(
minuteLeftOffset
));
expect
(
dayPeriodControlFinder
,
findsNothing
);
}
else
if
(
locale
==
const
Locale
(
'fr'
,
'CA'
))
{
expect
(
stringFragmentText
.
data
,
'h'
);
expect
(
hourLeftOffset
,
lessThan
(
stringFragmentLeftOffset
));
expect
(
stringFragmentLeftOffset
,
lessThan
(
minuteLeftOffset
));
expect
(
dayPeriodControlFinder
,
findsNothing
);
}
else
if
(
locale
==
const
Locale
(
'zh'
,
'ZH'
))
{
final
double
dayPeriodLeftOffset
=
tester
.
getTopLeft
(
dayPeriodControlFinder
).
dx
;
final
double
dayPeriodTopOffset
=
tester
.
getTopLeft
(
dayPeriodControlFinder
).
dy
;
expect
(
stringFragmentText
.
data
,
':'
);
expect
(
hourLeftOffset
,
lessThan
(
stringFragmentLeftOffset
));
expect
(
stringFragmentLeftOffset
,
lessThan
(
minuteLeftOffset
));
expect
(
hourLeftOffset
,
dayPeriodLeftOffset
);
expect
(
hourTopOffset
,
greaterThan
(
dayPeriodTopOffset
));
}
await
tester
.
tapAt
(
Offset
(
center
.
dx
,
center
.
dy
-
50.0
));
await
finishPicker
(
tester
);
await
finishPicker
(
tester
);
expect
(
result
,
equals
(
const
TimeOfDay
(
hour:
0
,
minute:
0
)));
}
}
tester
.
binding
.
window
.
physicalSizeTestValue
=
null
;
tester
.
binding
.
window
.
devicePixelRatioTestValue
=
null
;
});
});
testWidgets
(
'uses
two-ring 24-hour dial for H and HH hour
formats'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
'uses
single-ring 24-hour dial for all
formats'
,
(
WidgetTester
tester
)
async
{
const
List
<
Locale
>
locales
=
<
Locale
>[
const
List
<
Locale
>
locales
=
<
Locale
>[
Locale
(
'en'
,
'US'
),
// h
Locale
(
'en'
,
'GB'
),
// HH
Locale
(
'en'
,
'GB'
),
// HH
Locale
(
'es'
,
'ES'
),
// H
Locale
(
'es'
,
'ES'
),
// H
];
];
for
(
final
Locale
locale
in
locales
)
{
for
(
final
Locale
locale
in
locales
)
{
// Tap along the segment stretching from the center to the edge at
// Tap along the segment stretching from the center to the edge at
// 12:00 AM position.
There are two rings. At ~70% mark, the ring
// 12:00 AM position.
Because there's only one ring, no matter where you
//
switches between inner ring and outer ring
.
//
tap the time will be the same
.
for
(
int
i
=
1
;
i
<
10
;
i
++)
{
for
(
int
i
=
1
;
i
<
10
;
i
++)
{
TimeOfDay
result
;
TimeOfDay
result
;
final
Offset
center
=
await
startPicker
(
tester
,
(
TimeOfDay
time
)
{
result
=
time
;
},
locale:
locale
);
final
Offset
center
=
await
startPicker
(
tester
,
(
TimeOfDay
time
)
{
result
=
time
;
},
locale:
locale
);
...
@@ -125,14 +210,13 @@ void main() {
...
@@ -125,14 +210,13 @@ void main() {
final
double
dy
=
(
size
.
height
/
2.0
/
10
)
*
i
;
final
double
dy
=
(
size
.
height
/
2.0
/
10
)
*
i
;
await
tester
.
tapAt
(
Offset
(
center
.
dx
,
center
.
dy
-
dy
));
await
tester
.
tapAt
(
Offset
(
center
.
dx
,
center
.
dy
-
dy
));
await
finishPicker
(
tester
);
await
finishPicker
(
tester
);
expect
(
result
,
equals
(
TimeOfDay
(
hour:
i
<
7
?
12
:
0
,
minute:
0
)));
expect
(
result
,
equals
(
const
TimeOfDay
(
hour
:
0
,
minute:
0
)));
}
}
}
}
});
});
const
List
<
String
>
labels12To11
=
<
String
>[
'12'
,
'1'
,
'2'
,
'3'
,
'4'
,
'5'
,
'6'
,
'7'
,
'8'
,
'9'
,
'10'
,
'11'
];
const
List
<
String
>
labels12To11
=
<
String
>[
'12'
,
'1'
,
'2'
,
'3'
,
'4'
,
'5'
,
'6'
,
'7'
,
'8'
,
'9'
,
'10'
,
'11'
];
const
List
<
String
>
labels12To11TwoDigit
=
<
String
>[
'12'
,
'01'
,
'02'
,
'03'
,
'04'
,
'05'
,
'06'
,
'07'
,
'08'
,
'09'
,
'10'
,
'11'
];
const
List
<
String
>
labels00To22TwoDigit
=
<
String
>[
'00'
,
'02'
,
'04'
,
'06'
,
'08'
,
'10'
,
'12'
,
'14'
,
'16'
,
'18'
,
'20'
,
'22'
];
const
List
<
String
>
labels00To23
=
<
String
>[
'00'
,
'13'
,
'14'
,
'15'
,
'16'
,
'17'
,
'18'
,
'19'
,
'20'
,
'21'
,
'22'
,
'23'
];
Future
<
void
>
mediaQueryBoilerplate
(
WidgetTester
tester
,
bool
alwaysUse24HourFormat
)
async
{
Future
<
void
>
mediaQueryBoilerplate
(
WidgetTester
tester
,
bool
alwaysUse24HourFormat
)
async
{
await
tester
.
pumpWidget
(
await
tester
.
pumpWidget
(
...
@@ -174,19 +258,17 @@ void main() {
...
@@ -174,19 +258,17 @@ void main() {
final
CustomPaint
dialPaint
=
tester
.
widget
(
find
.
byKey
(
const
ValueKey
<
String
>(
'time-picker-dial'
)));
final
CustomPaint
dialPaint
=
tester
.
widget
(
find
.
byKey
(
const
ValueKey
<
String
>(
'time-picker-dial'
)));
final
dynamic
dialPainter
=
dialPaint
.
painter
;
final
dynamic
dialPainter
=
dialPaint
.
painter
;
final
List
<
dynamic
>
primary
OuterLabels
=
dialPainter
.
primaryOuter
Labels
as
List
<
dynamic
>;
final
List
<
dynamic
>
primary
Labels
=
dialPainter
.
primary
Labels
as
List
<
dynamic
>;
expect
(
expect
(
primary
Outer
Labels
.
map
<
String
>((
dynamic
tp
)
=>
((
tp
.
painter
as
TextPainter
).
text
as
TextSpan
).
text
),
primaryLabels
.
map
<
String
>((
dynamic
tp
)
=>
((
tp
.
painter
as
TextPainter
).
text
as
TextSpan
).
text
),
labels12To11
,
labels12To11
,
);
);
expect
(
dialPainter
.
primaryInnerLabels
,
null
);
final
List
<
dynamic
>
secondary
OuterLabels
=
dialPainter
.
secondaryOuter
Labels
as
List
<
dynamic
>;
final
List
<
dynamic
>
secondary
Labels
=
dialPainter
.
secondary
Labels
as
List
<
dynamic
>;
expect
(
expect
(
secondary
Outer
Labels
.
map
<
String
>((
dynamic
tp
)
=>
((
tp
.
painter
as
TextPainter
).
text
as
TextSpan
).
text
),
secondaryLabels
.
map
<
String
>((
dynamic
tp
)
=>
((
tp
.
painter
as
TextPainter
).
text
as
TextSpan
).
text
),
labels12To11
,
labels12To11
,
);
);
expect
(
dialPainter
.
secondaryInnerLabels
,
null
);
});
});
testWidgets
(
'respects MediaQueryData.alwaysUse24HourFormat == true'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
'respects MediaQueryData.alwaysUse24HourFormat == true'
,
(
WidgetTester
tester
)
async
{
...
@@ -194,26 +276,16 @@ void main() {
...
@@ -194,26 +276,16 @@ void main() {
final
CustomPaint
dialPaint
=
tester
.
widget
(
find
.
byKey
(
const
ValueKey
<
String
>(
'time-picker-dial'
)));
final
CustomPaint
dialPaint
=
tester
.
widget
(
find
.
byKey
(
const
ValueKey
<
String
>(
'time-picker-dial'
)));
final
dynamic
dialPainter
=
dialPaint
.
painter
;
final
dynamic
dialPainter
=
dialPaint
.
painter
;
final
List
<
dynamic
>
primaryOuterLabels
=
dialPainter
.
primaryOuterLabels
as
List
<
dynamic
>;
final
List
<
dynamic
>
primaryLabels
=
dialPainter
.
primaryLabels
as
List
<
dynamic
>;
expect
(
primaryOuterLabels
.
map
<
String
>((
dynamic
tp
)
=>
((
tp
.
painter
as
TextPainter
).
text
as
TextSpan
).
text
),
labels00To23
,
);
final
List
<
dynamic
>
primaryInnerLabels
=
dialPainter
.
primaryInnerLabels
as
List
<
dynamic
>;
expect
(
expect
(
primary
Inner
Labels
.
map
<
String
>((
dynamic
tp
)
=>
((
tp
.
painter
as
TextPainter
).
text
as
TextSpan
).
text
),
primaryLabels
.
map
<
String
>((
dynamic
tp
)
=>
((
tp
.
painter
as
TextPainter
).
text
as
TextSpan
).
text
),
labels
12To11
TwoDigit
,
labels
00To22
TwoDigit
,
);
);
final
List
<
dynamic
>
secondaryOuterLabels
=
dialPainter
.
secondaryOuterLabels
as
List
<
dynamic
>;
final
List
<
dynamic
>
secondaryLabels
=
dialPainter
.
secondaryLabels
as
List
<
dynamic
>;
expect
(
secondaryOuterLabels
.
map
<
String
>((
dynamic
tp
)
=>
((
tp
.
painter
as
TextPainter
).
text
as
TextSpan
).
text
),
labels00To23
,
);
final
List
<
dynamic
>
secondaryInnerLabels
=
dialPainter
.
secondaryInnerLabels
as
List
<
dynamic
>;
expect
(
expect
(
secondary
Inner
Labels
.
map
<
String
>((
dynamic
tp
)
=>
((
tp
.
painter
as
TextPainter
).
text
as
TextSpan
).
text
),
secondaryLabels
.
map
<
String
>((
dynamic
tp
)
=>
((
tp
.
painter
as
TextPainter
).
text
as
TextSpan
).
text
),
labels
12To11
TwoDigit
,
labels
00To22
TwoDigit
,
);
);
});
});
}
}
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