// 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")}; } } '''; }