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'; ...@@ -31,6 +31,7 @@ import 'package:gen_defaults/input_chip_template.dart';
import 'package:gen_defaults/input_decorator_template.dart'; import 'package:gen_defaults/input_decorator_template.dart';
import 'package:gen_defaults/navigation_bar_template.dart'; import 'package:gen_defaults/navigation_bar_template.dart';
import 'package:gen_defaults/navigation_rail_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/surface_tint.dart';
import 'package:gen_defaults/switch_template.dart'; import 'package:gen_defaults/switch_template.dart';
import 'package:gen_defaults/text_field_template.dart'; import 'package:gen_defaults/text_field_template.dart';
...@@ -79,6 +80,7 @@ Future<void> main(List<String> args) async { ...@@ -79,6 +80,7 @@ Future<void> main(List<String> args) async {
'navigation_drawer.json', 'navigation_drawer.json',
'navigation_rail.json', 'navigation_rail.json',
'palette.json', 'palette.json',
'radio_button.json',
'segmented_button_outlined.json', 'segmented_button_outlined.json',
'shape.json', 'shape.json',
'slider.json', 'slider.json',
...@@ -124,6 +126,7 @@ Future<void> main(List<String> args) async { ...@@ -124,6 +126,7 @@ Future<void> main(List<String> args) async {
InputDecoratorTemplate('InputDecorator', '$materialLib/input_decorator.dart', tokens).updateFile(); InputDecoratorTemplate('InputDecorator', '$materialLib/input_decorator.dart', tokens).updateFile();
NavigationBarTemplate('NavigationBar', '$materialLib/navigation_bar.dart', tokens).updateFile(); NavigationBarTemplate('NavigationBar', '$materialLib/navigation_bar.dart', tokens).updateFile();
NavigationRailTemplate('NavigationRail', '$materialLib/navigation_rail.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(); SurfaceTintTemplate('SurfaceTint', '$materialLib/elevation_overlay.dart', tokens).updateFile();
SwitchTemplate('Switch', '$materialLib/switch.dart', tokens).updateFile(); SwitchTemplate('Switch', '$materialLib/switch.dart', tokens).updateFile();
TextFieldTemplate('TextField', '$materialLib/text_field.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 @@ ...@@ -4,6 +4,8 @@
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'color_scheme.dart';
import 'colors.dart';
import 'constants.dart'; import 'constants.dart';
import 'debug.dart'; import 'debug.dart';
import 'material_state.dart'; import 'material_state.dart';
...@@ -360,30 +362,17 @@ class _RadioState<T> extends State<Radio<T>> with TickerProviderStateMixin, Togg ...@@ -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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
assert(debugCheckHasMaterial(context)); assert(debugCheckHasMaterial(context));
final ThemeData themeData = Theme.of(context);
final RadioThemeData radioTheme = RadioTheme.of(context); final RadioThemeData radioTheme = RadioTheme.of(context);
final RadioThemeData defaults = Theme.of(context).useMaterial3 ? _RadioDefaultsM3(context) : _RadioDefaultsM2(context);
final MaterialTapTargetSize effectiveMaterialTapTargetSize = widget.materialTapTargetSize final MaterialTapTargetSize effectiveMaterialTapTargetSize = widget.materialTapTargetSize
?? radioTheme.materialTapTargetSize ?? radioTheme.materialTapTargetSize
?? themeData.materialTapTargetSize; ?? defaults.materialTapTargetSize!;
final VisualDensity effectiveVisualDensity = widget.visualDensity final VisualDensity effectiveVisualDensity = widget.visualDensity
?? radioTheme.visualDensity ?? radioTheme.visualDensity
?? themeData.visualDensity; ?? defaults.visualDensity!;
Size size; Size size;
switch (effectiveMaterialTapTargetSize) { switch (effectiveMaterialTapTargetSize) {
case MaterialTapTargetSize.padded: case MaterialTapTargetSize.padded:
...@@ -405,36 +394,47 @@ class _RadioState<T> extends State<Radio<T>> with TickerProviderStateMixin, Togg ...@@ -405,36 +394,47 @@ class _RadioState<T> extends State<Radio<T>> with TickerProviderStateMixin, Togg
// so that they can be lerped between. // so that they can be lerped between.
final Set<MaterialState> activeStates = states..add(MaterialState.selected); final Set<MaterialState> activeStates = states..add(MaterialState.selected);
final Set<MaterialState> inactiveStates = states..remove(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) ?? _widgetFillColor.resolve(activeStates)
?? radioTheme.fillColor?.resolve(activeStates) ?? radioTheme.fillColor?.resolve(activeStates);
?? _defaultFillColor.resolve(activeStates); final Color effectiveActiveColor = activeColor ?? defaults.fillColor!.resolve(activeStates)!;
final Color effectiveInactiveColor = widget.fillColor?.resolve(inactiveStates) final Color? inactiveColor = widget.fillColor?.resolve(inactiveStates)
?? _widgetFillColor.resolve(inactiveStates) ?? _widgetFillColor.resolve(inactiveStates)
?? radioTheme.fillColor?.resolve(inactiveStates) ?? radioTheme.fillColor?.resolve(inactiveStates);
?? _defaultFillColor.resolve(inactiveStates); final Color effectiveInactiveColor = inactiveColor ?? defaults.fillColor!.resolve(inactiveStates)!;
final Set<MaterialState> focusedStates = states..add(MaterialState.focused); final Set<MaterialState> focusedStates = states..add(MaterialState.focused);
final Color effectiveFocusOverlayColor = widget.overlayColor?.resolve(focusedStates) Color effectiveFocusOverlayColor = widget.overlayColor?.resolve(focusedStates)
?? widget.focusColor ?? widget.focusColor
?? radioTheme.overlayColor?.resolve(focusedStates) ?? radioTheme.overlayColor?.resolve(focusedStates)
?? themeData.focusColor; ?? defaults.overlayColor!.resolve(focusedStates)!;
final Set<MaterialState> hoveredStates = states..add(MaterialState.hovered); final Set<MaterialState> hoveredStates = states..add(MaterialState.hovered);
final Color effectiveHoverOverlayColor = widget.overlayColor?.resolve(hoveredStates) Color effectiveHoverOverlayColor = widget.overlayColor?.resolve(hoveredStates)
?? widget.hoverColor ?? widget.hoverColor
?? radioTheme.overlayColor?.resolve(hoveredStates) ?? radioTheme.overlayColor?.resolve(hoveredStates)
?? themeData.hoverColor; ?? defaults.overlayColor!.resolve(hoveredStates)!;
final Set<MaterialState> activePressedStates = activeStates..add(MaterialState.pressed); final Set<MaterialState> activePressedStates = activeStates..add(MaterialState.pressed);
final Color effectiveActivePressedOverlayColor = widget.overlayColor?.resolve(activePressedStates) final Color effectiveActivePressedOverlayColor = widget.overlayColor?.resolve(activePressedStates)
?? radioTheme.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 Set<MaterialState> inactivePressedStates = inactiveStates..add(MaterialState.pressed);
final Color effectiveInactivePressedOverlayColor = widget.overlayColor?.resolve(inactivePressedStates) final Color effectiveInactivePressedOverlayColor = widget.overlayColor?.resolve(inactivePressedStates)
?? radioTheme.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( return Semantics(
inMutuallyExclusiveGroup: true, inMutuallyExclusiveGroup: true,
...@@ -485,3 +485,134 @@ class _RadioPainter extends ToggleablePainter { ...@@ -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 { ...@@ -1266,6 +1266,7 @@ class ThemeData with Diagnosticable {
/// * Lists: [ListTile] /// * Lists: [ListTile]
/// * Navigation bar: [NavigationBar] (new, replacing [BottomNavigationBar]) /// * Navigation bar: [NavigationBar] (new, replacing [BottomNavigationBar])
/// * [Navigation rail](https://m3.material.io/components/navigation-rail): [NavigationRail] /// * [Navigation rail](https://m3.material.io/components/navigation-rail): [NavigationRail]
/// * Radio button: [Radio]
/// * Switch: [Switch] /// * Switch: [Switch]
/// * Top app bar: [AppBar] /// * Top app bar: [AppBar]
/// ///
......
...@@ -19,17 +19,22 @@ import '../rendering/mock_canvas.dart'; ...@@ -19,17 +19,22 @@ import '../rendering/mock_canvas.dart';
import '../widgets/semantics_tester.dart'; import '../widgets/semantics_tester.dart';
void main() { void main() {
final ThemeData theme = ThemeData();
testWidgets('Radio control test', (WidgetTester tester) async { testWidgets('Radio control test', (WidgetTester tester) async {
final Key key = UniqueKey(); final Key key = UniqueKey();
final List<int?> log = <int?>[]; final List<int?> log = <int?>[];
await tester.pumpWidget(Material( await tester.pumpWidget(Theme(
child: Center( data: theme,
child: Radio<int>( child: Material(
key: key, child: Center(
value: 1, child: Radio<int>(
groupValue: 2, key: key,
onChanged: log.add, value: 1,
groupValue: 2,
onChanged: log.add,
),
), ),
), ),
)); ));
...@@ -39,14 +44,17 @@ void main() { ...@@ -39,14 +44,17 @@ void main() {
expect(log, equals(<int>[1])); expect(log, equals(<int>[1]));
log.clear(); log.clear();
await tester.pumpWidget(Material( await tester.pumpWidget(Theme(
child: Center( data: theme,
child: Radio<int>( child: Material(
key: key, child: Center(
value: 1, child: Radio<int>(
groupValue: 1, key: key,
onChanged: log.add, value: 1,
activeColor: Colors.green[500], groupValue: 1,
onChanged: log.add,
activeColor: Colors.green[500],
),
), ),
), ),
)); ));
...@@ -55,13 +63,16 @@ void main() { ...@@ -55,13 +63,16 @@ void main() {
expect(log, isEmpty); expect(log, isEmpty);
await tester.pumpWidget(Material( await tester.pumpWidget(Theme(
child: Center( data: theme,
child: Radio<int>( child: Material(
key: key, child: Center(
value: 1, child: Radio<int>(
groupValue: 2, key: key,
onChanged: null, value: 1,
groupValue: 2,
onChanged: null,
),
), ),
), ),
)); ));
...@@ -75,14 +86,17 @@ void main() { ...@@ -75,14 +86,17 @@ void main() {
final Key key = UniqueKey(); final Key key = UniqueKey();
final List<int?> log = <int?>[]; final List<int?> log = <int?>[];
await tester.pumpWidget(Material( await tester.pumpWidget(Theme(
child: Center( data: theme,
child: Radio<int>( child: Material(
key: key, child: Center(
value: 1, child: Radio<int>(
groupValue: 2, key: key,
onChanged: log.add, value: 1,
toggleable: true, groupValue: 2,
onChanged: log.add,
toggleable: true,
),
), ),
), ),
)); ));
...@@ -92,14 +106,17 @@ void main() { ...@@ -92,14 +106,17 @@ void main() {
expect(log, equals(<int>[1])); expect(log, equals(<int>[1]));
log.clear(); log.clear();
await tester.pumpWidget(Material( await tester.pumpWidget(Theme(
child: Center( data: theme,
child: Radio<int>( child: Material(
key: key, child: Center(
value: 1, child: Radio<int>(
groupValue: 1, key: key,
onChanged: log.add, value: 1,
toggleable: true, groupValue: 1,
onChanged: log.add,
toggleable: true,
),
), ),
), ),
)); ));
...@@ -109,14 +126,17 @@ void main() { ...@@ -109,14 +126,17 @@ void main() {
expect(log, equals(<int?>[null])); expect(log, equals(<int?>[null]));
log.clear(); log.clear();
await tester.pumpWidget(Material( await tester.pumpWidget(Theme(
child: Center( data: theme,
child: Radio<int>( child: Material(
key: key, child: Center(
value: 1, child: Radio<int>(
groupValue: null, key: key,
onChanged: log.add, value: 1,
toggleable: true, groupValue: null,
onChanged: log.add,
toggleable: true,
),
), ),
), ),
)); ));
...@@ -130,7 +150,7 @@ void main() { ...@@ -130,7 +150,7 @@ void main() {
final Key key1 = UniqueKey(); final Key key1 = UniqueKey();
await tester.pumpWidget( await tester.pumpWidget(
Theme( Theme(
data: ThemeData(materialTapTargetSize: MaterialTapTargetSize.padded), data: theme.copyWith(materialTapTargetSize: MaterialTapTargetSize.padded),
child: Directionality( child: Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
child: Material( child: Material(
...@@ -152,7 +172,7 @@ void main() { ...@@ -152,7 +172,7 @@ void main() {
final Key key2 = UniqueKey(); final Key key2 = UniqueKey();
await tester.pumpWidget( await tester.pumpWidget(
Theme( Theme(
data: ThemeData(materialTapTargetSize: MaterialTapTargetSize.shrinkWrap), data: theme.copyWith(materialTapTargetSize: MaterialTapTargetSize.shrinkWrap),
child: Directionality( child: Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
child: Material( child: Material(
...@@ -176,11 +196,14 @@ void main() { ...@@ -176,11 +196,14 @@ void main() {
testWidgets('Radio semantics', (WidgetTester tester) async { testWidgets('Radio semantics', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester); final SemanticsTester semantics = SemanticsTester(tester);
await tester.pumpWidget(Material( await tester.pumpWidget(Theme(
child: Radio<int>( data: theme,
value: 1, child: Material(
groupValue: 2, child: Radio<int>(
onChanged: (int? i) { }, value: 1,
groupValue: 2,
onChanged: (int? i) { },
),
), ),
)); ));
...@@ -202,11 +225,14 @@ void main() { ...@@ -202,11 +225,14 @@ void main() {
], ],
), ignoreRect: true, ignoreTransform: true)); ), ignoreRect: true, ignoreTransform: true));
await tester.pumpWidget(Material( await tester.pumpWidget(Theme(
child: Radio<int>( data: theme,
value: 2, child: Material(
groupValue: 2, child: Radio<int>(
onChanged: (int? i) { }, value: 2,
groupValue: 2,
onChanged: (int? i) { },
),
), ),
)); ));
...@@ -229,11 +255,14 @@ void main() { ...@@ -229,11 +255,14 @@ void main() {
], ],
), ignoreRect: true, ignoreTransform: true)); ), ignoreRect: true, ignoreTransform: true));
await tester.pumpWidget(const Material( await tester.pumpWidget(Theme(
child: Radio<int>( data: theme,
value: 1, child: const Material(
groupValue: 2, child: Radio<int>(
onChanged: null, value: 1,
groupValue: 2,
onChanged: null,
),
), ),
)); ));
...@@ -267,11 +296,14 @@ void main() { ...@@ -267,11 +296,14 @@ void main() {
], ],
), ignoreRect: true, ignoreTransform: true)); ), ignoreRect: true, ignoreTransform: true));
await tester.pumpWidget(const Material( await tester.pumpWidget(Theme(
child: Radio<int>( data: theme,
value: 2, child: const Material(
groupValue: 2, child: Radio<int>(
onChanged: null, value: 2,
groupValue: 2,
onChanged: null,
),
), ),
)); ));
...@@ -301,14 +333,17 @@ void main() { ...@@ -301,14 +333,17 @@ void main() {
semanticEvent = message; semanticEvent = message;
}); });
await tester.pumpWidget(Material( await tester.pumpWidget(Theme(
child: Radio<int>( data: theme,
key: key, child: Material(
value: 1, child: Radio<int>(
groupValue: radioValue, key: key,
onChanged: (int? i) { value: 1,
radioValue = i; groupValue: radioValue,
}, onChanged: (int? i) {
radioValue = i;
},
),
), ),
)); ));
...@@ -327,12 +362,12 @@ void main() { ...@@ -327,12 +362,12 @@ void main() {
tester.binding.defaultBinaryMessenger.setMockDecodedMessageHandler<dynamic>(SystemChannels.accessibility, null); 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(); final Key painterKey = UniqueKey();
const Key radioKey = Key('radio'); const Key radioKey = Key('radio');
await tester.pumpWidget(MaterialApp( await tester.pumpWidget(MaterialApp(
theme: ThemeData(), theme: ThemeData(useMaterial3: false),
home: Scaffold( home: Scaffold(
body: RepaintBoundary( body: RepaintBoundary(
key: painterKey, key: painterKey,
...@@ -366,6 +401,7 @@ void main() { ...@@ -366,6 +401,7 @@ void main() {
const double splashRadius = 30; const double splashRadius = 30;
Widget buildApp() { Widget buildApp() {
return MaterialApp( return MaterialApp(
theme: theme,
home: Material( home: Material(
child: Center( child: Center(
child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) { child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
...@@ -404,6 +440,7 @@ void main() { ...@@ -404,6 +440,7 @@ void main() {
const Key radioKey = Key('radio'); const Key radioKey = Key('radio');
Widget buildApp({bool enabled = true}) { Widget buildApp({bool enabled = true}) {
return MaterialApp( return MaterialApp(
theme: theme,
home: Material( home: Material(
child: Center( child: Center(
child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) { child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
...@@ -453,13 +490,15 @@ void main() { ...@@ -453,13 +490,15 @@ void main() {
expect(focusNode.hasPrimaryFocus, isTrue); expect(focusNode.hasPrimaryFocus, isTrue);
expect( expect(
Material.of(tester.element(find.byKey(radioKey))), Material.of(tester.element(find.byKey(radioKey))),
paints theme.useMaterial3
..rect( ? (paints..rect()..circle(color: Colors.orange[500])..circle(color: theme.colorScheme.onSurface))
color: const Color(0xffffffff), : (paints
rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0), ..rect(
) color: const Color(0xffffffff),
..circle(color: Colors.orange[500]) rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0),
..circle(color: const Color(0x8a000000), style: PaintingStyle.stroke, strokeWidth: 2.0), )
..circle(color: Colors.orange[500])
..circle(color: const Color(0x8a000000), style: PaintingStyle.stroke, strokeWidth: 2.0)),
); );
// Check when the radio is selected, but disabled. // Check when the radio is selected, but disabled.
...@@ -485,6 +524,7 @@ void main() { ...@@ -485,6 +524,7 @@ void main() {
const Key radioKey = Key('radio'); const Key radioKey = Key('radio');
Widget buildApp({bool enabled = true}) { Widget buildApp({bool enabled = true}) {
return MaterialApp( return MaterialApp(
theme: theme,
home: Material( home: Material(
child: Center( child: Center(
child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) { child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
...@@ -534,14 +574,14 @@ void main() { ...@@ -534,14 +574,14 @@ void main() {
await tester.pump(); await tester.pump();
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect( expect(
Material.of(tester.element(find.byKey(radioKey))), Material.of(tester.element(find.byKey(radioKey))),
paints paints
..rect( ..rect(
color: const Color(0xffffffff), color: const Color(0xffffffff),
rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0), rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0),
) )
..circle(color: Colors.orange[500]) ..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. // Check when the radio is selected, but disabled.
...@@ -570,6 +610,7 @@ void main() { ...@@ -570,6 +610,7 @@ void main() {
final FocusNode focusNode2 = FocusNode(debugLabel: 'radio2'); final FocusNode focusNode2 = FocusNode(debugLabel: 'radio2');
Widget buildApp({bool enabled = true}) { Widget buildApp({bool enabled = true}) {
return MaterialApp( return MaterialApp(
theme: theme,
home: Material( home: Material(
child: Center( child: Center(
child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) { child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
...@@ -644,6 +685,7 @@ void main() { ...@@ -644,6 +685,7 @@ void main() {
Future<void> buildTest(VisualDensity visualDensity) async { Future<void> buildTest(VisualDensity visualDensity) async {
return tester.pumpWidget( return tester.pumpWidget(
MaterialApp( MaterialApp(
theme: theme,
home: Material( home: Material(
child: Center( child: Center(
child: Radio<int>( child: Radio<int>(
...@@ -682,6 +724,7 @@ void main() { ...@@ -682,6 +724,7 @@ void main() {
// Test Radio() constructor // Test Radio() constructor
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
theme: theme,
home: Scaffold( home: Scaffold(
body: Align( body: Align(
alignment: Alignment.topLeft, alignment: Alignment.topLeft,
...@@ -713,6 +756,7 @@ void main() { ...@@ -713,6 +756,7 @@ void main() {
// Test default cursor // Test default cursor
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
theme: theme,
home: Scaffold( home: Scaffold(
body: Align( body: Align(
alignment: Alignment.topLeft, alignment: Alignment.topLeft,
...@@ -735,8 +779,9 @@ void main() { ...@@ -735,8 +779,9 @@ void main() {
// Test default cursor when disabled // Test default cursor when disabled
await tester.pumpWidget( await tester.pumpWidget(
const MaterialApp( MaterialApp(
home: Scaffold( theme: theme,
home: const Scaffold(
body: Align( body: Align(
alignment: Alignment.topLeft, alignment: Alignment.topLeft,
child: Material( child: Material(
...@@ -783,6 +828,7 @@ void main() { ...@@ -783,6 +828,7 @@ void main() {
const Key radioKey = Key('radio'); const Key radioKey = Key('radio');
Widget buildApp({required bool enabled}) { Widget buildApp({required bool enabled}) {
return MaterialApp( return MaterialApp(
theme: theme,
home: Material( home: Material(
child: Center( child: Center(
child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) { child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
...@@ -890,6 +936,7 @@ void main() { ...@@ -890,6 +936,7 @@ void main() {
const Key radioKey = Key('radio'); const Key radioKey = Key('radio');
Widget buildApp() { Widget buildApp() {
return MaterialApp( return MaterialApp(
theme: theme,
home: Material( home: Material(
child: Center( child: Center(
child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) { child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
...@@ -922,13 +969,15 @@ void main() { ...@@ -922,13 +969,15 @@ void main() {
expect(focusNode.hasPrimaryFocus, isTrue); expect(focusNode.hasPrimaryFocus, isTrue);
expect( expect(
Material.of(tester.element(find.byKey(radioKey))), Material.of(tester.element(find.byKey(radioKey))),
paints theme.useMaterial3
..rect( ? (paints..rect()..circle(color: theme.colorScheme.primary.withOpacity(0.12))..circle(color: focusedFillColor))
color: const Color(0xffffffff), : (paints
rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0), ..rect(
) color: const Color(0xffffffff),
..circle(color: Colors.black12) rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0),
..circle(color: focusedFillColor), )
..circle(color: Colors.black12)
..circle(color: focusedFillColor)),
); );
// Start hovering // Start hovering
...@@ -944,7 +993,7 @@ void main() { ...@@ -944,7 +993,7 @@ void main() {
color: const Color(0xffffffff), color: const Color(0xffffffff),
rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0), 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), ..circle(color: hoveredFillColor),
); );
}); });
...@@ -988,6 +1037,7 @@ void main() { ...@@ -988,6 +1037,7 @@ void main() {
Widget buildRadio({bool active = false, bool focused = false, bool useOverlay = true}) { Widget buildRadio({bool active = false, bool focused = false, bool useOverlay = true}) {
return MaterialApp( return MaterialApp(
theme: theme,
home: Scaffold( home: Scaffold(
body: Radio<bool>( body: Radio<bool>(
focusNode: focusNode, focusNode: focusNode,
...@@ -1061,6 +1111,7 @@ void main() { ...@@ -1061,6 +1111,7 @@ void main() {
reason: 'Active pressed Radio should have overlay color: $activePressedOverlayColor', reason: 'Active pressed Radio should have overlay color: $activePressedOverlayColor',
); );
await tester.pumpWidget(Container());
await tester.pumpWidget(buildRadio(focused: true)); await tester.pumpWidget(buildRadio(focused: true));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
...@@ -1097,6 +1148,7 @@ void main() { ...@@ -1097,6 +1148,7 @@ void main() {
Widget buildRadio(bool show) { Widget buildRadio(bool show) {
return MaterialApp( return MaterialApp(
theme: theme,
home: Material( home: Material(
child: Center( child: Center(
child: show ? Radio<bool>(key: key, value: true, groupValue: false, onChanged: (_) { }) : Container(), child: show ? Radio<bool>(key: key, value: true, groupValue: false, onChanged: (_) { }) : Container(),
...@@ -1121,8 +1173,9 @@ void main() { ...@@ -1121,8 +1173,9 @@ void main() {
const String longPressTooltip = 'long press tooltip'; const String longPressTooltip = 'long press tooltip';
const String tapTooltip = 'tap tooltip'; const String tapTooltip = 'tap tooltip';
await tester.pumpWidget( await tester.pumpWidget(
const MaterialApp( MaterialApp(
home: Material( theme: theme,
home: const Material(
child: Tooltip( child: Tooltip(
message: longPressTooltip, message: longPressTooltip,
child: Radio<bool>(value: true, groupValue: false, onChanged: null), child: Radio<bool>(value: true, groupValue: false, onChanged: null),
...@@ -1149,8 +1202,9 @@ void main() { ...@@ -1149,8 +1202,9 @@ void main() {
// Tooltip shows up after tapping when set triggerMode to TooltipTriggerMode.tap. // Tooltip shows up after tapping when set triggerMode to TooltipTriggerMode.tap.
await tester.pumpWidget( await tester.pumpWidget(
const MaterialApp( MaterialApp(
home: Material( theme: theme,
home: const Material(
child: Tooltip( child: Tooltip(
triggerMode: TooltipTriggerMode.tap, triggerMode: TooltipTriggerMode.tap,
message: tapTooltip, message: tapTooltip,
...@@ -1167,4 +1221,154 @@ void main() { ...@@ -1167,4 +1221,154 @@ void main() {
await tester.pump(const Duration(milliseconds: 10)); await tester.pump(const Duration(milliseconds: 10));
expect(find.text(tapTooltip), findsOneWidget); 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