| // 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() => ''' |
| 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. It's a BorderRadiusGeometry, so we have to |
| // resolve it before we can use it. |
| final BorderRadius selectorRadius = ${shape('$hourMinuteComponent.container')} |
| .borderRadius |
| .resolve(Directionality.of(context)); |
| 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")}; |
| } |
| } |
| '''; |
| } |