diff --git a/packages/flutter/lib/src/cupertino/switch.dart b/packages/flutter/lib/src/cupertino/switch.dart index 0fee8c5f71..b36b2946be 100644 --- a/packages/flutter/lib/src/cupertino/switch.dart +++ b/packages/flutter/lib/src/cupertino/switch.dart @@ -127,6 +127,7 @@ class _CupertinoSwitchRenderObjectWidget extends LeafRenderObjectWidget { value: value, activeColor: activeColor, onChanged: onChanged, + textDirection: Directionality.of(context), vsync: vsync, ); } @@ -137,6 +138,7 @@ class _CupertinoSwitchRenderObjectWidget extends LeafRenderObjectWidget { ..value = value ..activeColor = activeColor ..onChanged = onChanged + ..textDirection = Directionality.of(context) ..vsync = vsync; } } @@ -159,6 +161,7 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox implements SemanticsAc @required bool value, @required Color activeColor, ValueChanged onChanged, + @required TextDirection textDirection, @required TickerProvider vsync, }) : assert(value != null), assert(activeColor != null), @@ -166,6 +169,7 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox implements SemanticsAc _value = value, _activeColor = activeColor, _onChanged = onChanged, + _textDirection = textDirection, _vsync = vsync, super(additionalConstraints: const BoxConstraints.tightFor(width: _kSwitchWidth, height: _kSwitchHeight)) { _tap = new TapGestureRecognizer() @@ -254,6 +258,16 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox implements SemanticsAc } } + TextDirection get textDirection => _textDirection; + TextDirection _textDirection; + set textDirection(TextDirection value) { + assert(value != null); + if (_textDirection == value) + return; + _textDirection = value; + markNeedsPaint(); + } + bool get isInteractive => onChanged != null; TapGestureRecognizer _tap; @@ -328,7 +342,15 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox implements SemanticsAc _position ..curve = null ..reverseCurve = null; - _positionController.value += details.primaryDelta / _kTrackInnerLength; + final double delta = details.primaryDelta / _kTrackInnerLength; + switch (textDirection) { + case TextDirection.rtl: + _positionController.value -= delta; + break; + case TextDirection.ltr: + _positionController.value += delta; + break; + } } } @@ -378,11 +400,21 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox implements SemanticsAc void paint(PaintingContext context, Offset offset) { final Canvas canvas = context.canvas; - final double currentPosition = _position.value; + final double currentValue = _position.value; final double currentReactionValue = _reaction.value; + double visualPosition; + switch (textDirection) { + case TextDirection.rtl: + visualPosition = 1.0 - currentValue; + break; + case TextDirection.ltr: + visualPosition = currentValue; + break; + } + final Color trackColor = _value ? activeColor : _kTrackColor; - final double borderThickness = 1.5 + (_kTrackRadius - 1.5) * math.max(currentReactionValue, currentPosition); + final double borderThickness = 1.5 + (_kTrackRadius - 1.5) * math.max(currentReactionValue, currentValue); final Paint paint = new Paint() ..color = trackColor; @@ -401,12 +433,12 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox implements SemanticsAc final double thumbLeft = lerpDouble( trackRect.left + _kTrackInnerStart - CupertinoThumbPainter.radius, trackRect.left + _kTrackInnerEnd - CupertinoThumbPainter.radius - currentThumbExtension, - currentPosition, + visualPosition, ); final double thumbRight = lerpDouble( trackRect.left + _kTrackInnerStart + CupertinoThumbPainter.radius + currentThumbExtension, trackRect.left + _kTrackInnerEnd + CupertinoThumbPainter.radius, - currentPosition, + visualPosition, ); final double thumbCenterY = offset.dy + size.height / 2.0; diff --git a/packages/flutter/lib/src/material/switch.dart b/packages/flutter/lib/src/material/switch.dart index dce6fe993a..bc4953d84c 100644 --- a/packages/flutter/lib/src/material/switch.dart +++ b/packages/flutter/lib/src/material/switch.dart @@ -168,18 +168,21 @@ class _SwitchRenderObjectWidget extends LeafRenderObjectWidget { final TickerProvider vsync; @override - _RenderSwitch createRenderObject(BuildContext context) => new _RenderSwitch( - value: value, - activeColor: activeColor, - inactiveColor: inactiveColor, - activeThumbImage: activeThumbImage, - inactiveThumbImage: inactiveThumbImage, - activeTrackColor: activeTrackColor, - inactiveTrackColor: inactiveTrackColor, - configuration: configuration, - onChanged: onChanged, - vsync: vsync, - ); + _RenderSwitch createRenderObject(BuildContext context) { + return new _RenderSwitch( + value: value, + activeColor: activeColor, + inactiveColor: inactiveColor, + activeThumbImage: activeThumbImage, + inactiveThumbImage: inactiveThumbImage, + activeTrackColor: activeTrackColor, + inactiveTrackColor: inactiveTrackColor, + configuration: configuration, + onChanged: onChanged, + textDirection: Directionality.of(context), + vsync: vsync, + ); + } @override void updateRenderObject(BuildContext context, _RenderSwitch renderObject) { @@ -193,6 +196,7 @@ class _SwitchRenderObjectWidget extends LeafRenderObjectWidget { ..inactiveTrackColor = inactiveTrackColor ..configuration = configuration ..onChanged = onChanged + ..textDirection = Directionality.of(context) ..vsync = vsync; } } @@ -214,13 +218,16 @@ class _RenderSwitch extends RenderToggleable { Color activeTrackColor, Color inactiveTrackColor, ImageConfiguration configuration, + @required TextDirection textDirection, ValueChanged onChanged, @required TickerProvider vsync, - }) : _activeThumbImage = activeThumbImage, + }) : assert(textDirection != null), + _activeThumbImage = activeThumbImage, _inactiveThumbImage = inactiveThumbImage, _activeTrackColor = activeTrackColor, _inactiveTrackColor = inactiveTrackColor, _configuration = configuration, + _textDirection = textDirection, super( value: value, activeColor: activeColor, @@ -283,6 +290,16 @@ class _RenderSwitch extends RenderToggleable { markNeedsPaint(); } + TextDirection get textDirection => _textDirection; + TextDirection _textDirection; + set textDirection(TextDirection value) { + assert(value != null); + if (_textDirection == value) + return; + _textDirection = value; + markNeedsPaint(); + } + @override void detach() { _cachedThumbPainter?.dispose(); @@ -304,7 +321,15 @@ class _RenderSwitch extends RenderToggleable { position ..curve = null ..reverseCurve = null; - positionController.value += details.primaryDelta / _trackInnerLength; + final double delta = details.primaryDelta / _trackInnerLength; + switch (textDirection) { + case TextDirection.rtl: + positionController.value -= delta; + break; + case TextDirection.ltr: + positionController.value += delta; + break; + } } } @@ -353,9 +378,19 @@ class _RenderSwitch extends RenderToggleable { final Canvas canvas = context.canvas; final bool isActive = onChanged != null; - final double currentPosition = position.value; + final double currentValue = position.value; - final Color trackColor = isActive ? Color.lerp(inactiveTrackColor, activeTrackColor, currentPosition) : inactiveTrackColor; + double visualPosition; + switch (textDirection) { + case TextDirection.rtl: + visualPosition = 1.0 - currentValue; + break; + case TextDirection.ltr: + visualPosition = currentValue; + break; + } + + final Color trackColor = isActive ? Color.lerp(inactiveTrackColor, activeTrackColor, currentValue) : inactiveTrackColor; // Paint the track final Paint paint = new Paint() @@ -371,7 +406,7 @@ class _RenderSwitch extends RenderToggleable { canvas.drawRRect(trackRRect, paint); final Offset thumbPosition = new Offset( - kRadialReactionRadius + currentPosition * _trackInnerLength, + kRadialReactionRadius + visualPosition * _trackInnerLength, size.height / 2.0 ); @@ -380,8 +415,8 @@ class _RenderSwitch extends RenderToggleable { try { _isPainting = true; BoxPainter thumbPainter; - final Color thumbColor = isActive ? Color.lerp(inactiveColor, activeColor, currentPosition) : inactiveColor; - final ImageProvider thumbImage = isActive ? (currentPosition < 0.5 ? inactiveThumbImage : activeThumbImage) : inactiveThumbImage; + final Color thumbColor = isActive ? Color.lerp(inactiveColor, activeColor, currentValue) : inactiveColor; + final ImageProvider thumbImage = isActive ? (currentValue < 0.5 ? inactiveThumbImage : activeThumbImage) : inactiveThumbImage; if (_cachedThumbPainter == null || thumbColor != _cachedThumbColor || thumbImage != _cachedThumbImage) { _cachedThumbColor = thumbColor; _cachedThumbImage = thumbImage; @@ -390,7 +425,7 @@ class _RenderSwitch extends RenderToggleable { thumbPainter = _cachedThumbPainter; // The thumb contracts slightly during the animation - final double inset = 1.0 - (currentPosition - 0.5).abs() * 2.0; + final double inset = 1.0 - (currentValue - 0.5).abs() * 2.0; final double radius = _kThumbRadius - inset; thumbPainter.paint( canvas, diff --git a/packages/flutter/test/cupertino/switch_test.dart b/packages/flutter/test/cupertino/switch_test.dart index 0e16ab4859..cffb7c4fb8 100644 --- a/packages/flutter/test/cupertino/switch_test.dart +++ b/packages/flutter/test/cupertino/switch_test.dart @@ -10,20 +10,23 @@ void main() { final Key switchKey = new UniqueKey(); bool value = false; await tester.pumpWidget( - new StatefulBuilder( - builder: (BuildContext context, StateSetter setState) { - return new Center( - child: new CupertinoSwitch( - key: switchKey, - value: value, - onChanged: (bool newValue) { - setState(() { - value = newValue; - }); - }, - ), - ); - }, + new Directionality( + textDirection: TextDirection.ltr, + child: new StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return new Center( + child: new CupertinoSwitch( + key: switchKey, + value: value, + onChanged: (bool newValue) { + setState(() { + value = newValue; + }); + }, + ), + ); + }, + ), ), ); @@ -31,4 +34,93 @@ void main() { await tester.tap(find.byKey(switchKey)); expect(value, isTrue); }); + + testWidgets('Switch can drag (LTR)', (WidgetTester tester) async { + bool value = false; + + await tester.pumpWidget( + new Directionality( + textDirection: TextDirection.ltr, + child: new StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return new Center( + child: new CupertinoSwitch( + value: value, + onChanged: (bool newValue) { + setState(() { + value = newValue; + }); + }, + ), + ); + }, + ), + ), + ); + + expect(value, isFalse); + + await tester.drag(find.byType(CupertinoSwitch), const Offset(-30.0, 0.0)); + + expect(value, isFalse); + + await tester.drag(find.byType(CupertinoSwitch), const Offset(30.0, 0.0)); + + expect(value, isTrue); + + await tester.pump(); + await tester.drag(find.byType(CupertinoSwitch), const Offset(30.0, 0.0)); + + expect(value, isTrue); + + await tester.pump(); + await tester.drag(find.byType(CupertinoSwitch), const Offset(-30.0, 0.0)); + + expect(value, isFalse); + }); + + testWidgets('Switch can drag (RTL)', (WidgetTester tester) async { + bool value = false; + + await tester.pumpWidget( + new Directionality( + textDirection: TextDirection.rtl, + child: new StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return new Center( + child: new CupertinoSwitch( + value: value, + onChanged: (bool newValue) { + setState(() { + value = newValue; + }); + }, + ), + ); + }, + ), + ), + ); + + expect(value, isFalse); + + await tester.drag(find.byType(CupertinoSwitch), const Offset(30.0, 0.0)); + + expect(value, isFalse); + + await tester.drag(find.byType(CupertinoSwitch), const Offset(-30.0, 0.0)); + + expect(value, isTrue); + + await tester.pump(); + await tester.drag(find.byType(CupertinoSwitch), const Offset(-30.0, 0.0)); + + expect(value, isTrue); + + await tester.pump(); + await tester.drag(find.byType(CupertinoSwitch), const Offset(30.0, 0.0)); + + expect(value, isFalse); + }); + } diff --git a/packages/flutter/test/material/switch_test.dart b/packages/flutter/test/material/switch_test.dart index 1f60d91f20..c05e0df1bc 100644 --- a/packages/flutter/test/material/switch_test.dart +++ b/packages/flutter/test/material/switch_test.dart @@ -12,23 +12,26 @@ void main() { bool value = false; await tester.pumpWidget( - new StatefulBuilder( - builder: (BuildContext context, StateSetter setState) { - return new Material( - child: new Center( - child: new Switch( - key: switchKey, - value: value, - onChanged: (bool newValue) { - setState(() { - value = newValue; - }); - } - ) - ) - ); - } - ) + new Directionality( + textDirection: TextDirection.ltr, + child: new StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return new Material( + child: new Center( + child: new Switch( + key: switchKey, + value: value, + onChanged: (bool newValue) { + setState(() { + value = newValue; + }); + }, + ), + ), + ); + }, + ), + ), ); expect(value, isFalse); @@ -36,26 +39,29 @@ void main() { expect(value, isTrue); }); - testWidgets('Switch can drag', (WidgetTester tester) async { + testWidgets('Switch can drag (LTR)', (WidgetTester tester) async { bool value = false; await tester.pumpWidget( - new StatefulBuilder( - builder: (BuildContext context, StateSetter setState) { - return new Material( - child: new Center( - child: new Switch( - value: value, - onChanged: (bool newValue) { - setState(() { - value = newValue; - }); - } - ) - ) - ); - } - ) + new Directionality( + textDirection: TextDirection.ltr, + child: new StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return new Material( + child: new Center( + child: new Switch( + value: value, + onChanged: (bool newValue) { + setState(() { + value = newValue; + }); + }, + ), + ), + ); + }, + ), + ), ); expect(value, isFalse); @@ -78,4 +84,50 @@ void main() { expect(value, isFalse); }); + + testWidgets('Switch can drag (RTL)', (WidgetTester tester) async { + bool value = false; + + await tester.pumpWidget( + new Directionality( + textDirection: TextDirection.rtl, + child: new StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return new Material( + child: new Center( + child: new Switch( + value: value, + onChanged: (bool newValue) { + setState(() { + value = newValue; + }); + }, + ), + ), + ); + }, + ), + ), + ); + + expect(value, isFalse); + + await tester.drag(find.byType(Switch), const Offset(30.0, 0.0)); + + expect(value, isFalse); + + await tester.drag(find.byType(Switch), const Offset(-30.0, 0.0)); + + expect(value, isTrue); + + await tester.pump(); + await tester.drag(find.byType(Switch), const Offset(-30.0, 0.0)); + + expect(value, isTrue); + + await tester.pump(); + await tester.drag(find.byType(Switch), const Offset(30.0, 0.0)); + + expect(value, isFalse); + }); }