mirror of
https://github.com/flutter/flutter
synced 2024-09-13 21:32:11 +00:00
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:
parent
a59dd83d72
commit
fae458b925
|
@ -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();
|
||||
|
|
349
dev/tools/gen_defaults/lib/time_picker_template.dart
Normal file
349
dev/tools/gen_defaults/lib/time_picker_template.dart
Normal 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")};
|
||||
}
|
||||
}
|
||||
''';
|
||||
}
|
357
examples/api/lib/material/time_picker/show_time_picker.0.dart
Normal file
357
examples/api/lib/material/time_picker/show_time_picker.0.dart
Normal 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),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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
|
@ -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
|
@ -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),
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue