This repository has been archived by the owner on Feb 22, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 9.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
- Loading branch information
1 parent
a59dd83
commit fae458b
Showing
9 changed files
with
4,940 additions
and
2,601 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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")}; | ||
} | ||
} | ||
'''; | ||
} |
Oops, something went wrong.