Unverified Commit ebde5e25 authored by Tong Mu's avatar Tong Mu Committed by GitHub

Change modal barrier to dismissing on tap up (#42253)

* Change modal barrier to tap up

* Verify dismiss happen at release
parent a62bb3d9
...@@ -94,7 +94,7 @@ class ModalBarrier extends StatelessWidget { ...@@ -94,7 +94,7 @@ class ModalBarrier extends StatelessWidget {
// modal barriers are not dismissible in accessibility mode. // modal barriers are not dismissible in accessibility mode.
excluding: !semanticsDismissible || !modalBarrierSemanticsDismissible, excluding: !semanticsDismissible || !modalBarrierSemanticsDismissible,
child: _ModalBarrierGestureDetector( child: _ModalBarrierGestureDetector(
onAnyTapDown: () { onDismiss: () {
if (dismissible) if (dismissible)
Navigator.maybePop(context); Navigator.maybePop(context);
}, },
...@@ -195,12 +195,12 @@ class _AnyTapGestureRecognizer extends BaseTapGestureRecognizer { ...@@ -195,12 +195,12 @@ class _AnyTapGestureRecognizer extends BaseTapGestureRecognizer {
_AnyTapGestureRecognizer({ Object debugOwner }) _AnyTapGestureRecognizer({ Object debugOwner })
: super(debugOwner: debugOwner); : super(debugOwner: debugOwner);
VoidCallback onAnyTapDown; VoidCallback onAnyTapUp;
@protected @protected
@override @override
bool isPointerAllowed(PointerDownEvent event) { bool isPointerAllowed(PointerDownEvent event) {
if (onAnyTapDown == null) if (onAnyTapUp == null)
return false; return false;
return super.isPointerAllowed(event); return super.isPointerAllowed(event);
} }
...@@ -208,14 +208,14 @@ class _AnyTapGestureRecognizer extends BaseTapGestureRecognizer { ...@@ -208,14 +208,14 @@ class _AnyTapGestureRecognizer extends BaseTapGestureRecognizer {
@protected @protected
@override @override
void handleTapDown({PointerDownEvent down}) { void handleTapDown({PointerDownEvent down}) {
if (onAnyTapDown != null) // Do nothing.
onAnyTapDown();
} }
@protected @protected
@override @override
void handleTapUp({PointerDownEvent down, PointerUpEvent up}) { void handleTapUp({PointerDownEvent down, PointerUpEvent up}) {
// Do nothing. if (onAnyTapUp != null)
onAnyTapUp();
} }
@protected @protected
...@@ -229,28 +229,28 @@ class _AnyTapGestureRecognizer extends BaseTapGestureRecognizer { ...@@ -229,28 +229,28 @@ class _AnyTapGestureRecognizer extends BaseTapGestureRecognizer {
} }
class _ModalBarrierSemanticsDelegate extends SemanticsGestureDelegate { class _ModalBarrierSemanticsDelegate extends SemanticsGestureDelegate {
const _ModalBarrierSemanticsDelegate({this.onAnyTapDown}); const _ModalBarrierSemanticsDelegate({this.onDismiss});
final VoidCallback onAnyTapDown; final VoidCallback onDismiss;
@override @override
void assignSemantics(RenderSemanticsGestureHandler renderObject) { void assignSemantics(RenderSemanticsGestureHandler renderObject) {
renderObject.onTap = onAnyTapDown; renderObject.onTap = onDismiss;
} }
} }
class _AnyTapGestureRecognizerFactory extends GestureRecognizerFactory<_AnyTapGestureRecognizer> { class _AnyTapGestureRecognizerFactory extends GestureRecognizerFactory<_AnyTapGestureRecognizer> {
const _AnyTapGestureRecognizerFactory({this.onAnyTapDown}); const _AnyTapGestureRecognizerFactory({this.onAnyTapUp});
final VoidCallback onAnyTapDown; final VoidCallback onAnyTapUp;
@override @override
_AnyTapGestureRecognizer constructor() => _AnyTapGestureRecognizer(); _AnyTapGestureRecognizer constructor() => _AnyTapGestureRecognizer();
@override @override
void initializer(_AnyTapGestureRecognizer instance) { void initializer(_AnyTapGestureRecognizer instance) {
instance.onAnyTapDown = onAnyTapDown; instance.onAnyTapUp = onAnyTapUp;
} }
} }
...@@ -260,29 +260,29 @@ class _ModalBarrierGestureDetector extends StatelessWidget { ...@@ -260,29 +260,29 @@ class _ModalBarrierGestureDetector extends StatelessWidget {
const _ModalBarrierGestureDetector({ const _ModalBarrierGestureDetector({
Key key, Key key,
@required this.child, @required this.child,
@required this.onAnyTapDown, @required this.onDismiss,
}) : assert(child != null), }) : assert(child != null),
assert(onAnyTapDown != null), assert(onDismiss != null),
super(key: key); super(key: key);
/// The widget below this widget in the tree. /// The widget below this widget in the tree.
/// See [RawGestureDetector.child]. /// See [RawGestureDetector.child].
final Widget child; final Widget child;
/// Immediately called when a pointer causes a tap down. /// Immediately called when an event that should dismiss the modal barrier
/// See [_AnyTapGestureRecognizer.onAnyTapDown]. /// has happened.
final VoidCallback onAnyTapDown; final VoidCallback onDismiss;
@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(onAnyTapDown: onAnyTapDown), _AnyTapGestureRecognizer: _AnyTapGestureRecognizerFactory(onAnyTapUp: onDismiss),
}; };
return RawGestureDetector( return RawGestureDetector(
gestures: gestures, gestures: gestures,
behavior: HitTestBehavior.opaque, behavior: HitTestBehavior.opaque,
semantics: _ModalBarrierSemanticsDelegate(onAnyTapDown: onAnyTapDown), semantics: _ModalBarrierSemanticsDelegate(onDismiss: onDismiss),
child: child, child: child,
); );
} }
......
...@@ -105,41 +105,21 @@ void main() { ...@@ -105,41 +105,21 @@ void main() {
await tester.pump(); // begin transition await tester.pump(); // begin transition
await tester.pump(const Duration(seconds: 1)); // end transition await tester.pump(const Duration(seconds: 1)); // end transition
// Tap on the barrier to dismiss it // Press the barrier; it shouldn't dismiss yet
await tester.tap(find.byKey(const ValueKey<String>('barrier'))); final TestGesture gesture = await tester.press(
await tester.pump(); // begin transition find.byKey(const ValueKey<String>('barrier')),
await tester.pump(const Duration(seconds: 1)); // end transition );
await tester.pumpAndSettle(); // begin transition
expect(find.byKey(const ValueKey<String>('barrier')), findsNothing, expect(find.byKey(const ValueKey<String>('barrier')), findsOneWidget);
reason: 'The route should have been dismissed by tapping the barrier.');
});
testWidgets('ModalBarrier pops the Navigator when dismissed by primary tap down', (WidgetTester tester) async {
final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
'/': (BuildContext context) => FirstWidget(),
'/modal': (BuildContext context) => 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 to dismiss it
await tester.press(find.byKey(const ValueKey<String>('barrier')));
await tester.pump(); // begin transition
await tester.pump(const Duration(seconds: 1)); // end transition
// Release the pointer; the barrier should be dismissed
await gesture.up();
await tester.pumpAndSettle(const Duration(seconds: 1)); // end transition
expect(find.byKey(const ValueKey<String>('barrier')), findsNothing, expect(find.byKey(const ValueKey<String>('barrier')), findsNothing,
reason: 'The route should have been dismissed by tapping the barrier.'); reason: 'The route should have been dismissed by tapping the barrier.');
}); });
testWidgets('ModalBarrier pops the Navigator when dismissed by non-primary tap down', (WidgetTester tester) async { testWidgets('ModalBarrier pops the Navigator when dismissed by non-primary tap', (WidgetTester tester) async {
final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{ final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
'/': (BuildContext context) => FirstWidget(), '/': (BuildContext context) => FirstWidget(),
'/modal': (BuildContext context) => SecondWidget(), '/modal': (BuildContext context) => SecondWidget(),
...@@ -155,11 +135,17 @@ void main() { ...@@ -155,11 +135,17 @@ void main() {
await tester.pump(); // begin transition await tester.pump(); // begin transition
await tester.pump(const Duration(seconds: 1)); // end transition await tester.pump(const Duration(seconds: 1)); // end transition
// Press the barrier to dismiss it // Press the barrier; it shouldn't dismiss yet
await tester.press(find.byKey(const ValueKey<String>('barrier')), buttons: kSecondaryButton); final TestGesture gesture = await tester.press(
await tester.pump(); // begin transition find.byKey(const ValueKey<String>('barrier')),
await tester.pump(const Duration(seconds: 1)); // end transition buttons: kSecondaryButton,
);
await tester.pumpAndSettle(); // begin transition
expect(find.byKey(const ValueKey<String>('barrier')), findsOneWidget);
// Release the pointer; the barrier should be dismissed
await gesture.up();
await tester.pumpAndSettle(const Duration(seconds: 1)); // end transition
expect(find.byKey(const ValueKey<String>('barrier')), findsNothing, expect(find.byKey(const ValueKey<String>('barrier')), findsNothing,
reason: 'The route should have been dismissed by tapping the barrier.'); reason: 'The route should have been dismissed by tapping the barrier.');
}); });
......
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