mirror of
https://github.com/flutter/flutter
synced 2024-10-13 11:42:54 +00:00
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
|
||||
/// [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:
|
||||
///
|
||||
/// * [CupertinoAlertDialog], which is a dialog with title, contents, and
|
||||
|
|
|
@ -461,7 +461,7 @@ Future<T> showDialog<T>({
|
|||
bool barrierDismissible: true,
|
||||
@required Widget child,
|
||||
}) {
|
||||
return Navigator.push(context, new _DialogRoute<T>(
|
||||
return Navigator.of(context, rootNavigator: true).push(new _DialogRoute<T>(
|
||||
child: child,
|
||||
theme: Theme.of(context, shadowThemeOnly: true),
|
||||
barrierDismissible: barrierDismissible,
|
||||
|
|
|
@ -2003,6 +2003,17 @@ abstract class BuildContext {
|
|||
/// ```
|
||||
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
|
||||
/// that matches the given [TypeMatcher].
|
||||
///
|
||||
|
@ -3245,6 +3256,19 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
|
|||
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
|
||||
RenderObject ancestorRenderObjectOfType(TypeMatcher matcher) {
|
||||
assert(_debugCheckStateIsActiveForAncestorLoopkup());
|
||||
|
|
|
@ -708,8 +708,17 @@ class Navigator extends StatefulWidget {
|
|||
/// ..pop()
|
||||
/// ..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(() {
|
||||
if (navigator == null) {
|
||||
throw new FlutterError(
|
||||
|
|
|
@ -179,6 +179,73 @@ void main() {
|
|||
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 {
|
||||
final List<String> log = <String>[];
|
||||
final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
|
||||
|
|
Loading…
Reference in a new issue