Skip to content

Conversation

@MitchellGoodwin
Copy link
Contributor

@MitchellGoodwin MitchellGoodwin commented Jun 11, 2024

Fixes #33799

Allows for a route to inform the route below it in the navigation stack how to animate when the topmost route enters are leaves the stack.

It does this by making a DelegatedTransition available for the previous route to look up and use. If available, the route lower in the stack will wrap it's transition builders with that delegated transition and use it instead of it's default secondary transition.

This is what the sample code in this PR shows an app that is able to use both a Material zoom transition and a Cupertino slide transition in one app. It also includes a custom vertical transition. Every page animates off the screen in a way to match up with the incoming page's transition. When popped, the correct transitions play in reverse.

Screen.Recording.2024-09-04.at.10.18.45.AM.mov

The below video shows this logic making a pseudo iOS styled sheet transition.

Screen.Recording.2024-06-10.at.9.35.55.PM.mov

All existing page transitions in Flutter will be overwritten by the incoming route if a delegatedTransition is provided. This can be opted out of through canTransitionTo for a new route widget. Of Flutter's existing page transitions, this PR only adds a DelegatedTransition for the Zoom and Cupertino transitions. The other transitions possible in Material will get delegated transitions in a later PR.

@github-actions github-actions bot added framework flutter/packages/flutter repository. See also f: labels. f: material design flutter/packages/flutter/material repository. f: cupertino flutter/packages/flutter/cupertino repository d: api docs Issues with https://api.flutter.dev/ d: examples Sample code and demos f: routes Navigator, Router, and related APIs. labels Jun 11, 2024
@MitchellGoodwin MitchellGoodwin force-pushed the route-informed-animation-2 branch from 157ee4d to 7818f6e Compare July 3, 2024 22:58
@MitchellGoodwin MitchellGoodwin requested a review from justinmc July 3, 2024 23:00
@MitchellGoodwin
Copy link
Contributor Author

@justinmc this one has the refactor of route animation logic so that the prior route does not need to opt in to get it's transition overwritten, it will always happen. So DelegatedTransition is gone. There's documentation missing, but could you take a look and see if I'm on the right track? The switch happens in ModalRoute now. If this is fine, then I'll start moving this into a non-draft PR by adding a non MBS example, get the documentation solid, write tests etc.

Copy link
Contributor

@justinmc justinmc left a comment

Choose a reason for hiding this comment

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

Here's the review put together while @MitchellGoodwin walked me through this PR over GVC.

Widget buildTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
final PageTransitionsTheme theme = Theme.of(context).pageTransitionsTheme;
return theme.buildTransitions<T>(this, context, animation, secondaryAnimation, child);
delegatedTransition = theme.delegatedTransition(context);
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 curious how it works that delegatedTransition is on the PageTransitionsTheme.

Copy link
Contributor

Choose a reason for hiding this comment

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

I commented on this above in the example after discussing in person. The format of how the delegatedTransition will have to change. Ideally it will be provided alongside the transition itself, maybe part of the PageTransitionsBuilder.

static const bool _kProfileForceDisableSnapshotting = bool.fromEnvironment('flutter.benchmarks.force_disable_snapshot');

/// The delegated transition.
static Widget delegateTransition(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget? child) {
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 also confused why ZoomPageTransitionsBuilder needs to define a delegateTransition. Discussed in person, this is part of the new feature that ZoomPageTransitionBuilder also makes outgoing routes perform a zoom.

We also discussed that maybe some of this code could be deduplicated with _ZoomPageTransition if it makes sense.

Comment on lines 878 to 848
if (matchingBuilder == const ZoomPageTransitionsBuilder()) {
return ZoomPageTransitionsBuilder.delegateTransition;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Users would have to override this if they have their own PageTransitionBuilder with a delegateTransition? Discussed in another comment, the format of where the delegatedTransition is defined for a PageTransitionBuilder will probably change.

final DelegatedTransitionBuilder? receivedTransitionBuilder;

/// The delegated transition.
static Widget delegateTransition(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget? child) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why does CupertinoPageTransition now have a delegateTransition? It is making the outgoing route use this as its secondaryTransition? (If they are FlexibleTransitionRouteMixins?)

Copy link
Contributor

Choose a reason for hiding this comment

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

Discussed in person: This means that now, CupertinoPageRoute will cause other routes to use this transition when outgoing. Previously the outgoing route would do nothing, so this is a good change, but we should watch out for it being breaking, by running the Google tests. If they fail, maybe this change should be its own separate PR first.


@override
void didChangeNext(Route<dynamic>? nextRoute) {
if (nextRoute is FlexibleTransitionRouteMixin<T> && canTransitionTo(nextRoute)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Discussed in person: Talking about opt-out by default. The default implementations of TransitionRoute.canTransitionTo/From are both => true. This is good, because when they are true, we will use the delegatedTransition. If a user has previously set one/both of them to false, then they are saying that they don't want a transition to happen at all, and that's still the behavior they will get after this PR.

We should make sure we have a test(s) for these scenarios.

@MitchellGoodwin to check what happens when canTransitionTo and canTransitionFalse do not agree.

Copy link
Contributor

Choose a reason for hiding this comment

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

Now talking about opt-out not as in do nothing, but as in do your existing custom transition and NOT the delegatedTransition. I'm guessing most users that want to opt out will want to do that.

With the current state of this PR it's not possible. We'll have to add a way for these users to make some code change to explicitly opt out. We were thinking something like a boolean flag on ModalRoute?

We think that users that are currently doing this setup (they have a custom transition and they want it to always do its secondaryTransition on the way out regardless of what the next route is) must have implemented a custom ModalRoute. It wouldn't be possible to do this if you only wrote a PageTransitionsBuilder. Therefore it's OK to do this as a flag on ModalRoute.

Probably another thing to test.

}

@override
bool didPop(T? result) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Discussed in person: We have concerns about jumping from a delegatedTransition back to the original non-delegated transition while outgoing. So this is the case we should test:

  1. Push a non-MBS route and then an MBS route.
  2. Pop both of them quickly.

The non-MBS route will have its receivedTransition cleared (line 2534 here) in the middle of performing it, and will switch back to its normal non-delegated transition. It will probably look jumpy.

What can we do to avoid that and make it look smooth like the existing train hopping?

Copy link
Contributor

Choose a reason for hiding this comment

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

Well removing the didPopNext method below seems like it would do it... But is that breaking anything else? @MitchellGoodwin said that it's done that way in the existing train hopping logic as well, so maybe it's necessary for something related to train hopping.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Screen.Recording.2024-07-16.at.2.15.10.PM.mov

What it currently looks like. It is pretty jumpy.

Copy link
Contributor

@justinmc justinmc Jul 16, 2024

Choose a reason for hiding this comment

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

Making a note that we were talking offline about solving this jumpiness by:

  • Always keep all 3 transitions in the widget tree. Don't swap in/out the delegated/received transition tree.
  • Create a new Animation that only drives the delegated transition. Or make a copy of secondaryAnimation somehow?

Not sure how feasible that is at this point though...

bool didPop(T? result) {
final bool popResult = super.didPop(result);
if (popResult && receivedTransition != null) {
receivedTransition = null;
Copy link
Contributor

Choose a reason for hiding this comment

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

Discussed in person why this was needed: The case is when pushing two routes and then popping them in succession. Call them Route1, 2 and 3.

When Route3 is popped, Route2 is using its delegatedTransition. When that transition finishes and Route3 is long gone, Route2 still has Route3's delegatedTransition in its widget tree! And not its own usual transitions provided by something like PageTransitionsBuilder.buildTransitions. When Route2 is then popped, this didPop method is called and its receivedTransition is set to null so that it can go back to using its own original transition. But, that also causes the problem given in my comment a few lines above.

Copy link
Contributor

Choose a reason for hiding this comment

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

@MitchellGoodwin That makes me think of another possible bug here. Same setup as before: push Route2 and Route3, then pop Route3. Now instead of popping Route2, push a new Route4, which does not have a delegatedTransition. If I'm understanding correctly, Route2 will not animate out correctly.

We should add a test for that too.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry for the late response. That last scenario will actually currently work. If Route4 does not have a delegate transition, then Route2 will set its reveivedTransition to null, so it will have it's original transitions. Whether it plays that transition or not when Route4 is pushed on top is up to canTransitionTo and canTransitionFrom, which is current behavior before this PR.

/// for this route.
/// * [MaterialPage], which is a [Page] of this class.
class MaterialPageRoute<T> extends PageRoute<T> with MaterialRouteTransitionMixin<T> {
class MaterialPageRoute<T> extends PageRoute<T> with MaterialRouteTransitionMixin<T>, FlexibleTransitionRouteMixin<T> {
Copy link
Contributor

Choose a reason for hiding this comment

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

The use of FlexibleTransitionRouteMixin won't be necessary when that code is moved into ModalRoute, and any other people that extended ModalRoute will get the new functionality automatically 👍 .

@MitchellGoodwin MitchellGoodwin force-pushed the route-informed-animation-2 branch from 7818f6e to 15a3570 Compare July 16, 2024 18:48
@github-actions github-actions bot removed d: api docs Issues with https://api.flutter.dev/ d: examples Sample code and demos labels Jul 16, 2024
@github-actions github-actions bot added d: api docs Issues with https://api.flutter.dev/ d: examples Sample code and demos labels Jul 30, 2024
@justinmc
Copy link
Contributor

Here's my example of a custom non-MBS delegated transition:

Screen.Recording.2024-07-30.at.4.32.54.PM.mov
Code
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Mixing Routes',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        pageTransitionsTheme: const PageTransitionsTheme(
          builders: <TargetPlatform, PageTransitionsBuilder>{
            TargetPlatform.iOS: ZoomPageTransitionsBuilder(),
          },
        ),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Zoom Transition'),
    );
  }
}

class MyHomePage extends StatelessWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          backgroundColor: Theme.of(context).colorScheme.inversePrimary,
          title: Text(title),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              TextButton(
                onPressed: () {
                  Navigator.of(context).push(
                    VerticalTransitionPageRoute(
                      context: context,
                      builder: (BuildContext context) {
                        return  const MyHomePage(title: 'Crazy Vertical Transition');
                      },
                    ),
                  );
                },
                child: const Text('Crazy Vertical Transition'),
              ),
              TextButton(
                onPressed: () {
                  Navigator.of(context).push(
                    MaterialPageRoute(
                      builder: (BuildContext context) {
                        return  const MyHomePage(title: 'Zoom Transition');
                      },
                    ),
                  );
                },
                child: const Text('Zoom Transition'),
              ),
              TextButton(
                onPressed: () {
                  CupertinoPageRoute route = CupertinoPageRoute(
                    builder: (BuildContext context) {
                      return  const MyHomePage(title: 'Cupertino Transition');
                    }
                  );
                  Navigator.of(context).push(route);
                },
                child: const Text('Cupertino Transition'),
              ),
            ],
          ),
        ),
    );
  }
}

class VerticalTransitionPageRoute<T> extends PageRoute<T> with FlexibleTransitionRouteMixin<T> {
  /// Creates a page route for use in an iOS designed app.
  VerticalTransitionPageRoute({
    required this.builder,
    required BuildContext context,
  });

  final WidgetBuilder builder;

  @override
  DelegatedTransitionBuilder? get delegatedTransition => VerticalPageTransition._delegatedTransition;

  @override
  Color? get barrierColor => const Color(0x00000000);

  @override
  bool get barrierDismissible => false;

  @override
  String? get barrierLabel => 'Should be no visible barrier...';

  @override
  bool get maintainState => true;

  @override
  bool get opaque => false;

  // Begin PageRoute.

  @override
  Duration get transitionDuration => const Duration(milliseconds: 2000);

  @override
  Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
    return builder(context);
  }

  static Widget buildPageTransitions<T>(
    ModalRoute<T> route,
    BuildContext context,
    Animation<double> animation,
    Animation<double> secondaryAnimation,
    Widget child,
  ) {
    return VerticalPageTransition(
      primaryRouteAnimation: animation,
      secondaryRouteAnimation: secondaryAnimation,
      child: child,
    );
  }

  // TODO(justinmc): No canTransitionTo needed? I want to be able to have
  // back-to-back VerticalPageTransitions.

  @override
  Widget buildTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
    return buildPageTransitions<T>(this, context, animation, secondaryAnimation, child);
  }

  // End PageRoute.
}

class VerticalPageTransition extends StatelessWidget {
  VerticalPageTransition({
    super.key,
    required Animation<double> primaryRouteAnimation,
    required this.secondaryRouteAnimation,
    required this.child,
  }) : _primaryPositionAnimation =
           CurvedAnimation(
                 parent: primaryRouteAnimation,
                 curve: curve,
                 reverseCurve: curve,
               ).drive(_kBottomUpTween),
      _secondaryPositionAnimation =
           CurvedAnimation(
                 parent: secondaryRouteAnimation,
                 curve: curve,
                 reverseCurve: curve,
               )
           .drive(_kTopDownTween);

  // When this page is coming in to cover another page.
  final Animation<Offset> _primaryPositionAnimation;

  // When this page is being coverd by another page.
  final Animation<Offset> _secondaryPositionAnimation;

  /// Animation
  final Animation<double> secondaryRouteAnimation;

  /// The widget below this widget in the tree.
  final Widget child;

  static const Curve curve = Curves.decelerate;

  static final Animatable<Offset> _kBottomUpTween = Tween<Offset>(
    begin: const Offset(0.0, 1.0),
    end: Offset.zero,
  );

  static final Animatable<Offset> _kTopDownTween = Tween<Offset>(
    begin: Offset.zero,
    end: const Offset(0.0, -1.0),
  );

  static Widget _delegatedTransition(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget? child) {
    final Animatable<Offset> tween = Tween<Offset>(
      begin: Offset.zero,
      end: const Offset(0.0, -1.0),
    ).chain(CurveTween(curve: curve));

    return SlideTransition(
      position: secondaryAnimation.drive(tween),
      child: child,
    );
  }

  @override
  Widget build(BuildContext context) {
    assert(debugCheckHasDirectionality(context));
    final TextDirection textDirection = Directionality.of(context);
    return SlideTransition(
      position: _secondaryPositionAnimation,
      textDirection: textDirection,
      transformHitTests: false,
      child:  SlideTransition(
        position: _primaryPositionAnimation,
        textDirection: textDirection,
        child: ClipRRect(
          borderRadius: const BorderRadius.vertical(top: Radius.circular(12)),
          child: child,
        )
      ),
    );
  }
}

@justinmc
Copy link
Contributor

Also random thought: Any concerns about how the drag-to-close interaction will work with MBS? Seems like it should be fine with this API but I wanted to bring it up.

@MitchellGoodwin
Copy link
Contributor Author

Also random thought: Any concerns about how the drag-to-close interaction will work with MBS? Seems like it should be fine with this API but I wanted to bring it up.

As in dragging it to partially close the animation, like the Cupertino transition does? It should work fine. The Cupertino transition works correctly when dragging back to a non-Cupertino page with this PR.

@justinmc
Copy link
Contributor

I mean when the MBS is open, dragging it down to close it and dismiss all of its routes.

@MitchellGoodwin
Copy link
Contributor Author

Yes, I believe that shouldn't be affected.

@MitchellGoodwin MitchellGoodwin force-pushed the route-informed-animation-2 branch 2 times, most recently from 5ee88b2 to 7a5974e Compare August 9, 2024 21:08
Copy link
Contributor

@justinmc justinmc left a comment

Choose a reason for hiding this comment

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

I've been ignoring small things like incomplete docs in other reviews, but I decided to go all out on that nitpicky stuff in this one haha. Feel free to ignore if there's still anything major that might change.

Overall the architecture looks good as long as everything is working. The biggest API related question I had was just about buildFlexTransitions and nextRouteTransition, and needing help understanding them. I had an idea (below) about possibly simplifying them.

Otherwise just small stuff.

),
useMaterial3: true,
),
home: const MyHomePage(title: 'Zoom Transition'),
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: I would make it private: _MyHomePage.

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
Copy link
Contributor

Choose a reason for hiding this comment

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

Indentation is wrong here.

Comment on lines 91 to 93
class VerticalTransitionPageRoute<T> extends PageRoute<T> {
/// Creates a page route for use in an iOS designed app.
VerticalTransitionPageRoute({
Copy link
Contributor

Choose a reason for hiding this comment

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

I would make this private as well and then put the comment on the class:

// A PageRoute that applies VerticalPageTransition.

Comment on lines 141 to 142
// TODO(justinmc): No canTransitionTo needed? I want to be able to have
// back-to-back VerticalPageTransitions.
Copy link
Contributor

Choose a reason for hiding this comment

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

Don't forget to remove this. The question stands though. I guess it doesn't need canTransitionTo unless we want to demonstrate that somehow?

// End PageRoute.
}

class VerticalPageTransition extends StatelessWidget {
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd make it private and comment:

// A page transition that slides off the screen vertically, and uses
// delegatedTransition to ensure that the outgoing route slides with it.

Comment on lines 1464 to 1493
if (nextRouteTransition != null) {
return nextRouteTransition!(context, animation, secondaryAnimation, originalTransitions);
} else {
return originalTransitions;
Copy link
Contributor

Choose a reason for hiding this comment

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

If receivedTransition was an empty builder instead of null, could we get rid of all of this mechanism around including it or not including it? Just throwing that out there, not sure if it's a good idea or not yet...

Comment on lines 145 to 149
/// Convenience function for passing around a builder for a transiton's secondary animation.
typedef DelegatedTransitionBuilder = Widget Function(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget? child);
Copy link
Contributor

Choose a reason for hiding this comment

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


await tester.pump(const Duration(milliseconds: 50));
expect(tester.getTopLeft(find.byKey(secondPlaceholderKey)).dx, moreOrLessEquals(xLocationIntervalTwelve, epsilon: 0.1));
});
Copy link
Contributor

Choose a reason for hiding this comment

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

Would it be possible to include a test that uses WidgetsApp.router too?

Copy link
Contributor

Choose a reason for hiding this comment

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

Trying to think of other stuff to test... Maybe that all 3 transitions are in the widget tree at the same time?

Maybe a train hopping situation? Or at least a situation where a transition is interrupted while running.

Anything else from the edge cases we have talked about?

Copy link
Contributor

Choose a reason for hiding this comment

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

My first comment WidgetsApp.router is now covered by the second example 👍

The other ones are up to you. Maybe we should test the "interrupted" case, where a pop happens while a transition is still running?

expect(materialPageRoute.receivedTransition, CupertinoPageTransition.delegateTransition);
});

testWidgets('outgoing route does not receive a delegated transition from a route with the same transition', (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.

This just makes me wonder: it's not that the routes are the same type, it's that the routes' delegatedTransition is the same as its usual outgoing transition? Or how does that work?

Copy link
Contributor

Choose a reason for hiding this comment

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

This makes sense now with the DelegatedTransition subclassing.

@MitchellGoodwin MitchellGoodwin force-pushed the route-informed-animation-2 branch from 7a5974e to bfd91ef Compare August 20, 2024 20:30
@MitchellGoodwin MitchellGoodwin force-pushed the route-informed-animation-2 branch from 84ecf2e to 8e25d51 Compare August 27, 2024 20:41
Copy link
Contributor

@justinmc justinmc left a comment

Choose a reason for hiding this comment

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

I think the name string comparison is not doable because of the risk of collision. How about instead, for each of the places where you currently instantiate DelegatedTransition with a unique name string, create a subclass of DelegatedTransition with a hardcoded builder. E.g. ZoomPageDelegatedTransition or something like that. Ideally these subclasses would be const so you can just do a normal equality check, but if not you could do a type check.

final bool linearTransition;

/// The delegated transition.
static Widget delegatedTransitionBuilder(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget? child) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it weird that now DelegatedTransition is a type, but delegatedTransitionBuilder returns a Widget?

Comment on lines 98 to 106
DelegatedTransition? _delegatedTransition;

@override
DelegatedTransition? get delegatedTransition => _delegatedTransition;

set delegatedTransition(DelegatedTransition? newTransition) {
_delegatedTransition = newTransition;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Could this just be a plain variable instead of a getter and setter?

final DelegatedTransitionBuilder builder;

/// Placeholder
final String? name;
Copy link
Contributor

Choose a reason for hiding this comment

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

I worry about the possibility for collisions due to this being just a plain string.

}

/// Provide delegate transition for platform.
DelegatedTransition? delegatedTransition(TargetPlatform platform, bool allowSnapshotting) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: Maybe rename to getDelegatedTransition to distinguish a getter from a value.


/// Placeholder
@immutable
class DelegatedTransition {
Copy link
Contributor

Choose a reason for hiding this comment

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

This reminds me of PageTransitionsBuilder. I wonder if that class was created for a similar reason?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It seems like it. It looks like it was used for comparing equality of PageTransitionsTheme.

@MitchellGoodwin MitchellGoodwin force-pushed the route-informed-animation-2 branch from 8d8e14c to b63afc8 Compare August 28, 2024 22:12
@MitchellGoodwin
Copy link
Contributor Author

Ideally these subclasses would be const so you can just do a normal equality check, but if not you could do a type check.

@justinmc I went with a type check, because with the Zoom transition snapshotting behavior we talked about offline, there are cases were the builder may be different, but you would want to not overwrite the previous pages transition. If you do want to make two or more delegates that do override the previous one, you would make a class type for each variation.

engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Oct 5, 2024
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Oct 5, 2024
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Oct 6, 2024
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Oct 6, 2024
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Oct 8, 2024
auto-submit bot pushed a commit to flutter/packages that referenced this pull request Oct 8, 2024
Manual roll Flutter from 0975e61 to ec2e12b (54 revisions)

Manual roll requested by [email protected]

flutter/flutter@0975e61...ec2e12b

2024-10-03 [email protected] Roll Flutter Engine from bd44b58e3204 to 247bc68c578e (7 revisions) (flutter/flutter#156144)
2024-10-03 [email protected] Roll Flutter Engine from 70232fa124d0 to bd44b58e3204 (1 revision) (flutter/flutter#156124)
2024-10-03 [email protected] Roll Flutter Engine from 33ac1b30ab0a to 70232fa124d0 (2 revisions) (flutter/flutter#156122)
2024-10-02 [email protected] Update `ThemeData.dialogTheme` type to accept `DialogThemeData` (flutter/flutter#155129)
2024-10-02 [email protected] Roll Flutter Engine from 751ab9b3c5eb to 33ac1b30ab0a (4 revisions) (flutter/flutter#156118)
2024-10-02 [email protected] Add back main() methods to benchmark benches. (flutter/flutter#156083)
2024-10-02 [email protected] Roll pub packages (flutter/flutter#156117)
2024-10-02 [email protected] Add `mouseCursor` property to `CupertinoCheckbox` (flutter/flutter#151788)
2024-10-02 [email protected] Roll Flutter Engine from 3bdc1c0a30b6 to 751ab9b3c5eb (3 revisions) (flutter/flutter#156115)
2024-10-02 [email protected] Roll pub packages (flutter/flutter#156114)
2024-10-02 [email protected] [ Cocoon ] Wait for task results to be received by the task runner before shutting down the task process (flutter/flutter#156002)
2024-10-02 [email protected] Allow mixing route transitions in one app. (flutter/flutter#150031)
2024-10-02 [email protected] Roll Flutter Engine from f20681241753 to 3bdc1c0a30b6 (5 revisions) (flutter/flutter#156107)
2024-10-02 [email protected] Update Upgrading-Engine's-Android-API-version.md to reflect code move (flutter/flutter#156108)
2024-10-02 [email protected] Roll Packages from ebcc4f0 to 7c97c88 (5 revisions) (flutter/flutter#156106)
2024-10-02 [email protected] Roll pub packages (flutter/flutter#156105)
2024-10-02 [email protected] fix wrong test in "fixing `DropdownMenu` keyboard navigation"  (flutter/flutter#156084)
2024-10-02 [email protected] fix ReorderableList not passing in item extent builder (flutter/flutter#155994)
2024-10-02 98614782+auto-submit[bot]@users.noreply.github.com Reverts "integration_test: migrate to build.gradle.kts (#154125)" (flutter/flutter#156087)
2024-10-02 [email protected] Add deprecation warning for "flutter create --ios-language" (flutter/flutter#155867)
2024-10-02 [email protected] update flutter create generated projects to use package:flutter_lints 5.0.0 (flutter/flutter#156011)
2024-10-02 [email protected] Roll Flutter Engine from d48c35d16814 to f20681241753 (1 revision) (flutter/flutter#156080)
2024-10-02 [email protected] [Docs] `CupertinoListTile` API Example (flutter/flutter#154548)
2024-10-02 [email protected] integration_test: migrate to build.gradle.kts (flutter/flutter#154125)
2024-10-02 [email protected] Marks Windows_mokey native_assets_android to be flaky (flutter/flutter#156064)
2024-10-02 [email protected] Fix DropdownMenu does not rematch initialSelection when entries have changed (flutter/flutter#155757)
2024-10-02 [email protected] Roll Flutter Engine from 9b224bd2f895 to d48c35d16814 (1 revision) (flutter/flutter#156074)
2024-10-02 [email protected] Roll Flutter Engine from 8774940b9ddc to 9b224bd2f895 (1 revision) (flutter/flutter#156065)
2024-10-02 [email protected] Roll Flutter Engine from 21ad04948457 to 8774940b9ddc (1 revision) (flutter/flutter#156055)
2024-10-02 [email protected] Roll Flutter Engine from 767bdc38cf51 to 21ad04948457 (1 revision) (flutter/flutter#156049)
2024-10-02 [email protected] Roll Flutter Engine from 055969512dc5 to 767bdc38cf51 (1 revision) (flutter/flutter#156043)
2024-10-02 [email protected] Roll Flutter Engine from e0f049d69240 to 055969512dc5 (2 revisions) (flutter/flutter#156042)
2024-10-02 [email protected] fix `DropdownMenu` keyboard navigation when entries are filtered empty (flutter/flutter#155252)
2024-10-02 [email protected] Roll Flutter Engine from a5bc2e2708c7 to e0f049d69240 (1 revision) (flutter/flutter#156039)
2024-10-02 [email protected] mark {Linux,Windows} tool_integration_tests_* non-bringup (flutter/flutter#155773)
2024-10-02 [email protected] Roll Flutter Engine from a7abf7a8163e to a5bc2e2708c7 (2 revisions) (flutter/flutter#156038)
2024-10-02 [email protected] Roll Flutter Engine from df1982dd4482 to a7abf7a8163e (1 revision) (flutter/flutter#156032)
2024-10-02 [email protected] Add `enableSplash` parameter to `CarouselView` (flutter/flutter#155214)
2024-10-02 [email protected] Roll pub packages (flutter/flutter#156030)
2024-10-01 [email protected] Roll Flutter Engine from ab48d6d8c167 to df1982dd4482 (1 revision) (flutter/flutter#156029)
2024-10-01 [email protected] Marks Mac_arm64_ios hot_mode_dev_cycle_ios__benchmark to be unflaky (flutter/flutter#147289)
2024-10-01 [email protected] Add WidgetStateMouseCursor example and tests for it. (flutter/flutter#155552)
2024-10-01 49699333+dependabot[bot]@users.noreply.github.com Bump codecov/codecov-action from 4.5.0 to 4.6.0 (flutter/flutter#156024)
2024-10-01 [email protected] Roll Flutter Engine from e7b3ce717006 to ab48d6d8c167 (1 revision) (flutter/flutter#156023)
...
@brian-superlist
Copy link

brian-superlist commented Nov 17, 2024

Is there a quick and easy way to opt out of this change? It's breaking tests in our app where I'm trying to upgrade to the master branch to add WASM Support (I need master branch as it has a few important fixes for WASM). I'm already introducing so many changes to support the new js_interop/html and upgrading a bunch of our packages to support web 1.0, I'd really rather not refactor our routing stack to support this one change as well.

I've pinpointed this commit as the failure point.

@brian-superlist
Copy link

For those who hit this same issue, I found overriding the MaterialApp.pageTransitionsTheme with custom versions that do not include these new transitions fixes the issue for me.

@MitchellGoodwin
Copy link
Contributor Author

@brian-superlist could you file an issue with more info on what got broken? This was intended to not be a breaking change.

@brian-superlist
Copy link

@MitchellGoodwin I'll try to boil it down -- our tests that failed are broad integration tests that cover many widgets together and I'm unsure how long it will take to create a minimal repro. I'll see if I can do so in a reasonable timeframe.

@Piinks
Copy link
Contributor

Piinks commented Nov 19, 2024

@brian-superlist are these tests registered in flutter/tests? We run all of the tests registered there on every pull request before landing a change in flutter/flutter.

@brian-superlist
Copy link

@Piinks Nope, we have many of our open source projects on there, but this particular failure was against our closed source app. As far as I understand, that violates the first criterion:

All the code must be available publicly on GitHub under a license compatible with this effort.

@kuhnroyal
Copy link

kuhnroyal commented Nov 21, 2024

I have an app where the transition is broken after this change.
From a route built via PageRouteBuilder to a MaterialPageRoute(fullscreenDialog: true).
Before this commit, there was NO outgoing transition for the underlying PageRouteBuilder, now there is an outgoing animation which seems wrong. This is mainly noticeable on iOS where the underlying page is transitioned out to the left while the fullscreen page slides up from the bottom.

@Piinks
Copy link
Contributor

Piinks commented Nov 21, 2024

Please file an issue with a code sample that reproduces the problem. We cannot triage and address issues in closed PRs. Thanks!

@kuhnroyal
Copy link

Done!

github-merge-queue bot pushed a commit that referenced this pull request Nov 27, 2024
…og (#159312)

Fixes #159289

Cupertino and Material routes were looking to see if the next route was
a full screen dialog route, and not transitioning accordingly. However
with the updates to allow [mixed transitions in an
app](#150031), any route will try
and transition if a delegatedTransition is available. This PR makes it
so that Cupertino and Material routes that are fullscreen dialogs will
use `canTransitionFrom` to tell the previous route not to transition.

Before:

[388677067-d301238d-6615-42a7-b60a-611c61136d88.webm](https://github.com/user-attachments/assets/eea99ee9-0bc8-4981-a950-08f99a7fdb3e)

After:


https://github.com/user-attachments/assets/7deb5143-cd67-4696-95ec-9d7df329dfa4



## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [ ] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Dec 11, 2024
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Dec 12, 2024
eyebrowsoffire pushed a commit to eyebrowsoffire/flutter that referenced this pull request Jan 17, 2025
…llscreenDialog

CP issue: flutter#160420
Original PR: flutter#159312

Original description below:

Fixes flutter#159289

Cupertino and Material routes were looking to see if the next route was
a full screen dialog route, and not transitioning accordingly. However
with the updates to allow [mixed transitions in an
app](flutter#150031), any route will try
and transition if a delegatedTransition is available. This PR makes it
so that Cupertino and Material routes that are fullscreen dialogs will
use `canTransitionFrom` to tell the previous route not to transition.

Before:

[388677067-d301238d-6615-42a7-b60a-611c61136d88.webm](https://github.com/user-attachments/assets/eea99ee9-0bc8-4981-a950-08f99a7fdb3e)

After:

https://github.com/user-attachments/assets/7deb5143-cd67-4696-95ec-9d7df329dfa4

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [ ] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
auto-submit bot pushed a commit that referenced this pull request Jan 17, 2025
…llscreenDialog (#161784)

CP issue: #160420
Original PR: #159312

Original description below:

Fixes #159289

Cupertino and Material routes were looking to see if the next route was a full screen dialog route, and not transitioning accordingly. However with the updates to allow [mixed transitions in an app](#150031), any route will try and transition if a delegatedTransition is available. This PR makes it so that Cupertino and Material routes that are fullscreen dialogs will use `canTransitionFrom` to tell the previous route not to transition.

Before:

[388677067-d301238d-6615-42a7-b60a-611c61136d88.webm](https://github.com/user-attachments/assets/eea99ee9-0bc8-4981-a950-08f99a7fdb3e)

After:

https://github.com/user-attachments/assets/7deb5143-cd67-4696-95ec-9d7df329dfa4

https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

autosubmit Merge PR when tree becomes green via auto submit App d: api docs Issues with https://api.flutter.dev/ d: examples Sample code and demos f: cupertino flutter/packages/flutter/cupertino repository f: material design flutter/packages/flutter/material 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.

[iOS 13] navigation transition styles can now mix

7 participants