Update Radio button to material 3 (#111774)
This commit is contained in:
parent
ce7789e030
commit
06b87fba64
@ -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();
|
||||
|
90
dev/tools/gen_defaults/lib/radio_template.dart
Normal file
90
dev/tools/gen_defaults/lib/radio_template.dart
Normal file
@ -0,0 +1,90 @@
|
||||
// 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)
|
||||
?? widget.hoverColor
|
||||
?? radioTheme.overlayColor?.resolve(hoveredStates)
|
||||
?? themeData.hoverColor;
|
||||
Color effectiveHoverOverlayColor = widget.overlayColor?.resolve(hoveredStates)
|
||||
?? widget.hoverColor
|
||||
?? radioTheme.overlayColor?.resolve(hoveredStates)
|
||||
?? 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);
|
||||
?? radioTheme.overlayColor?.resolve(activePressedStates)
|
||||
?? 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);
|
||||
?? radioTheme.overlayColor?.resolve(inactivePressedStates)
|
||||
?? 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,17 +19,22 @@ 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(
|
||||
child: Center(
|
||||
child: Radio<int>(
|
||||
key: key,
|
||||
value: 1,
|
||||
groupValue: 2,
|
||||
onChanged: log.add,
|
||||
await tester.pumpWidget(Theme(
|
||||
data: theme,
|
||||
child: Material(
|
||||
child: Center(
|
||||
child: Radio<int>(
|
||||
key: key,
|
||||
value: 1,
|
||||
groupValue: 2,
|
||||
onChanged: log.add,
|
||||
),
|
||||
),
|
||||
),
|
||||
));
|
||||
@ -39,14 +44,17 @@ void main() {
|
||||
expect(log, equals(<int>[1]));
|
||||
log.clear();
|
||||
|
||||
await tester.pumpWidget(Material(
|
||||
child: Center(
|
||||
child: Radio<int>(
|
||||
key: key,
|
||||
value: 1,
|
||||
groupValue: 1,
|
||||
onChanged: log.add,
|
||||
activeColor: Colors.green[500],
|
||||
await tester.pumpWidget(Theme(
|
||||
data: theme,
|
||||
child: Material(
|
||||
child: Center(
|
||||
child: Radio<int>(
|
||||
key: key,
|
||||
value: 1,
|
||||
groupValue: 1,
|
||||
onChanged: log.add,
|
||||
activeColor: Colors.green[500],
|
||||
),
|
||||
),
|
||||
),
|
||||
));
|
||||
@ -55,13 +63,16 @@ void main() {
|
||||
|
||||
expect(log, isEmpty);
|
||||
|
||||
await tester.pumpWidget(Material(
|
||||
child: Center(
|
||||
child: Radio<int>(
|
||||
key: key,
|
||||
value: 1,
|
||||
groupValue: 2,
|
||||
onChanged: null,
|
||||
await tester.pumpWidget(Theme(
|
||||
data: theme,
|
||||
child: Material(
|
||||
child: Center(
|
||||
child: Radio<int>(
|
||||
key: key,
|
||||
value: 1,
|
||||
groupValue: 2,
|
||||
onChanged: null,
|
||||
),
|
||||
),
|
||||
),
|
||||
));
|
||||
@ -75,14 +86,17 @@ void main() {
|
||||
final Key key = UniqueKey();
|
||||
final List<int?> log = <int?>[];
|
||||
|
||||
await tester.pumpWidget(Material(
|
||||
child: Center(
|
||||
child: Radio<int>(
|
||||
key: key,
|
||||
value: 1,
|
||||
groupValue: 2,
|
||||
onChanged: log.add,
|
||||
toggleable: true,
|
||||
await tester.pumpWidget(Theme(
|
||||
data: theme,
|
||||
child: Material(
|
||||
child: Center(
|
||||
child: Radio<int>(
|
||||
key: key,
|
||||
value: 1,
|
||||
groupValue: 2,
|
||||
onChanged: log.add,
|
||||
toggleable: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
));
|
||||
@ -92,14 +106,17 @@ void main() {
|
||||
expect(log, equals(<int>[1]));
|
||||
log.clear();
|
||||
|
||||
await tester.pumpWidget(Material(
|
||||
child: Center(
|
||||
child: Radio<int>(
|
||||
key: key,
|
||||
value: 1,
|
||||
groupValue: 1,
|
||||
onChanged: log.add,
|
||||
toggleable: true,
|
||||
await tester.pumpWidget(Theme(
|
||||
data: theme,
|
||||
child: Material(
|
||||
child: Center(
|
||||
child: Radio<int>(
|
||||
key: key,
|
||||
value: 1,
|
||||
groupValue: 1,
|
||||
onChanged: log.add,
|
||||
toggleable: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
));
|
||||
@ -109,14 +126,17 @@ void main() {
|
||||
expect(log, equals(<int?>[null]));
|
||||
log.clear();
|
||||
|
||||
await tester.pumpWidget(Material(
|
||||
child: Center(
|
||||
child: Radio<int>(
|
||||
key: key,
|
||||
value: 1,
|
||||
groupValue: null,
|
||||
onChanged: log.add,
|
||||
toggleable: true,
|
||||
await tester.pumpWidget(Theme(
|
||||
data: theme,
|
||||
child: Material(
|
||||
child: Center(
|
||||
child: Radio<int>(
|
||||
key: key,
|
||||
value: 1,
|
||||
groupValue: null,
|
||||
onChanged: log.add,
|
||||
toggleable: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
));
|
||||
@ -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,11 +196,14 @@ void main() {
|
||||
testWidgets('Radio semantics', (WidgetTester tester) async {
|
||||
final SemanticsTester semantics = SemanticsTester(tester);
|
||||
|
||||
await tester.pumpWidget(Material(
|
||||
child: Radio<int>(
|
||||
value: 1,
|
||||
groupValue: 2,
|
||||
onChanged: (int? i) { },
|
||||
await tester.pumpWidget(Theme(
|
||||
data: theme,
|
||||
child: Material(
|
||||
child: Radio<int>(
|
||||
value: 1,
|
||||
groupValue: 2,
|
||||
onChanged: (int? i) { },
|
||||
),
|
||||
),
|
||||
));
|
||||
|
||||
@ -202,11 +225,14 @@ void main() {
|
||||
],
|
||||
), ignoreRect: true, ignoreTransform: true));
|
||||
|
||||
await tester.pumpWidget(Material(
|
||||
child: Radio<int>(
|
||||
value: 2,
|
||||
groupValue: 2,
|
||||
onChanged: (int? i) { },
|
||||
await tester.pumpWidget(Theme(
|
||||
data: theme,
|
||||
child: Material(
|
||||
child: Radio<int>(
|
||||
value: 2,
|
||||
groupValue: 2,
|
||||
onChanged: (int? i) { },
|
||||
),
|
||||
),
|
||||
));
|
||||
|
||||
@ -229,11 +255,14 @@ void main() {
|
||||
],
|
||||
), ignoreRect: true, ignoreTransform: true));
|
||||
|
||||
await tester.pumpWidget(const Material(
|
||||
child: Radio<int>(
|
||||
value: 1,
|
||||
groupValue: 2,
|
||||
onChanged: null,
|
||||
await tester.pumpWidget(Theme(
|
||||
data: theme,
|
||||
child: const Material(
|
||||
child: Radio<int>(
|
||||
value: 1,
|
||||
groupValue: 2,
|
||||
onChanged: null,
|
||||
),
|
||||
),
|
||||
));
|
||||
|
||||
@ -267,11 +296,14 @@ void main() {
|
||||
],
|
||||
), ignoreRect: true, ignoreTransform: true));
|
||||
|
||||
await tester.pumpWidget(const Material(
|
||||
child: Radio<int>(
|
||||
value: 2,
|
||||
groupValue: 2,
|
||||
onChanged: null,
|
||||
await tester.pumpWidget(Theme(
|
||||
data: theme,
|
||||
child: const Material(
|
||||
child: Radio<int>(
|
||||
value: 2,
|
||||
groupValue: 2,
|
||||
onChanged: null,
|
||||
),
|
||||
),
|
||||
));
|
||||
|
||||
@ -301,14 +333,17 @@ void main() {
|
||||
semanticEvent = message;
|
||||
});
|
||||
|
||||
await tester.pumpWidget(Material(
|
||||
child: Radio<int>(
|
||||
key: key,
|
||||
value: 1,
|
||||
groupValue: radioValue,
|
||||
onChanged: (int? i) {
|
||||
radioValue = i;
|
||||
},
|
||||
await tester.pumpWidget(Theme(
|
||||
data: theme,
|
||||
child: Material(
|
||||
child: Radio<int>(
|
||||
key: key,
|
||||
value: 1,
|
||||
groupValue: radioValue,
|
||||
onChanged: (int? i) {
|
||||
radioValue = i;
|
||||
},
|
||||
),
|
||||
),
|
||||
));
|
||||
|
||||
@ -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
|
||||
..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),
|
||||
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)),
|
||||
);
|
||||
|
||||
// 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) {
|
||||
@ -534,14 +574,14 @@ void main() {
|
||||
await tester.pump();
|
||||
await tester.pumpAndSettle();
|
||||
expect(
|
||||
Material.of(tester.element(find.byKey(radioKey))),
|
||||
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),
|
||||
Material.of(tester.element(find.byKey(radioKey))),
|
||||
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: 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
|
||||
..rect(
|
||||
color: const Color(0xffffffff),
|
||||
rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0),
|
||||
)
|
||||
..circle(color: Colors.black12)
|
||||
..circle(color: focusedFillColor),
|
||||
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)),
|
||||
);
|
||||
|
||||
// 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))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user