Convert TimePicker to Material 3 (#116396)

* Make some minor changes in preparation for updating the Time Picker to M3

* Revert OutlineInputBorder.borderRadius type change

* Revert more OutlineInputBorder.borderRadius changes.

* Convert TimePicker to Material 3

* Add example test

* Revert OutlineInputBorder.borderRadius type change

* Fix test

* Review Changes

* Merge changes

* Some sizing and elevation fixes

* Fix localization tests
This commit is contained in:
Greg Spencer 2022-12-13 16:09:52 -08:00 committed by GitHub
parent a59dd83d72
commit fae458b925
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 4897 additions and 2558 deletions

View file

@ -49,6 +49,7 @@ import 'package:gen_defaults/surface_tint.dart';
import 'package:gen_defaults/switch_template.dart';
import 'package:gen_defaults/tabs_template.dart';
import 'package:gen_defaults/text_field_template.dart';
import 'package:gen_defaults/time_picker_template.dart';
import 'package:gen_defaults/typography_template.dart';
Map<String, dynamic> _readTokenFile(String fileName) {
@ -167,6 +168,7 @@ Future<void> main(List<String> args) async {
SliderTemplate('md.comp.slider', 'Slider', '$materialLib/slider.dart', tokens).updateFile();
SurfaceTintTemplate('SurfaceTint', '$materialLib/elevation_overlay.dart', tokens).updateFile();
SwitchTemplate('Switch', '$materialLib/switch.dart', tokens).updateFile();
TimePickerTemplate('TimePicker', '$materialLib/time_picker.dart', tokens).updateFile();
TextFieldTemplate('TextField', '$materialLib/text_field.dart', tokens).updateFile();
TabsTemplate('Tabs', '$materialLib/tabs.dart', tokens).updateFile();
TypographyTemplate('Typography', '$materialLib/typography.dart', tokens).updateFile();

View file

@ -0,0 +1,349 @@
// 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 'template.dart';
class TimePickerTemplate extends TokenTemplate {
const TimePickerTemplate(super.blockName, super.fileName, super.tokens, {
super.colorSchemePrefix = '_colors.',
super.textThemePrefix = '_textTheme.'
});
static const String tokenGroup = 'md.comp.time-picker';
static const String hourMinuteComponent = '$tokenGroup.time-selector';
static const String dayPeriodComponent = '$tokenGroup.period-selector';
static const String dialComponent = '$tokenGroup.clock-dial';
static const String variant = '';
@override
String generate() => '''
// Generated version ${tokens["version"]}
class _${blockName}DefaultsM3 extends _TimePickerDefaults {
_${blockName}DefaultsM3(this.context);
final BuildContext context;
late final ColorScheme _colors = Theme.of(context).colorScheme;
late final TextTheme _textTheme = Theme.of(context).textTheme;
@override
Color get backgroundColor {
return ${componentColor("$tokenGroup.container")};
}
@override
ButtonStyle get cancelButtonStyle {
return TextButton.styleFrom();
}
@override
ButtonStyle get confirmButtonStyle {
return TextButton.styleFrom();
}
@override
BorderSide get dayPeriodBorderSide {
return ${border('$dayPeriodComponent.outline')};
}
@override
Color get dayPeriodColor {
return MaterialStateColor.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.selected)) {
return ${componentColor("$dayPeriodComponent.selected.container")};
}
// The unselected day period should match the overall picker dialog color.
// Making it transparent enables that without being redundant and allows
// the optional elevation overlay for dark mode to be visible.
return Colors.transparent;
});
}
@override
OutlinedBorder get dayPeriodShape {
return ${shape("$dayPeriodComponent.container")}.copyWith(side: dayPeriodBorderSide);
}
@override
Size get dayPeriodPortraitSize {
return ${size('$dayPeriodComponent.vertical.container')};
}
@override
Size get dayPeriodLandscapeSize {
return ${size('$dayPeriodComponent.horizontal.container')};
}
@override
Size get dayPeriodInputSize {
// Input size is eight pixels smaller than the portrait size in the spec,
// but there's not token for it yet.
return Size(dayPeriodPortraitSize.width, dayPeriodPortraitSize.height - 8);
}
@override
Color get dayPeriodTextColor {
return MaterialStateColor.resolveWith((Set<MaterialState> states) {
return _dayPeriodForegroundColor.resolve(states);
});
}
MaterialStateProperty<Color> get _dayPeriodForegroundColor {
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
Color? textColor;
if (states.contains(MaterialState.selected)) {
if (states.contains(MaterialState.pressed)) {
textColor = ${componentColor("$dayPeriodComponent.selected.pressed.label-text")};
} else {
// not pressed
if (states.contains(MaterialState.focused)) {
textColor = ${componentColor("$dayPeriodComponent.selected.focus.label-text")};
} else {
// not focused
if (states.contains(MaterialState.hovered)) {
textColor = ${componentColor("$dayPeriodComponent.selected.hover.label-text")};
}
}
}
} else {
// unselected
if (states.contains(MaterialState.pressed)) {
textColor = ${componentColor("$dayPeriodComponent.unselected.pressed.label-text")};
} else {
// not pressed
if (states.contains(MaterialState.focused)) {
textColor = ${componentColor("$dayPeriodComponent.unselected.focus.label-text")};
} else {
// not focused
if (states.contains(MaterialState.hovered)) {
textColor = ${componentColor("$dayPeriodComponent.unselected.hover.label-text")};
}
}
}
}
return textColor ?? ${componentColor("$dayPeriodComponent.selected.label-text")};
});
}
@override
TextStyle get dayPeriodTextStyle {
return ${textStyle("$dayPeriodComponent.label-text")}!.copyWith(color: dayPeriodTextColor);
}
@override
Color get dialBackgroundColor {
return ${componentColor(dialComponent)}.withOpacity(_colors.brightness == Brightness.dark ? 0.12 : 0.08);
}
@override
Color get dialHandColor {
return ${componentColor('$dialComponent.selector.handle.container')};
}
@override
Size get dialSize {
return ${size("$dialComponent.container")};
}
@override
double get handWidth {
return ${size("$dialComponent.selector.track.container")}.width;
}
@override
double get dotRadius {
return ${size("$dialComponent.selector.handle.container")}.width / 2;
}
@override
double get centerRadius {
return ${size("$dialComponent.selector.center.container")}.width / 2;
}
@override
Color get dialTextColor {
return MaterialStateColor.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.selected)) {
return ${componentColor('$dialComponent.selected.label-text')};
}
return ${componentColor('$dialComponent.unselected.label-text')};
});
}
@override
TextStyle get dialTextStyle {
return ${textStyle('$dialComponent.label-text')}!;
}
@override
double get elevation {
return ${elevation("$tokenGroup.container")};
}
@override
Color get entryModeIconColor {
return _colors.onSurface;
}
@override
TextStyle get helpTextStyle {
return MaterialStateTextStyle.resolveWith((Set<MaterialState> states) {
final TextStyle textStyle = ${textStyle('$tokenGroup.headline')}!;
return textStyle.copyWith(color: ${componentColor('$tokenGroup.headline')});
});
}
@override
EdgeInsetsGeometry get padding {
return const EdgeInsets.all(24);
}
@override
Color get hourMinuteColor {
return MaterialStateColor.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.selected)) {
Color overlayColor = ${componentColor('$hourMinuteComponent.selected.container')};
if (states.contains(MaterialState.pressed)) {
overlayColor = ${componentColor('$hourMinuteComponent.selected.pressed.state-layer')};
} else if (states.contains(MaterialState.focused)) {
const double focusOpacity = ${opacity('$hourMinuteComponent.focus.state-layer.opacity')};
overlayColor = ${componentColor('$hourMinuteComponent.selected.focus.state-layer')}.withOpacity(focusOpacity);
} else if (states.contains(MaterialState.hovered)) {
const double hoverOpacity = ${opacity('$hourMinuteComponent.hover.state-layer.opacity')};
overlayColor = ${componentColor('$hourMinuteComponent.selected.hover.state-layer')}.withOpacity(hoverOpacity);
}
return Color.alphaBlend(overlayColor, ${componentColor('$hourMinuteComponent.selected.container')});
} else {
Color overlayColor = ${componentColor('$hourMinuteComponent.unselected.container')};
if (states.contains(MaterialState.pressed)) {
overlayColor = ${componentColor('$hourMinuteComponent.unselected.pressed.state-layer')};
} else if (states.contains(MaterialState.focused)) {
const double focusOpacity = ${opacity('$hourMinuteComponent.focus.state-layer.opacity')};
overlayColor = ${componentColor('$hourMinuteComponent.unselected.focus.state-layer')}.withOpacity(focusOpacity);
} else if (states.contains(MaterialState.hovered)) {
const double hoverOpacity = ${opacity('$hourMinuteComponent.hover.state-layer.opacity')};
overlayColor = ${componentColor('$hourMinuteComponent.unselected.hover.state-layer')}.withOpacity(hoverOpacity);
}
return Color.alphaBlend(overlayColor, ${componentColor('$hourMinuteComponent.unselected.container')});
}
});
}
@override
ShapeBorder get hourMinuteShape {
return ${shape('$hourMinuteComponent.container')};
}
@override
Size get hourMinuteSize {
return ${size('$hourMinuteComponent.container')};
}
@override
Size get hourMinuteSize24Hour {
return Size(${size('$hourMinuteComponent.24h-vertical.container')}.width, hourMinuteSize.height);
}
@override
Size get hourMinuteInputSize {
// Input size is eight pixels smaller than the regular size in the spec, but
// there's not token for it yet.
return Size(hourMinuteSize.width, hourMinuteSize.height - 8);
}
@override
Size get hourMinuteInputSize24Hour {
// Input size is eight pixels smaller than the regular size in the spec, but
// there's not token for it yet.
return Size(hourMinuteSize24Hour.width, hourMinuteSize24Hour.height - 8);
}
@override
Color get hourMinuteTextColor {
return MaterialStateColor.resolveWith((Set<MaterialState> states) {
return _hourMinuteTextColor.resolve(states);
});
}
MaterialStateProperty<Color> get _hourMinuteTextColor {
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.selected)) {
if (states.contains(MaterialState.pressed)) {
return ${componentColor("$hourMinuteComponent.selected.pressed.label-text")};
}
if (states.contains(MaterialState.focused)) {
return ${componentColor("$hourMinuteComponent.selected.focus.label-text")};
}
if (states.contains(MaterialState.hovered)) {
return ${componentColor("$hourMinuteComponent.selected.hover.label-text")};
}
return ${componentColor("$hourMinuteComponent.selected.label-text")};
} else {
// unselected
if (states.contains(MaterialState.pressed)) {
return ${componentColor("$hourMinuteComponent.unselected.pressed.label-text")};
}
if (states.contains(MaterialState.focused)) {
return ${componentColor("$hourMinuteComponent.unselected.focus.label-text")};
}
if (states.contains(MaterialState.hovered)) {
return ${componentColor("$hourMinuteComponent.unselected.hover.label-text")};
}
return ${componentColor("$hourMinuteComponent.unselected.label-text")};
}
});
}
@override
TextStyle get hourMinuteTextStyle {
return MaterialStateTextStyle.resolveWith((Set<MaterialState> states) {
return ${textStyle('$hourMinuteComponent.label-text')}!.copyWith(color: _hourMinuteTextColor.resolve(states));
});
}
@override
InputDecorationTheme get inputDecorationTheme {
// This is NOT correct, but there's no token for
// 'time-input.container.shape', so this is using the radius from the shape
// for the hour/minute selector.
final BorderRadiusGeometry selectorRadius = ${shape('$hourMinuteComponent.container')}.borderRadius;
return InputDecorationTheme(
contentPadding: EdgeInsets.zero,
filled: true,
// This should be derived from a token, but there isn't one for 'time-input'.
fillColor: hourMinuteColor,
// This should be derived from a token, but there isn't one for 'time-input'.
focusColor: _colors.primaryContainer,
enabledBorder: OutlineInputBorder(
borderRadius: selectorRadius,
borderSide: const BorderSide(color: Colors.transparent),
),
errorBorder: OutlineInputBorder(
borderRadius: selectorRadius,
borderSide: BorderSide(color: _colors.error, width: 2),
),
focusedBorder: OutlineInputBorder(
borderRadius: selectorRadius,
borderSide: BorderSide(color: _colors.primary, width: 2),
),
focusedErrorBorder: OutlineInputBorder(
borderRadius: selectorRadius,
borderSide: BorderSide(color: _colors.error, width: 2),
),
hintStyle: hourMinuteTextStyle.copyWith(color: _colors.onSurface.withOpacity(0.36)),
// Prevent the error text from appearing.
// TODO(rami-a): Remove this workaround once
// https://github.com/flutter/flutter/issues/54104
// is fixed.
errorStyle: const TextStyle(fontSize: 0, height: 0),
);
}
@override
ShapeBorder get shape {
return ${shape("$tokenGroup.container")};
}
}
''';
}

View file

@ -0,0 +1,357 @@
// 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.
/// Flutter code sample for [showTimePicker].
import 'package:flutter/material.dart';
void main() {
runApp(const ShowTimePickerApp());
}
class ShowTimePickerApp extends StatefulWidget {
const ShowTimePickerApp({super.key});
@override
State<ShowTimePickerApp> createState() => _ShowTimePickerAppState();
}
class _ShowTimePickerAppState extends State<ShowTimePickerApp> {
ThemeMode themeMode = ThemeMode.dark;
bool useMaterial3 = true;
void setThemeMode(ThemeMode mode) {
setState(() {
themeMode = mode;
});
}
void setUseMaterial3(bool? value) {
setState(() {
useMaterial3 = value!;
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.light(useMaterial3: useMaterial3),
darkTheme: ThemeData.dark(useMaterial3: useMaterial3),
themeMode: themeMode,
home: TimePickerOptions(
themeMode: themeMode,
useMaterial3: useMaterial3,
setThemeMode: setThemeMode,
setUseMaterial3: setUseMaterial3,
),
);
}
}
class TimePickerOptions extends StatefulWidget {
const TimePickerOptions({
super.key,
required this.themeMode,
required this.useMaterial3,
required this.setThemeMode,
required this.setUseMaterial3,
});
final ThemeMode themeMode;
final bool useMaterial3;
final ValueChanged<ThemeMode> setThemeMode;
final ValueChanged<bool?> setUseMaterial3;
@override
State<TimePickerOptions> createState() => _TimePickerOptionsState();
}
class _TimePickerOptionsState extends State<TimePickerOptions> {
TimeOfDay? selectedTime;
TimePickerEntryMode entryMode = TimePickerEntryMode.dial;
Orientation? orientation;
TextDirection textDirection = TextDirection.ltr;
MaterialTapTargetSize tapTargetSize = MaterialTapTargetSize.padded;
bool use24HourTime = false;
void _entryModeChanged(TimePickerEntryMode? value) {
if (value != entryMode) {
setState(() {
entryMode = value!;
});
}
}
void _orientationChanged(Orientation? value) {
if (value != orientation) {
setState(() {
orientation = value;
});
}
}
void _textDirectionChanged(TextDirection? value) {
if (value != textDirection) {
setState(() {
textDirection = value!;
});
}
}
void _tapTargetSizeChanged(MaterialTapTargetSize? value) {
if (value != tapTargetSize) {
setState(() {
tapTargetSize = value!;
});
}
}
void _use24HourTimeChanged(bool? value) {
if (value != use24HourTime) {
setState(() {
use24HourTime = value!;
});
}
}
void _themeModeChanged(ThemeMode? value) {
widget.setThemeMode(value!);
}
@override
Widget build(BuildContext context) {
return Material(
child: Column(
children: <Widget>[
Expanded(
child: GridView(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 350,
mainAxisSpacing: 4,
mainAxisExtent: 200 * MediaQuery.textScaleFactorOf(context),
crossAxisSpacing: 4,
),
children: <Widget>[
EnumCard<TimePickerEntryMode>(
choices: TimePickerEntryMode.values,
value: entryMode,
onChanged: _entryModeChanged,
),
EnumCard<ThemeMode>(
choices: ThemeMode.values,
value: widget.themeMode,
onChanged: _themeModeChanged,
),
EnumCard<TextDirection>(
choices: TextDirection.values,
value: textDirection,
onChanged: _textDirectionChanged,
),
EnumCard<MaterialTapTargetSize>(
choices: MaterialTapTargetSize.values,
value: tapTargetSize,
onChanged: _tapTargetSizeChanged,
),
ChoiceCard<Orientation?>(
choices: const <Orientation?>[...Orientation.values, null],
value: orientation,
title: '$Orientation',
choiceLabels: <Orientation?, String>{
for (final Orientation choice in Orientation.values) choice: choice.name,
null: 'from MediaQuery',
},
onChanged: _orientationChanged,
),
ChoiceCard<bool>(
choices: const <bool>[false, true],
value: use24HourTime,
onChanged: _use24HourTimeChanged,
title: 'Time Mode',
choiceLabels: const <bool, String>{
false: '12-hour am/pm time',
true: '24-hour time',
},
),
ChoiceCard<bool>(
choices: const <bool>[false, true],
value: widget.useMaterial3,
onChanged: widget.setUseMaterial3,
title: 'Material Version',
choiceLabels: const <bool, String>{
false: 'Material 2',
true: 'Material 3',
},
),
],
),
),
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(12.0),
child: ElevatedButton(
child: const Text('Open time picker'),
onPressed: () async {
final TimeOfDay? time = await showTimePicker(
context: context,
initialTime: selectedTime ?? TimeOfDay.now(),
initialEntryMode: entryMode,
orientation: orientation,
builder: (BuildContext context, Widget? child) {
// We just wrap these environmental changes around the
// child in this builder so that we can apply the
// options selected above. In regular usage, this is
// rarely necessary, because the default values are
// usually used as-is.
return Theme(
data: Theme.of(context).copyWith(
materialTapTargetSize: tapTargetSize,
),
child: Directionality(
textDirection: textDirection,
child: MediaQuery(
data: MediaQuery.of(context).copyWith(
alwaysUse24HourFormat: use24HourTime,
),
child: child!,
),
),
);
},
);
setState(() {
selectedTime = time;
});
},
),
),
if (selectedTime != null) Text('Selected time: ${selectedTime!.format(context)}'),
],
),
),
],
),
);
}
}
// This is a simple card that presents a set of radio buttons (inside of a
// RadioSelection, defined below) for the user to select from.
class ChoiceCard<T extends Object?> extends StatelessWidget {
const ChoiceCard({
super.key,
required this.value,
required this.choices,
required this.onChanged,
required this.choiceLabels,
required this.title,
});
final T value;
final Iterable<T> choices;
final Map<T, String> choiceLabels;
final String title;
final ValueChanged<T?> onChanged;
@override
Widget build(BuildContext context) {
return Card(
// If the card gets too small, let it scroll both directions.
child: SingleChildScrollView(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(title),
),
for (final T choice in choices)
RadioSelection<T>(
value: choice,
groupValue: value,
onChanged: onChanged,
child: Text(choiceLabels[choice]!),
),
],
),
),
),
),
);
}
}
// This aggregates a ChoiceCard so that it presents a set of radio buttons for
// the allowed enum values for the user to select from.
class EnumCard<T extends Enum> extends StatelessWidget {
const EnumCard({
super.key,
required this.value,
required this.choices,
required this.onChanged,
});
final T value;
final Iterable<T> choices;
final ValueChanged<T?> onChanged;
@override
Widget build(BuildContext context) {
return ChoiceCard<T>(
value: value,
choices: choices,
onChanged: onChanged,
choiceLabels: <T, String>{
for (final T choice in choices) choice: choice.name,
},
title: value.runtimeType.toString());
}
}
// A button that has a radio button on one side and a label child. Tapping on
// the label or the radio button selects the item.
class RadioSelection<T extends Object?> extends StatefulWidget {
const RadioSelection({
super.key,
required this.value,
required this.groupValue,
required this.onChanged,
required this.child,
});
final T value;
final T? groupValue;
final ValueChanged<T?> onChanged;
final Widget child;
@override
State<RadioSelection<T>> createState() => _RadioSelectionState<T>();
}
class _RadioSelectionState<T extends Object?> extends State<RadioSelection<T>> {
@override
Widget build(BuildContext context) {
return Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Padding(
padding: const EdgeInsetsDirectional.only(end: 8),
child: Radio<T>(
groupValue: widget.groupValue,
value: widget.value,
onChanged: widget.onChanged,
),
),
GestureDetector(onTap: () => widget.onChanged(widget.value), child: widget.child),
],
);
}
}

View file

@ -0,0 +1,67 @@
// 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_api_samples/material/time_picker/show_time_picker.0.dart' as example;
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('Can open and modify time picker', (WidgetTester tester) async {
const String openPicker = 'Open time picker';
final List<String> options = <String>[
'$TimePickerEntryMode',
... TimePickerEntryMode.values.map<String>((TimePickerEntryMode value) => value.name),
'$ThemeMode',
... ThemeMode.values.map<String>((ThemeMode value) => value.name),
'$TextDirection',
... TextDirection.values.map<String>((TextDirection value) => value.name),
'$MaterialTapTargetSize',
... MaterialTapTargetSize.values.map<String>((MaterialTapTargetSize value) => value.name),
'$Orientation',
... Orientation.values.map<String>((Orientation value) => value.name),
'Time Mode',
'12-hour am/pm time',
'24-hour time',
'Material Version',
'Material 2',
'Material 3',
openPicker,
];
await tester.pumpWidget(
const MaterialApp(
home: Scaffold(
body: example.ShowTimePickerApp(),
),
),
);
for (final String option in options) {
expect(find.text(option), findsOneWidget, reason: 'Unable to find $option widget in example.');
}
// Open time picker
await tester.tap(find.text(openPicker));
await tester.pumpAndSettle();
expect(find.text('Select time'), findsOneWidget);
expect(find.text('Cancel'), findsOneWidget);
expect(find.text('OK'), findsOneWidget);
// Close time picker
await tester.tapAt(const Offset(1, 1));
await tester.pumpAndSettle();
expect(find.text('Select time'), findsNothing);
expect(find.text('Cancel'), findsNothing);
expect(find.text('OK'), findsNothing);
// Change an option.
await tester.tap(find.text('Material 2'));
await tester.pumpAndSettle();
await tester.tap(find.text(openPicker));
await tester.pumpAndSettle();
expect(find.text('SELECT TIME'), findsOneWidget);
expect(find.text('CANCEL'), findsOneWidget);
expect(find.text('OK'), findsOneWidget);
});
}

File diff suppressed because it is too large Load diff

View file

@ -2,10 +2,14 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:ui';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'button_style.dart';
import 'input_decorator.dart';
import 'material_state.dart';
import 'theme.dart';
// Examples can assume:
@ -36,22 +40,27 @@ class TimePickerThemeData with Diagnosticable {
/// [ThemeData.timePickerTheme].
const TimePickerThemeData({
this.backgroundColor,
this.hourMinuteTextColor,
this.hourMinuteColor,
this.dayPeriodTextColor,
this.dayPeriodColor,
this.dialHandColor,
this.dialBackgroundColor,
this.dialTextColor,
this.entryModeIconColor,
this.hourMinuteTextStyle,
this.dayPeriodTextStyle,
this.helpTextStyle,
this.shape,
this.hourMinuteShape,
this.dayPeriodShape,
this.cancelButtonStyle,
this.confirmButtonStyle,
this.dayPeriodBorderSide,
this.dayPeriodColor,
this.dayPeriodShape,
this.dayPeriodTextColor,
this.dayPeriodTextStyle,
this.dialBackgroundColor,
this.dialHandColor,
this.dialTextColor,
this.dialTextStyle,
this.elevation,
this.entryModeIconColor,
this.helpTextStyle,
this.hourMinuteColor,
this.hourMinuteShape,
this.hourMinuteTextColor,
this.hourMinuteTextStyle,
this.inputDecorationTheme,
this.padding,
this.shape,
});
/// The background color of a time picker.
@ -60,41 +69,25 @@ class TimePickerThemeData with Diagnosticable {
/// [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 style of the cancel button of a [TimePickerDialog].
final ButtonStyle? cancelButtonStyle;
/// 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 style of the conform (OK) button of a [TimePickerDialog].
final ButtonStyle? confirmButtonStyle;
/// The color of the day period text that represents AM/PM.
/// The color and weight of the day period's outline.
///
/// 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.
/// If this is null, the time picker defaults to:
///
/// 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;
/// ```dart
/// BorderSide(
/// color: Color.alphaBlend(
/// Theme.of(context).colorScheme.onBackground.withOpacity(0.38),
/// Theme.of(context).colorScheme.surface,
/// ),
/// ),
/// ```
final BorderSide? dayPeriodBorderSide;
/// The background color of the AM/PM toggle.
///
@ -111,69 +104,6 @@ class TimePickerThemeData with Diagnosticable {
/// [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 dial text that represents specific hours and minutes.
///
/// If [dialTextColor] is a [MaterialStateColor], then the effective
/// text color can depend on the [MaterialState.selected] state, i.e. if the
/// text is selected or not.
///
/// If this color is null then the dial's text colors are based on the
/// theme's [ThemeData.colorScheme].
final Color? dialTextColor;
/// The color of the entry mode [IconButton].
///
/// If this is null, the time picker defaults to:
///
/// ```dart
/// 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.titleMedium].
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.labelSmall].
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:
@ -186,64 +116,180 @@ class TimePickerThemeData with Diagnosticable {
/// ```
final OutlinedBorder? dayPeriodShape;
/// The color and weight of the day period's outline.
/// 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;
/// 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.titleMedium].
final TextStyle? dayPeriodTextStyle;
/// The background color of the time picker dial when the entry mode is
/// [TimePickerEntryMode.dial] or [TimePickerEntryMode.dialOnly].
///
/// If this is null, the time picker defaults to the overall theme's
/// [ColorScheme.primary].
final Color? dialBackgroundColor;
/// The color of the time picker dial's hand when the entry mode is
/// [TimePickerEntryMode.dial] or [TimePickerEntryMode.dialOnly].
///
/// If this is null, the time picker defaults to the overall theme's
/// [ColorScheme.primary].
final Color? dialHandColor;
/// The color of the dial text that represents specific hours and minutes.
///
/// If [dialTextColor] is a [MaterialStateColor], then the effective
/// text color can depend on the [MaterialState.selected] state, i.e. if the
/// text is selected or not.
///
/// If this color is null then the dial's text colors are based on the
/// theme's [ThemeData.colorScheme].
final Color? dialTextColor;
/// The [TextStyle] for the numbers on the time selection dial.
///
/// If [dialTextStyle]'s [TextStyle.color] is a [MaterialStateColor], then the
/// effective text color can depend on the [MaterialState.selected] state,
/// i.e. if the text is selected or not.
///
/// If this style is null then the dial's text style is based on the theme's
/// [ThemeData.textTheme].
final TextStyle? dialTextStyle;
/// The Material elevation for the time picker dialog.
final double? elevation;
/// The color of the entry mode [IconButton].
///
/// If this is null, the time picker defaults to:
///
///
/// ```dart
/// BorderSide(
/// color: Color.alphaBlend(
/// Theme.of(context).colorScheme.onBackground.withOpacity(0.38),
/// Theme.of(context).colorScheme.surface,
/// ),
/// ),
/// Theme.of(context).colorScheme.onSurface.withOpacity(
/// Theme.of(context).colorScheme.brightness == Brightness.dark ? 1.0 : 0.6,
/// )
/// ```
final BorderSide? dayPeriodBorderSide;
final Color? entryModeIconColor;
/// 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.labelSmall].
final TextStyle? helpTextStyle;
/// The background color of the hour and minute 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 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 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;
/// 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;
/// 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;
/// The padding around the time picker dialog when the entry mode is
/// [TimePickerEntryMode.dial] or [TimePickerEntryMode.dialOnly].
final EdgeInsetsGeometry? padding;
/// 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;
/// Creates a copy of this object with the given fields replaced with the
/// new values.
TimePickerThemeData copyWith({
Color? backgroundColor,
Color? hourMinuteTextColor,
Color? hourMinuteColor,
Color? dayPeriodTextColor,
Color? dayPeriodColor,
Color? dialHandColor,
Color? dialBackgroundColor,
Color? dialTextColor,
Color? entryModeIconColor,
TextStyle? hourMinuteTextStyle,
TextStyle? dayPeriodTextStyle,
TextStyle? helpTextStyle,
ShapeBorder? shape,
ShapeBorder? hourMinuteShape,
OutlinedBorder? dayPeriodShape,
ButtonStyle? cancelButtonStyle,
ButtonStyle? confirmButtonStyle,
ButtonStyle? dayPeriodButtonStyle,
BorderSide? dayPeriodBorderSide,
Color? dayPeriodColor,
OutlinedBorder? dayPeriodShape,
Color? dayPeriodTextColor,
TextStyle? dayPeriodTextStyle,
Color? dialBackgroundColor,
Color? dialHandColor,
Color? dialTextColor,
TextStyle? dialTextStyle,
double? elevation,
Color? entryModeIconColor,
TextStyle? helpTextStyle,
Color? hourMinuteColor,
ShapeBorder? hourMinuteShape,
Color? hourMinuteTextColor,
TextStyle? hourMinuteTextStyle,
InputDecorationTheme? inputDecorationTheme,
EdgeInsetsGeometry? padding,
ShapeBorder? shape,
}) {
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,
dialTextColor: dialTextColor ?? this.dialTextColor,
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,
cancelButtonStyle: cancelButtonStyle ?? this.cancelButtonStyle,
confirmButtonStyle: confirmButtonStyle ?? this.confirmButtonStyle,
dayPeriodBorderSide: dayPeriodBorderSide ?? this.dayPeriodBorderSide,
dayPeriodColor: dayPeriodColor ?? this.dayPeriodColor,
dayPeriodShape: dayPeriodShape ?? this.dayPeriodShape,
dayPeriodTextColor: dayPeriodTextColor ?? this.dayPeriodTextColor,
dayPeriodTextStyle: dayPeriodTextStyle ?? this.dayPeriodTextStyle,
dialBackgroundColor: dialBackgroundColor ?? this.dialBackgroundColor,
dialHandColor: dialHandColor ?? this.dialHandColor,
dialTextColor: dialTextColor ?? this.dialTextColor,
dialTextStyle: dialTextStyle ?? this.dialTextStyle,
elevation: elevation ?? this.elevation,
entryModeIconColor: entryModeIconColor ?? this.entryModeIconColor,
helpTextStyle: helpTextStyle ?? this.helpTextStyle,
hourMinuteColor: hourMinuteColor ?? this.hourMinuteColor,
hourMinuteShape: hourMinuteShape ?? this.hourMinuteShape,
hourMinuteTextColor: hourMinuteTextColor ?? this.hourMinuteTextColor,
hourMinuteTextStyle: hourMinuteTextStyle ?? this.hourMinuteTextStyle,
inputDecorationTheme: inputDecorationTheme ?? this.inputDecorationTheme,
padding: padding ?? this.padding,
shape: shape ?? this.shape,
);
}
@ -268,45 +314,55 @@ class TimePickerThemeData with Diagnosticable {
}
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),
dialTextColor: Color.lerp(a?.dialTextColor, b?.dialTextColor, 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?,
cancelButtonStyle: ButtonStyle.lerp(a?.cancelButtonStyle, b?.cancelButtonStyle, t),
confirmButtonStyle: ButtonStyle.lerp(a?.confirmButtonStyle, b?.confirmButtonStyle, t),
dayPeriodBorderSide: lerpedBorderSide,
dayPeriodColor: Color.lerp(a?.dayPeriodColor, b?.dayPeriodColor, t),
dayPeriodShape: ShapeBorder.lerp(a?.dayPeriodShape, b?.dayPeriodShape, t) as OutlinedBorder?,
dayPeriodTextColor: Color.lerp(a?.dayPeriodTextColor, b?.dayPeriodTextColor, t),
dayPeriodTextStyle: TextStyle.lerp(a?.dayPeriodTextStyle, b?.dayPeriodTextStyle, t),
dialBackgroundColor: Color.lerp(a?.dialBackgroundColor, b?.dialBackgroundColor, t),
dialHandColor: Color.lerp(a?.dialHandColor, b?.dialHandColor, t),
dialTextColor: Color.lerp(a?.dialTextColor, b?.dialTextColor, t),
dialTextStyle: TextStyle.lerp(a?.dialTextStyle, b?.dialTextStyle, t),
elevation: lerpDouble(a?.elevation, b?.elevation, t),
entryModeIconColor: Color.lerp(a?.entryModeIconColor, b?.entryModeIconColor, t),
helpTextStyle: TextStyle.lerp(a?.helpTextStyle, b?.helpTextStyle, t),
hourMinuteColor: Color.lerp(a?.hourMinuteColor, b?.hourMinuteColor, t),
hourMinuteShape: ShapeBorder.lerp(a?.hourMinuteShape, b?.hourMinuteShape, t),
hourMinuteTextColor: Color.lerp(a?.hourMinuteTextColor, b?.hourMinuteTextColor, t),
hourMinuteTextStyle: TextStyle.lerp(a?.hourMinuteTextStyle, b?.hourMinuteTextStyle, t),
inputDecorationTheme: t < 0.5 ? a?.inputDecorationTheme : b?.inputDecorationTheme,
padding: EdgeInsetsGeometry.lerp(a?.padding, b?.padding, t),
shape: ShapeBorder.lerp(a?.shape, b?.shape, t),
);
}
@override
int get hashCode => Object.hash(
int get hashCode => Object.hashAll(<Object?>[
backgroundColor,
hourMinuteTextColor,
hourMinuteColor,
dayPeriodTextColor,
dayPeriodColor,
dialHandColor,
dialBackgroundColor,
dialTextColor,
entryModeIconColor,
hourMinuteTextStyle,
dayPeriodTextStyle,
helpTextStyle,
shape,
hourMinuteShape,
dayPeriodShape,
cancelButtonStyle,
confirmButtonStyle,
dayPeriodBorderSide,
dayPeriodColor,
dayPeriodShape,
dayPeriodTextColor,
dayPeriodTextStyle,
dialBackgroundColor,
dialHandColor,
dialTextColor,
dialTextStyle,
elevation,
entryModeIconColor,
helpTextStyle,
hourMinuteColor,
hourMinuteShape,
hourMinuteTextColor,
hourMinuteTextStyle,
inputDecorationTheme,
);
padding,
shape,
]);
@override
bool operator ==(Object other) {
@ -318,44 +374,54 @@ class TimePickerThemeData with Diagnosticable {
}
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.dialTextColor == dialTextColor
&& other.entryModeIconColor == entryModeIconColor
&& other.hourMinuteTextStyle == hourMinuteTextStyle
&& other.dayPeriodTextStyle == dayPeriodTextStyle
&& other.helpTextStyle == helpTextStyle
&& other.shape == shape
&& other.hourMinuteShape == hourMinuteShape
&& other.dayPeriodShape == dayPeriodShape
&& other.cancelButtonStyle == cancelButtonStyle
&& other.confirmButtonStyle == confirmButtonStyle
&& other.dayPeriodBorderSide == dayPeriodBorderSide
&& other.inputDecorationTheme == inputDecorationTheme;
&& other.dayPeriodColor == dayPeriodColor
&& other.dayPeriodShape == dayPeriodShape
&& other.dayPeriodTextColor == dayPeriodTextColor
&& other.dayPeriodTextStyle == dayPeriodTextStyle
&& other.dialBackgroundColor == dialBackgroundColor
&& other.dialHandColor == dialHandColor
&& other.dialTextColor == dialTextColor
&& other.dialTextStyle == dialTextStyle
&& other.elevation == elevation
&& other.entryModeIconColor == entryModeIconColor
&& other.helpTextStyle == helpTextStyle
&& other.hourMinuteColor == hourMinuteColor
&& other.hourMinuteShape == hourMinuteShape
&& other.hourMinuteTextColor == hourMinuteTextColor
&& other.hourMinuteTextStyle == hourMinuteTextStyle
&& other.inputDecorationTheme == inputDecorationTheme
&& other.padding == padding
&& other.shape == shape;
}
@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('dialTextColor', dialTextColor, 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<ButtonStyle>('cancelButtonStyle', cancelButtonStyle, defaultValue: null));
properties.add(DiagnosticsProperty<ButtonStyle>('confirmButtonStyle', confirmButtonStyle, defaultValue: null));
properties.add(DiagnosticsProperty<BorderSide>('dayPeriodBorderSide', dayPeriodBorderSide, defaultValue: null));
properties.add(ColorProperty('dayPeriodColor', dayPeriodColor, defaultValue: null));
properties.add(DiagnosticsProperty<ShapeBorder>('dayPeriodShape', dayPeriodShape, defaultValue: null));
properties.add(ColorProperty('dayPeriodTextColor', dayPeriodTextColor, defaultValue: null));
properties.add(DiagnosticsProperty<TextStyle>('dayPeriodTextStyle', dayPeriodTextStyle, defaultValue: null));
properties.add(ColorProperty('dialBackgroundColor', dialBackgroundColor, defaultValue: null));
properties.add(ColorProperty('dialHandColor', dialHandColor, defaultValue: null));
properties.add(ColorProperty('dialTextColor', dialTextColor, defaultValue: null));
properties.add(DiagnosticsProperty<TextStyle?>('dialTextStyle', dialTextStyle, defaultValue: null));
properties.add(DoubleProperty('elevation', elevation, defaultValue: null));
properties.add(ColorProperty('entryModeIconColor', entryModeIconColor, defaultValue: null));
properties.add(DiagnosticsProperty<TextStyle>('helpTextStyle', helpTextStyle, defaultValue: null));
properties.add(ColorProperty('hourMinuteColor', hourMinuteColor, defaultValue: null));
properties.add(DiagnosticsProperty<ShapeBorder>('hourMinuteShape', hourMinuteShape, defaultValue: null));
properties.add(ColorProperty('hourMinuteTextColor', hourMinuteTextColor, defaultValue: null));
properties.add(DiagnosticsProperty<TextStyle>('hourMinuteTextStyle', hourMinuteTextStyle, defaultValue: null));
properties.add(DiagnosticsProperty<InputDecorationTheme>('inputDecorationTheme', inputDecorationTheme, defaultValue: null));
properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding, defaultValue: null));
properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null));
}
}

File diff suppressed because it is too large Load diff

View file

@ -75,21 +75,21 @@ void main() {
expect(description, <String>[
'backgroundColor: Color(0xffffffff)',
'hourMinuteTextColor: Color(0xffffffff)',
'hourMinuteColor: Color(0xffffffff)',
'dayPeriodTextColor: Color(0xffffffff)',
'dayPeriodBorderSide: BorderSide',
'dayPeriodColor: Color(0xffffffff)',
'dialHandColor: Color(0xffffffff)',
'dayPeriodShape: RoundedRectangleBorder(BorderSide(width: 0.0, style: none), BorderRadius.zero)',
'dayPeriodTextColor: Color(0xffffffff)',
'dayPeriodTextStyle: TextStyle(<all styles inherited>)',
'dialBackgroundColor: Color(0xffffffff)',
'dialHandColor: Color(0xffffffff)',
'dialTextColor: Color(0xffffffff)',
'entryModeIconColor: Color(0xffffffff)',
'hourMinuteTextStyle: TextStyle(<all styles inherited>)',
'dayPeriodTextStyle: TextStyle(<all styles inherited>)',
'helpTextStyle: TextStyle(<all styles inherited>)',
'shape: RoundedRectangleBorder(BorderSide(width: 0.0, style: none), BorderRadius.zero)',
'hourMinuteColor: Color(0xffffffff)',
'hourMinuteShape: RoundedRectangleBorder(BorderSide(width: 0.0, style: none), BorderRadius.zero)',
'dayPeriodShape: RoundedRectangleBorder(BorderSide(width: 0.0, style: none), BorderRadius.zero)',
'dayPeriodBorderSide: BorderSide',
'hourMinuteTextColor: Color(0xffffffff)',
'hourMinuteTextStyle: TextStyle(<all styles inherited>)',
'shape: RoundedRectangleBorder(BorderSide(width: 0.0, style: none), BorderRadius.zero)'
]);
});
@ -104,10 +104,11 @@ void main() {
expect(dialogMaterial.shape, const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4.0))));
final RenderBox dial = tester.firstRenderObject<RenderBox>(find.byType(CustomPaint));
debugPrint('Color: ${defaultTheme.colorScheme.onSurface.withOpacity(0.08)}');
expect(
dial,
paints
..circle(color: defaultTheme.colorScheme.onBackground.withOpacity(0.12)) // Dial background color.
..circle(color: defaultTheme.colorScheme.onSurface.withOpacity(0.08)) // Dial background color.
..circle(color: Color(defaultTheme.colorScheme.primary.value)), // Dial hand color.
);
@ -162,10 +163,10 @@ void main() {
.copyWith(color: defaultTheme.colorScheme.onSurface),
);
// ignore: avoid_dynamic_calls
final List<dynamic> secondaryLabels = dialPainter.secondaryLabels as List<dynamic>;
final List<dynamic> selectedLabels = dialPainter.selectedLabels as List<dynamic>;
expect(
// ignore: avoid_dynamic_calls
secondaryLabels.first.painter.text.style,
selectedLabels.first.painter.text.style,
Typography.material2014().englishLike.bodyLarge!
.merge(Typography.material2014().white.bodyLarge)
.copyWith(color: defaultTheme.colorScheme.onPrimary),
@ -186,7 +187,7 @@ void main() {
expect(pmMaterial.color, Colors.transparent);
final Color expectedBorderColor = Color.alphaBlend(
defaultTheme.colorScheme.onBackground.withOpacity(0.38),
defaultTheme.colorScheme.onSurface.withOpacity(0.38),
defaultTheme.colorScheme.surface,
);
final Material dayPeriodMaterial = _dayPeriodMaterial(tester);
@ -220,7 +221,7 @@ void main() {
final InputDecoration hourDecoration = _textField(tester, '7').decoration!;
expect(hourDecoration.filled, true);
expect(hourDecoration.fillColor, defaultTheme.colorScheme.onSurface.withOpacity(0.12));
expect(hourDecoration.fillColor, MaterialStateColor.resolveWith((Set<MaterialState> states) => 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)));
@ -307,10 +308,10 @@ void main() {
.copyWith(color: _unselectedColor),
);
// ignore: avoid_dynamic_calls
final List<dynamic> secondaryLabels = dialPainter.secondaryLabels as List<dynamic>;
final List<dynamic> selectedLabels = dialPainter.selectedLabels as List<dynamic>;
expect(
// ignore: avoid_dynamic_calls
secondaryLabels.first.painter.text.style,
selectedLabels.first.painter.text.style,
Typography.material2014().englishLike.bodyLarge!
.merge(Typography.material2014().white.bodyLarge)
.copyWith(color: _selectedColor),

View file

@ -6,62 +6,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_test/flutter_test.dart';
class _TimePickerLauncher extends StatelessWidget {
const _TimePickerLauncher({
this.onChanged,
required this.locale,
this.entryMode = TimePickerEntryMode.dial,
});
final ValueChanged<TimeOfDay?>? onChanged;
final Locale locale;
final TimePickerEntryMode entryMode;
@override
Widget build(BuildContext context) {
return MaterialApp(
locale: locale,
supportedLocales: <Locale>[locale],
localizationsDelegates: GlobalMaterialLocalizations.delegates,
home: Material(
child: Center(
child: Builder(
builder: (BuildContext context) {
return ElevatedButton(
child: const Text('X'),
onPressed: () async {
onChanged?.call(await showTimePicker(
context: context,
initialEntryMode: entryMode,
initialTime: const TimeOfDay(hour: 7, minute: 0),
));
},
);
}
),
),
),
);
}
}
Future<Offset> startPicker(
WidgetTester tester,
ValueChanged<TimeOfDay?> onChanged, {
Locale locale = const Locale('en', 'US'),
}) async {
await tester.pumpWidget(_TimePickerLauncher(onChanged: onChanged, locale: locale,));
await tester.tap(find.text('X'));
await tester.pumpAndSettle(const Duration(seconds: 1));
return tester.getCenter(find.byKey(const Key('time-picker-dial')));
}
Future<void> finishPicker(WidgetTester tester) async {
final MaterialLocalizations materialLocalizations = MaterialLocalizations.of(tester.element(find.byType(ElevatedButton)));
await tester.tap(find.text(materialLocalizations.okButtonLabel));
await tester.pumpAndSettle(const Duration(seconds: 1));
}
void main() {
testWidgets('can localize the header in all known formats - portrait', (WidgetTester tester) async {
// Ensure picker is displayed in portrait mode.
@ -213,13 +157,13 @@ void main() {
});
testWidgets('can localize input mode in all known formats', (WidgetTester tester) async {
final Finder hourControlFinder = find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_HourTextField');
final Finder minuteControlFinder = find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_MinuteTextField');
final Finder dayPeriodControlFinder = find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_DayPeriodControl');
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}' == '_HourTextField');
final Finder minuteControlFinder = find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_MinuteTextField');
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>[
@ -276,6 +220,7 @@ void main() {
expect(dayPeriodControlFinder, findsNothing);
}
await finishPicker(tester);
expect(tester.takeException(), isNot(throwsFlutterError));
}
});
@ -353,10 +298,10 @@ void main() {
);
// ignore: avoid_dynamic_calls
final List<dynamic> secondaryLabels = dialPainter.secondaryLabels as List<dynamic>;
final List<dynamic> selectedLabels = dialPainter.selectedLabels as List<dynamic>;
expect(
// ignore: avoid_dynamic_calls
secondaryLabels.map<String>((dynamic tp) => ((tp.painter as TextPainter).text! as TextSpan).text!),
selectedLabels.map<String>((dynamic tp) => ((tp.painter as TextPainter).text! as TextSpan).text!),
labels12To11,
);
});
@ -375,11 +320,72 @@ void main() {
);
// ignore: avoid_dynamic_calls
final List<dynamic> secondaryLabels = dialPainter.secondaryLabels as List<dynamic>;
final List<dynamic> selectedLabels = dialPainter.selectedLabels as List<dynamic>;
expect(
// ignore: avoid_dynamic_calls
secondaryLabels.map<String>((dynamic tp) => ((tp.painter as TextPainter).text! as TextSpan).text!),
selectedLabels.map<String>((dynamic tp) => ((tp.painter as TextPainter).text! as TextSpan).text!),
labels00To22TwoDigit,
);
});
}
class _TimePickerLauncher extends StatelessWidget {
const _TimePickerLauncher({
this.onChanged,
required this.locale,
this.entryMode = TimePickerEntryMode.dial,
});
final ValueChanged<TimeOfDay?>? onChanged;
final Locale locale;
final TimePickerEntryMode entryMode;
@override
Widget build(BuildContext context) {
return MaterialApp(
locale: locale,
supportedLocales: <Locale>[locale],
localizationsDelegates: GlobalMaterialLocalizations.delegates,
home: Material(
child: Center(
child: Builder(
builder: (BuildContext context) {
return ElevatedButton(
child: const Text('X'),
onPressed: () async {
onChanged?.call(await showTimePicker(
context: context,
initialEntryMode: entryMode,
initialTime: const TimeOfDay(hour: 7, minute: 0),
));
},
);
}
),
),
),
);
}
}
Future<Offset> startPicker(
WidgetTester tester,
ValueChanged<TimeOfDay?> onChanged, {
Locale locale = const Locale('en', 'US'),
}) async {
await tester.pumpWidget(
_TimePickerLauncher(
onChanged: onChanged,
locale: locale,
),
);
await tester.tap(find.text('X'));
await tester.pumpAndSettle(const Duration(seconds: 1));
return tester.getCenter(find.byKey(const Key('time-picker-dial')));
}
Future<void> finishPicker(WidgetTester tester) async {
final MaterialLocalizations materialLocalizations = MaterialLocalizations.of(tester.element(find.byType(ElevatedButton)));
await tester.tap(find.text(materialLocalizations.okButtonLabel));
await tester.pumpAndSettle(const Duration(seconds: 1));
}