diff --git a/packages/flutter/lib/src/cupertino/switch.dart b/packages/flutter/lib/src/cupertino/switch.dart index 2057e115a2..75518bcdec 100644 --- a/packages/flutter/lib/src/cupertino/switch.dart +++ b/packages/flutter/lib/src/cupertino/switch.dart @@ -75,6 +75,8 @@ class CupertinoSwitch extends StatefulWidget { this.thumbColor, this.applyTheme, this.focusColor, + this.onLabelColor, + this.offLabelColor, this.focusNode, this.onFocusChange, this.autofocus = false, @@ -133,6 +135,17 @@ class CupertinoSwitch extends StatefulWidget { /// Defaults to a slightly transparent [activeColor]. final Color? focusColor; + /// The color to use for the accessibility label when the switch is on. + /// + /// Defaults to [CupertinoColors.white] when null. + final Color? onLabelColor; + + /// The color to use for the accessibility label when the switch is off. + /// + /// Defaults to [Color.fromARGB(255, 179, 179, 179)] + /// (or [Color.fromARGB(255, 255, 255, 255)] in high contrast) when null. + final Color? offLabelColor; + /// {@macro flutter.widgets.Focus.focusNode} final FocusNode? focusNode; @@ -357,6 +370,19 @@ class _CupertinoSwitchState extends State with TickerProviderSt ?? CupertinoColors.systemGreen, context, ); + final (Color onLabelColor, Color offLabelColor)? onOffLabelColors = + MediaQuery.onOffSwitchLabelsOf(context) + ? ( + CupertinoDynamicColor.resolve( + widget.onLabelColor ?? CupertinoColors.white, + context, + ), + CupertinoDynamicColor.resolve( + widget.offLabelColor ?? _kOffLabelColor, + context, + ), + ) + : null; if (needsPositionAnimation) { _resumePositionAnimation(); } @@ -389,6 +415,7 @@ class _CupertinoSwitchState extends State with TickerProviderSt textDirection: Directionality.of(context), isFocused: isFocused, state: this, + onOffLabelColors: onOffLabelColors, ), ), ), @@ -417,6 +444,7 @@ class _CupertinoSwitchRenderObjectWidget extends LeafRenderObjectWidget { required this.textDirection, required this.isFocused, required this.state, + required this.onOffLabelColors, }); final bool value; @@ -428,6 +456,7 @@ class _CupertinoSwitchRenderObjectWidget extends LeafRenderObjectWidget { final _CupertinoSwitchState state; final TextDirection textDirection; final bool isFocused; + final (Color onLabelColor, Color offLabelColor)? onOffLabelColors; @override _RenderCupertinoSwitch createRenderObject(BuildContext context) { @@ -441,6 +470,7 @@ class _CupertinoSwitchRenderObjectWidget extends LeafRenderObjectWidget { textDirection: textDirection, isFocused: isFocused, state: state, + onOffLabelColors: onOffLabelColors, ); } @@ -467,6 +497,24 @@ const double _kTrackInnerEnd = _kTrackWidth - _kTrackInnerStart; const double _kTrackInnerLength = _kTrackInnerEnd - _kTrackInnerStart; const double _kSwitchWidth = 59.0; const double _kSwitchHeight = 39.0; +// Label sizes and padding taken from xcode inspector. +// See https://github.com/flutter/flutter/issues/4830#issuecomment-528495360 +const double _kOnLabelWidth = 1.0; +const double _kOnLabelHeight = 10.0; +const double _kOnLabelPaddingHorizontal = 11.0; +const double _kOffLabelWidth = 1.0; +const double _kOffLabelPaddingHorizontal = 12.0; +const double _kOffLabelRadius = 5.0; +const CupertinoDynamicColor _kOffLabelColor = CupertinoDynamicColor.withBrightnessAndContrast( + debugLabel: 'offSwitchLabel', + // Source: https://github.com/flutter/flutter/pull/39993#discussion_r321946033 + color: Color.fromARGB(255, 179, 179, 179), + // Source: https://github.com/flutter/flutter/pull/39993#issuecomment-535196665 + darkColor: Color.fromARGB(255, 179, 179, 179), + // Source: https://github.com/flutter/flutter/pull/127776#discussion_r1244208264 + highContrastColor: Color.fromARGB(255, 255, 255, 255), + darkHighContrastColor: Color.fromARGB(255, 255, 255, 255), +); // Opacity of a disabled switch, as eye-balled from iOS Simulator on Mac. const double _kCupertinoSwitchDisabledOpacity = 0.5; @@ -484,6 +532,7 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox { required TextDirection textDirection, required bool isFocused, required _CupertinoSwitchState state, + required (Color onLabelColor, Color offLabelColor)? onOffLabelColors, }) : _value = value, _activeColor = activeColor, _trackColor = trackColor, @@ -493,6 +542,7 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox { _textDirection = textDirection, _isFocused = isFocused, _state = state, + _onOffLabelColors = onOffLabelColors, super(additionalConstraints: const BoxConstraints.tightFor(width: _kSwitchWidth, height: _kSwitchHeight)) { state.position.addListener(markNeedsPaint); state._reaction.addListener(markNeedsPaint); @@ -584,6 +634,16 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox { markNeedsPaint(); } + (Color onLabelColor, Color offLabelColor)? get onOffLabelColors => _onOffLabelColors; + (Color onLabelColor, Color offLabelColor)? _onOffLabelColors; + set onOffLabelColors((Color onLabelColor, Color offLabelColor)? value) { + if (value == _onOffLabelColors) { + return; + } + _onOffLabelColors = value; + markNeedsPaint(); + } + bool get isInteractive => onChanged != null; @override @@ -649,6 +709,52 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox { canvas.drawRRect(borderTrackRRect, borderPaint); } + if (_onOffLabelColors != null) { + final (Color onLabelColor, Color offLabelColor) = onOffLabelColors!; + + final double leftLabelOpacity = visualPosition * (1.0 - currentReactionValue); + final double rightLabelOpacity = (1.0 - visualPosition) * (1.0 - currentReactionValue); + final (double onLabelOpacity, double offLabelOpacity) = + switch (textDirection) { + TextDirection.ltr => (leftLabelOpacity, rightLabelOpacity), + TextDirection.rtl => (rightLabelOpacity, leftLabelOpacity), + }; + + final (Offset onLabelOffset, Offset offLabelOffset) = + switch (textDirection) { + TextDirection.ltr => ( + trackRect.centerLeft.translate(_kOnLabelPaddingHorizontal, 0), + trackRect.centerRight.translate(-_kOffLabelPaddingHorizontal, 0), + ), + TextDirection.rtl => ( + trackRect.centerRight.translate(-_kOnLabelPaddingHorizontal, 0), + trackRect.centerLeft.translate(_kOffLabelPaddingHorizontal, 0), + ), + }; + + // Draws '|' label + final Rect onLabelRect = Rect.fromCenter( + center: onLabelOffset, + width: _kOnLabelWidth, + height: _kOnLabelHeight, + ); + final Paint onLabelPaint = Paint() + ..color = onLabelColor.withOpacity(onLabelOpacity) + ..style = PaintingStyle.fill; + canvas.drawRect(onLabelRect, onLabelPaint); + + // Draws 'O' label + final Paint offLabelPaint = Paint() + ..color = offLabelColor.withOpacity(offLabelOpacity) + ..style = PaintingStyle.stroke + ..strokeWidth = _kOffLabelWidth; + canvas.drawCircle( + offLabelOffset, + _kOffLabelRadius, + offLabelPaint, + ); + } + final double currentThumbExtension = CupertinoThumbPainter.extension * currentReactionValue; final double thumbLeft = lerpDouble( trackRect.left + _kTrackInnerStart - CupertinoThumbPainter.radius, diff --git a/packages/flutter/lib/src/widgets/media_query.dart b/packages/flutter/lib/src/widgets/media_query.dart index 8cd8ee3c2a..0714708e14 100644 --- a/packages/flutter/lib/src/widgets/media_query.dart +++ b/packages/flutter/lib/src/widgets/media_query.dart @@ -60,6 +60,8 @@ enum _MediaQueryAspect { invertColors, /// Specifies the aspect corresponding to [MediaQueryData.highContrast]. highContrast, + /// Specifies the aspect corresponding to [MediaQueryData.onOffSwitchLabels]. + onOffSwitchLabels, /// Specifies the aspect corresponding to [MediaQueryData.disableAnimations]. disableAnimations, /// Specifies the aspect corresponding to [MediaQueryData.boldText]. @@ -153,6 +155,7 @@ class MediaQueryData { this.accessibleNavigation = false, this.invertColors = false, this.highContrast = false, + this.onOffSwitchLabels = false, this.disableAnimations = false, this.boldText = false, this.navigationMode = NavigationMode.traditional, @@ -220,6 +223,7 @@ class MediaQueryData { disableAnimations = platformData?.disableAnimations ?? view.platformDispatcher.accessibilityFeatures.disableAnimations, boldText = platformData?.boldText ?? view.platformDispatcher.accessibilityFeatures.boldText, highContrast = platformData?.highContrast ?? view.platformDispatcher.accessibilityFeatures.highContrast, + onOffSwitchLabels = platformData?.onOffSwitchLabels ?? view.platformDispatcher.accessibilityFeatures.onOffSwitchLabels, alwaysUse24HourFormat = platformData?.alwaysUse24HourFormat ?? view.platformDispatcher.alwaysUse24HourFormat, navigationMode = platformData?.navigationMode ?? NavigationMode.traditional, gestureSettings = DeviceGestureSettings.fromView(view), @@ -416,6 +420,15 @@ class MediaQueryData { /// or above. final bool highContrast; + /// Whether the user requested to show on/off labels inside switches on iOS, + /// via Settings -> Accessibility -> Display & Text Size -> On/Off Labels. + /// + /// See also: + /// + /// * [dart:ui.PlatformDispatcher.accessibilityFeatures], where the setting + /// originates. + final bool onOffSwitchLabels; + /// Whether the platform is requesting that animations be disabled or reduced /// as much as possible. /// @@ -488,6 +501,7 @@ class MediaQueryData { EdgeInsets? systemGestureInsets, bool? alwaysUse24HourFormat, bool? highContrast, + bool? onOffSwitchLabels, bool? disableAnimations, bool? invertColors, bool? accessibleNavigation, @@ -508,6 +522,7 @@ class MediaQueryData { alwaysUse24HourFormat: alwaysUse24HourFormat ?? this.alwaysUse24HourFormat, invertColors: invertColors ?? this.invertColors, highContrast: highContrast ?? this.highContrast, + onOffSwitchLabels: onOffSwitchLabels ?? this.onOffSwitchLabels, disableAnimations: disableAnimations ?? this.disableAnimations, accessibleNavigation: accessibleNavigation ?? this.accessibleNavigation, boldText: boldText ?? this.boldText, @@ -699,6 +714,7 @@ class MediaQueryData { && other.systemGestureInsets == systemGestureInsets && other.alwaysUse24HourFormat == alwaysUse24HourFormat && other.highContrast == highContrast + && other.onOffSwitchLabels == onOffSwitchLabels && other.disableAnimations == disableAnimations && other.invertColors == invertColors && other.accessibleNavigation == accessibleNavigation @@ -719,6 +735,7 @@ class MediaQueryData { viewInsets, alwaysUse24HourFormat, highContrast, + onOffSwitchLabels, disableAnimations, invertColors, accessibleNavigation, @@ -742,6 +759,7 @@ class MediaQueryData { 'alwaysUse24HourFormat: $alwaysUse24HourFormat', 'accessibleNavigation: $accessibleNavigation', 'highContrast: $highContrast', + 'onOffSwitchLabels: $onOffSwitchLabels', 'disableAnimations: $disableAnimations', 'invertColors: $invertColors', 'boldText: $boldText', @@ -1255,6 +1273,25 @@ class MediaQuery extends InheritedModel<_MediaQueryAspect> { /// the [MediaQueryData.highContrast] property of the ancestor [MediaQuery] changes. static bool? maybeHighContrastOf(BuildContext context) => _maybeOf(context, _MediaQueryAspect.highContrast)?.highContrast; + /// Returns onOffSwitchLabels for the nearest MediaQuery ancestor or false, if no + /// such ancestor exists. + /// + /// See also: + /// + /// * [MediaQueryData.onOffSwitchLabels], which indicates the platform's + /// desire to show on/off labels inside switches. + /// + /// Use of this method will cause the given [context] to rebuild any time that + /// the [MediaQueryData.onOffSwitchLabels] property of the ancestor [MediaQuery] changes. + static bool onOffSwitchLabelsOf(BuildContext context) => maybeOnOffSwitchLabelsOf(context) ?? false; + + /// Returns onOffSwitchLabels for the nearest MediaQuery ancestor or + /// null, if no such ancestor exists. + /// + /// Use of this method will cause the given [context] to rebuild any time that + /// the [MediaQueryData.onOffSwitchLabels] property of the ancestor [MediaQuery] changes. + static bool? maybeOnOffSwitchLabelsOf(BuildContext context) => _maybeOf(context, _MediaQueryAspect.onOffSwitchLabels)?.onOffSwitchLabels; + /// Returns disableAnimations for the nearest MediaQuery ancestor or /// [Brightness.light], if no such ancestor exists. /// @@ -1406,6 +1443,10 @@ class MediaQuery extends InheritedModel<_MediaQueryAspect> { if (data.highContrast != oldWidget.data.highContrast) { return true; } + case _MediaQueryAspect.onOffSwitchLabels: + if (data.onOffSwitchLabels != oldWidget.data.onOffSwitchLabels) { + return true; + } case _MediaQueryAspect.disableAnimations: if (data.disableAnimations != oldWidget.data.disableAnimations) { return true; diff --git a/packages/flutter/test/cupertino/switch_test.dart b/packages/flutter/test/cupertino/switch_test.dart index f91fd0af87..af9a1336de 100644 --- a/packages/flutter/test/cupertino/switch_test.dart +++ b/packages/flutter/test/cupertino/switch_test.dart @@ -753,6 +753,187 @@ void main() { ); }); + PaintPattern onLabelPaintPattern({ + required int alpha, + bool isRtl = false, + }) => + paints + ..rect( + rect: Rect.fromLTWH(isRtl ? 43.5 : 14.5, 14.5, 1.0, 10.0), + color: const Color(0xffffffff).withAlpha(alpha), + style: PaintingStyle.fill, + ); + + PaintPattern offLabelPaintPattern({ + required int alpha, + bool highContrast = false, + bool isRtl = false, + }) => + paints + ..circle( + x: isRtl ? 16.0 : 43.0, + y: 19.5, + radius: 5.0, + color: + (highContrast ? const Color(0xffffffff) : const Color(0xffb3b3b3)) + .withAlpha(alpha), + strokeWidth: 1.0, + style: PaintingStyle.stroke, + ); + + testWidgets('Switch renders switch labels correctly before, during, and after being tapped', (WidgetTester tester) async { + final Key switchKey = UniqueKey(); + bool value = false; + await tester.pumpWidget( + MediaQuery( + data: const MediaQueryData(onOffSwitchLabels: true), + child: Directionality( + textDirection: TextDirection.ltr, + child: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return Center( + child: RepaintBoundary( + child: CupertinoSwitch( + key: switchKey, + value: value, + dragStartBehavior: DragStartBehavior.down, + onChanged: (bool newValue) { + setState(() { + value = newValue; + }); + }, + ), + ), + ); + }, + ), + ), + ), + ); + + final RenderObject switchRenderObject = + tester.element(find.byType(CupertinoSwitch)).renderObject!; + + expect(switchRenderObject, offLabelPaintPattern(alpha: 255)); + expect(switchRenderObject, onLabelPaintPattern(alpha: 0)); + + await tester.tap(find.byKey(switchKey)); + expect(value, isTrue); + + // Kick off animation, then advance to intermediate frame. + await tester.pump(); + await tester.pump(const Duration(milliseconds: 60)); + expect(switchRenderObject, onLabelPaintPattern(alpha: 131)); + expect(switchRenderObject, offLabelPaintPattern(alpha: 124)); + + await tester.pumpAndSettle(); + expect(switchRenderObject, onLabelPaintPattern(alpha: 255)); + expect(switchRenderObject, offLabelPaintPattern(alpha: 0)); + }); + + testWidgets('Switch renders switch labels correctly before, during, and after being tapped in high contrast', (WidgetTester tester) async { + final Key switchKey = UniqueKey(); + bool value = false; + await tester.pumpWidget( + MediaQuery( + data: const MediaQueryData( + onOffSwitchLabels: true, + highContrast: true, + ), + child: Directionality( + textDirection: TextDirection.ltr, + child: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return Center( + child: RepaintBoundary( + child: CupertinoSwitch( + key: switchKey, + value: value, + dragStartBehavior: DragStartBehavior.down, + onChanged: (bool newValue) { + setState(() { + value = newValue; + }); + }, + ), + ), + ); + }, + ), + ), + ), + ); + + final RenderObject switchRenderObject = + tester.element(find.byType(CupertinoSwitch)).renderObject!; + + expect(switchRenderObject, offLabelPaintPattern(highContrast: true, alpha: 255)); + expect(switchRenderObject, onLabelPaintPattern(alpha: 0)); + + await tester.tap(find.byKey(switchKey)); + expect(value, isTrue); + + // Kick off animation, then advance to intermediate frame. + await tester.pump(); + await tester.pump(const Duration(milliseconds: 60)); + expect(switchRenderObject, onLabelPaintPattern(alpha: 131)); + expect(switchRenderObject, offLabelPaintPattern(highContrast: true, alpha: 124)); + + await tester.pumpAndSettle(); + expect(switchRenderObject, onLabelPaintPattern(alpha: 255)); + expect(switchRenderObject, offLabelPaintPattern(highContrast: true, alpha: 0)); + }); + + testWidgets('Switch renders switch labels correctly before, during, and after being tapped with direction rtl', (WidgetTester tester) async { + final Key switchKey = UniqueKey(); + bool value = false; + await tester.pumpWidget( + MediaQuery( + data: const MediaQueryData(onOffSwitchLabels: true), + child: Directionality( + textDirection: TextDirection.rtl, + child: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return Center( + child: RepaintBoundary( + child: CupertinoSwitch( + key: switchKey, + value: value, + dragStartBehavior: DragStartBehavior.down, + onChanged: (bool newValue) { + setState(() { + value = newValue; + }); + }, + ), + ), + ); + }, + ), + ), + ), + ); + + final RenderObject switchRenderObject = + tester.element(find.byType(CupertinoSwitch)).renderObject!; + + expect(switchRenderObject, offLabelPaintPattern(isRtl: true, alpha: 255)); + expect(switchRenderObject, onLabelPaintPattern(isRtl: true, alpha: 0)); + + await tester.tap(find.byKey(switchKey)); + expect(value, isTrue); + + // Kick off animation, then advance to intermediate frame. + await tester.pump(); + await tester.pump(const Duration(milliseconds: 60)); + expect(switchRenderObject, onLabelPaintPattern(isRtl: true, alpha: 131)); + expect(switchRenderObject, offLabelPaintPattern(isRtl: true, alpha: 124)); + + await tester.pumpAndSettle(); + expect(switchRenderObject, onLabelPaintPattern(isRtl: true, alpha: 255)); + expect(switchRenderObject, offLabelPaintPattern(isRtl: true, alpha: 0)); + }); + testWidgets('Switch renders correctly in dark mode', (WidgetTester tester) async { final Key switchKey = UniqueKey(); bool value = false; diff --git a/packages/flutter/test/widgets/media_query_test.dart b/packages/flutter/test/widgets/media_query_test.dart index 9516e35e2f..135f284ac9 100644 --- a/packages/flutter/test/widgets/media_query_test.dart +++ b/packages/flutter/test/widgets/media_query_test.dart @@ -154,6 +154,7 @@ void main() { expect(data.disableAnimations, false); expect(data.boldText, false); expect(data.highContrast, false); + expect(data.onOffSwitchLabels, false); expect(data.platformBrightness, Brightness.light); expect(data.gestureSettings.touchSlop, null); expect(data.displayFeatures, isEmpty); @@ -168,6 +169,7 @@ void main() { disableAnimations: true, boldText: true, highContrast: true, + onOffSwitchLabels: true, alwaysUse24HourFormat: true, navigationMode: NavigationMode.directional, ); @@ -188,6 +190,7 @@ void main() { expect(data.disableAnimations, platformData.disableAnimations); expect(data.boldText, platformData.boldText); expect(data.highContrast, platformData.highContrast); + expect(data.onOffSwitchLabels, platformData.onOffSwitchLabels); expect(data.alwaysUse24HourFormat, platformData.alwaysUse24HourFormat); expect(data.navigationMode, platformData.navigationMode); expect(data.gestureSettings, DeviceGestureSettings.fromView(tester.view)); @@ -217,6 +220,7 @@ void main() { expect(data.disableAnimations, tester.platformDispatcher.accessibilityFeatures.disableAnimations); expect(data.boldText, tester.platformDispatcher.accessibilityFeatures.boldText); expect(data.highContrast, tester.platformDispatcher.accessibilityFeatures.highContrast); + expect(data.onOffSwitchLabels, tester.platformDispatcher.accessibilityFeatures.onOffSwitchLabels); expect(data.alwaysUse24HourFormat, tester.platformDispatcher.alwaysUse24HourFormat); expect(data.navigationMode, NavigationMode.traditional); expect(data.gestureSettings, DeviceGestureSettings.fromView(tester.view)); @@ -232,6 +236,7 @@ void main() { disableAnimations: true, boldText: true, highContrast: true, + onOffSwitchLabels: true, alwaysUse24HourFormat: true, navigationMode: NavigationMode.directional, ); @@ -264,6 +269,7 @@ void main() { expect(data.disableAnimations, platformData.disableAnimations); expect(data.boldText, platformData.boldText); expect(data.highContrast, platformData.highContrast); + expect(data.onOffSwitchLabels, platformData.onOffSwitchLabels); expect(data.alwaysUse24HourFormat, platformData.alwaysUse24HourFormat); expect(data.navigationMode, platformData.navigationMode); expect(data.gestureSettings, DeviceGestureSettings.fromView(tester.view)); @@ -311,6 +317,7 @@ void main() { expect(data.disableAnimations, tester.platformDispatcher.accessibilityFeatures.disableAnimations); expect(data.boldText, tester.platformDispatcher.accessibilityFeatures.boldText); expect(data.highContrast, tester.platformDispatcher.accessibilityFeatures.highContrast); + expect(data.onOffSwitchLabels, tester.platformDispatcher.accessibilityFeatures.onOffSwitchLabels); expect(data.alwaysUse24HourFormat, tester.platformDispatcher.alwaysUse24HourFormat); expect(data.navigationMode, NavigationMode.traditional); expect(data.gestureSettings, DeviceGestureSettings.fromView(tester.view)); @@ -489,6 +496,7 @@ void main() { expect(copied.disableAnimations, data.disableAnimations); expect(copied.boldText, data.boldText); expect(copied.highContrast, data.highContrast); + expect(copied.onOffSwitchLabels, data.onOffSwitchLabels); expect(copied.platformBrightness, data.platformBrightness); expect(copied.gestureSettings, data.gestureSettings); expect(copied.displayFeatures, data.displayFeatures); @@ -528,6 +536,7 @@ void main() { disableAnimations: true, boldText: true, highContrast: true, + onOffSwitchLabels: true, platformBrightness: Brightness.dark, navigationMode: NavigationMode.directional, gestureSettings: gestureSettings, @@ -546,6 +555,7 @@ void main() { expect(copied.disableAnimations, true); expect(copied.boldText, true); expect(copied.highContrast, true); + expect(copied.onOffSwitchLabels, true); expect(copied.platformBrightness, Brightness.dark); expect(copied.navigationMode, NavigationMode.directional); expect(copied.gestureSettings, gestureSettings); @@ -583,6 +593,7 @@ void main() { disableAnimations: true, boldText: true, highContrast: true, + onOffSwitchLabels: true, navigationMode: NavigationMode.directional, displayFeatures: displayFeatures, ), @@ -618,6 +629,7 @@ void main() { expect(unpadded.disableAnimations, true); expect(unpadded.boldText, true); expect(unpadded.highContrast, true); + expect(unpadded.onOffSwitchLabels, true); expect(unpadded.navigationMode, NavigationMode.directional); expect(unpadded.displayFeatures, displayFeatures); }); @@ -653,6 +665,7 @@ void main() { disableAnimations: true, boldText: true, highContrast: true, + onOffSwitchLabels: true, navigationMode: NavigationMode.directional, displayFeatures: displayFeatures, ), @@ -685,6 +698,7 @@ void main() { expect(unpadded.disableAnimations, true); expect(unpadded.boldText, true); expect(unpadded.highContrast, true); + expect(unpadded.onOffSwitchLabels, true); expect(unpadded.navigationMode, NavigationMode.directional); expect(unpadded.displayFeatures, displayFeatures); }); @@ -720,6 +734,7 @@ void main() { disableAnimations: true, boldText: true, highContrast: true, + onOffSwitchLabels: true, navigationMode: NavigationMode.directional, displayFeatures: displayFeatures, ), @@ -755,6 +770,7 @@ void main() { expect(unpadded.disableAnimations, true); expect(unpadded.boldText, true); expect(unpadded.highContrast, true); + expect(unpadded.onOffSwitchLabels, true); expect(unpadded.navigationMode, NavigationMode.directional); expect(unpadded.displayFeatures, displayFeatures); }); @@ -790,6 +806,7 @@ void main() { disableAnimations: true, boldText: true, highContrast: true, + onOffSwitchLabels: true, navigationMode: NavigationMode.directional, displayFeatures: displayFeatures, ), @@ -822,6 +839,7 @@ void main() { expect(unpadded.disableAnimations, true); expect(unpadded.boldText, true); expect(unpadded.highContrast, true); + expect(unpadded.onOffSwitchLabels, true); expect(unpadded.navigationMode, NavigationMode.directional); expect(unpadded.displayFeatures, displayFeatures); }); @@ -857,6 +875,7 @@ void main() { disableAnimations: true, boldText: true, highContrast: true, + onOffSwitchLabels: true, navigationMode: NavigationMode.directional, displayFeatures: displayFeatures, ), @@ -892,6 +911,7 @@ void main() { expect(unpadded.disableAnimations, true); expect(unpadded.boldText, true); expect(unpadded.highContrast, true); + expect(unpadded.onOffSwitchLabels, true); expect(unpadded.navigationMode, NavigationMode.directional); expect(unpadded.displayFeatures, displayFeatures); }); @@ -927,6 +947,7 @@ void main() { disableAnimations: true, boldText: true, highContrast: true, + onOffSwitchLabels: true, navigationMode: NavigationMode.directional, displayFeatures: displayFeatures, ), @@ -959,6 +980,7 @@ void main() { expect(unpadded.disableAnimations, true); expect(unpadded.boldText, true); expect(unpadded.highContrast, true); + expect(unpadded.onOffSwitchLabels, true); expect(unpadded.navigationMode, NavigationMode.directional); expect(unpadded.displayFeatures, displayFeatures); }); @@ -1044,6 +1066,33 @@ void main() { expect(insideHighContrast, true); }); + testWidgets('MediaQuery.onOffSwitchLabelsOf', (WidgetTester tester) async { + late bool outsideOnOffSwitchLabels; + late bool insideOnOffSwitchLabels; + + await tester.pumpWidget( + Builder( + builder: (BuildContext context) { + outsideOnOffSwitchLabels = MediaQuery.onOffSwitchLabelsOf(context); + return MediaQuery( + data: const MediaQueryData( + onOffSwitchLabels: true, + ), + child: Builder( + builder: (BuildContext context) { + insideOnOffSwitchLabels = MediaQuery.onOffSwitchLabelsOf(context); + return Container(); + }, + ), + ); + }, + ), + ); + + expect(outsideOnOffSwitchLabels, false); + expect(insideOnOffSwitchLabels, true); + }); + testWidgets('MediaQuery.boldTextOf', (WidgetTester tester) async { late bool outsideBoldTextOverride; late bool insideBoldTextOverride; @@ -1171,6 +1220,7 @@ void main() { disableAnimations: true, boldText: true, highContrast: true, + onOffSwitchLabels: true, displayFeatures: displayFeatures, ), child: Builder( @@ -1201,6 +1251,7 @@ void main() { expect(subScreenMediaQuery.disableAnimations, true); expect(subScreenMediaQuery.boldText, true); expect(subScreenMediaQuery.highContrast, true); + expect(subScreenMediaQuery.onOffSwitchLabels, true); expect(subScreenMediaQuery.displayFeatures, isEmpty); }); @@ -1244,6 +1295,7 @@ void main() { disableAnimations: true, boldText: true, highContrast: true, + onOffSwitchLabels: true, displayFeatures: displayFeatures, ), child: Builder( @@ -1283,6 +1335,7 @@ void main() { expect(subScreenMediaQuery.disableAnimations, true); expect(subScreenMediaQuery.boldText, true); expect(subScreenMediaQuery.highContrast, true); + expect(subScreenMediaQuery.onOffSwitchLabels, true); expect(subScreenMediaQuery.displayFeatures, [cutoutDisplayFeature]); }); @@ -1453,6 +1506,8 @@ void main() { const _MediaQueryAspectCase(MediaQuery.maybeInvertColorsOf, MediaQueryData(invertColors: true)), const _MediaQueryAspectCase(MediaQuery.highContrastOf, MediaQueryData(highContrast: true)), const _MediaQueryAspectCase(MediaQuery.maybeHighContrastOf, MediaQueryData(highContrast: true)), + const _MediaQueryAspectCase(MediaQuery.onOffSwitchLabelsOf, MediaQueryData(onOffSwitchLabels: true)), + const _MediaQueryAspectCase(MediaQuery.maybeOnOffSwitchLabelsOf, MediaQueryData(onOffSwitchLabels: true)), const _MediaQueryAspectCase(MediaQuery.disableAnimationsOf, MediaQueryData(disableAnimations: true)), const _MediaQueryAspectCase(MediaQuery.maybeDisableAnimationsOf, MediaQueryData(disableAnimations: true)), const _MediaQueryAspectCase(MediaQuery.boldTextOf, MediaQueryData(boldText: true)),