Unverified Commit 4a4fa2a7 authored by Ian Hickson's avatar Ian Hickson Committed by GitHub

Cupertino RTL (#13273)

Fixes the remaining known issues with widgets supporting RTL.
parent 6493c8b4
...@@ -39,7 +39,7 @@ class _CupertinoSliderDemoState extends State<CupertinoSliderDemo> { ...@@ -39,7 +39,7 @@ class _CupertinoSliderDemoState extends State<CupertinoSliderDemo> {
}); });
} }
), ),
const Text('Cupertino Continuous'), new Text('Cupertino Continuous: ${_value.toStringAsFixed(1)}'),
] ]
), ),
new Column( new Column(
...@@ -56,7 +56,7 @@ class _CupertinoSliderDemoState extends State<CupertinoSliderDemo> { ...@@ -56,7 +56,7 @@ class _CupertinoSliderDemoState extends State<CupertinoSliderDemo> {
}); });
} }
), ),
const Text('Cupertino Discrete'), new Text('Cupertino Discrete: $_discreteValue'),
] ]
), ),
], ],
......
...@@ -31,16 +31,17 @@ class CupertinoIcons { ...@@ -31,16 +31,17 @@ class CupertinoIcons {
/// The icon font used for Cupertino icons. /// The icon font used for Cupertino icons.
static const String iconFont = 'CupertinoIcons'; static const String iconFont = 'CupertinoIcons';
/// The dependent package providing the Cupertino icons font. /// The dependent package providing the Cupertino icons font.
static const String iconFontPackage = 'cupertino_icons'; static const String iconFontPackage = 'cupertino_icons';
// Manually maintained list // Manually maintained list.
/// A thin left chevron. /// A thin left chevron.
static const IconData left_chevron = const IconData(0xf3f0, fontFamily: iconFont, fontPackage: iconFontPackage); static const IconData left_chevron = const IconData(0xf3f0, fontFamily: iconFont, fontPackage: iconFontPackage, matchTextDirection: true);
/// A thin right chevron. /// A thin right chevron.
static const IconData right_chevron = const IconData(0xf3f2, fontFamily: iconFont, fontPackage: iconFontPackage); static const IconData right_chevron = const IconData(0xf3f2, fontFamily: iconFont, fontPackage: iconFontPackage, matchTextDirection: true);
/// iOS style share icon with an arrow pointing up from a box. /// iOS style share icon with an arrow pointing up from a box.
static const IconData share = const IconData(0xf4ca, fontFamily: iconFont, fontPackage: iconFontPackage); static const IconData share = const IconData(0xf4ca, fontFamily: iconFont, fontPackage: iconFontPackage);
...@@ -79,14 +80,14 @@ class CupertinoIcons { ...@@ -79,14 +80,14 @@ class CupertinoIcons {
static const IconData check_mark_circled = const IconData(0xf3fe, fontFamily: iconFont, fontPackage: iconFontPackage); static const IconData check_mark_circled = const IconData(0xf3fe, fontFamily: iconFont, fontPackage: iconFontPackage);
/// A thicker left chevron used in iOS for the nav bar back button. /// A thicker left chevron used in iOS for the nav bar back button.
static const IconData back = const IconData(0xf3cf, fontFamily: iconFont, fontPackage: iconFontPackage); static const IconData back = const IconData(0xf3cf, fontFamily: iconFont, fontPackage: iconFontPackage, matchTextDirection: true);
/// Outline of a simple front-facing house. /// Outline of a simple front-facing house.
static const IconData home = const IconData(0xf447, fontFamily: iconFont, fontPackage: iconFontPackage); static const IconData home = const IconData(0xf447, fontFamily: iconFont, fontPackage: iconFontPackage);
/// A right facing shopping cart outline. /// A right-facing shopping cart outline.
static const IconData shopping_cart = const IconData(0xf3f7, fontFamily: iconFont, fontPackage: iconFontPackage); static const IconData shopping_cart = const IconData(0xf3f7, fontFamily: iconFont, fontPackage: iconFontPackage);
/// 3 solid dots. /// Three solid dots.
static const IconData ellipsis = const IconData(0xf46a, fontFamily: iconFont, fontPackage: iconFontPackage); static const IconData ellipsis = const IconData(0xf46a, fontFamily: iconFont, fontPackage: iconFontPackage);
} }
...@@ -376,7 +376,7 @@ class _CupertinoPersistentNavigationBar extends StatelessWidget implements Prefe ...@@ -376,7 +376,7 @@ class _CupertinoPersistentNavigationBar extends StatelessWidget implements Prefe
? new Container( ? new Container(
height: _kNavBarPersistentHeight, height: _kNavBarPersistentHeight,
width: _kNavBarBackButtonTapWidth, width: _kNavBarBackButtonTapWidth,
alignment: Alignment.centerLeft, alignment: AlignmentDirectional.centerStart,
child: const Icon(CupertinoIcons.back, size: 34.0,) child: const Icon(CupertinoIcons.back, size: 34.0,)
) )
: const Text('Close'), : const Text('Close'),
...@@ -394,10 +394,10 @@ class _CupertinoPersistentNavigationBar extends StatelessWidget implements Prefe ...@@ -394,10 +394,10 @@ class _CupertinoPersistentNavigationBar extends StatelessWidget implements Prefe
size: 22.0, size: 22.0,
), ),
child: new Padding( child: new Padding(
padding: new EdgeInsets.only( padding: new EdgeInsetsDirectional.only(
top: MediaQuery.of(context).padding.top, top: MediaQuery.of(context).padding.top,
left: useBackButton ? _kNavBarBackButtonPadding : _kNavBarEdgePadding, start: useBackButton ? _kNavBarBackButtonPadding : _kNavBarEdgePadding,
right: _kNavBarEdgePadding, end: _kNavBarEdgePadding,
), ),
child: new MediaQuery.removePadding( child: new MediaQuery.removePadding(
context: context, context: context,
......
...@@ -35,9 +35,9 @@ final DecorationTween _kGradientShadowTween = new DecorationTween( ...@@ -35,9 +35,9 @@ final DecorationTween _kGradientShadowTween = new DecorationTween(
end: const _CupertinoEdgeShadowDecoration( end: const _CupertinoEdgeShadowDecoration(
edgeGradient: const LinearGradient( edgeGradient: const LinearGradient(
// Spans 5% of the page. // Spans 5% of the page.
begin: const Alignment(0.90, 0.0), begin: const AlignmentDirectional(0.90, 0.0),
end: Alignment.centerRight, end: AlignmentDirectional.centerEnd,
// Eyeballed gradient used to mimic a drop shadow on the left side only. // Eyeballed gradient used to mimic a drop shadow on the start side only.
colors: const <Color>[ colors: const <Color>[
const Color(0x00000000), const Color(0x00000000),
const Color(0x04000000), const Color(0x04000000),
...@@ -293,31 +293,31 @@ class CupertinoPageTransition extends StatelessWidget { ...@@ -293,31 +293,31 @@ class CupertinoPageTransition extends StatelessWidget {
@required Animation<double> primaryRouteAnimation, @required Animation<double> primaryRouteAnimation,
@required Animation<double> secondaryRouteAnimation, @required Animation<double> secondaryRouteAnimation,
@required this.child, @required this.child,
bool linearTransition, @required bool linearTransition,
}) : }) : assert(linearTransition != null),
_primaryPositionAnimation = linearTransition _primaryPositionAnimation = linearTransition
? _kRightMiddleTween.animate(primaryRouteAnimation) ? _kRightMiddleTween.animate(primaryRouteAnimation)
: _kRightMiddleTween.animate( : _kRightMiddleTween.animate(
new CurvedAnimation( new CurvedAnimation(
parent: primaryRouteAnimation, parent: primaryRouteAnimation,
curve: Curves.easeOut, curve: Curves.easeOut,
reverseCurve: Curves.easeIn, reverseCurve: Curves.easeIn,
) )
), ),
_secondaryPositionAnimation = _kMiddleLeftTween.animate( _secondaryPositionAnimation = _kMiddleLeftTween.animate(
new CurvedAnimation( new CurvedAnimation(
parent: secondaryRouteAnimation, parent: secondaryRouteAnimation,
curve: Curves.easeOut, curve: Curves.easeOut,
reverseCurve: Curves.easeIn, reverseCurve: Curves.easeIn,
) )
), ),
_primaryShadowAnimation = _kGradientShadowTween.animate( _primaryShadowAnimation = _kGradientShadowTween.animate(
new CurvedAnimation( new CurvedAnimation(
parent: primaryRouteAnimation, parent: primaryRouteAnimation,
curve: Curves.easeOut, curve: Curves.easeOut,
) )
), ),
super(key: key); super(key: key);
// When this page is coming in to cover another page. // When this page is coming in to cover another page.
final Animation<Offset> _primaryPositionAnimation; final Animation<Offset> _primaryPositionAnimation;
...@@ -330,12 +330,16 @@ class CupertinoPageTransition extends StatelessWidget { ...@@ -330,12 +330,16 @@ class CupertinoPageTransition extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
assert(debugCheckHasDirectionality(context));
final TextDirection textDirection = Directionality.of(context);
// TODO(ianh): tell the transform to be un-transformed for hit testing // TODO(ianh): tell the transform to be un-transformed for hit testing
// but not while being controlled by a gesture. // but not while being controlled by a gesture.
return new SlideTransition( return new SlideTransition(
position: _secondaryPositionAnimation, position: _secondaryPositionAnimation,
textDirection: textDirection,
child: new SlideTransition( child: new SlideTransition(
position: _primaryPositionAnimation, position: _primaryPositionAnimation,
textDirection: textDirection,
child: new DecoratedBoxTransition( child: new DecoratedBoxTransition(
decoration: _primaryShadowAnimation, decoration: _primaryShadowAnimation,
child: child, child: child,
...@@ -382,6 +386,9 @@ class CupertinoFullscreenDialogTransition extends StatelessWidget { ...@@ -382,6 +386,9 @@ class CupertinoFullscreenDialogTransition extends StatelessWidget {
/// This widget provides a gesture recognizer which, when it determines the /// This widget provides a gesture recognizer which, when it determines the
/// route can be closed with a back gesture, creates the controller and /// route can be closed with a back gesture, creates the controller and
/// feeds it the input from the gesture recognizer. /// feeds it the input from the gesture recognizer.
///
/// The gesture data is converted from absolute coordinates to logical
/// coordinates by this widget.
class _CupertinoBackGestureDetector extends StatefulWidget { class _CupertinoBackGestureDetector extends StatefulWidget {
const _CupertinoBackGestureDetector({ const _CupertinoBackGestureDetector({
Key key, Key key,
...@@ -433,13 +440,13 @@ class _CupertinoBackGestureDetectorState extends State<_CupertinoBackGestureDete ...@@ -433,13 +440,13 @@ class _CupertinoBackGestureDetectorState extends State<_CupertinoBackGestureDete
void _handleDragUpdate(DragUpdateDetails details) { void _handleDragUpdate(DragUpdateDetails details) {
assert(mounted); assert(mounted);
assert(_backGestureController != null); assert(_backGestureController != null);
_backGestureController.dragUpdate(details.primaryDelta / context.size.width); _backGestureController.dragUpdate(_convertToLogical(details.primaryDelta / context.size.width));
} }
void _handleDragEnd(DragEndDetails details) { void _handleDragEnd(DragEndDetails details) {
assert(mounted); assert(mounted);
assert(_backGestureController != null); assert(_backGestureController != null);
_backGestureController.dragEnd(details.velocity.pixelsPerSecond.dx / context.size.width); _backGestureController.dragEnd(_convertToLogical(details.velocity.pixelsPerSecond.dx / context.size.width));
_backGestureController = null; _backGestureController = null;
} }
...@@ -456,14 +463,25 @@ class _CupertinoBackGestureDetectorState extends State<_CupertinoBackGestureDete ...@@ -456,14 +463,25 @@ class _CupertinoBackGestureDetectorState extends State<_CupertinoBackGestureDete
_recognizer.addPointer(event); _recognizer.addPointer(event);
} }
double _convertToLogical(double value) {
switch (Directionality.of(context)) {
case TextDirection.rtl:
return -value;
case TextDirection.ltr:
return value;
}
return null;
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
assert(debugCheckHasDirectionality(context));
return new Stack( return new Stack(
fit: StackFit.passthrough, fit: StackFit.passthrough,
children: <Widget>[ children: <Widget>[
widget.child, widget.child,
new Positioned( new PositionedDirectional(
left: 0.0, start: 0.0,
width: _kBackGestureWidth, width: _kBackGestureWidth,
top: 0.0, top: 0.0,
bottom: 0.0, bottom: 0.0,
...@@ -484,6 +502,9 @@ class _CupertinoBackGestureDetectorState extends State<_CupertinoBackGestureDete ...@@ -484,6 +502,9 @@ class _CupertinoBackGestureDetectorState extends State<_CupertinoBackGestureDete
/// by a [_CupertinoBackGestureDetector] widget, which then also feeds it input /// by a [_CupertinoBackGestureDetector] widget, which then also feeds it input
/// from the gesture. It controls the animation controller owned by the route, /// from the gesture. It controls the animation controller owned by the route,
/// based on the input provided by the gesture detector. /// based on the input provided by the gesture detector.
///
/// This class works entirely in logical coordinates (0.0 is new page dismissed,
/// 1.0 is new page on top).
class _CupertinoBackGestureController { class _CupertinoBackGestureController {
/// Creates a controller for an iOS-style back gesture. /// Creates a controller for an iOS-style back gesture.
/// ///
...@@ -553,9 +574,15 @@ class _CupertinoBackGestureController { ...@@ -553,9 +574,15 @@ class _CupertinoBackGestureController {
} }
} }
/// A custom [Decoration] used to paint an extra shadow on the left edge of the // A custom [Decoration] used to paint an extra shadow on the start edge of the
/// box it's decorating. It's like a [BoxDecoration] with only a gradient except // box it's decorating. It's like a [BoxDecoration] with only a gradient except
/// it paints to the left of the box instead of behind the box. // it paints on the start side of the box instead of behind the box.
//
// The [edgeGradient] will be given a [TextDirection] when its shader is
// created, and so can be direction-sensitive; in this file we set it to a
// gradient that uses an AlignmentDirectional to position the gradient on the
// end edge of the gradient's box (which will be the edge adjacent to the start
// edge of the actual box we're supposed to paint in).
class _CupertinoEdgeShadowDecoration extends Decoration { class _CupertinoEdgeShadowDecoration extends Decoration {
const _CupertinoEdgeShadowDecoration({ this.edgeGradient }); const _CupertinoEdgeShadowDecoration({ this.edgeGradient });
...@@ -604,18 +631,14 @@ class _CupertinoEdgeShadowDecoration extends Decoration { ...@@ -604,18 +631,14 @@ class _CupertinoEdgeShadowDecoration extends Decoration {
@override @override
bool operator ==(dynamic other) { bool operator ==(dynamic other) {
if (identical(this, other)) if (runtimeType != other.runtimeType)
return true;
if (other.runtimeType != _CupertinoEdgeShadowDecoration)
return false; return false;
final _CupertinoEdgeShadowDecoration typedOther = other; final _CupertinoEdgeShadowDecoration typedOther = other;
return edgeGradient == typedOther.edgeGradient; return edgeGradient == typedOther.edgeGradient;
} }
@override @override
int get hashCode { int get hashCode => edgeGradient.hashCode;
return edgeGradient.hashCode;
}
@override @override
void debugFillProperties(DiagnosticPropertiesBuilder properties) { void debugFillProperties(DiagnosticPropertiesBuilder properties) {
...@@ -628,7 +651,7 @@ class _CupertinoEdgeShadowDecoration extends Decoration { ...@@ -628,7 +651,7 @@ class _CupertinoEdgeShadowDecoration extends Decoration {
class _CupertinoEdgeShadowPainter extends BoxPainter { class _CupertinoEdgeShadowPainter extends BoxPainter {
_CupertinoEdgeShadowPainter( _CupertinoEdgeShadowPainter(
this._decoration, this._decoration,
VoidCallback onChange VoidCallback onChange,
) : assert(_decoration != null), ) : assert(_decoration != null),
super(onChange); super(onChange);
...@@ -640,11 +663,21 @@ class _CupertinoEdgeShadowPainter extends BoxPainter { ...@@ -640,11 +663,21 @@ class _CupertinoEdgeShadowPainter extends BoxPainter {
if (gradient == null) if (gradient == null)
return; return;
// The drawable space for the gradient is a rect with the same size as // The drawable space for the gradient is a rect with the same size as
// its parent box one box width to the left of the box. // its parent box one box width on the start side of the box.
final Rect rect = final TextDirection textDirection = configuration.textDirection;
(offset & configuration.size).translate(-configuration.size.width, 0.0); assert(textDirection != null);
double deltaX;
switch (textDirection) {
case TextDirection.rtl:
deltaX = configuration.size.width;
break;
case TextDirection.ltr:
deltaX = -configuration.size.width;
break;
}
final Rect rect = (offset & configuration.size).translate(deltaX, 0.0);
final Paint paint = new Paint() final Paint paint = new Paint()
..shader = gradient.createShader(rect); ..shader = gradient.createShader(rect, textDirection: textDirection);
canvas.drawRect(rect, paint); canvas.drawRect(rect, paint);
} }
......
...@@ -343,13 +343,13 @@ class _RenderCupertinoSlider extends RenderConstrainedBox { ...@@ -343,13 +343,13 @@ class _RenderCupertinoSlider extends RenderConstrainedBox {
switch (textDirection) { switch (textDirection) {
case TextDirection.rtl: case TextDirection.rtl:
visualPosition = 1.0 - _position.value; visualPosition = 1.0 - _position.value;
leftColor = _kTrackColor; leftColor = _activeColor;
rightColor = _activeColor; rightColor = _kTrackColor;
break; break;
case TextDirection.ltr: case TextDirection.ltr:
visualPosition = _position.value; visualPosition = _position.value;
leftColor = _activeColor; leftColor = _kTrackColor;
rightColor = _kTrackColor; rightColor = _activeColor;
break; break;
} }
......
...@@ -101,6 +101,12 @@ class _AnimatedState extends State<AnimatedWidget> { ...@@ -101,6 +101,12 @@ class _AnimatedState extends State<AnimatedWidget> {
/// The translation is expressed as a [Offset] scaled to the child's size. For /// The translation is expressed as a [Offset] scaled to the child's size. For
/// example, an [Offset] with a `dx` of 0.25 will result in a horizontal /// example, an [Offset] with a `dx` of 0.25 will result in a horizontal
/// translation of one quarter the width of the child. /// translation of one quarter the width of the child.
///
/// By default, the offsets are applied in the coordinate system of the canvas
/// (so positive x offsets move the child towards the right). If a
/// [textDirection] is provided, then the offsets are applied in the reading
/// direction, so in right-to-left text, positive x offsets move towards the
/// left, and in left-to-right text, positive x offsets move towards the right.
class SlideTransition extends AnimatedWidget { class SlideTransition extends AnimatedWidget {
/// Creates a fractional translation transition. /// Creates a fractional translation transition.
/// ///
...@@ -109,6 +115,7 @@ class SlideTransition extends AnimatedWidget { ...@@ -109,6 +115,7 @@ class SlideTransition extends AnimatedWidget {
Key key, Key key,
@required Animation<Offset> position, @required Animation<Offset> position,
this.transformHitTests: true, this.transformHitTests: true,
this.textDirection,
this.child, this.child,
}) : assert(position != null), }) : assert(position != null),
super(key: key, listenable: position); super(key: key, listenable: position);
...@@ -117,9 +124,22 @@ class SlideTransition extends AnimatedWidget { ...@@ -117,9 +124,22 @@ class SlideTransition extends AnimatedWidget {
/// ///
/// If the current value of the position animation is `(dx, dy)`, the child /// If the current value of the position animation is `(dx, dy)`, the child
/// will be translated horizontally by `width * dx` and vertically by /// will be translated horizontally by `width * dx` and vertically by
/// `height * dy`. /// `height * dy`, after applying the [textDirection] if available.
Animation<Offset> get position => listenable; Animation<Offset> get position => listenable;
/// The direction to use for the x offset described by the [position].
///
/// If [textDirection] is null, the x offset is applied in the coordinate
/// system of the canvas (so positive x offsets move the child towards the
/// right).
///
/// If [textDirection] is [TextDirection.rtl], the x offset is applied in the
/// reading direction such that x offsets move the child towards the left.
///
/// If [textDirection] is [TextDirection.ltr], the x offset is applied in the
/// reading direction such that x offsets move the child towards the right.
final TextDirection textDirection;
/// Whether hit testing should be affected by the slide animation. /// Whether hit testing should be affected by the slide animation.
/// ///
/// If false, hit testing will proceed as if the child was not translated at /// If false, hit testing will proceed as if the child was not translated at
...@@ -133,8 +153,11 @@ class SlideTransition extends AnimatedWidget { ...@@ -133,8 +153,11 @@ class SlideTransition extends AnimatedWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Offset offset = position.value;
if (textDirection == TextDirection.rtl)
offset = new Offset(-offset.dx, offset.dy);
return new FractionalTranslation( return new FractionalTranslation(
translation: position.value, translation: offset,
transformHitTests: transformHitTests, transformHitTests: transformHitTests,
child: child, child: child,
); );
......
...@@ -6,7 +6,7 @@ import 'package:flutter/cupertino.dart'; ...@@ -6,7 +6,7 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
void main() { void main() {
testWidgets('test iOS page transition', (WidgetTester tester) async { testWidgets('test iOS page transition (LTR)', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
new WidgetsApp( new WidgetsApp(
color: const Color(0xFFFFFFFF), color: const Color(0xFFFFFFFF),
...@@ -74,6 +74,78 @@ void main() { ...@@ -74,6 +74,78 @@ void main() {
expect(widget1InitialTopLeft, equals(widget1TransientTopLeft)); expect(widget1InitialTopLeft, equals(widget1TransientTopLeft));
}); });
testWidgets('test iOS page transition (RTL)', (WidgetTester tester) async {
await tester.pumpWidget(
new WidgetsApp(
localizationsDelegates: <LocalizationsDelegate<dynamic>>[
const RtlOverrideWidgetsDelegate(),
],
color: const Color(0xFFFFFFFF),
onGenerateRoute: (RouteSettings settings) {
return new CupertinoPageRoute<Null>(
settings: settings,
builder: (BuildContext context) {
final String pageNumber = settings.name == '/' ? '1' : '2';
return new Center(child: new Text('Page $pageNumber'));
}
);
},
),
);
await tester.pump(); // to load the localization, since it doesn't use a synchronous future
final Offset widget1InitialTopLeft = tester.getTopLeft(find.text('Page 1'));
tester.state<NavigatorState>(find.byType(Navigator)).pushNamed('/next');
await tester.pump();
await tester.pump(const Duration(milliseconds: 150));
Offset widget1TransientTopLeft = tester.getTopLeft(find.text('Page 1'));
Offset widget2TopLeft = tester.getTopLeft(find.text('Page 2'));
// Page 1 is moving to the right.
expect(widget1TransientTopLeft.dx, greaterThan(widget1InitialTopLeft.dx));
// Page 1 isn't moving vertically.
expect(widget1TransientTopLeft.dy, equals(widget1InitialTopLeft.dy));
// iOS transition is horizontal only.
expect(widget1InitialTopLeft.dy, equals(widget2TopLeft.dy));
// Page 2 is coming in from the left.
expect(widget2TopLeft.dx, lessThan(widget1InitialTopLeft.dx));
await tester.pumpAndSettle();
// Page 2 covers page 1.
expect(find.text('Page 1'), findsNothing);
expect(find.text('Page 2'), isOnstage);
tester.state<NavigatorState>(find.byType(Navigator)).pop();
await tester.pump();
await tester.pump(const Duration(milliseconds: 100));
widget1TransientTopLeft = tester.getTopLeft(find.text('Page 1'));
widget2TopLeft = tester.getTopLeft(find.text('Page 2'));
// Page 1 is coming back from the right.
expect(widget1TransientTopLeft.dx, greaterThan(widget1InitialTopLeft.dx));
// Page 1 isn't moving vertically.
expect(widget1TransientTopLeft.dy, equals(widget1InitialTopLeft.dy));
// iOS transition is horizontal only.
expect(widget1InitialTopLeft.dy, equals(widget2TopLeft.dy));
// Page 2 is leaving towards the left.
expect(widget2TopLeft.dx, lessThan(widget1InitialTopLeft.dx));
await tester.pumpAndSettle();
expect(find.text('Page 1'), isOnstage);
expect(find.text('Page 2'), findsNothing);
widget1TransientTopLeft = tester.getTopLeft(find.text('Page 1'));
// Page 1 is back where it started.
expect(widget1InitialTopLeft, equals(widget1TransientTopLeft));
});
testWidgets('test iOS fullscreen dialog transition', (WidgetTester tester) async { testWidgets('test iOS fullscreen dialog transition', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
new WidgetsApp( new WidgetsApp(
...@@ -142,7 +214,7 @@ void main() { ...@@ -142,7 +214,7 @@ void main() {
expect(widget1InitialTopLeft, equals(widget1TransientTopLeft)); expect(widget1InitialTopLeft, equals(widget1TransientTopLeft));
}); });
testWidgets('test only edge swipes work', (WidgetTester tester) async { testWidgets('test only edge swipes work (LTR)', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
new WidgetsApp( new WidgetsApp(
color: const Color(0xFFFFFFFF), color: const Color(0xFFFFFFFF),
...@@ -176,13 +248,113 @@ void main() { ...@@ -176,13 +248,113 @@ void main() {
expect(find.text('Page 1'), findsNothing); expect(find.text('Page 1'), findsNothing);
expect(find.text('Page 2'), isOnstage); expect(find.text('Page 2'), isOnstage);
// Now drag from the edge. // Drag from the right to the left.
gesture = await tester.startGesture(const Offset(795.0, 200.0));
await gesture.moveBy(const Offset(-300.0, 0.0));
await tester.pump();
// Nothing should happen.
expect(find.text('Page 1'), findsNothing);
expect(find.text('Page 2'), isOnstage);
// Drag from the right to the further right.
gesture = await tester.startGesture(const Offset(795.0, 200.0));
await gesture.moveBy(const Offset(300.0, 0.0));
await tester.pump();
// Nothing should happen.
expect(find.text('Page 1'), findsNothing);
expect(find.text('Page 2'), isOnstage);
// Now drag from the left edge.
gesture = await tester.startGesture(const Offset(5.0, 200.0));
await gesture.moveBy(const Offset(300.0, 0.0));
await tester.pump();
// Page 1 is now visible.
expect(find.text('Page 1'), isOnstage);
expect(find.text('Page 2'), isOnstage);
});
testWidgets('test only edge swipes work (RTL)', (WidgetTester tester) async {
await tester.pumpWidget(
new WidgetsApp(
localizationsDelegates: <LocalizationsDelegate<dynamic>>[
const RtlOverrideWidgetsDelegate(),
],
color: const Color(0xFFFFFFFF),
onGenerateRoute: (RouteSettings settings) {
return new CupertinoPageRoute<Null>(
settings: settings,
builder: (BuildContext context) {
final String pageNumber = settings.name == '/' ? '1' : '2';
return new Center(child: new Text('Page $pageNumber'));
}
);
},
),
);
await tester.pump(); // to load the localization, since it doesn't use a synchronous future
tester.state<NavigatorState>(find.byType(Navigator)).pushNamed('/next');
await tester.pump();
await tester.pump(const Duration(milliseconds: 400));
// Page 2 covers page 1.
expect(find.text('Page 1'), findsNothing);
expect(find.text('Page 2'), isOnstage);
// Drag from the middle to the left.
TestGesture gesture = await tester.startGesture(const Offset(200.0, 200.0));
await gesture.moveBy(const Offset(-300.0, 0.0));
await tester.pump();
// Nothing should happen.
expect(find.text('Page 1'), findsNothing);
expect(find.text('Page 2'), isOnstage);
// Drag from the left to the right.
gesture = await tester.startGesture(const Offset(5.0, 200.0)); gesture = await tester.startGesture(const Offset(5.0, 200.0));
await gesture.moveBy(const Offset(300.0, 0.0)); await gesture.moveBy(const Offset(300.0, 0.0));
await tester.pump(); await tester.pump();
// Nothing should happen.
expect(find.text('Page 1'), findsNothing);
expect(find.text('Page 2'), isOnstage);
// Drag from the left to the further left.
gesture = await tester.startGesture(const Offset(5.0, 200.0));
await gesture.moveBy(const Offset(-300.0, 0.0));
await tester.pump();
// Nothing should happen.
expect(find.text('Page 1'), findsNothing);
expect(find.text('Page 2'), isOnstage);
// Now drag from the right edge.
gesture = await tester.startGesture(const Offset(795.0, 200.0));
await gesture.moveBy(const Offset(-300.0, 0.0));
await tester.pump();
// Page 1 is now visible. // Page 1 is now visible.
expect(find.text('Page 1'), isOnstage); expect(find.text('Page 1'), isOnstage);
expect(find.text('Page 2'), isOnstage); expect(find.text('Page 2'), isOnstage);
}); });
} }
class RtlOverrideWidgetsDelegate extends LocalizationsDelegate<WidgetsLocalizations> {
const RtlOverrideWidgetsDelegate();
@override
bool isSupported(Locale locale) => true;
@override
Future<WidgetsLocalizations> load(Locale locale) async => const RtlOverrideWidgetsLocalization();
@override
bool shouldReload(LocalizationsDelegate<WidgetsLocalizations> oldDelegate) => false;
}
class RtlOverrideWidgetsLocalization implements WidgetsLocalizations {
const RtlOverrideWidgetsLocalization();
@override
TextDirection get textDirection => TextDirection.rtl;
}
\ No newline at end of file
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