time_picker_theme.dart 19.3 KB
Newer Older
1 2 3 4
// 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.

5 6
import 'dart:ui';

7 8 9
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';

10
import 'button_style.dart';
11
import 'input_decorator.dart';
12
import 'material_state.dart';
13 14
import 'theme.dart';

15 16 17
// Examples can assume:
// late BuildContext context;

18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
/// 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,
43 44 45
    this.cancelButtonStyle,
    this.confirmButtonStyle,
    this.dayPeriodBorderSide,
46
    this.dayPeriodColor,
47 48 49
    this.dayPeriodShape,
    this.dayPeriodTextColor,
    this.dayPeriodTextStyle,
50
    this.dialBackgroundColor,
51
    this.dialHandColor,
52
    this.dialTextColor,
53 54
    this.dialTextStyle,
    this.elevation,
55 56
    this.entryModeIconColor,
    this.helpTextStyle,
57
    this.hourMinuteColor,
58
    this.hourMinuteShape,
59 60
    this.hourMinuteTextColor,
    this.hourMinuteTextStyle,
61
    this.inputDecorationTheme,
62 63
    this.padding,
    this.shape,
64 65 66 67 68 69
  });

  /// The background color of a time picker.
  ///
  /// If this is null, the time picker defaults to the overall theme's
  /// [ColorScheme.background].
70
  final Color? backgroundColor;
71

72 73 74
  /// The style of the cancel button of a [TimePickerDialog].
  final ButtonStyle? cancelButtonStyle;

75
  /// The style of the confirm (OK) button of a [TimePickerDialog].
76 77 78
  final ButtonStyle? confirmButtonStyle;

  /// The color and weight of the day period's outline.
79
  ///
80
  /// If this is null, the time picker defaults to:
81
  ///
82 83 84 85 86 87 88 89 90
  /// ```dart
  /// BorderSide(
  ///   color: Color.alphaBlend(
  ///     Theme.of(context).colorScheme.onBackground.withOpacity(0.38),
  ///     Theme.of(context).colorScheme.surface,
  ///   ),
  /// ),
  /// ```
  final BorderSide? dayPeriodBorderSide;
91

92
  /// The background color of the AM/PM toggle.
93
  ///
94
  /// If [dayPeriodColor] is a [MaterialStateColor], then the effective
95 96 97 98 99 100 101 102
  /// 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].
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
  /// 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;
118 119 120 121 122 123 124 125 126 127

  /// 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.
128
  final Color? dayPeriodTextColor;
129

130
  /// Used to configure the [TextStyle]s for the day period control.
131
  ///
132 133 134
  /// If this is null, the time picker defaults to the overall theme's
  /// [TextTheme.titleMedium].
  final TextStyle? dayPeriodTextStyle;
135

136 137
  /// The background color of the time picker dial when the entry mode is
  /// [TimePickerEntryMode.dial] or [TimePickerEntryMode.dialOnly].
138
  ///
139 140 141 142 143 144 145 146
  /// 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].
147
  final Color? dialBackgroundColor;
148

149 150
  /// The color of the time picker dial's hand when the entry mode is
  /// [TimePickerEntryMode.dial] or [TimePickerEntryMode.dialOnly].
151 152 153
  ///
  /// If this is null, the time picker defaults to the overall theme's
  /// [ColorScheme.primary].
154
  final Color? dialHandColor;
155

156 157 158 159 160 161
  /// 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.
  ///
162 163
  /// If this color is null then the dial's text colors are based on the
  /// theme's [ThemeData.colorScheme].
164
  final Color? dialTextColor;
165

166 167 168 169 170 171 172 173 174 175 176 177 178
  /// 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;

179 180
  /// The color of the entry mode [IconButton].
  ///
181
  /// If this is null, the time picker defaults to:
182
  ///
183
  ///
184
  /// ```dart
185 186 187 188
  /// Theme.of(context).colorScheme.onSurface.withOpacity(
  ///   Theme.of(context).colorScheme.brightness == Brightness.dark ? 1.0 : 0.6,
  /// )
  /// ```
189
  final Color? entryModeIconColor;
190 191 192 193

  /// 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
194
  /// [TextTheme.labelSmall].
195
  final TextStyle? helpTextStyle;
196

197
  /// The background color of the hour and minute header segments.
198
  ///
199 200 201 202 203 204 205 206 207 208 209 210
  /// 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;
211 212 213 214 215

  /// 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)))`.
216
  final ShapeBorder? hourMinuteShape;
217

218
  /// The color of the header text that represents hours and minutes.
219
  ///
220 221 222
  /// 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.
223
  ///
224 225 226
  /// 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;
227

228
  /// Used to configure the [TextStyle]s for the hour/minute controls.
229
  ///
230 231 232
  /// If this is null, the time picker defaults to the overall theme's
  /// [TextTheme.headline3].
  final TextStyle? hourMinuteTextStyle;
233 234 235 236

  /// The input decoration theme for the [TextField]s in the time picker.
  ///
  /// If this is null, the time picker provides its own defaults.
237
  final InputDecorationTheme? inputDecorationTheme;
238

239 240 241 242 243 244 245 246 247 248
  /// 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;

249 250 251
  /// Creates a copy of this object with the given fields replaced with the
  /// new values.
  TimePickerThemeData copyWith({
252
    Color? backgroundColor,
253 254 255 256
    ButtonStyle? cancelButtonStyle,
    ButtonStyle? confirmButtonStyle,
    ButtonStyle? dayPeriodButtonStyle,
    BorderSide? dayPeriodBorderSide,
257
    Color? dayPeriodColor,
258 259 260
    OutlinedBorder? dayPeriodShape,
    Color? dayPeriodTextColor,
    TextStyle? dayPeriodTextStyle,
261
    Color? dialBackgroundColor,
262
    Color? dialHandColor,
263
    Color? dialTextColor,
264 265
    TextStyle? dialTextStyle,
    double? elevation,
266 267
    Color? entryModeIconColor,
    TextStyle? helpTextStyle,
268
    Color? hourMinuteColor,
269
    ShapeBorder? hourMinuteShape,
270 271
    Color? hourMinuteTextColor,
    TextStyle? hourMinuteTextStyle,
272
    InputDecorationTheme? inputDecorationTheme,
273 274
    EdgeInsetsGeometry? padding,
    ShapeBorder? shape,
275 276 277
  }) {
    return TimePickerThemeData(
      backgroundColor: backgroundColor ?? this.backgroundColor,
278 279 280
      cancelButtonStyle: cancelButtonStyle ?? this.cancelButtonStyle,
      confirmButtonStyle: confirmButtonStyle ?? this.confirmButtonStyle,
      dayPeriodBorderSide: dayPeriodBorderSide ?? this.dayPeriodBorderSide,
281
      dayPeriodColor: dayPeriodColor ?? this.dayPeriodColor,
282 283 284
      dayPeriodShape: dayPeriodShape ?? this.dayPeriodShape,
      dayPeriodTextColor: dayPeriodTextColor ?? this.dayPeriodTextColor,
      dayPeriodTextStyle: dayPeriodTextStyle ?? this.dayPeriodTextStyle,
285
      dialBackgroundColor: dialBackgroundColor ?? this.dialBackgroundColor,
286
      dialHandColor: dialHandColor ?? this.dialHandColor,
287
      dialTextColor: dialTextColor ?? this.dialTextColor,
288 289
      dialTextStyle: dialTextStyle ?? this.dialTextStyle,
      elevation: elevation ?? this.elevation,
290 291
      entryModeIconColor: entryModeIconColor ?? this.entryModeIconColor,
      helpTextStyle: helpTextStyle ?? this.helpTextStyle,
292
      hourMinuteColor: hourMinuteColor ?? this.hourMinuteColor,
293
      hourMinuteShape: hourMinuteShape ?? this.hourMinuteShape,
294 295
      hourMinuteTextColor: hourMinuteTextColor ?? this.hourMinuteTextColor,
      hourMinuteTextStyle: hourMinuteTextStyle ?? this.hourMinuteTextStyle,
296
      inputDecorationTheme: inputDecorationTheme ?? this.inputDecorationTheme,
297 298
      padding: padding ?? this.padding,
      shape: shape ?? this.shape,
299 300 301 302 303 304
    );
  }

  /// Linearly interpolate between two time picker themes.
  ///
  /// {@macro dart.ui.shadow.lerp}
305
  static TimePickerThemeData lerp(TimePickerThemeData? a, TimePickerThemeData? b, double t) {
306 307 308
    if (identical(a, b) && a != null) {
      return a;
    }
309
    // Workaround since BorderSide's lerp does not allow for null arguments.
310
    BorderSide? lerpedBorderSide;
311 312 313 314 315 316 317
    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 {
318
      lerpedBorderSide = BorderSide.lerp(a!.dayPeriodBorderSide!, b!.dayPeriodBorderSide!, t);
319 320 321
    }
    return TimePickerThemeData(
      backgroundColor: Color.lerp(a?.backgroundColor, b?.backgroundColor, t),
322 323 324
      cancelButtonStyle: ButtonStyle.lerp(a?.cancelButtonStyle, b?.cancelButtonStyle, t),
      confirmButtonStyle: ButtonStyle.lerp(a?.confirmButtonStyle, b?.confirmButtonStyle, t),
      dayPeriodBorderSide: lerpedBorderSide,
325
      dayPeriodColor: Color.lerp(a?.dayPeriodColor, b?.dayPeriodColor, t),
326 327 328
      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),
329
      dialBackgroundColor: Color.lerp(a?.dialBackgroundColor, b?.dialBackgroundColor, t),
330
      dialHandColor: Color.lerp(a?.dialHandColor, b?.dialHandColor, t),
331
      dialTextColor: Color.lerp(a?.dialTextColor, b?.dialTextColor, t),
332 333
      dialTextStyle: TextStyle.lerp(a?.dialTextStyle, b?.dialTextStyle, t),
      elevation: lerpDouble(a?.elevation, b?.elevation, t),
334 335
      entryModeIconColor: Color.lerp(a?.entryModeIconColor, b?.entryModeIconColor, t),
      helpTextStyle: TextStyle.lerp(a?.helpTextStyle, b?.helpTextStyle, t),
336
      hourMinuteColor: Color.lerp(a?.hourMinuteColor, b?.hourMinuteColor, t),
337
      hourMinuteShape: ShapeBorder.lerp(a?.hourMinuteShape, b?.hourMinuteShape, t),
338 339
      hourMinuteTextColor: Color.lerp(a?.hourMinuteTextColor, b?.hourMinuteTextColor, t),
      hourMinuteTextStyle: TextStyle.lerp(a?.hourMinuteTextStyle, b?.hourMinuteTextStyle, t),
340
      inputDecorationTheme: t < 0.5 ? a?.inputDecorationTheme : b?.inputDecorationTheme,
341 342
      padding: EdgeInsetsGeometry.lerp(a?.padding, b?.padding, t),
      shape: ShapeBorder.lerp(a?.shape, b?.shape, t),
343 344 345 346
    );
  }

  @override
347
  int get hashCode => Object.hashAll(<Object?>[
348
    backgroundColor,
349 350 351
    cancelButtonStyle,
    confirmButtonStyle,
    dayPeriodBorderSide,
352
    dayPeriodColor,
353 354 355
    dayPeriodShape,
    dayPeriodTextColor,
    dayPeriodTextStyle,
356
    dialBackgroundColor,
357
    dialHandColor,
358
    dialTextColor,
359 360
    dialTextStyle,
    elevation,
361 362
    entryModeIconColor,
    helpTextStyle,
363
    hourMinuteColor,
364
    hourMinuteShape,
365 366
    hourMinuteTextColor,
    hourMinuteTextStyle,
367
    inputDecorationTheme,
368 369 370
    padding,
    shape,
  ]);
371 372 373

  @override
  bool operator ==(Object other) {
374
    if (identical(this, other)) {
375
      return true;
376 377
    }
    if (other.runtimeType != runtimeType) {
378
      return false;
379
    }
380 381
    return other is TimePickerThemeData
        && other.backgroundColor == backgroundColor
382 383 384
        && other.cancelButtonStyle == cancelButtonStyle
        && other.confirmButtonStyle == confirmButtonStyle
        && other.dayPeriodBorderSide == dayPeriodBorderSide
385
        && other.dayPeriodColor == dayPeriodColor
386 387 388
        && other.dayPeriodShape == dayPeriodShape
        && other.dayPeriodTextColor == dayPeriodTextColor
        && other.dayPeriodTextStyle == dayPeriodTextStyle
389
        && other.dialBackgroundColor == dialBackgroundColor
390
        && other.dialHandColor == dialHandColor
391
        && other.dialTextColor == dialTextColor
392 393
        && other.dialTextStyle == dialTextStyle
        && other.elevation == elevation
394 395
        && other.entryModeIconColor == entryModeIconColor
        && other.helpTextStyle == helpTextStyle
396
        && other.hourMinuteColor == hourMinuteColor
397
        && other.hourMinuteShape == hourMinuteShape
398 399 400 401 402
        && other.hourMinuteTextColor == hourMinuteTextColor
        && other.hourMinuteTextStyle == hourMinuteTextStyle
        && other.inputDecorationTheme == inputDecorationTheme
        && other.padding == padding
        && other.shape == shape;
403 404 405 406 407 408
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(ColorProperty('backgroundColor', backgroundColor, defaultValue: null));
409 410 411
    properties.add(DiagnosticsProperty<ButtonStyle>('cancelButtonStyle', cancelButtonStyle, defaultValue: null));
    properties.add(DiagnosticsProperty<ButtonStyle>('confirmButtonStyle', confirmButtonStyle, defaultValue: null));
    properties.add(DiagnosticsProperty<BorderSide>('dayPeriodBorderSide', dayPeriodBorderSide, defaultValue: null));
412
    properties.add(ColorProperty('dayPeriodColor', dayPeriodColor, defaultValue: null));
413 414 415
    properties.add(DiagnosticsProperty<ShapeBorder>('dayPeriodShape', dayPeriodShape, defaultValue: null));
    properties.add(ColorProperty('dayPeriodTextColor', dayPeriodTextColor, defaultValue: null));
    properties.add(DiagnosticsProperty<TextStyle>('dayPeriodTextStyle', dayPeriodTextStyle, defaultValue: null));
416
    properties.add(ColorProperty('dialBackgroundColor', dialBackgroundColor, defaultValue: null));
417
    properties.add(ColorProperty('dialHandColor', dialHandColor, defaultValue: null));
418
    properties.add(ColorProperty('dialTextColor', dialTextColor, defaultValue: null));
419 420
    properties.add(DiagnosticsProperty<TextStyle?>('dialTextStyle', dialTextStyle, defaultValue: null));
    properties.add(DoubleProperty('elevation', elevation, defaultValue: null));
421 422
    properties.add(ColorProperty('entryModeIconColor', entryModeIconColor, defaultValue: null));
    properties.add(DiagnosticsProperty<TextStyle>('helpTextStyle', helpTextStyle, defaultValue: null));
423
    properties.add(ColorProperty('hourMinuteColor', hourMinuteColor, defaultValue: null));
424
    properties.add(DiagnosticsProperty<ShapeBorder>('hourMinuteShape', hourMinuteShape, defaultValue: null));
425 426
    properties.add(ColorProperty('hourMinuteTextColor', hourMinuteTextColor, defaultValue: null));
    properties.add(DiagnosticsProperty<TextStyle>('hourMinuteTextStyle', hourMinuteTextStyle, defaultValue: null));
427
    properties.add(DiagnosticsProperty<InputDecorationTheme>('inputDecorationTheme', inputDecorationTheme, defaultValue: null));
428 429
    properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding, defaultValue: null));
    properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null));
430 431 432 433 434 435 436 437 438 439 440 441
  }
}

/// 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({
442
    super.key,
443
    required this.data,
444
    required super.child,
445
  });
446 447 448 449 450 451 452 453 454 455 456 457 458 459 460

  /// 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) {
461
    final TimePickerTheme? timePickerTheme = context.dependOnInheritedWidgetOfExactType<TimePickerTheme>();
462
    return timePickerTheme?.data ?? Theme.of(context).timePickerTheme;
463 464 465 466
  }

  @override
  Widget wrap(BuildContext context, Widget child) {
467
    return TimePickerTheme(data: data, child: child);
468 469 470 471 472
  }

  @override
  bool updateShouldNotify(TimePickerTheme oldWidget) => data != oldWidget.data;
}