blob: af359acf6cd90e230f18532bc9fd7389558753bb [file] [log] [blame]
// 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) {
if (states.contains(MaterialState.selected)) {
if (states.contains(MaterialState.focused)) {
return ${componentColor("$dayPeriodComponent.selected.focus.label-text")};
}
if (states.contains(MaterialState.hovered)) {
return ${componentColor("$dayPeriodComponent.selected.hover.label-text")};
}
if (states.contains(MaterialState.pressed)) {
return ${componentColor("$dayPeriodComponent.selected.pressed.label-text")};
}
return ${componentColor("$dayPeriodComponent.selected.label-text")};
}
if (states.contains(MaterialState.focused)) {
return ${componentColor("$dayPeriodComponent.unselected.focus.label-text")};
}
if (states.contains(MaterialState.hovered)) {
return ${componentColor("$dayPeriodComponent.unselected.hover.label-text")};
}
if (states.contains(MaterialState.pressed)) {
return ${componentColor("$dayPeriodComponent.unselected.pressed.label-text")};
}
return ${componentColor("$dayPeriodComponent.unselected.label-text")};
});
}
@override
TextStyle get dayPeriodTextStyle {
return ${textStyle("$dayPeriodComponent.label-text")}!.copyWith(color: dayPeriodTextColor);
}
@override
Color get dialBackgroundColor {
return ${componentColor(dialComponent)};
}
@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.hovered)) {
const double hoverOpacity = ${opacity('$hourMinuteComponent.hover.state-layer.opacity')};
overlayColor = ${componentColor('$hourMinuteComponent.selected.hover.state-layer')}.withOpacity(hoverOpacity);
} else if (states.contains(MaterialState.focused)) {
const double focusOpacity = ${opacity('$hourMinuteComponent.focus.state-layer.opacity')};
overlayColor = ${componentColor('$hourMinuteComponent.selected.focus.state-layer')}.withOpacity(focusOpacity);
}
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.hovered)) {
const double hoverOpacity = ${opacity('$hourMinuteComponent.hover.state-layer.opacity')};
overlayColor = ${componentColor('$hourMinuteComponent.unselected.hover.state-layer')}.withOpacity(hoverOpacity);
} else if (states.contains(MaterialState.focused)) {
const double focusOpacity = ${opacity('$hourMinuteComponent.focus.state-layer.opacity')};
overlayColor = ${componentColor('$hourMinuteComponent.unselected.focus.state-layer')}.withOpacity(focusOpacity);
}
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.hovered)) {
return ${componentColor("$hourMinuteComponent.selected.hover.label-text")};
}
if (states.contains(MaterialState.focused)) {
return ${componentColor("$hourMinuteComponent.selected.focus.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.hovered)) {
return ${componentColor("$hourMinuteComponent.unselected.hover.label-text")};
}
if (states.contains(MaterialState.focused)) {
return ${componentColor("$hourMinuteComponent.unselected.focus.label-text")};
}
return ${componentColor("$hourMinuteComponent.unselected.label-text")};
}
});
}
@override
TextStyle get hourMinuteTextStyle {
return MaterialStateTextStyle.resolveWith((Set<MaterialState> states) {
// TODO(tahatesser): Update this when https://github.com/flutter/flutter/issues/127035 is fixed.
// This is using the correct text style from Material 3 spec.
// https://m3.material.io/components/time-pickers/specs#fd0b6939-edab-4058-82e1-93d163945215
return _textTheme.displayMedium!.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")};
}
}
''';
}