-
Notifications
You must be signed in to change notification settings - Fork 27.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make sure everything in the Cupertino page transition can be linear when back swiping #28629
Conversation
@HansMuller want to get your feedback before I add the tests |
@@ -188,12 +188,21 @@ class CupertinoPageRoute<T> extends PageRoute<T> { | |||
|
|||
/// True if a Cupertino pop gesture is currently underway for [route]. | |||
/// | |||
/// This returns true for both the top route and the bottom route of a |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm just parachuting into this but ... it's not clear what a "Cupertino pop gesture" is. I don't see a definition elsewhere.
I assume that you just mean that this route is in the middle of running its animation as a result of being popped off the navigator's stack, or its in the middle of running its "secondary" animation because being exposed because the route above it was popped.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is still confusing since exactly what "pop gesture" means isn't self evident. Since we're just returning the value of route.navigator.userGestureInProgress
you could just say as much and provide an explanation there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
static Animation<double> _animationControllerForTransitionAnimation(Animation<double> animation) { | ||
Animation<double> controller = animation; | ||
while(controller is ProxyAnimation || controller is TrainHoppingAnimation) { | ||
final Animation<double> proxy = controller; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This works because we believe that the implementation only creates these two animation types? If someone changed the implementation and started using FooAnimationWrapper or, worse still _FooAnimationWrapper, we'd just silently fail, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Took a different approach
static final Set<PageRoute<dynamic>> _popGestureInProgress = Set<PageRoute<dynamic>>(); | ||
static bool isPopGestureInProgress(PageRoute<dynamic> route) { | ||
return _popGestureInProgress.contains(_animationControllerForTransitionAnimation(route.animation)) | ||
|| _popGestureInProgress.contains(_animationControllerForTransitionAnimation(route.secondaryAnimation)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure why _animationControllerForTransitionAnimation will always return route.secondaryAnimation if a route is using it. It seems like it would in simple cases, but all cases?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Took a different approach
aef23e8
to
74658fe
Compare
So I took a different approach and didn't need to add a previous route tracker on the route itself. Navigator has a userGestureInProgress that I can use. Removed a static from the routes class. Added a bunch of tests. Also fixed https://github.com/flutter/flutter/pull/28756/files#r262295964. It diffbases on top of #28855. |
4e0fe10
to
55ea847
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks mostly fine although I think future generations will appreciate a little less lingo.
@@ -188,12 +188,21 @@ class CupertinoPageRoute<T> extends PageRoute<T> { | |||
|
|||
/// True if a Cupertino pop gesture is currently underway for [route]. | |||
/// | |||
/// This returns true for both the top route and the bottom route of a |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is still confusing since exactly what "pop gesture" means isn't self evident. Since we're just returning the value of route.navigator.userGestureInProgress
you could just say as much and provide an explanation there.
@@ -646,9 +658,9 @@ class _CupertinoBackGestureController<T> { | |||
controller.removeStatusListener(_handleStatusChanged); | |||
} | |||
_animating = false; | |||
onEnded(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure why we now want to do this first. Previously it seemed like effectively an else clause. Why would we want to unconditionally dispose the route before removing it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought I wrote it somewhere in the PR but didn't. This is a bug on everything downstream from didStopUserGesture such as NavigatorObserver.didStopUserGesture or NavigatorState.userGestureInProgress. It never worked because the route would be disposed before the back controller would be disposed. We were just lucky that those APIs weren't really used.
Added new tests.
@@ -114,6 +114,12 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> { | |||
AnimationController get controller => _controller; | |||
AnimationController _controller; | |||
|
|||
/// The animation for the route being pushed on top of this route. This | |||
/// animation lets this route coordinate with the entrance and exit transition | |||
/// of routes pushed on top of this route. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
of the route being pushed on top of
Doesn't the secondary animation also apply to the route that's being exposed because the route above it is being popped?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just moved this block from below but all these docs are wrong. Re-editing them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not "routes pushed on top of this route" plural, it's just the one route pushed on top of this one?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, indeed it should be singular
// and then press the button. MaterialPageTransition duration is 300ms, | ||
// 275 = 300 * 0.75. | ||
// Run the dismiss animation 60%, which exposes the route "push" button, | ||
// and then press the button. A drag dropped animation is 400ms when dropped |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is a "drag dropped animation" and where does 400ms come from?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I moved this comment further down
await gesture.up(); | ||
await tester.pump(const Duration(milliseconds: 275)); // partially dismiss "route" | ||
expect(find.text('route'), findsOneWidget); | ||
await tester.pump(); // Trigger the dropped snapping animation. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is a "dropped snapping" animation?
Generally speaking I think it would be helpful to explain this stuff in terms that would make sense to a developer who only knows that it's possible to drag a platform-iOS route from the left edge to initiate a navigator pop.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
); | ||
|
||
tester.state<NavigatorState>(find.byType(Navigator)).push(route2); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be helpful to explain that the numbers below are based on animating a Foo curve over a duration of Bar
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doh, found #29068 as I looked into it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added more comments here
expect(tester.getTopLeft(find.text('2')).dx, moreOrLessEquals(659, epsilon: 1)); | ||
}); | ||
|
||
testWidgets('Dragged pop gesture is linear', (WidgetTester tester) async { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are all of the epsilons needed if we're not curving the animation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just to avoid dealing with all the irrational numbers from dividing by 3. But it's only needed on the bottom route. Tweaking.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good to me. I'll leave the final review to Hans.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry about the delay, LGTM
/// True if a Cupertino pop gesture is currently underway for [route]. | ||
/// True if an iOS-style back swipe pop gesture is currently underway for [route]. | ||
/// | ||
/// This just check for the route's [NavigatorState.userGestureInProgress]. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This just checks the route's ...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
static bool isPopGestureInProgress(PageRoute<dynamic> route) => _popGestureInProgress.contains(route); | ||
static final Set<PageRoute<dynamic>> _popGestureInProgress = <PageRoute<dynamic>>{}; | ||
static bool isPopGestureInProgress(PageRoute<dynamic> route) { | ||
return route.navigator.userGestureInProgress; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm surprised that we need this method, but OK. I like the fact that it's possible to just ask the navigator.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We absolutely don't need this method since it adds more indirection confusion. But we can't break the API.
@@ -114,6 +114,12 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> { | |||
AnimationController get controller => _controller; | |||
AnimationController _controller; | |||
|
|||
/// The animation for the route being pushed on top of this route. This | |||
/// animation lets this route coordinate with the entrance and exit transition | |||
/// of routes pushed on top of this route. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not "routes pushed on top of this route" plural, it's just the one route pushed on top of this one?
Description
Currently, the route transition can become disconnected between the top and the bottom pages of a Cupertino transition because they can follow different curves. They should both be linearToEaseOut if animated but both be linear if driven by a back swipe.
We should be checking for the participating animations for whether they're part of a swipe rather than checking the route.
Related Issues
#28529
Checklist
Before you create this PR confirm that it meets all requirements listed below by checking the relevant checkboxes (
[x]
). This will ensure a smooth and quick review process.///
).flutter analyze --flutter-repo
) does not report any problems on my PR.Breaking Change
Does your PR require Flutter developers to manually update their apps to accommodate your change?