Feat: Add yearShape property to DatePickerThemeData (#163909)

Feat: Add yearShape property to DatePickerThemeData
fixes: #163340 

## 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.

---------

Co-authored-by: Tong Mu <dkwingsmt@users.noreply.github.com>
This commit is contained in:
Kishan Rathore 2025-04-07 11:39:29 +05:30 committed by GitHub
parent 2f72db6c7f
commit c6c3876ba9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 160 additions and 18 deletions

View File

@ -50,6 +50,7 @@ class _${blockName}DefaultsM3 extends DatePickerThemeData {
// TODO(tahatesser): Update this to use token when gen_defaults
// supports `CircleBorder` for fully rounded corners.
dayShape: const WidgetStatePropertyAll<OutlinedBorder>(CircleBorder()),
yearShape: const WidgetStatePropertyAll<OutlinedBorder>(StadiumBorder()),
rangePickerElevation: ${elevation("md.comp.date-picker.modal.range-selection.container")},
rangePickerShape: ${shape("md.comp.date-picker.modal.range-selection.container")},
);

View File

@ -1409,17 +1409,19 @@ class _YearPickerState extends State<YearPicker> {
effectiveValue((DatePickerThemeData? theme) => theme?.yearOverlayColor?.resolve(states)),
);
BoxBorder? border;
final OutlinedBorder yearShape =
resolve<OutlinedBorder?>((DatePickerThemeData? theme) => theme?.yearShape, states)!;
BorderSide? borderSide;
if (isCurrentYear) {
final BorderSide? todayBorder = datePickerTheme.todayBorder ?? defaults.todayBorder;
if (todayBorder != null) {
border = Border.fromBorderSide(todayBorder.copyWith(color: textColor));
borderSide = datePickerTheme.todayBorder ?? defaults.todayBorder;
if (borderSide != null) {
borderSide = borderSide.copyWith(color: textColor);
}
}
final BoxDecoration decoration = BoxDecoration(
border: border,
final ShapeDecoration decoration = ShapeDecoration(
color: background,
borderRadius: BorderRadius.circular(decorationHeight / 2),
shape: yearShape.copyWith(side: borderSide),
);
final TextStyle? itemStyle = (datePickerTheme.yearStyle ?? defaults.yearStyle)?.apply(

View File

@ -67,6 +67,7 @@ class DatePickerThemeData with Diagnosticable {
this.yearForegroundColor,
this.yearBackgroundColor,
this.yearOverlayColor,
this.yearShape,
this.rangePickerBackgroundColor,
this.rangePickerElevation,
this.rangePickerShadowColor,
@ -251,6 +252,19 @@ class DatePickerThemeData with Diagnosticable {
/// or pressed.
final WidgetStateProperty<Color?>? yearOverlayColor;
/// Overrides the default shape used to paint the shape decoration of the
/// year labels in the list of the year picker.
///
/// If the selected year is the current year, the provided shape with the
/// value of [todayBackgroundColor] is used to paint the shape decoration of
/// the year label and the value of [todayBorder] and [todayForegroundColor] is
/// used to paint the border.
///
/// If the selected year is not the current year, the provided shape with the
/// value of [yearBackgroundColor] is used to paint the shape decoration of
/// the year label.
final WidgetStateProperty<OutlinedBorder?>? yearShape;
/// Overrides the default [Scaffold.backgroundColor] for
/// [DateRangePickerDialog].
final Color? rangePickerBackgroundColor;
@ -384,6 +398,7 @@ class DatePickerThemeData with Diagnosticable {
WidgetStateProperty<Color?>? yearForegroundColor,
WidgetStateProperty<Color?>? yearBackgroundColor,
WidgetStateProperty<Color?>? yearOverlayColor,
WidgetStateProperty<OutlinedBorder?>? yearShape,
Color? rangePickerBackgroundColor,
double? rangePickerElevation,
Color? rangePickerShadowColor,
@ -424,6 +439,7 @@ class DatePickerThemeData with Diagnosticable {
yearForegroundColor: yearForegroundColor ?? this.yearForegroundColor,
yearBackgroundColor: yearBackgroundColor ?? this.yearBackgroundColor,
yearOverlayColor: yearOverlayColor ?? this.yearOverlayColor,
yearShape: yearShape ?? this.yearShape,
rangePickerBackgroundColor: rangePickerBackgroundColor ?? this.rangePickerBackgroundColor,
rangePickerElevation: rangePickerElevation ?? this.rangePickerElevation,
rangePickerShadowColor: rangePickerShadowColor ?? this.rangePickerShadowColor,
@ -520,6 +536,12 @@ class DatePickerThemeData with Diagnosticable {
t,
Color.lerp,
),
yearShape: WidgetStateProperty.lerp<OutlinedBorder?>(
a?.yearShape,
b?.yearShape,
t,
OutlinedBorder.lerp,
),
rangePickerBackgroundColor: Color.lerp(
a?.rangePickerBackgroundColor,
b?.rangePickerBackgroundColor,
@ -606,6 +628,7 @@ class DatePickerThemeData with Diagnosticable {
yearForegroundColor,
yearBackgroundColor,
yearOverlayColor,
yearShape,
rangePickerBackgroundColor,
rangePickerElevation,
rangePickerShadowColor,
@ -652,6 +675,7 @@ class DatePickerThemeData with Diagnosticable {
other.yearForegroundColor == yearForegroundColor &&
other.yearBackgroundColor == yearBackgroundColor &&
other.yearOverlayColor == yearOverlayColor &&
other.yearShape == yearShape &&
other.rangePickerBackgroundColor == rangePickerBackgroundColor &&
other.rangePickerElevation == rangePickerElevation &&
other.rangePickerShadowColor == rangePickerShadowColor &&
@ -765,6 +789,13 @@ class DatePickerThemeData with Diagnosticable {
defaultValue: null,
),
);
properties.add(
DiagnosticsProperty<WidgetStateProperty<OutlinedBorder?>>(
'yearShape',
yearShape,
defaultValue: null,
),
);
properties.add(
ColorProperty('rangePickerBackgroundColor', rangePickerBackgroundColor, defaultValue: null),
);
@ -943,6 +974,7 @@ class _DatePickerDefaultsM2 extends DatePickerThemeData {
elevation: 24.0,
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4.0))),
dayShape: const WidgetStatePropertyAll<OutlinedBorder>(CircleBorder()),
yearShape: const WidgetStatePropertyAll<OutlinedBorder>(StadiumBorder()),
rangePickerElevation: 0.0,
rangePickerShape: const RoundedRectangleBorder(),
);
@ -1117,6 +1149,7 @@ class _DatePickerDefaultsM3 extends DatePickerThemeData {
// TODO(tahatesser): Update this to use token when gen_defaults
// supports `CircleBorder` for fully rounded corners.
dayShape: const WidgetStatePropertyAll<OutlinedBorder>(CircleBorder()),
yearShape: const WidgetStatePropertyAll<OutlinedBorder>(StadiumBorder()),
rangePickerElevation: 0.0,
rangePickerShape: const RoundedRectangleBorder(),
);

View File

@ -32,6 +32,7 @@ void main() {
yearForegroundColor: MaterialStatePropertyAll<Color>(Color(0xfffffffa)),
yearBackgroundColor: MaterialStatePropertyAll<Color>(Color(0xfffffffb)),
yearOverlayColor: MaterialStatePropertyAll<Color>(Color(0xfffffffc)),
yearShape: MaterialStatePropertyAll<OutlinedBorder>(RoundedRectangleBorder()),
rangePickerBackgroundColor: Color(0xfffffffd),
rangePickerElevation: 7,
rangePickerShadowColor: Color(0xfffffffe),
@ -69,11 +70,11 @@ void main() {
);
}
BoxDecoration? findTextDecoration(WidgetTester tester, String date) {
ShapeDecoration? 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?;
return container.decoration as ShapeDecoration?;
}
ShapeDecoration? findDayDecoration(WidgetTester tester, String day) {
@ -449,6 +450,7 @@ void main() {
equalsIgnoringHashCodes(TextButton.styleFrom().toString()),
);
expect(m2.locale, null);
expect(m2.yearShape?.resolve(<MaterialState>{}), const StadiumBorder());
});
testWidgets('Default DatePickerThemeData debugFillProperties', (WidgetTester tester) async {
@ -500,6 +502,7 @@ void main() {
'yearForegroundColor: WidgetStatePropertyAll(${const Color(0xfffffffa)})',
'yearBackgroundColor: WidgetStatePropertyAll(${const Color(0xfffffffb)})',
'yearOverlayColor: WidgetStatePropertyAll(${const Color(0xfffffffc)})',
'yearShape: WidgetStatePropertyAll(RoundedRectangleBorder(BorderSide(width: 0.0, style: none), BorderRadius.zero))',
'rangePickerBackgroundColor: ${const Color(0xfffffffd)}',
'rangePickerElevation: 7.0',
'rangePickerShadowColor: ${const Color(0xfffffffe)}',
@ -607,30 +610,28 @@ void main() {
await tester.pumpAndSettle();
final Text year2022 = tester.widget<Text>(find.text('2022'));
final BoxDecoration year2022Decoration = findTextDecoration(tester, '2022')!;
final ShapeDecoration 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>{}),
);
expect(year2022Decoration.shape, datePickerTheme.yearShape?.resolve(<MaterialState>{}));
final Text year2023 = tester.widget<Text>(find.text('2023')); // DatePickerDialog.currentDate
final BoxDecoration year2023Decoration = findTextDecoration(tester, '2023')!;
final ShapeDecoration 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);
final RoundedRectangleBorder roundedRectangleBorder =
year2023Decoration.shape as RoundedRectangleBorder;
expect(roundedRectangleBorder.side.width, datePickerTheme.todayBorder?.width);
expect(
year2023Decoration.border?.top.color,
datePickerTheme.todayForegroundColor?.resolve(<MaterialState>{}),
);
expect(
year2023Decoration.border?.bottom.color,
roundedRectangleBorder.side.color,
datePickerTheme.todayForegroundColor?.resolve(<MaterialState>{}),
);
@ -1206,4 +1207,109 @@ void main() {
);
}
});
testWidgets('YearPicker maintains default year shape at textScaleFactor 1, 1.5, 2', (
WidgetTester tester,
) async {
double textScaleFactor = 1.0;
Widget buildFrame() {
return MaterialApp(
home: Builder(
builder: (BuildContext context) {
return MediaQuery.withClampedTextScaling(
minScaleFactor: textScaleFactor,
maxScaleFactor: textScaleFactor,
child: Scaffold(
body: YearPicker(
currentDate: DateTime(2025),
firstDate: DateTime(2021),
lastDate: DateTime(2030),
selectedDate: DateTime(2025),
onChanged: (DateTime value) {},
),
),
);
},
),
);
}
await tester.pumpWidget(buildFrame());
// Find container whose child is text 2025.
final Finder yearContainer =
find.ancestor(of: find.text('2025'), matching: find.byType(Container)).first;
expect(
tester.renderObject(yearContainer),
paints..rrect(
rrect: RRect.fromLTRBR(0.5, 0.5, 71.5, 35.5, const Radius.circular(17.5)),
color: const Color(0xFF6750A4),
),
);
textScaleFactor = 1.5;
await tester.pumpWidget(buildFrame());
expect(
tester.renderObject(yearContainer),
paints..rrect(
rrect: RRect.fromLTRBR(0.5, 0.5, 107.5, 51.5, const Radius.circular(25.5)),
color: const Color(0xFF6750A4),
),
);
textScaleFactor = 2;
await tester.pumpWidget(buildFrame());
expect(
tester.renderObject(yearContainer),
paints..rrect(
rrect: RRect.fromLTRBR(0.5, 0.5, 143.5, 51.5, const Radius.circular(25.5)),
color: const Color(0xFF6750A4),
),
);
});
testWidgets('YearPicker applies shape from DatePickerThemeData.yearShape correctly', (
WidgetTester tester,
) async {
const OutlinedBorder yearShpae = CircleBorder();
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
datePickerTheme: datePickerTheme.copyWith(
yearShape: MaterialStateProperty.all<OutlinedBorder>(yearShpae),
),
),
home: Directionality(
textDirection: TextDirection.ltr,
child: Material(
child: Center(
child: YearPicker(
currentDate: DateTime(2025),
firstDate: DateTime(2021),
lastDate: DateTime(2030),
selectedDate: DateTime(2025),
onChanged: (DateTime value) {},
),
),
),
),
),
);
final ShapeDecoration year2022Decoration = findTextDecoration(tester, '2022')!;
final OutlinedBorder year2022roundedRectangleBorder = year2022Decoration.shape as CircleBorder;
expect(year2022roundedRectangleBorder.side.width, 0.0);
expect(year2022roundedRectangleBorder.side.color, yearShpae.side.color);
final ShapeDecoration year2025Decoration = findTextDecoration(tester, '2025')!;
final OutlinedBorder year2022RoundedRectangleBorder = year2025Decoration.shape as CircleBorder;
expect(year2022RoundedRectangleBorder.side.width, datePickerTheme.todayBorder?.width);
expect(
year2022RoundedRectangleBorder.side.color,
datePickerTheme.todayForegroundColor?.resolve(<MaterialState>{}),
);
});
}