time_picker_theme.dart 19 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 75 76 77 78
  /// 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.
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
  ///
  /// If this is null, the time picker defaults to the overall theme's
  /// [ColorScheme.primary].
141
  final Color? dialBackgroundColor;
142

143 144
  /// The color of the time picker dial's hand when the entry mode is
  /// [TimePickerEntryMode.dial] or [TimePickerEntryMode.dialOnly].
145 146 147
  ///
  /// If this is null, the time picker defaults to the overall theme's
  /// [ColorScheme.primary].
148
  final Color? dialHandColor;
149

150 151 152 153 154 155
  /// 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.
  ///
156 157
  /// If this color is null then the dial's text colors are based on the
  /// theme's [ThemeData.colorScheme].
158
  final Color? dialTextColor;
159

160 161 162 163 164 165 166 167 168 169 170 171 172
  /// 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;

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

  /// 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
188
  /// [TextTheme.labelSmall].
189
  final TextStyle? helpTextStyle;
190

191
  /// The background color of the hour and minute header segments.
192
  ///
193 194 195 196 197 198 199 200 201 202 203 204
  /// 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;
205 206 207 208 209

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

212
  /// The color of the header text that represents hours and minutes.
213
  ///
214 215 216
  /// 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.
217
  ///
218 219 220
  /// 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;
221

222
  /// Used to configure the [TextStyle]s for the hour/minute controls.
223
  ///
224 225 226
  /// If this is null, the time picker defaults to the overall theme's
  /// [TextTheme.headline3].
  final TextStyle? hourMinuteTextStyle;
227 228 229 230

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

233 234 235 236 237 238 239 240 241 242
  /// 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;

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

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

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

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

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

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

  /// 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) {
457
    final TimePickerTheme? timePickerTheme = context.dependOnInheritedWidgetOfExactType<TimePickerTheme>();
458
    return timePickerTheme?.data ?? Theme.of(context).timePickerTheme;
459 460 461 462
  }

  @override
  Widget wrap(BuildContext context, Widget child) {
463
    return TimePickerTheme(data: data, child: child);
464 465 466 467 468
  }

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