Skip to content

Commit c6c3876

Browse files
Feat: Add yearShape property to DatePickerThemeData (flutter#163909)
Feat: Add yearShape property to DatePickerThemeData fixes: flutter#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 <[email protected]>
1 parent 2f72db6 commit c6c3876

File tree

4 files changed

+160
-18
lines changed

4 files changed

+160
-18
lines changed

dev/tools/gen_defaults/lib/date_picker_template.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ class _${blockName}DefaultsM3 extends DatePickerThemeData {
5050
// TODO(tahatesser): Update this to use token when gen_defaults
5151
// supports `CircleBorder` for fully rounded corners.
5252
dayShape: const WidgetStatePropertyAll<OutlinedBorder>(CircleBorder()),
53+
yearShape: const WidgetStatePropertyAll<OutlinedBorder>(StadiumBorder()),
5354
rangePickerElevation: ${elevation("md.comp.date-picker.modal.range-selection.container")},
5455
rangePickerShape: ${shape("md.comp.date-picker.modal.range-selection.container")},
5556
);

packages/flutter/lib/src/material/calendar_date_picker.dart

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1409,17 +1409,19 @@ class _YearPickerState extends State<YearPicker> {
14091409
effectiveValue((DatePickerThemeData? theme) => theme?.yearOverlayColor?.resolve(states)),
14101410
);
14111411

1412-
BoxBorder? border;
1412+
final OutlinedBorder yearShape =
1413+
resolve<OutlinedBorder?>((DatePickerThemeData? theme) => theme?.yearShape, states)!;
1414+
1415+
BorderSide? borderSide;
14131416
if (isCurrentYear) {
1414-
final BorderSide? todayBorder = datePickerTheme.todayBorder ?? defaults.todayBorder;
1415-
if (todayBorder != null) {
1416-
border = Border.fromBorderSide(todayBorder.copyWith(color: textColor));
1417+
borderSide = datePickerTheme.todayBorder ?? defaults.todayBorder;
1418+
if (borderSide != null) {
1419+
borderSide = borderSide.copyWith(color: textColor);
14171420
}
14181421
}
1419-
final BoxDecoration decoration = BoxDecoration(
1420-
border: border,
1422+
final ShapeDecoration decoration = ShapeDecoration(
14211423
color: background,
1422-
borderRadius: BorderRadius.circular(decorationHeight / 2),
1424+
shape: yearShape.copyWith(side: borderSide),
14231425
);
14241426

14251427
final TextStyle? itemStyle = (datePickerTheme.yearStyle ?? defaults.yearStyle)?.apply(

packages/flutter/lib/src/material/date_picker_theme.dart

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ class DatePickerThemeData with Diagnosticable {
6767
this.yearForegroundColor,
6868
this.yearBackgroundColor,
6969
this.yearOverlayColor,
70+
this.yearShape,
7071
this.rangePickerBackgroundColor,
7172
this.rangePickerElevation,
7273
this.rangePickerShadowColor,
@@ -251,6 +252,19 @@ class DatePickerThemeData with Diagnosticable {
251252
/// or pressed.
252253
final WidgetStateProperty<Color?>? yearOverlayColor;
253254

255+
/// Overrides the default shape used to paint the shape decoration of the
256+
/// year labels in the list of the year picker.
257+
///
258+
/// If the selected year is the current year, the provided shape with the
259+
/// value of [todayBackgroundColor] is used to paint the shape decoration of
260+
/// the year label and the value of [todayBorder] and [todayForegroundColor] is
261+
/// used to paint the border.
262+
///
263+
/// If the selected year is not the current year, the provided shape with the
264+
/// value of [yearBackgroundColor] is used to paint the shape decoration of
265+
/// the year label.
266+
final WidgetStateProperty<OutlinedBorder?>? yearShape;
267+
254268
/// Overrides the default [Scaffold.backgroundColor] for
255269
/// [DateRangePickerDialog].
256270
final Color? rangePickerBackgroundColor;
@@ -384,6 +398,7 @@ class DatePickerThemeData with Diagnosticable {
384398
WidgetStateProperty<Color?>? yearForegroundColor,
385399
WidgetStateProperty<Color?>? yearBackgroundColor,
386400
WidgetStateProperty<Color?>? yearOverlayColor,
401+
WidgetStateProperty<OutlinedBorder?>? yearShape,
387402
Color? rangePickerBackgroundColor,
388403
double? rangePickerElevation,
389404
Color? rangePickerShadowColor,
@@ -424,6 +439,7 @@ class DatePickerThemeData with Diagnosticable {
424439
yearForegroundColor: yearForegroundColor ?? this.yearForegroundColor,
425440
yearBackgroundColor: yearBackgroundColor ?? this.yearBackgroundColor,
426441
yearOverlayColor: yearOverlayColor ?? this.yearOverlayColor,
442+
yearShape: yearShape ?? this.yearShape,
427443
rangePickerBackgroundColor: rangePickerBackgroundColor ?? this.rangePickerBackgroundColor,
428444
rangePickerElevation: rangePickerElevation ?? this.rangePickerElevation,
429445
rangePickerShadowColor: rangePickerShadowColor ?? this.rangePickerShadowColor,
@@ -520,6 +536,12 @@ class DatePickerThemeData with Diagnosticable {
520536
t,
521537
Color.lerp,
522538
),
539+
yearShape: WidgetStateProperty.lerp<OutlinedBorder?>(
540+
a?.yearShape,
541+
b?.yearShape,
542+
t,
543+
OutlinedBorder.lerp,
544+
),
523545
rangePickerBackgroundColor: Color.lerp(
524546
a?.rangePickerBackgroundColor,
525547
b?.rangePickerBackgroundColor,
@@ -606,6 +628,7 @@ class DatePickerThemeData with Diagnosticable {
606628
yearForegroundColor,
607629
yearBackgroundColor,
608630
yearOverlayColor,
631+
yearShape,
609632
rangePickerBackgroundColor,
610633
rangePickerElevation,
611634
rangePickerShadowColor,
@@ -652,6 +675,7 @@ class DatePickerThemeData with Diagnosticable {
652675
other.yearForegroundColor == yearForegroundColor &&
653676
other.yearBackgroundColor == yearBackgroundColor &&
654677
other.yearOverlayColor == yearOverlayColor &&
678+
other.yearShape == yearShape &&
655679
other.rangePickerBackgroundColor == rangePickerBackgroundColor &&
656680
other.rangePickerElevation == rangePickerElevation &&
657681
other.rangePickerShadowColor == rangePickerShadowColor &&
@@ -765,6 +789,13 @@ class DatePickerThemeData with Diagnosticable {
765789
defaultValue: null,
766790
),
767791
);
792+
properties.add(
793+
DiagnosticsProperty<WidgetStateProperty<OutlinedBorder?>>(
794+
'yearShape',
795+
yearShape,
796+
defaultValue: null,
797+
),
798+
);
768799
properties.add(
769800
ColorProperty('rangePickerBackgroundColor', rangePickerBackgroundColor, defaultValue: null),
770801
);
@@ -943,6 +974,7 @@ class _DatePickerDefaultsM2 extends DatePickerThemeData {
943974
elevation: 24.0,
944975
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4.0))),
945976
dayShape: const WidgetStatePropertyAll<OutlinedBorder>(CircleBorder()),
977+
yearShape: const WidgetStatePropertyAll<OutlinedBorder>(StadiumBorder()),
946978
rangePickerElevation: 0.0,
947979
rangePickerShape: const RoundedRectangleBorder(),
948980
);
@@ -1117,6 +1149,7 @@ class _DatePickerDefaultsM3 extends DatePickerThemeData {
11171149
// TODO(tahatesser): Update this to use token when gen_defaults
11181150
// supports `CircleBorder` for fully rounded corners.
11191151
dayShape: const WidgetStatePropertyAll<OutlinedBorder>(CircleBorder()),
1152+
yearShape: const WidgetStatePropertyAll<OutlinedBorder>(StadiumBorder()),
11201153
rangePickerElevation: 0.0,
11211154
rangePickerShape: const RoundedRectangleBorder(),
11221155
);

packages/flutter/test/material/date_picker_theme_test.dart

Lines changed: 117 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ void main() {
3232
yearForegroundColor: MaterialStatePropertyAll<Color>(Color(0xfffffffa)),
3333
yearBackgroundColor: MaterialStatePropertyAll<Color>(Color(0xfffffffb)),
3434
yearOverlayColor: MaterialStatePropertyAll<Color>(Color(0xfffffffc)),
35+
yearShape: MaterialStatePropertyAll<OutlinedBorder>(RoundedRectangleBorder()),
3536
rangePickerBackgroundColor: Color(0xfffffffd),
3637
rangePickerElevation: 7,
3738
rangePickerShadowColor: Color(0xfffffffe),
@@ -69,11 +70,11 @@ void main() {
6970
);
7071
}
7172

72-
BoxDecoration? findTextDecoration(WidgetTester tester, String date) {
73+
ShapeDecoration? findTextDecoration(WidgetTester tester, String date) {
7374
final Container container = tester.widget<Container>(
7475
find.ancestor(of: find.text(date), matching: find.byType(Container)).first,
7576
);
76-
return container.decoration as BoxDecoration?;
77+
return container.decoration as ShapeDecoration?;
7778
}
7879

7980
ShapeDecoration? findDayDecoration(WidgetTester tester, String day) {
@@ -449,6 +450,7 @@ void main() {
449450
equalsIgnoringHashCodes(TextButton.styleFrom().toString()),
450451
);
451452
expect(m2.locale, null);
453+
expect(m2.yearShape?.resolve(<MaterialState>{}), const StadiumBorder());
452454
});
453455

454456
testWidgets('Default DatePickerThemeData debugFillProperties', (WidgetTester tester) async {
@@ -500,6 +502,7 @@ void main() {
500502
'yearForegroundColor: WidgetStatePropertyAll(${const Color(0xfffffffa)})',
501503
'yearBackgroundColor: WidgetStatePropertyAll(${const Color(0xfffffffb)})',
502504
'yearOverlayColor: WidgetStatePropertyAll(${const Color(0xfffffffc)})',
505+
'yearShape: WidgetStatePropertyAll(RoundedRectangleBorder(BorderSide(width: 0.0, style: none), BorderRadius.zero))',
503506
'rangePickerBackgroundColor: ${const Color(0xfffffffd)}',
504507
'rangePickerElevation: 7.0',
505508
'rangePickerShadowColor: ${const Color(0xfffffffe)}',
@@ -607,30 +610,28 @@ void main() {
607610
await tester.pumpAndSettle();
608611

609612
final Text year2022 = tester.widget<Text>(find.text('2022'));
610-
final BoxDecoration year2022Decoration = findTextDecoration(tester, '2022')!;
613+
final ShapeDecoration year2022Decoration = findTextDecoration(tester, '2022')!;
611614
expect(year2022.style?.fontSize, datePickerTheme.yearStyle?.fontSize);
612615
expect(year2022.style?.color, datePickerTheme.yearForegroundColor?.resolve(<MaterialState>{}));
613616
expect(
614617
year2022Decoration.color,
615618
datePickerTheme.yearBackgroundColor?.resolve(<MaterialState>{}),
616619
);
620+
expect(year2022Decoration.shape, datePickerTheme.yearShape?.resolve(<MaterialState>{}));
617621

618622
final Text year2023 = tester.widget<Text>(find.text('2023')); // DatePickerDialog.currentDate
619-
final BoxDecoration year2023Decoration = findTextDecoration(tester, '2023')!;
623+
final ShapeDecoration year2023Decoration = findTextDecoration(tester, '2023')!;
620624
expect(year2023.style?.fontSize, datePickerTheme.yearStyle?.fontSize);
621625
expect(year2023.style?.color, datePickerTheme.todayForegroundColor?.resolve(<MaterialState>{}));
622626
expect(
623627
year2023Decoration.color,
624628
datePickerTheme.todayBackgroundColor?.resolve(<MaterialState>{}),
625629
);
626-
expect(year2023Decoration.border?.top.width, datePickerTheme.todayBorder?.width);
627-
expect(year2023Decoration.border?.bottom.width, datePickerTheme.todayBorder?.width);
630+
final RoundedRectangleBorder roundedRectangleBorder =
631+
year2023Decoration.shape as RoundedRectangleBorder;
632+
expect(roundedRectangleBorder.side.width, datePickerTheme.todayBorder?.width);
628633
expect(
629-
year2023Decoration.border?.top.color,
630-
datePickerTheme.todayForegroundColor?.resolve(<MaterialState>{}),
631-
);
632-
expect(
633-
year2023Decoration.border?.bottom.color,
634+
roundedRectangleBorder.side.color,
634635
datePickerTheme.todayForegroundColor?.resolve(<MaterialState>{}),
635636
);
636637

@@ -1206,4 +1207,109 @@ void main() {
12061207
);
12071208
}
12081209
});
1210+
1211+
testWidgets('YearPicker maintains default year shape at textScaleFactor 1, 1.5, 2', (
1212+
WidgetTester tester,
1213+
) async {
1214+
double textScaleFactor = 1.0;
1215+
Widget buildFrame() {
1216+
return MaterialApp(
1217+
home: Builder(
1218+
builder: (BuildContext context) {
1219+
return MediaQuery.withClampedTextScaling(
1220+
minScaleFactor: textScaleFactor,
1221+
maxScaleFactor: textScaleFactor,
1222+
child: Scaffold(
1223+
body: YearPicker(
1224+
currentDate: DateTime(2025),
1225+
firstDate: DateTime(2021),
1226+
lastDate: DateTime(2030),
1227+
selectedDate: DateTime(2025),
1228+
onChanged: (DateTime value) {},
1229+
),
1230+
),
1231+
);
1232+
},
1233+
),
1234+
);
1235+
}
1236+
1237+
await tester.pumpWidget(buildFrame());
1238+
1239+
// Find container whose child is text 2025.
1240+
final Finder yearContainer =
1241+
find.ancestor(of: find.text('2025'), matching: find.byType(Container)).first;
1242+
1243+
expect(
1244+
tester.renderObject(yearContainer),
1245+
paints..rrect(
1246+
rrect: RRect.fromLTRBR(0.5, 0.5, 71.5, 35.5, const Radius.circular(17.5)),
1247+
color: const Color(0xFF6750A4),
1248+
),
1249+
);
1250+
1251+
textScaleFactor = 1.5;
1252+
await tester.pumpWidget(buildFrame());
1253+
1254+
expect(
1255+
tester.renderObject(yearContainer),
1256+
paints..rrect(
1257+
rrect: RRect.fromLTRBR(0.5, 0.5, 107.5, 51.5, const Radius.circular(25.5)),
1258+
color: const Color(0xFF6750A4),
1259+
),
1260+
);
1261+
1262+
textScaleFactor = 2;
1263+
await tester.pumpWidget(buildFrame());
1264+
1265+
expect(
1266+
tester.renderObject(yearContainer),
1267+
paints..rrect(
1268+
rrect: RRect.fromLTRBR(0.5, 0.5, 143.5, 51.5, const Radius.circular(25.5)),
1269+
color: const Color(0xFF6750A4),
1270+
),
1271+
);
1272+
});
1273+
1274+
testWidgets('YearPicker applies shape from DatePickerThemeData.yearShape correctly', (
1275+
WidgetTester tester,
1276+
) async {
1277+
const OutlinedBorder yearShpae = CircleBorder();
1278+
await tester.pumpWidget(
1279+
MaterialApp(
1280+
theme: ThemeData(
1281+
datePickerTheme: datePickerTheme.copyWith(
1282+
yearShape: MaterialStateProperty.all<OutlinedBorder>(yearShpae),
1283+
),
1284+
),
1285+
home: Directionality(
1286+
textDirection: TextDirection.ltr,
1287+
child: Material(
1288+
child: Center(
1289+
child: YearPicker(
1290+
currentDate: DateTime(2025),
1291+
firstDate: DateTime(2021),
1292+
lastDate: DateTime(2030),
1293+
selectedDate: DateTime(2025),
1294+
onChanged: (DateTime value) {},
1295+
),
1296+
),
1297+
),
1298+
),
1299+
),
1300+
);
1301+
1302+
final ShapeDecoration year2022Decoration = findTextDecoration(tester, '2022')!;
1303+
final OutlinedBorder year2022roundedRectangleBorder = year2022Decoration.shape as CircleBorder;
1304+
expect(year2022roundedRectangleBorder.side.width, 0.0);
1305+
expect(year2022roundedRectangleBorder.side.color, yearShpae.side.color);
1306+
1307+
final ShapeDecoration year2025Decoration = findTextDecoration(tester, '2025')!;
1308+
final OutlinedBorder year2022RoundedRectangleBorder = year2025Decoration.shape as CircleBorder;
1309+
expect(year2022RoundedRectangleBorder.side.width, datePickerTheme.todayBorder?.width);
1310+
expect(
1311+
year2022RoundedRectangleBorder.side.color,
1312+
datePickerTheme.todayForegroundColor?.resolve(<MaterialState>{}),
1313+
);
1314+
});
12091315
}

0 commit comments

Comments
 (0)