-
Notifications
You must be signed in to change notification settings - Fork 28.9k
Fix tooltip crash when route has secondary animation #172678
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
base: master
Are you sure you want to change the base?
Conversation
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.
LGTM with nits 👍 .
@@ -607,7 +608,7 @@ class TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin { | |||
|
|||
void _handlePointerDown(PointerDownEvent event) { | |||
assert(mounted); | |||
if (!(ModalRoute.isCurrentOf(context) ?? true)) { | |||
if (!(_route?.isCurrent ?? true) || (_route?.secondaryAnimation?.isAnimating ?? false)) { |
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.
The secondaryAnimation is animating, meaning that the route is on its way out, so the tooltip shouldn't be interactive? Maybe consider leaving a comment explaining this if you think it's worth it. I guess you'd have to do that in all 3 places here.
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 curious to understand this too.
I think pulling this logic out as a small private helper method would be helpful in any case; and that'd make a good home for the comment.
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.
(In particular, I think even just writing down a name for that helper function will be helpful for clarifying what this logic is meant to do.)
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); | ||
await gesture.addPointer(); | ||
await tester.tap(find.byType(BackButton)); | ||
await tester.pump(const Duration(milliseconds: 250)); |
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 supposed to be a fraction of the page transition right? Use PageTransitionObserver.transitionDuration. That way if the transition changes to be shorter than 250ms, this test will still work.
It's unlikely to be a problem but I was just dealing with this so I'm eager to use PageTransitionObserver :)
await gesture.moveTo(tester.getCenter(find.text('World'))); | ||
await tester.pumpAndSettle(); | ||
|
||
expect(tester.takeException(), isNull); |
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.
Is it worth it to also test that the tooltip is not visible? Up to you, it's possible that would unnecessarily make the test more brittle.
await tester.tap(find.text('Go to Second Page')); | ||
await tester.pumpAndSettle(); | ||
await tester.tap(find.text('Go to Third Page')); | ||
await tester.pumpAndSettle(); |
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.
Usually pumpAndSettle is not enough to pump through a page transition, though it appears to be working here. Maybe add expect
s for text that's present on each page so you verify which page(s) are visible at any time.
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 for fixing this!
// If the route's secondary animation is animating, the route is on its way | ||
// out. So, the tooltip should be non-interactive. | ||
final bool routeIsAnimating = _route?.secondaryAnimation?.isAnimating ?? false; |
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.
Very helpful, thanks!
bool _isTooltipInteractive() { | ||
final bool routeIsCurrent = _route?.isCurrent ?? false; | ||
// If the route's secondary animation is animating, the route is on its way | ||
// out. So, the tooltip should be non-interactive. | ||
final bool routeIsAnimating = _route?.secondaryAnimation?.isAnimating ?? false; | ||
return routeIsCurrent && !routeIsAnimating; |
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.
If _route is null, then both branches of this fall back, but they end up doing so in opposite directions.
I think it'll probably be easiest to think through the logic if there's an if (_route == null)
condition at the top that returns the desired result for that case. Then the rest can say _route!
and not think about the null case.
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.
In particular it looks like this PR currently flips the answer that's used when the route is null. I'm not sure what the right answer for that is — but I'm also not sure if if that change was intended, because this logic is a bit hard to read for that case 🙂
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.
(With your update, I believe it no longer flips the answer for when the route is null — instead that continues to be treated as meaning the tooltip should be interactive, as if all this logic weren't here. Which I think is the right answer.)
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! LGTM.
@@ -605,9 +606,19 @@ class TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin { | |||
} | |||
} | |||
|
|||
bool _isTooltipInteractive() { |
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.
Is this the right fix? Tooltip ideally should not know about route. This feels too specific and the problem may come up again in a different set up.
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.
Good question! I agree Tooltip ideally shouldn't know about Route. There's probably a cleaner solution by using some other abstraction.
The same comment applies already to the previous fix #168546, which this PR fixes a bug in.
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 the core issue: #167359 (comment). Basically the tooltip shouldn't be displayed mid-transition.
Tooltip uses OverlayPortal behind the scenes:
return OverlayPortal( |
So maybe the deeper solution is to have OverlayPortal not show its overlaychild in the middle of a route transition? But is that always desired behavior? Or can we hide it behind a flag?
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 used the workaround in this PR previously in another PR for the CupertinoContextMenu
overlay entry.
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.
The root issue is that RenderObject.getTransformTo
should not be called during build, it's unsafe even if in a LayoutBuilder.
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.
#170186 feels okish to me because the framework doesn't have a lifecycle callback to tell an Element that they're going offstage. So you kinda have to make Route special there to get the signal that the text field is going offstage (hopefully we can introduce new APIs to avoid making this assumption, I think we get bitten by that a lot).
Makes the tooltip interactive if the route is the current route and the route is not animating out.
Added
_route
to cacheModalRoute.of(context)
.Consolidates Fix the issue with Tooltip and Delay showing tooltip during page transition
Fix [Desktop] [Web] [Regression] [3.32] AppBar back - RenderBox was not laid out - TooltipState._buildTooltipOverlay