Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions packages/cupertino_ui/lib/src/route.dart
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,18 @@ mixin CupertinoRouteTransitionMixin<T> on PageRoute<T> {
@protected
Widget buildContent(BuildContext context);

/// {@template cupertino_ui.CupertinoRouteTransitionMixin.includeRouteSemantics}
/// Whether this route introduces a route scope in the semantics tree.
///
/// Defaults to true. When true, screen readers can treat pushes and pops of
/// this route as navigation to a new screen and announce the change to users.
///
/// Set this to false for routes that update only part of the screen, such as
/// tab or shell content in a nested navigator. This prevents screen readers
/// from treating the route as a new screen.
/// {@endtemplate}
bool get includeRouteSemantics => true;

/// {@template cupertino_ui.CupertinoRouteTransitionMixin.title}
/// A title string for this route.
///
Expand Down Expand Up @@ -194,6 +206,9 @@ mixin CupertinoRouteTransitionMixin<T> on PageRoute<T> {
Animation<double> secondaryAnimation,
) {
final Widget child = buildContent(context);
if (!includeRouteSemantics) {
return child;
}
return Semantics(scopesRoute: true, explicitChildNodes: true, child: child);
}

Expand Down Expand Up @@ -309,6 +324,7 @@ class CupertinoPageRoute<T> extends PageRoute<T> with CupertinoRouteTransitionMi
this.maintainState = true,
super.fullscreenDialog,
super.allowSnapshotting = true,
this.includeRouteSemantics = true,
super.barrierDismissible = false,
}) {
assert(opaque);
Expand All @@ -330,6 +346,10 @@ class CupertinoPageRoute<T> extends PageRoute<T> with CupertinoRouteTransitionMi
@override
final bool maintainState;

/// {@macro cupertino_ui.CupertinoRouteTransitionMixin.includeRouteSemantics}
@override
final bool includeRouteSemantics;

@override
String get debugLabel => '${super.debugLabel}(${settings.name})';
}
Expand Down Expand Up @@ -362,6 +382,9 @@ class _PageBasedCupertinoPageRoute<T> extends PageRoute<T> with CupertinoRouteTr
@override
bool get fullscreenDialog => _page.fullscreenDialog;

@override
bool get includeRouteSemantics => _page.includeRouteSemantics;

@override
String get debugLabel => '${super.debugLabel}(${_page.name})';
}
Expand Down Expand Up @@ -390,6 +413,7 @@ class CupertinoPage<T> extends Page<T> {
this.title,
this.fullscreenDialog = false,
this.allowSnapshotting = true,
this.includeRouteSemantics = true,
super.canPop,
super.onPopInvoked,
super.key,
Expand All @@ -413,6 +437,9 @@ class CupertinoPage<T> extends Page<T> {
/// {@macro flutter.widgets.TransitionRoute.allowSnapshotting}
final bool allowSnapshotting;

/// {@macro cupertino_ui.CupertinoRouteTransitionMixin.includeRouteSemantics}
final bool includeRouteSemantics;

@override
Route<T> createRoute(BuildContext context) {
return _PageBasedCupertinoPageRoute<T>(page: this, allowSnapshotting: allowSnapshotting);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
changelog: |
- Adds an option for `CupertinoPageRoute` and `CupertinoPage` to opt out of introducing a semantics route scope.
version: minor
38 changes: 38 additions & 0 deletions packages/cupertino_ui/test/route_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2340,6 +2340,44 @@ void main() {
expect(find.text('Visible'), findsOneWidget);
});

testWidgets('CupertinoPageRoute can opt out of route semantics', (WidgetTester tester) async {
final SemanticsHandle handle = tester.ensureSemantics();

await tester.pumpWidget(
CupertinoApp(
onGenerateRoute: (RouteSettings settings) {
return CupertinoPageRoute<void>(
includeRouteSemantics: false,
builder: (BuildContext context) => const Text('Page'),
);
},
),
);

expect(find.semantics.byFlag(SemanticsFlag.scopesRoute), findsNothing);
handle.dispose();
});

testWidgets('CupertinoPage can opt out of route semantics', (WidgetTester tester) async {
final SemanticsHandle handle = tester.ensureSemantics();

await tester.pumpWidget(
buildNavigator(
view: tester.view,
pages: const <Page<void>>[
CupertinoPage<void>(includeRouteSemantics: false, child: Text('Page')),
],
onPopPage: (Route<dynamic> route, dynamic result) {
assert(false); // The test shouldn't call this.
return true;
},
),
);

expect(find.semantics.byFlag(SemanticsFlag.scopesRoute), findsNothing);
handle.dispose();
});

testWidgets('CupertinoPage works', (WidgetTester tester) async {
final LocalKey pageKey = UniqueKey();
final detector = TransitionDetector();
Expand Down
27 changes: 27 additions & 0 deletions packages/material_ui/lib/src/page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class MaterialPageRoute<T> extends PageRoute<T> with MaterialRouteTransitionMixi
this.maintainState = true,
super.fullscreenDialog,
super.allowSnapshotting = true,
this.includeRouteSemantics = true,
super.barrierDismissible = false,
super.traversalEdgeBehavior,
super.directionalTraversalEdgeBehavior,
Expand All @@ -57,6 +58,10 @@ class MaterialPageRoute<T> extends PageRoute<T> with MaterialRouteTransitionMixi
@override
final bool maintainState;

/// {@macro material_ui.MaterialRouteTransitionMixin.includeRouteSemantics}
@override
final bool includeRouteSemantics;

@override
String get debugLabel => '${super.debugLabel}(${settings.name})';
}
Expand Down Expand Up @@ -87,6 +92,18 @@ mixin MaterialRouteTransitionMixin<T> on PageRoute<T> {
@protected
Widget buildContent(BuildContext context);

/// {@template material_ui.MaterialRouteTransitionMixin.includeRouteSemantics}
/// Whether this route introduces a route scope in the semantics tree.
///
/// Defaults to true. When true, screen readers can treat pushes and pops of
/// this route as navigation to a new screen and announce the change to users.
///
/// Set this to false for routes that update only part of the screen, such as
/// tab or shell content in a nested navigator. This prevents screen readers
/// from treating the route as a new screen.
/// {@endtemplate}
bool get includeRouteSemantics => true;

@override
Duration get transitionDuration =>
_getPageTransitionBuilder(navigator!.context)?.transitionDuration ??
Expand Down Expand Up @@ -191,6 +208,9 @@ mixin MaterialRouteTransitionMixin<T> on PageRoute<T> {
Animation<double> secondaryAnimation,
) {
final Widget result = buildContent(context);
if (!includeRouteSemantics) {
return result;
}
return Semantics(scopesRoute: true, explicitChildNodes: true, child: result);
}

Expand Down Expand Up @@ -233,6 +253,7 @@ class MaterialPage<T> extends Page<T> {
this.maintainState = true,
this.fullscreenDialog = false,
this.allowSnapshotting = true,
this.includeRouteSemantics = true,
super.key,
super.canPop,
super.onPopInvoked,
Expand All @@ -253,6 +274,9 @@ class MaterialPage<T> extends Page<T> {
/// {@macro flutter.widgets.TransitionRoute.allowSnapshotting}
final bool allowSnapshotting;

/// {@macro material_ui.MaterialRouteTransitionMixin.includeRouteSemantics}
final bool includeRouteSemantics;

@override
Route<T> createRoute(BuildContext context) {
return _PageBasedMaterialPageRoute<T>(page: this, allowSnapshotting: allowSnapshotting);
Expand Down Expand Up @@ -282,6 +306,9 @@ class _PageBasedMaterialPageRoute<T> extends PageRoute<T> with MaterialRouteTran
@override
bool get fullscreenDialog => _page.fullscreenDialog;

@override
bool get includeRouteSemantics => _page.includeRouteSemantics;

@override
String get debugLabel => '${super.debugLabel}(${_page.name})';
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
changelog: |
- Adds an option for `MaterialPageRoute` and `MaterialPage` to opt out of introducing a semantics route scope.
version: minor
38 changes: 38 additions & 0 deletions packages/material_ui/test/page_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1196,6 +1196,44 @@ void main() {
}),
);

testWidgets('MaterialPageRoute can opt out of route semantics', (WidgetTester tester) async {
final SemanticsHandle handle = tester.ensureSemantics();

await tester.pumpWidget(
MaterialApp(
onGenerateRoute: (RouteSettings settings) {
return MaterialPageRoute<void>(
includeRouteSemantics: false,
builder: (BuildContext context) => const Text('Page'),
);
},
),
);

expect(find.semantics.byFlag(SemanticsFlag.scopesRoute), findsNothing);
handle.dispose();
});

testWidgets('MaterialPage can opt out of route semantics', (WidgetTester tester) async {
final SemanticsHandle handle = tester.ensureSemantics();

await tester.pumpWidget(
buildNavigator(
view: tester.view,
pages: const <Page<void>>[
MaterialPage<void>(includeRouteSemantics: false, child: Text('Page')),
],
onPopPage: (Route<dynamic> route, dynamic result) {
assert(false); // The test shouldn't call this.
return true;
},
),
);

expect(find.semantics.byFlag(SemanticsFlag.scopesRoute), findsNothing);
handle.dispose();
});

testWidgets('MaterialPage works', (WidgetTester tester) async {
final LocalKey pageKey = UniqueKey();
final detector = TransitionDetector();
Expand Down