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).
This commit is contained in:
Bruno Leroux 2024-12-05 17:44:06 +01:00 committed by GitHub
parent 44cc2c4303
commit c5132b52c2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 365 additions and 54 deletions

View File

@ -14,6 +14,15 @@ void main() {
const Color todayForegroundColor = Colors.black; const Color todayForegroundColor = Colors.black;
const BorderSide todayBorder = BorderSide(width: 2); const BorderSide todayBorder = BorderSide(width: 2);
ShapeDecoration? findDayDecoration(WidgetTester tester, String day) {
return tester.widget<Ink>(
find.ancestor(
of: find.text(day),
matching: find.byType(Ink)
),
).decoration as ShapeDecoration?;
}
await tester.pumpWidget( await tester.pumpWidget(
const example.DatePickerApp(), const example.DatePickerApp(),
); );
@ -21,21 +30,13 @@ void main() {
await tester.tap(find.text('Open Date Picker')); await tester.tap(find.text('Open Date Picker'));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
ShapeDecoration dayShapeDecoration = tester.widget<DecoratedBox>(find.ancestor(
of: find.text('15'),
matching: find.byType(DecoratedBox),
)).decoration as ShapeDecoration;
// Test the current day shape decoration. // Test the current day shape decoration.
ShapeDecoration dayShapeDecoration = findDayDecoration(tester, '15')!;
expect(dayShapeDecoration.color, todayBackgroundColor); expect(dayShapeDecoration.color, todayBackgroundColor);
expect(dayShapeDecoration.shape, dayShape.copyWith(side: todayBorder.copyWith(color: todayForegroundColor))); expect(dayShapeDecoration.shape, dayShape.copyWith(side: todayBorder.copyWith(color: todayForegroundColor)));
dayShapeDecoration = tester.widget<DecoratedBox>(find.ancestor(
of: find.text('20'),
matching: find.byType(DecoratedBox),
)).decoration as ShapeDecoration;
// Test the selected day shape decoration. // Test the selected day shape decoration.
dayShapeDecoration = findDayDecoration(tester, '20')!;
expect(dayShapeDecoration.color, theme.colorScheme.primary); expect(dayShapeDecoration.color, theme.colorScheme.primary);
expect(dayShapeDecoration.shape, dayShape); expect(dayShapeDecoration.shape, dayShape);
@ -43,12 +44,8 @@ void main() {
await tester.tap(find.text('15')); await tester.tap(find.text('15'));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
dayShapeDecoration = tester.widget<DecoratedBox>(find.ancestor(
of: find.text('15'),
matching: find.byType(DecoratedBox),
)).decoration as ShapeDecoration;
// Test the selected day shape decoration. // Test the selected day shape decoration.
dayShapeDecoration = findDayDecoration(tester, '15')!;
expect(dayShapeDecoration.color, todayBackgroundColor); expect(dayShapeDecoration.color, todayBackgroundColor);
expect(dayShapeDecoration.shape, dayShape.copyWith(side: todayBorder.copyWith(color: todayForegroundColor))); expect(dayShapeDecoration.shape, dayShape.copyWith(side: todayBorder.copyWith(color: todayForegroundColor)));
}); });

View File

@ -20,6 +20,7 @@ import 'debug.dart';
import 'divider.dart'; import 'divider.dart';
import 'icon_button.dart'; import 'icon_button.dart';
import 'icons.dart'; import 'icons.dart';
import 'ink_decoration.dart';
import 'ink_well.dart'; import 'ink_well.dart';
import 'material_localizations.dart'; import 'material_localizations.dart';
import 'material_state.dart'; import 'material_state.dart';
@ -1056,7 +1057,7 @@ class _Day extends StatefulWidget {
final bool isSelectedDay; final bool isSelectedDay;
final bool isToday; final bool isToday;
final ValueChanged<DateTime> onChanged; final ValueChanged<DateTime> onChanged;
final FocusNode? focusNode; final FocusNode focusNode;
@override @override
State<_Day> createState() => _DayState(); State<_Day> createState() => _DayState();
@ -1111,7 +1112,7 @@ class _DayState extends State<_Day> {
shape: dayShape, shape: dayShape,
); );
Widget dayWidget = DecoratedBox( Widget dayWidget = Ink(
decoration: decoration, decoration: decoration,
child: Center( child: Center(
child: Text(localizations.formatDecimal(widget.day.day), style: dayStyle?.apply(color: dayForegroundColor)), child: Text(localizations.formatDecimal(widget.day.day), style: dayStyle?.apply(color: dayForegroundColor)),

View File

@ -1196,7 +1196,7 @@ void main() {
await gesture.moveTo(tester.getCenter(find.text('25'))); await gesture.moveTo(tester.getCenter(find.text('25')));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures'); 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)); expect(inkFeatures, paintsExactlyCountTimes(#clipPath, 1));
final Rect expectedClipRect = Rect.fromCircle(center: const Offset(400.0, 241.0), radius: 35.0); final Rect expectedClipRect = Rect.fromCircle(center: const Offset(400.0, 241.0), radius: 35.0);

View File

@ -132,6 +132,21 @@ void main() {
await callback(date); await callback(date);
} }
ShapeDecoration? findDayDecoration(WidgetTester tester, String day) {
return tester.widget<Ink>(
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', () { group('showDatePicker Dialog', () {
testWidgets('Default dialog size', (WidgetTester tester) async { testWidgets('Default dialog size', (WidgetTester tester) async {
Future<void> showPicker(WidgetTester tester, Size size) async { Future<void> showPicker(WidgetTester tester, Size size) async {
@ -1264,43 +1279,170 @@ void main() {
await tester.pump(); await tester.pump();
const Color todayColor = Color(0xff2196f3); // default primary color const Color todayColor = Color(0xff2196f3); // default primary color
expect( expect(
Material.of(tester.element(find.text('2'))), findDayGridMaterial(tester),
// The current day should be painted with a circle outline // The current day should be painted with a circle outline
paints..circle(color: todayColor, style: PaintingStyle.stroke, strokeWidth: 1.0), paints..circle(color: todayColor, style: PaintingStyle.stroke, strokeWidth: 1.0),
); );
}); });
}); });
testWidgets('Date picker dayOverlayColor resolves pressed state', (WidgetTester tester) async { testWidgets('Date picker dayOverlayColor resolves hovered state', (WidgetTester tester) async {
today = DateTime(2023, 5, 4);
final ThemeData theme = ThemeData(); final ThemeData theme = ThemeData();
final bool material3 = theme.useMaterial3; await prepareDatePicker(tester, (Future<DateTime?> date) async {}, theme: theme);
await prepareDatePicker(tester, (Future<DateTime?> date) async {
await tester.pump();
// Hovered. final Offset center = tester.getCenter(find.text('30'));
final Offset center = tester.getCenter(find.text('30')); final TestGesture gesture = await tester.createGesture(
final TestGesture gesture = await tester.createGesture( kind: PointerDeviceKind.mouse,
kind: PointerDeviceKind.mouse, );
); await gesture.addPointer();
await gesture.addPointer(); addTearDown(gesture.removePointer);
await gesture.moveTo(center); await gesture.moveTo(center);
await tester.pumpAndSettle(); 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)),
);
// Highlighted (pressed). expect(
await gesture.down(center); findDayGridMaterial(tester),
await tester.pumpAndSettle(); paints
expect( ..circle() // Today decoration.
Material.of(tester.element(find.text('30'))), ..circle() // Selected day decoration.
paints..circle()..circle(color: material3 ? theme.colorScheme.onSurfaceVariant.withOpacity(0.1) : theme.colorScheme.onSurfaceVariant.withOpacity(0.12)) ..circle(color: theme.colorScheme.onSurfaceVariant.withOpacity(0.08)),
); );
await gesture.up(); });
await tester.pumpAndSettle();
}, theme: theme); testWidgets('Date picker dayOverlayColor resolves focused state', (WidgetTester tester) async {
FocusManager.instance.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
final ThemeData theme = ThemeData();
await prepareDatePicker(tester, (Future<DateTime?> 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<DateTime?> 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<DateTime?> 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<DateTime?> 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<DateTime?> 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 { 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<DateTime?> 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<DateTime?> 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<DateTime?> 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<DateTime?> 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<DateTime?> 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<DateTime?> 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();
});
}); });
} }

View File

@ -82,10 +82,10 @@ void main() {
} }
ShapeDecoration? findDayDecoration(WidgetTester tester, String day) { ShapeDecoration? findDayDecoration(WidgetTester tester, String day) {
return tester.widget<DecoratedBox>( return tester.widget<Ink>(
find.ancestor( find.ancestor(
of: find.text(day), of: find.text(day),
matching: find.byType(DecoratedBox) matching: find.byType(Ink)
), ),
).decoration as ShapeDecoration?; ).decoration as ShapeDecoration?;
} }
@ -756,7 +756,6 @@ void main() {
datePickerTheme: DatePickerThemeData( datePickerTheme: DatePickerThemeData(
dayOverlayColor: dayOverlayColor, dayOverlayColor: dayOverlayColor,
), ),
useMaterial3: true,
), ),
home: Directionality( home: Directionality(
textDirection: TextDirection.ltr, 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. // Test the hover overlay color.
final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures');
final TestGesture gesture = await tester.createGesture( final TestGesture gesture = await tester.createGesture(
kind: PointerDeviceKind.mouse, kind: PointerDeviceKind.mouse,
); );
await gesture.addPointer(); await gesture.addPointer();
await gesture.moveTo(tester.getCenter(find.text('20'))); await gesture.moveTo(tester.getCenter(find.text('20')));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect( expect(
inkFeatures, findDayGridMaterial(tester),
paints paints
..circle() // Today decoration.
..circle() // Selected day decoration.
..circle(color: dayOverlayColor.resolve(<MaterialState>{MaterialState.hovered})), ..circle(color: dayOverlayColor.resolve(<MaterialState>{MaterialState.hovered})),
); );
@ -796,16 +803,20 @@ void main() {
if (kIsWeb) { if (kIsWeb) {
// An extra circle is painted on the web for the hovered state. // An extra circle is painted on the web for the hovered state.
expect( expect(
inkFeatures, findDayGridMaterial(tester),
paints paints
..circle() // Today decoration.
..circle() // Selected day decoration.
..circle(color: dayOverlayColor.resolve(<MaterialState>{MaterialState.hovered})) ..circle(color: dayOverlayColor.resolve(<MaterialState>{MaterialState.hovered}))
..circle(color: dayOverlayColor.resolve(<MaterialState>{MaterialState.hovered})) ..circle(color: dayOverlayColor.resolve(<MaterialState>{MaterialState.hovered}))
..circle(color: dayOverlayColor.resolve(<MaterialState>{MaterialState.pressed})), ..circle(color: dayOverlayColor.resolve(<MaterialState>{MaterialState.pressed})),
); );
} else { } else {
expect( expect(
inkFeatures, findDayGridMaterial(tester),
paints paints
..circle() // Today decoration.
..circle() // Selected day decoration.
..circle(color: dayOverlayColor.resolve(<MaterialState>{MaterialState.hovered})) ..circle(color: dayOverlayColor.resolve(<MaterialState>{MaterialState.hovered}))
..circle(color: dayOverlayColor.resolve(<MaterialState>{MaterialState.pressed})), ..circle(color: dayOverlayColor.resolve(<MaterialState>{MaterialState.pressed})),
); );
@ -822,8 +833,10 @@ void main() {
// Test the focused overlay color. // Test the focused overlay color.
expect( expect(
inkFeatures, findDayGridMaterial(tester),
paints paints
..circle() // Today decoration.
..circle() // Selected day decoration.
..circle(color: dayOverlayColor.resolve(<MaterialState>{MaterialState.focused})), ..circle(color: dayOverlayColor.resolve(<MaterialState>{MaterialState.focused})),
); );
}); });