Skip to content

Commit de230d3

Browse files
authored
Revert "Add ability for ModalRoutes to ignore pointers during transitions and do so on Cupertino routes (#95757)" (#104520)
This reverts commit 4c0b0be.
1 parent 7ca4984 commit de230d3

File tree

8 files changed

+93
-508
lines changed

8 files changed

+93
-508
lines changed

packages/flutter/lib/src/cupertino/route.dart

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -235,9 +235,6 @@ mixin CupertinoRouteTransitionMixin<T> on PageRoute<T> {
235235
return result;
236236
}
237237

238-
@override
239-
bool get ignorePointerDuringTransitions => true;
240-
241238
// Called by _CupertinoBackGestureDetector when a pop ("back") drag start
242239
// gesture is detected. The returned controller handles all of the subsequent
243240
// drag events.
@@ -1052,9 +1049,6 @@ class CupertinoModalPopupRoute<T> extends PopupRoute<T> {
10521049
@override
10531050
Duration get transitionDuration => _kModalPopupTransitionDuration;
10541051

1055-
@override
1056-
bool get ignorePointerDuringTransitions => true;
1057-
10581052
Animation<double>? _animation;
10591053

10601054
late Tween<Offset> _offsetTween;
@@ -1355,7 +1349,4 @@ class CupertinoDialogRoute<T> extends RawDialogRoute<T> {
13551349
barrierLabel: barrierLabel ?? CupertinoLocalizations.of(context).modalBarrierDismissLabel,
13561350
barrierColor: barrierColor ?? CupertinoDynamicColor.resolve(kCupertinoModalBarrierColor, context),
13571351
);
1358-
1359-
@override
1360-
bool get ignorePointerDuringTransitions => true;
13611352
}

packages/flutter/lib/src/widgets/routes.dart

Lines changed: 75 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -293,85 +293,73 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> {
293293
final VoidCallback? previousTrainHoppingListenerRemover = _trainHoppingListenerRemover;
294294
_trainHoppingListenerRemover = null;
295295

296-
if (nextRoute is TransitionRoute<dynamic>) {
297-
if (canTransitionTo(nextRoute) && nextRoute.canTransitionFrom(this)) {
298-
final Animation<double>? current = _secondaryAnimation.parent;
299-
if (current != null) {
300-
final Animation<double> currentTrain = (current is TrainHoppingAnimation ? current.currentTrain : current)!;
301-
final Animation<double> nextTrain = nextRoute._animation!;
302-
if (
303-
currentTrain.value == nextTrain.value ||
304-
nextTrain.status == AnimationStatus.completed ||
305-
nextTrain.status == AnimationStatus.dismissed
306-
) {
307-
_setSecondaryAnimation(nextTrain, nextRoute.completed);
308-
} else {
309-
// Two trains animate at different values. We have to do train hopping.
310-
// There are three possibilities of train hopping:
311-
// 1. We hop on the nextTrain when two trains meet in the middle using
312-
// TrainHoppingAnimation.
313-
// 2. There is no chance to hop on nextTrain because two trains never
314-
// cross each other. We have to directly set the animation to
315-
// nextTrain once the nextTrain stops animating.
316-
// 3. A new _updateSecondaryAnimation is called before train hopping
317-
// finishes. We leave a listener remover for the next call to
318-
// properly clean up the existing train hopping.
319-
TrainHoppingAnimation? newAnimation;
320-
void jumpOnAnimationEnd(AnimationStatus status) {
321-
switch (status) {
322-
case AnimationStatus.completed:
323-
case AnimationStatus.dismissed:
324-
// The nextTrain has stopped animating without train hopping.
325-
// Directly sets the secondary animation and disposes the
326-
// TrainHoppingAnimation.
327-
_setSecondaryAnimation(nextTrain, nextRoute.completed);
328-
if (_trainHoppingListenerRemover != null) {
329-
_trainHoppingListenerRemover!();
330-
_trainHoppingListenerRemover = null;
331-
}
332-
break;
333-
case AnimationStatus.forward:
334-
case AnimationStatus.reverse:
335-
break;
336-
}
337-
}
338-
_trainHoppingListenerRemover = () {
339-
nextTrain.removeStatusListener(jumpOnAnimationEnd);
340-
newAnimation?.dispose();
341-
};
342-
nextTrain.addStatusListener(jumpOnAnimationEnd);
343-
newAnimation = TrainHoppingAnimation(
344-
currentTrain,
345-
nextTrain,
346-
onSwitchedTrain: () {
347-
assert(_secondaryAnimation.parent == newAnimation);
348-
assert(newAnimation!.currentTrain == nextRoute._animation);
349-
// We can hop on the nextTrain, so we don't need to listen to
350-
// whether the nextTrain has stopped.
351-
_setSecondaryAnimation(newAnimation!.currentTrain, nextRoute.completed);
296+
if (nextRoute is TransitionRoute<dynamic> && canTransitionTo(nextRoute) && nextRoute.canTransitionFrom(this)) {
297+
final Animation<double>? current = _secondaryAnimation.parent;
298+
if (current != null) {
299+
final Animation<double> currentTrain = (current is TrainHoppingAnimation ? current.currentTrain : current)!;
300+
final Animation<double> nextTrain = nextRoute._animation!;
301+
if (
302+
currentTrain.value == nextTrain.value ||
303+
nextTrain.status == AnimationStatus.completed ||
304+
nextTrain.status == AnimationStatus.dismissed
305+
) {
306+
_setSecondaryAnimation(nextTrain, nextRoute.completed);
307+
} else {
308+
// Two trains animate at different values. We have to do train hopping.
309+
// There are three possibilities of train hopping:
310+
// 1. We hop on the nextTrain when two trains meet in the middle using
311+
// TrainHoppingAnimation.
312+
// 2. There is no chance to hop on nextTrain because two trains never
313+
// cross each other. We have to directly set the animation to
314+
// nextTrain once the nextTrain stops animating.
315+
// 3. A new _updateSecondaryAnimation is called before train hopping
316+
// finishes. We leave a listener remover for the next call to
317+
// properly clean up the existing train hopping.
318+
TrainHoppingAnimation? newAnimation;
319+
void jumpOnAnimationEnd(AnimationStatus status) {
320+
switch (status) {
321+
case AnimationStatus.completed:
322+
case AnimationStatus.dismissed:
323+
// The nextTrain has stopped animating without train hopping.
324+
// Directly sets the secondary animation and disposes the
325+
// TrainHoppingAnimation.
326+
_setSecondaryAnimation(nextTrain, nextRoute.completed);
352327
if (_trainHoppingListenerRemover != null) {
353328
_trainHoppingListenerRemover!();
354329
_trainHoppingListenerRemover = null;
355330
}
356-
},
357-
);
358-
_setSecondaryAnimation(newAnimation, nextRoute.completed);
331+
break;
332+
case AnimationStatus.forward:
333+
case AnimationStatus.reverse:
334+
break;
335+
}
359336
}
360-
} else { // This route has no secondary animation.
361-
_setSecondaryAnimation(nextRoute._animation, nextRoute.completed);
337+
_trainHoppingListenerRemover = () {
338+
nextTrain.removeStatusListener(jumpOnAnimationEnd);
339+
newAnimation?.dispose();
340+
};
341+
nextTrain.addStatusListener(jumpOnAnimationEnd);
342+
newAnimation = TrainHoppingAnimation(
343+
currentTrain,
344+
nextTrain,
345+
onSwitchedTrain: () {
346+
assert(_secondaryAnimation.parent == newAnimation);
347+
assert(newAnimation!.currentTrain == nextRoute._animation);
348+
// We can hop on the nextTrain, so we don't need to listen to
349+
// whether the nextTrain has stopped.
350+
_setSecondaryAnimation(newAnimation!.currentTrain, nextRoute.completed);
351+
if (_trainHoppingListenerRemover != null) {
352+
_trainHoppingListenerRemover!();
353+
_trainHoppingListenerRemover = null;
354+
}
355+
},
356+
);
357+
_setSecondaryAnimation(newAnimation, nextRoute.completed);
362358
}
363359
} else {
364-
// This route cannot coordinate transitions with nextRoute, so it should
365-
// have no visible secondary animation. By using an AnimationMin, the
366-
// animation's value will always be zero, but it will have nextRoute.animation's
367-
// status until it finishes, allowing this route to wait until all visible
368-
// transitions are complete to stop ignoring pointers.
369-
_setSecondaryAnimation(
370-
AnimationMin<double>(kAlwaysDismissedAnimation, nextRoute._animation!),
371-
nextRoute.completed,
372-
);
360+
_setSecondaryAnimation(nextRoute._animation, nextRoute.completed);
373361
}
374-
} else { // The next route is not a TransitionRoute.
362+
} else {
375363
_setSecondaryAnimation(kAlwaysDismissedAnimation);
376364
}
377365
// Finally, we dispose any previous train hopping animation because it
@@ -408,9 +396,9 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> {
408396
/// the [nextRoute] is popped off of this route, the
409397
/// `secondaryAnimation` will run from 1.0 - 0.0.
410398
///
411-
/// If false, this route's [ModalRoute.buildTransitions] `secondaryAnimation`
412-
/// will proxy an animation with a constant value of 0. In other words, this
413-
/// route will not animate when [nextRoute] is pushed on top of it or when
399+
/// If false, this route's [ModalRoute.buildTransitions] `secondaryAnimation` parameter
400+
/// value will be [kAlwaysDismissedAnimation]. In other words, this route
401+
/// will not animate when [nextRoute] is pushed on top of it or when
414402
/// [nextRoute] is popped off of it.
415403
///
416404
/// Returns true by default.
@@ -858,19 +846,17 @@ class _ModalScopeState<T> extends State<_ModalScope<T>> {
858846
context,
859847
widget.route.animation!,
860848
widget.route.secondaryAnimation!,
861-
// _listenable updates when this route's animations change
862-
// values, but the _ignorePointerNotifier can also update
863-
// when the status of animations on popping routes change,
864-
// even when this route's animations' values don't. Also,
865-
// when the value of the _ignorePointerNotifier changes,
866-
// it's only necessary to rebuild the IgnorePointer
867-
// widget and set the focus node's ability to focus.
849+
// This additional AnimatedBuilder is include because if the
850+
// value of the userGestureInProgressNotifier changes, it's
851+
// only necessary to rebuild the IgnorePointer widget and set
852+
// the focus node's ability to focus.
868853
AnimatedBuilder(
869-
animation: widget.route._ignorePointerNotifier,
854+
animation: widget.route.navigator?.userGestureInProgressNotifier ?? ValueNotifier<bool>(false),
870855
builder: (BuildContext context, Widget? child) {
871-
focusScopeNode.canRequestFocus = !_shouldIgnoreFocusRequest;
856+
final bool ignoreEvents = _shouldIgnoreFocusRequest;
857+
focusScopeNode.canRequestFocus = !ignoreEvents;
872858
return IgnorePointer(
873-
ignoring: widget.route._ignorePointer,
859+
ignoring: ignoreEvents,
874860
child: child,
875861
);
876862
},
@@ -1154,36 +1140,11 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
11541140
return child;
11551141
}
11561142

1157-
/// Whether this route should ignore pointers when transitions are in progress.
1158-
///
1159-
/// Pointers always are ignored when [isCurrent] is false (e.g., when a route
1160-
/// has a new route pushed on top of it, or during a route's exit transition
1161-
/// after popping). Override this value to also ignore pointers on pages during
1162-
/// transitions where this route is the current route (e.g., after the route
1163-
/// above this route pops, or during this route's entrance transition).
1164-
///
1165-
/// Returns false by default.
1166-
///
1167-
/// See also:
1168-
///
1169-
/// * [CupertinoRouteTransitionMixin], [CupertinoModalPopupRoute], and
1170-
/// [CupertinoDialogRoute], which use this property to specify that
1171-
/// Cupertino routes ignore pointers during transitions.
1172-
@protected
1173-
bool get ignorePointerDuringTransitions => false;
1174-
11751143
@override
11761144
void install() {
11771145
super.install();
1178-
_animationProxy = ProxyAnimation(super.animation)
1179-
..addStatusListener(_handleAnimationStatusChanged);
1180-
_secondaryAnimationProxy = ProxyAnimation(super.secondaryAnimation)
1181-
..addStatusListener(_handleAnimationStatusChanged);
1182-
navigator!.userGestureInProgressNotifier.addListener(_maybeUpdateIgnorePointer);
1183-
}
1184-
1185-
void _handleAnimationStatusChanged(AnimationStatus status) {
1186-
_maybeUpdateIgnorePointer();
1146+
_animationProxy = ProxyAnimation(super.animation);
1147+
_secondaryAnimationProxy = ProxyAnimation(super.secondaryAnimation);
11871148
}
11881149

11891150
@override
@@ -1419,19 +1380,6 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
14191380
Animation<double>? get secondaryAnimation => _secondaryAnimationProxy;
14201381
ProxyAnimation? _secondaryAnimationProxy;
14211382

1422-
bool get _ignorePointer => _ignorePointerNotifier.value;
1423-
final ValueNotifier<bool> _ignorePointerNotifier = ValueNotifier<bool>(false);
1424-
1425-
void _maybeUpdateIgnorePointer() {
1426-
bool isTransitioning(Animation<double>? animation) {
1427-
return animation?.status == AnimationStatus.forward || animation?.status == AnimationStatus.reverse;
1428-
}
1429-
_ignorePointerNotifier.value = !isCurrent ||
1430-
(navigator?.userGestureInProgress ?? false) ||
1431-
(ignorePointerDuringTransitions &&
1432-
(isTransitioning(animation) || isTransitioning(secondaryAnimation)));
1433-
}
1434-
14351383
final List<WillPopCallback> _willPopCallbacks = <WillPopCallback>[];
14361384

14371385
/// Returns [RoutePopDisposition.doNotPop] if any of callbacks added with
@@ -1650,14 +1598,9 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
16501598
child: barrier,
16511599
);
16521600
}
1653-
barrier = AnimatedBuilder(
1654-
animation: _ignorePointerNotifier,
1655-
builder: (BuildContext context, Widget? child) {
1656-
return IgnorePointer(
1657-
ignoring: _ignorePointer,
1658-
child: child,
1659-
);
1660-
},
1601+
barrier = IgnorePointer(
1602+
ignoring: animation!.status == AnimationStatus.reverse || // changedInternalState is called when animation.status updates
1603+
animation!.status == AnimationStatus.dismissed, // dismissed is possible when doing a manual pop gesture
16611604
child: barrier,
16621605
);
16631606
if (semanticsDismissible && barrierDismissible) {

packages/flutter/test/cupertino/action_sheet_test.dart

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,13 @@ void main() {
2929
);
3030

3131
await tester.tap(find.text('Go'));
32-
await tester.pumpAndSettle();
32+
await tester.pump();
3333

34-
expect(find.byType(CupertinoActionSheet), findsOneWidget);
34+
expect(find.text('Action Sheet'), findsOneWidget);
3535

36-
await tester.tap(find.byType(ModalBarrier).last);
37-
await tester.pumpAndSettle();
38-
expect(find.byType(CupertinoActionSheet), findsNothing);
36+
await tester.tapAt(const Offset(20.0, 20.0));
37+
await tester.pump();
38+
expect(find.text('Action Sheet'), findsNothing);
3939
});
4040

4141
testWidgets('Verify that a tap on title section (not buttons) does not dismiss an action sheet', (WidgetTester tester) async {
@@ -867,7 +867,7 @@ void main() {
867867
expect(find.byType(CupertinoActionSheet), findsNothing);
868868
});
869869

870-
testWidgets('Modal barrier cannot be dismissed during transition', (WidgetTester tester) async {
870+
testWidgets('Modal barrier is pressed during transition', (WidgetTester tester) async {
871871
await tester.pumpWidget(
872872
createAppWithButtonThatLaunchesActionSheet(
873873
CupertinoActionSheet(
@@ -906,20 +906,21 @@ void main() {
906906
await tester.pump(const Duration(milliseconds: 60));
907907
expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, moreOrLessEquals(337.1, epsilon: 0.1));
908908

909-
// Attempt to dismiss
909+
// Exit animation
910910
await tester.tapAt(const Offset(20.0, 20.0));
911911
await tester.pump(const Duration(milliseconds: 60));
912912

913-
// Enter animation is continuing
914-
expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, moreOrLessEquals(325.4, epsilon: 0.1));
913+
await tester.pump(const Duration(milliseconds: 60));
914+
expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, moreOrLessEquals(374.3, epsilon: 0.1));
915915

916-
await tester.pumpAndSettle();
916+
await tester.pump(const Duration(milliseconds: 60));
917+
expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, moreOrLessEquals(470.0, epsilon: 0.1));
917918

918-
// Attempt to dismiss again
919-
await tester.tapAt(const Offset(20.0, 20.0));
920-
await tester.pumpAndSettle();
919+
await tester.pump(const Duration(milliseconds: 60));
920+
expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, 600.0);
921921

922922
// Action sheet has disappeared
923+
await tester.pump(const Duration(milliseconds: 60));
923924
expect(find.byType(CupertinoActionSheet), findsNothing);
924925
});
925926

@@ -951,7 +952,7 @@ void main() {
951952
);
952953

953954
await tester.tap(find.text('Go'));
954-
await tester.pumpAndSettle();
955+
await tester.pump();
955956

956957
expect(
957958
semantics,

packages/flutter/test/cupertino/dialog_test.dart

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1074,8 +1074,6 @@ void main() {
10741074
transition = tester.firstWidget(fadeTransitionFinder);
10751075
expect(transition.opacity.value, moreOrLessEquals(1.0, epsilon: 0.001));
10761076

1077-
await tester.pumpAndSettle();
1078-
10791077
await tester.tap(find.text('Delete'));
10801078

10811079
// Exit animation, look at reverse FadeTransition.

0 commit comments

Comments
 (0)