| // 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 conform (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. |
| /// |
| /// The argument `t` must not be null. |
| /// |
| /// {@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; |
| } |