Unverified Commit 198a51ac authored by Hans Muller's avatar Hans Muller Committed by GitHub

Migrate the Material Date pickers to M3 Reprise (#119033)

parent 38843814
......@@ -27,6 +27,7 @@ import 'package:gen_defaults/button_template.dart';
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 @@ Future<void> main(List<String> args) async {
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();
......
// 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")};
}
''';
}
......@@ -65,6 +65,7 @@ export 'src/material/data_table_source.dart';
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';
......
......@@ -11,12 +11,14 @@ import 'package:flutter/widgets.dart';
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 @@ class _CalendarDatePickerState extends State<CalendarDatePicker> {
firstDate: widget.firstDate,
lastDate: widget.lastDate,
initialDate: _currentDisplayedMonthDate,
selectedDate: _selectedDate,
selectedDate: _currentDisplayedMonthDate,
onChanged: _handleYearChanged,
),
);
......@@ -920,18 +922,11 @@ class _DayPickerState extends State<_DayPicker> {
@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 @@ class _DayPickerState extends State<_DayPicker> {
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 @@ class _DayPickerState extends State<_DayPicker> {
dayItems.add(Container());
} else {
final DateTime dayToBuild = DateTime(year, month, day);
final bool isDisabled = dayToBuild.isAfter(widget.lastDate) ||
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,
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)),
);
} 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),
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,
);
} else if (isDisabled) {
dayColor = disabledDayColor;
}
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 @@ class _DayPickerState extends State<_DayPicker> {
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 @@ class _YearPickerState extends State<YearPicker> {
}
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 @@ class _YearPickerState extends State<YearPicker> {
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,
),
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 @@ class _YearPickerState extends State<YearPicker> {
yearItem = InkWell(
key: ValueKey<int>(year),
onTap: () => widget.onChanged(DateTime(year, widget.initialDate.month)),
statesController: MaterialStatesController(states),
overlayColor: overlayColor,
child: yearItem,
);
}
......
......@@ -10,9 +10,12 @@ import 'package:flutter/widgets.dart';
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_date_picker_form_field.dart';
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 @@ class _DatePickerDialogState extends State<DatePickerDialog> with RestorationMix
}
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 @@ class _DatePickerDialogState extends State<DatePickerDialog> with RestorationMix
case DatePickerEntryMode.inputOnly:
switch (orientation) {
case Orientation.portrait:
return _inputPortraitDialogSize;
return useMaterial3 ? _inputPortraitDialogSizeM3 : _inputPortraitDialogSizeM2;
case Orientation.landscape:
return _inputLandscapeDialogSize;
}
......@@ -441,21 +451,28 @@ class _DatePickerDialogState extends State<DatePickerDialog> with RestorationMix
@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 @@ class _DatePickerDialogState extends State<DatePickerDialog> with RestorationMix
TextButton(
onPressed: _handleCancel,
child: Text(widget.cancelText ?? (
theme.useMaterial3
useMaterial3
? localizations.cancelButtonLabel
: localizations.cancelButtonLabel.toUpperCase()
)),
......@@ -533,8 +550,8 @@ class _DatePickerDialogState extends State<DatePickerDialog> with RestorationMix
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 @@ class _DatePickerDialogState extends State<DatePickerDialog> with RestorationMix
picker = inputDatePicker();
entryModeButton = IconButton(
icon: const Icon(Icons.calendar_today),
color: onPrimarySurface,
color: headerForegroundColor,
tooltip: localizations.calendarModeButtonLabel,
onPressed: _handleEntryModeToggle,
);
......@@ -563,19 +580,29 @@ class _DatePickerDialogState extends State<DatePickerDialog> with RestorationMix
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 @@ class _DatePickerDialogState extends State<DatePickerDialog> with RestorationMix
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
header,
if (useMaterial3) const Divider(),
Expanded(child: picker),
actions,
],
......@@ -605,6 +633,7 @@ class _DatePickerDialogState extends State<DatePickerDialog> with RestorationMix
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
header,
if (useMaterial3) const VerticalDivider(),
Flexible(
child: Column(
mainAxisSize: MainAxisSize.min,
......@@ -736,17 +765,12 @@ class _DatePickerHeader extends StatelessWidget {
@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 @@ class _DatePickerHeader extends StatelessWidget {
return SizedBox(
height: _datePickerHeaderPortraitHeight,
child: Material(
color: primarySurfaceColor,
color: backgroundColor,
child: Padding(
padding: const EdgeInsetsDirectional.only(
start: 24,
......@@ -796,7 +820,7 @@ class _DatePickerHeader extends StatelessWidget {
return SizedBox(
width: _datePickerHeaderLandscapeWidth,
child: Material(
color: primarySurfaceColor,
color: backgroundColor,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
......@@ -1292,18 +1316,20 @@ class _DateRangePickerDialogState extends State<DateRangePickerDialog> with Rest
@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 @@ class _DateRangePickerDialogState extends State<DateRangePickerDialog> with Rest
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 @@ class _DateRangePickerDialogState extends State<DateRangePickerDialog> with Rest
? 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 @@ class _CalendarRangePickerDialog extends StatelessWidget {
@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 @@ class _CalendarRangePickerDialog extends StatelessWidget {
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 @@ class _CalendarRangePickerDialog extends StatelessWidget {
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 @@ class _CalendarRangePickerDialog extends StatelessWidget {
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 @@ class _CalendarRangePickerDialog extends StatelessWidget {
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 @@ class _MonthItemState extends State<_MonthItem> {
}
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 @@ class _MonthItemState extends State<_MonthItem> {
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 @@ class _MonthItemState extends State<_MonthItem> {
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 @@ class _MonthItemState extends State<_MonthItem> {
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 @@ class _MonthItemState extends State<_MonthItem> {
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 @@ class _InputDateRangePickerDialog extends StatelessWidget {
@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? headerForegroundColor = datePickerTheme.headerForegroundColor ?? defaults.headerForegroundColor;
final TextStyle? headlineStyle = (datePickerTheme.headerHeadlineStyle ?? defaults.headerHeadlineStyle)?.copyWith(
color: headerForegroundColor,
);
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 String dateText = _formatDateRange(context, selectedStartDate, selectedEndDate, currentDate!);
final String semanticDateText = selectedStartDate != null && selectedEndDate != null
? '${localizations.formatMediumDate(selectedStartDate!)}${localizations.formatMediumDate(selectedEndDate!)}'
......@@ -2585,13 +2661,13 @@ class _InputDateRangePickerDialog extends StatelessWidget {
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 @@ class _InputDateRangePickerDialog extends StatelessWidget {
TextButton(
onPressed: onCancel,
child: Text(cancelText ?? (
theme.useMaterial3
useMaterial3
? localizations.cancelButtonLabel
: localizations.cancelButtonLabel.toUpperCase()
)),
......@@ -2856,8 +2932,13 @@ class _InputDateRangePickerState extends State<_InputDateRangePicker> {
@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 @@ class _InputDateRangePickerState extends State<_InputDateRangePicker> {
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 @@ class _InputDateRangePickerState extends State<_InputDateRangePicker> {
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,
......
// 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
......@@ -234,11 +234,16 @@ class _InputDatePickerFormFieldState extends State<InputDatePickerFormField> {
@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,
......
......@@ -254,6 +254,7 @@ class Material extends StatefulWidget {
/// 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 @@ class Material extends StatefulWidget {
/// 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 @@ class Material extends StatefulWidget {
/// 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 @@ class Material extends StatefulWidget {
/// 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].
......
......@@ -22,6 +22,7 @@ import 'color_scheme.dart';
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 @@ class ThemeData with Diagnosticable {
CheckboxThemeData? checkboxTheme,
ChipThemeData? chipTheme,
DataTableThemeData? dataTableTheme,
DatePickerThemeData? datePickerTheme,
DialogTheme? dialogTheme,
DividerThemeData? dividerTheme,
DrawerThemeData? drawerTheme,
......@@ -600,6 +602,7 @@ class ThemeData with Diagnosticable {
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 @@ class ThemeData with Diagnosticable {
checkboxTheme: checkboxTheme,
chipTheme: chipTheme,
dataTableTheme: dataTableTheme,
datePickerTheme: datePickerTheme,
dialogTheme: dialogTheme,
dividerTheme: dividerTheme,
drawerTheme: drawerTheme,
......@@ -810,6 +814,7 @@ class ThemeData with Diagnosticable {
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 @@ class ThemeData with Diagnosticable {
/// - [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 @@ class ThemeData with Diagnosticable {
/// * 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 @@ class ThemeData with Diagnosticable {
/// 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 @@ class ThemeData with Diagnosticable {
CheckboxThemeData? checkboxTheme,
ChipThemeData? chipTheme,
DataTableThemeData? dataTableTheme,
DatePickerThemeData? datePickerTheme,
DialogTheme? dialogTheme,
DividerThemeData? dividerTheme,
DrawerThemeData? drawerTheme,
......@@ -1971,6 +1983,7 @@ class ThemeData with Diagnosticable {
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 @@ class ThemeData with Diagnosticable {
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 @@ class ThemeData with Diagnosticable {
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 @@ class ThemeData with Diagnosticable {
checkboxTheme,
chipTheme,
dataTableTheme,
datePickerTheme,
dialogTheme,
dividerTheme,
drawerTheme,
......@@ -2485,6 +2501,7 @@ class ThemeData with Diagnosticable {
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));
......
// 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);
});
}
......@@ -781,6 +781,7 @@ void main() {
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 @@ void main() {
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 @@ void main() {
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 @@ void main() {
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 @@ void main() {
'checkboxTheme',
'chipTheme',
'dataTableTheme',
'datePickerTheme',
'dialogTheme',
'dividerTheme',
'drawerTheme',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment