Revert "Add ability for ModalRoutes
to ignore pointers during transitions and do so on Cupertino
routes (#95757)" (#104520)
This reverts commit 4c0b0be2da5d1ee80c3d713e68ddd88d2cf2e72d.
This commit is contained in:
parent
7ca498489d
commit
de230d393c
@ -235,9 +235,6 @@ mixin CupertinoRouteTransitionMixin<T> on PageRoute<T> {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
bool get ignorePointerDuringTransitions => true;
|
|
||||||
|
|
||||||
// Called by _CupertinoBackGestureDetector when a pop ("back") drag start
|
// Called by _CupertinoBackGestureDetector when a pop ("back") drag start
|
||||||
// gesture is detected. The returned controller handles all of the subsequent
|
// gesture is detected. The returned controller handles all of the subsequent
|
||||||
// drag events.
|
// drag events.
|
||||||
@ -1052,9 +1049,6 @@ class CupertinoModalPopupRoute<T> extends PopupRoute<T> {
|
|||||||
@override
|
@override
|
||||||
Duration get transitionDuration => _kModalPopupTransitionDuration;
|
Duration get transitionDuration => _kModalPopupTransitionDuration;
|
||||||
|
|
||||||
@override
|
|
||||||
bool get ignorePointerDuringTransitions => true;
|
|
||||||
|
|
||||||
Animation<double>? _animation;
|
Animation<double>? _animation;
|
||||||
|
|
||||||
late Tween<Offset> _offsetTween;
|
late Tween<Offset> _offsetTween;
|
||||||
@ -1355,7 +1349,4 @@ class CupertinoDialogRoute<T> extends RawDialogRoute<T> {
|
|||||||
barrierLabel: barrierLabel ?? CupertinoLocalizations.of(context).modalBarrierDismissLabel,
|
barrierLabel: barrierLabel ?? CupertinoLocalizations.of(context).modalBarrierDismissLabel,
|
||||||
barrierColor: barrierColor ?? CupertinoDynamicColor.resolve(kCupertinoModalBarrierColor, context),
|
barrierColor: barrierColor ?? CupertinoDynamicColor.resolve(kCupertinoModalBarrierColor, context),
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
|
||||||
bool get ignorePointerDuringTransitions => true;
|
|
||||||
}
|
}
|
||||||
|
@ -293,85 +293,73 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> {
|
|||||||
final VoidCallback? previousTrainHoppingListenerRemover = _trainHoppingListenerRemover;
|
final VoidCallback? previousTrainHoppingListenerRemover = _trainHoppingListenerRemover;
|
||||||
_trainHoppingListenerRemover = null;
|
_trainHoppingListenerRemover = null;
|
||||||
|
|
||||||
if (nextRoute is TransitionRoute<dynamic>) {
|
if (nextRoute is TransitionRoute<dynamic> && canTransitionTo(nextRoute) && nextRoute.canTransitionFrom(this)) {
|
||||||
if (canTransitionTo(nextRoute) && nextRoute.canTransitionFrom(this)) {
|
final Animation<double>? current = _secondaryAnimation.parent;
|
||||||
final Animation<double>? current = _secondaryAnimation.parent;
|
if (current != null) {
|
||||||
if (current != null) {
|
final Animation<double> currentTrain = (current is TrainHoppingAnimation ? current.currentTrain : current)!;
|
||||||
final Animation<double> currentTrain = (current is TrainHoppingAnimation ? current.currentTrain : current)!;
|
final Animation<double> nextTrain = nextRoute._animation!;
|
||||||
final Animation<double> nextTrain = nextRoute._animation!;
|
if (
|
||||||
if (
|
currentTrain.value == nextTrain.value ||
|
||||||
currentTrain.value == nextTrain.value ||
|
nextTrain.status == AnimationStatus.completed ||
|
||||||
nextTrain.status == AnimationStatus.completed ||
|
nextTrain.status == AnimationStatus.dismissed
|
||||||
nextTrain.status == AnimationStatus.dismissed
|
) {
|
||||||
) {
|
_setSecondaryAnimation(nextTrain, nextRoute.completed);
|
||||||
_setSecondaryAnimation(nextTrain, nextRoute.completed);
|
} else {
|
||||||
} else {
|
// Two trains animate at different values. We have to do train hopping.
|
||||||
// Two trains animate at different values. We have to do train hopping.
|
// There are three possibilities of train hopping:
|
||||||
// There are three possibilities of train hopping:
|
// 1. We hop on the nextTrain when two trains meet in the middle using
|
||||||
// 1. We hop on the nextTrain when two trains meet in the middle using
|
// TrainHoppingAnimation.
|
||||||
// TrainHoppingAnimation.
|
// 2. There is no chance to hop on nextTrain because two trains never
|
||||||
// 2. There is no chance to hop on nextTrain because two trains never
|
// cross each other. We have to directly set the animation to
|
||||||
// cross each other. We have to directly set the animation to
|
// nextTrain once the nextTrain stops animating.
|
||||||
// nextTrain once the nextTrain stops animating.
|
// 3. A new _updateSecondaryAnimation is called before train hopping
|
||||||
// 3. A new _updateSecondaryAnimation is called before train hopping
|
// finishes. We leave a listener remover for the next call to
|
||||||
// finishes. We leave a listener remover for the next call to
|
// properly clean up the existing train hopping.
|
||||||
// properly clean up the existing train hopping.
|
TrainHoppingAnimation? newAnimation;
|
||||||
TrainHoppingAnimation? newAnimation;
|
void jumpOnAnimationEnd(AnimationStatus status) {
|
||||||
void jumpOnAnimationEnd(AnimationStatus status) {
|
switch (status) {
|
||||||
switch (status) {
|
case AnimationStatus.completed:
|
||||||
case AnimationStatus.completed:
|
case AnimationStatus.dismissed:
|
||||||
case AnimationStatus.dismissed:
|
// The nextTrain has stopped animating without train hopping.
|
||||||
// The nextTrain has stopped animating without train hopping.
|
// Directly sets the secondary animation and disposes the
|
||||||
// Directly sets the secondary animation and disposes the
|
// TrainHoppingAnimation.
|
||||||
// TrainHoppingAnimation.
|
_setSecondaryAnimation(nextTrain, nextRoute.completed);
|
||||||
_setSecondaryAnimation(nextTrain, nextRoute.completed);
|
|
||||||
if (_trainHoppingListenerRemover != null) {
|
|
||||||
_trainHoppingListenerRemover!();
|
|
||||||
_trainHoppingListenerRemover = null;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case AnimationStatus.forward:
|
|
||||||
case AnimationStatus.reverse:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_trainHoppingListenerRemover = () {
|
|
||||||
nextTrain.removeStatusListener(jumpOnAnimationEnd);
|
|
||||||
newAnimation?.dispose();
|
|
||||||
};
|
|
||||||
nextTrain.addStatusListener(jumpOnAnimationEnd);
|
|
||||||
newAnimation = TrainHoppingAnimation(
|
|
||||||
currentTrain,
|
|
||||||
nextTrain,
|
|
||||||
onSwitchedTrain: () {
|
|
||||||
assert(_secondaryAnimation.parent == newAnimation);
|
|
||||||
assert(newAnimation!.currentTrain == nextRoute._animation);
|
|
||||||
// We can hop on the nextTrain, so we don't need to listen to
|
|
||||||
// whether the nextTrain has stopped.
|
|
||||||
_setSecondaryAnimation(newAnimation!.currentTrain, nextRoute.completed);
|
|
||||||
if (_trainHoppingListenerRemover != null) {
|
if (_trainHoppingListenerRemover != null) {
|
||||||
_trainHoppingListenerRemover!();
|
_trainHoppingListenerRemover!();
|
||||||
_trainHoppingListenerRemover = null;
|
_trainHoppingListenerRemover = null;
|
||||||
}
|
}
|
||||||
},
|
break;
|
||||||
);
|
case AnimationStatus.forward:
|
||||||
_setSecondaryAnimation(newAnimation, nextRoute.completed);
|
case AnimationStatus.reverse:
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else { // This route has no secondary animation.
|
_trainHoppingListenerRemover = () {
|
||||||
_setSecondaryAnimation(nextRoute._animation, nextRoute.completed);
|
nextTrain.removeStatusListener(jumpOnAnimationEnd);
|
||||||
|
newAnimation?.dispose();
|
||||||
|
};
|
||||||
|
nextTrain.addStatusListener(jumpOnAnimationEnd);
|
||||||
|
newAnimation = TrainHoppingAnimation(
|
||||||
|
currentTrain,
|
||||||
|
nextTrain,
|
||||||
|
onSwitchedTrain: () {
|
||||||
|
assert(_secondaryAnimation.parent == newAnimation);
|
||||||
|
assert(newAnimation!.currentTrain == nextRoute._animation);
|
||||||
|
// We can hop on the nextTrain, so we don't need to listen to
|
||||||
|
// whether the nextTrain has stopped.
|
||||||
|
_setSecondaryAnimation(newAnimation!.currentTrain, nextRoute.completed);
|
||||||
|
if (_trainHoppingListenerRemover != null) {
|
||||||
|
_trainHoppingListenerRemover!();
|
||||||
|
_trainHoppingListenerRemover = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
_setSecondaryAnimation(newAnimation, nextRoute.completed);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// This route cannot coordinate transitions with nextRoute, so it should
|
_setSecondaryAnimation(nextRoute._animation, nextRoute.completed);
|
||||||
// have no visible secondary animation. By using an AnimationMin, the
|
|
||||||
// animation's value will always be zero, but it will have nextRoute.animation's
|
|
||||||
// status until it finishes, allowing this route to wait until all visible
|
|
||||||
// transitions are complete to stop ignoring pointers.
|
|
||||||
_setSecondaryAnimation(
|
|
||||||
AnimationMin<double>(kAlwaysDismissedAnimation, nextRoute._animation!),
|
|
||||||
nextRoute.completed,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} else { // The next route is not a TransitionRoute.
|
} else {
|
||||||
_setSecondaryAnimation(kAlwaysDismissedAnimation);
|
_setSecondaryAnimation(kAlwaysDismissedAnimation);
|
||||||
}
|
}
|
||||||
// Finally, we dispose any previous train hopping animation because it
|
// Finally, we dispose any previous train hopping animation because it
|
||||||
@ -408,9 +396,9 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> {
|
|||||||
/// the [nextRoute] is popped off of this route, the
|
/// the [nextRoute] is popped off of this route, the
|
||||||
/// `secondaryAnimation` will run from 1.0 - 0.0.
|
/// `secondaryAnimation` will run from 1.0 - 0.0.
|
||||||
///
|
///
|
||||||
/// If false, this route's [ModalRoute.buildTransitions] `secondaryAnimation`
|
/// If false, this route's [ModalRoute.buildTransitions] `secondaryAnimation` parameter
|
||||||
/// will proxy an animation with a constant value of 0. In other words, this
|
/// value will be [kAlwaysDismissedAnimation]. In other words, this route
|
||||||
/// route will not animate when [nextRoute] is pushed on top of it or when
|
/// will not animate when [nextRoute] is pushed on top of it or when
|
||||||
/// [nextRoute] is popped off of it.
|
/// [nextRoute] is popped off of it.
|
||||||
///
|
///
|
||||||
/// Returns true by default.
|
/// Returns true by default.
|
||||||
@ -858,19 +846,17 @@ class _ModalScopeState<T> extends State<_ModalScope<T>> {
|
|||||||
context,
|
context,
|
||||||
widget.route.animation!,
|
widget.route.animation!,
|
||||||
widget.route.secondaryAnimation!,
|
widget.route.secondaryAnimation!,
|
||||||
// _listenable updates when this route's animations change
|
// This additional AnimatedBuilder is include because if the
|
||||||
// values, but the _ignorePointerNotifier can also update
|
// value of the userGestureInProgressNotifier changes, it's
|
||||||
// when the status of animations on popping routes change,
|
// only necessary to rebuild the IgnorePointer widget and set
|
||||||
// even when this route's animations' values don't. Also,
|
// the focus node's ability to focus.
|
||||||
// when the value of the _ignorePointerNotifier changes,
|
|
||||||
// it's only necessary to rebuild the IgnorePointer
|
|
||||||
// widget and set the focus node's ability to focus.
|
|
||||||
AnimatedBuilder(
|
AnimatedBuilder(
|
||||||
animation: widget.route._ignorePointerNotifier,
|
animation: widget.route.navigator?.userGestureInProgressNotifier ?? ValueNotifier<bool>(false),
|
||||||
builder: (BuildContext context, Widget? child) {
|
builder: (BuildContext context, Widget? child) {
|
||||||
focusScopeNode.canRequestFocus = !_shouldIgnoreFocusRequest;
|
final bool ignoreEvents = _shouldIgnoreFocusRequest;
|
||||||
|
focusScopeNode.canRequestFocus = !ignoreEvents;
|
||||||
return IgnorePointer(
|
return IgnorePointer(
|
||||||
ignoring: widget.route._ignorePointer,
|
ignoring: ignoreEvents,
|
||||||
child: child,
|
child: child,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -1154,36 +1140,11 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
|
|||||||
return child;
|
return child;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether this route should ignore pointers when transitions are in progress.
|
|
||||||
///
|
|
||||||
/// Pointers always are ignored when [isCurrent] is false (e.g., when a route
|
|
||||||
/// has a new route pushed on top of it, or during a route's exit transition
|
|
||||||
/// after popping). Override this value to also ignore pointers on pages during
|
|
||||||
/// transitions where this route is the current route (e.g., after the route
|
|
||||||
/// above this route pops, or during this route's entrance transition).
|
|
||||||
///
|
|
||||||
/// Returns false by default.
|
|
||||||
///
|
|
||||||
/// See also:
|
|
||||||
///
|
|
||||||
/// * [CupertinoRouteTransitionMixin], [CupertinoModalPopupRoute], and
|
|
||||||
/// [CupertinoDialogRoute], which use this property to specify that
|
|
||||||
/// Cupertino routes ignore pointers during transitions.
|
|
||||||
@protected
|
|
||||||
bool get ignorePointerDuringTransitions => false;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void install() {
|
void install() {
|
||||||
super.install();
|
super.install();
|
||||||
_animationProxy = ProxyAnimation(super.animation)
|
_animationProxy = ProxyAnimation(super.animation);
|
||||||
..addStatusListener(_handleAnimationStatusChanged);
|
_secondaryAnimationProxy = ProxyAnimation(super.secondaryAnimation);
|
||||||
_secondaryAnimationProxy = ProxyAnimation(super.secondaryAnimation)
|
|
||||||
..addStatusListener(_handleAnimationStatusChanged);
|
|
||||||
navigator!.userGestureInProgressNotifier.addListener(_maybeUpdateIgnorePointer);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handleAnimationStatusChanged(AnimationStatus status) {
|
|
||||||
_maybeUpdateIgnorePointer();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -1419,19 +1380,6 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
|
|||||||
Animation<double>? get secondaryAnimation => _secondaryAnimationProxy;
|
Animation<double>? get secondaryAnimation => _secondaryAnimationProxy;
|
||||||
ProxyAnimation? _secondaryAnimationProxy;
|
ProxyAnimation? _secondaryAnimationProxy;
|
||||||
|
|
||||||
bool get _ignorePointer => _ignorePointerNotifier.value;
|
|
||||||
final ValueNotifier<bool> _ignorePointerNotifier = ValueNotifier<bool>(false);
|
|
||||||
|
|
||||||
void _maybeUpdateIgnorePointer() {
|
|
||||||
bool isTransitioning(Animation<double>? animation) {
|
|
||||||
return animation?.status == AnimationStatus.forward || animation?.status == AnimationStatus.reverse;
|
|
||||||
}
|
|
||||||
_ignorePointerNotifier.value = !isCurrent ||
|
|
||||||
(navigator?.userGestureInProgress ?? false) ||
|
|
||||||
(ignorePointerDuringTransitions &&
|
|
||||||
(isTransitioning(animation) || isTransitioning(secondaryAnimation)));
|
|
||||||
}
|
|
||||||
|
|
||||||
final List<WillPopCallback> _willPopCallbacks = <WillPopCallback>[];
|
final List<WillPopCallback> _willPopCallbacks = <WillPopCallback>[];
|
||||||
|
|
||||||
/// Returns [RoutePopDisposition.doNotPop] if any of callbacks added with
|
/// Returns [RoutePopDisposition.doNotPop] if any of callbacks added with
|
||||||
@ -1650,14 +1598,9 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
|
|||||||
child: barrier,
|
child: barrier,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
barrier = AnimatedBuilder(
|
barrier = IgnorePointer(
|
||||||
animation: _ignorePointerNotifier,
|
ignoring: animation!.status == AnimationStatus.reverse || // changedInternalState is called when animation.status updates
|
||||||
builder: (BuildContext context, Widget? child) {
|
animation!.status == AnimationStatus.dismissed, // dismissed is possible when doing a manual pop gesture
|
||||||
return IgnorePointer(
|
|
||||||
ignoring: _ignorePointer,
|
|
||||||
child: child,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: barrier,
|
child: barrier,
|
||||||
);
|
);
|
||||||
if (semanticsDismissible && barrierDismissible) {
|
if (semanticsDismissible && barrierDismissible) {
|
||||||
|
@ -29,13 +29,13 @@ void main() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
await tester.tap(find.text('Go'));
|
await tester.tap(find.text('Go'));
|
||||||
await tester.pumpAndSettle();
|
await tester.pump();
|
||||||
|
|
||||||
expect(find.byType(CupertinoActionSheet), findsOneWidget);
|
expect(find.text('Action Sheet'), findsOneWidget);
|
||||||
|
|
||||||
await tester.tap(find.byType(ModalBarrier).last);
|
await tester.tapAt(const Offset(20.0, 20.0));
|
||||||
await tester.pumpAndSettle();
|
await tester.pump();
|
||||||
expect(find.byType(CupertinoActionSheet), findsNothing);
|
expect(find.text('Action Sheet'), findsNothing);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('Verify that a tap on title section (not buttons) does not dismiss an action sheet', (WidgetTester tester) async {
|
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() {
|
|||||||
expect(find.byType(CupertinoActionSheet), findsNothing);
|
expect(find.byType(CupertinoActionSheet), findsNothing);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('Modal barrier cannot be dismissed during transition', (WidgetTester tester) async {
|
testWidgets('Modal barrier is pressed during transition', (WidgetTester tester) async {
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
createAppWithButtonThatLaunchesActionSheet(
|
createAppWithButtonThatLaunchesActionSheet(
|
||||||
CupertinoActionSheet(
|
CupertinoActionSheet(
|
||||||
@ -906,20 +906,21 @@ void main() {
|
|||||||
await tester.pump(const Duration(milliseconds: 60));
|
await tester.pump(const Duration(milliseconds: 60));
|
||||||
expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, moreOrLessEquals(337.1, epsilon: 0.1));
|
expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, moreOrLessEquals(337.1, epsilon: 0.1));
|
||||||
|
|
||||||
// Attempt to dismiss
|
// Exit animation
|
||||||
await tester.tapAt(const Offset(20.0, 20.0));
|
await tester.tapAt(const Offset(20.0, 20.0));
|
||||||
await tester.pump(const Duration(milliseconds: 60));
|
await tester.pump(const Duration(milliseconds: 60));
|
||||||
|
|
||||||
// Enter animation is continuing
|
await tester.pump(const Duration(milliseconds: 60));
|
||||||
expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, moreOrLessEquals(325.4, epsilon: 0.1));
|
expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, moreOrLessEquals(374.3, epsilon: 0.1));
|
||||||
|
|
||||||
await tester.pumpAndSettle();
|
await tester.pump(const Duration(milliseconds: 60));
|
||||||
|
expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, moreOrLessEquals(470.0, epsilon: 0.1));
|
||||||
|
|
||||||
// Attempt to dismiss again
|
await tester.pump(const Duration(milliseconds: 60));
|
||||||
await tester.tapAt(const Offset(20.0, 20.0));
|
expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, 600.0);
|
||||||
await tester.pumpAndSettle();
|
|
||||||
|
|
||||||
// Action sheet has disappeared
|
// Action sheet has disappeared
|
||||||
|
await tester.pump(const Duration(milliseconds: 60));
|
||||||
expect(find.byType(CupertinoActionSheet), findsNothing);
|
expect(find.byType(CupertinoActionSheet), findsNothing);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -951,7 +952,7 @@ void main() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
await tester.tap(find.text('Go'));
|
await tester.tap(find.text('Go'));
|
||||||
await tester.pumpAndSettle();
|
await tester.pump();
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
semantics,
|
semantics,
|
||||||
|
@ -1074,8 +1074,6 @@ void main() {
|
|||||||
transition = tester.firstWidget(fadeTransitionFinder);
|
transition = tester.firstWidget(fadeTransitionFinder);
|
||||||
expect(transition.opacity.value, moreOrLessEquals(1.0, epsilon: 0.001));
|
expect(transition.opacity.value, moreOrLessEquals(1.0, epsilon: 0.001));
|
||||||
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
|
|
||||||
await tester.tap(find.text('Delete'));
|
await tester.tap(find.text('Delete'));
|
||||||
|
|
||||||
// Exit animation, look at reverse FadeTransition.
|
// Exit animation, look at reverse FadeTransition.
|
||||||
|
@ -442,8 +442,6 @@ void main() {
|
|||||||
await tester.pump(const Duration(milliseconds: 40));
|
await tester.pump(const Duration(milliseconds: 40));
|
||||||
expect(tester.getTopLeft(find.byType(Placeholder)).dy, moreOrLessEquals(0.0, epsilon: 0.1));
|
expect(tester.getTopLeft(find.byType(Placeholder)).dy, moreOrLessEquals(0.0, epsilon: 0.1));
|
||||||
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
|
|
||||||
// Exit animation
|
// Exit animation
|
||||||
await tester.tap(find.text('Close'));
|
await tester.tap(find.text('Close'));
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
@ -549,8 +547,6 @@ void main() {
|
|||||||
await tester.pump(const Duration(milliseconds: 40));
|
await tester.pump(const Duration(milliseconds: 40));
|
||||||
expect(tester.getTopLeft(find.byType(Placeholder)).dx, moreOrLessEquals(-267.0, epsilon: 1.0));
|
expect(tester.getTopLeft(find.byType(Placeholder)).dx, moreOrLessEquals(-267.0, epsilon: 1.0));
|
||||||
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
|
|
||||||
// Exit animation
|
// Exit animation
|
||||||
await tester.tap(find.text('Close'));
|
await tester.tap(find.text('Close'));
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
@ -640,8 +636,6 @@ void main() {
|
|||||||
await tester.pump(const Duration(milliseconds: 40));
|
await tester.pump(const Duration(milliseconds: 40));
|
||||||
expect(tester.getTopLeft(find.byType(Placeholder)).dx, 0.0);
|
expect(tester.getTopLeft(find.byType(Placeholder)).dx, 0.0);
|
||||||
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
|
|
||||||
// Exit animation
|
// Exit animation
|
||||||
await tester.tap(find.text('Close'));
|
await tester.tap(find.text('Close'));
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
@ -661,221 +655,6 @@ void main() {
|
|||||||
await testNoParallax(tester, fromFullscreenDialog: true);
|
await testNoParallax(tester, fromFullscreenDialog: true);
|
||||||
});
|
});
|
||||||
|
|
||||||
group('Route interactivity during transition animations', () {
|
|
||||||
testWidgets('CupertinoPageRoute ignores pointers when route on top of it pops', (WidgetTester tester) async {
|
|
||||||
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
|
||||||
bool homeTapped = false;
|
|
||||||
await tester.pumpWidget(
|
|
||||||
CupertinoApp(
|
|
||||||
navigatorKey: navigatorKey,
|
|
||||||
home: TextButton(
|
|
||||||
onPressed: () => homeTapped = true,
|
|
||||||
child: const Text('Home'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
navigatorKey.currentState!.push<void>(
|
|
||||||
CupertinoPageRoute<void>(
|
|
||||||
builder: (_) => const Text('Page 2'),
|
|
||||||
)
|
|
||||||
);
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
|
|
||||||
expect(find.text('Page 2'), findsOneWidget);
|
|
||||||
|
|
||||||
navigatorKey.currentState!.pop();
|
|
||||||
await tester.pump(const Duration(milliseconds: 100));
|
|
||||||
|
|
||||||
expect(find.text('Page 2'), findsOneWidget); // Transition still in progress
|
|
||||||
|
|
||||||
await tester.tap(find.text('Home'), warnIfMissed: false); // Home route is not tappable
|
|
||||||
expect(homeTapped, false);
|
|
||||||
|
|
||||||
await tester.pumpAndSettle(); // Transition completes
|
|
||||||
|
|
||||||
await tester.tap(find.text('Home'));
|
|
||||||
expect(homeTapped, true);
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('fullscreenDialog CupertinoPageRoute ignores pointers when route on top of it pops', (WidgetTester tester) async {
|
|
||||||
bool homeTapped = false;
|
|
||||||
await tester.pumpWidget(
|
|
||||||
CupertinoApp(
|
|
||||||
home: TextButton(
|
|
||||||
onPressed: () => homeTapped = true,
|
|
||||||
child: const Text('Home'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
tester.state<NavigatorState>(find.byType(Navigator)).push<void>(
|
|
||||||
CupertinoPageRoute<void>(
|
|
||||||
fullscreenDialog: true,
|
|
||||||
builder: (_) => const Text('Page 2'),
|
|
||||||
)
|
|
||||||
);
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
|
|
||||||
expect(find.text('Page 2'), findsOneWidget);
|
|
||||||
|
|
||||||
tester.state<NavigatorState>(find.byType(Navigator)).pop();
|
|
||||||
await tester.pump(const Duration(milliseconds: 100));
|
|
||||||
|
|
||||||
expect(find.text('Page 2'), findsOneWidget); // Transition still in progress
|
|
||||||
|
|
||||||
await tester.tap(find.text('Home'), warnIfMissed: false); // Home route is not tappable
|
|
||||||
expect(homeTapped, false);
|
|
||||||
|
|
||||||
await tester.pumpAndSettle(); // Transition completes
|
|
||||||
|
|
||||||
await tester.tap(find.text('Home'));
|
|
||||||
expect(homeTapped, true);
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('CupertinoPageRoute ignores pointers when user pop gesture is in progress', (WidgetTester tester) async {
|
|
||||||
bool homeTapped = false;
|
|
||||||
await tester.pumpWidget(
|
|
||||||
CupertinoApp(
|
|
||||||
home: TextButton(
|
|
||||||
onPressed: () => homeTapped = true,
|
|
||||||
child: const Text('Page 1'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
tester.state<NavigatorState>(find.byType(Navigator)).push(
|
|
||||||
CupertinoPageRoute<void>(
|
|
||||||
builder: (_) => const Text('Page 2'),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
|
|
||||||
expect(find.text('Page 1'), findsNothing);
|
|
||||||
|
|
||||||
final TestGesture swipeGesture = await tester.startGesture(const Offset(5, 100));
|
|
||||||
|
|
||||||
await swipeGesture.moveBy(const Offset(100, 0));
|
|
||||||
await tester.pump();
|
|
||||||
|
|
||||||
expect(find.text('Page 1'), findsOneWidget);
|
|
||||||
expect(tester.state<NavigatorState>(find.byType(Navigator)).userGestureInProgress, true);
|
|
||||||
|
|
||||||
await tester.tap(find.text('Page 1'), warnIfMissed: false);
|
|
||||||
expect(homeTapped, false);
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('CupertinoPageRoute ignores pointers when it is pushed on top of other route', (WidgetTester tester) async {
|
|
||||||
await tester.pumpWidget(
|
|
||||||
CupertinoApp(
|
|
||||||
onGenerateRoute: (_) => CupertinoPageRoute<void>(
|
|
||||||
builder: (_) => const Text('Home'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
await tester.tap(find.text('Home'));
|
|
||||||
tester.state<NavigatorState>(find.byType(Navigator)).push(
|
|
||||||
CupertinoPageRoute<void>(
|
|
||||||
builder: (_) => const Text('Page 2'),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
await tester.pump();
|
|
||||||
await tester.pump(const Duration(milliseconds: 100));
|
|
||||||
|
|
||||||
expect(find.text('Home'), findsOneWidget); // Transition still in progress
|
|
||||||
|
|
||||||
// Can't test directly for taps because route is interactive but offstage
|
|
||||||
// One ignore pointer for each of two overlay entries (ModalScope, ModalBarrier) on each of two routes
|
|
||||||
expect(find.byType(IgnorePointer, skipOffstage: false), findsNWidgets(4));
|
|
||||||
final List<Element> ignorePointers = find.byType(IgnorePointer, skipOffstage: false).evaluate().toList();
|
|
||||||
expect((ignorePointers.first.widget as IgnorePointer).ignoring, true); // Home modalBarrier
|
|
||||||
expect((ignorePointers[1].widget as IgnorePointer).ignoring, true); // Home modalScope
|
|
||||||
expect((ignorePointers[2].widget as IgnorePointer).ignoring, true); // Page 2 modalBarrier
|
|
||||||
expect((ignorePointers.last.widget as IgnorePointer).ignoring, true); // Page 2 modalScope
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('showCupertinoDialog ignores pointers until transition completes', (WidgetTester tester) async {
|
|
||||||
await tester.pumpWidget(
|
|
||||||
CupertinoApp(
|
|
||||||
home: Builder(
|
|
||||||
builder: (BuildContext context) {
|
|
||||||
return TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
showCupertinoModalPopup<void>(
|
|
||||||
context: context,
|
|
||||||
builder: (BuildContext innerContext) => TextButton(
|
|
||||||
onPressed: Navigator.of(innerContext).pop,
|
|
||||||
child: const Text('dialog'),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: const Text('Show Dialog'),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Open the dialog.
|
|
||||||
await tester.tap(find.byType(TextButton));
|
|
||||||
await tester.pump(const Duration(milliseconds: 100));
|
|
||||||
|
|
||||||
// Trigger pop while the transition is in progress
|
|
||||||
await tester.tap(find.text('dialog'), warnIfMissed: false);
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
|
|
||||||
// Transition is over and the dialog has not been dismissed
|
|
||||||
expect(find.text('dialog'), findsOneWidget);
|
|
||||||
|
|
||||||
await tester.tap(find.text('dialog'));
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
|
|
||||||
// The dialog has not been dismissed
|
|
||||||
expect(find.text('dialog'), findsNothing);
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('showCupertinoModalPopup ignores pointers until transition completes', (WidgetTester tester) async {
|
|
||||||
await tester.pumpWidget(
|
|
||||||
CupertinoApp(
|
|
||||||
home: Builder(
|
|
||||||
builder: (BuildContext context) {
|
|
||||||
return TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
showCupertinoModalPopup<void>(
|
|
||||||
context: context,
|
|
||||||
builder: (BuildContext innerContext) => TextButton(
|
|
||||||
onPressed: Navigator.of(innerContext).pop,
|
|
||||||
child: const Text('modal'),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: const Text('Show modal'),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Open the modal popup
|
|
||||||
await tester.tap(find.byType(TextButton));
|
|
||||||
await tester.pump(const Duration(milliseconds: 100));
|
|
||||||
|
|
||||||
// Trigger pop while the transition is in progress
|
|
||||||
await tester.tap(find.text('modal'), warnIfMissed: false);
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
|
|
||||||
// Transition is over and the dialog has not been dismissed
|
|
||||||
expect(find.text('modal'), findsOneWidget);
|
|
||||||
|
|
||||||
await tester.tap(find.text('modal'));
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
|
|
||||||
// The dialog has not been dismissed
|
|
||||||
expect(find.text('modal'), findsNothing);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('Animated push/pop is not linear', (WidgetTester tester) async {
|
testWidgets('Animated push/pop is not linear', (WidgetTester tester) async {
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
const CupertinoApp(
|
const CupertinoApp(
|
||||||
|
@ -432,7 +432,6 @@ void main() {
|
|||||||
routes: routes,
|
routes: routes,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
expect(await tester.pumpAndSettle(const Duration(minutes: 1)), 2);
|
|
||||||
await tester.tap(find.text('PUSH'));
|
await tester.tap(find.text('PUSH'));
|
||||||
expect(await tester.pumpAndSettle(const Duration(minutes: 1)), 2);
|
expect(await tester.pumpAndSettle(const Duration(minutes: 1)), 2);
|
||||||
expect(find.text('PUSH'), findsNothing);
|
expect(find.text('PUSH'), findsNothing);
|
||||||
@ -791,8 +790,8 @@ void main() {
|
|||||||
|
|
||||||
// Tapping the "page" route's back button doesn't do anything either.
|
// Tapping the "page" route's back button doesn't do anything either.
|
||||||
await tester.tap(find.byTooltip('Back'), warnIfMissed: false);
|
await tester.tap(find.byTooltip('Back'), warnIfMissed: false);
|
||||||
await tester.pump();
|
await tester.pumpAndSettle();
|
||||||
expect(tester.getTopLeft(find.byKey(pageScaffoldKey, skipOffstage: false)), const Offset(400, 0));
|
expect(tester.getTopLeft(find.byKey(pageScaffoldKey)), const Offset(400, 0));
|
||||||
expect(tester.getTopLeft(find.byKey(homeScaffoldKey)).dx, lessThan(0));
|
expect(tester.getTopLeft(find.byKey(homeScaffoldKey)).dx, lessThan(0));
|
||||||
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
|
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
|
||||||
|
|
||||||
|
@ -940,36 +940,6 @@ void main() {
|
|||||||
expect(trainHopper2.currentTrain, isNull); // Has been disposed.
|
expect(trainHopper2.currentTrain, isNull); // Has been disposed.
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('secondary animation is AnimationMin when transition route that cannot be transitioned to or from pops', (WidgetTester tester) async {
|
|
||||||
final PageRoute<void> pageRouteOne = MaterialPageRoute<void>(
|
|
||||||
builder: (_) => const Text('Page One'),
|
|
||||||
);
|
|
||||||
|
|
||||||
await tester.pumpWidget(
|
|
||||||
MaterialApp(
|
|
||||||
onGenerateRoute: (_) => pageRouteOne,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
final PageRoute<void> pageRouteTwo = MaterialPageRoute<void>(
|
|
||||||
fullscreenDialog: true,
|
|
||||||
builder: (_) => const Text('Page Two'),
|
|
||||||
);
|
|
||||||
|
|
||||||
tester.state<NavigatorState>(find.byType(Navigator)).push(pageRouteTwo);
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
|
|
||||||
tester.state<NavigatorState>(find.byType(Navigator)).pop();
|
|
||||||
await tester.pump();
|
|
||||||
|
|
||||||
expect(
|
|
||||||
((pageRouteOne.secondaryAnimation! as ProxyAnimation).parent! as ProxyAnimation).parent,
|
|
||||||
isA<AnimationMin<double>>()
|
|
||||||
..having((AnimationMin<double> a) => a.first, 'first', equals(kAlwaysDismissedAnimation))
|
|
||||||
..having((AnimationMin<double> a) => a.next, 'first', equals(pageRouteTwo.animation)),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('secondary animation is triggered when pop initial route', (WidgetTester tester) async {
|
testWidgets('secondary animation is triggered when pop initial route', (WidgetTester tester) async {
|
||||||
final GlobalKey<NavigatorState> navigator = GlobalKey<NavigatorState>();
|
final GlobalKey<NavigatorState> navigator = GlobalKey<NavigatorState>();
|
||||||
late Animation<double> secondaryAnimationOfRouteOne;
|
late Animation<double> secondaryAnimationOfRouteOne;
|
||||||
@ -1041,41 +1011,6 @@ void main() {
|
|||||||
expect(find.byType(ModalBarrier), findsNWidgets(1));
|
expect(find.byType(ModalBarrier), findsNWidgets(1));
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('showGeneralDialog ModalBarrier does not ignore pointers during transitions', (WidgetTester tester) async {
|
|
||||||
await tester.pumpWidget(
|
|
||||||
MaterialApp(
|
|
||||||
home: Builder(
|
|
||||||
builder: (BuildContext context) {
|
|
||||||
return TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
showGeneralDialog<void>(
|
|
||||||
context: context,
|
|
||||||
transitionDuration: const Duration(milliseconds: 400),
|
|
||||||
pageBuilder: (BuildContext innerContext, __, ___) => TextButton(
|
|
||||||
onPressed: Navigator.of(innerContext).pop,
|
|
||||||
child: const Text('dialog'),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: const Text('Show Dialog'),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Open the dialog.
|
|
||||||
await tester.tap(find.byType(TextButton));
|
|
||||||
await tester.pump(const Duration(milliseconds: 200));
|
|
||||||
|
|
||||||
// Trigger pop while the transition is in progress
|
|
||||||
await tester.tap(find.text('dialog'));
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
|
|
||||||
// The dialog has been dismissed mid-transition
|
|
||||||
expect(find.text('dialog'), findsNothing);
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('showGeneralDialog adds non-dismissible barrier when barrierDismissible is false', (WidgetTester tester) async {
|
testWidgets('showGeneralDialog adds non-dismissible barrier when barrierDismissible is false', (WidgetTester tester) async {
|
||||||
await tester.pumpWidget(MaterialApp(
|
await tester.pumpWidget(MaterialApp(
|
||||||
home: Builder(
|
home: Builder(
|
||||||
@ -1371,67 +1306,6 @@ void main() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('does not ignore pointers when route on top of it pops', (WidgetTester tester) async {
|
|
||||||
await tester.pumpWidget(
|
|
||||||
MaterialApp(
|
|
||||||
theme: ThemeData(
|
|
||||||
pageTransitionsTheme: const PageTransitionsTheme(
|
|
||||||
builders: <TargetPlatform, PageTransitionsBuilder>{
|
|
||||||
// Use a transitions builder that will keep the underlying content
|
|
||||||
// partially visible during a transition
|
|
||||||
TargetPlatform.android: FadeUpwardsPageTransitionsBuilder(),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
),
|
|
||||||
home: const Text('Home'),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
tester.state<NavigatorState>(find.byType(Navigator)).push<void>(
|
|
||||||
MaterialPageRoute<void>(builder: (_) => const Text('Page 2'))
|
|
||||||
);
|
|
||||||
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
expect(find.text('Page 2'), findsOneWidget);
|
|
||||||
|
|
||||||
tester.state<NavigatorState>(find.byType(Navigator)).pop();
|
|
||||||
await tester.pump(const Duration(milliseconds: 100));
|
|
||||||
|
|
||||||
expect(find.text('Page 2'), findsOneWidget); // Transition still in progress
|
|
||||||
|
|
||||||
await tester.tap(find.text('Home')); // Home route is tappable
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('does not ignore pointers during its own entrance animation', (WidgetTester tester) async {
|
|
||||||
await tester.pumpWidget(
|
|
||||||
MaterialApp(
|
|
||||||
onGenerateRoute: (_) => MaterialPageRoute<void>(
|
|
||||||
builder: (_) => const Text('Home'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
await tester.tap(find.text('Home'));
|
|
||||||
tester.state<NavigatorState>(find.byType(Navigator)).push(
|
|
||||||
MaterialPageRoute<void>(
|
|
||||||
builder: (_) => const Text('Page 2'),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
await tester.pump();
|
|
||||||
await tester.pump(const Duration(milliseconds: 100));
|
|
||||||
|
|
||||||
expect(find.text('Home'), findsOneWidget); // Transition still in progress
|
|
||||||
|
|
||||||
// Can't test directly for taps because route is interactive but offstage
|
|
||||||
// One ignore pointer for each of two overlay entries (ModalScope, ModalBarrier) on each of two routes
|
|
||||||
expect(find.byType(IgnorePointer, skipOffstage: false), findsNWidgets(4));
|
|
||||||
final List<Element> ignorePointers = find.byType(IgnorePointer, skipOffstage: false).evaluate().toList();
|
|
||||||
expect((ignorePointers.first.widget as IgnorePointer).ignoring, true); // Home modalBarrier
|
|
||||||
expect((ignorePointers[1].widget as IgnorePointer).ignoring, true); // Home modalScope
|
|
||||||
expect((ignorePointers[2].widget as IgnorePointer).ignoring, false); // Page 2 modalBarrier
|
|
||||||
expect((ignorePointers.last.widget as IgnorePointer).ignoring, false); // Page 2 modalScope
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('reverseTransitionDuration defaults to transitionDuration', (WidgetTester tester) async {
|
testWidgets('reverseTransitionDuration defaults to transitionDuration', (WidgetTester tester) async {
|
||||||
final GlobalKey containerKey = GlobalKey();
|
final GlobalKey containerKey = GlobalKey();
|
||||||
|
|
||||||
|
@ -440,7 +440,7 @@ void main() {
|
|||||||
|
|
||||||
await tester.tap(find.text('Next'));
|
await tester.tap(find.text('Next'));
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
await tester.pumpAndSettle();
|
await tester.pump(const Duration(milliseconds: 400));
|
||||||
|
|
||||||
await tester.pageBack();
|
await tester.pageBack();
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user