Add timeSelectorSeparatorColor and timeSelectorSeparatorTextStyle for Material 3 Time Picker (#143739)

fixes [`Time selector separator` in TimePicker is not centered vertically](https://github.com/flutter/flutter/issues/143691)

Separator currently `hourMinuteTextStyle` to style itself.

This introduces `timeSelectorSeparatorColor` and `timeSelectorSeparatorTextStyle` from Material 3 specs to correctly style  the separator. This also adds ability to change separator color without changing `hourMinuteTextColor`.

### Specs for the time selector separator
https://m3.material.io/components/time-pickers/specs
![image](https://github.com/flutter/flutter/assets/48603081/0c84f649-545d-441b-adbf-2b9ec872b14c)

### Code sample

<details>
<summary>expand to view the code sample</summary> 

```dart
import 'package:flutter/material.dart';

void main() {
  runApp(const App());
}

class App extends StatelessWidget {
  const App({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        // timePickerTheme: TimePickerThemeData(
        //   hourMinuteTextColor: Colors.amber,
        // )
      ),
      home: Scaffold(
        body: Center(
          child: Builder(builder: (context) {
            return ElevatedButton(
              onPressed: () async {
                await showTimePicker(
                  context: context,
                  initialTime: TimeOfDay.now(),
                );
              },
              child: const Text('Pick Time'),
            );
          }),
        ),
      ),
    );
  }
}

```

</details>

| Before | After |
| --------------- | --------------- |
| <img src="https://github.com/flutter/flutter/assets/48603081/20beeba4-5cc2-49ee-bba8-1c552c0d1e44" /> | <img src="https://github.com/flutter/flutter/assets/48603081/24927187-aff7-4191-930c-bceab6a4b4c2" /> |
This commit is contained in:
Taha Tesser 2024-02-21 10:10:01 +02:00 committed by GitHub
parent f923375654
commit 95cdebedae
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 170 additions and 38 deletions

View file

@ -333,6 +333,20 @@ class _${blockName}DefaultsM3 extends _TimePickerDefaults {
ShapeBorder get shape {
return ${shape("$tokenGroup.container")};
}
@override
MaterialStateProperty<Color?>? get timeSelectorSeparatorColor {
// TODO(tahatesser): Update this when tokens are available.
// This is taken from https://m3.material.io/components/time-pickers/specs.
return MaterialStatePropertyAll<Color>(_colors.onSurface);
}
@override
MaterialStateProperty<TextStyle?>? get timeSelectorSeparatorTextStyle {
// TODO(tahatesser): Update this when tokens are available.
// This is taken from https://m3.material.io/components/time-pickers/specs.
return MaterialStatePropertyAll<TextStyle?>(_textTheme.displayLarge);
}
}
''';
}

View file

@ -242,7 +242,7 @@ class _TimePickerHeader extends StatelessWidget {
textDirection: TextDirection.ltr,
children: <Widget>[
const Expanded(child: _HourControl()),
_StringFragment(timeOfDayFormat: timeOfDayFormat),
_TimeSelectorSeparator(timeOfDayFormat: timeOfDayFormat),
const Expanded(child: _MinuteControl()),
],
),
@ -278,7 +278,7 @@ class _TimePickerHeader extends StatelessWidget {
textDirection: TextDirection.ltr,
children: <Widget>[
const Expanded(child: _HourControl()),
_StringFragment(timeOfDayFormat: timeOfDayFormat),
_TimeSelectorSeparator(timeOfDayFormat: timeOfDayFormat),
const Expanded(child: _MinuteControl()),
],
),
@ -428,12 +428,12 @@ class _HourControl extends StatelessWidget {
/// A passive fragment showing a string value.
///
/// Used to display the appropriate separator between the input fields.
class _StringFragment extends StatelessWidget {
const _StringFragment({ required this.timeOfDayFormat });
class _TimeSelectorSeparator extends StatelessWidget {
const _TimeSelectorSeparator({ required this.timeOfDayFormat });
final TimeOfDayFormat timeOfDayFormat;
String _stringFragmentValue(TimeOfDayFormat timeOfDayFormat) {
String _timeSelectorSeparatorValue(TimeOfDayFormat timeOfDayFormat) {
switch (timeOfDayFormat) {
case TimeOfDayFormat.h_colon_mm_space_a:
case TimeOfDayFormat.a_space_h_colon_mm:
@ -455,11 +455,17 @@ class _StringFragment extends StatelessWidget {
final Set<MaterialState> states = <MaterialState>{};
final Color effectiveTextColor = MaterialStateProperty.resolveAs<Color>(
timePickerTheme.hourMinuteTextColor ?? defaultTheme.hourMinuteTextColor,
timePickerTheme.timeSelectorSeparatorColor?.resolve(states)
?? timePickerTheme.hourMinuteTextColor
?? defaultTheme.timeSelectorSeparatorColor?.resolve(states)
?? defaultTheme.hourMinuteTextColor,
states,
);
final TextStyle effectiveStyle = MaterialStateProperty.resolveAs<TextStyle>(
timePickerTheme.hourMinuteTextStyle ?? defaultTheme.hourMinuteTextStyle,
timePickerTheme.timeSelectorSeparatorTextStyle?.resolve(states)
?? timePickerTheme.hourMinuteTextStyle
?? defaultTheme.timeSelectorSeparatorTextStyle?.resolve(states)
?? defaultTheme.hourMinuteTextStyle,
states,
).copyWith(color: effectiveTextColor);
@ -478,7 +484,7 @@ class _StringFragment extends StatelessWidget {
width: timeOfDayFormat == TimeOfDayFormat.frenchCanadian ? 36 : 24,
height: height,
child: Text(
_stringFragmentValue(timeOfDayFormat),
_timeSelectorSeparatorValue(timeOfDayFormat),
style: effectiveStyle,
textScaler: TextScaler.noScaling,
textAlign: TextAlign.center,
@ -1801,7 +1807,7 @@ class _TimePickerInputState extends State<_TimePickerInput> with RestorationMixi
],
),
),
_StringFragment(timeOfDayFormat: timeOfDayFormat),
_TimeSelectorSeparator(timeOfDayFormat: timeOfDayFormat),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@ -3655,6 +3661,20 @@ class _TimePickerDefaultsM3 extends _TimePickerDefaults {
ShapeBorder get shape {
return const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(28.0)));
}
@override
MaterialStateProperty<Color?>? get timeSelectorSeparatorColor {
// TODO(tahatesser): Update this when tokens are available.
// This is taken from https://m3.material.io/components/time-pickers/specs.
return MaterialStatePropertyAll<Color>(_colors.onSurface);
}
@override
MaterialStateProperty<TextStyle?>? get timeSelectorSeparatorTextStyle {
// TODO(tahatesser): Update this when tokens are available.
// This is taken from https://m3.material.io/components/time-pickers/specs.
return MaterialStatePropertyAll<TextStyle?>(_textTheme.displayLarge);
}
}
// END GENERATED TOKEN PROPERTIES - TimePicker

View file

@ -62,6 +62,8 @@ class TimePickerThemeData with Diagnosticable {
this.inputDecorationTheme,
this.padding,
this.shape,
this.timeSelectorSeparatorColor,
this.timeSelectorSeparatorTextStyle,
}) : _dayPeriodColor = dayPeriodColor;
/// The background color of a time picker.
@ -261,6 +263,25 @@ class TimePickerThemeData with Diagnosticable {
/// `RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4.0)))`.
final ShapeBorder? shape;
/// The color of the time selector seperator between the hour and minute controls.
///
/// if this is null, the time picker defaults to the overall theme's
/// [ColorScheme.onSurface].
///
/// If this is null and [ThemeData.useMaterial3] is false, then defaults to the value of
/// [hourMinuteTextColor].
final MaterialStateProperty<Color?>? timeSelectorSeparatorColor;
/// Used to configure the text style for the time selector seperator between the hour
/// and minute controls.
///
/// If this is null, the time picker defaults to the overall theme's
/// [TextTheme.displayLarge].
///
/// If this is null and [ThemeData.useMaterial3] is false, then defaults to the value of
/// [hourMinuteTextStyle].
final MaterialStateProperty<TextStyle?>? timeSelectorSeparatorTextStyle;
/// Creates a copy of this object with the given fields replaced with the
/// new values.
TimePickerThemeData copyWith({
@ -287,6 +308,8 @@ class TimePickerThemeData with Diagnosticable {
InputDecorationTheme? inputDecorationTheme,
EdgeInsetsGeometry? padding,
ShapeBorder? shape,
MaterialStateProperty<Color?>? timeSelectorSeparatorColor,
MaterialStateProperty<TextStyle?>? timeSelectorSeparatorTextStyle,
}) {
return TimePickerThemeData(
backgroundColor: backgroundColor ?? this.backgroundColor,
@ -311,6 +334,8 @@ class TimePickerThemeData with Diagnosticable {
inputDecorationTheme: inputDecorationTheme ?? this.inputDecorationTheme,
padding: padding ?? this.padding,
shape: shape ?? this.shape,
timeSelectorSeparatorColor: timeSelectorSeparatorColor ?? this.timeSelectorSeparatorColor,
timeSelectorSeparatorTextStyle: timeSelectorSeparatorTextStyle ?? this.timeSelectorSeparatorTextStyle,
);
}
@ -355,6 +380,8 @@ class TimePickerThemeData with Diagnosticable {
inputDecorationTheme: t < 0.5 ? a?.inputDecorationTheme : b?.inputDecorationTheme,
padding: EdgeInsetsGeometry.lerp(a?.padding, b?.padding, t),
shape: ShapeBorder.lerp(a?.shape, b?.shape, t),
timeSelectorSeparatorColor: MaterialStateProperty.lerp<Color?>(a?.timeSelectorSeparatorColor, b?.timeSelectorSeparatorColor, t, Color.lerp),
timeSelectorSeparatorTextStyle: MaterialStateProperty.lerp<TextStyle?>(a?.timeSelectorSeparatorTextStyle, b?.timeSelectorSeparatorTextStyle, t, TextStyle.lerp),
);
}
@ -382,6 +409,8 @@ class TimePickerThemeData with Diagnosticable {
inputDecorationTheme,
padding,
shape,
timeSelectorSeparatorColor,
timeSelectorSeparatorTextStyle,
]);
@override
@ -414,7 +443,9 @@ class TimePickerThemeData with Diagnosticable {
&& other.hourMinuteTextStyle == hourMinuteTextStyle
&& other.inputDecorationTheme == inputDecorationTheme
&& other.padding == padding
&& other.shape == shape;
&& other.shape == shape
&& other.timeSelectorSeparatorColor == timeSelectorSeparatorColor
&& other.timeSelectorSeparatorTextStyle == timeSelectorSeparatorTextStyle;
}
@override
@ -442,6 +473,8 @@ class TimePickerThemeData with Diagnosticable {
properties.add(DiagnosticsProperty<InputDecorationTheme>('inputDecorationTheme', inputDecorationTheme, defaultValue: null));
properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding, defaultValue: null));
properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('timeSelectorSeparatorColor', timeSelectorSeparatorColor, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialStateProperty<TextStyle?>>('timeSelectorSeparatorTextStyle', timeSelectorSeparatorTextStyle, defaultValue: null));
}
}

View file

@ -1831,7 +1831,7 @@ void main() {
final double minuteFieldTop =
tester.getTopLeft(find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_MinuteTextField')).dy;
final double separatorTop =
tester.getTopLeft(find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_StringFragment')).dy;
tester.getTopLeft(find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_TimeSelectorSeparator')).dy;
expect(hourFieldTop, separatorTop);
expect(minuteFieldTop, separatorTop);
});
@ -1965,6 +1965,32 @@ void main() {
});
});
}
testWidgets('Material3 - Time selector separator default text style', (WidgetTester tester) async {
final ThemeData theme = ThemeData();
await startPicker(
tester,
(TimeOfDay? value) { },
theme: theme,
);
final RenderParagraph paragraph = tester.renderObject(find.text(':'));
expect(paragraph.text.style!.color, theme.colorScheme.onSurface);
expect(paragraph.text.style!.fontSize, 57.0);
});
testWidgets('Material2 - Time selector separator default text style', (WidgetTester tester) async {
final ThemeData theme = ThemeData(useMaterial3: false);
await startPicker(
tester,
(TimeOfDay? value) { },
theme: theme,
);
final RenderParagraph paragraph = tester.renderObject(find.text(':'));
expect(paragraph.text.style!.color, theme.colorScheme.onSurface);
expect(paragraph.text.style!.fontSize, 56.0);
});
}
final Finder findDialPaint = find.descendant(
@ -2175,10 +2201,11 @@ Future<Offset?> startPicker(
ValueChanged<TimeOfDay?> onChanged, {
TimePickerEntryMode entryMode = TimePickerEntryMode.dial,
String? restorationId,
required MaterialType materialType,
ThemeData? theme,
MaterialType? materialType,
}) async {
await tester.pumpWidget(MaterialApp(
theme: ThemeData(useMaterial3: materialType == MaterialType.material3),
theme: theme ?? ThemeData(useMaterial3: materialType == MaterialType.material3),
restorationScopeId: 'app',
locale: const Locale('en', 'US'),
home: _TimePickerLauncher(

View file

@ -42,6 +42,8 @@ void main() {
expect(timePickerTheme.entryModeIconColor, null);
expect(timePickerTheme.padding, null);
expect(timePickerTheme.shape, null);
expect(timePickerTheme.timeSelectorSeparatorColor, null);
expect(timePickerTheme.timeSelectorSeparatorTextStyle, null);
});
testWidgets('Default TimePickerThemeData debugFillProperties', (WidgetTester tester) async {
@ -89,6 +91,8 @@ void main() {
shape: RoundedRectangleBorder(
side: BorderSide(color: Color(0xfffffff3)),
),
timeSelectorSeparatorColor: MaterialStatePropertyAll<Color>(Color(0xfffffff4)),
timeSelectorSeparatorTextStyle: MaterialStatePropertyAll<TextStyle>(TextStyle(color: Color(0xfffffff5))),
).debugFillProperties(builder);
final List<String> description = builder.properties
@ -118,7 +122,9 @@ void main() {
'hourMinuteTextStyle: TextStyle(inherit: true, color: Color(0xfffffff1))',
'inputDecorationTheme: InputDecorationTheme#ff861(labelStyle: TextStyle(inherit: true, color: Color(0xfffffff2)))',
'padding: EdgeInsets.all(1.0)',
'shape: RoundedRectangleBorder(BorderSide(color: Color(0xfffffff3)), BorderRadius.zero)'
'shape: RoundedRectangleBorder(BorderSide(color: Color(0xfffffff3)), BorderRadius.zero)',
'timeSelectorSeparatorColor: MaterialStatePropertyAll(Color(0xfffffff4))',
'timeSelectorSeparatorTextStyle: MaterialStatePropertyAll(TextStyle(inherit: true, color: Color(0xfffffff5)))'
]));
});
@ -798,6 +804,38 @@ void main() {
final Material pmMaterial = _textMaterial(tester, 'PM');
expect(pmMaterial.color, Colors.blue);
});
testWidgets('Time selector separator color uses the timeSelectorSeparatorColor value', (WidgetTester tester) async {
final TimePickerThemeData timePickerTheme = _timePickerTheme().copyWith(
timeSelectorSeparatorColor: const MaterialStatePropertyAll<Color>(Color(0xff00ff00))
);
final ThemeData theme = ThemeData(timePickerTheme: timePickerTheme);
await tester.pumpWidget(_TimePickerLauncher(themeData: theme, entryMode: TimePickerEntryMode.input));
await tester.tap(find.text('X'));
await tester.pumpAndSettle(const Duration(seconds: 1));
final RenderParagraph paragraph = tester.renderObject(find.text(':'));
expect(paragraph.text.style!.color, const Color(0xff00ff00));
});
testWidgets('Time selector separator text style uses the timeSelectorSeparatorTextStyle value', (WidgetTester tester) async {
final TimePickerThemeData timePickerTheme = _timePickerTheme().copyWith(
timeSelectorSeparatorTextStyle: const MaterialStatePropertyAll<TextStyle>(
TextStyle(
fontSize: 35.0,
fontStyle: FontStyle.italic,
),
),
);
final ThemeData theme = ThemeData(timePickerTheme: timePickerTheme);
await tester.pumpWidget(_TimePickerLauncher(themeData: theme, entryMode: TimePickerEntryMode.input));
await tester.tap(find.text('X'));
await tester.pumpAndSettle(const Duration(seconds: 1));
final RenderParagraph paragraph = tester.renderObject(find.text(':'));
expect(paragraph.text.style!.fontSize, 35.0);
expect(paragraph.text.style!.fontStyle, FontStyle.italic);
});
}
final Color _selectedColor = Colors.green[100]!;

View file

@ -13,8 +13,8 @@ void main() {
tester.view.devicePixelRatio = 1;
addTearDown(tester.view.reset);
final Finder stringFragmentTextFinder = find.descendant(
of: find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_StringFragment'),
final Finder timeSelectorSeparatorFinder = find.descendant(
of: find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_TimeSelectorSeparator'),
matching: find.byType(Text),
).first;
final Finder hourControlFinder = find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_HourControl');
@ -33,10 +33,10 @@ void main() {
for (final Locale locale in locales) {
final Offset center = await startPicker(tester, (TimeOfDay? time) { }, locale: locale, useMaterial3: false);
final Text stringFragmentText = tester.widget(stringFragmentTextFinder);
final Text stringFragmentText = tester.widget(timeSelectorSeparatorFinder);
final double hourLeftOffset = tester.getTopLeft(hourControlFinder).dx;
final double minuteLeftOffset = tester.getTopLeft(minuteControlFinder).dx;
final double stringFragmentLeftOffset = tester.getTopLeft(stringFragmentTextFinder).dx;
final double stringFragmentLeftOffset = tester.getTopLeft(timeSelectorSeparatorFinder).dx;
if (locale == const Locale('en', 'US')) {
final double dayPeriodLeftOffset = tester.getTopLeft(dayPeriodControlFinder).dx;
@ -83,8 +83,8 @@ void main() {
tester.view.devicePixelRatio = 1;
addTearDown(tester.view.reset);
final Finder stringFragmentTextFinder = find.descendant(
of: find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_StringFragment'),
final Finder timeSelectorSeparatorFinder = find.descendant(
of: find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_TimeSelectorSeparator'),
matching: find.byType(Text),
).first;
final Finder hourControlFinder = find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_HourControl');
@ -103,10 +103,10 @@ void main() {
for (final Locale locale in locales) {
final Offset center = await startPicker(tester, (TimeOfDay? time) { }, locale: locale, useMaterial3: true);
final Text stringFragmentText = tester.widget(stringFragmentTextFinder);
final Text stringFragmentText = tester.widget(timeSelectorSeparatorFinder);
final double hourLeftOffset = tester.getTopLeft(hourControlFinder).dx;
final double minuteLeftOffset = tester.getTopLeft(minuteControlFinder).dx;
final double stringFragmentLeftOffset = tester.getTopLeft(stringFragmentTextFinder).dx;
final double stringFragmentLeftOffset = tester.getTopLeft(timeSelectorSeparatorFinder).dx;
if (locale == const Locale('en', 'US')) {
final double dayPeriodLeftOffset = tester.getTopLeft(dayPeriodControlFinder).dx;
@ -153,8 +153,8 @@ void main() {
tester.view.devicePixelRatio = 1;
addTearDown(tester.view.reset);
final Finder stringFragmentTextFinder = find.descendant(
of: find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_StringFragment'),
final Finder timeSelectorSeparatorFinder = find.descendant(
of: find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_TimeSelectorSeparator'),
matching: find.byType(Text),
).first;
final Finder hourControlFinder = find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_HourControl');
@ -173,11 +173,11 @@ void main() {
for (final Locale locale in locales) {
final Offset center = await startPicker(tester, (TimeOfDay? time) { }, locale: locale, useMaterial3: false);
final Text stringFragmentText = tester.widget(stringFragmentTextFinder);
final Text stringFragmentText = tester.widget(timeSelectorSeparatorFinder);
final double hourLeftOffset = tester.getTopLeft(hourControlFinder).dx;
final double hourTopOffset = tester.getTopLeft(hourControlFinder).dy;
final double minuteLeftOffset = tester.getTopLeft(minuteControlFinder).dx;
final double stringFragmentLeftOffset = tester.getTopLeft(stringFragmentTextFinder).dx;
final double stringFragmentLeftOffset = tester.getTopLeft(timeSelectorSeparatorFinder).dx;
if (locale == const Locale('en', 'US')) {
final double dayPeriodLeftOffset = tester.getTopLeft(dayPeriodControlFinder).dx;
@ -228,8 +228,8 @@ void main() {
tester.view.devicePixelRatio = 1;
addTearDown(tester.view.reset);
final Finder stringFragmentTextFinder = find.descendant(
of: find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_StringFragment'),
final Finder timeSelectorSeparatorFinder = find.descendant(
of: find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_TimeSelectorSeparator'),
matching: find.byType(Text),
).first;
final Finder hourControlFinder = find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_HourControl');
@ -248,11 +248,11 @@ void main() {
for (final Locale locale in locales) {
final Offset center = await startPicker(tester, (TimeOfDay? time) { }, locale: locale, useMaterial3: true);
final Text stringFragmentText = tester.widget(stringFragmentTextFinder);
final Text stringFragmentText = tester.widget(timeSelectorSeparatorFinder);
final double hourLeftOffset = tester.getTopLeft(hourControlFinder).dx;
final double hourTopOffset = tester.getTopLeft(hourControlFinder).dy;
final double minuteLeftOffset = tester.getTopLeft(minuteControlFinder).dx;
final double stringFragmentLeftOffset = tester.getTopLeft(stringFragmentTextFinder).dx;
final double stringFragmentLeftOffset = tester.getTopLeft(timeSelectorSeparatorFinder).dx;
if (locale == const Locale('en', 'US')) {
final double dayPeriodLeftOffset = tester.getTopLeft(dayPeriodControlFinder).dx;
@ -301,8 +301,8 @@ void main() {
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'),
final Finder timeSelectorSeparatorFinder = find.descendant(
of: find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_TimeSelectorSeparator'),
matching: find.byType(Text),
).first;
@ -321,10 +321,10 @@ void main() {
await tester.tap(find.text('X'));
await tester.pumpAndSettle(const Duration(seconds: 1));
final Text stringFragmentText = tester.widget(stringFragmentTextFinder);
final Text stringFragmentText = tester.widget(timeSelectorSeparatorFinder);
final double hourLeftOffset = tester.getTopLeft(hourControlFinder).dx;
final double minuteLeftOffset = tester.getTopLeft(minuteControlFinder).dx;
final double stringFragmentLeftOffset = tester.getTopLeft(stringFragmentTextFinder).dx;
final double stringFragmentLeftOffset = tester.getTopLeft(timeSelectorSeparatorFinder).dx;
if (locale == const Locale('en', 'US')) {
final double dayPeriodLeftOffset = tester.getTopLeft(dayPeriodControlFinder).dx;
@ -369,8 +369,8 @@ void main() {
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'),
final Finder timeSelectorSeparatorFinder = find.descendant(
of: find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_TimeSelectorSeparator'),
matching: find.byType(Text),
).first;
@ -389,10 +389,10 @@ void main() {
await tester.tap(find.text('X'));
await tester.pumpAndSettle(const Duration(seconds: 1));
final Text stringFragmentText = tester.widget(stringFragmentTextFinder);
final Text stringFragmentText = tester.widget(timeSelectorSeparatorFinder);
final double hourLeftOffset = tester.getTopLeft(hourControlFinder).dx;
final double minuteLeftOffset = tester.getTopLeft(minuteControlFinder).dx;
final double stringFragmentLeftOffset = tester.getTopLeft(stringFragmentTextFinder).dx;
final double stringFragmentLeftOffset = tester.getTopLeft(timeSelectorSeparatorFinder).dx;
if (locale == const Locale('en', 'US')) {
final double dayPeriodLeftOffset = tester.getTopLeft(dayPeriodControlFinder).dx;