Commit d0161464 authored by rami-a's avatar rami-a Committed by Flutter GitHub Bot

Allow for cupertino modal popups to be dismissed with semantics (#48915)

parent eaa0e620
...@@ -108,6 +108,7 @@ class _CupertinoPickerDemoState extends State<CupertinoPickerDemo> { ...@@ -108,6 +108,7 @@ class _CupertinoPickerDemoState extends State<CupertinoPickerDemo> {
onTap: () async { onTap: () async {
await showCupertinoModalPopup<void>( await showCupertinoModalPopup<void>(
context: context, context: context,
semanticsDismissible: true,
builder: (BuildContext context) { builder: (BuildContext context) {
return _BottomPicker( return _BottomPicker(
child: CupertinoPicker( child: CupertinoPicker(
...@@ -144,6 +145,7 @@ class _CupertinoPickerDemoState extends State<CupertinoPickerDemo> { ...@@ -144,6 +145,7 @@ class _CupertinoPickerDemoState extends State<CupertinoPickerDemo> {
onTap: () { onTap: () {
showCupertinoModalPopup<void>( showCupertinoModalPopup<void>(
context: context, context: context,
semanticsDismissible: true,
builder: (BuildContext context) { builder: (BuildContext context) {
return _BottomPicker( return _BottomPicker(
child: CupertinoTimerPicker( child: CupertinoTimerPicker(
...@@ -176,6 +178,7 @@ class _CupertinoPickerDemoState extends State<CupertinoPickerDemo> { ...@@ -176,6 +178,7 @@ class _CupertinoPickerDemoState extends State<CupertinoPickerDemo> {
onTap: () { onTap: () {
showCupertinoModalPopup<void>( showCupertinoModalPopup<void>(
context: context, context: context,
semanticsDismissible: true,
builder: (BuildContext context) { builder: (BuildContext context) {
return _BottomPicker( return _BottomPicker(
child: CupertinoDatePicker( child: CupertinoDatePicker(
...@@ -207,6 +210,7 @@ class _CupertinoPickerDemoState extends State<CupertinoPickerDemo> { ...@@ -207,6 +210,7 @@ class _CupertinoPickerDemoState extends State<CupertinoPickerDemo> {
onTap: () { onTap: () {
showCupertinoModalPopup<void>( showCupertinoModalPopup<void>(
context: context, context: context,
semanticsDismissible: true,
builder: (BuildContext context) { builder: (BuildContext context) {
return _BottomPicker( return _BottomPicker(
child: CupertinoDatePicker( child: CupertinoDatePicker(
...@@ -238,6 +242,7 @@ class _CupertinoPickerDemoState extends State<CupertinoPickerDemo> { ...@@ -238,6 +242,7 @@ class _CupertinoPickerDemoState extends State<CupertinoPickerDemo> {
onTap: () { onTap: () {
showCupertinoModalPopup<void>( showCupertinoModalPopup<void>(
context: context, context: context,
semanticsDismissible: true,
builder: (BuildContext context) { builder: (BuildContext context) {
return _BottomPicker( return _BottomPicker(
child: CupertinoDatePicker( child: CupertinoDatePicker(
......
...@@ -35,7 +35,8 @@ const double _kOverAndUnderCenterOpacity = 0.447; ...@@ -35,7 +35,8 @@ const double _kOverAndUnderCenterOpacity = 0.447;
/// that child the initially selected child. /// that child the initially selected child.
/// ///
/// Can be used with [showCupertinoModalPopup] to display the picker modally at the /// Can be used with [showCupertinoModalPopup] to display the picker modally at the
/// bottom of the screen. /// bottom of the screen. When calling [showCupertinoModalPopup], be sure to set
/// `semanticsDismissible` to true to enable dismissing the modal via semantics.
/// ///
/// Sizes itself to its parent. All children are sized to the same size based /// Sizes itself to its parent. All children are sized to the same size based
/// on [itemExtent]. /// on [itemExtent].
......
...@@ -792,14 +792,18 @@ class _CupertinoModalPopupRoute<T> extends PopupRoute<T> { ...@@ -792,14 +792,18 @@ class _CupertinoModalPopupRoute<T> extends PopupRoute<T> {
this.barrierColor, this.barrierColor,
this.barrierLabel, this.barrierLabel,
this.builder, this.builder,
bool semanticsDismissible,
ImageFilter filter, ImageFilter filter,
RouteSettings settings, RouteSettings settings,
}) : super( }) : super(
filter: filter, filter: filter,
settings: settings, settings: settings,
); ) {
_semanticsDismissible = semanticsDismissible;
}
final WidgetBuilder builder; final WidgetBuilder builder;
bool _semanticsDismissible;
@override @override
final String barrierLabel; final String barrierLabel;
...@@ -811,7 +815,7 @@ class _CupertinoModalPopupRoute<T> extends PopupRoute<T> { ...@@ -811,7 +815,7 @@ class _CupertinoModalPopupRoute<T> extends PopupRoute<T> {
bool get barrierDismissible => true; bool get barrierDismissible => true;
@override @override
bool get semanticsDismissible => false; bool get semanticsDismissible => _semanticsDismissible ?? false;
@override @override
Duration get transitionDuration => _kModalPopupTransitionDuration; Duration get transitionDuration => _kModalPopupTransitionDuration;
...@@ -871,6 +875,9 @@ class _CupertinoModalPopupRoute<T> extends PopupRoute<T> { ...@@ -871,6 +875,9 @@ class _CupertinoModalPopupRoute<T> extends PopupRoute<T> {
/// popup to the [Navigator] furthest from or nearest to the given `context`. It /// popup to the [Navigator] furthest from or nearest to the given `context`. It
/// is `false` by default. /// is `false` by default.
/// ///
/// The `semanticsDismissble` argument is used to determine whether the
/// semantics of the modal barrier are included in the semantics tree.
///
/// The `builder` argument typically builds a [CupertinoActionSheet] widget. /// The `builder` argument typically builds a [CupertinoActionSheet] widget.
/// Content below the widget is dimmed with a [ModalBarrier]. The widget built /// Content below the widget is dimmed with a [ModalBarrier]. The widget built
/// by the `builder` does not share a context with the location that /// by the `builder` does not share a context with the location that
...@@ -891,6 +898,7 @@ Future<T> showCupertinoModalPopup<T>({ ...@@ -891,6 +898,7 @@ Future<T> showCupertinoModalPopup<T>({
@required WidgetBuilder builder, @required WidgetBuilder builder,
ImageFilter filter, ImageFilter filter,
bool useRootNavigator = true, bool useRootNavigator = true,
bool semanticsDismissible,
}) { }) {
assert(useRootNavigator != null); assert(useRootNavigator != null);
return Navigator.of(context, rootNavigator: useRootNavigator).push( return Navigator.of(context, rootNavigator: useRootNavigator).push(
...@@ -899,6 +907,7 @@ Future<T> showCupertinoModalPopup<T>({ ...@@ -899,6 +907,7 @@ Future<T> showCupertinoModalPopup<T>({
barrierLabel: 'Dismiss', barrierLabel: 'Dismiss',
builder: builder, builder: builder,
filter: filter, filter: filter,
semanticsDismissible: semanticsDismissible,
), ),
); );
} }
......
...@@ -3,11 +3,14 @@ ...@@ -3,11 +3,14 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart'; import 'package:mockito/mockito.dart';
import '../widgets/semantics_tester.dart';
void main() { void main() {
MockNavigatorObserver navigatorObserver; MockNavigatorObserver navigatorObserver;
...@@ -1052,6 +1055,75 @@ void main() { ...@@ -1052,6 +1055,75 @@ void main() {
expect(rootObserver.dialogCount, 0); expect(rootObserver.dialogCount, 0);
expect(nestedObserver.dialogCount, 1); expect(nestedObserver.dialogCount, 1);
}); });
testWidgets('showCupertinoModalPopup does not allow for semantics dismiss by default', (WidgetTester tester) async {
debugDefaultTargetPlatformOverride = TargetPlatform.iOS;
final SemanticsTester semantics = SemanticsTester(tester);
await tester.pumpWidget(CupertinoApp(
home: Navigator(
onGenerateRoute: (RouteSettings settings) {
return PageRouteBuilder<dynamic>(
pageBuilder: (BuildContext context, Animation<double> _, Animation<double> __) {
return GestureDetector(
onTap: () async {
await showCupertinoModalPopup<void>(
context: context,
builder: (BuildContext context) => const SizedBox(),
);
},
child: const Text('tap'),
);
},
);
},
),
));
// Push the route.
await tester.tap(find.text('tap'));
await tester.pumpAndSettle();
expect(semantics, isNot(includesNodeWith(
actions: <SemanticsAction>[SemanticsAction.tap],
label: 'Dismiss',
)));
debugDefaultTargetPlatformOverride = null;
});
testWidgets('showCupertinoModalPopup allows for semantics dismiss when set', (WidgetTester tester) async {
debugDefaultTargetPlatformOverride = TargetPlatform.iOS;
final SemanticsTester semantics = SemanticsTester(tester);
await tester.pumpWidget(CupertinoApp(
home: Navigator(
onGenerateRoute: (RouteSettings settings) {
return PageRouteBuilder<dynamic>(
pageBuilder: (BuildContext context, Animation<double> _, Animation<double> __) {
return GestureDetector(
onTap: () async {
await showCupertinoModalPopup<void>(
context: context,
semanticsDismissible: true,
builder: (BuildContext context) => const SizedBox(),
);
},
child: const Text('tap'),
);
},
);
},
),
));
// Push the route.
await tester.tap(find.text('tap'));
await tester.pumpAndSettle();
expect(semantics, includesNodeWith(
actions: <SemanticsAction>[SemanticsAction.tap],
label: 'Dismiss',
));
debugDefaultTargetPlatformOverride = null;
});
} }
class MockNavigatorObserver extends Mock implements NavigatorObserver {} class MockNavigatorObserver extends Mock implements NavigatorObserver {}
......
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