From c5132b52c2e9476aa3b544bb9b7e7af2f364aee1 Mon Sep 17 00:00:00 2001 From: Bruno Leroux Date: Thu, 5 Dec 2024 17:44:06 +0100 Subject: [PATCH] Reland Fix Date picker overlay colors aren't applied on selected state (#159839) Reland https://github.com/flutter/flutter/pull/159203 without change. The initial PR was reverted in https://github.com/flutter/flutter/pull/159583. Fixes [Date picker overlay colors aren't applied on MaterialState.selected state](https://github.com/flutter/flutter/issues/130586). --- .../date_picker_theme_day_shape.0_test.dart | 27 +- .../src/material/calendar_date_picker.dart | 5 +- .../material/calendar_date_picker_test.dart | 2 +- .../test/material/date_picker_test.dart | 356 ++++++++++++++++-- .../test/material/date_picker_theme_test.dart | 29 +- 5 files changed, 365 insertions(+), 54 deletions(-) diff --git a/examples/api/test/material/date_picker/date_picker_theme_day_shape.0_test.dart b/examples/api/test/material/date_picker/date_picker_theme_day_shape.0_test.dart index dc239d9049..cbaa88f15f 100644 --- a/examples/api/test/material/date_picker/date_picker_theme_day_shape.0_test.dart +++ b/examples/api/test/material/date_picker/date_picker_theme_day_shape.0_test.dart @@ -14,6 +14,15 @@ void main() { const Color todayForegroundColor = Colors.black; const BorderSide todayBorder = BorderSide(width: 2); + ShapeDecoration? findDayDecoration(WidgetTester tester, String day) { + return tester.widget( + find.ancestor( + of: find.text(day), + matching: find.byType(Ink) + ), + ).decoration as ShapeDecoration?; + } + await tester.pumpWidget( const example.DatePickerApp(), ); @@ -21,21 +30,13 @@ void main() { await tester.tap(find.text('Open Date Picker')); await tester.pumpAndSettle(); - ShapeDecoration dayShapeDecoration = tester.widget(find.ancestor( - of: find.text('15'), - matching: find.byType(DecoratedBox), - )).decoration as ShapeDecoration; - // Test the current day shape decoration. + ShapeDecoration dayShapeDecoration = findDayDecoration(tester, '15')!; expect(dayShapeDecoration.color, todayBackgroundColor); expect(dayShapeDecoration.shape, dayShape.copyWith(side: todayBorder.copyWith(color: todayForegroundColor))); - dayShapeDecoration = tester.widget(find.ancestor( - of: find.text('20'), - matching: find.byType(DecoratedBox), - )).decoration as ShapeDecoration; - // Test the selected day shape decoration. + dayShapeDecoration = findDayDecoration(tester, '20')!; expect(dayShapeDecoration.color, theme.colorScheme.primary); expect(dayShapeDecoration.shape, dayShape); @@ -43,12 +44,8 @@ void main() { await tester.tap(find.text('15')); await tester.pumpAndSettle(); - dayShapeDecoration = tester.widget(find.ancestor( - of: find.text('15'), - matching: find.byType(DecoratedBox), - )).decoration as ShapeDecoration; - // Test the selected day shape decoration. + dayShapeDecoration = findDayDecoration(tester, '15')!; expect(dayShapeDecoration.color, todayBackgroundColor); expect(dayShapeDecoration.shape, dayShape.copyWith(side: todayBorder.copyWith(color: todayForegroundColor))); }); diff --git a/packages/flutter/lib/src/material/calendar_date_picker.dart b/packages/flutter/lib/src/material/calendar_date_picker.dart index 6c96ec343e..20f82a05d3 100644 --- a/packages/flutter/lib/src/material/calendar_date_picker.dart +++ b/packages/flutter/lib/src/material/calendar_date_picker.dart @@ -20,6 +20,7 @@ import 'debug.dart'; import 'divider.dart'; import 'icon_button.dart'; import 'icons.dart'; +import 'ink_decoration.dart'; import 'ink_well.dart'; import 'material_localizations.dart'; import 'material_state.dart'; @@ -1056,7 +1057,7 @@ class _Day extends StatefulWidget { final bool isSelectedDay; final bool isToday; final ValueChanged onChanged; - final FocusNode? focusNode; + final FocusNode focusNode; @override State<_Day> createState() => _DayState(); @@ -1111,7 +1112,7 @@ class _DayState extends State<_Day> { shape: dayShape, ); - Widget dayWidget = DecoratedBox( + Widget dayWidget = Ink( decoration: decoration, child: Center( child: Text(localizations.formatDecimal(widget.day.day), style: dayStyle?.apply(color: dayForegroundColor)), diff --git a/packages/flutter/test/material/calendar_date_picker_test.dart b/packages/flutter/test/material/calendar_date_picker_test.dart index d166040f84..20edff6c3b 100644 --- a/packages/flutter/test/material/calendar_date_picker_test.dart +++ b/packages/flutter/test/material/calendar_date_picker_test.dart @@ -1196,7 +1196,7 @@ void main() { await gesture.moveTo(tester.getCenter(find.text('25'))); await tester.pumpAndSettle(); inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures'); - expect(inkFeatures, paints..circle(radius: 35.0, color: theme.colorScheme.onSurfaceVariant.withOpacity(0.08))); + expect(inkFeatures, paints..circle()..circle(radius: 35.0, color: theme.colorScheme.onSurfaceVariant.withOpacity(0.08))); expect(inkFeatures, paintsExactlyCountTimes(#clipPath, 1)); final Rect expectedClipRect = Rect.fromCircle(center: const Offset(400.0, 241.0), radius: 35.0); diff --git a/packages/flutter/test/material/date_picker_test.dart b/packages/flutter/test/material/date_picker_test.dart index 380372d039..810fa5e2cf 100644 --- a/packages/flutter/test/material/date_picker_test.dart +++ b/packages/flutter/test/material/date_picker_test.dart @@ -132,6 +132,21 @@ void main() { await callback(date); } + ShapeDecoration? findDayDecoration(WidgetTester tester, String day) { + return tester.widget( + find.ancestor( + of: find.text(day), + matching: find.byType(Ink) + ), + ).decoration as ShapeDecoration?; + } + + MaterialInkController findDayGridMaterial(WidgetTester tester) { + // All days are painted on the same Material widget. + // Use an arbitrary day to find this Material. + return Material.of(tester.element(find.text('17'))); + } + group('showDatePicker Dialog', () { testWidgets('Default dialog size', (WidgetTester tester) async { Future showPicker(WidgetTester tester, Size size) async { @@ -1264,43 +1279,170 @@ void main() { await tester.pump(); const Color todayColor = Color(0xff2196f3); // default primary color expect( - Material.of(tester.element(find.text('2'))), + findDayGridMaterial(tester), // The current day should be painted with a circle outline paints..circle(color: todayColor, style: PaintingStyle.stroke, strokeWidth: 1.0), ); }); }); - testWidgets('Date picker dayOverlayColor resolves pressed state', (WidgetTester tester) async { - today = DateTime(2023, 5, 4); + testWidgets('Date picker dayOverlayColor resolves hovered state', (WidgetTester tester) async { final ThemeData theme = ThemeData(); - final bool material3 = theme.useMaterial3; - await prepareDatePicker(tester, (Future date) async { - await tester.pump(); + await prepareDatePicker(tester, (Future date) async {}, theme: theme); - // Hovered. - final Offset center = tester.getCenter(find.text('30')); - final TestGesture gesture = await tester.createGesture( - kind: PointerDeviceKind.mouse, - ); - await gesture.addPointer(); - await gesture.moveTo(center); - await tester.pumpAndSettle(); - expect( - Material.of(tester.element(find.text('30'))), - paints..circle(color: material3 ? theme.colorScheme.onSurfaceVariant.withOpacity(0.08) : theme.colorScheme.onSurfaceVariant.withOpacity(0.08)), - ); + final Offset center = tester.getCenter(find.text('30')); + final TestGesture gesture = await tester.createGesture( + kind: PointerDeviceKind.mouse, + ); + await gesture.addPointer(); + addTearDown(gesture.removePointer); + await gesture.moveTo(center); + await tester.pumpAndSettle(); - // Highlighted (pressed). - await gesture.down(center); - await tester.pumpAndSettle(); - expect( - Material.of(tester.element(find.text('30'))), - paints..circle()..circle(color: material3 ? theme.colorScheme.onSurfaceVariant.withOpacity(0.1) : theme.colorScheme.onSurfaceVariant.withOpacity(0.12)) - ); - await gesture.up(); - await tester.pumpAndSettle(); - }, theme: theme); + expect( + findDayGridMaterial(tester), + paints + ..circle() // Today decoration. + ..circle() // Selected day decoration. + ..circle(color: theme.colorScheme.onSurfaceVariant.withOpacity(0.08)), + ); + }); + + testWidgets('Date picker dayOverlayColor resolves focused state', (WidgetTester tester) async { + FocusManager.instance.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; + final ThemeData theme = ThemeData(); + await prepareDatePicker(tester, (Future date) async {}, theme: theme); + + // Navigate to the grid. + await tester.sendKeyEvent(LogicalKeyboardKey.tab); + await tester.sendKeyEvent(LogicalKeyboardKey.tab); + await tester.sendKeyEvent(LogicalKeyboardKey.tab); + await tester.sendKeyEvent(LogicalKeyboardKey.tab); + + // Navigate to day 30. + await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown); + await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown); + await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight); + await tester.pumpAndSettle(); + + expect( + findDayGridMaterial(tester), + paints + ..circle() // Today decoration. + ..circle() // Selected day decoration. + ..circle(color: theme.colorScheme.onSurfaceVariant.withOpacity(0.10)), + ); + }); + + testWidgets('Date picker dayOverlayColor resolves pressed state', (WidgetTester tester) async { + final ThemeData theme = ThemeData(); + await prepareDatePicker(tester, (Future date) async {}, theme: theme); + + final Offset center = tester.getCenter(find.text('30')); + final TestGesture gesture = await tester.createGesture( + kind: PointerDeviceKind.mouse, + ); + await gesture.addPointer(); + addTearDown(gesture.removePointer); + await gesture.down(center); + await tester.pumpAndSettle(); + + expect( + findDayGridMaterial(tester), + paints + ..circle() // Today decoration. + ..circle() // Selected day decoration. + ..circle() // Hovered decoration. + ..circle(color: theme.colorScheme.onSurfaceVariant.withOpacity(0.10)), + ); + await gesture.up(); + }); + + // Regression test for https://github.com/flutter/flutter/issues/130586. + testWidgets('Date picker dayOverlayColor resolves selected and hovered state', (WidgetTester tester) async { + final ThemeData theme = ThemeData(); + await prepareDatePicker(tester, (Future date) async {}, theme: theme); + + // Select day 30. + await tester.tap(find.text('30')); + await tester.pumpAndSettle(); + final ShapeDecoration day30Decoration = findDayDecoration(tester, '30')!; + expect(day30Decoration.color, theme.colorScheme.primary); + + final Offset center = tester.getCenter(find.text('30')); + final TestGesture gesture = await tester.createGesture( + kind: PointerDeviceKind.mouse, + ); + await gesture.addPointer(); + addTearDown(gesture.removePointer); + await gesture.moveTo(center); + await tester.pumpAndSettle(); + + expect( + findDayGridMaterial(tester), + paints + ..circle() // Today decoration. + ..circle() // Selected day decoration. + ..circle(color: theme.colorScheme.onPrimary.withOpacity(0.08)), + ); + }); + + testWidgets('Date picker dayOverlayColor resolves selected and focused state', (WidgetTester tester) async { + FocusManager.instance.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; + final ThemeData theme = ThemeData(); + await prepareDatePicker(tester, (Future date) async {}, theme: theme); + + // Select day 30. + await tester.tap(find.text('30')); + await tester.pumpAndSettle(); + final ShapeDecoration day30Decoration = findDayDecoration(tester, '30')!; + expect(day30Decoration.color, theme.colorScheme.primary); + + // Navigate to the grid. + await tester.sendKeyEvent(LogicalKeyboardKey.tab); + await tester.sendKeyEvent(LogicalKeyboardKey.tab); + await tester.sendKeyEvent(LogicalKeyboardKey.tab); + await tester.sendKeyEvent(LogicalKeyboardKey.tab); + await tester.pumpAndSettle(); + + // Day 30 is selected and focused. + expect( + findDayGridMaterial(tester), + paints + ..circle() // Today decoration. + ..circle() // Selected day decoration. + ..circle(color: theme.colorScheme.onPrimary.withOpacity(0.10)), + ); + }); + + testWidgets('Date picker dayOverlayColor resolves selected and pressed state', (WidgetTester tester) async { + final ThemeData theme = ThemeData(); + await prepareDatePicker(tester, (Future date) async {}, theme: theme); + + // Select day 30. + await tester.tap(find.text('30')); + await tester.pumpAndSettle(); + final ShapeDecoration day30Decoration = findDayDecoration(tester, '30')!; + expect(day30Decoration.color, theme.colorScheme.primary); + + final Offset center = tester.getCenter(find.text('30')); + final TestGesture gesture = await tester.createGesture( + kind: PointerDeviceKind.mouse, + ); + await gesture.addPointer(); + addTearDown(gesture.removePointer); + await gesture.down(center); + await tester.pumpAndSettle(); + + expect( + findDayGridMaterial(tester), + paints + ..circle() // Today decoration. + ..circle() // Selected day decoration. + ..circle() // Hovered decoration. + ..circle(color: theme.colorScheme.onPrimary.withOpacity(0.10)), + ); + await gesture.up(); }); testWidgets('Selecting date does not switch picker to year selection', (WidgetTester tester) async { @@ -2306,6 +2448,164 @@ void main() { }); }); }); + + testWidgets('Date picker dayOverlayColor resolves hovered state', (WidgetTester tester) async { + final ThemeData theme = ThemeData(useMaterial3: false); + await prepareDatePicker(tester, (Future date) async {}, theme: theme); + + final Offset center = tester.getCenter(find.text('30')); + final TestGesture gesture = await tester.createGesture( + kind: PointerDeviceKind.mouse, + ); + await gesture.addPointer(); + await gesture.moveTo(center); + await tester.pumpAndSettle(); + + expect( + findDayGridMaterial(tester), + paints + ..circle() // Today decoration. + ..circle() // Selected day decoration. + ..circle(color: theme.colorScheme.onSurfaceVariant.withOpacity(0.08)), + ); + }); + + testWidgets('Date picker dayOverlayColor resolves focused state', (WidgetTester tester) async { + FocusManager.instance.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; + final ThemeData theme = ThemeData(useMaterial3: false); + await prepareDatePicker(tester, (Future date) async {}, theme: theme); + + // Navigate to the grid. + await tester.sendKeyEvent(LogicalKeyboardKey.tab); + await tester.sendKeyEvent(LogicalKeyboardKey.tab); + await tester.sendKeyEvent(LogicalKeyboardKey.tab); + await tester.sendKeyEvent(LogicalKeyboardKey.tab); + + // Navigate to day 30. + await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown); + await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown); + await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight); + await tester.pumpAndSettle(); + + expect( + findDayGridMaterial(tester), + paints + ..circle() // Today decoration. + ..circle() // Selected day decoration. + ..circle(color: theme.colorScheme.onSurfaceVariant.withOpacity(0.12)), + ); + }); + + testWidgets('Date picker dayOverlayColor resolves pressed state', (WidgetTester tester) async { + final ThemeData theme = ThemeData(useMaterial3: false); + await prepareDatePicker(tester, (Future date) async {}, theme: theme); + + final Offset center = tester.getCenter(find.text('30')); + final TestGesture gesture = await tester.createGesture( + kind: PointerDeviceKind.mouse, + ); + await gesture.addPointer(); + addTearDown(gesture.removePointer); + await gesture.down(center); + await tester.pumpAndSettle(); + + expect( + findDayGridMaterial(tester), + paints + ..circle() // Today decoration. + ..circle() // Selected day decoration. + ..circle() // Hovered decoration. + ..circle(color: theme.colorScheme.onSurfaceVariant.withOpacity(0.12)), + ); + await gesture.up(); + }); + + // Regression test for https://github.com/flutter/flutter/issues/130586. + testWidgets('Date picker dayOverlayColor resolves selected and hovered state', (WidgetTester tester) async { + final ThemeData theme = ThemeData(useMaterial3: false); + await prepareDatePicker(tester, (Future date) async {}, theme: theme); + + // Select day 30. + await tester.tap(find.text('30')); + await tester.pumpAndSettle(); + final ShapeDecoration day30Decoration = findDayDecoration(tester, '30')!; + expect(day30Decoration.color, theme.colorScheme.primary); + + final Offset center = tester.getCenter(find.text('30')); + final TestGesture gesture = await tester.createGesture( + kind: PointerDeviceKind.mouse, + ); + await gesture.addPointer(); + addTearDown(gesture.removePointer); + await gesture.moveTo(center); + await tester.pumpAndSettle(); + + expect( + findDayGridMaterial(tester), + paints + ..circle() // Today decoration. + ..circle() // Selected day decoration. + ..circle(color: theme.colorScheme.onPrimary.withOpacity(0.08)), + ); + }); + + testWidgets('Date picker dayOverlayColor resolves selected and focused state', (WidgetTester tester) async { + FocusManager.instance.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; + final ThemeData theme = ThemeData(useMaterial3: false); + await prepareDatePicker(tester, (Future date) async {}, theme: theme); + + // Select day 30. + await tester.tap(find.text('30')); + await tester.pumpAndSettle(); + final ShapeDecoration day30Decoration = findDayDecoration(tester, '30')!; + expect(day30Decoration.color, theme.colorScheme.primary); + + // Navigate to the grid. + await tester.sendKeyEvent(LogicalKeyboardKey.tab); + await tester.sendKeyEvent(LogicalKeyboardKey.tab); + await tester.sendKeyEvent(LogicalKeyboardKey.tab); + await tester.sendKeyEvent(LogicalKeyboardKey.tab); + await tester.pumpAndSettle(); + + // Day 30 is selected and focused. + expect( + findDayGridMaterial(tester), + paints + ..circle() // Today decoration. + ..circle() // Selected day decoration. + ..circle(color: theme.colorScheme.onPrimary.withOpacity(0.12)), + ); + }); + + testWidgets('Date picker dayOverlayColor resolves selected and pressed state', (WidgetTester tester) async { + final ThemeData theme = ThemeData(useMaterial3: false); + await prepareDatePicker(tester, (Future date) async {}, theme: theme); + + // Select day 30. + await tester.tap(find.text('30')); + await tester.pumpAndSettle(); + final ShapeDecoration day30Decoration = findDayDecoration(tester, '30')!; + expect(day30Decoration.color, theme.colorScheme.primary); + + final Offset center = tester.getCenter(find.text('30')); + final TestGesture gesture = await tester.createGesture( + kind: PointerDeviceKind.mouse, + ); + await gesture.addPointer(); + addTearDown(gesture.removePointer); + await gesture.down(center); + await tester.pumpAndSettle(); + + expect( + findDayGridMaterial(tester), + paints + ..circle() // Today decoration. + ..circle() // Selected day decoration. + ..circle() // Hovered decoration. + ..circle(color: theme.colorScheme.onPrimary.withOpacity(0.38)), + ); + await gesture.up(); + }); }); } diff --git a/packages/flutter/test/material/date_picker_theme_test.dart b/packages/flutter/test/material/date_picker_theme_test.dart index 0f949a6a1e..28a328d364 100644 --- a/packages/flutter/test/material/date_picker_theme_test.dart +++ b/packages/flutter/test/material/date_picker_theme_test.dart @@ -82,10 +82,10 @@ void main() { } ShapeDecoration? findDayDecoration(WidgetTester tester, String day) { - return tester.widget( + return tester.widget( find.ancestor( of: find.text(day), - matching: find.byType(DecoratedBox) + matching: find.byType(Ink) ), ).decoration as ShapeDecoration?; } @@ -756,7 +756,6 @@ void main() { datePickerTheme: DatePickerThemeData( dayOverlayColor: dayOverlayColor, ), - useMaterial3: true, ), home: Directionality( textDirection: TextDirection.ltr, @@ -776,17 +775,25 @@ void main() { ), ); + MaterialInkController findDayGridMaterial(WidgetTester tester) { + // All days are painted on the same Material widget. + // Use an arbitrary day to find this Material. + return Material.of(tester.element(find.text('17'))); + } + // Test the hover overlay color. - final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures'); final TestGesture gesture = await tester.createGesture( kind: PointerDeviceKind.mouse, ); await gesture.addPointer(); await gesture.moveTo(tester.getCenter(find.text('20'))); await tester.pumpAndSettle(); + expect( - inkFeatures, + findDayGridMaterial(tester), paints + ..circle() // Today decoration. + ..circle() // Selected day decoration. ..circle(color: dayOverlayColor.resolve({MaterialState.hovered})), ); @@ -796,16 +803,20 @@ void main() { if (kIsWeb) { // An extra circle is painted on the web for the hovered state. expect( - inkFeatures, + findDayGridMaterial(tester), paints + ..circle() // Today decoration. + ..circle() // Selected day decoration. ..circle(color: dayOverlayColor.resolve({MaterialState.hovered})) ..circle(color: dayOverlayColor.resolve({MaterialState.hovered})) ..circle(color: dayOverlayColor.resolve({MaterialState.pressed})), ); } else { expect( - inkFeatures, + findDayGridMaterial(tester), paints + ..circle() // Today decoration. + ..circle() // Selected day decoration. ..circle(color: dayOverlayColor.resolve({MaterialState.hovered})) ..circle(color: dayOverlayColor.resolve({MaterialState.pressed})), ); @@ -822,8 +833,10 @@ void main() { // Test the focused overlay color. expect( - inkFeatures, + findDayGridMaterial(tester), paints + ..circle() // Today decoration. + ..circle() // Selected day decoration. ..circle(color: dayOverlayColor.resolve({MaterialState.focused})), ); });