Unverified Commit 06b87fba authored by Qun Cheng's avatar Qun Cheng Committed by GitHub

Update Radio button to material 3 (#111774)

parent ce7789e0
......@@ -31,6 +31,7 @@ import 'package:gen_defaults/input_chip_template.dart';
import 'package:gen_defaults/input_decorator_template.dart';
import 'package:gen_defaults/navigation_bar_template.dart';
import 'package:gen_defaults/navigation_rail_template.dart';
import 'package:gen_defaults/radio_template.dart';
import 'package:gen_defaults/surface_tint.dart';
import 'package:gen_defaults/switch_template.dart';
import 'package:gen_defaults/text_field_template.dart';
......@@ -79,6 +80,7 @@ Future<void> main(List<String> args) async {
'navigation_drawer.json',
'navigation_rail.json',
'palette.json',
'radio_button.json',
'segmented_button_outlined.json',
'shape.json',
'slider.json',
......@@ -124,6 +126,7 @@ Future<void> main(List<String> args) async {
InputDecoratorTemplate('InputDecorator', '$materialLib/input_decorator.dart', tokens).updateFile();
NavigationBarTemplate('NavigationBar', '$materialLib/navigation_bar.dart', tokens).updateFile();
NavigationRailTemplate('NavigationRail', '$materialLib/navigation_rail.dart', tokens).updateFile();
RadioTemplate('Radio<T>', '$materialLib/radio.dart', tokens).updateFile();
SurfaceTintTemplate('SurfaceTint', '$materialLib/elevation_overlay.dart', tokens).updateFile();
SwitchTemplate('Switch', '$materialLib/switch.dart', tokens).updateFile();
TextFieldTemplate('TextField', '$materialLib/text_field.dart', tokens).updateFile();
......
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'template.dart';
class RadioTemplate extends TokenTemplate {
const RadioTemplate(super.blockName, super.fileName, super.tokens, {
super.colorSchemePrefix = '_colors.',
});
@override
String generate() => '''
class _RadioDefaultsM3 extends RadioThemeData {
_RadioDefaultsM3(this.context);
final BuildContext context;
late final ThemeData _theme = Theme.of(context);
late final ColorScheme _colors = _theme.colorScheme;
@override
MaterialStateProperty<Color> get fillColor {
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.selected)) {
if (states.contains(MaterialState.disabled)) {
return ${componentColor('md.comp.radio-button.disabled.selected.icon')};
}
if (states.contains(MaterialState.pressed)) {
return ${componentColor('md.comp.radio-button.selected.pressed.icon')};
}
if (states.contains(MaterialState.hovered)) {
return ${componentColor('md.comp.radio-button.selected.hover.icon')};
}
if (states.contains(MaterialState.focused)) {
return ${componentColor('md.comp.radio-button.selected.focus.icon')};
}
return ${componentColor('md.comp.radio-button.selected.icon')};
}
if (states.contains(MaterialState.disabled)) {
return ${componentColor('md.comp.radio-button.disabled.unselected.icon')};
}
if (states.contains(MaterialState.pressed)) {
return ${componentColor('md.comp.radio-button.unselected.pressed.icon')};
}
if (states.contains(MaterialState.hovered)) {
return ${componentColor('md.comp.radio-button.unselected.hover.icon')};
}
if (states.contains(MaterialState.focused)) {
return ${componentColor('md.comp.radio-button.unselected.focus.icon')};
}
return ${componentColor('md.comp.radio-button.unselected.icon')};
});
}
@override
MaterialStateProperty<Color> get overlayColor {
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.selected)) {
if (states.contains(MaterialState.pressed)) {
return ${componentColor('md.comp.radio-button.selected.pressed.state-layer')};
}
if (states.contains(MaterialState.hovered)) {
return ${componentColor('md.comp.radio-button.selected.hover.state-layer')};
}
if (states.contains(MaterialState.focused)) {
return ${componentColor('md.comp.radio-button.selected.focus.state-layer')};
}
return Colors.transparent;
}
if (states.contains(MaterialState.pressed)) {
return ${componentColor('md.comp.radio-button.unselected.pressed.state-layer')};
}
if (states.contains(MaterialState.hovered)) {
return ${componentColor('md.comp.radio-button.unselected.hover.state-layer')};
}
if (states.contains(MaterialState.focused)) {
return ${componentColor('md.comp.radio-button.unselected.focus.state-layer')};
}
return Colors.transparent;
});
}
@override
MaterialTapTargetSize get materialTapTargetSize => _theme.materialTapTargetSize;
@override
VisualDensity get visualDensity => _theme.visualDensity;
}
''';
}
......@@ -4,6 +4,8 @@
import 'package:flutter/widgets.dart';
import 'color_scheme.dart';
import 'colors.dart';
import 'constants.dart';
import 'debug.dart';
import 'material_state.dart';
......@@ -360,30 +362,17 @@ class _RadioState<T> extends State<Radio<T>> with TickerProviderStateMixin, Togg
});
}
MaterialStateProperty<Color> get _defaultFillColor {
final ThemeData themeData = Theme.of(context);
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.disabled)) {
return themeData.disabledColor;
}
if (states.contains(MaterialState.selected)) {
return themeData.colorScheme.secondary;
}
return themeData.unselectedWidgetColor;
});
}
@override
Widget build(BuildContext context) {
assert(debugCheckHasMaterial(context));
final ThemeData themeData = Theme.of(context);
final RadioThemeData radioTheme = RadioTheme.of(context);
final RadioThemeData defaults = Theme.of(context).useMaterial3 ? _RadioDefaultsM3(context) : _RadioDefaultsM2(context);
final MaterialTapTargetSize effectiveMaterialTapTargetSize = widget.materialTapTargetSize
?? radioTheme.materialTapTargetSize
?? themeData.materialTapTargetSize;
?? defaults.materialTapTargetSize!;
final VisualDensity effectiveVisualDensity = widget.visualDensity
?? radioTheme.visualDensity
?? themeData.visualDensity;
?? defaults.visualDensity!;
Size size;
switch (effectiveMaterialTapTargetSize) {
case MaterialTapTargetSize.padded:
......@@ -405,36 +394,47 @@ class _RadioState<T> extends State<Radio<T>> with TickerProviderStateMixin, Togg
// so that they can be lerped between.
final Set<MaterialState> activeStates = states..add(MaterialState.selected);
final Set<MaterialState> inactiveStates = states..remove(MaterialState.selected);
final Color effectiveActiveColor = widget.fillColor?.resolve(activeStates)
final Color? activeColor = widget.fillColor?.resolve(activeStates)
?? _widgetFillColor.resolve(activeStates)
?? radioTheme.fillColor?.resolve(activeStates)
?? _defaultFillColor.resolve(activeStates);
final Color effectiveInactiveColor = widget.fillColor?.resolve(inactiveStates)
?? radioTheme.fillColor?.resolve(activeStates);
final Color effectiveActiveColor = activeColor ?? defaults.fillColor!.resolve(activeStates)!;
final Color? inactiveColor = widget.fillColor?.resolve(inactiveStates)
?? _widgetFillColor.resolve(inactiveStates)
?? radioTheme.fillColor?.resolve(inactiveStates)
?? _defaultFillColor.resolve(inactiveStates);
?? radioTheme.fillColor?.resolve(inactiveStates);
final Color effectiveInactiveColor = inactiveColor ?? defaults.fillColor!.resolve(inactiveStates)!;
final Set<MaterialState> focusedStates = states..add(MaterialState.focused);
final Color effectiveFocusOverlayColor = widget.overlayColor?.resolve(focusedStates)
Color effectiveFocusOverlayColor = widget.overlayColor?.resolve(focusedStates)
?? widget.focusColor
?? radioTheme.overlayColor?.resolve(focusedStates)
?? themeData.focusColor;
?? defaults.overlayColor!.resolve(focusedStates)!;
final Set<MaterialState> hoveredStates = states..add(MaterialState.hovered);
final Color effectiveHoverOverlayColor = widget.overlayColor?.resolve(hoveredStates)
Color effectiveHoverOverlayColor = widget.overlayColor?.resolve(hoveredStates)
?? widget.hoverColor
?? radioTheme.overlayColor?.resolve(hoveredStates)
?? themeData.hoverColor;
?? defaults.overlayColor!.resolve(hoveredStates)!;
final Set<MaterialState> activePressedStates = activeStates..add(MaterialState.pressed);
final Color effectiveActivePressedOverlayColor = widget.overlayColor?.resolve(activePressedStates)
?? radioTheme.overlayColor?.resolve(activePressedStates)
?? effectiveActiveColor.withAlpha(kRadialReactionAlpha);
?? activeColor?.withAlpha(kRadialReactionAlpha)
?? defaults.overlayColor!.resolve(activePressedStates)!;
final Set<MaterialState> inactivePressedStates = inactiveStates..add(MaterialState.pressed);
final Color effectiveInactivePressedOverlayColor = widget.overlayColor?.resolve(inactivePressedStates)
?? radioTheme.overlayColor?.resolve(inactivePressedStates)
?? effectiveActiveColor.withAlpha(kRadialReactionAlpha);
?? inactiveColor?.withAlpha(kRadialReactionAlpha)
?? defaults.overlayColor!.resolve(inactivePressedStates)!;
if (downPosition != null) {
effectiveHoverOverlayColor = states.contains(MaterialState.selected)
? effectiveActivePressedOverlayColor
: effectiveInactivePressedOverlayColor;
effectiveFocusOverlayColor = states.contains(MaterialState.selected)
? effectiveActivePressedOverlayColor
: effectiveInactivePressedOverlayColor;
}
return Semantics(
inMutuallyExclusiveGroup: true,
......@@ -485,3 +485,134 @@ class _RadioPainter extends ToggleablePainter {
}
}
}
// Hand coded defaults based on Material Design 2.
class _RadioDefaultsM2 extends RadioThemeData {
_RadioDefaultsM2(this.context);
final BuildContext context;
late final ThemeData _theme = Theme.of(context);
late final ColorScheme _colors = _theme.colorScheme;
@override
MaterialStateProperty<Color> get fillColor {
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.disabled)) {
return _theme.disabledColor;
}
if (states.contains(MaterialState.selected)) {
return _colors.secondary;
}
return _theme.unselectedWidgetColor;
});
}
@override
MaterialStateProperty<Color> get overlayColor {
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.pressed)) {
return fillColor.resolve(states).withAlpha(kRadialReactionAlpha);
}
if (states.contains(MaterialState.focused)) {
return _theme.focusColor;
}
if (states.contains(MaterialState.hovered)) {
return _theme.hoverColor;
}
return Colors.transparent;
});
}
@override
MaterialTapTargetSize get materialTapTargetSize => _theme.materialTapTargetSize;
@override
VisualDensity get visualDensity => _theme.visualDensity;
}
// BEGIN GENERATED TOKEN PROPERTIES - Radio<T>
// Do not edit by hand. The code between the "BEGIN GENERATED" and
// "END GENERATED" comments are generated from data in the Material
// Design token database by the script:
// dev/tools/gen_defaults/bin/gen_defaults.dart.
// Token database version: v0_132
class _RadioDefaultsM3 extends RadioThemeData {
_RadioDefaultsM3(this.context);
final BuildContext context;
late final ThemeData _theme = Theme.of(context);
late final ColorScheme _colors = _theme.colorScheme;
@override
MaterialStateProperty<Color> get fillColor {
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.selected)) {
if (states.contains(MaterialState.disabled)) {
return _colors.onSurface.withOpacity(0.38);
}
if (states.contains(MaterialState.pressed)) {
return _colors.primary;
}
if (states.contains(MaterialState.hovered)) {
return _colors.primary;
}
if (states.contains(MaterialState.focused)) {
return _colors.primary;
}
return _colors.primary;
}
if (states.contains(MaterialState.disabled)) {
return _colors.onSurface.withOpacity(0.38);
}
if (states.contains(MaterialState.pressed)) {
return _colors.onSurface;
}
if (states.contains(MaterialState.hovered)) {
return _colors.onSurface;
}
if (states.contains(MaterialState.focused)) {
return _colors.onSurface;
}
return _colors.onSurfaceVariant;
});
}
@override
MaterialStateProperty<Color> get overlayColor {
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.selected)) {
if (states.contains(MaterialState.pressed)) {
return _colors.onSurface.withOpacity(0.12);
}
if (states.contains(MaterialState.hovered)) {
return _colors.primary.withOpacity(0.08);
}
if (states.contains(MaterialState.focused)) {
return _colors.primary.withOpacity(0.12);
}
return Colors.transparent;
}
if (states.contains(MaterialState.pressed)) {
return _colors.primary.withOpacity(0.12);
}
if (states.contains(MaterialState.hovered)) {
return _colors.onSurface.withOpacity(0.08);
}
if (states.contains(MaterialState.focused)) {
return _colors.onSurface.withOpacity(0.12);
}
return Colors.transparent;
});
}
@override
MaterialTapTargetSize get materialTapTargetSize => _theme.materialTapTargetSize;
@override
VisualDensity get visualDensity => _theme.visualDensity;
}
// END GENERATED TOKEN PROPERTIES - Radio<T>
......@@ -1266,6 +1266,7 @@ class ThemeData with Diagnosticable {
/// * Lists: [ListTile]
/// * Navigation bar: [NavigationBar] (new, replacing [BottomNavigationBar])
/// * [Navigation rail](https://m3.material.io/components/navigation-rail): [NavigationRail]
/// * Radio button: [Radio]
/// * Switch: [Switch]
/// * Top app bar: [AppBar]
///
......
......@@ -19,11 +19,15 @@ import '../rendering/mock_canvas.dart';
import '../widgets/semantics_tester.dart';
void main() {
final ThemeData theme = ThemeData();
testWidgets('Radio control test', (WidgetTester tester) async {
final Key key = UniqueKey();
final List<int?> log = <int?>[];
await tester.pumpWidget(Material(
await tester.pumpWidget(Theme(
data: theme,
child: Material(
child: Center(
child: Radio<int>(
key: key,
......@@ -32,6 +36,7 @@ void main() {
onChanged: log.add,
),
),
),
));
await tester.tap(find.byKey(key));
......@@ -39,7 +44,9 @@ void main() {
expect(log, equals(<int>[1]));
log.clear();
await tester.pumpWidget(Material(
await tester.pumpWidget(Theme(
data: theme,
child: Material(
child: Center(
child: Radio<int>(
key: key,
......@@ -49,13 +56,16 @@ void main() {
activeColor: Colors.green[500],
),
),
),
));
await tester.tap(find.byKey(key));
expect(log, isEmpty);
await tester.pumpWidget(Material(
await tester.pumpWidget(Theme(
data: theme,
child: Material(
child: Center(
child: Radio<int>(
key: key,
......@@ -64,6 +74,7 @@ void main() {
onChanged: null,
),
),
),
));
await tester.tap(find.byKey(key));
......@@ -75,7 +86,9 @@ void main() {
final Key key = UniqueKey();
final List<int?> log = <int?>[];
await tester.pumpWidget(Material(
await tester.pumpWidget(Theme(
data: theme,
child: Material(
child: Center(
child: Radio<int>(
key: key,
......@@ -85,6 +98,7 @@ void main() {
toggleable: true,
),
),
),
));
await tester.tap(find.byKey(key));
......@@ -92,7 +106,9 @@ void main() {
expect(log, equals(<int>[1]));
log.clear();
await tester.pumpWidget(Material(
await tester.pumpWidget(Theme(
data: theme,
child: Material(
child: Center(
child: Radio<int>(
key: key,
......@@ -102,6 +118,7 @@ void main() {
toggleable: true,
),
),
),
));
await tester.tap(find.byKey(key));
......@@ -109,7 +126,9 @@ void main() {
expect(log, equals(<int?>[null]));
log.clear();
await tester.pumpWidget(Material(
await tester.pumpWidget(Theme(
data: theme,
child: Material(
child: Center(
child: Radio<int>(
key: key,
......@@ -119,6 +138,7 @@ void main() {
toggleable: true,
),
),
),
));
await tester.tap(find.byKey(key));
......@@ -130,7 +150,7 @@ void main() {
final Key key1 = UniqueKey();
await tester.pumpWidget(
Theme(
data: ThemeData(materialTapTargetSize: MaterialTapTargetSize.padded),
data: theme.copyWith(materialTapTargetSize: MaterialTapTargetSize.padded),
child: Directionality(
textDirection: TextDirection.ltr,
child: Material(
......@@ -152,7 +172,7 @@ void main() {
final Key key2 = UniqueKey();
await tester.pumpWidget(
Theme(
data: ThemeData(materialTapTargetSize: MaterialTapTargetSize.shrinkWrap),
data: theme.copyWith(materialTapTargetSize: MaterialTapTargetSize.shrinkWrap),
child: Directionality(
textDirection: TextDirection.ltr,
child: Material(
......@@ -176,12 +196,15 @@ void main() {
testWidgets('Radio semantics', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
await tester.pumpWidget(Material(
await tester.pumpWidget(Theme(
data: theme,
child: Material(
child: Radio<int>(
value: 1,
groupValue: 2,
onChanged: (int? i) { },
),
),
));
expect(semantics, hasSemantics(TestSemantics.root(
......@@ -202,12 +225,15 @@ void main() {
],
), ignoreRect: true, ignoreTransform: true));
await tester.pumpWidget(Material(
await tester.pumpWidget(Theme(
data: theme,
child: Material(
child: Radio<int>(
value: 2,
groupValue: 2,
onChanged: (int? i) { },
),
),
));
expect(semantics, hasSemantics(TestSemantics.root(
......@@ -229,12 +255,15 @@ void main() {
],
), ignoreRect: true, ignoreTransform: true));
await tester.pumpWidget(const Material(
await tester.pumpWidget(Theme(
data: theme,
child: const Material(
child: Radio<int>(
value: 1,
groupValue: 2,
onChanged: null,
),
),
));
expect(semantics, hasSemantics(TestSemantics.root(
......@@ -267,12 +296,15 @@ void main() {
],
), ignoreRect: true, ignoreTransform: true));
await tester.pumpWidget(const Material(
await tester.pumpWidget(Theme(
data: theme,
child: const Material(
child: Radio<int>(
value: 2,
groupValue: 2,
onChanged: null,
),
),
));
expect(semantics, hasSemantics(TestSemantics.root(
......@@ -301,7 +333,9 @@ void main() {
semanticEvent = message;
});
await tester.pumpWidget(Material(
await tester.pumpWidget(Theme(
data: theme,
child: Material(
child: Radio<int>(
key: key,
value: 1,
......@@ -310,6 +344,7 @@ void main() {
radioValue = i;
},
),
),
));
await tester.tap(find.byKey(key));
......@@ -327,12 +362,12 @@ void main() {
tester.binding.defaultBinaryMessenger.setMockDecodedMessageHandler<dynamic>(SystemChannels.accessibility, null);
});
testWidgets('Radio ink ripple is displayed correctly', (WidgetTester tester) async {
testWidgets('Radio ink ripple is displayed correctly - M2', (WidgetTester tester) async {
final Key painterKey = UniqueKey();
const Key radioKey = Key('radio');
await tester.pumpWidget(MaterialApp(
theme: ThemeData(),
theme: ThemeData(useMaterial3: false),
home: Scaffold(
body: RepaintBoundary(
key: painterKey,
......@@ -366,6 +401,7 @@ void main() {
const double splashRadius = 30;
Widget buildApp() {
return MaterialApp(
theme: theme,
home: Material(
child: Center(
child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
......@@ -404,6 +440,7 @@ void main() {
const Key radioKey = Key('radio');
Widget buildApp({bool enabled = true}) {
return MaterialApp(
theme: theme,
home: Material(
child: Center(
child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
......@@ -453,13 +490,15 @@ void main() {
expect(focusNode.hasPrimaryFocus, isTrue);
expect(
Material.of(tester.element(find.byKey(radioKey))),
paints
theme.useMaterial3
? (paints..rect()..circle(color: Colors.orange[500])..circle(color: theme.colorScheme.onSurface))
: (paints
..rect(
color: const Color(0xffffffff),
rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0),
)
..circle(color: Colors.orange[500])
..circle(color: const Color(0x8a000000), style: PaintingStyle.stroke, strokeWidth: 2.0),
..circle(color: const Color(0x8a000000), style: PaintingStyle.stroke, strokeWidth: 2.0)),
);
// Check when the radio is selected, but disabled.
......@@ -485,6 +524,7 @@ void main() {
const Key radioKey = Key('radio');
Widget buildApp({bool enabled = true}) {
return MaterialApp(
theme: theme,
home: Material(
child: Center(
child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
......@@ -541,7 +581,7 @@ void main() {
rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0),
)
..circle(color: Colors.orange[500])
..circle(color: const Color(0x8a000000), style: PaintingStyle.stroke, strokeWidth: 2.0),
..circle(color: theme.useMaterial3 ? theme.colorScheme.onSurface : const Color(0x8a000000), style: PaintingStyle.stroke, strokeWidth: 2.0),
);
// Check when the radio is selected, but disabled.
......@@ -570,6 +610,7 @@ void main() {
final FocusNode focusNode2 = FocusNode(debugLabel: 'radio2');
Widget buildApp({bool enabled = true}) {
return MaterialApp(
theme: theme,
home: Material(
child: Center(
child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
......@@ -644,6 +685,7 @@ void main() {
Future<void> buildTest(VisualDensity visualDensity) async {
return tester.pumpWidget(
MaterialApp(
theme: theme,
home: Material(
child: Center(
child: Radio<int>(
......@@ -682,6 +724,7 @@ void main() {
// Test Radio() constructor
await tester.pumpWidget(
MaterialApp(
theme: theme,
home: Scaffold(
body: Align(
alignment: Alignment.topLeft,
......@@ -713,6 +756,7 @@ void main() {
// Test default cursor
await tester.pumpWidget(
MaterialApp(
theme: theme,
home: Scaffold(
body: Align(
alignment: Alignment.topLeft,
......@@ -735,8 +779,9 @@ void main() {
// Test default cursor when disabled
await tester.pumpWidget(
const MaterialApp(
home: Scaffold(
MaterialApp(
theme: theme,
home: const Scaffold(
body: Align(
alignment: Alignment.topLeft,
child: Material(
......@@ -783,6 +828,7 @@ void main() {
const Key radioKey = Key('radio');
Widget buildApp({required bool enabled}) {
return MaterialApp(
theme: theme,
home: Material(
child: Center(
child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
......@@ -890,6 +936,7 @@ void main() {
const Key radioKey = Key('radio');
Widget buildApp() {
return MaterialApp(
theme: theme,
home: Material(
child: Center(
child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
......@@ -922,13 +969,15 @@ void main() {
expect(focusNode.hasPrimaryFocus, isTrue);
expect(
Material.of(tester.element(find.byKey(radioKey))),
paints
theme.useMaterial3
? (paints..rect()..circle(color: theme.colorScheme.primary.withOpacity(0.12))..circle(color: focusedFillColor))
: (paints
..rect(
color: const Color(0xffffffff),
rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0),
)
..circle(color: Colors.black12)
..circle(color: focusedFillColor),
..circle(color: focusedFillColor)),
);
// Start hovering
......@@ -944,7 +993,7 @@ void main() {
color: const Color(0xffffffff),
rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0),
)
..circle(color: Colors.black12)
..circle(color: theme.useMaterial3 ? theme.colorScheme.primary.withOpacity(0.08) : Colors.black12)
..circle(color: hoveredFillColor),
);
});
......@@ -988,6 +1037,7 @@ void main() {
Widget buildRadio({bool active = false, bool focused = false, bool useOverlay = true}) {
return MaterialApp(
theme: theme,
home: Scaffold(
body: Radio<bool>(
focusNode: focusNode,
......@@ -1061,6 +1111,7 @@ void main() {
reason: 'Active pressed Radio should have overlay color: $activePressedOverlayColor',
);
await tester.pumpWidget(Container());
await tester.pumpWidget(buildRadio(focused: true));
await tester.pumpAndSettle();
......@@ -1097,6 +1148,7 @@ void main() {
Widget buildRadio(bool show) {
return MaterialApp(
theme: theme,
home: Material(
child: Center(
child: show ? Radio<bool>(key: key, value: true, groupValue: false, onChanged: (_) { }) : Container(),
......@@ -1121,8 +1173,9 @@ void main() {
const String longPressTooltip = 'long press tooltip';
const String tapTooltip = 'tap tooltip';
await tester.pumpWidget(
const MaterialApp(
home: Material(
MaterialApp(
theme: theme,
home: const Material(
child: Tooltip(
message: longPressTooltip,
child: Radio<bool>(value: true, groupValue: false, onChanged: null),
......@@ -1149,8 +1202,9 @@ void main() {
// Tooltip shows up after tapping when set triggerMode to TooltipTriggerMode.tap.
await tester.pumpWidget(
const MaterialApp(
home: Material(
MaterialApp(
theme: theme,
home: const Material(
child: Tooltip(
triggerMode: TooltipTriggerMode.tap,
message: tapTooltip,
......@@ -1167,4 +1221,154 @@ void main() {
await tester.pump(const Duration(milliseconds: 10));
expect(find.text(tapTooltip), findsOneWidget);
});
testWidgets('Radio button default colors', (WidgetTester tester) async {
Widget buildRadio({bool enabled = true, bool selected = true}) {
return MaterialApp(
theme: theme,
home: Scaffold(
body: Radio<bool>(
value: true,
groupValue: true,
onChanged: enabled ? (_) {} : null,
),
)
);
}
await tester.pumpWidget(buildRadio());
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Radio<bool>))),
paints
..circle(color: const Color(0xFF2196F3)) // Outer circle - blue primary value
..circle(color: const Color(0xFF2196F3))..restore(), // Inner circle - blue primary value
);
await tester.pumpWidget(Container());
await tester.pumpWidget(buildRadio(selected: false));
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Radio<bool>))),
paints
..save()
..circle(color: const Color(0xFF2196F3))
..restore(),
);
await tester.pumpWidget(Container());
await tester.pumpWidget(buildRadio(enabled: false));
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Radio<bool>))),
theme.useMaterial3
? (paints
..circle(color: theme.colorScheme.onSurface.withOpacity(0.38)))
: (paints..circle(color: Colors.black38))
);
});
testWidgets('Radio button default overlay colors in hover/focus/press states', (WidgetTester tester) async {
final FocusNode focusNode = FocusNode(debugLabel: 'Radio');
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
final ColorScheme colors = theme.colorScheme;
final bool material3 = theme.useMaterial3;
Widget buildRadio({bool enabled = true, bool focused = false, bool selected = true}) {
return MaterialApp(
theme: theme,
home: Scaffold(
body: Radio<bool>(
focusNode: focusNode,
autofocus: focused,
value: true,
groupValue: selected,
onChanged: enabled ? (_) {} : null,
),
),
);
}
// default selected radio
await tester.pumpWidget(buildRadio());
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Radio<bool>))),
material3
? (paints..circle(color: colors.primary.withOpacity(1)))
: (paints..circle(color: colors.secondary))
);
// selected radio in pressed state
await tester.pumpWidget(buildRadio());
await tester.startGesture(tester.getCenter(find.byType(Radio<bool>)));
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Radio<bool>))),
paints..circle(color: material3
? colors.onSurface.withOpacity(0.12)
: colors.secondary.withAlpha(0x1F))
..circle(color: material3
? colors.primary.withOpacity(1)
: colors.secondary
)
);
// unselected radio in pressed state
await tester.pumpWidget(buildRadio(selected: false));
await tester.startGesture(tester.getCenter(find.byType(Radio<bool>)));
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Radio<bool>))),
material3
? (paints..circle(color: colors.primary.withOpacity(0.12))..circle(color: colors.onSurface.withOpacity(1)))
: (paints..circle(color: theme.unselectedWidgetColor.withAlpha(0x1F))..circle(color: theme.unselectedWidgetColor))
);
// selected radio in focused state
await tester.pumpWidget(Container()); // reset test
await tester.pumpWidget(buildRadio(focused: true));
await tester.pumpAndSettle();
expect(focusNode.hasPrimaryFocus, isTrue);
expect(
Material.of(tester.element(find.byType(Radio<bool>))),
material3
? (paints..circle(color: colors.primary.withOpacity(0.12))..circle(color: colors.primary.withOpacity(1)))
: (paints..circle(color: theme.focusColor)..circle(color: colors.secondary))
);
// unselected radio in focused state
await tester.pumpWidget(Container()); // reset test
await tester.pumpWidget(buildRadio(focused: true, selected: false));
await tester.pumpAndSettle();
expect(focusNode.hasPrimaryFocus, isTrue);
expect(
Material.of(tester.element(find.byType(Radio<bool>))),
material3
? (paints..circle(color: colors.onSurface.withOpacity(0.12))..circle(color: colors.onSurface.withOpacity(1)))
: (paints..circle(color: theme.focusColor)..circle(color: theme.unselectedWidgetColor))
);
// selected radio in hovered state
await tester.pumpWidget(Container()); // reset test
await tester.pumpWidget(buildRadio());
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
await gesture.addPointer();
await gesture.moveTo(tester.getCenter(find.byType(Radio<bool>)));
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Radio<bool>))),
material3
? (paints..circle(color: colors.primary.withOpacity(0.08))..circle(color: colors.primary.withOpacity(1)))
: (paints..circle(color: theme.hoverColor)..circle(color: colors.secondary))
);
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment