Skip to content
This repository has been archived by the owner on Feb 22, 2023. It is now read-only.

Commit

Permalink
Convert TimePicker to Material 3 (#116396)
Browse files Browse the repository at this point in the history
* 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
gspencergoog authored Dec 14, 2022
1 parent a59dd83 commit fae458b
Show file tree
Hide file tree
Showing 9 changed files with 4,940 additions and 2,601 deletions.
2 changes: 2 additions & 0 deletions dev/tools/gen_defaults/bin/gen_defaults.dart
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import 'package:gen_defaults/surface_tint.dart';
import 'package:gen_defaults/switch_template.dart';
import 'package:gen_defaults/tabs_template.dart';
import 'package:gen_defaults/text_field_template.dart';
import 'package:gen_defaults/time_picker_template.dart';
import 'package:gen_defaults/typography_template.dart';

Map<String, dynamic> _readTokenFile(String fileName) {
Expand Down Expand Up @@ -167,6 +168,7 @@ Future<void> main(List<String> args) async {
SliderTemplate('md.comp.slider', 'Slider', '$materialLib/slider.dart', tokens).updateFile();
SurfaceTintTemplate('SurfaceTint', '$materialLib/elevation_overlay.dart', tokens).updateFile();
SwitchTemplate('Switch', '$materialLib/switch.dart', tokens).updateFile();
TimePickerTemplate('TimePicker', '$materialLib/time_picker.dart', tokens).updateFile();
TextFieldTemplate('TextField', '$materialLib/text_field.dart', tokens).updateFile();
TabsTemplate('Tabs', '$materialLib/tabs.dart', tokens).updateFile();
TypographyTemplate('Typography', '$materialLib/typography.dart', tokens).updateFile();
Expand Down
349 changes: 349 additions & 0 deletions dev/tools/gen_defaults/lib/time_picker_template.dart
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")};
}
}
''';
}
Loading

0 comments on commit fae458b

Please sign in to comment.