Add a rootNavigator option to Navigator.of (#12580)
This commit is contained in:
parent
964a138d80
commit
822084b235
@ -53,6 +53,9 @@ const BoxDecoration _kCupertinoDialogBackFill = const BoxDecoration(
|
|||||||
/// dialog. Rather than using this widget directly, consider using
|
/// dialog. Rather than using this widget directly, consider using
|
||||||
/// [CupertinoAlertDialog], which implement a specific kind of dialog.
|
/// [CupertinoAlertDialog], which implement a specific kind of dialog.
|
||||||
///
|
///
|
||||||
|
/// Push with `Navigator.of(..., rootNavigator: true)` when using with
|
||||||
|
/// [CupertinoTabScaffold] to ensure that the dialog appears above the tabs.
|
||||||
|
///
|
||||||
/// See also:
|
/// See also:
|
||||||
///
|
///
|
||||||
/// * [CupertinoAlertDialog], which is a dialog with title, contents, and
|
/// * [CupertinoAlertDialog], which is a dialog with title, contents, and
|
||||||
|
@ -461,7 +461,7 @@ Future<T> showDialog<T>({
|
|||||||
bool barrierDismissible: true,
|
bool barrierDismissible: true,
|
||||||
@required Widget child,
|
@required Widget child,
|
||||||
}) {
|
}) {
|
||||||
return Navigator.push(context, new _DialogRoute<T>(
|
return Navigator.of(context, rootNavigator: true).push(new _DialogRoute<T>(
|
||||||
child: child,
|
child: child,
|
||||||
theme: Theme.of(context, shadowThemeOnly: true),
|
theme: Theme.of(context, shadowThemeOnly: true),
|
||||||
barrierDismissible: barrierDismissible,
|
barrierDismissible: barrierDismissible,
|
||||||
|
@ -2003,6 +2003,17 @@ abstract class BuildContext {
|
|||||||
/// ```
|
/// ```
|
||||||
State ancestorStateOfType(TypeMatcher matcher);
|
State ancestorStateOfType(TypeMatcher matcher);
|
||||||
|
|
||||||
|
/// Returns the [State] object of the furthest ancestor [StatefulWidget] widget
|
||||||
|
/// that matches the given [TypeMatcher].
|
||||||
|
///
|
||||||
|
/// Functions the same way as [ancestorStateOfType] but keeps visiting subsequent
|
||||||
|
/// ancestors until there are none of the type matching [TypeMatcher] remaining.
|
||||||
|
/// Then returns the last one found.
|
||||||
|
///
|
||||||
|
/// This operation is O(N) as well though N is the entire widget tree rather than
|
||||||
|
/// a subtree.
|
||||||
|
State rootAncestorStateOfType(TypeMatcher matcher);
|
||||||
|
|
||||||
/// Returns the [RenderObject] object of the nearest ancestor [RenderObjectWidget] widget
|
/// Returns the [RenderObject] object of the nearest ancestor [RenderObjectWidget] widget
|
||||||
/// that matches the given [TypeMatcher].
|
/// that matches the given [TypeMatcher].
|
||||||
///
|
///
|
||||||
@ -3245,6 +3256,19 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
|
|||||||
return statefulAncestor?.state;
|
return statefulAncestor?.state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
State rootAncestorStateOfType(TypeMatcher matcher) {
|
||||||
|
assert(_debugCheckStateIsActiveForAncestorLoopkup());
|
||||||
|
Element ancestor = _parent;
|
||||||
|
StatefulElement statefulAncestor;
|
||||||
|
while (ancestor != null) {
|
||||||
|
if (ancestor is StatefulElement && matcher.check(ancestor.state))
|
||||||
|
statefulAncestor = ancestor;
|
||||||
|
ancestor = ancestor._parent;
|
||||||
|
}
|
||||||
|
return statefulAncestor?.state;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
RenderObject ancestorRenderObjectOfType(TypeMatcher matcher) {
|
RenderObject ancestorRenderObjectOfType(TypeMatcher matcher) {
|
||||||
assert(_debugCheckStateIsActiveForAncestorLoopkup());
|
assert(_debugCheckStateIsActiveForAncestorLoopkup());
|
||||||
|
@ -708,8 +708,17 @@ class Navigator extends StatefulWidget {
|
|||||||
/// ..pop()
|
/// ..pop()
|
||||||
/// ..pushNamed('/settings');
|
/// ..pushNamed('/settings');
|
||||||
/// ```
|
/// ```
|
||||||
static NavigatorState of(BuildContext context) {
|
///
|
||||||
final NavigatorState navigator = context.ancestorStateOfType(const TypeMatcher<NavigatorState>());
|
/// If `rootNavigator` is set to true, the state from the furthest instance of
|
||||||
|
/// this class is given instead. Useful for pushing contents above all subsequent
|
||||||
|
/// instances of [Navigator].
|
||||||
|
static NavigatorState of(
|
||||||
|
BuildContext context, {
|
||||||
|
bool rootNavigator: false
|
||||||
|
}) {
|
||||||
|
final NavigatorState navigator = rootNavigator
|
||||||
|
? context.rootAncestorStateOfType(const TypeMatcher<NavigatorState>())
|
||||||
|
: context.ancestorStateOfType(const TypeMatcher<NavigatorState>());
|
||||||
assert(() {
|
assert(() {
|
||||||
if (navigator == null) {
|
if (navigator == null) {
|
||||||
throw new FlutterError(
|
throw new FlutterError(
|
||||||
|
@ -179,6 +179,73 @@ void main() {
|
|||||||
expect('$exception', startsWith('Navigator operation requested with a context'));
|
expect('$exception', startsWith('Navigator operation requested with a context'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('Navigator.of rootNavigator finds root Navigator', (WidgetTester tester) async {
|
||||||
|
await tester.pumpWidget(new MaterialApp(
|
||||||
|
home: new Material(
|
||||||
|
child: new Column(
|
||||||
|
children: <Widget>[
|
||||||
|
const SizedBox(
|
||||||
|
height: 300.0,
|
||||||
|
child: const Text('Root page'),
|
||||||
|
),
|
||||||
|
new SizedBox(
|
||||||
|
height: 300.0,
|
||||||
|
child: new Navigator(
|
||||||
|
onGenerateRoute: (RouteSettings settings) {
|
||||||
|
if (settings.isInitialRoute) {
|
||||||
|
return new MaterialPageRoute<Null>(
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return new RaisedButton(
|
||||||
|
child: const Text('Next'),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).push(
|
||||||
|
new MaterialPageRoute<Null>(
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return new RaisedButton(
|
||||||
|
child: const Text('Inner page'),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context, rootNavigator: true).push(
|
||||||
|
new MaterialPageRoute<Null>(
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return const Text('Dialog');
|
||||||
|
}
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
|
||||||
|
await tester.tap(find.text('Next'));
|
||||||
|
await tester.pump();
|
||||||
|
await tester.pump(const Duration(milliseconds: 300));
|
||||||
|
|
||||||
|
// Both elements are on screen.
|
||||||
|
expect(tester.getTopLeft(find.text('Root page')).dy, 0.0);
|
||||||
|
expect(tester.getTopLeft(find.text('Inner page')).dy, greaterThan(300.0));
|
||||||
|
|
||||||
|
await tester.tap(find.text('Inner page'));
|
||||||
|
await tester.pump();
|
||||||
|
await tester.pump(const Duration(milliseconds: 300));
|
||||||
|
|
||||||
|
// Dialog is pushed to the whole page and is at the top of the screen, not
|
||||||
|
// inside the inner page.
|
||||||
|
expect(tester.getTopLeft(find.text('Dialog')).dy, 0.0);
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets('Gestures between push and build are ignored', (WidgetTester tester) async {
|
testWidgets('Gestures between push and build are ignored', (WidgetTester tester) async {
|
||||||
final List<String> log = <String>[];
|
final List<String> log = <String>[];
|
||||||
final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
|
final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user