blob: 1f44f352442926d449f2a4d3f4f69ab8212406ea [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 '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, the time picker defaults to the overall theme's
/// [ColorScheme.primary].
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) {
assert(t != null);
// 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,
}) : assert(data != null);
/// 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;
}