modify toggle mode style with DatePickerTheme (#164102)

- Add properties to DatePickerThemeData:
- `toggleModeStyle` allow customization on _DatePickerModeToggleButton
TextStyle;
- `toggleModeForegroundColor` allow changing the color of both button
and Icon with consistency;
- Compatible with existing code by the use of defaults;
- Adjust test to cover new properties

How to customize toggle mode style before:

```dart
  showDatePicker(
    context: context,
    firstDate: DateTime(2025),
    lastDate: DateTime(2026),
    builder:
        (context, child) => Theme(
          data: ThemeData.light().copyWith(
            textTheme: TextTheme(titleSmall: TextStyle(fontSize: 16)),
            datePickerTheme: DatePickerThemeData(
              dayStyle: TextStyle(fontSize: 12),
            ),
          ),
          child: child!,
        ),
  );
```

Now:

```dart
  showDatePicker(
    context: context,
    firstDate: DateTime(2025),
    lastDate: DateTime(2026),
    builder:
        (context, child) => DatePickerTheme(
          data: DatePickerThemeData(
            toggleModeStyle: TextStyle(fontSize: 16),
            dayStyle: TextStyle(fontSize: 12),
          ),
          child: child!,
        ),
  );
```

Ref #163866
Ref #160591

<!--
Thanks for filing a pull request!
Reviewers are typically assigned within a week of filing a request.
To learn more about code review, see our documentation on Tree Hygiene:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
-->

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
This commit is contained in:
Mairon Lucas 2025-04-11 16:38:16 -03:00 committed by GitHub
parent 37e79eb90c
commit dc7c2892d9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 156 additions and 13 deletions

View File

@ -132,4 +132,5 @@ dy0gu <support@dy0gu.com>
Albert Wolszon <albert@wolszon.me>
Mohammed Chahboun <m97.chahboun@gmail.com>
Abdessalem Mohellebi <mohellebiabdessalem@gmail.com>
Jin Jeongsu <jinjs.dev@gmail.com>
Jin Jeongsu <jinjs.dev@gmail.com>
Mairon Slusarz <maironlucaslusarz@gmail.com>

View File

@ -63,6 +63,14 @@ class _${blockName}DefaultsM3 extends DatePickerThemeData {
@override
Color? get backgroundColor => ${componentColor("md.comp.date-picker.modal.container")};
@override
Color? get subHeaderForegroundColor => ${componentColor("md.comp.date-picker.modal.weekdays.label-text")}.withOpacity(0.60);
@override
TextStyle? get toggleButtonTextStyle => ${textStyle("md.comp.date-picker.modal.range-selection.month.subhead")}?.apply(
color: subHeaderForegroundColor,
);
@override
ButtonStyle get cancelButtonStyle {
return TextButton.styleFrom();

View File

@ -13,7 +13,6 @@ import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'color_scheme.dart';
import 'date.dart';
import 'date_picker_theme.dart';
import 'debug.dart';
@ -24,7 +23,6 @@ import 'ink_decoration.dart';
import 'ink_well.dart';
import 'material_localizations.dart';
import 'material_state.dart';
import 'text_theme.dart';
import 'theme.dart';
const Duration _monthScrollDuration = Duration(milliseconds: 200);
@ -450,9 +448,16 @@ class _DatePickerModeToggleButtonState extends State<_DatePickerModeToggleButton
@override
Widget build(BuildContext context) {
final ColorScheme colorScheme = Theme.of(context).colorScheme;
final TextTheme textTheme = Theme.of(context).textTheme;
final Color controlColor = colorScheme.onSurface.withOpacity(0.60);
final DatePickerThemeData datePickerTheme = DatePickerTheme.of(context);
final DatePickerThemeData defaults = DatePickerTheme.defaults(context);
final TextStyle? buttonTextStyle =
datePickerTheme.toggleButtonTextStyle ?? defaults.toggleButtonTextStyle;
final Color? subHeaderForegroundColor =
datePickerTheme.subHeaderForegroundColor ?? defaults.subHeaderForegroundColor;
final Color? buttonTextColor =
datePickerTheme.toggleButtonTextStyle?.color ??
datePickerTheme.subHeaderForegroundColor ??
defaults.toggleButtonTextStyle?.color;
return SizedBox(
height: _subHeaderHeight,
@ -477,12 +482,12 @@ class _DatePickerModeToggleButtonState extends State<_DatePickerModeToggleButton
child: Text(
widget.title,
overflow: TextOverflow.ellipsis,
style: textTheme.titleSmall?.copyWith(color: controlColor),
style: buttonTextStyle?.apply(color: buttonTextColor),
),
),
RotationTransition(
turns: _controller,
child: Icon(Icons.arrow_drop_down, color: controlColor),
child: Icon(Icons.arrow_drop_down, color: subHeaderForegroundColor),
),
],
),
@ -826,7 +831,9 @@ class _MonthPickerState extends State<_MonthPicker> {
@override
Widget build(BuildContext context) {
final Color controlColor = Theme.of(context).colorScheme.onSurface.withOpacity(0.60);
final Color? subHeaderForegroundColor =
DatePickerTheme.of(context).subHeaderForegroundColor ??
DatePickerTheme.defaults(context).subHeaderForegroundColor;
return Semantics(
container: true,
@ -842,13 +849,13 @@ class _MonthPickerState extends State<_MonthPicker> {
const Spacer(),
IconButton(
icon: const Icon(Icons.chevron_left),
color: controlColor,
color: subHeaderForegroundColor,
tooltip: _isDisplayingFirstMonth ? null : _localizations.previousMonthTooltip,
onPressed: _isDisplayingFirstMonth ? null : _handlePreviousMonth,
),
IconButton(
icon: const Icon(Icons.chevron_right),
color: controlColor,
color: subHeaderForegroundColor,
tooltip: _isDisplayingLastMonth ? null : _localizations.nextMonthTooltip,
onPressed: _isDisplayingLastMonth ? null : _handleNextMonth,
),

View File

@ -84,6 +84,8 @@ class DatePickerThemeData with Diagnosticable {
this.cancelButtonStyle,
this.confirmButtonStyle,
this.locale,
this.toggleButtonTextStyle,
this.subHeaderForegroundColor,
});
/// Overrides the default value of [Dialog.backgroundColor].
@ -373,6 +375,16 @@ class DatePickerThemeData with Diagnosticable {
/// picker. It defaults to the ambient locale provided by [Localizations].
final Locale? locale;
/// Overrides the default text style used for the text of toggle mode button.
///
/// If no [TextStyle.color] is given, [subHeaderForegroundColor] will be used.
final TextStyle? toggleButtonTextStyle;
/// Overrides the default color used for text labels and icons of sub header foreground.
///
/// This is used in [TextStyle.color] property of [toggleButtonTextStyle] if no color is given.
final Color? subHeaderForegroundColor;
/// Creates a copy of this object with the given fields replaced with the
/// new values.
DatePickerThemeData copyWith({
@ -415,6 +427,8 @@ class DatePickerThemeData with Diagnosticable {
ButtonStyle? cancelButtonStyle,
ButtonStyle? confirmButtonStyle,
Locale? locale,
TextStyle? toggleButtonTextStyle,
Color? subHeaderForegroundColor,
}) {
return DatePickerThemeData(
backgroundColor: backgroundColor ?? this.backgroundColor,
@ -460,6 +474,8 @@ class DatePickerThemeData with Diagnosticable {
cancelButtonStyle: cancelButtonStyle ?? this.cancelButtonStyle,
confirmButtonStyle: confirmButtonStyle ?? this.confirmButtonStyle,
locale: locale ?? this.locale,
toggleButtonTextStyle: toggleButtonTextStyle ?? this.toggleButtonTextStyle,
subHeaderForegroundColor: subHeaderForegroundColor ?? this.subHeaderForegroundColor,
);
}
@ -591,6 +607,12 @@ class DatePickerThemeData with Diagnosticable {
cancelButtonStyle: ButtonStyle.lerp(a?.cancelButtonStyle, b?.cancelButtonStyle, t),
confirmButtonStyle: ButtonStyle.lerp(a?.confirmButtonStyle, b?.confirmButtonStyle, t),
locale: t < 0.5 ? a?.locale : b?.locale,
toggleButtonTextStyle: TextStyle.lerp(a?.toggleButtonTextStyle, b?.toggleButtonTextStyle, t),
subHeaderForegroundColor: Color.lerp(
a?.subHeaderForegroundColor,
b?.subHeaderForegroundColor,
t,
),
);
}
@ -645,6 +667,8 @@ class DatePickerThemeData with Diagnosticable {
cancelButtonStyle,
confirmButtonStyle,
locale,
toggleButtonTextStyle,
subHeaderForegroundColor,
]);
@override
@ -691,7 +715,9 @@ class DatePickerThemeData with Diagnosticable {
other.inputDecorationTheme == inputDecorationTheme &&
other.cancelButtonStyle == cancelButtonStyle &&
other.confirmButtonStyle == confirmButtonStyle &&
other.locale == locale;
other.locale == locale &&
other.toggleButtonTextStyle == toggleButtonTextStyle &&
other.subHeaderForegroundColor == subHeaderForegroundColor;
}
@override
@ -872,6 +898,16 @@ class DatePickerThemeData with Diagnosticable {
),
);
properties.add(DiagnosticsProperty<Locale>('locale', locale, defaultValue: null));
properties.add(
DiagnosticsProperty<TextStyle>(
'toggleButtonTextStyle',
toggleButtonTextStyle,
defaultValue: null,
),
);
properties.add(
ColorProperty('subHeaderForegroundColor', subHeaderForegroundColor, defaultValue: null),
);
}
}
@ -988,6 +1024,13 @@ class _DatePickerDefaultsM2 extends DatePickerThemeData {
@override
Color? get headerBackgroundColor => _isDark ? _colors.surface : _colors.primary;
@override
Color? get subHeaderForegroundColor => _colors.onSurface.withOpacity(0.60);
@override
TextStyle? get toggleButtonTextStyle =>
_textTheme.titleSmall?.apply(color: subHeaderForegroundColor);
@override
ButtonStyle get cancelButtonStyle {
return TextButton.styleFrom();
@ -1132,7 +1175,6 @@ class _DatePickerDefaultsM2 extends DatePickerThemeData {
return null;
});
}
// BEGIN GENERATED TOKEN PROPERTIES - DatePicker
// Do not edit by hand. The code between the "BEGIN GENERATED" and
@ -1162,6 +1204,14 @@ class _DatePickerDefaultsM3 extends DatePickerThemeData {
@override
Color? get backgroundColor => _colors.surfaceContainerHigh;
@override
Color? get subHeaderForegroundColor => _colors.onSurface.withOpacity(0.60);
@override
TextStyle? get toggleButtonTextStyle => _textTheme.titleSmall?.apply(
color: subHeaderForegroundColor,
);
@override
ButtonStyle get cancelButtonStyle {
return TextButton.styleFrom();

View File

@ -56,6 +56,8 @@ void main() {
foregroundColor: MaterialStatePropertyAll<Color>(Color(0xffffff7f)),
),
locale: Locale('en'),
subHeaderForegroundColor: Color(0xffffff8f),
toggleButtonTextStyle: TextStyle(fontSize: 13),
);
Material findDialogMaterial(WidgetTester tester) {
@ -141,6 +143,8 @@ void main() {
expect(theme.cancelButtonStyle, null);
expect(theme.confirmButtonStyle, null);
expect(theme.locale, null);
expect(theme.subHeaderForegroundColor, null);
expect(theme.toggleButtonTextStyle, null);
});
testWidgets('DatePickerTheme.defaults M3 defaults', (WidgetTester tester) async {
@ -296,6 +300,11 @@ void main() {
equalsIgnoringHashCodes(TextButton.styleFrom().toString()),
);
expect(m3.locale, null);
expect(m3.subHeaderForegroundColor, colorScheme.onSurface.withOpacity(0.60));
expect(
m3.toggleButtonTextStyle,
textTheme.titleSmall?.apply(color: m3.subHeaderForegroundColor),
);
});
testWidgets('DatePickerTheme.defaults M2 defaults', (WidgetTester tester) async {
@ -451,6 +460,11 @@ void main() {
);
expect(m2.locale, null);
expect(m2.yearShape?.resolve(<MaterialState>{}), const StadiumBorder());
expect(m2.subHeaderForegroundColor, colorScheme.onSurface.withOpacity(0.60));
expect(
m2.toggleButtonTextStyle,
textTheme.titleSmall?.apply(color: m2.subHeaderForegroundColor),
);
});
testWidgets('Default DatePickerThemeData debugFillProperties', (WidgetTester tester) async {
@ -519,6 +533,8 @@ void main() {
'cancelButtonStyle: ButtonStyle#00000(foregroundColor: WidgetStatePropertyAll(${const Color(0xffffff6f)}))',
'confirmButtonStyle: ButtonStyle#00000(foregroundColor: WidgetStatePropertyAll(${const Color(0xffffff7f)}))',
'locale: en',
'toggleButtonTextStyle: TextStyle(inherit: true, size: 13.0)',
'subHeaderForegroundColor: ${const Color(0xffffff8f)}',
]),
);
});
@ -591,6 +607,13 @@ void main() {
);
expect(day24Shape.side.width, datePickerTheme.todayBorder?.width);
// Test the toggle mode button style.
final Text january2023 = tester.widget<Text>(find.text('January 2023'));
expect(january2023.style?.fontSize, datePickerTheme.toggleButtonTextStyle?.fontSize);
expect(january2023.style?.color, datePickerTheme.subHeaderForegroundColor);
final Icon arrowIcon = tester.widget<Icon>(find.byIcon(Icons.arrow_drop_down));
expect(arrowIcon.color, datePickerTheme.subHeaderForegroundColor);
// Test the day overlay color.
final RenderObject inkFeatures = tester.allRenderObjects.firstWhere(
(RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures',
@ -1312,4 +1335,58 @@ void main() {
datePickerTheme.todayForegroundColor?.resolve(<MaterialState>{}),
);
});
testWidgets('Toggle button uses DatePickerTheme.toggleButtonTextStyle.color when it is defined', (
WidgetTester tester,
) async {
const Color toggleButtonTextColor = Color(0xff00ff00);
const Color subHeaderForegroundColor = Color(0xffff0000);
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
datePickerTheme: const DatePickerThemeData(
toggleButtonTextStyle: TextStyle(color: toggleButtonTextColor),
subHeaderForegroundColor: subHeaderForegroundColor,
),
),
home: DatePickerDialog(
initialDate: DateTime(2023, DateTime.january, 25),
firstDate: DateTime(2022),
lastDate: DateTime(2024, DateTime.december, 31),
currentDate: DateTime(2023, DateTime.january, 24),
),
),
);
final Text toggleButtonText = tester.widget(find.text('January 2023'));
expect(toggleButtonText.style?.color, toggleButtonTextColor);
});
testWidgets(
'Toggle button uses DatePickerTheme.subHeaderForegroundColor when DatePickerTheme.toggleButtonTextStyle.color is not defined',
(WidgetTester tester) async {
const Color subHeaderForegroundColor = Color(0xffff0000);
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
datePickerTheme: const DatePickerThemeData(
toggleButtonTextStyle: TextStyle(),
subHeaderForegroundColor: subHeaderForegroundColor,
),
),
home: DatePickerDialog(
initialDate: DateTime(2023, DateTime.january, 25),
firstDate: DateTime(2022),
lastDate: DateTime(2024, DateTime.december, 31),
currentDate: DateTime(2023, DateTime.january, 24),
),
),
);
final Text toggleButtonText = tester.widget(find.text('January 2023'));
expect(toggleButtonText.style?.color, subHeaderForegroundColor);
},
);
}