mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +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/switch_template.dart';
|
||||||
import 'package:gen_defaults/tabs_template.dart';
|
import 'package:gen_defaults/tabs_template.dart';
|
||||||
import 'package:gen_defaults/text_field_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';
|
import 'package:gen_defaults/typography_template.dart';
|
||||||
|
|
||||||
Map<String, dynamic> _readTokenFile(String fileName) {
|
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();
|
SliderTemplate('md.comp.slider', 'Slider', '$materialLib/slider.dart', tokens).updateFile();
|
||||||
SurfaceTintTemplate('SurfaceTint', '$materialLib/elevation_overlay.dart', tokens).updateFile();
|
SurfaceTintTemplate('SurfaceTint', '$materialLib/elevation_overlay.dart', tokens).updateFile();
|
||||||
SwitchTemplate('Switch', '$materialLib/switch.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();
|
TextFieldTemplate('TextField', '$materialLib/text_field.dart', tokens).updateFile();
|
||||||
TabsTemplate('Tabs', '$materialLib/tabs.dart', tokens).updateFile();
|
TabsTemplate('Tabs', '$materialLib/tabs.dart', tokens).updateFile();
|
||||||
TypographyTemplate('Typography', '$materialLib/typography.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
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
import 'button_style.dart';
|
||||||
import 'input_decorator.dart';
|
import 'input_decorator.dart';
|
||||||
|
import 'material_state.dart';
|
||||||
import 'theme.dart';
|
import 'theme.dart';
|
||||||
|
|
||||||
// Examples can assume:
|
// Examples can assume:
|
||||||
@ -36,22 +40,27 @@ class TimePickerThemeData with Diagnosticable {
|
|||||||
/// [ThemeData.timePickerTheme].
|
/// [ThemeData.timePickerTheme].
|
||||||
const TimePickerThemeData({
|
const TimePickerThemeData({
|
||||||
this.backgroundColor,
|
this.backgroundColor,
|
||||||
this.hourMinuteTextColor,
|
this.cancelButtonStyle,
|
||||||
this.hourMinuteColor,
|
this.confirmButtonStyle,
|
||||||
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.dayPeriodBorderSide,
|
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.inputDecorationTheme,
|
||||||
|
this.padding,
|
||||||
|
this.shape,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// The background color of a time picker.
|
/// The background color of a time picker.
|
||||||
@ -60,41 +69,25 @@ class TimePickerThemeData with Diagnosticable {
|
|||||||
/// [ColorScheme.background].
|
/// [ColorScheme.background].
|
||||||
final Color? backgroundColor;
|
final Color? backgroundColor;
|
||||||
|
|
||||||
/// The color of the header text that represents hours and minutes.
|
/// The style of the cancel button of a [TimePickerDialog].
|
||||||
///
|
final ButtonStyle? cancelButtonStyle;
|
||||||
/// If [hourMinuteTextColor] is a [MaterialStateColor], then the effective
|
|
||||||
/// text color can depend on the [MaterialState.selected] state, i.e. if the
|
|
||||||
/// text is selected or not.
|
|
||||||
///
|
|
||||||
/// By default the overall theme's [ColorScheme.primary] color is used when
|
|
||||||
/// the text is selected and [ColorScheme.onSurface] when it's not selected.
|
|
||||||
final Color? hourMinuteTextColor;
|
|
||||||
|
|
||||||
/// The background color of the hour and minutes header segments.
|
/// The style of the conform (OK) button of a [TimePickerDialog].
|
||||||
///
|
final ButtonStyle? confirmButtonStyle;
|
||||||
/// If [hourMinuteColor] is a [MaterialStateColor], then the effective
|
|
||||||
/// background color can depend on the [MaterialState.selected] state, i.e.
|
|
||||||
/// if the segment is selected or not.
|
|
||||||
///
|
|
||||||
/// By default, if the segment is selected, the overall theme's
|
|
||||||
/// `ColorScheme.primary.withOpacity(0.12)` is used when the overall theme's
|
|
||||||
/// brightness is [Brightness.light] and
|
|
||||||
/// `ColorScheme.primary.withOpacity(0.24)` is used when the overall theme's
|
|
||||||
/// brightness is [Brightness.dark].
|
|
||||||
/// If the segment is not selected, the overall theme's
|
|
||||||
/// `ColorScheme.onSurface.withOpacity(0.12)` is used.
|
|
||||||
final Color? hourMinuteColor;
|
|
||||||
|
|
||||||
/// The color of the day period text that represents AM/PM.
|
/// The color and weight of the day period's outline.
|
||||||
///
|
///
|
||||||
/// If [dayPeriodTextColor] is a [MaterialStateColor], then the effective
|
/// If this is null, the time picker defaults to:
|
||||||
/// 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
|
/// ```dart
|
||||||
/// the text is selected and `ColorScheme.onSurface.withOpacity(0.60)` when
|
/// BorderSide(
|
||||||
/// it's not selected.
|
/// color: Color.alphaBlend(
|
||||||
final Color? dayPeriodTextColor;
|
/// Theme.of(context).colorScheme.onBackground.withOpacity(0.38),
|
||||||
|
/// Theme.of(context).colorScheme.surface,
|
||||||
|
/// ),
|
||||||
|
/// ),
|
||||||
|
/// ```
|
||||||
|
final BorderSide? dayPeriodBorderSide;
|
||||||
|
|
||||||
/// The background color of the AM/PM toggle.
|
/// The background color of the AM/PM toggle.
|
||||||
///
|
///
|
||||||
@ -111,69 +104,6 @@ class TimePickerThemeData with Diagnosticable {
|
|||||||
/// [Dialog]'s color to be used.
|
/// [Dialog]'s color to be used.
|
||||||
final Color? dayPeriodColor;
|
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.
|
/// The shape of the day period that the time picker uses.
|
||||||
///
|
///
|
||||||
/// If this is null, the time picker defaults to:
|
/// If this is null, the time picker defaults to:
|
||||||
@ -186,64 +116,180 @@ class TimePickerThemeData with Diagnosticable {
|
|||||||
/// ```
|
/// ```
|
||||||
final OutlinedBorder? dayPeriodShape;
|
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:
|
/// If this is null, the time picker defaults to:
|
||||||
///
|
///
|
||||||
|
///
|
||||||
/// ```dart
|
/// ```dart
|
||||||
/// BorderSide(
|
/// Theme.of(context).colorScheme.onSurface.withOpacity(
|
||||||
/// color: Color.alphaBlend(
|
/// Theme.of(context).colorScheme.brightness == Brightness.dark ? 1.0 : 0.6,
|
||||||
/// Theme.of(context).colorScheme.onBackground.withOpacity(0.38),
|
/// )
|
||||||
/// Theme.of(context).colorScheme.surface,
|
|
||||||
/// ),
|
|
||||||
/// ),
|
|
||||||
/// ```
|
/// ```
|
||||||
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.
|
/// The input decoration theme for the [TextField]s in the time picker.
|
||||||
///
|
///
|
||||||
/// If this is null, the time picker provides its own defaults.
|
/// If this is null, the time picker provides its own defaults.
|
||||||
final InputDecorationTheme? inputDecorationTheme;
|
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
|
/// Creates a copy of this object with the given fields replaced with the
|
||||||
/// new values.
|
/// new values.
|
||||||
TimePickerThemeData copyWith({
|
TimePickerThemeData copyWith({
|
||||||
Color? backgroundColor,
|
Color? backgroundColor,
|
||||||
Color? hourMinuteTextColor,
|
ButtonStyle? cancelButtonStyle,
|
||||||
Color? hourMinuteColor,
|
ButtonStyle? confirmButtonStyle,
|
||||||
Color? dayPeriodTextColor,
|
ButtonStyle? dayPeriodButtonStyle,
|
||||||
Color? dayPeriodColor,
|
|
||||||
Color? dialHandColor,
|
|
||||||
Color? dialBackgroundColor,
|
|
||||||
Color? dialTextColor,
|
|
||||||
Color? entryModeIconColor,
|
|
||||||
TextStyle? hourMinuteTextStyle,
|
|
||||||
TextStyle? dayPeriodTextStyle,
|
|
||||||
TextStyle? helpTextStyle,
|
|
||||||
ShapeBorder? shape,
|
|
||||||
ShapeBorder? hourMinuteShape,
|
|
||||||
OutlinedBorder? dayPeriodShape,
|
|
||||||
BorderSide? dayPeriodBorderSide,
|
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,
|
InputDecorationTheme? inputDecorationTheme,
|
||||||
|
EdgeInsetsGeometry? padding,
|
||||||
|
ShapeBorder? shape,
|
||||||
}) {
|
}) {
|
||||||
return TimePickerThemeData(
|
return TimePickerThemeData(
|
||||||
backgroundColor: backgroundColor ?? this.backgroundColor,
|
backgroundColor: backgroundColor ?? this.backgroundColor,
|
||||||
hourMinuteTextColor: hourMinuteTextColor ?? this.hourMinuteTextColor,
|
cancelButtonStyle: cancelButtonStyle ?? this.cancelButtonStyle,
|
||||||
hourMinuteColor: hourMinuteColor ?? this.hourMinuteColor,
|
confirmButtonStyle: confirmButtonStyle ?? this.confirmButtonStyle,
|
||||||
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,
|
|
||||||
dayPeriodBorderSide: dayPeriodBorderSide ?? this.dayPeriodBorderSide,
|
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,
|
inputDecorationTheme: inputDecorationTheme ?? this.inputDecorationTheme,
|
||||||
|
padding: padding ?? this.padding,
|
||||||
|
shape: shape ?? this.shape,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -268,45 +314,55 @@ class TimePickerThemeData with Diagnosticable {
|
|||||||
}
|
}
|
||||||
return TimePickerThemeData(
|
return TimePickerThemeData(
|
||||||
backgroundColor: Color.lerp(a?.backgroundColor, b?.backgroundColor, t),
|
backgroundColor: Color.lerp(a?.backgroundColor, b?.backgroundColor, t),
|
||||||
hourMinuteTextColor: Color.lerp(a?.hourMinuteTextColor, b?.hourMinuteTextColor, t),
|
cancelButtonStyle: ButtonStyle.lerp(a?.cancelButtonStyle, b?.cancelButtonStyle, t),
|
||||||
hourMinuteColor: Color.lerp(a?.hourMinuteColor, b?.hourMinuteColor, t),
|
confirmButtonStyle: ButtonStyle.lerp(a?.confirmButtonStyle, b?.confirmButtonStyle, 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?,
|
|
||||||
dayPeriodBorderSide: lerpedBorderSide,
|
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,
|
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
|
@override
|
||||||
int get hashCode => Object.hash(
|
int get hashCode => Object.hashAll(<Object?>[
|
||||||
backgroundColor,
|
backgroundColor,
|
||||||
hourMinuteTextColor,
|
cancelButtonStyle,
|
||||||
hourMinuteColor,
|
confirmButtonStyle,
|
||||||
dayPeriodTextColor,
|
|
||||||
dayPeriodColor,
|
|
||||||
dialHandColor,
|
|
||||||
dialBackgroundColor,
|
|
||||||
dialTextColor,
|
|
||||||
entryModeIconColor,
|
|
||||||
hourMinuteTextStyle,
|
|
||||||
dayPeriodTextStyle,
|
|
||||||
helpTextStyle,
|
|
||||||
shape,
|
|
||||||
hourMinuteShape,
|
|
||||||
dayPeriodShape,
|
|
||||||
dayPeriodBorderSide,
|
dayPeriodBorderSide,
|
||||||
|
dayPeriodColor,
|
||||||
|
dayPeriodShape,
|
||||||
|
dayPeriodTextColor,
|
||||||
|
dayPeriodTextStyle,
|
||||||
|
dialBackgroundColor,
|
||||||
|
dialHandColor,
|
||||||
|
dialTextColor,
|
||||||
|
dialTextStyle,
|
||||||
|
elevation,
|
||||||
|
entryModeIconColor,
|
||||||
|
helpTextStyle,
|
||||||
|
hourMinuteColor,
|
||||||
|
hourMinuteShape,
|
||||||
|
hourMinuteTextColor,
|
||||||
|
hourMinuteTextStyle,
|
||||||
inputDecorationTheme,
|
inputDecorationTheme,
|
||||||
);
|
padding,
|
||||||
|
shape,
|
||||||
|
]);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
@ -318,44 +374,54 @@ class TimePickerThemeData with Diagnosticable {
|
|||||||
}
|
}
|
||||||
return other is TimePickerThemeData
|
return other is TimePickerThemeData
|
||||||
&& other.backgroundColor == backgroundColor
|
&& other.backgroundColor == backgroundColor
|
||||||
&& other.hourMinuteTextColor == hourMinuteTextColor
|
&& other.cancelButtonStyle == cancelButtonStyle
|
||||||
&& other.hourMinuteColor == hourMinuteColor
|
&& other.confirmButtonStyle == confirmButtonStyle
|
||||||
&& 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.dayPeriodBorderSide == dayPeriodBorderSide
|
&& 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
|
@override
|
||||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||||
super.debugFillProperties(properties);
|
super.debugFillProperties(properties);
|
||||||
properties.add(ColorProperty('backgroundColor', backgroundColor, defaultValue: null));
|
properties.add(ColorProperty('backgroundColor', backgroundColor, defaultValue: null));
|
||||||
properties.add(ColorProperty('hourMinuteTextColor', hourMinuteTextColor, defaultValue: null));
|
properties.add(DiagnosticsProperty<ButtonStyle>('cancelButtonStyle', cancelButtonStyle, defaultValue: null));
|
||||||
properties.add(ColorProperty('hourMinuteColor', hourMinuteColor, defaultValue: null));
|
properties.add(DiagnosticsProperty<ButtonStyle>('confirmButtonStyle', confirmButtonStyle, 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<BorderSide>('dayPeriodBorderSide', dayPeriodBorderSide, 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<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>[
|
expect(description, <String>[
|
||||||
'backgroundColor: Color(0xffffffff)',
|
'backgroundColor: Color(0xffffffff)',
|
||||||
'hourMinuteTextColor: Color(0xffffffff)',
|
'dayPeriodBorderSide: BorderSide',
|
||||||
'hourMinuteColor: Color(0xffffffff)',
|
|
||||||
'dayPeriodTextColor: Color(0xffffffff)',
|
|
||||||
'dayPeriodColor: Color(0xffffffff)',
|
'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)',
|
'dialBackgroundColor: Color(0xffffffff)',
|
||||||
|
'dialHandColor: Color(0xffffffff)',
|
||||||
'dialTextColor: Color(0xffffffff)',
|
'dialTextColor: Color(0xffffffff)',
|
||||||
'entryModeIconColor: Color(0xffffffff)',
|
'entryModeIconColor: Color(0xffffffff)',
|
||||||
'hourMinuteTextStyle: TextStyle(<all styles inherited>)',
|
|
||||||
'dayPeriodTextStyle: TextStyle(<all styles inherited>)',
|
|
||||||
'helpTextStyle: 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)',
|
'hourMinuteShape: RoundedRectangleBorder(BorderSide(width: 0.0, style: none), BorderRadius.zero)',
|
||||||
'dayPeriodShape: RoundedRectangleBorder(BorderSide(width: 0.0, style: none), BorderRadius.zero)',
|
'hourMinuteTextColor: Color(0xffffffff)',
|
||||||
'dayPeriodBorderSide: BorderSide',
|
'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))));
|
expect(dialogMaterial.shape, const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4.0))));
|
||||||
|
|
||||||
final RenderBox dial = tester.firstRenderObject<RenderBox>(find.byType(CustomPaint));
|
final RenderBox dial = tester.firstRenderObject<RenderBox>(find.byType(CustomPaint));
|
||||||
|
debugPrint('Color: ${defaultTheme.colorScheme.onSurface.withOpacity(0.08)}');
|
||||||
expect(
|
expect(
|
||||||
dial,
|
dial,
|
||||||
paints
|
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.
|
..circle(color: Color(defaultTheme.colorScheme.primary.value)), // Dial hand color.
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -162,10 +163,10 @@ void main() {
|
|||||||
.copyWith(color: defaultTheme.colorScheme.onSurface),
|
.copyWith(color: defaultTheme.colorScheme.onSurface),
|
||||||
);
|
);
|
||||||
// ignore: avoid_dynamic_calls
|
// ignore: avoid_dynamic_calls
|
||||||
final List<dynamic> secondaryLabels = dialPainter.secondaryLabels as List<dynamic>;
|
final List<dynamic> selectedLabels = dialPainter.selectedLabels as List<dynamic>;
|
||||||
expect(
|
expect(
|
||||||
// ignore: avoid_dynamic_calls
|
// ignore: avoid_dynamic_calls
|
||||||
secondaryLabels.first.painter.text.style,
|
selectedLabels.first.painter.text.style,
|
||||||
Typography.material2014().englishLike.bodyLarge!
|
Typography.material2014().englishLike.bodyLarge!
|
||||||
.merge(Typography.material2014().white.bodyLarge)
|
.merge(Typography.material2014().white.bodyLarge)
|
||||||
.copyWith(color: defaultTheme.colorScheme.onPrimary),
|
.copyWith(color: defaultTheme.colorScheme.onPrimary),
|
||||||
@ -186,7 +187,7 @@ void main() {
|
|||||||
expect(pmMaterial.color, Colors.transparent);
|
expect(pmMaterial.color, Colors.transparent);
|
||||||
|
|
||||||
final Color expectedBorderColor = Color.alphaBlend(
|
final Color expectedBorderColor = Color.alphaBlend(
|
||||||
defaultTheme.colorScheme.onBackground.withOpacity(0.38),
|
defaultTheme.colorScheme.onSurface.withOpacity(0.38),
|
||||||
defaultTheme.colorScheme.surface,
|
defaultTheme.colorScheme.surface,
|
||||||
);
|
);
|
||||||
final Material dayPeriodMaterial = _dayPeriodMaterial(tester);
|
final Material dayPeriodMaterial = _dayPeriodMaterial(tester);
|
||||||
@ -220,7 +221,7 @@ void main() {
|
|||||||
|
|
||||||
final InputDecoration hourDecoration = _textField(tester, '7').decoration!;
|
final InputDecoration hourDecoration = _textField(tester, '7').decoration!;
|
||||||
expect(hourDecoration.filled, true);
|
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.enabledBorder, const OutlineInputBorder(borderSide: BorderSide(color: Colors.transparent)));
|
||||||
expect(hourDecoration.errorBorder, OutlineInputBorder(borderSide: BorderSide(color: defaultTheme.colorScheme.error, width: 2)));
|
expect(hourDecoration.errorBorder, OutlineInputBorder(borderSide: BorderSide(color: defaultTheme.colorScheme.error, width: 2)));
|
||||||
expect(hourDecoration.focusedBorder, OutlineInputBorder(borderSide: BorderSide(color: defaultTheme.colorScheme.primary, width: 2)));
|
expect(hourDecoration.focusedBorder, OutlineInputBorder(borderSide: BorderSide(color: defaultTheme.colorScheme.primary, width: 2)));
|
||||||
@ -307,10 +308,10 @@ void main() {
|
|||||||
.copyWith(color: _unselectedColor),
|
.copyWith(color: _unselectedColor),
|
||||||
);
|
);
|
||||||
// ignore: avoid_dynamic_calls
|
// ignore: avoid_dynamic_calls
|
||||||
final List<dynamic> secondaryLabels = dialPainter.secondaryLabels as List<dynamic>;
|
final List<dynamic> selectedLabels = dialPainter.selectedLabels as List<dynamic>;
|
||||||
expect(
|
expect(
|
||||||
// ignore: avoid_dynamic_calls
|
// ignore: avoid_dynamic_calls
|
||||||
secondaryLabels.first.painter.text.style,
|
selectedLabels.first.painter.text.style,
|
||||||
Typography.material2014().englishLike.bodyLarge!
|
Typography.material2014().englishLike.bodyLarge!
|
||||||
.merge(Typography.material2014().white.bodyLarge)
|
.merge(Typography.material2014().white.bodyLarge)
|
||||||
.copyWith(color: _selectedColor),
|
.copyWith(color: _selectedColor),
|
||||||
|
@ -6,62 +6,6 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
import 'package:flutter_test/flutter_test.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() {
|
void main() {
|
||||||
testWidgets('can localize the header in all known formats - portrait', (WidgetTester tester) async {
|
testWidgets('can localize the header in all known formats - portrait', (WidgetTester tester) async {
|
||||||
// Ensure picker is displayed in portrait mode.
|
// 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 {
|
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(
|
final Finder stringFragmentTextFinder = find.descendant(
|
||||||
of: find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_StringFragment'),
|
of: find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_StringFragment'),
|
||||||
matching: find.byType(Text),
|
matching: find.byType(Text),
|
||||||
).first;
|
).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
|
// 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>[
|
final List<Locale> locales = <Locale>[
|
||||||
@ -276,6 +220,7 @@ void main() {
|
|||||||
expect(dayPeriodControlFinder, findsNothing);
|
expect(dayPeriodControlFinder, findsNothing);
|
||||||
}
|
}
|
||||||
await finishPicker(tester);
|
await finishPicker(tester);
|
||||||
|
expect(tester.takeException(), isNot(throwsFlutterError));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -353,10 +298,10 @@ void main() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// ignore: avoid_dynamic_calls
|
// ignore: avoid_dynamic_calls
|
||||||
final List<dynamic> secondaryLabels = dialPainter.secondaryLabels as List<dynamic>;
|
final List<dynamic> selectedLabels = dialPainter.selectedLabels as List<dynamic>;
|
||||||
expect(
|
expect(
|
||||||
// ignore: avoid_dynamic_calls
|
// 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,
|
labels12To11,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -375,11 +320,72 @@ void main() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// ignore: avoid_dynamic_calls
|
// ignore: avoid_dynamic_calls
|
||||||
final List<dynamic> secondaryLabels = dialPainter.secondaryLabels as List<dynamic>;
|
final List<dynamic> selectedLabels = dialPainter.selectedLabels as List<dynamic>;
|
||||||
expect(
|
expect(
|
||||||
// ignore: avoid_dynamic_calls
|
// 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,
|
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 New Issue
Block a user