Unverified Commit 58ad6e1b authored by Taha Tesser's avatar Taha Tesser Committed by GitHub

Dismiss Modal Barrier on `handleTapCancel` (#98191)

parent 0acfb216
...@@ -47,7 +47,8 @@ class ModalBarrier extends StatelessWidget { ...@@ -47,7 +47,8 @@ class ModalBarrier extends StatelessWidget {
/// [ModalBarrier] built by [ModalRoute] pages. /// [ModalBarrier] built by [ModalRoute] pages.
final Color? color; final Color? color;
/// Specifies if the barrier will be dismissed when the user taps on it. /// Specifies if the barrier will be dismissed when the user taps or
/// performs a scroll gesture on it.
/// ///
/// If true, and [onDismiss] is non-null, [onDismiss] will be called, /// If true, and [onDismiss] is non-null, [onDismiss] will be called,
/// otherwise the current route will be popped from the ambient [Navigator]. /// otherwise the current route will be popped from the ambient [Navigator].
...@@ -228,12 +229,14 @@ class _AnyTapGestureRecognizer extends BaseTapGestureRecognizer { ...@@ -228,12 +229,14 @@ class _AnyTapGestureRecognizer extends BaseTapGestureRecognizer {
: super(debugOwner: debugOwner); : super(debugOwner: debugOwner);
VoidCallback? onAnyTapUp; VoidCallback? onAnyTapUp;
VoidCallback? onAnyTapCancel;
@protected @protected
@override @override
bool isPointerAllowed(PointerDownEvent event) { bool isPointerAllowed(PointerDownEvent event) {
if (onAnyTapUp == null) if (onAnyTapUp == null && onAnyTapCancel == null) {
return false; return false;
}
return super.isPointerAllowed(event); return super.isPointerAllowed(event);
} }
...@@ -252,7 +255,7 @@ class _AnyTapGestureRecognizer extends BaseTapGestureRecognizer { ...@@ -252,7 +255,7 @@ class _AnyTapGestureRecognizer extends BaseTapGestureRecognizer {
@protected @protected
@override @override
void handleTapCancel({PointerDownEvent? down, PointerCancelEvent? cancel, String? reason}) { void handleTapCancel({PointerDownEvent? down, PointerCancelEvent? cancel, String? reason}) {
// Do nothing. onAnyTapCancel?.call();
} }
@override @override
...@@ -272,9 +275,10 @@ class _ModalBarrierSemanticsDelegate extends SemanticsGestureDelegate { ...@@ -272,9 +275,10 @@ class _ModalBarrierSemanticsDelegate extends SemanticsGestureDelegate {
class _AnyTapGestureRecognizerFactory extends GestureRecognizerFactory<_AnyTapGestureRecognizer> { class _AnyTapGestureRecognizerFactory extends GestureRecognizerFactory<_AnyTapGestureRecognizer> {
const _AnyTapGestureRecognizerFactory({this.onAnyTapUp}); const _AnyTapGestureRecognizerFactory({this.onAnyTapUp, this.onAnyTapCancel});
final VoidCallback? onAnyTapUp; final VoidCallback? onAnyTapUp;
final VoidCallback? onAnyTapCancel;
@override @override
_AnyTapGestureRecognizer constructor() => _AnyTapGestureRecognizer(); _AnyTapGestureRecognizer constructor() => _AnyTapGestureRecognizer();
...@@ -282,6 +286,7 @@ class _AnyTapGestureRecognizerFactory extends GestureRecognizerFactory<_AnyTapGe ...@@ -282,6 +286,7 @@ class _AnyTapGestureRecognizerFactory extends GestureRecognizerFactory<_AnyTapGe
@override @override
void initializer(_AnyTapGestureRecognizer instance) { void initializer(_AnyTapGestureRecognizer instance) {
instance.onAnyTapUp = onAnyTapUp; instance.onAnyTapUp = onAnyTapUp;
instance.onAnyTapCancel = onAnyTapCancel;
} }
} }
...@@ -307,7 +312,7 @@ class _ModalBarrierGestureDetector extends StatelessWidget { ...@@ -307,7 +312,7 @@ class _ModalBarrierGestureDetector extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{ final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{
_AnyTapGestureRecognizer: _AnyTapGestureRecognizerFactory(onAnyTapUp: onDismiss), _AnyTapGestureRecognizer: _AnyTapGestureRecognizerFactory(onAnyTapUp: onDismiss, onAnyTapCancel: onDismiss),
}; };
return RawGestureDetector( return RawGestureDetector(
......
...@@ -251,6 +251,39 @@ void main() { ...@@ -251,6 +251,39 @@ void main() {
); );
}); });
testWidgets('ModalBarrier pops the Navigator when dismissed by tap cancel', (WidgetTester tester) async {
final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
'/': (BuildContext context) => const FirstWidget(),
'/modal': (BuildContext context) => const SecondWidget(),
};
await tester.pumpWidget(MaterialApp(routes: routes));
// Initially the barrier is not visible
expect(find.byKey(const ValueKey<String>('barrier')), findsNothing);
// Tapping on X routes to the barrier
await tester.tap(find.text('X'));
await tester.pump(); // begin transition
await tester.pump(const Duration(seconds: 1)); // end transition
// Press the barrier; it shouldn't dismiss yet
final TestGesture gesture = await tester.press(
find.byKey(const ValueKey<String>('barrier')),
);
await tester.pumpAndSettle(); // begin transition
expect(find.byKey(const ValueKey<String>('barrier')), findsOneWidget);
// Cancel the pointer; the barrier should be dismissed
await gesture.cancel();
await tester.pumpAndSettle(const Duration(seconds: 1)); // end transition
expect(
find.byKey(const ValueKey<String>('barrier')),
findsNothing,
reason: 'The route should have been dismissed by tapping the barrier.',
);
});
testWidgets('ModalBarrier may pop the Navigator when competing with other gestures', (WidgetTester tester) async { testWidgets('ModalBarrier may pop the Navigator when competing with other gestures', (WidgetTester tester) async {
final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{ final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
'/': (BuildContext context) => const FirstWidget(), '/': (BuildContext context) => const FirstWidget(),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment