Unverified Commit 6ea0b2c9 authored by Darren Austin's avatar Darren Austin Committed by GitHub

Added `onDismiss` callback to ModalBarrier. (#83860)

parent 3fba55a1
...@@ -34,6 +34,7 @@ class ModalBarrier extends StatelessWidget { ...@@ -34,6 +34,7 @@ class ModalBarrier extends StatelessWidget {
Key? key, Key? key,
this.color, this.color,
this.dismissible = true, this.dismissible = true,
this.onDismiss,
this.semanticsLabel, this.semanticsLabel,
this.barrierSemanticsDismissible = true, this.barrierSemanticsDismissible = true,
}) : super(key: key); }) : super(key: key);
...@@ -46,7 +47,12 @@ class ModalBarrier extends StatelessWidget { ...@@ -46,7 +47,12 @@ class ModalBarrier extends StatelessWidget {
/// [ModalBarrier] built by [ModalRoute] pages. /// [ModalBarrier] built by [ModalRoute] pages.
final Color? color; final Color? color;
/// Whether touching the barrier will pop the current route off the [Navigator]. /// Specifies if the barrier will be dismissed when the user taps on it.
///
/// If true, and [onDismiss] is non-null, [onDismiss] will be called,
/// otherwise the current route will be popped from the ambient [Navigator].
///
/// If false, tapping on the barrier will do nothing.
/// ///
/// See also: /// See also:
/// ///
...@@ -54,6 +60,16 @@ class ModalBarrier extends StatelessWidget { ...@@ -54,6 +60,16 @@ class ModalBarrier extends StatelessWidget {
/// [ModalBarrier] built by [ModalRoute] pages. /// [ModalBarrier] built by [ModalRoute] pages.
final bool dismissible; final bool dismissible;
/// Called when the barrier is being dismissed.
///
/// If non-null [onDismiss] will be called in place of popping the current
/// route. It is up to the callback to handle dismissing the barrier.
///
/// If null, the ambient [Navigator]'s current route will be popped.
///
/// This field is ignored if [dismissible] is false.
final VoidCallback? onDismiss;
/// Whether the modal barrier semantics are included in the semantics tree. /// Whether the modal barrier semantics are included in the semantics tree.
/// ///
/// See also: /// See also:
...@@ -94,7 +110,15 @@ class ModalBarrier extends StatelessWidget { ...@@ -94,7 +110,15 @@ class ModalBarrier extends StatelessWidget {
final bool modalBarrierSemanticsDismissible = barrierSemanticsDismissible ?? semanticsDismissible; final bool modalBarrierSemanticsDismissible = barrierSemanticsDismissible ?? semanticsDismissible;
void handleDismiss() { void handleDismiss() {
Navigator.maybePop(context); if (dismissible) {
if (onDismiss != null) {
onDismiss!();
} else {
Navigator.maybePop(context);
}
} else {
SystemSound.play(SystemSoundType.alert);
}
} }
return BlockSemantics( return BlockSemantics(
...@@ -103,12 +127,7 @@ class ModalBarrier extends StatelessWidget { ...@@ -103,12 +127,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(
onDismiss: () { onDismiss: handleDismiss,
if (dismissible)
handleDismiss();
else
SystemSound.play(SystemSoundType.alert);
},
child: Semantics( child: Semantics(
label: semanticsDismissible ? semanticsLabel : null, label: semanticsDismissible ? semanticsLabel : null,
onDismiss: semanticsDismissible ? handleDismiss : null, onDismiss: semanticsDismissible ? handleDismiss : null,
......
...@@ -365,6 +365,60 @@ void main() { ...@@ -365,6 +365,60 @@ void main() {
expect(willPopCalled, isTrue); expect(willPopCalled, isTrue);
}); });
testWidgets('ModalBarrier will call onDismiss callback', (WidgetTester tester) async {
bool dismissCallbackCalled = false;
final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
'/': (BuildContext context) => const FirstWidget(),
'/modal': (BuildContext context) => SecondWidget(onDismiss: () {
dismissCallbackCalled = true;
}),
};
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
expect(find.byKey(const ValueKey<String>('barrier')), findsOneWidget);
expect(dismissCallbackCalled, false);
// Tap on the barrier
await tester.tap(find.byKey(const ValueKey<String>('barrier')));
await tester.pumpAndSettle(const Duration(seconds: 1)); // end transition
expect(dismissCallbackCalled, true);
});
testWidgets('ModalBarrier will not pop when given an onDismiss callback', (WidgetTester tester) async {
final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
'/': (BuildContext context) => const FirstWidget(),
'/modal': (BuildContext context) => SecondWidget(onDismiss: () {}),
};
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
expect(find.byKey(const ValueKey<String>('barrier')), findsOneWidget);
// Tap on the barrier
await tester.tap(find.byKey(const ValueKey<String>('barrier')));
await tester.pumpAndSettle(const Duration(seconds: 1)); // end transition
expect(
find.byKey(const ValueKey<String>('barrier')),
findsOneWidget,
reason: 'The route should not have been dismissed by tapping the barrier, as there was a onDismiss callback given.',
);
});
testWidgets('Undismissible ModalBarrier hidden in semantic tree', (WidgetTester tester) async { testWidgets('Undismissible ModalBarrier hidden in semantic tree', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester); final SemanticsTester semantics = SemanticsTester(tester);
await tester.pumpWidget(const ModalBarrier(dismissible: false)); await tester.pumpWidget(const ModalBarrier(dismissible: false));
...@@ -442,11 +496,15 @@ class FirstWidget extends StatelessWidget { ...@@ -442,11 +496,15 @@ class FirstWidget extends StatelessWidget {
} }
class SecondWidget extends StatelessWidget { class SecondWidget extends StatelessWidget {
const SecondWidget({ Key? key }) : super(key: key); const SecondWidget({ Key? key, this.onDismiss }) : super(key: key);
final VoidCallback? onDismiss;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return const ModalBarrier( return ModalBarrier(
key: ValueKey<String>('barrier'), key: const ValueKey<String>('barrier'),
onDismiss: onDismiss,
); );
} }
} }
......
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