// 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 'dart:ui'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'button_style.dart'; import 'input_decorator.dart'; import 'material_state.dart'; import 'theme.dart'; // Examples can assume: // late BuildContext context; /// Defines the visual properties of the widget displayed with [showTimePicker]. /// /// Descendant widgets obtain the current [TimePickerThemeData] object using /// `TimePickerTheme.of(context)`. Instances of [TimePickerThemeData] /// can be customized with [TimePickerThemeData.copyWith]. /// /// Typically a [TimePickerThemeData] is specified as part of the overall /// [Theme] with [ThemeData.timePickerTheme]. /// /// All [TimePickerThemeData] properties are `null` by default. When null, /// [showTimePicker] will provide its own defaults. /// /// See also: /// /// * [ThemeData], which describes the overall theme information for the /// application. /// * [TimePickerTheme], which describes the actual configuration of a time /// picker theme. @immutable class TimePickerThemeData with Diagnosticable { /// Creates a theme that can be used for [TimePickerTheme] or /// [ThemeData.timePickerTheme]. const TimePickerThemeData({ this.backgroundColor, this.cancelButtonStyle, this.confirmButtonStyle, this.dayPeriodBorderSide, this.dayPeriodColor, this.dayPeriodShape, this.dayPeriodTextColor, this.dayPeriodTextStyle, this.dialBackgroundColor, this.dialHandColor, this.dialTextColor, this.dialTextStyle, this.elevation, this.entryModeIconColor, this.helpTextStyle, this.hourMinuteColor, this.hourMinuteShape, this.hourMinuteTextColor, this.hourMinuteTextStyle, this.inputDecorationTheme, this.padding, this.shape, }); /// The background color of a time picker. /// /// If this is null, the time picker defaults to the overall theme's /// [ColorScheme.background]. final Color? backgroundColor; /// The style of the cancel button of a [TimePickerDialog]. final ButtonStyle? cancelButtonStyle; /// The style of the confirm (OK) button of a [TimePickerDialog]. final ButtonStyle? confirmButtonStyle; /// The color and weight of the day period's outline. /// /// If this is null, the time picker defaults to: /// /// ```dart /// BorderSide( /// color: Color.alphaBlend( /// Theme.of(context).colorScheme.onBackground.withOpacity(0.38), /// Theme.of(context).colorScheme.surface, /// ), /// ), /// ``` final BorderSide? dayPeriodBorderSide; /// The background color of the AM/PM toggle. /// /// If [dayPeriodColor] is a [MaterialStateColor], then the effective /// background color can depend on the [MaterialState.selected] state, i.e. /// if the segment is selected or not. /// /// By default, if the segment is selected, the overall theme's /// `ColorScheme.primary.withOpacity(0.12)` is used when the overall theme's /// brightness is [Brightness.light] and /// `ColorScheme.primary.withOpacity(0.24)` is used when the overall theme's /// brightness is [Brightness.dark]. /// If the segment is not selected, [Colors.transparent] is used to allow the /// [Dialog]'s color to be used. final Color? dayPeriodColor; /// The shape of the day period that the time picker uses. /// /// If this is null, the time picker defaults to: /// /// ```dart /// const RoundedRectangleBorder( /// borderRadius: BorderRadius.all(Radius.circular(4.0)), /// side: BorderSide(), /// ) /// ``` final OutlinedBorder? dayPeriodShape; /// The color of the day period text that represents AM/PM. /// /// If [dayPeriodTextColor] is a [MaterialStateColor], then the effective /// text color can depend on the [MaterialState.selected] state, i.e. if the /// text is selected or not. /// /// By default the overall theme's [ColorScheme.primary] color is used when /// the text is selected and `ColorScheme.onSurface.withOpacity(0.60)` when /// it's not selected. final Color? dayPeriodTextColor; /// Used to configure the [TextStyle]s for the day period control. /// /// If this is null, the time picker defaults to the overall theme's /// [TextTheme.titleMedium]. final TextStyle? dayPeriodTextStyle; /// The background color of the time picker dial when the entry mode is /// [TimePickerEntryMode.dial] or [TimePickerEntryMode.dialOnly]. /// /// If this is null and [ThemeData.useMaterial3] is true, the time picker /// dial background color defaults [ColorScheme.surfaceVariant] color. /// /// If this is null and [ThemeData.useMaterial3] is false, the time picker /// dial background color defaults to [ColorScheme.onSurface] color with /// an opacity of 0.08 when the overall theme's brightness is [Brightness.light] /// and [ColorScheme.onSurface] color with an opacity of 0.12 when the overall /// theme's brightness is [Brightness.dark]. final Color? dialBackgroundColor; /// The color of the time picker dial's hand when the entry mode is /// [TimePickerEntryMode.dial] or [TimePickerEntryMode.dialOnly]. /// /// If this is null, the time picker defaults to the overall theme's /// [ColorScheme.primary]. final Color? dialHandColor; /// The color of the dial text that represents specific hours and minutes. /// /// If [dialTextColor] is a [MaterialStateColor], then the effective /// text color can depend on the [MaterialState.selected] state, i.e. if the /// text is selected or not. /// /// If this color is null then the dial's text colors are based on the /// theme's [ThemeData.colorScheme]. final Color? dialTextColor; /// The [TextStyle] for the numbers on the time selection dial. /// /// If [dialTextStyle]'s [TextStyle.color] is a [MaterialStateColor], then the /// effective text color can depend on the [MaterialState.selected] state, /// i.e. if the text is selected or not. /// /// If this style is null then the dial's text style is based on the theme's /// [ThemeData.textTheme]. final TextStyle? dialTextStyle; /// The Material elevation for the time picker dialog. final double? elevation; /// The color of the entry mode [IconButton]. /// /// If this is null, the time picker defaults to: /// /// /// ```dart /// Theme.of(context).colorScheme.onSurface.withOpacity( /// Theme.of(context).colorScheme.brightness == Brightness.dark ? 1.0 : 0.6, /// ) /// ``` final Color? entryModeIconColor; /// Used to configure the [TextStyle]s for the helper text in the header. /// /// If this is null, the time picker defaults to the overall theme's /// [TextTheme.labelSmall]. final TextStyle? helpTextStyle; /// The background color of the hour and minute header segments. /// /// If [hourMinuteColor] is a [MaterialStateColor], then the effective /// background color can depend on the [MaterialState.selected] state, i.e. /// if the segment is selected or not. /// /// By default, if the segment is selected, the overall theme's /// `ColorScheme.primary.withOpacity(0.12)` is used when the overall theme's /// brightness is [Brightness.light] and /// `ColorScheme.primary.withOpacity(0.24)` is used when the overall theme's /// brightness is [Brightness.dark]. /// If the segment is not selected, the overall theme's /// `ColorScheme.onSurface.withOpacity(0.12)` is used. final Color? hourMinuteColor; /// The shape of the hour and minute controls that the time picker uses. /// /// If this is null, the time picker defaults to /// `RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4.0)))`. final ShapeBorder? hourMinuteShape; /// The color of the header text that represents hours and minutes. /// /// If [hourMinuteTextColor] is a [MaterialStateColor], then the effective /// text color can depend on the [MaterialState.selected] state, i.e. if the /// text is selected or not. /// /// By default the overall theme's [ColorScheme.primary] color is used when /// the text is selected and [ColorScheme.onSurface] when it's not selected. final Color? hourMinuteTextColor; /// Used to configure the [TextStyle]s for the hour/minute controls. /// /// If this is null, the time picker defaults to the overall theme's /// [TextTheme.headline3]. final TextStyle? hourMinuteTextStyle; /// The input decoration theme for the [TextField]s in the time picker. /// /// If this is null, the time picker provides its own defaults. final InputDecorationTheme? inputDecorationTheme; /// The padding around the time picker dialog when the entry mode is /// [TimePickerEntryMode.dial] or [TimePickerEntryMode.dialOnly]. final EdgeInsetsGeometry? padding; /// The shape of the [Dialog] that the time picker is presented in. /// /// If this is null, the time picker defaults to /// `RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4.0)))`. final ShapeBorder? shape; /// Creates a copy of this object with the given fields replaced with the /// new values. TimePickerThemeData copyWith({ Color? backgroundColor, ButtonStyle? cancelButtonStyle, ButtonStyle? confirmButtonStyle, ButtonStyle? dayPeriodButtonStyle, BorderSide? dayPeriodBorderSide, Color? dayPeriodColor, OutlinedBorder? dayPeriodShape, Color? dayPeriodTextColor, TextStyle? dayPeriodTextStyle, Color? dialBackgroundColor, Color? dialHandColor, Color? dialTextColor, TextStyle? dialTextStyle, double? elevation, Color? entryModeIconColor, TextStyle? helpTextStyle, Color? hourMinuteColor, ShapeBorder? hourMinuteShape, Color? hourMinuteTextColor, TextStyle? hourMinuteTextStyle, InputDecorationTheme? inputDecorationTheme, EdgeInsetsGeometry? padding, ShapeBorder? shape, }) { return TimePickerThemeData( backgroundColor: backgroundColor ?? this.backgroundColor, cancelButtonStyle: cancelButtonStyle ?? this.cancelButtonStyle, confirmButtonStyle: confirmButtonStyle ?? this.confirmButtonStyle, dayPeriodBorderSide: dayPeriodBorderSide ?? this.dayPeriodBorderSide, dayPeriodColor: dayPeriodColor ?? this.dayPeriodColor, dayPeriodShape: dayPeriodShape ?? this.dayPeriodShape, dayPeriodTextColor: dayPeriodTextColor ?? this.dayPeriodTextColor, dayPeriodTextStyle: dayPeriodTextStyle ?? this.dayPeriodTextStyle, dialBackgroundColor: dialBackgroundColor ?? this.dialBackgroundColor, dialHandColor: dialHandColor ?? this.dialHandColor, dialTextColor: dialTextColor ?? this.dialTextColor, dialTextStyle: dialTextStyle ?? this.dialTextStyle, elevation: elevation ?? this.elevation, entryModeIconColor: entryModeIconColor ?? this.entryModeIconColor, helpTextStyle: helpTextStyle ?? this.helpTextStyle, hourMinuteColor: hourMinuteColor ?? this.hourMinuteColor, hourMinuteShape: hourMinuteShape ?? this.hourMinuteShape, hourMinuteTextColor: hourMinuteTextColor ?? this.hourMinuteTextColor, hourMinuteTextStyle: hourMinuteTextStyle ?? this.hourMinuteTextStyle, inputDecorationTheme: inputDecorationTheme ?? this.inputDecorationTheme, padding: padding ?? this.padding, shape: shape ?? this.shape, ); } /// Linearly interpolate between two time picker themes. /// /// {@macro dart.ui.shadow.lerp} static TimePickerThemeData lerp(TimePickerThemeData? a, TimePickerThemeData? b, double t) { if (identical(a, b) && a != null) { return a; } // Workaround since BorderSide's lerp does not allow for null arguments. BorderSide? lerpedBorderSide; if (a?.dayPeriodBorderSide == null && b?.dayPeriodBorderSide == null) { lerpedBorderSide = null; } else if (a?.dayPeriodBorderSide == null) { lerpedBorderSide = b?.dayPeriodBorderSide; } else if (b?.dayPeriodBorderSide == null) { lerpedBorderSide = a?.dayPeriodBorderSide; } else { lerpedBorderSide = BorderSide.lerp(a!.dayPeriodBorderSide!, b!.dayPeriodBorderSide!, t); } return TimePickerThemeData( backgroundColor: Color.lerp(a?.backgroundColor, b?.backgroundColor, t), cancelButtonStyle: ButtonStyle.lerp(a?.cancelButtonStyle, b?.cancelButtonStyle, t), confirmButtonStyle: ButtonStyle.lerp(a?.confirmButtonStyle, b?.confirmButtonStyle, t), dayPeriodBorderSide: lerpedBorderSide, dayPeriodColor: Color.lerp(a?.dayPeriodColor, b?.dayPeriodColor, t), dayPeriodShape: ShapeBorder.lerp(a?.dayPeriodShape, b?.dayPeriodShape, t) as OutlinedBorder?, dayPeriodTextColor: Color.lerp(a?.dayPeriodTextColor, b?.dayPeriodTextColor, t), dayPeriodTextStyle: TextStyle.lerp(a?.dayPeriodTextStyle, b?.dayPeriodTextStyle, t), dialBackgroundColor: Color.lerp(a?.dialBackgroundColor, b?.dialBackgroundColor, t), dialHandColor: Color.lerp(a?.dialHandColor, b?.dialHandColor, t), dialTextColor: Color.lerp(a?.dialTextColor, b?.dialTextColor, t), dialTextStyle: TextStyle.lerp(a?.dialTextStyle, b?.dialTextStyle, t), elevation: lerpDouble(a?.elevation, b?.elevation, t), entryModeIconColor: Color.lerp(a?.entryModeIconColor, b?.entryModeIconColor, t), helpTextStyle: TextStyle.lerp(a?.helpTextStyle, b?.helpTextStyle, t), hourMinuteColor: Color.lerp(a?.hourMinuteColor, b?.hourMinuteColor, t), hourMinuteShape: ShapeBorder.lerp(a?.hourMinuteShape, b?.hourMinuteShape, t), hourMinuteTextColor: Color.lerp(a?.hourMinuteTextColor, b?.hourMinuteTextColor, t), hourMinuteTextStyle: TextStyle.lerp(a?.hourMinuteTextStyle, b?.hourMinuteTextStyle, t), inputDecorationTheme: t < 0.5 ? a?.inputDecorationTheme : b?.inputDecorationTheme, padding: EdgeInsetsGeometry.lerp(a?.padding, b?.padding, t), shape: ShapeBorder.lerp(a?.shape, b?.shape, t), ); } @override int get hashCode => Object.hashAll(<Object?>[ backgroundColor, cancelButtonStyle, confirmButtonStyle, dayPeriodBorderSide, dayPeriodColor, dayPeriodShape, dayPeriodTextColor, dayPeriodTextStyle, dialBackgroundColor, dialHandColor, dialTextColor, dialTextStyle, elevation, entryModeIconColor, helpTextStyle, hourMinuteColor, hourMinuteShape, hourMinuteTextColor, hourMinuteTextStyle, inputDecorationTheme, padding, shape, ]); @override bool operator ==(Object other) { if (identical(this, other)) { return true; } if (other.runtimeType != runtimeType) { return false; } return other is TimePickerThemeData && other.backgroundColor == backgroundColor && other.cancelButtonStyle == cancelButtonStyle && other.confirmButtonStyle == confirmButtonStyle && other.dayPeriodBorderSide == dayPeriodBorderSide && other.dayPeriodColor == dayPeriodColor && other.dayPeriodShape == dayPeriodShape && other.dayPeriodTextColor == dayPeriodTextColor && other.dayPeriodTextStyle == dayPeriodTextStyle && other.dialBackgroundColor == dialBackgroundColor && other.dialHandColor == dialHandColor && other.dialTextColor == dialTextColor && other.dialTextStyle == dialTextStyle && other.elevation == elevation && other.entryModeIconColor == entryModeIconColor && other.helpTextStyle == helpTextStyle && other.hourMinuteColor == hourMinuteColor && other.hourMinuteShape == hourMinuteShape && other.hourMinuteTextColor == hourMinuteTextColor && other.hourMinuteTextStyle == hourMinuteTextStyle && other.inputDecorationTheme == inputDecorationTheme && other.padding == padding && other.shape == shape; } @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties.add(ColorProperty('backgroundColor', backgroundColor, defaultValue: null)); properties.add(DiagnosticsProperty<ButtonStyle>('cancelButtonStyle', cancelButtonStyle, defaultValue: null)); properties.add(DiagnosticsProperty<ButtonStyle>('confirmButtonStyle', confirmButtonStyle, defaultValue: null)); properties.add(DiagnosticsProperty<BorderSide>('dayPeriodBorderSide', dayPeriodBorderSide, defaultValue: null)); properties.add(ColorProperty('dayPeriodColor', dayPeriodColor, defaultValue: null)); properties.add(DiagnosticsProperty<ShapeBorder>('dayPeriodShape', dayPeriodShape, defaultValue: null)); properties.add(ColorProperty('dayPeriodTextColor', dayPeriodTextColor, defaultValue: null)); properties.add(DiagnosticsProperty<TextStyle>('dayPeriodTextStyle', dayPeriodTextStyle, defaultValue: null)); properties.add(ColorProperty('dialBackgroundColor', dialBackgroundColor, defaultValue: null)); properties.add(ColorProperty('dialHandColor', dialHandColor, defaultValue: null)); properties.add(ColorProperty('dialTextColor', dialTextColor, defaultValue: null)); properties.add(DiagnosticsProperty<TextStyle?>('dialTextStyle', dialTextStyle, defaultValue: null)); properties.add(DoubleProperty('elevation', elevation, defaultValue: null)); properties.add(ColorProperty('entryModeIconColor', entryModeIconColor, defaultValue: null)); properties.add(DiagnosticsProperty<TextStyle>('helpTextStyle', helpTextStyle, defaultValue: null)); properties.add(ColorProperty('hourMinuteColor', hourMinuteColor, defaultValue: null)); properties.add(DiagnosticsProperty<ShapeBorder>('hourMinuteShape', hourMinuteShape, defaultValue: null)); properties.add(ColorProperty('hourMinuteTextColor', hourMinuteTextColor, defaultValue: null)); properties.add(DiagnosticsProperty<TextStyle>('hourMinuteTextStyle', hourMinuteTextStyle, defaultValue: null)); properties.add(DiagnosticsProperty<InputDecorationTheme>('inputDecorationTheme', inputDecorationTheme, defaultValue: null)); properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding, defaultValue: null)); properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null)); } } /// An inherited widget that defines the configuration for time pickers /// displayed using [showTimePicker] in this widget's subtree. /// /// Values specified here are used for time picker properties that are not /// given an explicit non-null value. class TimePickerTheme extends InheritedTheme { /// Creates a time picker theme that controls the configurations for /// time pickers displayed in its widget subtree. const TimePickerTheme({ super.key, required this.data, required super.child, }); /// The properties for descendant time picker widgets. final TimePickerThemeData data; /// The [data] value of the closest [TimePickerTheme] ancestor. /// /// If there is no ancestor, it returns [ThemeData.timePickerTheme]. /// Applications can assume that the returned value will not be null. /// /// Typical usage is as follows: /// /// ```dart /// TimePickerThemeData theme = TimePickerTheme.of(context); /// ``` static TimePickerThemeData of(BuildContext context) { final TimePickerTheme? timePickerTheme = context.dependOnInheritedWidgetOfExactType<TimePickerTheme>(); return timePickerTheme?.data ?? Theme.of(context).timePickerTheme; } @override Widget wrap(BuildContext context, Widget child) { return TimePickerTheme(data: data, child: child); } @override bool updateShouldNotify(TimePickerTheme oldWidget) => data != oldWidget.data; }