Migrate the Material Date pickers to M3 Reprise (#119033)
diff --git a/dev/tools/gen_defaults/bin/gen_defaults.dart b/dev/tools/gen_defaults/bin/gen_defaults.dart
index fa156d8..9f71ec6 100644
--- a/dev/tools/gen_defaults/bin/gen_defaults.dart
+++ b/dev/tools/gen_defaults/bin/gen_defaults.dart
@@ -27,6 +27,7 @@
import 'package:gen_defaults/card_template.dart';
import 'package:gen_defaults/checkbox_template.dart';
import 'package:gen_defaults/color_scheme_template.dart';
+import 'package:gen_defaults/date_picker_template.dart';
import 'package:gen_defaults/dialog_template.dart';
import 'package:gen_defaults/divider_template.dart';
import 'package:gen_defaults/drawer_template.dart';
@@ -147,6 +148,7 @@
CardTemplate('Card', '$materialLib/card.dart', tokens).updateFile();
CheckboxTemplate('Checkbox', '$materialLib/checkbox.dart', tokens).updateFile();
ColorSchemeTemplate('ColorScheme', '$materialLib/theme_data.dart', tokens).updateFile();
+ DatePickerTemplate('DatePicker', '$materialLib/date_picker_theme.dart', tokens).updateFile();
DialogFullscreenTemplate('DialogFullscreen', '$materialLib/dialog.dart', tokens).updateFile();
DialogTemplate('Dialog', '$materialLib/dialog.dart', tokens).updateFile();
DividerTemplate('Divider', '$materialLib/divider.dart', tokens).updateFile();
diff --git a/dev/tools/gen_defaults/lib/date_picker_template.dart b/dev/tools/gen_defaults/lib/date_picker_template.dart
new file mode 100644
index 0000000..6b684cf
--- /dev/null
+++ b/dev/tools/gen_defaults/lib/date_picker_template.dart
@@ -0,0 +1,238 @@
+// 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 'template.dart';
+
+class DatePickerTemplate extends TokenTemplate {
+ const DatePickerTemplate(super.blockName, super.fileName, super.tokens, {
+ super.colorSchemePrefix = '_colors.',
+ super.textThemePrefix = '_textTheme.'
+ });
+
+ String _layerOpacity(String layerToken) {
+ if (tokens.containsKey(layerToken)) {
+ final String? layerValue = tokens[layerToken] as String?;
+ if (tokens.containsKey(layerValue)) {
+ final String? opacityValue = opacity(layerValue!);
+ if (opacityValue != null) {
+ return '.withOpacity($opacityValue)';
+ }
+ }
+ }
+ return '';
+ }
+
+ String _stateColor(String componentToken, String? type, String state) {
+ final String baseColor = color(
+ type != null
+ ? '$componentToken.$type.$state.state-layer.color'
+ : '$componentToken.$state.state-layer.color',
+ ''
+ );
+ if (baseColor.isEmpty) {
+ return 'null';
+ }
+ final String opacity = _layerOpacity('$componentToken.$state.state-layer.opacity');
+ return '$baseColor$opacity';
+ }
+
+ @override
+ String generate() => '''
+class _${blockName}DefaultsM3 extends DatePickerThemeData {
+ _${blockName}DefaultsM3(this.context)
+ : super(
+ elevation: ${elevation("md.comp.date-picker.modal.container")},
+ shape: ${shape("md.comp.date-picker.modal.container")},
+ rangePickerElevation: ${elevation("md.comp.date-picker.modal.range-selection.container")},
+ rangePickerShape: ${shape("md.comp.date-picker.modal.range-selection.container")},
+ );
+
+ final BuildContext context;
+ late final ThemeData _theme = Theme.of(context);
+ late final ColorScheme _colors = _theme.colorScheme;
+ late final TextTheme _textTheme = _theme.textTheme;
+
+ @override
+ Color? get backgroundColor => ${componentColor("md.comp.date-picker.modal.container")};
+
+ @override
+ Color? get shadowColor => ${colorOrTransparent("md.comp.date-picker.modal.container.shadow-color")};
+
+ @override
+ Color? get surfaceTintColor => ${colorOrTransparent("md.comp.date-picker.modal.container.surface-tint-layer.color")};
+
+ @override
+ Color? get headerBackgroundColor => ${colorOrTransparent("md.comp.date-picker.modal.header.container.color")};
+
+ @override
+ Color? get headerForegroundColor => ${colorOrTransparent("md.comp.date-picker.modal.header.headline.color")};
+
+ @override
+ TextStyle? get headerHeadlineStyle => ${textStyle("md.comp.date-picker.modal.header.headline")};
+
+ @override
+ TextStyle? get headerHelpStyle => ${textStyle("md.comp.date-picker.modal.header.supporting-text")};
+
+ @override
+ TextStyle? get weekdayStyle => ${textStyle("md.comp.date-picker.modal.weekdays.label-text")}?.apply(
+ color: ${componentColor("md.comp.date-picker.modal.weekdays.label-text")},
+ );
+
+ @override
+ TextStyle? get dayStyle => ${textStyle("md.comp.date-picker.modal.date.label-text")};
+
+ @override
+ MaterialStateProperty<Color?>? get dayForegroundColor =>
+ MaterialStateProperty.resolveWith((Set<MaterialState> states) {
+ if (states.contains(MaterialState.selected)) {
+ return ${componentColor('md.comp.date-picker.modal.date.selected.label-text')};
+ } else if (states.contains(MaterialState.disabled)) {
+ return ${componentColor('md.comp.date-picker.modal.date.unselected.label-text')}.withOpacity(0.38);
+ }
+ return ${componentColor('md.comp.date-picker.modal.date.unselected.label-text')};
+ });
+
+ @override
+ MaterialStateProperty<Color?>? get dayBackgroundColor =>
+ MaterialStateProperty.resolveWith((Set<MaterialState> states) {
+ if (states.contains(MaterialState.selected)) {
+ return ${componentColor('md.comp.date-picker.modal.date.selected.container')};
+ }
+ return ${componentColor('md.comp.date-picker.modal.date.unselected.container')};
+ });
+
+ @override
+ MaterialStateProperty<Color?>? get dayOverlayColor =>
+ MaterialStateProperty.resolveWith((Set<MaterialState> states) {
+ if (states.contains(MaterialState.selected)) {
+ if (states.contains(MaterialState.hovered)) {
+ return ${_stateColor('md.comp.date-picker.modal.date', 'selected', 'hover')};
+ }
+ if (states.contains(MaterialState.focused)) {
+ return ${_stateColor('md.comp.date-picker.modal.date', 'selected', 'focus')};
+ }
+ if (states.contains(MaterialState.pressed)) {
+ return ${_stateColor('md.comp.date-picker.modal.date', 'selected', 'pressed')};
+ }
+ } else {
+ if (states.contains(MaterialState.hovered)) {
+ return ${_stateColor('md.comp.date-picker.modal.date', 'unselected', 'hover')};
+ }
+ if (states.contains(MaterialState.focused)) {
+ return ${_stateColor('md.comp.date-picker.modal.date', 'unselected', 'focus')};
+ }
+ if (states.contains(MaterialState.pressed)) {
+ return ${_stateColor('md.comp.date-picker.modal.date', 'unselected', 'pressed')};
+ }
+ }
+ return null;
+ });
+
+ @override
+ MaterialStateProperty<Color?>? get todayForegroundColor =>
+ MaterialStateProperty.resolveWith((Set<MaterialState> states) {
+ if (states.contains(MaterialState.selected)) {
+ return ${componentColor('md.comp.date-picker.modal.date.selected.label-text')};
+ } else if (states.contains(MaterialState.disabled)) {
+ return ${componentColor('md.comp.date-picker.modal.date.today.label-text')}.withOpacity(0.38);
+ }
+ return ${componentColor('md.comp.date-picker.modal.date.today.label-text')};
+ });
+
+ @override
+ MaterialStateProperty<Color?>? get todayBackgroundColor => dayBackgroundColor;
+
+ @override
+ BorderSide? get todayBorder => ${border('md.comp.date-picker.modal.date.today.container.outline')};
+
+ @override
+ TextStyle? get yearStyle => ${textStyle("md.comp.date-picker.modal.year-selection.year.label-text")};
+
+ @override
+ MaterialStateProperty<Color?>? get yearForegroundColor =>
+ MaterialStateProperty.resolveWith((Set<MaterialState> states) {
+ if (states.contains(MaterialState.selected)) {
+ return ${componentColor('md.comp.date-picker.modal.year-selection.year.selected.label-text')};
+ } else if (states.contains(MaterialState.disabled)) {
+ return ${componentColor('md.comp.date-picker.modal.year-selection.year.unselected.label-text')}.withOpacity(0.38);
+ }
+ return ${componentColor('md.comp.date-picker.modal.year-selection.year.unselected.label-text')};
+ });
+
+ @override
+ MaterialStateProperty<Color?>? get yearBackgroundColor =>
+ MaterialStateProperty.resolveWith((Set<MaterialState> states) {
+ if (states.contains(MaterialState.selected)) {
+ return ${componentColor('md.comp.date-picker.modal.year-selection.year.selected.container')};
+ }
+ return ${componentColor('md.comp.date-picker.modal.year-selection.year.unselected.container')};
+ });
+
+ @override
+ MaterialStateProperty<Color?>? get yearOverlayColor =>
+ MaterialStateProperty.resolveWith((Set<MaterialState> states) {
+ if (states.contains(MaterialState.selected)) {
+ if (states.contains(MaterialState.hovered)) {
+ return ${_stateColor('md.comp.date-picker.modal.year-selection.year', 'selected', 'hover')};
+ }
+ if (states.contains(MaterialState.focused)) {
+ return ${_stateColor('md.comp.date-picker.modal.year-selection.year', 'selected', 'focus')};
+ }
+ if (states.contains(MaterialState.pressed)) {
+ return ${_stateColor('md.comp.date-picker.modal.year-selection.year', 'selected', 'pressed')};
+ }
+ } else {
+ if (states.contains(MaterialState.hovered)) {
+ return ${_stateColor('md.comp.date-picker.modal.year-selection.year', 'unselected', 'hover')};
+ }
+ if (states.contains(MaterialState.focused)) {
+ return ${_stateColor('md.comp.date-picker.modal.year-selection.year', 'unselected', 'focus')};
+ }
+ if (states.contains(MaterialState.pressed)) {
+ return ${_stateColor('md.comp.date-picker.modal.year-selection.year', 'unselected', 'pressed')};
+ }
+ }
+ return null;
+ });
+
+ @override
+ Color? get rangePickerShadowColor => ${colorOrTransparent("md.comp.date-picker.modal.range-selection.container.shadow-color")};
+
+ @override
+ Color? get rangePickerSurfaceTintColor => ${colorOrTransparent("md.comp.date-picker.modal.range-selection.container.surface-tint-layer.color")};
+
+ @override
+ Color? get rangeSelectionBackgroundColor => ${colorOrTransparent("md.comp.date-picker.modal.range-selection.active-indicator.container.color")};
+
+ @override
+ MaterialStateProperty<Color?>? get rangeSelectionOverlayColor =>
+ MaterialStateProperty.resolveWith((Set<MaterialState> states) {
+ if (states.contains(MaterialState.hovered)) {
+ return ${_stateColor('md.comp.date-picker.modal.range-selection.date.in-range.', null, 'hover')};
+ }
+ if (states.contains(MaterialState.focused)) {
+ return ${_stateColor('md.comp.date-picker.modal.range-selection.date.in-range.', null, 'focus')};
+ }
+ if (states.contains(MaterialState.pressed)) {
+ return ${_stateColor('md.comp.date-picker.modal.range-selection.date.in-range.', null, 'pressed')};
+ }
+ return null;
+ });
+
+ @override
+ Color? get rangePickerHeaderBackgroundColor => ${colorOrTransparent("md.comp.date-picker.modal.header.container.color")};
+
+ @override
+ Color? get rangePickerHeaderForegroundColor => ${colorOrTransparent("md.comp.date-picker.modal.header.headline.color")};
+
+ @override
+ TextStyle? get rangePickerHeaderHeadlineStyle => ${textStyle("md.comp.date-picker.modal.range-selection.header.headline")};
+
+ @override
+ TextStyle? get rangePickerHeaderHelpStyle => ${textStyle("md.comp.date-picker.modal.range-selection.month.subhead")};
+
+
+}
+''';
+}
diff --git a/packages/flutter/lib/material.dart b/packages/flutter/lib/material.dart
index 1943379..fb07102 100644
--- a/packages/flutter/lib/material.dart
+++ b/packages/flutter/lib/material.dart
@@ -65,6 +65,7 @@
export 'src/material/data_table_theme.dart';
export 'src/material/date.dart';
export 'src/material/date_picker.dart';
+export 'src/material/date_picker_theme.dart';
export 'src/material/debug.dart';
export 'src/material/desktop_text_selection.dart';
export 'src/material/desktop_text_selection_toolbar.dart';
diff --git a/packages/flutter/lib/src/material/calendar_date_picker.dart b/packages/flutter/lib/src/material/calendar_date_picker.dart
index 189d240..1e49a9b 100644
--- a/packages/flutter/lib/src/material/calendar_date_picker.dart
+++ b/packages/flutter/lib/src/material/calendar_date_picker.dart
@@ -11,12 +11,14 @@
import 'color_scheme.dart';
import 'date.dart';
+import 'date_picker_theme.dart';
import 'debug.dart';
import 'divider.dart';
import 'icon_button.dart';
import 'icons.dart';
import 'ink_well.dart';
import 'material_localizations.dart';
+import 'material_state.dart';
import 'text_theme.dart';
import 'theme.dart';
@@ -279,7 +281,7 @@
firstDate: widget.firstDate,
lastDate: widget.lastDate,
initialDate: _currentDisplayedMonthDate,
- selectedDate: _selectedDate,
+ selectedDate: _currentDisplayedMonthDate,
onChanged: _handleYearChanged,
),
);
@@ -920,18 +922,11 @@
@override
Widget build(BuildContext context) {
- final ColorScheme colorScheme = Theme.of(context).colorScheme;
final MaterialLocalizations localizations = MaterialLocalizations.of(context);
- final TextTheme textTheme = Theme.of(context).textTheme;
- final TextStyle? headerStyle = textTheme.bodySmall?.apply(
- color: colorScheme.onSurface.withOpacity(0.60),
- );
- final TextStyle dayStyle = textTheme.bodySmall!;
- final Color enabledDayColor = colorScheme.onSurface.withOpacity(0.87);
- final Color disabledDayColor = colorScheme.onSurface.withOpacity(0.38);
- final Color selectedDayColor = colorScheme.onPrimary;
- final Color selectedDayBackground = colorScheme.primary;
- final Color todayColor = colorScheme.primary;
+ final DatePickerThemeData datePickerTheme = DatePickerTheme.of(context);
+ final DatePickerThemeData defaults = DatePickerTheme.defaults(context);
+ final TextStyle? weekdayStyle = datePickerTheme.weekdayStyle ?? defaults.weekdayStyle;
+ final TextStyle? dayStyle = datePickerTheme.dayStyle ?? defaults.dayStyle;
final int year = widget.displayedMonth.year;
final int month = widget.displayedMonth.month;
@@ -939,7 +934,19 @@
final int daysInMonth = DateUtils.getDaysInMonth(year, month);
final int dayOffset = DateUtils.firstDayOffset(year, month, localizations);
- final List<Widget> dayItems = _dayHeaders(headerStyle, localizations);
+ T? effectiveValue<T>(T? Function(DatePickerThemeData? theme) getProperty) {
+ return getProperty(datePickerTheme) ?? getProperty(defaults);
+ }
+
+ T? resolve<T>(MaterialStateProperty<T>? Function(DatePickerThemeData? theme) getProperty, Set<MaterialState> states) {
+ return effectiveValue(
+ (DatePickerThemeData? theme) {
+ return getProperty(theme)?.resolve(states);
+ },
+ );
+ }
+
+ final List<Widget> dayItems = _dayHeaders(weekdayStyle, localizations);
// 1-based day of month, e.g. 1-31 for January, and 1-29 for February on
// a leap year.
int day = -dayOffset;
@@ -949,43 +956,42 @@
dayItems.add(Container());
} else {
final DateTime dayToBuild = DateTime(year, month, day);
- final bool isDisabled = dayToBuild.isAfter(widget.lastDate) ||
- dayToBuild.isBefore(widget.firstDate) ||
- (widget.selectableDayPredicate != null && !widget.selectableDayPredicate!(dayToBuild));
+ final bool isDisabled =
+ dayToBuild.isAfter(widget.lastDate) ||
+ dayToBuild.isBefore(widget.firstDate) ||
+ (widget.selectableDayPredicate != null && !widget.selectableDayPredicate!(dayToBuild));
final bool isSelectedDay = DateUtils.isSameDay(widget.selectedDate, dayToBuild);
final bool isToday = DateUtils.isSameDay(widget.currentDate, dayToBuild);
final String semanticLabelSuffix = isToday ? ', ${localizations.currentDateLabel}' : '';
- BoxDecoration? decoration;
- Color dayColor = enabledDayColor;
- if (isSelectedDay) {
- // The selected day gets a circle background highlight, and a
- // contrasting text color.
- dayColor = selectedDayColor;
- decoration = BoxDecoration(
- color: selectedDayBackground,
- shape: BoxShape.circle,
- );
- } else if (isToday) {
- // The current day gets a different text color (if enabled) and a circle stroke
- // border.
- if (isDisabled) {
- dayColor = disabledDayColor;
- } else {
- dayColor = todayColor;
- }
- decoration = BoxDecoration(
- border: Border.all(color: dayColor),
- shape: BoxShape.circle,
- );
- } else if (isDisabled) {
- dayColor = disabledDayColor;
- }
+ final Set<MaterialState> states = <MaterialState>{
+ if (isDisabled) MaterialState.disabled,
+ if (isSelectedDay) MaterialState.selected,
+ };
+
+ final Color? dayForegroundColor = resolve<Color?>((DatePickerThemeData? theme) => isToday ? theme?.todayForegroundColor : theme?.dayForegroundColor, states);
+ final Color? dayBackgroundColor = resolve<Color?>((DatePickerThemeData? theme) => isToday ? theme?.todayBackgroundColor : theme?.dayBackgroundColor, states);
+ final MaterialStateProperty<Color?> dayOverlayColor = MaterialStateProperty.resolveWith<Color?>(
+ (Set<MaterialState> states) => effectiveValue((DatePickerThemeData? theme) => theme?.dayOverlayColor?.resolve(states)),
+ );
+ final BoxDecoration decoration = isToday
+ ? BoxDecoration(
+ color: dayBackgroundColor,
+ border: Border.fromBorderSide(
+ (datePickerTheme.todayBorder ?? defaults.todayBorder!)
+ .copyWith(color: dayForegroundColor)
+ ),
+ shape: BoxShape.circle,
+ )
+ : BoxDecoration(
+ color: dayBackgroundColor,
+ shape: BoxShape.circle,
+ );
Widget dayWidget = Container(
decoration: decoration,
child: Center(
- child: Text(localizations.formatDecimal(day), style: dayStyle.apply(color: dayColor)),
+ child: Text(localizations.formatDecimal(day), style: dayStyle?.apply(color: dayForegroundColor)),
),
);
@@ -998,7 +1004,8 @@
focusNode: _dayFocusNodes[day - 1],
onTap: () => widget.onChanged(dayToBuild),
radius: _dayPickerRowHeight / 2 + 4,
- splashColor: selectedDayBackground.withOpacity(0.38),
+ statesController: MaterialStatesController(states),
+ overlayColor: dayOverlayColor,
child: Semantics(
// We want the day of month to be spoken first irrespective of the
// locale-specific preferences or TextDirection. This is because
@@ -1150,8 +1157,20 @@
}
Widget _buildYearItem(BuildContext context, int index) {
- final ColorScheme colorScheme = Theme.of(context).colorScheme;
- final TextTheme textTheme = Theme.of(context).textTheme;
+ final DatePickerThemeData datePickerTheme = DatePickerTheme.of(context);
+ final DatePickerThemeData defaults = DatePickerTheme.defaults(context);
+
+ T? effectiveValue<T>(T? Function(DatePickerThemeData? theme) getProperty) {
+ return getProperty(datePickerTheme) ?? getProperty(defaults);
+ }
+
+ T? resolve<T>(MaterialStateProperty<T>? Function(DatePickerThemeData? theme) getProperty, Set<MaterialState> states) {
+ return effectiveValue(
+ (DatePickerThemeData? theme) {
+ return getProperty(theme)?.resolve(states);
+ },
+ );
+ }
// Backfill the _YearPicker with disabled years if necessary.
final int offset = _itemCount < minYears ? (minYears - _itemCount) ~/ 2 : 0;
@@ -1162,33 +1181,32 @@
const double decorationHeight = 36.0;
const double decorationWidth = 72.0;
- final Color textColor;
- if (isSelected) {
- textColor = colorScheme.onPrimary;
- } else if (isDisabled) {
- textColor = colorScheme.onSurface.withOpacity(0.38);
- } else if (isCurrentYear) {
- textColor = colorScheme.primary;
- } else {
- textColor = colorScheme.onSurface.withOpacity(0.87);
- }
- final TextStyle? itemStyle = textTheme.bodyLarge?.apply(color: textColor);
+ final Set<MaterialState> states = <MaterialState>{
+ if (isDisabled) MaterialState.disabled,
+ if (isSelected) MaterialState.selected,
+ };
- BoxDecoration? decoration;
- if (isSelected) {
- decoration = BoxDecoration(
- color: colorScheme.primary,
- borderRadius: BorderRadius.circular(decorationHeight / 2),
+ final Color? textColor = resolve<Color?>((DatePickerThemeData? theme) => isCurrentYear ? theme?.todayForegroundColor : theme?.yearForegroundColor, states);
+ final Color? background = resolve<Color?>((DatePickerThemeData? theme) => isCurrentYear ? theme?.todayBackgroundColor : theme?.yearBackgroundColor, states);
+ final MaterialStateProperty<Color?> overlayColor =
+ MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) =>
+ effectiveValue((DatePickerThemeData? theme) => theme?.dayOverlayColor?.resolve(states)),
);
- } else if (isCurrentYear && !isDisabled) {
- decoration = BoxDecoration(
- border: Border.all(
- color: colorScheme.primary,
- ),
- borderRadius: BorderRadius.circular(decorationHeight / 2),
- );
- }
+ BoxBorder? border;
+ if (isCurrentYear) {
+ final BorderSide? todayBorder = datePickerTheme.todayBorder ?? defaults.todayBorder;
+ if (todayBorder != null) {
+ border = Border.fromBorderSide(todayBorder.copyWith(color: textColor));
+ }
+ }
+ final BoxDecoration decoration = BoxDecoration(
+ border: border,
+ color: background,
+ borderRadius: BorderRadius.circular(decorationHeight / 2),
+ );
+
+ final TextStyle? itemStyle = (datePickerTheme.yearStyle ?? defaults.yearStyle)?.apply(color: textColor);
Widget yearItem = Center(
child: Container(
decoration: decoration,
@@ -1212,6 +1230,8 @@
yearItem = InkWell(
key: ValueKey<int>(year),
onTap: () => widget.onChanged(DateTime(year, widget.initialDate.month)),
+ statesController: MaterialStatesController(states),
+ overlayColor: overlayColor,
child: yearItem,
);
}
diff --git a/packages/flutter/lib/src/material/date_picker.dart b/packages/flutter/lib/src/material/date_picker.dart
index cb963f5..db9fd8b 100644
--- a/packages/flutter/lib/src/material/date_picker.dart
+++ b/packages/flutter/lib/src/material/date_picker.dart
@@ -10,9 +10,12 @@
import 'app_bar.dart';
import 'back_button.dart';
+import 'button_style.dart';
import 'calendar_date_picker.dart';
import 'color_scheme.dart';
+import 'colors.dart';
import 'date.dart';
+import 'date_picker_theme.dart';
import 'debug.dart';
import 'dialog.dart';
import 'dialog_theme.dart';
@@ -25,15 +28,20 @@
import 'input_decorator.dart';
import 'material.dart';
import 'material_localizations.dart';
+import 'material_state.dart';
import 'scaffold.dart';
import 'text_button.dart';
import 'text_field.dart';
import 'text_theme.dart';
import 'theme.dart';
-const Size _calendarPortraitDialogSize = Size(330.0, 518.0);
+// The M3 sizes are coming from the tokens, but are hand coded,
+// as the current token DB does not contain landscape versions.
+const Size _calendarPortraitDialogSizeM2 = Size(330.0, 518.0);
+const Size _calendarPortraitDialogSizeM3 = Size(328.0, 512.0);
const Size _calendarLandscapeDialogSize = Size(496.0, 346.0);
-const Size _inputPortraitDialogSize = Size(330.0, 270.0);
+const Size _inputPortraitDialogSizeM2 = Size(330.0, 270.0);
+const Size _inputPortraitDialogSizeM3 = Size(328.0, 270.0);
const Size _inputLandscapeDialogSize = Size(496, 160.0);
const Size _inputRangeLandscapeDialogSize = Size(496, 164.0);
const Duration _dialogSizeAnimationDuration = Duration(milliseconds: 200);
@@ -412,13 +420,15 @@
}
Size _dialogSize(BuildContext context) {
+ final bool useMaterial3 = Theme.of(context).useMaterial3;
final Orientation orientation = MediaQuery.orientationOf(context);
+
switch (_entryMode.value) {
case DatePickerEntryMode.calendar:
case DatePickerEntryMode.calendarOnly:
switch (orientation) {
case Orientation.portrait:
- return _calendarPortraitDialogSize;
+ return useMaterial3 ? _calendarPortraitDialogSizeM3 : _calendarPortraitDialogSizeM2;
case Orientation.landscape:
return _calendarLandscapeDialogSize;
}
@@ -426,7 +436,7 @@
case DatePickerEntryMode.inputOnly:
switch (orientation) {
case Orientation.portrait:
- return _inputPortraitDialogSize;
+ return useMaterial3 ? _inputPortraitDialogSizeM3 : _inputPortraitDialogSizeM2;
case Orientation.landscape:
return _inputLandscapeDialogSize;
}
@@ -441,21 +451,28 @@
@override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
- final ColorScheme colorScheme = theme.colorScheme;
+ final bool useMaterial3 = theme.useMaterial3;
final MaterialLocalizations localizations = MaterialLocalizations.of(context);
final Orientation orientation = MediaQuery.orientationOf(context);
+ final DatePickerThemeData datePickerTheme = DatePickerTheme.of(context);
+ final DatePickerThemeData defaults = DatePickerTheme.defaults(context);
final TextTheme textTheme = theme.textTheme;
+
// Constrain the textScaleFactor to the largest supported value to prevent
// layout issues.
final double textScaleFactor = math.min(MediaQuery.textScaleFactorOf(context), 1.3);
+ final Color? headerForegroundColor = datePickerTheme.headerForegroundColor ?? defaults.headerForegroundColor;
+ final TextStyle? headlineStyle = useMaterial3
+ ? (datePickerTheme.headerHeadlineStyle ?? defaults.headerHeadlineStyle)?.copyWith(
+ color: headerForegroundColor,
+ )
+ // Material2 has support for landscape and the current M3 spec doesn't
+ // address this layout, so handling it seperately here.
+ : (orientation == Orientation.landscape
+ ? textTheme.headlineSmall?.copyWith(color: headerForegroundColor)
+ : textTheme.headlineMedium?.copyWith(color: headerForegroundColor));
final String dateText = localizations.formatMediumDate(_selectedDate.value);
- final Color onPrimarySurface = colorScheme.brightness == Brightness.light
- ? colorScheme.onPrimary
- : colorScheme.onSurface;
- final TextStyle? dateStyle = orientation == Orientation.landscape
- ? textTheme.headlineSmall?.copyWith(color: onPrimarySurface)
- : textTheme.headlineMedium?.copyWith(color: onPrimarySurface);
final Widget actions = Container(
alignment: AlignmentDirectional.centerEnd,
@@ -467,7 +484,7 @@
TextButton(
onPressed: _handleCancel,
child: Text(widget.cancelText ?? (
- theme.useMaterial3
+ useMaterial3
? localizations.cancelButtonLabel
: localizations.cancelButtonLabel.toUpperCase()
)),
@@ -533,8 +550,8 @@
case DatePickerEntryMode.calendar:
picker = calendarDatePicker();
entryModeButton = IconButton(
- icon: const Icon(Icons.edit),
- color: onPrimarySurface,
+ icon: Icon(useMaterial3 ? Icons.edit_outlined : Icons.edit),
+ color: headerForegroundColor,
tooltip: localizations.inputDateModeButtonLabel,
onPressed: _handleEntryModeToggle,
);
@@ -549,7 +566,7 @@
picker = inputDatePicker();
entryModeButton = IconButton(
icon: const Icon(Icons.calendar_today),
- color: onPrimarySurface,
+ color: headerForegroundColor,
tooltip: localizations.calendarModeButtonLabel,
onPressed: _handleEntryModeToggle,
);
@@ -563,19 +580,29 @@
final Widget header = _DatePickerHeader(
helpText: widget.helpText ?? (
- Theme.of(context).useMaterial3
+ useMaterial3
? localizations.datePickerHelpText
: localizations.datePickerHelpText.toUpperCase()
),
titleText: dateText,
- titleStyle: dateStyle,
+ titleStyle: headlineStyle,
orientation: orientation,
isShort: orientation == Orientation.landscape,
entryModeButton: entryModeButton,
);
final Size dialogSize = _dialogSize(context) * textScaleFactor;
+ final DialogTheme dialogTheme = theme.dialogTheme;
return Dialog(
+ backgroundColor: datePickerTheme.backgroundColor ?? defaults.backgroundColor,
+ elevation: useMaterial3
+ ? datePickerTheme.elevation ?? defaults.elevation!
+ : datePickerTheme.elevation ?? dialogTheme.elevation ?? 24,
+ shadowColor: datePickerTheme.shadowColor ?? defaults.shadowColor,
+ surfaceTintColor: datePickerTheme.surfaceTintColor ?? defaults.surfaceTintColor,
+ shape: useMaterial3
+ ? datePickerTheme.shape ?? defaults.shape
+ : datePickerTheme.shape ?? dialogTheme.shape ?? defaults.shape,
insetPadding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 24.0),
clipBehavior: Clip.antiAlias,
child: AnimatedContainer(
@@ -595,6 +622,7 @@
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
header,
+ if (useMaterial3) const Divider(),
Expanded(child: picker),
actions,
],
@@ -605,6 +633,7 @@
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
header,
+ if (useMaterial3) const VerticalDivider(),
Flexible(
child: Column(
mainAxisSize: MainAxisSize.min,
@@ -736,17 +765,12 @@
@override
Widget build(BuildContext context) {
- final ThemeData theme = Theme.of(context);
- final ColorScheme colorScheme = theme.colorScheme;
- final TextTheme textTheme = theme.textTheme;
-
- // The header should use the primary color in light themes and surface color in dark
- final bool isDark = colorScheme.brightness == Brightness.dark;
- final Color primarySurfaceColor = isDark ? colorScheme.surface : colorScheme.primary;
- final Color onPrimarySurfaceColor = isDark ? colorScheme.onSurface : colorScheme.onPrimary;
-
- final TextStyle? helpStyle = textTheme.labelSmall?.copyWith(
- color: onPrimarySurfaceColor,
+ final DatePickerThemeData themeData = DatePickerTheme.of(context);
+ final DatePickerThemeData defaults = DatePickerTheme.defaults(context);
+ final Color? backgroundColor = themeData.headerBackgroundColor ?? defaults.headerBackgroundColor;
+ final Color? foregroundColor = themeData.headerForegroundColor ?? defaults.headerForegroundColor;
+ final TextStyle? helpStyle = (themeData.headerHelpStyle ?? defaults.headerHelpStyle)?.copyWith(
+ color: foregroundColor,
);
final Text help = Text(
@@ -768,7 +792,7 @@
return SizedBox(
height: _datePickerHeaderPortraitHeight,
child: Material(
- color: primarySurfaceColor,
+ color: backgroundColor,
child: Padding(
padding: const EdgeInsetsDirectional.only(
start: 24,
@@ -796,7 +820,7 @@
return SizedBox(
width: _datePickerHeaderLandscapeWidth,
child: Material(
- color: primarySurfaceColor,
+ color: backgroundColor,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
@@ -1292,18 +1316,20 @@
@override
Widget build(BuildContext context) {
+ final ThemeData theme = Theme.of(context);
+ final bool useMaterial3 = theme.useMaterial3;
final Orientation orientation = MediaQuery.orientationOf(context);
final double textScaleFactor = math.min(MediaQuery.textScaleFactorOf(context), 1.3);
final MaterialLocalizations localizations = MaterialLocalizations.of(context);
- final ColorScheme colors = Theme.of(context).colorScheme;
- final Color onPrimarySurface = colors.brightness == Brightness.light
- ? colors.onPrimary
- : colors.onSurface;
+ final DatePickerThemeData datePickerTheme = DatePickerTheme.of(context);
+ final DatePickerThemeData defaults = DatePickerTheme.defaults(context);
final Widget contents;
final Size size;
- ShapeBorder? shape;
- final double elevation;
+ final double? elevation;
+ final Color? shadowColor;
+ final Color? surfaceTintColor;
+ final ShapeBorder? shape;
final EdgeInsets insetPadding;
final bool showEntryModeButton =
_entryMode.value == DatePickerEntryMode.calendar ||
@@ -1324,28 +1350,29 @@
onCancel: _handleCancel,
entryModeButton: showEntryModeButton
? IconButton(
- icon: const Icon(Icons.edit),
+ icon: Icon(useMaterial3 ? Icons.edit_outlined : Icons.edit),
padding: EdgeInsets.zero,
- color: onPrimarySurface,
tooltip: localizations.inputDateModeButtonLabel,
onPressed: _handleEntryModeToggle,
)
: null,
confirmText: widget.saveText ?? (
- Theme.of(context).useMaterial3
+ useMaterial3
? localizations.saveButtonLabel
: localizations.saveButtonLabel.toUpperCase()
),
helpText: widget.helpText ?? (
- Theme.of(context).useMaterial3
+ useMaterial3
? localizations.dateRangePickerHelpText
: localizations.dateRangePickerHelpText.toUpperCase()
),
);
size = MediaQuery.sizeOf(context);
insetPadding = EdgeInsets.zero;
- shape = const RoundedRectangleBorder();
- elevation = 0;
+ elevation = datePickerTheme.rangePickerElevation ?? defaults.rangePickerElevation!;
+ shadowColor = datePickerTheme.rangePickerShadowColor ?? defaults.rangePickerShadowColor!;
+ surfaceTintColor = datePickerTheme.rangePickerSurfaceTintColor ?? defaults.rangePickerSurfaceTintColor!;
+ shape = datePickerTheme.rangePickerShape ?? defaults.rangePickerShape;
break;
case DatePickerEntryMode.input:
@@ -1391,35 +1418,46 @@
? IconButton(
icon: const Icon(Icons.calendar_today),
padding: EdgeInsets.zero,
- color: onPrimarySurface,
tooltip: localizations.calendarModeButtonLabel,
onPressed: _handleEntryModeToggle,
)
: null,
confirmText: widget.confirmText ?? localizations.okButtonLabel,
cancelText: widget.cancelText ?? (
- Theme.of(context).useMaterial3
+ useMaterial3
? localizations.cancelButtonLabel
: localizations.cancelButtonLabel.toUpperCase()
),
helpText: widget.helpText ?? (
- Theme.of(context).useMaterial3
+ useMaterial3
? localizations.dateRangePickerHelpText
: localizations.dateRangePickerHelpText.toUpperCase()
),
);
- final DialogTheme dialogTheme = Theme.of(context).dialogTheme;
- size = orientation == Orientation.portrait ? _inputPortraitDialogSize : _inputRangeLandscapeDialogSize;
+ final DialogTheme dialogTheme = theme.dialogTheme;
+ size = orientation == Orientation.portrait
+ ? (useMaterial3 ? _inputPortraitDialogSizeM3 : _inputPortraitDialogSizeM2)
+ : _inputRangeLandscapeDialogSize;
+ elevation = useMaterial3
+ ? datePickerTheme.elevation ?? defaults.elevation!
+ : datePickerTheme.elevation ?? dialogTheme.elevation ?? 24;
+ shadowColor = datePickerTheme.shadowColor ?? defaults.shadowColor;
+ surfaceTintColor = datePickerTheme.surfaceTintColor ?? defaults.surfaceTintColor;
+ shape = useMaterial3
+ ? datePickerTheme.shape ?? defaults.shape
+ : datePickerTheme.shape ?? dialogTheme.shape ?? defaults.shape;
+
insetPadding = const EdgeInsets.symmetric(horizontal: 16.0, vertical: 24.0);
- shape = dialogTheme.shape;
- elevation = dialogTheme.elevation ?? 24;
break;
}
return Dialog(
insetPadding: insetPadding,
- shape: shape,
+ backgroundColor: datePickerTheme.backgroundColor ?? defaults.backgroundColor,
elevation: elevation,
+ shadowColor: shadowColor,
+ surfaceTintColor: surfaceTintColor,
+ shape: shape,
clipBehavior: Clip.antiAlias,
child: AnimatedContainer(
width: size.width,
@@ -1472,26 +1510,29 @@
@override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
- final ColorScheme colorScheme = theme.colorScheme;
+ final bool useMaterial3 = theme.useMaterial3;
final MaterialLocalizations localizations = MaterialLocalizations.of(context);
final Orientation orientation = MediaQuery.orientationOf(context);
- final TextTheme textTheme = theme.textTheme;
- final Color headerForeground = colorScheme.brightness == Brightness.light
- ? colorScheme.onPrimary
- : colorScheme.onSurface;
- final Color headerDisabledForeground = headerForeground.withOpacity(0.38);
+ final DatePickerThemeData themeData = DatePickerTheme.of(context);
+ final DatePickerThemeData defaults = DatePickerTheme.defaults(context);
+ final Color? dialogBackground = themeData.rangePickerBackgroundColor ?? defaults.rangePickerBackgroundColor;
+ final Color? headerForeground = themeData.rangePickerHeaderForegroundColor ?? defaults.rangePickerHeaderForegroundColor;
+ final Color? headerDisabledForeground = headerForeground?.withOpacity(0.38);
+ final TextStyle? headlineStyle = themeData.rangePickerHeaderHeadlineStyle ?? defaults.rangePickerHeaderHeadlineStyle;
+ final TextStyle? headlineHelpStyle = (themeData.rangePickerHeaderHelpStyle ?? defaults.rangePickerHeaderHelpStyle)?.apply(color: headerForeground);
final String startDateText = _formatRangeStartDate(localizations, selectedStartDate, selectedEndDate);
final String endDateText = _formatRangeEndDate(localizations, selectedStartDate, selectedEndDate, DateTime.now());
- final TextStyle? headlineStyle = textTheme.headlineSmall;
final TextStyle? startDateStyle = headlineStyle?.apply(
color: selectedStartDate != null ? headerForeground : headerDisabledForeground,
);
final TextStyle? endDateStyle = headlineStyle?.apply(
color: selectedEndDate != null ? headerForeground : headerDisabledForeground,
);
- final TextStyle saveButtonStyle = textTheme.labelLarge!.apply(
- color: onConfirm != null ? headerForeground : headerDisabledForeground,
+ final ButtonStyle buttonStyle = TextButton.styleFrom(
+ foregroundColor: headerForeground,
+ disabledForegroundColor: headerDisabledForeground
);
+ final IconThemeData iconTheme = IconThemeData(color: headerForeground);
return SafeArea(
top: false,
@@ -1499,6 +1540,11 @@
right: false,
child: Scaffold(
appBar: AppBar(
+ iconTheme: iconTheme,
+ actionsIconTheme: iconTheme,
+ elevation: useMaterial3 ? 0 : null,
+ scrolledUnderElevation: useMaterial3 ? 0 : null,
+ backgroundColor: useMaterial3 ? Colors.transparent : null,
leading: CloseButton(
onPressed: onCancel,
),
@@ -1506,8 +1552,9 @@
if (orientation == Orientation.landscape && entryModeButton != null)
entryModeButton!,
TextButton(
+ style: buttonStyle,
onPressed: onConfirm,
- child: Text(confirmText, style: saveButtonStyle),
+ child: Text(confirmText),
),
const SizedBox(width: 8),
],
@@ -1522,12 +1569,7 @@
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
- Text(
- helpText,
- style: textTheme.labelSmall!.apply(
- color: headerForeground,
- ),
- ),
+ Text(helpText, style: headlineHelpStyle),
const SizedBox(height: 8),
Row(
children: <Widget>[
@@ -1557,11 +1599,15 @@
if (orientation == Orientation.portrait && entryModeButton != null)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
- child: entryModeButton,
+ child: IconTheme(
+ data: iconTheme,
+ child: entryModeButton!,
+ ),
),
]),
),
),
+ backgroundColor: dialogBackground,
body: _CalendarDateRangePicker(
initialStartDate: selectedStartDate,
initialEndDate: selectedEndDate,
@@ -2201,7 +2247,8 @@
}
Color _highlightColor(BuildContext context) {
- return Theme.of(context).colorScheme.primary.withOpacity(0.12);
+ return DatePickerTheme.of(context).rangeSelectionBackgroundColor
+ ?? DatePickerTheme.defaults(context).rangeSelectionBackgroundColor!;
}
void _dayFocusChanged(bool focused) {
@@ -2232,6 +2279,8 @@
final ColorScheme colorScheme = theme.colorScheme;
final TextTheme textTheme = theme.textTheme;
final MaterialLocalizations localizations = MaterialLocalizations.of(context);
+ final DatePickerThemeData datePickerTheme = DatePickerTheme.of(context);
+ final DatePickerThemeData defaults = DatePickerTheme.defaults(context);
final TextDirection textDirection = Directionality.of(context);
final Color highlightColor = _highlightColor(context);
final int day = dayToBuild.day;
@@ -2248,14 +2297,42 @@
dayToBuild.isAfter(widget.selectedDateStart!) &&
dayToBuild.isBefore(widget.selectedDateEnd!);
+ T? effectiveValue<T>(T? Function(DatePickerThemeData? theme) getProperty) {
+ return getProperty(datePickerTheme) ?? getProperty(defaults);
+ }
+
+ T? resolve<T>(MaterialStateProperty<T>? Function(DatePickerThemeData? theme) getProperty, Set<MaterialState> states) {
+ return effectiveValue(
+ (DatePickerThemeData? theme) {
+ return getProperty(theme)?.resolve(states);
+ },
+ );
+ }
+
+ final Set<MaterialState> states = <MaterialState>{
+ if (isDisabled) MaterialState.disabled,
+ if (isSelectedDayStart || isSelectedDayEnd) MaterialState.selected,
+ };
+
+ final Color? dayForegroundColor = resolve<Color?>((DatePickerThemeData? theme) => theme?.dayForegroundColor, states);
+ final Color? dayBackgroundColor = resolve<Color?>((DatePickerThemeData? theme) => theme?.dayBackgroundColor, states);
+ final MaterialStateProperty<Color?> dayOverlayColor = MaterialStateProperty.resolveWith<Color?>(
+ (Set<MaterialState> states) => effectiveValue(
+ (DatePickerThemeData? theme) =>
+ isInRange
+ ? theme?.rangeSelectionOverlayColor?.resolve(states)
+ : theme?.dayOverlayColor?.resolve(states),
+ )
+ );
+
_HighlightPainter? highlightPainter;
if (isSelectedDayStart || isSelectedDayEnd) {
// The selected start and end dates gets a circle background
// highlight, and a contrasting text color.
- itemStyle = textTheme.bodyMedium?.apply(color: colorScheme.onPrimary);
+ itemStyle = textTheme.bodyMedium?.apply(color: dayForegroundColor);
decoration = BoxDecoration(
- color: colorScheme.primary,
+ color: dayBackgroundColor,
shape: BoxShape.circle,
);
@@ -2327,7 +2404,8 @@
focusNode: _dayFocusNodes[day - 1],
onTap: () => widget.onChanged(dayToBuild),
radius: _monthItemRowHeight / 2 + 4,
- splashColor: colorScheme.primary.withOpacity(0.38),
+ statesController: MaterialStatesController(states),
+ overlayColor: dayOverlayColor,
onFocusChange: _dayFocusChanged,
child: dayWidget,
);
@@ -2350,8 +2428,7 @@
final int daysInMonth = DateUtils.getDaysInMonth(year, month);
final int dayOffset = DateUtils.firstDayOffset(year, month, localizations);
final int weeks = ((daysInMonth + dayOffset) / DateTime.daysPerWeek).ceil();
- final double gridHeight =
- weeks * _monthItemRowHeight + (weeks - 1) * _monthItemSpaceBetweenRows;
+ final double gridHeight = weeks * _monthItemRowHeight + (weeks - 1) * _monthItemSpaceBetweenRows;
final List<Widget> dayItems = <Widget>[];
for (int i = 0; true; i += 1) {
@@ -2566,18 +2643,17 @@
@override
Widget build(BuildContext context) {
- final ThemeData theme = Theme.of(context);
- final ColorScheme colorScheme = theme.colorScheme;
+ final bool useMaterial3 = Theme.of(context).useMaterial3;
final MaterialLocalizations localizations = MaterialLocalizations.of(context);
final Orientation orientation = MediaQuery.orientationOf(context);
- final TextTheme textTheme = theme.textTheme;
+ final DatePickerThemeData datePickerTheme = DatePickerTheme.of(context);
+ final DatePickerThemeData defaults = DatePickerTheme.defaults(context);
- final Color onPrimarySurfaceColor = colorScheme.brightness == Brightness.light
- ? colorScheme.onPrimary
- : colorScheme.onSurface;
- final TextStyle? dateStyle = orientation == Orientation.landscape
- ? textTheme.headlineSmall?.apply(color: onPrimarySurfaceColor)
- : textTheme.headlineMedium?.apply(color: onPrimarySurfaceColor);
+ final Color? headerForegroundColor = datePickerTheme.headerForegroundColor ?? defaults.headerForegroundColor;
+ final TextStyle? headlineStyle = (datePickerTheme.headerHeadlineStyle ?? defaults.headerHeadlineStyle)?.copyWith(
+ color: headerForegroundColor,
+ );
+
final String dateText = _formatDateRange(context, selectedStartDate, selectedEndDate, currentDate!);
final String semanticDateText = selectedStartDate != null && selectedEndDate != null
? '${localizations.formatMediumDate(selectedStartDate!)} – ${localizations.formatMediumDate(selectedEndDate!)}'
@@ -2585,13 +2661,13 @@
final Widget header = _DatePickerHeader(
helpText: helpText ?? (
- Theme.of(context).useMaterial3
+ useMaterial3
? localizations.dateRangePickerHelpText
: localizations.dateRangePickerHelpText.toUpperCase()
),
titleText: dateText,
titleSemanticsLabel: semanticDateText,
- titleStyle: dateStyle,
+ titleStyle: headlineStyle,
orientation: orientation,
isShort: orientation == Orientation.landscape,
entryModeButton: entryModeButton,
@@ -2607,7 +2683,7 @@
TextButton(
onPressed: onCancel,
child: Text(cancelText ?? (
- theme.useMaterial3
+ useMaterial3
? localizations.cancelButtonLabel
: localizations.cancelButtonLabel.toUpperCase()
)),
@@ -2856,8 +2932,13 @@
@override
Widget build(BuildContext context) {
+ final ThemeData theme = Theme.of(context);
+ final bool useMaterial3 = theme.useMaterial3;
final MaterialLocalizations localizations = MaterialLocalizations.of(context);
- final InputDecorationTheme inputTheme = Theme.of(context).inputDecorationTheme;
+ final InputDecorationTheme inputTheme = theme.inputDecorationTheme;
+ final InputBorder inputBorder = inputTheme.border
+ ?? (useMaterial3 ? const OutlineInputBorder() : const UnderlineInputBorder());
+
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
@@ -2865,7 +2946,7 @@
child: TextField(
controller: _startController,
decoration: InputDecoration(
- border: inputTheme.border ?? const UnderlineInputBorder(),
+ border: inputBorder,
filled: inputTheme.filled,
hintText: widget.fieldStartHintText ?? localizations.dateHelpText,
labelText: widget.fieldStartLabelText ?? localizations.dateRangeStartLabel,
@@ -2881,7 +2962,7 @@
child: TextField(
controller: _endController,
decoration: InputDecoration(
- border: inputTheme.border ?? const UnderlineInputBorder(),
+ border: inputBorder,
filled: inputTheme.filled,
hintText: widget.fieldEndHintText ?? localizations.dateHelpText,
labelText: widget.fieldEndLabelText ?? localizations.dateRangeEndLabel,
diff --git a/packages/flutter/lib/src/material/date_picker_theme.dart b/packages/flutter/lib/src/material/date_picker_theme.dart
new file mode 100644
index 0000000..0f6de36
--- /dev/null
+++ b/packages/flutter/lib/src/material/date_picker_theme.dart
@@ -0,0 +1,975 @@
+// 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' show lerpDouble;
+
+import 'package:flutter/foundation.dart';
+import 'package:flutter/widgets.dart';
+
+import 'color_scheme.dart';
+import 'colors.dart';
+import 'material_state.dart';
+import 'text_theme.dart';
+import 'theme.dart';
+
+// Examples can assume:
+// late BuildContext context;
+
+/// Overrides the default values of visual properties for descendant
+/// [DatePickerDialog] widgets.
+///
+/// Descendant widgets obtain the current [DatePickerThemeData] object with
+/// [DatePickerTheme.of]. Instances of [DatePickerTheme] can
+/// be customized with [DatePickerThemeData.copyWith].
+///
+/// Typically a [DatePickerTheme] is specified as part of the overall
+/// [Theme] with [ThemeData.datePickerTheme].
+///
+/// All [DatePickerThemeData] properties are null by default. When null,
+/// the [DatePickerDialog] computes its own default values, typically based on
+/// the overall theme's [ThemeData.colorScheme], [ThemeData.textTheme], and
+/// [ThemeData.iconTheme].
+@immutable
+class DatePickerThemeData with Diagnosticable {
+ /// Creates a [DatePickerThemeData] that can be used to override default properties
+ /// in a [DatePickerTheme] widget.
+ const DatePickerThemeData({
+ this.backgroundColor,
+ this.elevation,
+ this.shadowColor,
+ this.surfaceTintColor,
+ this.shape,
+ this.headerBackgroundColor,
+ this.headerForegroundColor,
+ this.headerHeadlineStyle,
+ this.headerHelpStyle,
+ this.weekdayStyle,
+ this.dayStyle,
+ this.dayForegroundColor,
+ this.dayBackgroundColor,
+ this.dayOverlayColor,
+ this.todayForegroundColor,
+ this.todayBackgroundColor,
+ this.todayBorder,
+ this.yearStyle,
+ this.yearForegroundColor,
+ this.yearBackgroundColor,
+ this.yearOverlayColor,
+ this.rangePickerBackgroundColor,
+ this.rangePickerElevation,
+ this.rangePickerShadowColor,
+ this.rangePickerSurfaceTintColor,
+ this.rangePickerShape,
+ this.rangePickerHeaderBackgroundColor,
+ this.rangePickerHeaderForegroundColor,
+ this.rangePickerHeaderHeadlineStyle,
+ this.rangePickerHeaderHelpStyle,
+ this.rangeSelectionBackgroundColor,
+ this.rangeSelectionOverlayColor,
+ });
+
+ /// Overrides the default value of [Dialog.backgroundColor].
+ final Color? backgroundColor;
+
+ /// Overrides the default value of [Dialog.elevation].
+ ///
+ /// See also:
+ /// [Material.elevation], which explains how elevation is related to a component's shadow.
+ final double? elevation;
+
+ /// Overrides the default value of [Dialog.shadowColor].
+ ///
+ /// See also:
+ /// [Material.shadowColor], which explains how the shadow is rendered.
+ final Color? shadowColor;
+
+ /// Overrides the default value of [Dialog.surfaceTintColor].
+ ///
+ /// See also:
+ /// [Material.surfaceTintColor], which explains how this color is related to
+ /// [elevation] and [backgroundColor].
+ final Color? surfaceTintColor;
+
+ /// Overrides the default value of [Dialog.shape].
+ ///
+ /// If [elevation] is greater than zero then a shadow is shown and the shadow's
+ /// shape mirrors the shape of the dialog.
+ final ShapeBorder? shape;
+
+ /// Overrides the header's default background fill color.
+ ///
+ /// The dialog's header displays the currently selected date.
+ final Color? headerBackgroundColor;
+
+ /// Overrides the header's default color used for text labels and icons.
+ ///
+ /// The dialog's header displays the currently selected date.
+ ///
+ /// This is used instead of the [TextStyle.color] property of [headerHeadlineStyle]
+ /// and [headerHelpStyle].
+ final Color? headerForegroundColor;
+
+ /// Overrides the header's default headline text style.
+ ///
+ /// The dialog's header displays the currently selected date.
+ ///
+ /// The [TextStyle.color] of the [headerHeadlineStyle] is not used,
+ /// [headerForegroundColor] is used instead.
+ final TextStyle? headerHeadlineStyle;
+
+ /// Overrides the header's default help text style.
+ ///
+ /// The help text (also referred to as "supporting text" in the Material
+ /// spec) is usually a prompt to the user at the top of the header
+ /// (i.e. 'Select date').
+ ///
+ /// The [TextStyle.color] of the [headerHelpStyle] is not used,
+ /// [headerForegroundColor] is used instead.
+ ///
+ /// See also:
+ /// [DatePickerDialog.helpText], which specifies the help text.
+ final TextStyle? headerHelpStyle;
+
+ /// Overrides the default text style used for the row of weekday
+ /// labels at the top of the date picker grid.
+ final TextStyle? weekdayStyle;
+
+ /// Overrides the default text style used for each individual day
+ /// label in the grid of the date picker.
+ ///
+ /// The [TextStyle.color] of the [dayStyle] is not used,
+ /// [dayForegroundColor] is used instead.
+ final TextStyle? dayStyle;
+
+ /// Overrides the default color used to paint the day labels in the
+ /// grid of the date picker.
+ ///
+ /// This will be used instead of the color provided in [dayStyle].
+ final MaterialStateProperty<Color?>? dayForegroundColor;
+
+ /// Overrides the default color used to paint the background of the
+ /// day labels in the grid of the date picker.
+ final MaterialStateProperty<Color?>? dayBackgroundColor;
+
+ /// Overriddes the default highlight color that's typically used to
+ /// indicate that a day in the grid is focused, hovered, or pressed.
+ final MaterialStateProperty<Color?>? dayOverlayColor;
+
+ /// Overrides the default color used to paint the
+ /// [DatePickerDialog.currentDate] label in the grid of the dialog's
+ /// [CalendarDatePicker] and the corresponding year in the dialog's
+ /// [YearPicker].
+ ///
+ /// This will be used instead of the [TextStyle.color] provided in [dayStyle].
+ final MaterialStateProperty<Color?>? todayForegroundColor;
+
+ /// Overrides the default color used to paint the background of the
+ /// [DatePickerDialog.currentDate] label in the grid of the date picker.
+ final MaterialStateProperty<Color?>? todayBackgroundColor;
+
+ /// Overrides the border used to paint the
+ /// [DatePickerDialog.currentDate] label in the grid of the date
+ /// picker.
+ ///
+ /// The border side's [BorderSide.color] is not used,
+ /// [todayForegroundColor] is used instead.
+ final BorderSide? todayBorder;
+
+ /// Overrides the default text style used to paint each of the year
+ /// entries in the year selector of the date picker.
+ ///
+ /// The [TextStyle.color] of the [yearStyle] is not used,
+ /// [yearForegroundColor] is used instead.
+ final TextStyle? yearStyle;
+
+ /// Overrides the default color used to paint the year labels in the year
+ /// selector of the date picker.
+ ///
+ /// This will be used instead of the color provided in [yearStyle].
+ final MaterialStateProperty<Color?>? yearForegroundColor;
+
+ /// Overrides the default color used to paint the background of the
+ /// year labels in the year selector of the of the date picker.
+ final MaterialStateProperty<Color?>? yearBackgroundColor;
+
+ /// Overrides the default highlight color that's typically used to
+ /// indicate that a year in the year selector is focused, hovered,
+ /// or pressed.
+ final MaterialStateProperty<Color?>? yearOverlayColor;
+
+ /// Overrides the default [Scaffold.backgroundColor] for
+ /// [DateRangePickerDialog].
+ final Color? rangePickerBackgroundColor;
+
+ /// Overrides the default elevation of the full screen
+ /// [DateRangePickerDialog].
+ ///
+ /// See also:
+ /// [Material.elevation], which explains how elevation is related to a component's shadow.
+ final double? rangePickerElevation;
+
+ /// Overrides the color of the shadow painted below a full screen
+ /// [DateRangePickerDialog].
+ ///
+ /// See also:
+ /// [Material.shadowColor], which explains how the shadow is rendered.
+ final Color? rangePickerShadowColor;
+
+ /// Overrides the default color of the surface tint overlay applied
+ /// to the [backgroundColor] of a full screen
+ /// [DateRangePickerDialog]'s to indicate elevation.
+ ///
+ /// See also:
+ /// [Material.surfaceTintColor], which explains how this color is related to
+ /// [elevation].
+ final Color? rangePickerSurfaceTintColor;
+
+ /// Overrides the default overall shape of a full screen
+ /// [DateRangePickerDialog].
+ ///
+ /// If [elevation] is greater than zero then a shadow is shown and the shadow's
+ /// shape mirrors the shape of the dialog.
+ ///
+ /// [Material.surfaceTintColor], which explains how this color is related to
+ /// [elevation].
+ final ShapeBorder? rangePickerShape;
+
+ /// Overrides the default background fill color for [DateRangePickerDialog].
+ ///
+ /// The dialog's header displays the currently selected date range.
+ final Color? rangePickerHeaderBackgroundColor;
+
+ /// Overrides the default color used for text labels and icons in
+ /// the header of a full screen [DateRangePickerDialog]
+ ///
+ /// The dialog's header displays the currently selected date range.
+ ///
+ /// This is used instead of any colors provided by
+ /// [rangePickerHeaderHeadlineStyle] or [rangePickerHeaderHelpStyle].
+ final Color? rangePickerHeaderForegroundColor;
+
+ /// Overrides the default text style used for the headline text in
+ /// the header of a full screen [DateRangePickerDialog].
+ ///
+ /// The dialog's header displays the currently selected date range.
+ ///
+ /// The [TextStyle.color] of [rangePickerHeaderHeadlineStyle] is not used,
+ /// [rangePickerHeaderForegroundColor] is used instead.
+ final TextStyle? rangePickerHeaderHeadlineStyle;
+
+ /// Overrides the default text style used for the help text of the
+ /// header of a full screen [DateRangePickerDialog].
+ ///
+ /// The help text (also referred to as "supporting text" in the Material
+ /// spec) is usually a prompt to the user at the top of the header
+ /// (i.e. 'Select date').
+ ///
+ /// The [TextStyle.color] of the [rangePickerHeaderHelpStyle] is not used,
+ /// [rangePickerHeaderForegroundColor] is used instead.
+ ///
+ /// See also:
+ /// [DateRangePickerDialog.helpText], which specifies the help text.
+ final TextStyle? rangePickerHeaderHelpStyle;
+
+ /// Overrides the default background color used to paint days
+ /// selected between the start and end dates in a
+ /// [DateRangePickerDialog].
+ final Color? rangeSelectionBackgroundColor;
+
+ /// Overrides the default highlight color that's typically used to
+ /// indicate that a date in the selected range of a
+ /// [DateRangePickerDialog] is focused, hovered, or pressed.
+ final MaterialStateProperty<Color?>? rangeSelectionOverlayColor;
+
+ /// Creates a copy of this object with the given fields replaced with the
+ /// new values.
+ DatePickerThemeData copyWith({
+ Color? backgroundColor,
+ double? elevation,
+ Color? shadowColor,
+ Color? surfaceTintColor,
+ ShapeBorder? shape,
+ Color? headerBackgroundColor,
+ Color? headerForegroundColor,
+ TextStyle? headerHeadlineStyle,
+ TextStyle? headerHelpStyle,
+ TextStyle? weekdayStyle,
+ TextStyle? dayStyle,
+ MaterialStateProperty<Color?>? dayForegroundColor,
+ MaterialStateProperty<Color?>? dayBackgroundColor,
+ MaterialStateProperty<Color?>? dayOverlayColor,
+ MaterialStateProperty<Color?>? todayForegroundColor,
+ MaterialStateProperty<Color?>? todayBackgroundColor,
+ BorderSide? todayBorder,
+ TextStyle? yearStyle,
+ MaterialStateProperty<Color?>? yearForegroundColor,
+ MaterialStateProperty<Color?>? yearBackgroundColor,
+ MaterialStateProperty<Color?>? yearOverlayColor,
+ Color? rangePickerBackgroundColor,
+ double? rangePickerElevation,
+ Color? rangePickerShadowColor,
+ Color? rangePickerSurfaceTintColor,
+ ShapeBorder? rangePickerShape,
+ Color? rangePickerHeaderBackgroundColor,
+ Color? rangePickerHeaderForegroundColor,
+ TextStyle? rangePickerHeaderHeadlineStyle,
+ TextStyle? rangePickerHeaderHelpStyle,
+ Color? rangeSelectionBackgroundColor,
+ MaterialStateProperty<Color?>? rangeSelectionOverlayColor,
+ }) {
+ return DatePickerThemeData(
+ backgroundColor: backgroundColor ?? this.backgroundColor,
+ elevation: elevation ?? this.elevation,
+ shadowColor: shadowColor ?? this.shadowColor,
+ surfaceTintColor: surfaceTintColor ?? this.surfaceTintColor,
+ shape: shape ?? this.shape,
+ headerBackgroundColor: headerBackgroundColor ?? this.headerBackgroundColor,
+ headerForegroundColor: headerForegroundColor ?? this.headerForegroundColor,
+ headerHeadlineStyle: headerHeadlineStyle ?? this.headerHeadlineStyle,
+ headerHelpStyle: headerHelpStyle ?? this.headerHelpStyle,
+ weekdayStyle: weekdayStyle ?? this.weekdayStyle,
+ dayStyle: dayStyle ?? this.dayStyle,
+ dayForegroundColor: dayForegroundColor ?? this.dayForegroundColor,
+ dayBackgroundColor: dayBackgroundColor ?? this.dayBackgroundColor,
+ dayOverlayColor: dayOverlayColor ?? this.dayOverlayColor,
+ todayForegroundColor: todayForegroundColor ?? this.todayForegroundColor,
+ todayBackgroundColor: todayBackgroundColor ?? this.todayBackgroundColor,
+ todayBorder: todayBorder ?? this.todayBorder,
+ yearStyle: yearStyle ?? this.yearStyle,
+ yearForegroundColor: yearForegroundColor ?? this.yearForegroundColor,
+ yearBackgroundColor: yearBackgroundColor ?? this.yearBackgroundColor,
+ yearOverlayColor: yearOverlayColor ?? this.yearOverlayColor,
+ rangePickerBackgroundColor: rangePickerBackgroundColor ?? this.rangePickerBackgroundColor,
+ rangePickerElevation: rangePickerElevation ?? this.rangePickerElevation,
+ rangePickerShadowColor: rangePickerShadowColor ?? this.rangePickerShadowColor,
+ rangePickerSurfaceTintColor: rangePickerSurfaceTintColor ?? this.rangePickerSurfaceTintColor,
+ rangePickerShape: rangePickerShape ?? this.rangePickerShape,
+ rangePickerHeaderBackgroundColor: rangePickerHeaderBackgroundColor ?? this.rangePickerHeaderBackgroundColor,
+ rangePickerHeaderForegroundColor: rangePickerHeaderForegroundColor ?? this.rangePickerHeaderForegroundColor,
+ rangePickerHeaderHeadlineStyle: rangePickerHeaderHeadlineStyle ?? this.rangePickerHeaderHeadlineStyle,
+ rangePickerHeaderHelpStyle: rangePickerHeaderHelpStyle ?? this.rangePickerHeaderHelpStyle,
+ rangeSelectionBackgroundColor: rangeSelectionBackgroundColor ?? this.rangeSelectionBackgroundColor,
+ rangeSelectionOverlayColor: rangeSelectionOverlayColor ?? this.rangeSelectionOverlayColor,
+ );
+ }
+
+ /// Linearly interpolates between two [DatePickerThemeData].
+ static DatePickerThemeData lerp(DatePickerThemeData? a, DatePickerThemeData? b, double t) {
+ return DatePickerThemeData(
+ backgroundColor: Color.lerp(a?.backgroundColor, b?.backgroundColor, t),
+ elevation: lerpDouble(a?.elevation, b?.elevation, t),
+ shadowColor: Color.lerp(a?.shadowColor, b?.shadowColor, t),
+ surfaceTintColor: Color.lerp(a?.surfaceTintColor, b?.surfaceTintColor, t),
+ shape: ShapeBorder.lerp(a?.shape, b?.shape, t),
+ headerBackgroundColor: Color.lerp(a?.headerBackgroundColor, b?.headerBackgroundColor, t),
+ headerForegroundColor: Color.lerp(a?.headerForegroundColor, b?.headerForegroundColor, t),
+ headerHeadlineStyle: TextStyle.lerp(a?.headerHeadlineStyle, b?.headerHeadlineStyle, t),
+ headerHelpStyle: TextStyle.lerp(a?.headerHelpStyle, b?.headerHelpStyle, t),
+ weekdayStyle: TextStyle.lerp(a?.weekdayStyle, b?.weekdayStyle, t),
+ dayStyle: TextStyle.lerp(a?.dayStyle, b?.dayStyle, t),
+ dayForegroundColor: MaterialStateProperty.lerp<Color?>(a?.dayForegroundColor, b?.dayForegroundColor, t, Color.lerp),
+ dayBackgroundColor: MaterialStateProperty.lerp<Color?>(a?.dayBackgroundColor, b?.dayBackgroundColor, t, Color.lerp),
+ dayOverlayColor: MaterialStateProperty.lerp<Color?>(a?.dayOverlayColor, b?.dayOverlayColor, t, Color.lerp),
+ todayForegroundColor: MaterialStateProperty.lerp<Color?>(a?.todayForegroundColor, b?.todayForegroundColor, t, Color.lerp),
+ todayBackgroundColor: MaterialStateProperty.lerp<Color?>(a?.todayBackgroundColor, b?.todayBackgroundColor, t, Color.lerp),
+ todayBorder: _lerpBorderSide(a?.todayBorder, b?.todayBorder, t),
+ yearStyle: TextStyle.lerp(a?.yearStyle, b?.yearStyle, t),
+ yearForegroundColor: MaterialStateProperty.lerp<Color?>(a?.yearForegroundColor, b?.yearForegroundColor, t, Color.lerp),
+ yearBackgroundColor: MaterialStateProperty.lerp<Color?>(a?.yearBackgroundColor, b?.yearBackgroundColor, t, Color.lerp),
+ yearOverlayColor: MaterialStateProperty.lerp<Color?>(a?.yearOverlayColor, b?.yearOverlayColor, t, Color.lerp),
+ rangePickerBackgroundColor: Color.lerp(a?.rangePickerBackgroundColor, b?.rangePickerBackgroundColor, t),
+ rangePickerElevation: lerpDouble(a?.rangePickerElevation, b?.rangePickerElevation, t),
+ rangePickerShadowColor: Color.lerp(a?.rangePickerShadowColor, b?.rangePickerShadowColor, t),
+ rangePickerSurfaceTintColor: Color.lerp(a?.rangePickerSurfaceTintColor, b?.rangePickerSurfaceTintColor, t),
+ rangePickerShape: ShapeBorder.lerp(a?.rangePickerShape, b?.rangePickerShape, t),
+ rangePickerHeaderBackgroundColor: Color.lerp(a?.rangePickerHeaderBackgroundColor, b?.rangePickerHeaderBackgroundColor, t),
+ rangePickerHeaderForegroundColor: Color.lerp(a?.rangePickerHeaderForegroundColor, b?.rangePickerHeaderForegroundColor, t),
+ rangePickerHeaderHeadlineStyle: TextStyle.lerp(a?.rangePickerHeaderHeadlineStyle, b?.rangePickerHeaderHeadlineStyle, t),
+ rangePickerHeaderHelpStyle: TextStyle.lerp(a?.rangePickerHeaderHelpStyle, b?.rangePickerHeaderHelpStyle, t),
+ rangeSelectionBackgroundColor: Color.lerp(a?.rangeSelectionBackgroundColor, b?.rangeSelectionBackgroundColor, t),
+ rangeSelectionOverlayColor: MaterialStateProperty.lerp<Color?>(a?.rangeSelectionOverlayColor, b?.rangeSelectionOverlayColor, t, Color.lerp),
+ );
+ }
+
+ static BorderSide? _lerpBorderSide(BorderSide? a, BorderSide? b, double t) {
+ if (a == null && b == null) {
+ return null;
+ }
+ if (a == null) {
+ return BorderSide.lerp(BorderSide(width: 0, color: b!.color.withAlpha(0)), b, t);
+ }
+ return BorderSide.lerp(a, BorderSide(width: 0, color: a.color.withAlpha(0)), t);
+ }
+
+ @override
+ int get hashCode => Object.hashAll(<Object?>[
+ backgroundColor,
+ elevation,
+ shadowColor,
+ surfaceTintColor,
+ shape,
+ headerBackgroundColor,
+ headerForegroundColor,
+ headerHeadlineStyle,
+ headerHelpStyle,
+ weekdayStyle,
+ dayStyle,
+ dayForegroundColor,
+ dayBackgroundColor,
+ dayOverlayColor,
+ todayForegroundColor,
+ todayBackgroundColor,
+ todayBorder,
+ yearStyle,
+ yearForegroundColor,
+ yearBackgroundColor,
+ yearOverlayColor,
+ rangePickerBackgroundColor,
+ rangePickerElevation,
+ rangePickerShadowColor,
+ rangePickerSurfaceTintColor,
+ rangePickerShape,
+ rangePickerHeaderBackgroundColor,
+ rangePickerHeaderForegroundColor,
+ rangePickerHeaderHeadlineStyle,
+ rangePickerHeaderHelpStyle,
+ rangeSelectionBackgroundColor,
+ rangeSelectionOverlayColor,
+ ]);
+
+ @override
+ bool operator ==(Object other) {
+ if (identical(this, other)) {
+ return true;
+ }
+ return other is DatePickerThemeData
+ && other.backgroundColor == backgroundColor
+ && other.elevation == elevation
+ && other.shadowColor == shadowColor
+ && other.surfaceTintColor == surfaceTintColor
+ && other.shape == shape
+ && other.headerBackgroundColor == headerBackgroundColor
+ && other.headerForegroundColor == headerForegroundColor
+ && other.headerHeadlineStyle == headerHeadlineStyle
+ && other.headerHelpStyle == headerHelpStyle
+ && other.weekdayStyle == weekdayStyle
+ && other.dayStyle == dayStyle
+ && other.dayForegroundColor == dayForegroundColor
+ && other.dayBackgroundColor == dayBackgroundColor
+ && other.dayOverlayColor == dayOverlayColor
+ && other.todayForegroundColor == todayForegroundColor
+ && other.todayBackgroundColor == todayBackgroundColor
+ && other.todayBorder == todayBorder
+ && other.yearStyle == yearStyle
+ && other.yearForegroundColor == yearForegroundColor
+ && other.yearBackgroundColor == yearBackgroundColor
+ && other.yearOverlayColor == yearOverlayColor
+ && other.rangePickerBackgroundColor == rangePickerBackgroundColor
+ && other.rangePickerElevation == rangePickerElevation
+ && other.rangePickerShadowColor == rangePickerShadowColor
+ && other.rangePickerSurfaceTintColor == rangePickerSurfaceTintColor
+ && other.rangePickerShape == rangePickerShape
+ && other.rangePickerHeaderBackgroundColor == rangePickerHeaderBackgroundColor
+ && other.rangePickerHeaderForegroundColor == rangePickerHeaderForegroundColor
+ && other.rangePickerHeaderHeadlineStyle == rangePickerHeaderHeadlineStyle
+ && other.rangePickerHeaderHelpStyle == rangePickerHeaderHelpStyle
+ && other.rangeSelectionBackgroundColor == rangeSelectionBackgroundColor
+ && other.rangeSelectionOverlayColor == rangeSelectionOverlayColor;
+ }
+
+ @override
+ void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+ super.debugFillProperties(properties);
+ properties.add(ColorProperty('backgroundColor', backgroundColor, defaultValue: null));
+ properties.add(DoubleProperty('elevation', elevation, defaultValue: null));
+ properties.add(ColorProperty('shadowColor', shadowColor, defaultValue: null));
+ properties.add(ColorProperty('surfaceTintColor', surfaceTintColor, defaultValue: null));
+ properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null));
+ properties.add(ColorProperty('headerBackgroundColor', headerBackgroundColor, defaultValue: null));
+ properties.add(ColorProperty('headerForegroundColor', headerForegroundColor, defaultValue: null));
+ properties.add(DiagnosticsProperty<TextStyle>('headerHeadlineStyle', headerHeadlineStyle, defaultValue: null));
+ properties.add(DiagnosticsProperty<TextStyle>('headerHelpStyle', headerHelpStyle, defaultValue: null));
+ properties.add(DiagnosticsProperty<TextStyle>('weekDayStyle', weekdayStyle, defaultValue: null));
+ properties.add(DiagnosticsProperty<TextStyle>('dayStyle', dayStyle, defaultValue: null));
+ properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('dayForegroundColor', dayForegroundColor, defaultValue: null));
+ properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('dayBackgroundColor', dayBackgroundColor, defaultValue: null));
+ properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('dayOverlayColor', dayOverlayColor, defaultValue: null));
+ properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('todayForegroundColor', todayForegroundColor, defaultValue: null));
+ properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('todayBackgroundColor', todayBackgroundColor, defaultValue: null));
+ properties.add(DiagnosticsProperty<BorderSide?>('todayBorder', todayBorder, defaultValue: null));
+ properties.add(DiagnosticsProperty<TextStyle>('yearStyle', yearStyle, defaultValue: null));
+ properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('yearForegroundColor', yearForegroundColor, defaultValue: null));
+ properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('yearBackgroundColor', yearBackgroundColor, defaultValue: null));
+ properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('yearOverlayColor', yearOverlayColor, defaultValue: null));
+ properties.add(ColorProperty('rangePickerBackgroundColor', rangePickerBackgroundColor, defaultValue: null));
+ properties.add(DoubleProperty('rangePickerElevation', rangePickerElevation, defaultValue: null));
+ properties.add(ColorProperty('rangePickerShadowColor', rangePickerShadowColor, defaultValue: null));
+ properties.add(ColorProperty('rangePickerSurfaceTintColor', rangePickerSurfaceTintColor, defaultValue: null));
+ properties.add(DiagnosticsProperty<ShapeBorder>('rangePickerShape', rangePickerShape, defaultValue: null));
+ properties.add(ColorProperty('rangePickerHeaderBackgroundColor', rangePickerHeaderBackgroundColor, defaultValue: null));
+ properties.add(ColorProperty('rangePickerHeaderForegroundColor', rangePickerHeaderForegroundColor, defaultValue: null));
+ properties.add(DiagnosticsProperty<TextStyle>('rangePickerHeaderHeadlineStyle', rangePickerHeaderHeadlineStyle, defaultValue: null));
+ properties.add(DiagnosticsProperty<TextStyle>('rangePickerHeaderHelpStyle', rangePickerHeaderHelpStyle, defaultValue: null));
+ properties.add(ColorProperty('rangeSelectionBackgroundColor', rangeSelectionBackgroundColor, defaultValue: null));
+ properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('rangeSelectionOverlayColor', rangeSelectionOverlayColor, defaultValue: null));
+ }
+}
+
+/// An inherited widget that defines the visual properties for
+/// [DatePickerDialog]s in this widget's subtree.
+///
+/// Values specified here are used for [DatePickerDialog] properties that are not
+/// given an explicit non-null value.
+class DatePickerTheme extends InheritedTheme {
+ /// Creates a [DatePickerTheme] that controls visual parameters for
+ /// descendent [DatePickerDialog]s.
+ const DatePickerTheme({
+ super.key,
+ required this.data,
+ required super.child,
+ });
+
+ /// Specifies the visual properties used by descendant [DatePickerDialog]
+ /// widgets.
+ final DatePickerThemeData data;
+
+ /// The [data] from the closest instance of this class that encloses the given
+ /// context.
+ ///
+ /// If there is no [DatePickerTheme] in scope, this will return
+ /// [ThemeData.datePickerTheme] from the ambient [Theme].
+ ///
+ /// Typical usage is as follows:
+ ///
+ /// ```dart
+ /// DatePickerThemeData theme = DatePickerTheme.of(context);
+ /// ```
+ ///
+ /// See also:
+ ///
+ /// * [maybeOf], which returns null if it doesn't find a
+ /// [DatePickerTheme] ancestor.
+ /// * [defaults], which will return the default properties used when no
+ /// other [DatePickerTheme] has been provided.
+ static DatePickerThemeData of(BuildContext context) {
+ return maybeOf(context) ?? Theme.of(context).datePickerTheme;
+ }
+
+ /// The data from the closest instance of this class that encloses the given
+ /// context, if any.
+ ///
+ /// Use this function if you want to allow situations where no
+ /// [DatePickerTheme] is in scope. Prefer using [DatePickerTheme.of]
+ /// in situations where a [DatePickerThemeData] is expected to be
+ /// non-null.
+ ///
+ /// If there is no [DatePickerTheme] in scope, then this function will
+ /// return null.
+ ///
+ /// Typical usage is as follows:
+ ///
+ /// ```dart
+ /// DatePickerThemeData? theme = DatePickerTheme.maybeOf(context);
+ /// if (theme == null) {
+ /// // Do something else instead.
+ /// }
+ /// ```
+ ///
+ /// See also:
+ ///
+ /// * [of], which will return [ThemeData.datePickerTheme] if it doesn't
+ /// find a [DatePickerTheme] ancestor, instead of returning null.
+ /// * [defaults], which will return the default properties used when no
+ /// other [DatePickerTheme] has been provided.
+ static DatePickerThemeData? maybeOf(BuildContext context) {
+ return context.dependOnInheritedWidgetOfExactType<DatePickerTheme>()?.data;
+ }
+
+ /// A DatePickerThemeData used as the default properties for date pickers.
+ ///
+ /// This is only used for properties not already specified in the ambient
+ /// [DatePickerTheme.of].
+ ///
+ /// See also:
+ ///
+ /// * [of], which will return [ThemeData.datePickerTheme] if it doesn't
+ /// find a [DatePickerTheme] ancestor, instead of returning null.
+ /// * [maybeOf], which returns null if it doesn't find a
+ /// [DatePickerTheme] ancestor.
+ static DatePickerThemeData defaults(BuildContext context) {
+ return Theme.of(context).useMaterial3
+ ? _DatePickerDefaultsM3(context)
+ : _DatePickerDefaultsM2(context);
+ }
+
+ @override
+ Widget wrap(BuildContext context, Widget child) {
+ return DatePickerTheme(data: data, child: child);
+ }
+
+ @override
+ bool updateShouldNotify(DatePickerTheme oldWidget) => data != oldWidget.data;
+}
+
+// Hand coded defaults based on Material Design 2.
+class _DatePickerDefaultsM2 extends DatePickerThemeData {
+ _DatePickerDefaultsM2(this.context)
+ : super(
+ elevation: 24.0,
+ shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4.0))),
+ rangePickerElevation: 0.0,
+ rangePickerShape: const RoundedRectangleBorder(),
+ );
+
+ final BuildContext context;
+ late final ThemeData _theme = Theme.of(context);
+ late final ColorScheme _colors = _theme.colorScheme;
+ late final TextTheme _textTheme = _theme.textTheme;
+ late final bool _isDark = _colors.brightness == Brightness.dark;
+
+ @override
+ Color? get headerBackgroundColor => _isDark ? _colors.surface : _colors.primary;
+
+ @override
+ Color? get headerForegroundColor => _isDark ? _colors.onSurface : _colors.onPrimary;
+
+ @override
+ TextStyle? get headerHeadlineStyle => _textTheme.headlineSmall;
+
+ @override
+ TextStyle? get headerHelpStyle => _textTheme.labelSmall;
+
+ @override
+ TextStyle? get weekdayStyle => _textTheme.bodySmall?.apply(
+ color: _colors.onSurface.withOpacity(0.60),
+ );
+
+ @override
+ TextStyle? get dayStyle => _textTheme.bodySmall;
+
+ @override
+ MaterialStateProperty<Color?>? get dayForegroundColor =>
+ MaterialStateProperty.resolveWith((Set<MaterialState> states) {
+ if (states.contains(MaterialState.selected)) {
+ return _colors.onPrimary;
+ } else if (states.contains(MaterialState.disabled)) {
+ return _colors.onSurface.withOpacity(0.38);
+ }
+ return _colors.onSurface;
+ });
+
+ @override
+ MaterialStateProperty<Color?>? get dayBackgroundColor =>
+ MaterialStateProperty.resolveWith((Set<MaterialState> states) {
+ if (states.contains(MaterialState.selected)) {
+ return _colors.primary;
+ }
+ return null;
+ });
+
+ @override
+ MaterialStateProperty<Color?>? get dayOverlayColor =>
+ MaterialStateProperty.resolveWith((Set<MaterialState> states) {
+ if (states.contains(MaterialState.selected)) {
+ if (states.contains(MaterialState.hovered)) {
+ return _colors.onPrimary.withOpacity(0.08);
+ }
+ if (states.contains(MaterialState.focused)) {
+ return _colors.onPrimary.withOpacity(0.12);
+ }
+ if (states.contains(MaterialState.pressed)) {
+ return _colors.onPrimary.withOpacity(0.38);
+ }
+ } else {
+ if (states.contains(MaterialState.hovered)) {
+ return _colors.onSurfaceVariant.withOpacity(0.08);
+ }
+ if (states.contains(MaterialState.focused)) {
+ return _colors.onSurfaceVariant.withOpacity(0.12);
+ }
+ if (states.contains(MaterialState.pressed)) {
+ return _colors.onSurfaceVariant.withOpacity(0.12);
+ }
+ }
+ return null;
+ });
+
+ @override
+ MaterialStateProperty<Color?>? get todayForegroundColor =>
+ MaterialStateProperty.resolveWith((Set<MaterialState> states) {
+ if (states.contains(MaterialState.selected)) {
+ return _colors.onPrimary;
+ } else if (states.contains(MaterialState.disabled)) {
+ return _colors.onSurface.withOpacity(0.38);
+ }
+ return _colors.primary;
+ });
+
+ @override
+ MaterialStateProperty<Color?>? get todayBackgroundColor => dayBackgroundColor;
+
+ @override
+ BorderSide? get todayBorder => BorderSide(color: _colors.primary);
+
+ @override
+ TextStyle? get yearStyle => _textTheme.bodyLarge;
+
+ @override
+ Color? get rangePickerBackgroundColor => _colors.surface;
+
+ @override
+ Color? get rangePickerShadowColor => Colors.transparent;
+
+ @override
+ Color? get rangePickerSurfaceTintColor => Colors.transparent;
+
+ @override
+ Color? get rangePickerHeaderBackgroundColor => _isDark ? _colors.surface : _colors.primary;
+
+ @override
+ Color? get rangePickerHeaderForegroundColor => _isDark ? _colors.onSurface : _colors.onPrimary;
+
+ @override
+ TextStyle? get rangePickerHeaderHeadlineStyle => _textTheme.headlineSmall;
+
+ @override
+ TextStyle? get rangePickerHeaderHelpStyle => _textTheme.labelSmall;
+
+ @override
+ Color? get rangeSelectionBackgroundColor => _colors.primary.withOpacity(0.12);
+
+ @override
+ MaterialStateProperty<Color?>? get rangeSelectionOverlayColor =>
+ MaterialStateProperty.resolveWith((Set<MaterialState> states) {
+ if (states.contains(MaterialState.selected)) {
+ if (states.contains(MaterialState.hovered)) {
+ return _colors.onPrimary.withOpacity(0.08);
+ }
+ if (states.contains(MaterialState.focused)) {
+ return _colors.onPrimary.withOpacity(0.12);
+ }
+ if (states.contains(MaterialState.pressed)) {
+ return _colors.onPrimary.withOpacity(0.38);
+ }
+ } else {
+ if (states.contains(MaterialState.hovered)) {
+ return _colors.onSurfaceVariant.withOpacity(0.08);
+ }
+ if (states.contains(MaterialState.focused)) {
+ return _colors.onSurfaceVariant.withOpacity(0.12);
+ }
+ if (states.contains(MaterialState.pressed)) {
+ return _colors.onSurfaceVariant.withOpacity(0.12);
+ }
+ }
+ return null;
+ });
+}
+
+// BEGIN GENERATED TOKEN PROPERTIES - DatePicker
+
+// Do not edit by hand. The code between the "BEGIN GENERATED" and
+// "END GENERATED" comments are generated from data in the Material
+// Design token database by the script:
+// dev/tools/gen_defaults/bin/gen_defaults.dart.
+
+// Token database version: v0_150
+
+class _DatePickerDefaultsM3 extends DatePickerThemeData {
+ _DatePickerDefaultsM3(this.context)
+ : super(
+ elevation: 6.0,
+ shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(28.0))),
+ rangePickerElevation: 0.0,
+ rangePickerShape: const RoundedRectangleBorder(),
+ );
+
+ final BuildContext context;
+ late final ThemeData _theme = Theme.of(context);
+ late final ColorScheme _colors = _theme.colorScheme;
+ late final TextTheme _textTheme = _theme.textTheme;
+
+ @override
+ Color? get backgroundColor => _colors.surface;
+
+ @override
+ Color? get shadowColor => Colors.transparent;
+
+ @override
+ Color? get surfaceTintColor => _colors.surfaceTint;
+
+ @override
+ Color? get headerBackgroundColor => Colors.transparent;
+
+ @override
+ Color? get headerForegroundColor => _colors.onSurfaceVariant;
+
+ @override
+ TextStyle? get headerHeadlineStyle => _textTheme.headlineLarge;
+
+ @override
+ TextStyle? get headerHelpStyle => _textTheme.labelMedium;
+
+ @override
+ TextStyle? get weekdayStyle => _textTheme.bodySmall?.apply(
+ color: _colors.onSurface,
+ );
+
+ @override
+ TextStyle? get dayStyle => _textTheme.bodySmall;
+
+ @override
+ MaterialStateProperty<Color?>? get dayForegroundColor =>
+ MaterialStateProperty.resolveWith((Set<MaterialState> states) {
+ if (states.contains(MaterialState.selected)) {
+ return _colors.onPrimary;
+ } else if (states.contains(MaterialState.disabled)) {
+ return _colors.onSurface.withOpacity(0.38);
+ }
+ return _colors.onSurface;
+ });
+
+ @override
+ MaterialStateProperty<Color?>? get dayBackgroundColor =>
+ MaterialStateProperty.resolveWith((Set<MaterialState> states) {
+ if (states.contains(MaterialState.selected)) {
+ return _colors.primary;
+ }
+ return null;
+ });
+
+ @override
+ MaterialStateProperty<Color?>? get dayOverlayColor =>
+ MaterialStateProperty.resolveWith((Set<MaterialState> states) {
+ if (states.contains(MaterialState.selected)) {
+ if (states.contains(MaterialState.hovered)) {
+ return _colors.onPrimary.withOpacity(0.08);
+ }
+ if (states.contains(MaterialState.focused)) {
+ return _colors.onPrimary.withOpacity(0.12);
+ }
+ if (states.contains(MaterialState.pressed)) {
+ return _colors.onPrimary.withOpacity(0.12);
+ }
+ } else {
+ if (states.contains(MaterialState.hovered)) {
+ return _colors.onSurfaceVariant.withOpacity(0.08);
+ }
+ if (states.contains(MaterialState.focused)) {
+ return _colors.onSurfaceVariant.withOpacity(0.12);
+ }
+ if (states.contains(MaterialState.pressed)) {
+ return _colors.onSurfaceVariant.withOpacity(0.12);
+ }
+ }
+ return null;
+ });
+
+ @override
+ MaterialStateProperty<Color?>? get todayForegroundColor =>
+ MaterialStateProperty.resolveWith((Set<MaterialState> states) {
+ if (states.contains(MaterialState.selected)) {
+ return _colors.onPrimary;
+ } else if (states.contains(MaterialState.disabled)) {
+ return _colors.primary.withOpacity(0.38);
+ }
+ return _colors.primary;
+ });
+
+ @override
+ MaterialStateProperty<Color?>? get todayBackgroundColor => dayBackgroundColor;
+
+ @override
+ BorderSide? get todayBorder => BorderSide(color: _colors.primary);
+
+ @override
+ TextStyle? get yearStyle => _textTheme.bodyLarge;
+
+ @override
+ MaterialStateProperty<Color?>? get yearForegroundColor =>
+ MaterialStateProperty.resolveWith((Set<MaterialState> states) {
+ if (states.contains(MaterialState.selected)) {
+ return _colors.onPrimary;
+ } else if (states.contains(MaterialState.disabled)) {
+ return _colors.onSurfaceVariant.withOpacity(0.38);
+ }
+ return _colors.onSurfaceVariant;
+ });
+
+ @override
+ MaterialStateProperty<Color?>? get yearBackgroundColor =>
+ MaterialStateProperty.resolveWith((Set<MaterialState> states) {
+ if (states.contains(MaterialState.selected)) {
+ return _colors.primary;
+ }
+ return null;
+ });
+
+ @override
+ MaterialStateProperty<Color?>? get yearOverlayColor =>
+ MaterialStateProperty.resolveWith((Set<MaterialState> states) {
+ if (states.contains(MaterialState.selected)) {
+ if (states.contains(MaterialState.hovered)) {
+ return _colors.onPrimary.withOpacity(0.08);
+ }
+ if (states.contains(MaterialState.focused)) {
+ return _colors.onPrimary.withOpacity(0.12);
+ }
+ if (states.contains(MaterialState.pressed)) {
+ return _colors.onPrimary.withOpacity(0.12);
+ }
+ } else {
+ if (states.contains(MaterialState.hovered)) {
+ return _colors.onSurfaceVariant.withOpacity(0.08);
+ }
+ if (states.contains(MaterialState.focused)) {
+ return _colors.onSurfaceVariant.withOpacity(0.12);
+ }
+ if (states.contains(MaterialState.pressed)) {
+ return _colors.onSurfaceVariant.withOpacity(0.12);
+ }
+ }
+ return null;
+ });
+
+ @override
+ Color? get rangePickerShadowColor => Colors.transparent;
+
+ @override
+ Color? get rangePickerSurfaceTintColor => Colors.transparent;
+
+ @override
+ Color? get rangeSelectionBackgroundColor => _colors.primaryContainer;
+
+ @override
+ MaterialStateProperty<Color?>? get rangeSelectionOverlayColor =>
+ MaterialStateProperty.resolveWith((Set<MaterialState> states) {
+ if (states.contains(MaterialState.hovered)) {
+ return null;
+ }
+ if (states.contains(MaterialState.focused)) {
+ return null;
+ }
+ if (states.contains(MaterialState.pressed)) {
+ return null;
+ }
+ return null;
+ });
+
+ @override
+ Color? get rangePickerHeaderBackgroundColor => Colors.transparent;
+
+ @override
+ Color? get rangePickerHeaderForegroundColor => _colors.onSurfaceVariant;
+
+ @override
+ TextStyle? get rangePickerHeaderHeadlineStyle => _textTheme.titleLarge;
+
+ @override
+ TextStyle? get rangePickerHeaderHelpStyle => _textTheme.titleSmall;
+
+
+}
+
+// END GENERATED TOKEN PROPERTIES - DatePicker
diff --git a/packages/flutter/lib/src/material/input_date_picker_form_field.dart b/packages/flutter/lib/src/material/input_date_picker_form_field.dart
index 9cb7615..7c68804 100644
--- a/packages/flutter/lib/src/material/input_date_picker_form_field.dart
+++ b/packages/flutter/lib/src/material/input_date_picker_form_field.dart
@@ -234,11 +234,16 @@
@override
Widget build(BuildContext context) {
+ final ThemeData theme = Theme.of(context);
+ final bool useMaterial3 = theme.useMaterial3;
final MaterialLocalizations localizations = MaterialLocalizations.of(context);
- final InputDecorationTheme inputTheme = Theme.of(context).inputDecorationTheme;
+ final InputDecorationTheme inputTheme = theme.inputDecorationTheme;
+ final InputBorder inputBorder = inputTheme.border
+ ?? (useMaterial3 ? const OutlineInputBorder() : const UnderlineInputBorder());
+
return TextFormField(
decoration: InputDecoration(
- border: inputTheme.border ?? const UnderlineInputBorder(),
+ border: inputBorder,
filled: inputTheme.filled,
hintText: widget.fieldHintText ?? localizations.dateHelpText,
labelText: widget.fieldLabelText ?? localizations.dateInputLabel,
diff --git a/packages/flutter/lib/src/material/material.dart b/packages/flutter/lib/src/material/material.dart
index 1de0831..a531f4a 100644
--- a/packages/flutter/lib/src/material/material.dart
+++ b/packages/flutter/lib/src/material/material.dart
@@ -254,6 +254,7 @@
/// The color to paint the shadow below the material.
///
+ /// {@template flutter.material.material.shadowColor}
/// If null and [ThemeData.useMaterial3] is true then [ThemeData]'s
/// [ColorScheme.shadow] will be used. If [ThemeData.useMaterial3] is false
/// then [ThemeData.shadowColor] will be used.
@@ -266,11 +267,13 @@
/// property if it is null.
/// * [ThemeData.applyElevationOverlayColor], which turns elevation overlay
/// on or off for dark themes.
+ /// {@endtemplate}
final Color? shadowColor;
/// The color of the surface tint overlay applied to the material color
/// to indicate elevation.
///
+ /// {@template flutter.material.material.surfaceTintColor}
/// Material Design 3 introduced a new way for some components to indicate
/// their elevation by using a surface tint color overlay on top of the
/// base material [color]. This overlay is painted with an opacity that is
@@ -291,6 +294,7 @@
/// tint.
/// * https://m3.material.io/styles/color/the-color-system/color-roles
/// which specifies how the overlay is applied.
+ /// {@endtemplate}
final Color? surfaceTintColor;
/// The typographical style to use for text within this material.
@@ -298,11 +302,13 @@
/// Defines the material's shape as well its shadow.
///
+ /// {@template flutter.material.material.shape}
/// If shape is non null, the [borderRadius] is ignored and the material's
/// clip boundary and shadow are defined by the shape.
///
/// A shadow is only displayed if the [elevation] is greater than
/// zero.
+ /// {@endtemplate}
final ShapeBorder? shape;
/// Whether to paint the [shape] border in front of the [child].
diff --git a/packages/flutter/lib/src/material/theme_data.dart b/packages/flutter/lib/src/material/theme_data.dart
index 0381885..bec0bea 100644
--- a/packages/flutter/lib/src/material/theme_data.dart
+++ b/packages/flutter/lib/src/material/theme_data.dart
@@ -22,6 +22,7 @@
import 'colors.dart';
import 'constants.dart';
import 'data_table_theme.dart';
+import 'date_picker_theme.dart';
import 'dialog_theme.dart';
import 'divider_theme.dart';
import 'drawer_theme.dart';
@@ -348,6 +349,7 @@
CheckboxThemeData? checkboxTheme,
ChipThemeData? chipTheme,
DataTableThemeData? dataTableTheme,
+ DatePickerThemeData? datePickerTheme,
DialogTheme? dialogTheme,
DividerThemeData? dividerTheme,
DrawerThemeData? drawerTheme,
@@ -600,6 +602,7 @@
checkboxTheme ??= const CheckboxThemeData();
chipTheme ??= const ChipThemeData();
dataTableTheme ??= const DataTableThemeData();
+ datePickerTheme ??= const DatePickerThemeData();
dialogTheme ??= const DialogTheme();
dividerTheme ??= const DividerThemeData();
drawerTheme ??= const DrawerThemeData();
@@ -697,6 +700,7 @@
checkboxTheme: checkboxTheme,
chipTheme: chipTheme,
dataTableTheme: dataTableTheme,
+ datePickerTheme: datePickerTheme,
dialogTheme: dialogTheme,
dividerTheme: dividerTheme,
drawerTheme: drawerTheme,
@@ -810,6 +814,7 @@
required this.checkboxTheme,
required this.chipTheme,
required this.dataTableTheme,
+ required this.datePickerTheme,
required this.dialogTheme,
required this.dividerTheme,
required this.drawerTheme,
@@ -1238,6 +1243,7 @@
/// - [ActionChip] (used for Assist and Suggestion chips),
/// - [FilterChip], [ChoiceChip] (used for single selection filter chips),
/// - [InputChip]
+ /// * Date pickers: [showDatePicker], [showDateRangePicker], [DatePickerDialog], [DateRangePickerDialog], [InputDatePickerFormField]
/// * Dialogs: [Dialog], [AlertDialog]
/// * Divider: [Divider]
/// * Lists: [ListTile]
@@ -1252,6 +1258,7 @@
/// * Switch: [Switch]
/// * Tabs: [TabBar]
/// * TextFields: [TextField] together with its [InputDecoration]
+ /// * Time pickers: [showTimePicker], [TimePickerDialog]
/// * Top app bar: [AppBar]
///
/// In addition, this flag enables features introduced in Android 12.
@@ -1473,6 +1480,10 @@
/// widgets.
final DataTableThemeData dataTableTheme;
+ /// A theme for customizing the appearance and layout of [DatePickerDialog]
+ /// widgets.
+ final DatePickerThemeData datePickerTheme;
+
/// A theme for customizing the shape of a dialog.
final DialogTheme dialogTheme;
@@ -1811,6 +1822,7 @@
CheckboxThemeData? checkboxTheme,
ChipThemeData? chipTheme,
DataTableThemeData? dataTableTheme,
+ DatePickerThemeData? datePickerTheme,
DialogTheme? dialogTheme,
DividerThemeData? dividerTheme,
DrawerThemeData? drawerTheme,
@@ -1971,6 +1983,7 @@
checkboxTheme: checkboxTheme ?? this.checkboxTheme,
chipTheme: chipTheme ?? this.chipTheme,
dataTableTheme: dataTableTheme ?? this.dataTableTheme,
+ datePickerTheme: datePickerTheme ?? this.datePickerTheme,
dialogTheme: dialogTheme ?? this.dialogTheme,
dividerTheme: dividerTheme ?? this.dividerTheme,
drawerTheme: drawerTheme ?? this.drawerTheme,
@@ -2165,6 +2178,7 @@
checkboxTheme: CheckboxThemeData.lerp(a.checkboxTheme, b.checkboxTheme, t),
chipTheme: ChipThemeData.lerp(a.chipTheme, b.chipTheme, t)!,
dataTableTheme: DataTableThemeData.lerp(a.dataTableTheme, b.dataTableTheme, t),
+ datePickerTheme: DatePickerThemeData.lerp(a.datePickerTheme, b.datePickerTheme, t),
dialogTheme: DialogTheme.lerp(a.dialogTheme, b.dialogTheme, t),
dividerTheme: DividerThemeData.lerp(a.dividerTheme, b.dividerTheme, t),
drawerTheme: DrawerThemeData.lerp(a.drawerTheme, b.drawerTheme, t)!,
@@ -2273,6 +2287,7 @@
other.checkboxTheme == checkboxTheme &&
other.chipTheme == chipTheme &&
other.dataTableTheme == dataTableTheme &&
+ other.datePickerTheme == datePickerTheme &&
other.dialogTheme == dialogTheme &&
other.dividerTheme == dividerTheme &&
other.drawerTheme == drawerTheme &&
@@ -2378,6 +2393,7 @@
checkboxTheme,
chipTheme,
dataTableTheme,
+ datePickerTheme,
dialogTheme,
dividerTheme,
drawerTheme,
@@ -2485,6 +2501,7 @@
properties.add(DiagnosticsProperty<CheckboxThemeData>('checkboxTheme', checkboxTheme, defaultValue: defaultData.checkboxTheme, level: DiagnosticLevel.debug));
properties.add(DiagnosticsProperty<ChipThemeData>('chipTheme', chipTheme, level: DiagnosticLevel.debug));
properties.add(DiagnosticsProperty<DataTableThemeData>('dataTableTheme', dataTableTheme, defaultValue: defaultData.dataTableTheme, level: DiagnosticLevel.debug));
+ properties.add(DiagnosticsProperty<DatePickerThemeData>('datePickerTheme', datePickerTheme, defaultValue: defaultData.datePickerTheme, level: DiagnosticLevel.debug));
properties.add(DiagnosticsProperty<DialogTheme>('dialogTheme', dialogTheme, defaultValue: defaultData.dialogTheme, level: DiagnosticLevel.debug));
properties.add(DiagnosticsProperty<DividerThemeData>('dividerTheme', dividerTheme, defaultValue: defaultData.dividerTheme, level: DiagnosticLevel.debug));
properties.add(DiagnosticsProperty<DrawerThemeData>('drawerTheme', drawerTheme, defaultValue: defaultData.drawerTheme, level: DiagnosticLevel.debug));
diff --git a/packages/flutter/test/material/date_picker_theme_test.dart b/packages/flutter/test/material/date_picker_theme_test.dart
new file mode 100644
index 0000000..c204771
--- /dev/null
+++ b/packages/flutter/test/material/date_picker_theme_test.dart
@@ -0,0 +1,426 @@
+// 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 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+void main() {
+ const DatePickerThemeData datePickerTheme = DatePickerThemeData(
+ backgroundColor: Color(0xfffffff0),
+ elevation: 6,
+ shadowColor: Color(0xfffffff1),
+ surfaceTintColor: Color(0xfffffff2),
+ shape: RoundedRectangleBorder(),
+ headerBackgroundColor: Color(0xfffffff3),
+ headerForegroundColor: Color(0xfffffff4),
+ headerHeadlineStyle: TextStyle(fontSize: 10),
+ headerHelpStyle: TextStyle(fontSize: 11),
+ weekdayStyle: TextStyle(fontSize: 12),
+ dayStyle: TextStyle(fontSize: 13),
+ dayForegroundColor: MaterialStatePropertyAll<Color>(Color(0xfffffff5)),
+ dayBackgroundColor: MaterialStatePropertyAll<Color>(Color(0xfffffff6)),
+ dayOverlayColor: MaterialStatePropertyAll<Color>(Color(0xfffffff7)),
+ todayForegroundColor: MaterialStatePropertyAll<Color>(Color(0xfffffff8)),
+ todayBackgroundColor: MaterialStatePropertyAll<Color>(Color(0xfffffff9)),
+ todayBorder: BorderSide(width: 3),
+ yearStyle: TextStyle(fontSize: 13),
+ yearForegroundColor: MaterialStatePropertyAll<Color>(Color(0xfffffffa)),
+ yearBackgroundColor: MaterialStatePropertyAll<Color>(Color(0xfffffffb)),
+ yearOverlayColor: MaterialStatePropertyAll<Color>(Color(0xfffffffc)),
+ rangePickerBackgroundColor: Color(0xfffffffd),
+ rangePickerElevation: 7,
+ rangePickerShadowColor: Color(0xfffffffe),
+ rangePickerSurfaceTintColor: Color(0xffffffff),
+ rangePickerShape: RoundedRectangleBorder(),
+ rangePickerHeaderBackgroundColor: Color(0xffffff0f),
+ rangePickerHeaderForegroundColor: Color(0xffffff1f),
+ rangePickerHeaderHeadlineStyle: TextStyle(fontSize: 14),
+ rangePickerHeaderHelpStyle: TextStyle(fontSize: 15),
+ rangeSelectionBackgroundColor: Color(0xffffff2f),
+ rangeSelectionOverlayColor: MaterialStatePropertyAll<Color>(Color(0xffffff3f)),
+ );
+
+ Material findDialogMaterial(WidgetTester tester) {
+ return tester.widget<Material>(
+ find.descendant(
+ of: find.byType(Dialog),
+ matching: find.byType(Material)
+ ).first
+ );
+ }
+
+ Material findHeaderMaterial(WidgetTester tester, String text) {
+ return tester.widget<Material>(
+ find.ancestor(
+ of: find.text(text),
+ matching: find.byType(Material)
+ ).first,
+ );
+ }
+
+ BoxDecoration? findTextDecoration(WidgetTester tester, String date) {
+ final Container container = tester.widget<Container>(
+ find.ancestor(
+ of: find.text(date),
+ matching: find.byType(Container)
+ ).first,
+ );
+ return container.decoration as BoxDecoration?;
+ }
+
+ test('DatePickerThemeData copyWith, ==, hashCode basics', () {
+ expect(const DatePickerThemeData(), const DatePickerThemeData().copyWith());
+ expect(const DatePickerThemeData().hashCode, const DatePickerThemeData().copyWith().hashCode);
+ });
+
+ test('DatePickerThemeData defaults', () {
+ const DatePickerThemeData theme = DatePickerThemeData();
+ expect(theme.backgroundColor, null);
+ expect(theme.elevation, null);
+ expect(theme.shadowColor, null);
+ expect(theme.surfaceTintColor, null);
+ expect(theme.shape, null);
+ expect(theme.headerBackgroundColor, null);
+ expect(theme.headerForegroundColor, null);
+ expect(theme.headerHeadlineStyle, null);
+ expect(theme.headerHelpStyle, null);
+ expect(theme.weekdayStyle, null);
+ expect(theme.dayStyle, null);
+ expect(theme.dayForegroundColor, null);
+ expect(theme.dayBackgroundColor, null);
+ expect(theme.dayOverlayColor, null);
+ expect(theme.todayForegroundColor, null);
+ expect(theme.todayBackgroundColor, null);
+ expect(theme.todayBorder, null);
+ expect(theme.yearStyle, null);
+ expect(theme.yearForegroundColor, null);
+ expect(theme.yearBackgroundColor, null);
+ expect(theme.yearOverlayColor, null);
+ expect(theme.rangePickerBackgroundColor, null);
+ expect(theme.rangePickerElevation, null);
+ expect(theme.rangePickerShadowColor, null);
+ expect(theme.rangePickerSurfaceTintColor, null);
+ expect(theme.rangePickerShape, null);
+ expect(theme.rangePickerHeaderBackgroundColor, null);
+ expect(theme.rangePickerHeaderForegroundColor, null);
+ expect(theme.rangePickerHeaderHeadlineStyle, null);
+ expect(theme.rangePickerHeaderHelpStyle, null);
+ expect(theme.rangeSelectionBackgroundColor, null);
+ expect(theme.rangeSelectionOverlayColor, null);
+ });
+
+ testWidgets('DatePickerTheme.defaults M3 defaults', (WidgetTester tester) async {
+ late final DatePickerThemeData m3; // M3 Defaults
+ late final ThemeData theme;
+ late final ColorScheme colorScheme;
+ late final TextTheme textTheme;
+
+ await tester.pumpWidget(
+ MaterialApp(
+ theme: ThemeData.light(useMaterial3: true),
+ home: Builder(
+ builder: (BuildContext context) {
+ m3 = DatePickerTheme.defaults(context);
+ theme = Theme.of(context);
+ colorScheme = theme.colorScheme;
+ textTheme = theme.textTheme;
+ return Container();
+ },
+ ),
+ ),
+ );
+
+ expect(m3.backgroundColor, colorScheme.surface);
+ expect(m3.elevation, 6);
+ expect(m3.shadowColor, const Color(0x00000000)); // Colors.transparent
+ expect(m3.surfaceTintColor, colorScheme.surfaceTint);
+ expect(m3.shape, RoundedRectangleBorder(borderRadius: BorderRadius.circular(28)));
+ expect(m3.headerBackgroundColor, const Color(0x00000000)); // Colors.transparent
+ expect(m3.headerForegroundColor, colorScheme.onSurfaceVariant);
+ expect(m3.headerHeadlineStyle, textTheme.headlineLarge);
+ expect(m3.headerHelpStyle, textTheme.labelMedium);
+ expect(m3.weekdayStyle, textTheme.bodySmall?.apply(color: colorScheme.onSurface));
+ expect(m3.dayStyle, textTheme.bodySmall);
+ expect(m3.dayForegroundColor?.resolve(<MaterialState>{}), colorScheme.onSurface);
+ expect(m3.dayForegroundColor?.resolve(<MaterialState>{MaterialState.selected}), colorScheme.onPrimary);
+ expect(m3.dayForegroundColor?.resolve(<MaterialState>{MaterialState.disabled}), colorScheme.onSurface.withOpacity(0.38));
+ expect(m3.dayBackgroundColor?.resolve(<MaterialState>{}), null);
+ expect(m3.dayBackgroundColor?.resolve(<MaterialState>{MaterialState.selected}), colorScheme.primary);
+ expect(m3.dayOverlayColor?.resolve(<MaterialState>{}), null);
+ expect(m3.dayOverlayColor?.resolve(<MaterialState>{MaterialState.selected, MaterialState.hovered}), colorScheme.onPrimary.withOpacity(0.08));
+ expect(m3.dayOverlayColor?.resolve(<MaterialState>{MaterialState.selected, MaterialState.focused}), colorScheme.onPrimary.withOpacity(0.12));
+ expect(m3.dayOverlayColor?.resolve(<MaterialState>{MaterialState.hovered}), colorScheme.onSurfaceVariant.withOpacity(0.08));
+ expect(m3.dayOverlayColor?.resolve(<MaterialState>{MaterialState.focused}), colorScheme.onSurfaceVariant.withOpacity(0.12));
+ expect(m3.dayOverlayColor?.resolve(<MaterialState>{MaterialState.pressed}), colorScheme.onSurfaceVariant.withOpacity(0.12));
+ expect(m3.todayForegroundColor?.resolve(<MaterialState>{}), colorScheme.primary);
+ expect(m3.todayForegroundColor?.resolve(<MaterialState>{MaterialState.disabled}), colorScheme.primary.withOpacity(0.38));
+ expect(m3.todayBorder, BorderSide(color: colorScheme.primary));
+ expect(m3.yearStyle, textTheme.bodyLarge);
+ expect(m3.yearForegroundColor?.resolve(<MaterialState>{}), colorScheme.onSurfaceVariant);
+ expect(m3.yearForegroundColor?.resolve(<MaterialState>{MaterialState.selected}), colorScheme.onPrimary);
+ expect(m3.yearForegroundColor?.resolve(<MaterialState>{MaterialState.disabled}), colorScheme.onSurfaceVariant.withOpacity(0.38));
+ expect(m3.yearBackgroundColor?.resolve(<MaterialState>{}), null);
+ expect(m3.yearBackgroundColor?.resolve(<MaterialState>{MaterialState.selected}), colorScheme.primary);
+ expect(m3.yearOverlayColor?.resolve(<MaterialState>{}), null);
+ expect(m3.yearOverlayColor?.resolve(<MaterialState>{MaterialState.selected, MaterialState.hovered}), colorScheme.onPrimary.withOpacity(0.08));
+ expect(m3.yearOverlayColor?.resolve(<MaterialState>{MaterialState.selected, MaterialState.focused}), colorScheme.onPrimary.withOpacity(0.12));
+ expect(m3.yearOverlayColor?.resolve(<MaterialState>{MaterialState.hovered}), colorScheme.onSurfaceVariant.withOpacity(0.08));
+ expect(m3.yearOverlayColor?.resolve(<MaterialState>{MaterialState.focused}), colorScheme.onSurfaceVariant.withOpacity(0.12));
+ expect(m3.yearOverlayColor?.resolve(<MaterialState>{MaterialState.pressed}), colorScheme.onSurfaceVariant.withOpacity(0.12));
+ expect(m3.rangePickerElevation, 0);
+ expect(m3.rangePickerShape, const RoundedRectangleBorder());
+ expect(m3.rangePickerShadowColor, Colors.transparent);
+ expect(m3.rangePickerSurfaceTintColor, Colors.transparent);
+ expect(m3.rangeSelectionOverlayColor?.resolve(<MaterialState>{}), null);
+ expect(m3.rangePickerHeaderBackgroundColor, Colors.transparent);
+ expect(m3.rangePickerHeaderForegroundColor, colorScheme.onSurfaceVariant);
+ expect(m3.rangePickerHeaderHeadlineStyle, textTheme.titleLarge);
+ expect(m3.rangePickerHeaderHelpStyle, textTheme.titleSmall);
+ });
+
+
+ testWidgets('DatePickerTheme.defaults M2 defaults', (WidgetTester tester) async {
+ late final DatePickerThemeData m2; // M2 defaults
+ late final ThemeData theme;
+ late final ColorScheme colorScheme;
+ late final TextTheme textTheme;
+
+ await tester.pumpWidget(
+ MaterialApp(
+ theme: ThemeData.light(useMaterial3: false),
+ home: Builder(
+ builder: (BuildContext context) {
+ m2 = DatePickerTheme.defaults(context);
+ theme = Theme.of(context);
+ colorScheme = theme.colorScheme;
+ textTheme = theme.textTheme;
+ return Container();
+ },
+ ),
+ ),
+ );
+
+ expect(m2.elevation, 24);
+ expect(m2.shape, const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4.0))));
+ expect(m2.headerBackgroundColor, colorScheme.primary);
+ expect(m2.headerForegroundColor, colorScheme.onPrimary);
+ expect(m2.headerHeadlineStyle, textTheme.headlineSmall);
+ expect(m2.headerHelpStyle, textTheme.labelSmall);
+ expect(m2.weekdayStyle, textTheme.bodySmall?.apply(color: colorScheme.onSurface.withOpacity(0.60)));
+ expect(m2.dayStyle, textTheme.bodySmall);
+ expect(m2.dayForegroundColor?.resolve(<MaterialState>{}), colorScheme.onSurface);
+ expect(m2.dayForegroundColor?.resolve(<MaterialState>{MaterialState.selected}), colorScheme.onPrimary);
+ expect(m2.dayForegroundColor?.resolve(<MaterialState>{MaterialState.disabled}), colorScheme.onSurface.withOpacity(0.38));
+ expect(m2.dayBackgroundColor?.resolve(<MaterialState>{}), null);
+ expect(m2.dayBackgroundColor?.resolve(<MaterialState>{MaterialState.selected}), colorScheme.primary);
+ expect(m2.dayOverlayColor?.resolve(<MaterialState>{}), null);
+ expect(m2.dayOverlayColor?.resolve(<MaterialState>{MaterialState.selected, MaterialState.hovered}), colorScheme.onPrimary.withOpacity(0.08));
+ expect(m2.dayOverlayColor?.resolve(<MaterialState>{MaterialState.selected, MaterialState.focused}), colorScheme.onPrimary.withOpacity(0.12));
+ expect(m2.dayOverlayColor?.resolve(<MaterialState>{MaterialState.selected, MaterialState.pressed}), colorScheme.onPrimary.withOpacity(0.38));
+ expect(m2.dayOverlayColor?.resolve(<MaterialState>{MaterialState.hovered}), colorScheme.onSurfaceVariant.withOpacity(0.08));
+ expect(m2.dayOverlayColor?.resolve(<MaterialState>{MaterialState.focused}), colorScheme.onSurfaceVariant.withOpacity(0.12));
+ expect(m2.dayOverlayColor?.resolve(<MaterialState>{MaterialState.pressed}), colorScheme.onSurfaceVariant.withOpacity(0.12));
+ expect(m2.todayForegroundColor?.resolve(<MaterialState>{}), colorScheme.primary);
+ expect(m2.todayForegroundColor?.resolve(<MaterialState>{MaterialState.disabled}), colorScheme.onSurface.withOpacity(0.38));
+ expect(m2.todayBorder, BorderSide(color: colorScheme.primary));
+ expect(m2.yearStyle, textTheme.bodyLarge);
+ expect(m2.rangePickerBackgroundColor, colorScheme.surface);
+ expect(m2.rangePickerElevation, 0);
+ expect(m2.rangePickerShape, const RoundedRectangleBorder());
+ expect(m2.rangePickerShadowColor, Colors.transparent);
+ expect(m2.rangePickerSurfaceTintColor, Colors.transparent);
+ expect(m2.rangeSelectionOverlayColor?.resolve(<MaterialState>{}), null);
+ expect(m2.rangeSelectionOverlayColor?.resolve(<MaterialState>{MaterialState.selected, MaterialState.hovered}), colorScheme.onPrimary.withOpacity(0.08));
+ expect(m2.rangeSelectionOverlayColor?.resolve(<MaterialState>{MaterialState.selected, MaterialState.focused}), colorScheme.onPrimary.withOpacity(0.12));
+ expect(m2.rangeSelectionOverlayColor?.resolve(<MaterialState>{MaterialState.selected, MaterialState.pressed}), colorScheme.onPrimary.withOpacity(0.38));
+ expect(m2.rangeSelectionOverlayColor?.resolve(<MaterialState>{MaterialState.hovered}), colorScheme.onSurfaceVariant.withOpacity(0.08));
+ expect(m2.rangeSelectionOverlayColor?.resolve(<MaterialState>{MaterialState.focused}), colorScheme.onSurfaceVariant.withOpacity(0.12));
+ expect(m2.rangeSelectionOverlayColor?.resolve(<MaterialState>{MaterialState.pressed}), colorScheme.onSurfaceVariant.withOpacity(0.12));
+ expect(m2.rangePickerHeaderBackgroundColor, colorScheme.primary);
+ expect(m2.rangePickerHeaderForegroundColor, colorScheme.onPrimary);
+ expect(m2.rangePickerHeaderHeadlineStyle, textTheme.headlineSmall);
+ expect(m2.rangePickerHeaderHelpStyle, textTheme.labelSmall);
+ });
+
+ testWidgets('Default DatePickerThemeData debugFillProperties', (WidgetTester tester) async {
+ final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
+ const DatePickerThemeData().debugFillProperties(builder);
+
+ final List<String> description = builder.properties
+ .where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
+ .map((DiagnosticsNode node) => node.toString())
+ .toList();
+
+ expect(description, <String>[]);
+ });
+
+ testWidgets('DatePickerThemeData implements debugFillProperties', (WidgetTester tester) async {
+ final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
+
+ datePickerTheme.debugFillProperties(builder);
+
+ final List<String> description = builder.properties
+ .where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
+ .map((DiagnosticsNode node) => node.toString())
+ .toList();
+
+ expect(description, <String>[
+ 'backgroundColor: Color(0xfffffff0)',
+ 'elevation: 6.0',
+ 'shadowColor: Color(0xfffffff1)',
+ 'surfaceTintColor: Color(0xfffffff2)',
+ 'shape: RoundedRectangleBorder(BorderSide(width: 0.0, style: none), BorderRadius.zero)',
+ 'headerBackgroundColor: Color(0xfffffff3)',
+ 'headerForegroundColor: Color(0xfffffff4)',
+ 'headerHeadlineStyle: TextStyle(inherit: true, size: 10.0)',
+ 'headerHelpStyle: TextStyle(inherit: true, size: 11.0)',
+ 'weekDayStyle: TextStyle(inherit: true, size: 12.0)',
+ 'dayStyle: TextStyle(inherit: true, size: 13.0)',
+ 'dayForegroundColor: MaterialStatePropertyAll(Color(0xfffffff5))',
+ 'dayBackgroundColor: MaterialStatePropertyAll(Color(0xfffffff6))',
+ 'dayOverlayColor: MaterialStatePropertyAll(Color(0xfffffff7))',
+ 'todayForegroundColor: MaterialStatePropertyAll(Color(0xfffffff8))',
+ 'todayBackgroundColor: MaterialStatePropertyAll(Color(0xfffffff9))',
+ 'todayBorder: BorderSide(width: 3.0)',
+ 'yearStyle: TextStyle(inherit: true, size: 13.0)',
+ 'yearForegroundColor: MaterialStatePropertyAll(Color(0xfffffffa))',
+ 'yearBackgroundColor: MaterialStatePropertyAll(Color(0xfffffffb))',
+ 'yearOverlayColor: MaterialStatePropertyAll(Color(0xfffffffc))',
+ 'rangePickerBackgroundColor: Color(0xfffffffd)',
+ 'rangePickerElevation: 7.0',
+ 'rangePickerShadowColor: Color(0xfffffffe)',
+ 'rangePickerSurfaceTintColor: Color(0xffffffff)',
+ 'rangePickerShape: RoundedRectangleBorder(BorderSide(width: 0.0, style: none), BorderRadius.zero)',
+ 'rangePickerHeaderBackgroundColor: Color(0xffffff0f)',
+ 'rangePickerHeaderForegroundColor: Color(0xffffff1f)',
+ 'rangePickerHeaderHeadlineStyle: TextStyle(inherit: true, size: 14.0)',
+ 'rangePickerHeaderHelpStyle: TextStyle(inherit: true, size: 15.0)',
+ 'rangeSelectionBackgroundColor: Color(0xffffff2f)',
+ 'rangeSelectionOverlayColor: MaterialStatePropertyAll(Color(0xffffff3f))',
+ ]);
+ });
+
+ testWidgets('DatePickerDialog uses ThemeData datePicker theme', (WidgetTester tester) async {
+ await tester.pumpWidget(
+ MaterialApp(
+ theme: ThemeData.light(useMaterial3: true).copyWith(
+ datePickerTheme: datePickerTheme,
+ ),
+ home: Directionality(
+ textDirection: TextDirection.ltr,
+ child: Material(
+ child: Center(
+ child: DatePickerDialog(
+ initialDate: DateTime(2023, DateTime.january, 25),
+ firstDate: DateTime(2022),
+ lastDate: DateTime(2024, DateTime.december, 31),
+ currentDate: DateTime(2023, DateTime.january, 24),
+ ),
+ ),
+ ),
+ ),
+ ),
+ );
+
+ final Material material = findDialogMaterial(tester);
+ expect(material.color, datePickerTheme.backgroundColor);
+ expect(material.elevation, datePickerTheme.elevation);
+ expect(material.shadowColor, datePickerTheme.shadowColor);
+ expect(material.surfaceTintColor, datePickerTheme.surfaceTintColor);
+ expect(material.shape, datePickerTheme.shape);
+
+ final Text selectDate = tester.widget<Text>(find.text('Select date'));
+ final Material headerMaterial = findHeaderMaterial(tester, 'Select date');
+ expect(selectDate.style?.color, datePickerTheme.headerForegroundColor);
+ expect(selectDate.style?.fontSize, datePickerTheme.headerHelpStyle?.fontSize);
+ expect(headerMaterial.color, datePickerTheme.headerBackgroundColor);
+
+ final Text weekday = tester.widget<Text>(find.text('W'));
+ expect(weekday.style?.color, datePickerTheme.weekdayStyle?.color);
+ expect(weekday.style?.fontSize, datePickerTheme.weekdayStyle?.fontSize);
+
+ final Text selectedDate = tester.widget<Text>(find.text('Wed, Jan 25'));
+ expect(selectedDate.style?.color, datePickerTheme.headerForegroundColor);
+ expect(selectedDate.style?.fontSize, datePickerTheme.headerHeadlineStyle?.fontSize);
+
+ final Text day31 = tester.widget<Text>(find.text('31'));
+ final BoxDecoration day31Decoration = findTextDecoration(tester, '31')!;
+ expect(day31.style?.color, datePickerTheme.dayForegroundColor?.resolve(<MaterialState>{}));
+ expect(day31.style?.fontSize, datePickerTheme.dayStyle?.fontSize);
+ expect(day31Decoration.color, datePickerTheme.dayBackgroundColor?.resolve(<MaterialState>{}));
+
+ final Text day24 = tester.widget<Text>(find.text('24')); // DatePickerDialog.currentDate
+ final BoxDecoration day24Decoration = findTextDecoration(tester, '24')!;
+ expect(day24.style?.fontSize, datePickerTheme.dayStyle?.fontSize);
+ expect(day24.style?.color, datePickerTheme.todayForegroundColor?.resolve(<MaterialState>{}));
+ expect(day24Decoration.color, datePickerTheme.todayBackgroundColor?.resolve(<MaterialState>{}));
+ expect(day24Decoration.border?.top.width, datePickerTheme.todayBorder?.width);
+ expect(day24Decoration.border?.bottom.width, datePickerTheme.todayBorder?.width);
+
+ // Show the year selector.
+
+ await tester.tap(find.text('January 2023'));
+ await tester.pumpAndSettle();
+
+ final Text year2022 = tester.widget<Text>(find.text('2022'));
+ final BoxDecoration year2022Decoration = findTextDecoration(tester, '2022')!;
+ expect(year2022.style?.fontSize, datePickerTheme.yearStyle?.fontSize);
+ expect(year2022.style?.color, datePickerTheme.yearForegroundColor?.resolve(<MaterialState>{}));
+ expect(year2022Decoration.color, datePickerTheme.yearBackgroundColor?.resolve(<MaterialState>{}));
+
+ final Text year2023 = tester.widget<Text>(find.text('2023')); // DatePickerDialog.currentDate
+ final BoxDecoration year2023Decoration = findTextDecoration(tester, '2023')!;
+ expect(year2023.style?.fontSize, datePickerTheme.yearStyle?.fontSize);
+ expect(year2023.style?.color, datePickerTheme.todayForegroundColor?.resolve(<MaterialState>{}));
+ expect(year2023Decoration.color, datePickerTheme.todayBackgroundColor?.resolve(<MaterialState>{}));
+ expect(year2023Decoration.border?.top.width, datePickerTheme.todayBorder?.width);
+ expect(year2023Decoration.border?.bottom.width, datePickerTheme.todayBorder?.width);
+ expect(year2023Decoration.border?.top.color, datePickerTheme.todayForegroundColor?.resolve(<MaterialState>{}));
+ expect(year2023Decoration.border?.bottom.color, datePickerTheme.todayForegroundColor?.resolve(<MaterialState>{}));
+ });
+
+
+ testWidgets('DateRangePickerDialog uses ThemeData datePicker theme', (WidgetTester tester) async {
+ await tester.pumpWidget(
+ MaterialApp(
+ theme: ThemeData.light(useMaterial3: true).copyWith(
+ datePickerTheme: datePickerTheme,
+ ),
+ home: Directionality(
+ textDirection: TextDirection.ltr,
+ child: Material(
+ child: Center(
+ child: DateRangePickerDialog(
+ firstDate: DateTime(2023),
+ lastDate: DateTime(2023, DateTime.january, 31),
+ initialDateRange: DateTimeRange(
+ start: DateTime(2023, DateTime.january, 17),
+ end: DateTime(2023, DateTime.january, 20),
+ ),
+ currentDate: DateTime(2023, DateTime.january, 23),
+ ),
+ ),
+ ),
+ ),
+ ),
+ );
+
+ final Material material = findDialogMaterial(tester);
+ expect(material.color, datePickerTheme.backgroundColor); //!!
+ expect(tester.widget<Scaffold>(find.byType(Scaffold)).backgroundColor, datePickerTheme.rangePickerBackgroundColor);
+ expect(material.elevation, datePickerTheme.rangePickerElevation);
+ expect(material.shadowColor, datePickerTheme.rangePickerShadowColor);
+ expect(material.surfaceTintColor, datePickerTheme.rangePickerSurfaceTintColor);
+ expect(material.shape, datePickerTheme.rangePickerShape);
+
+ final Text selectRange = tester.widget<Text>(find.text('Select range'));
+ expect(selectRange.style?.color, datePickerTheme.rangePickerHeaderForegroundColor);
+ expect(selectRange.style?.fontSize, datePickerTheme.rangePickerHeaderHelpStyle?.fontSize);
+
+ final Text selectedDate = tester.widget<Text>(find.text('Jan 17'));
+ expect(selectedDate.style?.color, datePickerTheme.rangePickerHeaderForegroundColor);
+ expect(selectedDate.style?.fontSize, datePickerTheme.rangePickerHeaderHeadlineStyle?.fontSize);
+ });
+}
diff --git a/packages/flutter/test/material/theme_data_test.dart b/packages/flutter/test/material/theme_data_test.dart
index bd255e5..07eb2b4 100644
--- a/packages/flutter/test/material/theme_data_test.dart
+++ b/packages/flutter/test/material/theme_data_test.dart
@@ -781,6 +781,7 @@
checkboxTheme: const CheckboxThemeData(),
chipTheme: chipTheme,
dataTableTheme: const DataTableThemeData(),
+ datePickerTheme: const DatePickerThemeData(),
dialogTheme: const DialogTheme(backgroundColor: Colors.black),
dividerTheme: const DividerThemeData(color: Colors.black),
drawerTheme: const DrawerThemeData(),
@@ -901,6 +902,7 @@
checkboxTheme: const CheckboxThemeData(),
chipTheme: otherChipTheme,
dataTableTheme: const DataTableThemeData(),
+ datePickerTheme: const DatePickerThemeData(backgroundColor: Colors.amber),
dialogTheme: const DialogTheme(backgroundColor: Colors.white),
dividerTheme: const DividerThemeData(color: Colors.white),
drawerTheme: const DrawerThemeData(),
@@ -1008,6 +1010,7 @@
chipTheme: otherTheme.chipTheme,
dataTableTheme: otherTheme.dataTableTheme,
dialogTheme: otherTheme.dialogTheme,
+ datePickerTheme: otherTheme.datePickerTheme,
dividerTheme: otherTheme.dividerTheme,
drawerTheme: otherTheme.drawerTheme,
elevatedButtonTheme: otherTheme.elevatedButtonTheme,
@@ -1110,6 +1113,7 @@
expect(themeDataCopy.checkboxTheme, equals(otherTheme.checkboxTheme));
expect(themeDataCopy.chipTheme, equals(otherTheme.chipTheme));
expect(themeDataCopy.dataTableTheme, equals(otherTheme.dataTableTheme));
+ expect(themeDataCopy.datePickerTheme, equals(otherTheme.datePickerTheme));
expect(themeDataCopy.dialogTheme, equals(otherTheme.dialogTheme));
expect(themeDataCopy.dividerTheme, equals(otherTheme.dividerTheme));
expect(themeDataCopy.drawerTheme, equals(otherTheme.drawerTheme));
@@ -1248,6 +1252,7 @@
'checkboxTheme',
'chipTheme',
'dataTableTheme',
+ 'datePickerTheme',
'dialogTheme',
'dividerTheme',
'drawerTheme',