mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Make Cupertino page transitions match native behaviours (#9138)
* Moved stuff around yet * Fix depedencies * Add more dartdoc comments to packages * Remove Cupertino dependency on material * Removed mountain_view package and added page transition test * Fix analyze warnings * Remove commented code * Partial solution. Still need to stop the animation on the previous page for modal * Some review notes * Move the cupertino back gesture controller’s lifecycle management back to its parent * Reviews * Add background color * Directional curves, full screen transition * Don’t perform the exit animation if the incoming page is a dialog * It works! * Test structures * Add a bunch of more tests and fix the gallery * One more comment * Review notes * final controller * Use that sweet sweet `is!` keyword * Play golf, because I’m bitter that there’s no nullable `as` or something in dart * Remove a space * Review notes * Remove the last deprecated test
This commit is contained in:
parent
c7f98efb63
commit
58ddde88f4
@ -192,7 +192,8 @@ class DialogDemoState extends State<DialogDemo> {
|
||||
child: new Text('FULLSCREEN'),
|
||||
onPressed: () {
|
||||
Navigator.push(context, new MaterialPageRoute<DismissDialogAction>(
|
||||
builder: (BuildContext context) => new FullScreenDialogDemo()
|
||||
builder: (BuildContext context) => new FullScreenDialogDemo(),
|
||||
fullscreenDialog: true,
|
||||
));
|
||||
}
|
||||
),
|
||||
|
||||
@ -8,6 +8,18 @@ import 'package:flutter/widgets.dart';
|
||||
const double _kMinFlingVelocity = 1.0; // screen width per second.
|
||||
const Color _kBackgroundColor = const Color(0xFFEFEFF4); // iOS 10 background color.
|
||||
|
||||
// Fractional offset from offscreen to the right to fully on screen.
|
||||
final FractionalOffsetTween _kRightMiddleTween = new FractionalOffsetTween(
|
||||
begin: FractionalOffset.topRight,
|
||||
end: FractionalOffset.topLeft,
|
||||
);
|
||||
|
||||
// Fractional offset from fully on screen to 1/3 offscreen to the left.
|
||||
final FractionalOffsetTween _kMiddleLeftTween = new FractionalOffsetTween(
|
||||
begin: FractionalOffset.topLeft,
|
||||
end: const FractionalOffset(-1.0/3.0, 0.0),
|
||||
);
|
||||
|
||||
/// Provides the native iOS page transition animation.
|
||||
///
|
||||
/// Takes in a page widget and a route animation from a [TransitionRoute] and produces an
|
||||
@ -18,21 +30,39 @@ const Color _kBackgroundColor = const Color(0xFFEFEFF4); // iOS 10 background co
|
||||
class CupertinoPageTransition extends AnimatedWidget {
|
||||
CupertinoPageTransition({
|
||||
Key key,
|
||||
@required Animation<double> animation,
|
||||
// Linear route animation from 0.0 to 1.0 when this screen is being pushed.
|
||||
@required Animation<double> incomingRouteAnimation,
|
||||
// Linear route animation from 0.0 to 1.0 when another screen is being pushed on top of this
|
||||
// one.
|
||||
@required Animation<double> outgoingRouteAnimation,
|
||||
@required this.child,
|
||||
}) : super(
|
||||
key: key,
|
||||
listenable: _kTween.animate(new CurvedAnimation(
|
||||
parent: animation,
|
||||
curve: new _CupertinoTransitionCurve(null),
|
||||
),
|
||||
));
|
||||
|
||||
static final FractionalOffsetTween _kTween = new FractionalOffsetTween(
|
||||
begin: FractionalOffset.topRight,
|
||||
end: -FractionalOffset.topRight,
|
||||
);
|
||||
}) :
|
||||
incomingPositionAnimation = _kRightMiddleTween.animate(
|
||||
new CurvedAnimation(
|
||||
parent: incomingRouteAnimation,
|
||||
curve: Curves.easeOut,
|
||||
reverseCurve: Curves.easeIn,
|
||||
)
|
||||
),
|
||||
outgoingPositionAnimation = _kMiddleLeftTween.animate(
|
||||
new CurvedAnimation(
|
||||
parent: outgoingRouteAnimation,
|
||||
curve: Curves.easeOut,
|
||||
reverseCurve: Curves.easeIn,
|
||||
)
|
||||
),
|
||||
super(
|
||||
key: key,
|
||||
// Trigger a rebuild whenever any of the 2 animation route happens.
|
||||
listenable: new Listenable.merge(
|
||||
<Listenable>[incomingRouteAnimation, outgoingRouteAnimation]
|
||||
),
|
||||
);
|
||||
|
||||
// When this page is coming in to cover another page.
|
||||
final Animation<FractionalOffset> incomingPositionAnimation;
|
||||
// When this page is becoming covered by another page.
|
||||
final Animation<FractionalOffset> outgoingPositionAnimation;
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
@ -40,45 +70,50 @@ class CupertinoPageTransition extends AnimatedWidget {
|
||||
// TODO(ianh): tell the transform to be un-transformed for hit testing
|
||||
// but not while being controlled by a gesture.
|
||||
return new SlideTransition(
|
||||
position: listenable,
|
||||
child: new PhysicalModel(
|
||||
shape: BoxShape.rectangle,
|
||||
color: _kBackgroundColor,
|
||||
elevation: 16,
|
||||
child: child,
|
||||
)
|
||||
position: outgoingPositionAnimation,
|
||||
child: new SlideTransition(
|
||||
position: incomingPositionAnimation,
|
||||
child: new PhysicalModel(
|
||||
shape: BoxShape.rectangle,
|
||||
color: _kBackgroundColor,
|
||||
elevation: 32,
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Custom curve for iOS page transitions.
|
||||
class _CupertinoTransitionCurve extends Curve {
|
||||
_CupertinoTransitionCurve(this.curve);
|
||||
/// Transitions used for summoning fullscreen dialogs in iOS such as creating a new
|
||||
/// calendar event etc by bringing in the next screen from the bottom.
|
||||
class CupertinoFullscreenDialogTransition extends AnimatedWidget {
|
||||
CupertinoFullscreenDialogTransition({
|
||||
Key key,
|
||||
@required Animation<double> animation,
|
||||
@required this.child,
|
||||
}) : super(
|
||||
key: key,
|
||||
listenable: _kBottomUpTween.animate(
|
||||
new CurvedAnimation(
|
||||
parent: animation,
|
||||
curve: Curves.easeInOut,
|
||||
)
|
||||
),
|
||||
);
|
||||
|
||||
final Curve curve;
|
||||
static final FractionalOffsetTween _kBottomUpTween = new FractionalOffsetTween(
|
||||
begin: FractionalOffset.bottomLeft,
|
||||
end: FractionalOffset.topLeft,
|
||||
);
|
||||
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
double transform(double t) {
|
||||
// The input [t] is the average of the current and next route's animation.
|
||||
// This means t=0.5 represents when the route is fully onscreen. At
|
||||
// t > 0.5, it is partially offscreen to the left (which happens when there
|
||||
// is another route on top). At t < 0.5, the route is to the right.
|
||||
// We divide the range into two halves, each with a different transition,
|
||||
// and scale each half to the range [0.0, 1.0] before applying curves so that
|
||||
// each half goes through the full range of the curve.
|
||||
if (t > 0.5) {
|
||||
// Route is to the left of center.
|
||||
t = (t - 0.5) * 2.0;
|
||||
if (curve != null)
|
||||
t = curve.transform(t);
|
||||
t = t / 3.0;
|
||||
t = t / 2.0 + 0.5;
|
||||
} else {
|
||||
// Route is to the right of center.
|
||||
if (curve != null)
|
||||
t = curve.transform(t * 2.0) / 2.0;
|
||||
}
|
||||
return t;
|
||||
Widget build(BuildContext context) {
|
||||
return new SlideTransition(
|
||||
position: listenable,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -96,7 +131,7 @@ class CupertinoBackGestureController extends NavigationGestureController {
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
controller.removeStatusListener(handleStatusChanged);
|
||||
controller.removeStatusListener(_handleStatusChanged);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@ -130,13 +165,13 @@ class CupertinoBackGestureController extends NavigationGestureController {
|
||||
|
||||
// Don't end the gesture until the transition completes.
|
||||
final AnimationStatus status = controller.status;
|
||||
handleStatusChanged(status);
|
||||
controller?.addStatusListener(handleStatusChanged);
|
||||
_handleStatusChanged(status);
|
||||
controller?.addStatusListener(_handleStatusChanged);
|
||||
|
||||
return (status == AnimationStatus.reverse || status == AnimationStatus.dismissed);
|
||||
}
|
||||
|
||||
void handleStatusChanged(AnimationStatus status) {
|
||||
void _handleStatusChanged(AnimationStatus status) {
|
||||
if (status == AnimationStatus.dismissed)
|
||||
navigator.pop();
|
||||
}
|
||||
|
||||
@ -58,12 +58,16 @@ class _MountainViewPageTransition extends AnimatedWidget {
|
||||
/// By default, when a modal route is replaced by another, the previous route
|
||||
/// remains in memory. To free all the resources when this is not necessary, set
|
||||
/// [maintainState] to false.
|
||||
///
|
||||
/// Specify whether the incoming page is a fullscreen modal dialog. On iOS, those
|
||||
/// pages animate bottom->up rather than right->left.
|
||||
class MaterialPageRoute<T> extends PageRoute<T> {
|
||||
/// Creates a page route for use in a material design app.
|
||||
MaterialPageRoute({
|
||||
@required this.builder,
|
||||
RouteSettings settings: const RouteSettings(),
|
||||
this.maintainState: true,
|
||||
this.fullscreenDialog: false,
|
||||
}) : super(settings: settings) {
|
||||
assert(builder != null);
|
||||
assert(opaque);
|
||||
@ -71,6 +75,7 @@ class MaterialPageRoute<T> extends PageRoute<T> {
|
||||
|
||||
/// Builds the primary contents of the route.
|
||||
final WidgetBuilder builder;
|
||||
final bool fullscreenDialog;
|
||||
|
||||
@override
|
||||
final bool maintainState;
|
||||
@ -86,6 +91,12 @@ class MaterialPageRoute<T> extends PageRoute<T> {
|
||||
return nextRoute is MaterialPageRoute<dynamic>;
|
||||
}
|
||||
|
||||
@override
|
||||
bool canTransitionTo(TransitionRoute<dynamic> nextRoute) {
|
||||
// Don't perform outgoing animation if the next route is a fullscreen dialog.
|
||||
return nextRoute is MaterialPageRoute && !nextRoute.fullscreenDialog;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_backGestureController?.dispose();
|
||||
@ -109,6 +120,9 @@ class MaterialPageRoute<T> extends PageRoute<T> {
|
||||
// allow the user to dismiss the route with a swipe.
|
||||
if (hasScopedWillPopCallback)
|
||||
return null;
|
||||
// Fullscreen dialogs aren't dismissable by back swipe.
|
||||
if (fullscreenDialog)
|
||||
return null;
|
||||
if (controller.status != AnimationStatus.completed)
|
||||
return null;
|
||||
assert(_backGestureController == null);
|
||||
@ -146,12 +160,18 @@ class MaterialPageRoute<T> extends PageRoute<T> {
|
||||
|
||||
@override
|
||||
Widget buildTransitions(BuildContext context, Animation<double> animation, Animation<double> forwardAnimation, Widget child) {
|
||||
if (Theme.of(context).platform == TargetPlatform.iOS &&
|
||||
Navigator.of(context).userGestureInProgress) {
|
||||
return new CupertinoPageTransition(
|
||||
animation: new AnimationMean(left: animation, right: forwardAnimation),
|
||||
child: child
|
||||
);
|
||||
if (Theme.of(context).platform == TargetPlatform.iOS) {
|
||||
if (fullscreenDialog)
|
||||
return new CupertinoFullscreenDialogTransition(
|
||||
animation: animation,
|
||||
child: child,
|
||||
);
|
||||
else
|
||||
return new CupertinoPageTransition(
|
||||
incomingRouteAnimation: animation,
|
||||
outgoingRouteAnimation: forwardAnimation,
|
||||
child: child,
|
||||
);
|
||||
} else {
|
||||
return new _MountainViewPageTransition(
|
||||
routeAnimation: animation,
|
||||
|
||||
@ -15,7 +15,7 @@ void main() {
|
||||
'/next': (BuildContext context) {
|
||||
return new Material(child: new Text('Page 2'));
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
);
|
||||
|
||||
@ -25,11 +25,11 @@ void main() {
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 1));
|
||||
|
||||
final Opacity widget2Opacity =
|
||||
tester.element(find.text('Page 2')).ancestorWidgetOfExactType(Opacity);
|
||||
final Point widget2TopLeft = tester.getTopLeft(find.text('Page 2'));
|
||||
Opacity widget2Opacity = tester.element(find.text('Page 2')).ancestorWidgetOfExactType(Opacity);
|
||||
Point widget2TopLeft = tester.getTopLeft(find.text('Page 2'));
|
||||
final Size widget2Size = tester.getSize(find.text('Page 2'));
|
||||
|
||||
// Android transition is vertical only.
|
||||
expect(widget1TopLeft.x == widget2TopLeft.x, true);
|
||||
// Page 1 is above page 2 mid-transition.
|
||||
expect(widget1TopLeft.y < widget2TopLeft.y, true);
|
||||
@ -37,6 +37,29 @@ void main() {
|
||||
expect(widget2TopLeft.y < widget2Size.height / 4.0, true);
|
||||
// Animation starts with page 2 being near transparent.
|
||||
expect(widget2Opacity.opacity < 0.01, true);
|
||||
|
||||
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: 1));
|
||||
|
||||
widget2Opacity = tester.element(find.text('Page 2')).ancestorWidgetOfExactType(Opacity);
|
||||
widget2TopLeft = tester.getTopLeft(find.text('Page 2'));
|
||||
|
||||
// Page 2 starts to move down.
|
||||
expect(widget1TopLeft.y < widget2TopLeft.y, true);
|
||||
// Page 2 starts to lose opacity.
|
||||
expect(widget2Opacity.opacity < 1.0, true);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Page 1'), isOnstage);
|
||||
expect(find.text('Page 2'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('test iOS page transition', (WidgetTester tester) async {
|
||||
@ -48,21 +71,210 @@ void main() {
|
||||
'/next': (BuildContext context) {
|
||||
return new Material(child: new Text('Page 2'));
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
);
|
||||
|
||||
final Point widget1TopLeft = tester.getTopLeft(find.text('Page 1'));
|
||||
final Point widget1InitialTopLeft = tester.getTopLeft(find.text('Page 1'));
|
||||
|
||||
tester.state<NavigatorState>(find.byType(Navigator)).pushNamed('/next');
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 250));
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
|
||||
final Point widget2TopLeft = tester.getTopLeft(find.text('Page 2'));
|
||||
Point widget1TransientTopLeft = tester.getTopLeft(find.text('Page 1'));
|
||||
Point widget2TopLeft = tester.getTopLeft(find.text('Page 2'));
|
||||
|
||||
// This is currently an incorrect behaviour and we want right to left transition instead.
|
||||
// See https://github.com/flutter/flutter/issues/8726.
|
||||
expect(widget1TopLeft.x == widget2TopLeft.x, true);
|
||||
expect(widget1TopLeft.y - widget2TopLeft.y < 0, true);
|
||||
// Page 1 is moving to the left.
|
||||
expect(widget1TransientTopLeft.x < widget1InitialTopLeft.x, true);
|
||||
// Page 1 isn't moving vertically.
|
||||
expect(widget1TransientTopLeft.y == widget1InitialTopLeft.y, true);
|
||||
// iOS transition is horizontal only.
|
||||
expect(widget1InitialTopLeft.y == widget2TopLeft.y, true);
|
||||
// Page 2 is coming in from the right.
|
||||
expect(widget2TopLeft.x > widget1InitialTopLeft.x, true);
|
||||
|
||||
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 left.
|
||||
expect(widget1TransientTopLeft.x < widget1InitialTopLeft.x, true);
|
||||
// Page 1 isn't moving vertically.
|
||||
expect(widget1TransientTopLeft.y == widget1InitialTopLeft.y, true);
|
||||
// iOS transition is horizontal only.
|
||||
expect(widget1InitialTopLeft.y == widget2TopLeft.y, true);
|
||||
// Page 2 is leaving towards the right.
|
||||
expect(widget2TopLeft.x > widget1InitialTopLeft.x, true);
|
||||
|
||||
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 == widget1TransientTopLeft, true);
|
||||
});
|
||||
|
||||
testWidgets('test iOS fullscreen dialog transition', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
new MaterialApp(
|
||||
theme: new ThemeData(platform: TargetPlatform.iOS),
|
||||
home: new Material(child: new Text('Page 1')),
|
||||
)
|
||||
);
|
||||
|
||||
final Point widget1InitialTopLeft = tester.getTopLeft(find.text('Page 1'));
|
||||
|
||||
tester.state<NavigatorState>(find.byType(Navigator)).push(new MaterialPageRoute<Null>(
|
||||
builder: (BuildContext context) {
|
||||
return new Material(child: new Text('Page 2'));
|
||||
},
|
||||
fullscreenDialog: true,
|
||||
));
|
||||
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
|
||||
Point widget1TransientTopLeft = tester.getTopLeft(find.text('Page 1'));
|
||||
Point widget2TopLeft = tester.getTopLeft(find.text('Page 2'));
|
||||
|
||||
// Page 1 doesn't move.
|
||||
expect(widget1TransientTopLeft == widget1InitialTopLeft, true);
|
||||
// Fullscreen dialogs transitions vertically only.
|
||||
expect(widget1InitialTopLeft.x == widget2TopLeft.x, true);
|
||||
// Page 2 is coming in from the bottom.
|
||||
expect(widget2TopLeft.y > widget1InitialTopLeft.y, true);
|
||||
|
||||
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 doesn't move.
|
||||
expect(widget1TransientTopLeft == widget1InitialTopLeft, true);
|
||||
// Fullscreen dialogs transitions vertically only.
|
||||
expect(widget1InitialTopLeft.x == widget2TopLeft.x, true);
|
||||
// Page 2 is leaving towards the bottom.
|
||||
expect(widget2TopLeft.y > widget1InitialTopLeft.y, true);
|
||||
|
||||
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 == widget1TransientTopLeft, true);
|
||||
});
|
||||
|
||||
testWidgets('test no back gesture on Android', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
new MaterialApp(
|
||||
theme: new ThemeData(platform: TargetPlatform.android),
|
||||
home: new Scaffold(body: new Text('Page 1')),
|
||||
routes: <String, WidgetBuilder>{
|
||||
'/next': (BuildContext context) {
|
||||
return new Scaffold(body: new Text('Page 2'));
|
||||
},
|
||||
},
|
||||
)
|
||||
);
|
||||
|
||||
tester.state<NavigatorState>(find.byType(Navigator)).pushNamed('/next');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Page 1'), findsNothing);
|
||||
expect(find.text('Page 2'), isOnstage);
|
||||
|
||||
// Drag from left edge to invoke the gesture.
|
||||
final TestGesture gesture = await tester.startGesture(const Point(5.0, 100.0));
|
||||
await gesture.moveBy(const Offset(400.0, 0.0));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Page 1'), findsNothing);
|
||||
expect(find.text('Page 2'), isOnstage);
|
||||
|
||||
// Page 2 didn't move
|
||||
expect(tester.getTopLeft(find.text('Page 2')), Point.origin);
|
||||
});
|
||||
|
||||
testWidgets('test back gesture on iOS', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
new MaterialApp(
|
||||
theme: new ThemeData(platform: TargetPlatform.iOS),
|
||||
home: new Scaffold(body: new Text('Page 1')),
|
||||
routes: <String, WidgetBuilder>{
|
||||
'/next': (BuildContext context) {
|
||||
return new Scaffold(body: new Text('Page 2'));
|
||||
},
|
||||
},
|
||||
)
|
||||
);
|
||||
|
||||
tester.state<NavigatorState>(find.byType(Navigator)).pushNamed('/next');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Page 1'), findsNothing);
|
||||
expect(find.text('Page 2'), isOnstage);
|
||||
|
||||
// Drag from left edge to invoke the gesture.
|
||||
final TestGesture gesture = await tester.startGesture(const Point(5.0, 100.0));
|
||||
await gesture.moveBy(const Offset(400.0, 0.0));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Page 1 is now visible.
|
||||
expect(find.text('Page 1'), isOnstage);
|
||||
expect(find.text('Page 2'), isOnstage);
|
||||
});
|
||||
|
||||
testWidgets('test no back gesture on iOS fullscreen dialogs', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
new MaterialApp(
|
||||
theme: new ThemeData(platform: TargetPlatform.iOS),
|
||||
home: new Scaffold(body: new Text('Page 1')),
|
||||
)
|
||||
);
|
||||
|
||||
tester.state<NavigatorState>(find.byType(Navigator)).push(new MaterialPageRoute<Null>(
|
||||
builder: (BuildContext context) {
|
||||
return new Scaffold(body: new Text('Page 2'));
|
||||
},
|
||||
fullscreenDialog: true,
|
||||
));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Page 1'), findsNothing);
|
||||
expect(find.text('Page 2'), isOnstage);
|
||||
|
||||
// Drag from left edge to invoke the gesture.
|
||||
final TestGesture gesture = await tester.startGesture(const Point(5.0, 100.0));
|
||||
await gesture.moveBy(const Offset(400.0, 0.0));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Page 1'), findsNothing);
|
||||
expect(find.text('Page 2'), isOnstage);
|
||||
|
||||
// Page 2 didn't move
|
||||
expect(tester.getTopLeft(find.text('Page 2')), Point.origin);
|
||||
});
|
||||
}
|
||||
|
||||
@ -128,126 +128,6 @@ void main() {
|
||||
expect(Navigator.canPop(containerKey1.currentContext), isFalse);
|
||||
});
|
||||
|
||||
testWidgets('Check back gesture works on iOS', (WidgetTester tester) async {
|
||||
final GlobalKey containerKey1 = new GlobalKey();
|
||||
final GlobalKey containerKey2 = new GlobalKey();
|
||||
final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
|
||||
'/': (_) => new Scaffold(key: containerKey1, body: new Text('Home')),
|
||||
'/settings': (_) => new Scaffold(key: containerKey2, body: new Text('Settings')),
|
||||
};
|
||||
|
||||
await tester.pumpWidget(new MaterialApp(
|
||||
routes: routes,
|
||||
theme: new ThemeData(platform: TargetPlatform.iOS),
|
||||
));
|
||||
|
||||
Navigator.pushNamed(containerKey1.currentContext, '/settings');
|
||||
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
|
||||
expect(find.text('Home'), findsNothing);
|
||||
expect(find.text('Settings'), isOnstage);
|
||||
|
||||
// Drag from left edge to invoke the gesture.
|
||||
final TestGesture gesture = await tester.startGesture(const Point(5.0, 100.0));
|
||||
await gesture.moveBy(const Offset(50.0, 0.0));
|
||||
await tester.pump();
|
||||
|
||||
// Home is now visible.
|
||||
expect(find.text('Home'), isOnstage);
|
||||
expect(find.text('Settings'), isOnstage);
|
||||
});
|
||||
|
||||
testWidgets('Check back gesture does nothing on android', (WidgetTester tester) async {
|
||||
final GlobalKey containerKey1 = new GlobalKey();
|
||||
final GlobalKey containerKey2 = new GlobalKey();
|
||||
final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
|
||||
'/': (_) => new Scaffold(key: containerKey1, body: new Text('Home')),
|
||||
'/settings': (_) => new Scaffold(key: containerKey2, body: new Text('Settings')),
|
||||
};
|
||||
|
||||
await tester.pumpWidget(new MaterialApp(
|
||||
routes: routes,
|
||||
theme: new ThemeData(platform: TargetPlatform.android),
|
||||
));
|
||||
|
||||
Navigator.pushNamed(containerKey1.currentContext, '/settings');
|
||||
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
|
||||
expect(find.text('Home'), findsNothing);
|
||||
expect(find.text('Settings'), isOnstage);
|
||||
|
||||
// Drag from left edge to invoke the gesture.
|
||||
final TestGesture gesture = await tester.startGesture(const Point(5.0, 100.0));
|
||||
await gesture.moveBy(const Offset(50.0, 0.0));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text('Home'), findsNothing);
|
||||
expect(find.text('Settings'), isOnstage);
|
||||
});
|
||||
|
||||
testWidgets('Check page transition positioning on iOS', (WidgetTester tester) async {
|
||||
final GlobalKey containerKey1 = new GlobalKey();
|
||||
final GlobalKey containerKey2 = new GlobalKey();
|
||||
final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
|
||||
'/': (_) => new Scaffold(key: containerKey1, body: new Text('Home')),
|
||||
'/settings': (_) => new Scaffold(key: containerKey2, body: new Text('Settings')),
|
||||
};
|
||||
|
||||
await tester.pumpWidget(new MaterialApp(
|
||||
routes: routes,
|
||||
theme: new ThemeData(platform: TargetPlatform.iOS),
|
||||
));
|
||||
|
||||
Navigator.pushNamed(containerKey1.currentContext, '/settings');
|
||||
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 16));
|
||||
|
||||
expect(find.text('Home'), isOnstage);
|
||||
expect(find.text('Settings'), isOnstage);
|
||||
|
||||
// Home page is staying in place.
|
||||
Point homeOffset = tester.getTopLeft(find.text('Home'));
|
||||
expect(homeOffset.x, 0.0);
|
||||
expect(homeOffset.y, 0.0);
|
||||
|
||||
// Settings page is sliding up from the bottom.
|
||||
Point settingsOffset = tester.getTopLeft(find.text('Settings'));
|
||||
expect(settingsOffset.x, 0.0);
|
||||
expect(settingsOffset.y, greaterThan(0.0));
|
||||
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
|
||||
expect(find.text('Home'), findsNothing);
|
||||
expect(find.text('Settings'), isOnstage);
|
||||
|
||||
// Settings page is in position.
|
||||
settingsOffset = tester.getTopLeft(find.text('Settings'));
|
||||
expect(settingsOffset.x, 0.0);
|
||||
expect(settingsOffset.y, 0.0);
|
||||
|
||||
Navigator.pop(containerKey1.currentContext);
|
||||
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 16));
|
||||
|
||||
// Home page is staying in place.
|
||||
homeOffset = tester.getTopLeft(find.text('Home'));
|
||||
expect(homeOffset.x, 0.0);
|
||||
expect(homeOffset.y, 0.0);
|
||||
|
||||
// Settings page is sliding down off the bottom.
|
||||
settingsOffset = tester.getTopLeft(find.text('Settings'));
|
||||
expect(settingsOffset.x, 0.0);
|
||||
expect(settingsOffset.y, greaterThan(0.0));
|
||||
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
});
|
||||
|
||||
testWidgets('Check back gesture disables Heroes', (WidgetTester tester) async {
|
||||
final GlobalKey containerKey1 = new GlobalKey();
|
||||
final GlobalKey containerKey2 = new GlobalKey();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user