Unverified Commit 5e506aeb authored by Qun Cheng's avatar Qun Cheng Committed by GitHub

Add missing parameters to `SwitchListTile` (#120115)

* Add missing parameters to SwitchListTile

* Update SwitchListTile doc

* Update doc for existing APIs

* Address comment

* Fix typo

---------
Co-authored-by: 's avatarQun Cheng <quncheng@google.com>
parent 99961267
......@@ -197,7 +197,9 @@ class Switch extends StatelessWidget {
/// ```
final ValueChanged<bool>? onChanged;
/// {@template flutter.material.switch.activeColor}
/// The color to use when this switch is on.
/// {@endtemplate}
///
/// Defaults to [ColorScheme.secondary].
///
......@@ -205,7 +207,9 @@ class Switch extends StatelessWidget {
/// state, it will be used instead of this color.
final Color? activeColor;
/// {@template flutter.material.switch.activeTrackColor}
/// The color to use on the track when this switch is on.
/// {@endtemplate}
///
/// Defaults to [ColorScheme.secondary] with the opacity set at 50%.
///
......@@ -215,7 +219,9 @@ class Switch extends StatelessWidget {
/// state, it will be used instead of this color.
final Color? activeTrackColor;
/// {@template flutter.material.switch.inactiveThumbColor}
/// The color to use on the thumb when this switch is off.
/// {@endtemplate}
///
/// Defaults to the colors described in the Material design specification.
///
......@@ -225,7 +231,9 @@ class Switch extends StatelessWidget {
/// used instead of this color.
final Color? inactiveThumbColor;
/// {@template flutter.material.switch.inactiveTrackColor}
/// The color to use on the track when this switch is off.
/// {@endtemplate}
///
/// Defaults to the colors described in the Material design specification.
///
......@@ -235,22 +243,30 @@ class Switch extends StatelessWidget {
/// used instead of this color.
final Color? inactiveTrackColor;
/// {@template flutter.material.switch.activeThumbImage}
/// An image to use on the thumb of this switch when the switch is on.
/// {@endtemplate}
///
/// Ignored if this switch is created with [Switch.adaptive].
final ImageProvider? activeThumbImage;
/// {@template flutter.material.switch.onActiveThumbImageError}
/// An optional error callback for errors emitted when loading
/// [activeThumbImage].
/// {@endtemplate}
final ImageErrorListener? onActiveThumbImageError;
/// {@template flutter.material.switch.inactiveThumbImage}
/// An image to use on the thumb of this switch when the switch is off.
/// {@endtemplate}
///
/// Ignored if this switch is created with [Switch.adaptive].
final ImageProvider? inactiveThumbImage;
/// {@template flutter.material.switch.onInactiveThumbImageError}
/// An optional error callback for errors emitted when loading
/// [inactiveThumbImage].
/// {@endtemplate}
final ImageErrorListener? onInactiveThumbImageError;
/// {@template flutter.material.switch.thumbColor}
......
......@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/gestures.dart';
import 'package:flutter/widgets.dart';
import 'list_tile.dart';
......@@ -163,13 +164,27 @@ class SwitchListTile extends StatelessWidget {
super.key,
required this.value,
required this.onChanged,
this.tileColor,
this.activeColor,
this.activeTrackColor,
this.inactiveThumbColor,
this.inactiveTrackColor,
this.activeThumbImage,
this.onActiveThumbImageError,
this.inactiveThumbImage,
this.onInactiveThumbImageError,
this.thumbColor,
this.trackColor,
this.trackOutlineColor,
this.thumbIcon,
this.materialTapTargetSize,
this.dragStartBehavior = DragStartBehavior.start,
this.mouseCursor,
this.overlayColor,
this.splashRadius,
this.focusNode,
this.onFocusChange,
this.autofocus = false,
this.tileColor,
this.title,
this.subtitle,
this.isThreeLine = false,
......@@ -177,17 +192,16 @@ class SwitchListTile extends StatelessWidget {
this.contentPadding,
this.secondary,
this.selected = false,
this.autofocus = false,
this.controlAffinity = ListTileControlAffinity.platform,
this.shape,
this.selectedTileColor,
this.visualDensity,
this.focusNode,
this.onFocusChange,
this.enableFeedback,
this.hoverColor,
this.trackOutlineColor,
}) : _switchListTileType = _SwitchListTileType.material,
applyCupertinoTheme = false,
assert(activeThumbImage != null || onActiveThumbImageError == null),
assert(inactiveThumbImage != null || onInactiveThumbImageError == null),
assert(!isThreeLine || subtitle != null);
/// Creates a Material [ListTile] with an adaptive [Switch], following
......@@ -206,13 +220,28 @@ class SwitchListTile extends StatelessWidget {
super.key,
required this.value,
required this.onChanged,
this.tileColor,
this.activeColor,
this.activeTrackColor,
this.inactiveThumbColor,
this.inactiveTrackColor,
this.activeThumbImage,
this.onActiveThumbImageError,
this.inactiveThumbImage,
this.onInactiveThumbImageError,
this.thumbColor,
this.trackColor,
this.trackOutlineColor,
this.thumbIcon,
this.materialTapTargetSize,
this.dragStartBehavior = DragStartBehavior.start,
this.mouseCursor,
this.overlayColor,
this.splashRadius,
this.focusNode,
this.onFocusChange,
this.autofocus = false,
this.applyCupertinoTheme,
this.tileColor,
this.title,
this.subtitle,
this.isThreeLine = false,
......@@ -220,18 +249,16 @@ class SwitchListTile extends StatelessWidget {
this.contentPadding,
this.secondary,
this.selected = false,
this.autofocus = false,
this.controlAffinity = ListTileControlAffinity.platform,
this.shape,
this.selectedTileColor,
this.visualDensity,
this.focusNode,
this.onFocusChange,
this.enableFeedback,
this.hoverColor,
this.trackOutlineColor,
}) : _switchListTileType = _SwitchListTileType.adaptive,
assert(!isThreeLine || subtitle != null);
assert(!isThreeLine || subtitle != null),
assert(activeThumbImage != null || onActiveThumbImageError == null),
assert(inactiveThumbImage != null || onInactiveThumbImageError == null);
/// Whether this switch is checked.
///
......@@ -265,43 +292,146 @@ class SwitchListTile extends StatelessWidget {
/// {@end-tool}
final ValueChanged<bool>? onChanged;
/// The color to use when this switch is on.
/// {@macro flutter.material.switch.activeColor}
///
/// Defaults to [ColorScheme.secondary] of the current [Theme].
final Color? activeColor;
/// The color to use on the track when this switch is on.
/// {@macro flutter.material.switch.activeTrackColor}
///
/// Defaults to [ThemeData.toggleableActiveColor] with the opacity set at 50%.
///
/// Ignored if created with [SwitchListTile.adaptive].
final Color? activeTrackColor;
/// The color to use on the thumb when this switch is off.
/// {@macro flutter.material.switch.inactiveThumbColor}
///
/// Defaults to the colors described in the Material design specification.
///
/// Ignored if created with [SwitchListTile.adaptive].
final Color? inactiveThumbColor;
/// The color to use on the track when this switch is off.
/// {@macro flutter.material.switch.inactiveTrackColor}
///
/// Defaults to the colors described in the Material design specification.
///
/// Ignored if created with [SwitchListTile.adaptive].
final Color? inactiveTrackColor;
/// {@macro flutter.material.ListTile.tileColor}
final Color? tileColor;
/// An image to use on the thumb of this switch when the switch is on.
/// {@macro flutter.material.switch.activeThumbImage}
final ImageProvider? activeThumbImage;
/// An image to use on the thumb of this switch when the switch is off.
/// {@macro flutter.material.switch.onActiveThumbImageError}
final ImageErrorListener? onActiveThumbImageError;
/// {@macro flutter.material.switch.inactiveThumbImage}
///
/// Ignored if created with [SwitchListTile.adaptive].
final ImageProvider? inactiveThumbImage;
/// {@macro flutter.material.switch.onInactiveThumbImageError}
final ImageErrorListener? onInactiveThumbImageError;
/// The color of this switch's thumb.
///
/// Resolved in the following states:
/// * [MaterialState.selected].
/// * [MaterialState.hovered].
/// * [MaterialState.disabled].
///
/// If null, then the value of [activeColor] is used in the selected state
/// and [inactiveThumbColor] in the default state. If that is also null, then
/// the value of [SwitchThemeData.thumbColor] is used. If that is also null,
/// The default value is used.
final MaterialStateProperty<Color?>? thumbColor;
/// The color of this switch's track.
///
/// Resolved in the following states:
/// * [MaterialState.selected].
/// * [MaterialState.hovered].
/// * [MaterialState.disabled].
///
/// If null, then the value of [activeTrackColor] is used in the selected
/// state and [inactiveTrackColor] in the default state. If that is also null,
/// then the value of [SwitchThemeData.trackColor] is used. If that is also
/// null, then the default value is used.
final MaterialStateProperty<Color?>? trackColor;
/// {@macro flutter.material.switch.trackOutlineColor}
///
/// The [ListTile] will be focused when this [SwitchListTile] requests focus,
/// so the focused outline color of the switch will be ignored.
///
/// In Material 3, the outline color defaults to transparent in the selected
/// state and [ColorScheme.outline] in the unselected state. In Material 2,
/// the [Switch] track has no outline.
final MaterialStateProperty<Color?>? trackOutlineColor;
/// The icon to use on the thumb of this switch
///
/// Resolved in the following states:
/// * [MaterialState.selected].
/// * [MaterialState.hovered].
/// * [MaterialState.disabled].
///
/// If null, then the value of [SwitchThemeData.thumbIcon] is used. If this is
/// also null, then the [Switch] does not have any icons on the thumb.
final MaterialStateProperty<Icon?>? thumbIcon;
/// {@macro flutter.material.switch.materialTapTargetSize}
///
/// defaults to [MaterialTapTargetSize.shrinkWrap].
final MaterialTapTargetSize? materialTapTargetSize;
/// {@macro flutter.cupertino.CupertinoSwitch.dragStartBehavior}
final DragStartBehavior dragStartBehavior;
/// The cursor for a mouse pointer when it enters or is hovering over the
/// widget.
///
/// If [mouseCursor] is a [MaterialStateProperty<MouseCursor>],
/// [MaterialStateProperty.resolve] is used for the following [MaterialState]s:
///
/// * [MaterialState.selected].
/// * [MaterialState.hovered].
/// * [MaterialState.disabled].
///
/// If null, then the value of [SwitchThemeData.mouseCursor] is used. If that
/// is also null, then [MaterialStateMouseCursor.clickable] is used.
final MouseCursor? mouseCursor;
/// The color for the switch's [Material].
///
/// Resolves in the following states:
/// * [MaterialState.pressed].
/// * [MaterialState.selected].
/// * [MaterialState.hovered].
///
/// If null, then the value of [activeColor] with alpha [kRadialReactionAlpha]
/// and [hoverColor] is used in the pressed and hovered state. If that is also
/// null, the value of [SwitchThemeData.overlayColor] is used. If that is
/// also null, then the default value is used in the pressed and hovered state.
final MaterialStateProperty<Color?>? overlayColor;
/// {@macro flutter.material.switch.splashRadius}
///
/// If null, then the value of [SwitchThemeData.splashRadius] is used. If that
/// is also null, then [kRadialReactionRadius] is used.
final double? splashRadius;
/// {@macro flutter.widgets.Focus.focusNode}
final FocusNode? focusNode;
/// {@macro flutter.material.inkwell.onFocusChange}
final ValueChanged<bool>? onFocusChange;
/// {@macro flutter.widgets.Focus.autofocus}
final bool autofocus;
/// {@macro flutter.material.ListTile.tileColor}
final Color? tileColor;
/// The primary content of the list tile.
///
/// Typically a [Text] widget.
......@@ -346,9 +476,6 @@ class SwitchListTile extends StatelessWidget {
/// Normally, this property is left to its default value, false.
final bool selected;
/// {@macro flutter.widgets.Focus.autofocus}
final bool autofocus;
/// If adaptive, creates the switch with [Switch.adaptive].
final _SwitchListTileType _switchListTileType;
......@@ -368,12 +495,6 @@ class SwitchListTile extends StatelessWidget {
/// {@macro flutter.material.themedata.visualDensity}
final VisualDensity? visualDensity;
/// {@macro flutter.widgets.Focus.focusNode}
final FocusNode? focusNode;
/// {@macro flutter.material.inkwell.onFocusChange}
final ValueChanged<bool>? onFocusChange;
/// {@macro flutter.material.ListTile.enableFeedback}
///
/// See also:
......@@ -384,15 +505,8 @@ class SwitchListTile extends StatelessWidget {
/// The color for the tile's [Material] when a pointer is hovering over it.
final Color? hoverColor;
/// {@macro flutter.material.switch.trackOutlineColor}
///
/// The [ListTile] will be focused when this [SwitchListTile] requests focus,
/// so the focused outline color of the switch will be ignored.
///
/// In Material 3, the outline color defaults to transparent in the selected
/// state and [ColorScheme.outline] in the unselected state. In Material 2,
/// the [Switch] track has no outline.
final MaterialStateProperty<Color?>? trackOutlineColor;
/// {@macro flutter.cupertino.CupertinoSwitch.applyTheme}
final bool? applyCupertinoTheme;
@override
Widget build(BuildContext context) {
......@@ -405,13 +519,23 @@ class SwitchListTile extends StatelessWidget {
activeColor: activeColor,
activeThumbImage: activeThumbImage,
inactiveThumbImage: inactiveThumbImage,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
materialTapTargetSize: materialTapTargetSize ?? MaterialTapTargetSize.shrinkWrap,
activeTrackColor: activeTrackColor,
inactiveTrackColor: inactiveTrackColor,
inactiveThumbColor: inactiveThumbColor,
autofocus: autofocus,
onFocusChange: onFocusChange,
onActiveThumbImageError: onActiveThumbImageError,
onInactiveThumbImageError: onInactiveThumbImageError,
thumbColor: thumbColor,
trackColor: trackColor,
trackOutlineColor: trackOutlineColor,
thumbIcon: thumbIcon,
applyCupertinoTheme: applyCupertinoTheme,
dragStartBehavior: dragStartBehavior,
mouseCursor: mouseCursor,
splashRadius: splashRadius,
overlayColor: overlayColor,
);
break;
......@@ -422,13 +546,22 @@ class SwitchListTile extends StatelessWidget {
activeColor: activeColor,
activeThumbImage: activeThumbImage,
inactiveThumbImage: inactiveThumbImage,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
materialTapTargetSize: materialTapTargetSize ?? MaterialTapTargetSize.shrinkWrap,
activeTrackColor: activeTrackColor,
inactiveTrackColor: inactiveTrackColor,
inactiveThumbColor: inactiveThumbColor,
autofocus: autofocus,
onFocusChange: onFocusChange,
onActiveThumbImageError: onActiveThumbImageError,
onInactiveThumbImageError: onInactiveThumbImageError,
thumbColor: thumbColor,
trackColor: trackColor,
trackOutlineColor: trackOutlineColor,
thumbIcon: thumbIcon,
dragStartBehavior: dragStartBehavior,
mouseCursor: mouseCursor,
splashRadius: splashRadius,
overlayColor: overlayColor,
);
}
......
......@@ -603,19 +603,660 @@ void main() {
);
// Start hovering
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
final TestGesture gesture = await tester.createGesture(
kind: PointerDeviceKind.mouse);
await gesture.moveTo(tester.getCenter(find.byKey(key)));
await tester.pump();
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byKey(key))),
paints..rect()..rect(
color: Colors.orange[500],
rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0),
)
);
});
testWidgets('SwitchListTile respects thumbColor in active/enabled states', (WidgetTester tester) async {
const Color activeEnabledThumbColor = Color(0xFF000001);
const Color activeDisabledThumbColor = Color(0xFF000002);
const Color inactiveEnabledThumbColor = Color(0xFF000003);
const Color inactiveDisabledThumbColor = Color(0xFF000004);
Color getThumbColor(Set<MaterialState> states) {
if (states.contains(MaterialState.disabled)) {
if (states.contains(MaterialState.selected)) {
return activeDisabledThumbColor;
}
return inactiveDisabledThumbColor;
}
if (states.contains(MaterialState.selected)) {
return activeEnabledThumbColor;
}
return inactiveEnabledThumbColor;
}
final MaterialStateProperty<Color> thumbColor = MaterialStateColor.resolveWith(getThumbColor);
Widget buildSwitchListTile({required bool enabled, required bool selected}) {
return wrap(
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return SwitchListTile(
value: selected,
thumbColor: thumbColor,
onChanged: enabled ? (_) { } : null,
);
}),
);
}
await tester.pumpWidget(buildSwitchListTile(enabled: false, selected: false));
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect()..rrect()..rrect()..rrect()
..rrect(color: inactiveDisabledThumbColor),
);
await tester.pumpWidget(buildSwitchListTile(enabled: false, selected: true));
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rect()
..rect(
color: Colors.orange[500],
rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0),
)
..rrect()..rrect()..rrect()..rrect()
..rrect(color: activeDisabledThumbColor),
);
await tester.pumpWidget(buildSwitchListTile(enabled: true, selected: false));
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect()..rrect()..rrect()..rrect()
..rrect(color: inactiveEnabledThumbColor),
);
await tester.pumpWidget(buildSwitchListTile(enabled: true, selected: true));
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect()..rrect()..rrect()..rrect()
..rrect(color: activeEnabledThumbColor),
);
});
testWidgets('SwitchListTile respects thumbColor in hovered/pressed states', (WidgetTester tester) async {
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
const Color hoveredThumbColor = Color(0xFF4caf50);
const Color pressedThumbColor = Color(0xFFF44336);
Color getThumbColor(Set<MaterialState> states) {
if (states.contains(MaterialState.pressed)) {
return pressedThumbColor;
}
if (states.contains(MaterialState.hovered)) {
return hoveredThumbColor;
}
return Colors.transparent;
}
final MaterialStateProperty<Color> thumbColor = MaterialStateColor.resolveWith(getThumbColor);
Widget buildSwitchListTile() {
return MaterialApp(
theme: ThemeData(),
home: wrap(
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return SwitchListTile(
value: false,
thumbColor: thumbColor,
onChanged: (_) { },
);
}),
),
);
}
await tester.pumpWidget(buildSwitchListTile());
await tester.pumpAndSettle();
// Start hovering
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
await gesture.moveTo(tester.getCenter(find.byType(Switch)));
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect()..rrect()..rrect()..rrect()
..rrect(color: hoveredThumbColor),
);
// On pressed state
await tester.press(find.byType(Switch));
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect()..rrect()..rrect()..rrect()
..rrect(color: pressedThumbColor),
);
});
testWidgets('SwitchListTile respects trackColor in active/enabled states', (WidgetTester tester) async {
const Color activeEnabledTrackColor = Color(0xFF000001);
const Color activeDisabledTrackColor = Color(0xFF000002);
const Color inactiveEnabledTrackColor = Color(0xFF000003);
const Color inactiveDisabledTrackColor = Color(0xFF000004);
Color getTrackColor(Set<MaterialState> states) {
if (states.contains(MaterialState.disabled)) {
if (states.contains(MaterialState.selected)) {
return activeDisabledTrackColor;
}
return inactiveDisabledTrackColor;
}
if (states.contains(MaterialState.selected)) {
return activeEnabledTrackColor;
}
return inactiveEnabledTrackColor;
}
final MaterialStateProperty<Color> trackColor = MaterialStateColor.resolveWith(getTrackColor);
Widget buildSwitchListTile({required bool enabled, required bool selected}) {
return wrap(
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return SwitchListTile(
value: selected,
trackColor: trackColor,
onChanged: enabled ? (_) { } : null,
);
}),
);
}
await tester.pumpWidget(buildSwitchListTile(enabled: false, selected: false));
expect(
Material.of(tester.element(find.byType(Switch))),
paints..rrect(color: inactiveDisabledTrackColor),
);
await tester.pumpWidget(buildSwitchListTile(enabled: false, selected: true));
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Switch))),
paints..rrect(color: activeDisabledTrackColor),
);
await tester.pumpWidget(buildSwitchListTile(enabled: true, selected: false));
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Switch))),
paints..rrect(color: inactiveEnabledTrackColor),
);
await tester.pumpWidget(buildSwitchListTile(enabled: true, selected: true));
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Switch))),
paints..rrect(color: activeEnabledTrackColor),
);
});
testWidgets('SwitchListTile respects trackColor in hovered states', (WidgetTester tester) async {
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
const Color hoveredTrackColor = Color(0xFF4caf50);
Color getTrackColor(Set<MaterialState> states) {
if (states.contains(MaterialState.hovered)) {
return hoveredTrackColor;
}
return Colors.transparent;
}
final MaterialStateProperty<Color> trackColor = MaterialStateColor.resolveWith(getTrackColor);
Widget buildSwitchListTile() {
return wrap(
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return SwitchListTile(
value: false,
trackColor: trackColor,
onChanged: (_) { },
);
}),
);
}
await tester.pumpWidget(buildSwitchListTile());
await tester.pumpAndSettle();
// Start hovering
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
await gesture.moveTo(tester.getCenter(find.byType(Switch)));
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Switch))),
paints..rrect(color: hoveredTrackColor),
);
});
testWidgets('SwitchListTile respects thumbIcon - M3', (WidgetTester tester) async {
const Icon activeIcon = Icon(Icons.check);
const Icon inactiveIcon = Icon(Icons.close);
MaterialStateProperty<Icon?> thumbIcon(Icon? activeIcon, Icon? inactiveIcon) {
return MaterialStateProperty.resolveWith<Icon?>((Set<MaterialState> states) {
if (states.contains(MaterialState.selected)) {
return activeIcon;
}
return inactiveIcon;
});
}
Widget buildSwitchListTile({required bool enabled, required bool active, Icon? activeIcon, Icon? inactiveIcon}) {
return MaterialApp(
theme: ThemeData(useMaterial3: true),
home: wrap(
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return SwitchListTile(
thumbIcon: thumbIcon(activeIcon, inactiveIcon),
value: active,
onChanged: enabled ? (_) {} : null,
);
}),
),
);
}
// active icon shows when switch is on.
await tester.pumpWidget(buildSwitchListTile(enabled: true, active: true, activeIcon: activeIcon));
await tester.pumpAndSettle();
final Switch switchWidget0 = tester.widget<Switch>(find.byType(Switch));
expect(switchWidget0.thumbIcon?.resolve(<MaterialState>{MaterialState.selected}), activeIcon);
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect()..rrect()
..paragraph(offset: const Offset(32.0, 12.0)),
);
// inactive icon shows when switch is off.
await tester.pumpWidget(buildSwitchListTile(enabled: true, active: false, inactiveIcon: inactiveIcon));
await tester.pumpAndSettle();
final Switch switchWidget1 = tester.widget<Switch>(find.byType(Switch));
expect(switchWidget1.thumbIcon?.resolve(<MaterialState>{}), inactiveIcon);
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect()..rrect()
..rrect()
..paragraph(offset: const Offset(12.0, 12.0)),
);
// active icon doesn't show when switch is off.
await tester.pumpWidget(buildSwitchListTile(enabled: true, active: false, activeIcon: activeIcon));
await tester.pumpAndSettle();
final Switch switchWidget2 = tester.widget<Switch>(find.byType(Switch));
expect(switchWidget2.thumbIcon?.resolve(<MaterialState>{MaterialState.selected}), activeIcon);
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect()..rrect()..rrect()
);
// inactive icon doesn't show when switch is on.
await tester.pumpWidget(buildSwitchListTile(enabled: true, active: true, inactiveIcon: inactiveIcon));
await tester.pumpAndSettle();
final Switch switchWidget3 = tester.widget<Switch>(find.byType(Switch));
expect(switchWidget3.thumbIcon?.resolve(<MaterialState>{}), inactiveIcon);
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect()..rrect()..restore(),
);
// without icon
await tester.pumpWidget(buildSwitchListTile(enabled: true, active: false));
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect()..rrect()..rrect()..restore(),
);
});
testWidgets('SwitchListTile respects materialTapTargetSize', (WidgetTester tester) async {
Widget buildSwitchListTile(MaterialTapTargetSize materialTapTargetSize) {
return wrap(
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return SwitchListTile(
materialTapTargetSize: materialTapTargetSize,
value: false,
onChanged: (_) {},
);
}),
);
}
await tester.pumpWidget(buildSwitchListTile(MaterialTapTargetSize.padded));
final Switch switchWidget = tester.widget<Switch>(find.byType(Switch));
expect(switchWidget.materialTapTargetSize, MaterialTapTargetSize.padded);
expect(tester.getSize(find.byType(Switch)), const Size(59.0, 48.0));
await tester.pumpWidget(buildSwitchListTile(MaterialTapTargetSize.shrinkWrap));
final Switch switchWidget1 = tester.widget<Switch>(find.byType(Switch));
expect(switchWidget1.materialTapTargetSize, MaterialTapTargetSize.shrinkWrap);
expect(tester.getSize(find.byType(Switch)), const Size(59.0, 40.0));
});
testWidgets('SwitchListTile.adaptive respects applyCupertinoTheme', (WidgetTester tester) async {
final ThemeData theme = ThemeData();
Widget buildSwitchListTile(bool applyCupertinoTheme, TargetPlatform platform) {
return MaterialApp(
theme: theme.copyWith(platform: platform),
home: wrap(
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return SwitchListTile.adaptive(
applyCupertinoTheme: applyCupertinoTheme,
value: true,
onChanged: (_) {},
);
}),
),
);
}
for (final TargetPlatform platform in <TargetPlatform>[ TargetPlatform.iOS, TargetPlatform.macOS ]) {
await tester.pumpWidget(buildSwitchListTile(true, platform));
await tester.pumpAndSettle();
expect(find.byType(CupertinoSwitch), findsOneWidget);
expect(
Material.of(tester.element(find.byType(Switch))),
paints..rrect(color: theme.useMaterial3 ? const Color(0xFF6750A4) : const Color(0xFF2196F3)),
);
await tester.pumpWidget(buildSwitchListTile(false, platform));
await tester.pumpAndSettle();
expect(find.byType(CupertinoSwitch), findsOneWidget);
expect(
Material.of(tester.element(find.byType(Switch))),
paints..rrect(color: const Color(0xFF34C759)),
);
}
});
testWidgets('SwitchListTile respects materialTapTargetSize', (WidgetTester tester) async {
Widget buildSwitchListTile(MaterialTapTargetSize materialTapTargetSize) {
return wrap(
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return SwitchListTile(
materialTapTargetSize: materialTapTargetSize,
value: false,
onChanged: (_) {},
);
}),
);
}
await tester.pumpWidget(buildSwitchListTile(MaterialTapTargetSize.padded));
final Switch switchWidget = tester.widget<Switch>(find.byType(Switch));
expect(switchWidget.materialTapTargetSize, MaterialTapTargetSize.padded);
expect(tester.getSize(find.byType(Switch)), const Size(59.0, 48.0));
await tester.pumpWidget(buildSwitchListTile(MaterialTapTargetSize.shrinkWrap));
final Switch switchWidget1 = tester.widget<Switch>(find.byType(Switch));
expect(switchWidget1.materialTapTargetSize, MaterialTapTargetSize.shrinkWrap);
expect(tester.getSize(find.byType(Switch)), const Size(59.0, 40.0));
});
testWidgets('SwitchListTile passes the value of dragStartBehavior to Switch', (WidgetTester tester) async {
Widget buildSwitchListTile(DragStartBehavior dragStartBehavior) {
return wrap(
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return SwitchListTile(
dragStartBehavior: dragStartBehavior,
value: false,
onChanged: (_) {},
);
}),
);
}
await tester.pumpWidget(buildSwitchListTile(DragStartBehavior.start));
final Switch switchWidget = tester.widget<Switch>(find.byType(Switch));
expect(switchWidget.dragStartBehavior, DragStartBehavior.start);
await tester.pumpWidget(buildSwitchListTile(DragStartBehavior.down));
final Switch switchWidget1 = tester.widget<Switch>(find.byType(Switch));
expect(switchWidget1.dragStartBehavior, DragStartBehavior.down);
});
testWidgets('Switch on SwitchListTile changes mouse cursor when hovered', (WidgetTester tester) async {
// Test SwitchListTile.adaptive() constructor
await tester.pumpWidget(wrap(
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return SwitchListTile.adaptive(
mouseCursor: SystemMouseCursors.text,
value: false,
onChanged: (_) {},
);
}),
));
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse, pointer: 1);
await gesture.addPointer(location: tester.getCenter(find.byType(Switch)));
await tester.pump();
expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.text);
// Test SwitchListTile() constructor
await tester.pumpWidget(wrap(
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return SwitchListTile(
mouseCursor: SystemMouseCursors.forbidden,
value: false,
onChanged: (_) {},
);
}),
));
await gesture.moveTo(tester.getCenter(find.byType(Switch)));
expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.forbidden);
// Test default cursor
await tester.pumpWidget(wrap(
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return SwitchListTile(
value: false,
onChanged: (_) {},
);
}),
));
expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.click);
// Test default cursor when disabled
await tester.pumpWidget(wrap(
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return const SwitchListTile(
value: false,
onChanged: null,
);
}),
));
expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.basic);
});
testWidgets('Switch with splash radius set', (WidgetTester tester) async {
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
const double splashRadius = 35;
await tester.pumpWidget(wrap(
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return SwitchListTile(
splashRadius: splashRadius,
value: false,
onChanged: (_) {},
);
}),
));
await tester.pumpAndSettle();
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
await gesture.moveTo(tester.getCenter(find.byType(Switch)));
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Switch))),
paints..circle(radius: splashRadius),
);
});
testWidgets('The overlay color for the thumb of the switch resolves in active/pressed/hovered states', (WidgetTester tester) async {
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
const Color activeThumbColor = Color(0xFF000000);
const Color inactiveThumbColor = Color(0xFF000010);
const Color activePressedOverlayColor = Color(0xFF000001);
const Color inactivePressedOverlayColor = Color(0xFF000002);
const Color hoverOverlayColor = Color(0xFF000003);
const Color hoverColor = Color(0xFF000005);
Color? getOverlayColor(Set<MaterialState> states) {
if (states.contains(MaterialState.pressed)) {
if (states.contains(MaterialState.selected)) {
return activePressedOverlayColor;
}
return inactivePressedOverlayColor;
}
if (states.contains(MaterialState.hovered)) {
return hoverOverlayColor;
}
return null;
}
Widget buildSwitch({bool active = false, bool focused = false, bool useOverlay = true}) {
return MaterialApp(
home: Scaffold(
body: SwitchListTile(
value: active,
onChanged: (_) { },
thumbColor: MaterialStateProperty.resolveWith<Color>((Set<MaterialState> states) {
if (states.contains(MaterialState.selected)) {
return activeThumbColor;
}
return inactiveThumbColor;
}),
overlayColor: useOverlay ? MaterialStateProperty.resolveWith(getOverlayColor) : null,
hoverColor: hoverColor,
),
),
);
}
// test inactive Switch, and overlayColor is set to null.
await tester.pumpWidget(buildSwitch(useOverlay: false));
await tester.press(find.byType(Switch));
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect()
..circle(
color: inactiveThumbColor.withAlpha(kRadialReactionAlpha),
),
reason: 'Default inactive pressed Switch should have overlay color from thumbColor',
);
// test active Switch, and overlayColor is set to null.
await tester.pumpWidget(buildSwitch(active: true, useOverlay: false));
await tester.press(find.byType(Switch));
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect()
..circle(
color: activeThumbColor.withAlpha(kRadialReactionAlpha),
),
reason: 'Default active pressed Switch should have overlay color from thumbColor',
);
// test inactive Switch with an overlayColor
await tester.pumpWidget(buildSwitch());
await tester.press(find.byType(Switch));
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect()
..circle(
color: inactivePressedOverlayColor,
),
reason: 'Inactive pressed Switch should have overlay color: $inactivePressedOverlayColor',
);
// test active Switch with an overlayColor
await tester.pumpWidget(buildSwitch(active: true));
await tester.press(find.byType(Switch));
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect()
..circle(
color: activePressedOverlayColor,
),
reason: 'Active pressed Switch should have overlay color: $activePressedOverlayColor',
);
await tester.pumpWidget(buildSwitch(focused: true));
await tester.pumpAndSettle();
// Start hovering
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
await gesture.addPointer();
await gesture.moveTo(tester.getCenter(find.byType(Switch)));
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect()
..circle(
color: hoverOverlayColor,
),
reason: 'Hovered Switch should use overlay color $hoverOverlayColor over $hoverColor',
);
});
......
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