Added onDismiss
callback to ModalBarrier. (#83860)
This commit is contained in:
parent
3fba55a18d
commit
6ea0b2c929
@ -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 {
|
|||||||
/// [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 {
|
|||||||
/// [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,8 +110,16 @@ class ModalBarrier extends StatelessWidget {
|
|||||||
final bool modalBarrierSemanticsDismissible = barrierSemanticsDismissible ?? semanticsDismissible;
|
final bool modalBarrierSemanticsDismissible = barrierSemanticsDismissible ?? semanticsDismissible;
|
||||||
|
|
||||||
void handleDismiss() {
|
void handleDismiss() {
|
||||||
|
if (dismissible) {
|
||||||
|
if (onDismiss != null) {
|
||||||
|
onDismiss!();
|
||||||
|
} else {
|
||||||
Navigator.maybePop(context);
|
Navigator.maybePop(context);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
SystemSound.play(SystemSoundType.alert);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return BlockSemantics(
|
return BlockSemantics(
|
||||||
child: ExcludeSemantics(
|
child: ExcludeSemantics(
|
||||||
@ -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() {
|
|||||||
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 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user