Skip to content
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

Merged
merged 7 commits into from
Mar 29, 2019

Conversation

xster
Copy link
Member

@xster xster commented Feb 28, 2019

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.

  • I read the [Contributor Guide] and followed the process outlined there for submitting PRs.
  • My PR includes tests for all changed/updated/fixed behaviors (See [Test Coverage]).
  • All existing and new tests are passing.
  • I updated/added relevant documentation (doc comments with ///).
  • The analyzer (flutter analyze --flutter-repo) does not report any problems on my PR.
  • I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement].
  • I signed the [CLA].
  • I am willing to follow-up on review comments in a timely manner.

Breaking Change

Does your PR require Flutter developers to manually update their apps to accommodate your change?

  • Yes, this is a breaking change (Please read [Handling breaking changes]).
  • No, this is not a breaking change.

@xster
Copy link
Member Author

xster commented Feb 28, 2019

@HansMuller want to get your feedback before I add the tests

@zoechi zoechi added framework flutter/packages/flutter repository. See also f: labels. f: cupertino flutter/packages/flutter/cupertino repository f: routes Navigator, Router, and related APIs. labels Feb 28, 2019
@@ -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
Copy link
Contributor

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.

Copy link
Contributor

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.

Copy link
Member Author

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;
Copy link
Contributor

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?

Copy link
Member Author

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));
Copy link
Contributor

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?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Took a different approach

@xster
Copy link
Member Author

xster commented Mar 5, 2019

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.

Copy link
Contributor

@HansMuller HansMuller left a 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
Copy link
Contributor

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();
Copy link
Contributor

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?

Copy link
Member Author

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.
Copy link
Contributor

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?

Copy link
Member Author

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.

Copy link
Contributor

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?

Copy link
Member Author

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
Copy link
Contributor

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?

Copy link
Member Author

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.
Copy link
Contributor

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.

Copy link
Member Author

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);

Copy link
Contributor

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

Copy link
Member Author

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

Copy link
Member Author

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 {
Copy link
Contributor

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?

Copy link
Member Author

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.

Copy link
Member

@goderbauer goderbauer left a 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.

Copy link
Contributor

@HansMuller HansMuller left a 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].
Copy link
Contributor

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 ...

Copy link
Member Author

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;
Copy link
Contributor

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.

Copy link
Member Author

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.
Copy link
Contributor

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?

@xster xster merged commit d166a8d into flutter:master Mar 29, 2019
@xster xster deleted the linear-swipe branch March 29, 2019 22:56
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Aug 7, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
f: cupertino flutter/packages/flutter/cupertino repository f: routes Navigator, Router, and related APIs. framework flutter/packages/flutter repository. See also f: labels.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants