routes.dart 68.2 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
Adam Barth's avatar
Adam Barth committed
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6
// @dart = 2.8

7
import 'dart:async';
8
import 'dart:ui' as ui;
9

10
import 'package:flutter/foundation.dart';
11
import 'package:flutter/scheduler.dart';
12
import 'package:flutter/semantics.dart';
13

14
import 'actions.dart';
Adam Barth's avatar
Adam Barth committed
15
import 'basic.dart';
16 17
import 'focus_manager.dart';
import 'focus_scope.dart';
Adam Barth's avatar
Adam Barth committed
18
import 'framework.dart';
19
import 'modal_barrier.dart';
Adam Barth's avatar
Adam Barth committed
20 21
import 'navigator.dart';
import 'overlay.dart';
22
import 'page_storage.dart';
23
import 'transitions.dart';
Adam Barth's avatar
Adam Barth committed
24

25 26
// Examples can assume:
// dynamic routeObserver;
27
// NavigatorState navigator;
28

29
const Color _kTransparent = Color(0x00000000);
Hixie's avatar
Hixie committed
30

31
/// A route that displays widgets in the [Navigator]'s [Overlay].
Hixie's avatar
Hixie committed
32
abstract class OverlayRoute<T> extends Route<T> {
33 34 35 36 37
  /// Creates a route that knows how to interact with an [Overlay].
  OverlayRoute({
    RouteSettings settings,
  }) : super(settings: settings);

38
  /// Subclasses should override this getter to return the builders for the overlay.
39
  @factory
40
  Iterable<OverlayEntry> createOverlayEntries();
Adam Barth's avatar
Adam Barth committed
41

42
  @override
Adam Barth's avatar
Adam Barth committed
43
  List<OverlayEntry> get overlayEntries => _overlayEntries;
44
  final List<OverlayEntry> _overlayEntries = <OverlayEntry>[];
Adam Barth's avatar
Adam Barth committed
45

46
  @override
47
  void install() {
48
    assert(_overlayEntries.isEmpty);
49
    _overlayEntries.addAll(createOverlayEntries());
50
    super.install();
Adam Barth's avatar
Adam Barth committed
51 52
  }

53
  /// Controls whether [didPop] calls [NavigatorState.finalizeRoute].
54
  ///
55
  /// If true, this route removes its overlay entries during [didPop].
56 57
  /// Subclasses can override this getter if they want to delay finalization
  /// (for example to animate the route's exit before removing it from the
58
  /// overlay).
59 60 61
  ///
  /// Subclasses that return false from [finishedWhenPopped] are responsible for
  /// calling [NavigatorState.finalizeRoute] themselves.
62 63 64
  @protected
  bool get finishedWhenPopped => true;

65
  @override
Hixie's avatar
Hixie committed
66
  bool didPop(T result) {
67 68
    final bool returnValue = super.didPop(result);
    assert(returnValue);
69
    if (finishedWhenPopped)
70 71
      navigator.finalizeRoute(this);
    return returnValue;
Hixie's avatar
Hixie committed
72 73
  }

74 75
  @override
  void dispose() {
Adam Barth's avatar
Adam Barth committed
76
    _overlayEntries.clear();
77
    super.dispose();
78
  }
Adam Barth's avatar
Adam Barth committed
79 80
}

81
/// A route with entrance and exit transitions.
Hixie's avatar
Hixie committed
82
abstract class TransitionRoute<T> extends OverlayRoute<T> {
83 84 85 86 87
  /// Creates a route that animates itself when it is pushed or popped.
  TransitionRoute({
    RouteSettings settings,
  }) : super(settings: settings);

Hixie's avatar
Hixie committed
88 89
  /// This future completes only once the transition itself has finished, after
  /// the overlay entries have been removed from the navigator's overlay.
90 91
  ///
  /// This future completes once the animation has been dismissed. That will be
92 93
  /// after [popped], because [popped] typically completes before the animation
  /// even starts, as soon as the route is popped.
94
  Future<T> get completed => _transitionCompleter.future;
95
  final Completer<T> _transitionCompleter = Completer<T>();
96

97
  /// {@template flutter.widgets.transitionRoute.transitionDuration}
98 99 100 101 102 103
  /// The duration the transition going forwards.
  ///
  /// See also:
  ///
  /// * [reverseTransitionDuration], which controls the duration of the
  /// transition when it is in reverse.
104
  /// {@endtemplate}
Adam Barth's avatar
Adam Barth committed
105
  Duration get transitionDuration;
106

107
  /// {@template flutter.widgets.transitionRoute.reverseTransitionDuration}
108 109 110 111
  /// The duration the transition going in reverse.
  ///
  /// By default, the reverse transition duration is set to the value of
  /// the forwards [transitionDuration].
112
  /// {@endtemplate}
113 114
  Duration get reverseTransitionDuration => transitionDuration;

115
  /// {@template flutter.widgets.transitionRoute.opaque}
116 117 118 119
  /// Whether the route obscures previous routes when the transition is complete.
  ///
  /// When an opaque route's entrance transition is complete, the routes behind
  /// the opaque route will not be built to save resources.
120
  /// {@endtemplate}
Adam Barth's avatar
Adam Barth committed
121 122
  bool get opaque;

123 124 125 126
  // This ensures that if we got to the dismissed state while still current,
  // we will still be disposed when we are eventually popped.
  //
  // This situation arises when dealing with the Cupertino dismiss gesture.
127
  @override
128
  bool get finishedWhenPopped => _controller.status == AnimationStatus.dismissed;
129

130 131
  /// The animation that drives the route's transition and the previous route's
  /// forward transition.
132 133
  Animation<double> get animation => _animation;
  Animation<double> _animation;
134

135 136 137
  /// The animation controller that the route uses to drive the transitions.
  ///
  /// The animation itself is exposed by the [animation] property.
138 139
  @protected
  AnimationController get controller => _controller;
140
  AnimationController _controller;
141

142 143 144 145 146 147
  /// The animation for the route being pushed on top of this route. This
  /// animation lets this route coordinate with the entrance and exit transition
  /// of route pushed on top of this route.
  Animation<double> get secondaryAnimation => _secondaryAnimation;
  final ProxyAnimation _secondaryAnimation = ProxyAnimation(kAlwaysDismissedAnimation);

148
  /// Called to create the animation controller that will drive the transitions to
149 150
  /// this route from the previous one, and back to the previous route from this
  /// one.
151
  AnimationController createAnimationController() {
152
    assert(!_transitionCompleter.isCompleted, 'Cannot reuse a $runtimeType after disposing it.');
153
    final Duration duration = transitionDuration;
154
    final Duration reverseDuration = reverseTransitionDuration;
155
    assert(duration != null && duration >= Duration.zero);
156
    return AnimationController(
157
      duration: duration,
158
      reverseDuration: reverseDuration,
159 160 161
      debugLabel: debugLabel,
      vsync: navigator,
    );
Adam Barth's avatar
Adam Barth committed
162 163
  }

164 165
  /// Called to create the animation that exposes the current progress of
  /// the transition controlled by the animation controller created by
166
  /// [createAnimationController()].
167
  Animation<double> createAnimation() {
168
    assert(!_transitionCompleter.isCompleted, 'Cannot reuse a $runtimeType after disposing it.');
169 170
    assert(_controller != null);
    return _controller.view;
171 172
  }

Hixie's avatar
Hixie committed
173
  T _result;
Adam Barth's avatar
Adam Barth committed
174

175
  void _handleStatusChanged(AnimationStatus status) {
Adam Barth's avatar
Adam Barth committed
176
    switch (status) {
177
      case AnimationStatus.completed:
Adam Barth's avatar
Adam Barth committed
178 179 180
        if (overlayEntries.isNotEmpty)
          overlayEntries.first.opaque = opaque;
        break;
181 182
      case AnimationStatus.forward:
      case AnimationStatus.reverse:
Adam Barth's avatar
Adam Barth committed
183 184 185
        if (overlayEntries.isNotEmpty)
          overlayEntries.first.opaque = false;
        break;
186
      case AnimationStatus.dismissed:
187
        // We might still be an active route if a subclass is controlling the
Pierre-Louis's avatar
Pierre-Louis committed
188
        // transition and hits the dismissed status. For example, the iOS
189
        // back gesture drives this animation to the dismissed status before
190 191
        // removing the route and disposing it.
        if (!isActive) {
192 193 194
          navigator.finalizeRoute(this);
          assert(overlayEntries.isEmpty);
        }
Adam Barth's avatar
Adam Barth committed
195 196 197 198
        break;
    }
  }

199
  @override
200
  void install() {
201
    assert(!_transitionCompleter.isCompleted, 'Cannot install a $runtimeType after disposing it.');
202
    _controller = createAnimationController();
203
    assert(_controller != null, '$runtimeType.createAnimationController() returned null.');
204 205
    _animation = createAnimation()
      ..addStatusListener(_handleStatusChanged);
206
    assert(_animation != null, '$runtimeType.createAnimation() returned null.');
207
    super.install();
208 209 210
    if (_animation.isCompleted && overlayEntries.isNotEmpty) {
      overlayEntries.first.opaque = opaque;
    }
211 212
  }

213
  @override
214
  TickerFuture didPush() {
215 216
    assert(_controller != null, '$runtimeType.didPush called before calling install() or after calling dispose().');
    assert(!_transitionCompleter.isCompleted, 'Cannot reuse a $runtimeType after disposing it.');
217
    super.didPush();
218
    return _controller.forward();
Adam Barth's avatar
Adam Barth committed
219 220
  }

221 222 223 224 225 226 227 228
  @override
  void didAdd() {
    assert(_controller != null, '$runtimeType.didPush called before calling install() or after calling dispose().');
    assert(!_transitionCompleter.isCompleted, 'Cannot reuse a $runtimeType after disposing it.');
    super.didAdd();
    _controller.value = _controller.upperBound;
  }

229
  @override
230
  void didReplace(Route<dynamic> oldRoute) {
231 232
    assert(_controller != null, '$runtimeType.didReplace called before calling install() or after calling dispose().');
    assert(!_transitionCompleter.isCompleted, 'Cannot reuse a $runtimeType after disposing it.');
233
    if (oldRoute is TransitionRoute)
234
      _controller.value = oldRoute._controller.value;
235 236 237
    super.didReplace(oldRoute);
  }

238
  @override
Hixie's avatar
Hixie committed
239
  bool didPop(T result) {
240 241
    assert(_controller != null, '$runtimeType.didPop called before calling install() or after calling dispose().');
    assert(!_transitionCompleter.isCompleted, 'Cannot reuse a $runtimeType after disposing it.');
Adam Barth's avatar
Adam Barth committed
242
    _result = result;
243
    _controller.reverse();
244
    return super.didPop(result);
Hixie's avatar
Hixie committed
245 246
  }

247
  @override
248
  void didPopNext(Route<dynamic> nextRoute) {
249 250
    assert(_controller != null, '$runtimeType.didPopNext called before calling install() or after calling dispose().');
    assert(!_transitionCompleter.isCompleted, 'Cannot reuse a $runtimeType after disposing it.');
251
    _updateSecondaryAnimation(nextRoute);
Hixie's avatar
Hixie committed
252
    super.didPopNext(nextRoute);
Adam Barth's avatar
Adam Barth committed
253 254
  }

255
  @override
256
  void didChangeNext(Route<dynamic> nextRoute) {
257 258
    assert(_controller != null, '$runtimeType.didChangeNext called before calling install() or after calling dispose().');
    assert(!_transitionCompleter.isCompleted, 'Cannot reuse a $runtimeType after disposing it.');
259
    _updateSecondaryAnimation(nextRoute);
Hixie's avatar
Hixie committed
260
    super.didChangeNext(nextRoute);
261 262
  }

263 264 265 266 267 268 269
  // A callback method that disposes existing train hopping animation and
  // removes its listener.
  //
  // This property is non-null if there is a train hopping in progress, and the
  // caller must reset this property to null after it is called.
  VoidCallback _trainHoppingListenerRemover;

270
  void _updateSecondaryAnimation(Route<dynamic> nextRoute) {
271 272 273 274 275 276
    // There is an existing train hopping in progress. Unfortunately, we cannot
    // dispose current train hopping animation until we replace it with a new
    // animation.
    final VoidCallback previousTrainHoppingListenerRemover = _trainHoppingListenerRemover;
    _trainHoppingListenerRemover = null;

277
    if (nextRoute is TransitionRoute<dynamic> && canTransitionTo(nextRoute) && nextRoute.canTransitionFrom(this)) {
278
      final Animation<double> current = _secondaryAnimation.parent;
Hixie's avatar
Hixie committed
279
      if (current != null) {
280 281
        final Animation<double> currentTrain = current is TrainHoppingAnimation ? current.currentTrain : current;
        final Animation<double> nextTrain = nextRoute._animation;
282 283 284 285 286
        if (
          currentTrain.value == nextTrain.value ||
          nextTrain.status == AnimationStatus.completed ||
          nextTrain.status == AnimationStatus.dismissed
        ) {
287 288
          _setSecondaryAnimation(nextTrain, nextRoute.completed);
        } else {
289 290 291 292 293 294 295 296 297 298
          // Two trains animate at different values. We have to do train hopping.
          // There are three possibilities of train hopping:
          //  1. We hop on the nextTrain when two trains meet in the middle using
          //     TrainHoppingAnimation.
          //  2. There is no chance to hop on nextTrain because two trains never
          //     cross each other. We have to directly set the animation to
          //     nextTrain once the nextTrain stops animating.
          //  3. A new _updateSecondaryAnimation is called before train hopping
          //     finishes. We leave a listener remover for the next call to
          //     properly clean up the existing train hopping.
299
          TrainHoppingAnimation newAnimation;
300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322
          void _jumpOnAnimationEnd(AnimationStatus status) {
            switch (status) {
              case AnimationStatus.completed:
              case AnimationStatus.dismissed:
                // The nextTrain has stopped animating without train hopping.
                // Directly sets the secondary animation and disposes the
                // TrainHoppingAnimation.
                _setSecondaryAnimation(nextTrain, nextRoute.completed);
                if (_trainHoppingListenerRemover != null) {
                  _trainHoppingListenerRemover();
                  _trainHoppingListenerRemover = null;
                }
                break;
              case AnimationStatus.forward:
              case AnimationStatus.reverse:
                break;
            }
          }
          _trainHoppingListenerRemover = () {
            nextTrain.removeStatusListener(_jumpOnAnimationEnd);
            newAnimation?.dispose();
          };
          nextTrain.addStatusListener(_jumpOnAnimationEnd);
323
          newAnimation = TrainHoppingAnimation(
324 325
            currentTrain,
            nextTrain,
Hixie's avatar
Hixie committed
326
            onSwitchedTrain: () {
327
              assert(_secondaryAnimation.parent == newAnimation);
328
              assert(newAnimation.currentTrain == nextRoute._animation);
329 330
              // We can hop on the nextTrain, so we don't need to listen to
              // whether the nextTrain has stopped.
331
              _setSecondaryAnimation(newAnimation.currentTrain, nextRoute.completed);
332 333 334 335
              if (_trainHoppingListenerRemover != null) {
                _trainHoppingListenerRemover();
                _trainHoppingListenerRemover = null;
              }
336
            },
Hixie's avatar
Hixie committed
337
          );
338 339
          _setSecondaryAnimation(newAnimation, nextRoute.completed);
        }
Hixie's avatar
Hixie committed
340
      } else {
341
        _setSecondaryAnimation(nextRoute._animation, nextRoute.completed);
Hixie's avatar
Hixie committed
342
      }
Hixie's avatar
Hixie committed
343
    } else {
344
      _setSecondaryAnimation(kAlwaysDismissedAnimation);
Hixie's avatar
Hixie committed
345
    }
346 347 348 349 350
    // Finally, we dispose any previous train hopping animation because it
    // has been successfully updated at this point.
    if (previousTrainHoppingListenerRemover != null) {
      previousTrainHoppingListenerRemover();
    }
Hixie's avatar
Hixie committed
351 352
  }

353 354
  void _setSecondaryAnimation(Animation<double> animation, [Future<dynamic> disposed]) {
    _secondaryAnimation.parent = animation;
355
    // Releases the reference to the next route's animation when that route
356 357 358 359 360 361 362 363 364 365 366
    // is disposed.
    disposed?.then((dynamic _) {
      if (_secondaryAnimation.parent == animation) {
        _secondaryAnimation.parent = kAlwaysDismissedAnimation;
        if (animation is TrainHoppingAnimation) {
          animation.dispose();
        }
      }
    });
  }

367 368 369
  /// Returns true if this route supports a transition animation that runs
  /// when [nextRoute] is pushed on top of it or when [nextRoute] is popped
  /// off of it.
370
  ///
371
  /// Subclasses can override this method to restrict the set of routes they
372
  /// need to coordinate transitions with.
373 374
  ///
  /// If true, and `nextRoute.canTransitionFrom()` is true, then the
375
  /// [ModalRoute.buildTransitions] `secondaryAnimation` will run from 0.0 - 1.0
376 377 378 379
  /// when [nextRoute] is pushed on top of this one.  Similarly, if
  /// the [nextRoute] is popped off of this route, the
  /// `secondaryAnimation` will run from 1.0 - 0.0.
  ///
380
  /// If false, this route's [ModalRoute.buildTransitions] `secondaryAnimation` parameter
381 382 383 384 385 386 387 388 389
  /// value will be [kAlwaysDismissedAnimation]. In other words, this route
  /// will not animate when when [nextRoute] is pushed on top of it or when
  /// [nextRoute] is popped off of it.
  ///
  /// Returns true by default.
  ///
  /// See also:
  ///
  ///  * [canTransitionFrom], which must be true for [nextRoute] for the
390
  ///    [ModalRoute.buildTransitions] `secondaryAnimation` to run.
391
  bool canTransitionTo(TransitionRoute<dynamic> nextRoute) => true;
392

393 394
  /// Returns true if [previousRoute] should animate when this route
  /// is pushed on top of it or when then this route is popped off of it.
395
  ///
396
  /// Subclasses can override this method to restrict the set of routes they
397
  /// need to coordinate transitions with.
398 399
  ///
  /// If true, and `previousRoute.canTransitionTo()` is true, then the
400
  /// previous route's [ModalRoute.buildTransitions] `secondaryAnimation` will
401 402 403 404
  /// run from 0.0 - 1.0 when this route is pushed on top of
  /// it. Similarly, if this route is popped off of [previousRoute]
  /// the previous route's `secondaryAnimation` will run from 1.0 - 0.0.
  ///
405
  /// If false, then the previous route's [ModalRoute.buildTransitions]
406 407 408 409 410 411 412 413 414
  /// `secondaryAnimation` value will be kAlwaysDismissedAnimation. In
  /// other words [previousRoute] will not animate when this route is
  /// pushed on top of it or when then this route is popped off of it.
  ///
  /// Returns true by default.
  ///
  /// See also:
  ///
  ///  * [canTransitionTo], which must be true for [previousRoute] for its
415
  ///    [ModalRoute.buildTransitions] `secondaryAnimation` to run.
416
  bool canTransitionFrom(TransitionRoute<dynamic> previousRoute) => true;
417

418
  @override
Hixie's avatar
Hixie committed
419
  void dispose() {
420 421
    assert(!_transitionCompleter.isCompleted, 'Cannot dispose a $runtimeType twice.');
    _controller?.dispose();
422
    _transitionCompleter.complete(_result);
Hixie's avatar
Hixie committed
423 424 425
    super.dispose();
  }

426
  /// A short description of this route useful for debugging.
427
  String get debugLabel => objectRuntimeType(this, 'TransitionRoute');
428 429

  @override
430
  String toString() => '${objectRuntimeType(this, 'TransitionRoute')}(animation: $_controller)';
Adam Barth's avatar
Adam Barth committed
431
}
432

433
/// An entry in the history of a [LocalHistoryRoute].
Hixie's avatar
Hixie committed
434
class LocalHistoryEntry {
435
  /// Creates an entry in the history of a [LocalHistoryRoute].
Hixie's avatar
Hixie committed
436
  LocalHistoryEntry({ this.onRemove });
437 438

  /// Called when this entry is removed from the history of its associated [LocalHistoryRoute].
Hixie's avatar
Hixie committed
439
  final VoidCallback onRemove;
440

441
  LocalHistoryRoute<dynamic> _owner;
442 443

  /// Remove this entry from the history of its associated [LocalHistoryRoute].
Hixie's avatar
Hixie committed
444 445
  void remove() {
    _owner.removeLocalHistoryEntry(this);
446
    assert(_owner == null);
Hixie's avatar
Hixie committed
447
  }
448

Hixie's avatar
Hixie committed
449 450 451 452 453 454
  void _notifyRemoved() {
    if (onRemove != null)
      onRemove();
  }
}

455
/// A mixin used by routes to handle back navigations internally by popping a list.
456 457
///
/// When a [Navigator] is instructed to pop, the current route is given an
458
/// opportunity to handle the pop internally. A `LocalHistoryRoute` handles the
459 460
/// pop internally if its list of local history entries is non-empty. Rather
/// than being removed as the current route, the most recent [LocalHistoryEntry]
461
/// is removed from the list and its [LocalHistoryEntry.onRemove] is called.
462
mixin LocalHistoryRoute<T> on Route<T> {
Hixie's avatar
Hixie committed
463
  List<LocalHistoryEntry> _localHistory;
464 465 466

  /// Adds a local history entry to this route.
  ///
467
  /// When asked to pop, if this route has any local history entries, this route
468 469 470 471 472
  /// will handle the pop internally by removing the most recently added local
  /// history entry.
  ///
  /// The given local history entry must not already be part of another local
  /// history route.
473
  ///
474
  /// {@tool snippet}
475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490
  ///
  /// The following example is an app with 2 pages: `HomePage` and `SecondPage`.
  /// The `HomePage` can navigate to the `SecondPage`.
  ///
  /// The `SecondPage` uses a [LocalHistoryEntry] to implement local navigation
  /// within that page. Pressing 'show rectangle' displays a red rectangle and
  /// adds a local history entry. At that point, pressing the '< back' button
  /// pops the latest route, which is the local history entry, and the red
  /// rectangle disappears. Pressing the '< back' button a second time
  /// once again pops the latest route, which is the `SecondPage`, itself.
  /// Therefore, the second press navigates back to the `HomePage`.
  ///
  /// ```dart
  /// class App extends StatelessWidget {
  ///   @override
  ///   Widget build(BuildContext context) {
491
  ///     return MaterialApp(
492 493
  ///       initialRoute: '/',
  ///       routes: {
494 495
  ///         '/': (BuildContext context) => HomePage(),
  ///         '/second_page': (BuildContext context) => SecondPage(),
496 497 498 499 500 501 502 503 504
  ///       },
  ///     );
  ///   }
  /// }
  ///
  /// class HomePage extends StatefulWidget {
  ///   HomePage();
  ///
  ///   @override
505
  ///   _HomePageState createState() => _HomePageState();
506 507 508 509 510
  /// }
  ///
  /// class _HomePageState extends State<HomePage> {
  ///   @override
  ///   Widget build(BuildContext context) {
511 512
  ///     return Scaffold(
  ///       body: Center(
513 514 515
  ///         child: Column(
  ///           mainAxisSize: MainAxisSize.min,
  ///           children: <Widget>[
516
  ///             Text('HomePage'),
517
  ///             // Press this button to open the SecondPage.
518
  ///             ElevatedButton(
519
  ///               child: Text('Second Page >'),
520 521 522 523 524 525 526 527 528 529 530 531 532
  ///               onPressed: () {
  ///                 Navigator.pushNamed(context, '/second_page');
  ///               },
  ///             ),
  ///           ],
  ///         ),
  ///       ),
  ///     );
  ///   }
  /// }
  ///
  /// class SecondPage extends StatefulWidget {
  ///   @override
533
  ///   _SecondPageState createState() => _SecondPageState();
534 535 536 537 538 539 540 541 542 543 544 545
  /// }
  ///
  /// class _SecondPageState extends State<SecondPage> {
  ///
  ///   bool _showRectangle = false;
  ///
  ///   void _navigateLocallyToShowRectangle() async {
  ///     // This local history entry essentially represents the display of the red
  ///     // rectangle. When this local history entry is removed, we hide the red
  ///     // rectangle.
  ///     setState(() => _showRectangle = true);
  ///     ModalRoute.of(context).addLocalHistoryEntry(
546
  ///         LocalHistoryEntry(
547 548 549 550 551 552 553 554 555 556 557
  ///             onRemove: () {
  ///               // Hide the red rectangle.
  ///               setState(() => _showRectangle = false);
  ///             }
  ///         )
  ///     );
  ///   }
  ///
  ///   @override
  ///   Widget build(BuildContext context) {
  ///     final localNavContent = _showRectangle
558
  ///       ? Container(
559 560 561 562
  ///           width: 100.0,
  ///           height: 100.0,
  ///           color: Colors.red,
  ///         )
563
  ///       : ElevatedButton(
564
  ///           child: Text('Show Rectangle'),
565 566 567
  ///           onPressed: _navigateLocallyToShowRectangle,
  ///         );
  ///
568
  ///     return Scaffold(
569
  ///       body: Center(
570
  ///         child: Column(
571 572 573
  ///           mainAxisAlignment: MainAxisAlignment.center,
  ///           children: <Widget>[
  ///             localNavContent,
574
  ///             ElevatedButton(
575
  ///               child: Text('< Back'),
576 577 578 579 580 581 582 583 584 585 586 587 588 589 590
  ///               onPressed: () {
  ///                 // Pop a route. If this is pressed while the red rectangle is
  ///                 // visible then it will will pop our local history entry, which
  ///                 // will hide the red rectangle. Otherwise, the SecondPage will
  ///                 // navigate back to the HomePage.
  ///                 Navigator.of(context).pop();
  ///               },
  ///             ),
  ///           ],
  ///         ),
  ///       ),
  ///     );
  ///   }
  /// }
  /// ```
591
  /// {@end-tool}
Hixie's avatar
Hixie committed
592 593 594 595
  void addLocalHistoryEntry(LocalHistoryEntry entry) {
    assert(entry._owner == null);
    entry._owner = this;
    _localHistory ??= <LocalHistoryEntry>[];
596
    final bool wasEmpty = _localHistory.isEmpty;
Hixie's avatar
Hixie committed
597
    _localHistory.add(entry);
598 599
    if (wasEmpty)
      changedInternalState();
Hixie's avatar
Hixie committed
600
  }
601 602 603

  /// Remove a local history entry from this route.
  ///
604 605
  /// The entry's [LocalHistoryEntry.onRemove] callback, if any, will be called
  /// synchronously.
Hixie's avatar
Hixie committed
606 607 608 609 610 611 612
  void removeLocalHistoryEntry(LocalHistoryEntry entry) {
    assert(entry != null);
    assert(entry._owner == this);
    assert(_localHistory.contains(entry));
    _localHistory.remove(entry);
    entry._owner = null;
    entry._notifyRemoved();
613 614 615 616 617 618 619 620 621 622 623 624
    if (_localHistory.isEmpty) {
      if (SchedulerBinding.instance.schedulerPhase == SchedulerPhase.persistentCallbacks) {
        // The local history might be removed as a result of disposing inactive
        // elements during finalizeTree. The state is locked at this moment, and
        // we can only notify state has changed in the next frame.
        SchedulerBinding.instance.addPostFrameCallback((Duration duration) {
          changedInternalState();
        });
      } else {
        changedInternalState();
      }
    }
Hixie's avatar
Hixie committed
625
  }
626

627
  @override
628 629 630
  Future<RoutePopDisposition> willPop() async {
    if (willHandlePopInternally)
      return RoutePopDisposition.pop;
631
    return super.willPop();
632 633
  }

634
  @override
Hixie's avatar
Hixie committed
635
  bool didPop(T result) {
636
    if (_localHistory != null && _localHistory.isNotEmpty) {
637
      final LocalHistoryEntry entry = _localHistory.removeLast();
Hixie's avatar
Hixie committed
638 639 640
      assert(entry._owner == this);
      entry._owner = null;
      entry._notifyRemoved();
641 642
      if (_localHistory.isEmpty)
        changedInternalState();
Hixie's avatar
Hixie committed
643 644 645 646
      return false;
    }
    return super.didPop(result);
  }
647 648

  @override
Hixie's avatar
Hixie committed
649
  bool get willHandlePopInternally {
650
    return _localHistory != null && _localHistory.isNotEmpty;
Hixie's avatar
Hixie committed
651
  }
Hixie's avatar
Hixie committed
652 653
}

654 655 656 657 658 659 660
class _DismissModalAction extends DismissAction {
  @override
  Object invoke(DismissIntent intent) {
    return Navigator.of(primaryFocus.context).maybePop();
  }
}

Hixie's avatar
Hixie committed
661
class _ModalScopeStatus extends InheritedWidget {
662
  const _ModalScopeStatus({
Hixie's avatar
Hixie committed
663
    Key key,
664 665 666
    @required this.isCurrent,
    @required this.canPop,
    @required this.route,
667
    @required Widget child,
668 669 670 671 672
  }) : assert(isCurrent != null),
       assert(canPop != null),
       assert(route != null),
       assert(child != null),
       super(key: key, child: child);
Hixie's avatar
Hixie committed
673

674
  final bool isCurrent;
675
  final bool canPop;
676
  final Route<dynamic> route;
Hixie's avatar
Hixie committed
677

678
  @override
Hixie's avatar
Hixie committed
679
  bool updateShouldNotify(_ModalScopeStatus old) {
680
    return isCurrent != old.isCurrent ||
681
           canPop != old.canPop ||
Hixie's avatar
Hixie committed
682 683 684
           route != old.route;
  }

685
  @override
686
  void debugFillProperties(DiagnosticPropertiesBuilder description) {
687
    super.debugFillProperties(description);
688 689
    description.add(FlagProperty('isCurrent', value: isCurrent, ifTrue: 'active', ifFalse: 'inactive'));
    description.add(FlagProperty('canPop', value: canPop, ifTrue: 'can pop'));
Hixie's avatar
Hixie committed
690 691 692
  }
}

693
class _ModalScope<T> extends StatefulWidget {
694
  const _ModalScope({
695
    Key key,
696
    this.route,
697
  }) : super(key: key);
698

699
  final ModalRoute<T> route;
700

701
  @override
702
  _ModalScopeState<T> createState() => _ModalScopeState<T>();
703 704
}

705 706 707 708 709 710 711 712 713
class _ModalScopeState<T> extends State<_ModalScope<T>> {
  // We cache the result of calling the route's buildPage, and clear the cache
  // whenever the dependencies change. This implements the contract described in
  // the documentation for buildPage, namely that it gets called once, unless
  // something like a ModalRoute.of() dependency triggers an update.
  Widget _page;

  // This is the combination of the two animations for the route.
  Listenable _listenable;
714

715 716 717
  /// The node this scope will use for its root [FocusScope] widget.
  final FocusScopeNode focusScopeNode = FocusScopeNode(debugLabel: '$_ModalScopeState Focus Scope');

718
  @override
719 720
  void initState() {
    super.initState();
721 722 723 724
    final List<Listenable> animations = <Listenable>[
      if (widget.route.animation != null) widget.route.animation,
      if (widget.route.secondaryAnimation != null) widget.route.secondaryAnimation,
    ];
725
    _listenable = Listenable.merge(animations);
726 727 728
    if (widget.route.isCurrent) {
      widget.route.navigator.focusScopeNode.setFirstFocus(focusScopeNode);
    }
729 730
  }

731
  @override
732
  void didUpdateWidget(_ModalScope<T> oldWidget) {
733
    super.didUpdateWidget(oldWidget);
734
    assert(widget.route == oldWidget.route);
735 736 737
    if (widget.route.isCurrent) {
      widget.route.navigator.focusScopeNode.setFirstFocus(focusScopeNode);
    }
738 739
  }

740
  @override
741 742 743
  void didChangeDependencies() {
    super.didChangeDependencies();
    _page = null;
744 745
  }

746
  void _forceRebuildPage() {
747
    setState(() {
748
      _page = null;
749 750 751
    });
  }

752 753 754 755 756 757
  @override
  void dispose() {
    focusScopeNode.dispose();
    super.dispose();
  }

758 759 760 761 762
  bool get _shouldIgnoreFocusRequest {
    return widget.route.animation?.status == AnimationStatus.reverse ||
      (widget.route.navigator?.userGestureInProgress ?? false);
  }

763 764
  // This should be called to wrap any changes to route.isCurrent, route.canPop,
  // and route.offstage.
765
  void _routeSetState(VoidCallback fn) {
766
    if (widget.route.isCurrent && !_shouldIgnoreFocusRequest) {
767 768
      widget.route.navigator.focusScopeNode.setFirstFocus(focusScopeNode);
    }
769
    setState(fn);
770 771
  }

772 773 774 775
  static final Map<Type, Action<Intent>> _actionMap = <Type, Action<Intent>>{
    DismissIntent: _DismissModalAction(),
  };

776
  @override
777
  Widget build(BuildContext context) {
778
    return _ModalScopeStatus(
779 780 781
      route: widget.route,
      isCurrent: widget.route.isCurrent, // _routeSetState is called if this updates
      canPop: widget.route.canPop, // _routeSetState is called if this updates
782
      child: Offstage(
783
        offstage: widget.route.offstage, // _routeSetState is called if this updates
784
        child: PageStorage(
785
          bucket: widget.route._storageBucket, // immutable
786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823
          child: Actions(
            actions: _actionMap,
            child: FocusScope(
              node: focusScopeNode, // immutable
              child: RepaintBoundary(
                child: AnimatedBuilder(
                  animation: _listenable, // immutable
                  builder: (BuildContext context, Widget child) {
                    return widget.route.buildTransitions(
                      context,
                      widget.route.animation,
                      widget.route.secondaryAnimation,
                      // This additional AnimatedBuilder is include because if the
                      // value of the userGestureInProgressNotifier changes, it's
                      // only necessary to rebuild the IgnorePointer widget and set
                      // the focus node's ability to focus.
                      AnimatedBuilder(
                        animation: widget.route.navigator?.userGestureInProgressNotifier ?? ValueNotifier<bool>(false),
                        builder: (BuildContext context, Widget child) {
                          final bool ignoreEvents = _shouldIgnoreFocusRequest;
                          focusScopeNode.canRequestFocus = !ignoreEvents;
                          return IgnorePointer(
                            ignoring: ignoreEvents,
                            child: child,
                          );
                        },
                        child: child,
                      ),
                    );
                  },
                  child: _page ??= RepaintBoundary(
                    key: widget.route._subtreeKey, // immutable
                    child: Builder(
                      builder: (BuildContext context) {
                        return widget.route.buildPage(
                          context,
                          widget.route.animation,
                          widget.route.secondaryAnimation,
824 825
                        );
                      },
826
                    ),
827
                  ),
828 829 830 831 832 833
                ),
              ),
            ),
          ),
        ),
      ),
834
    );
835 836 837
  }
}

838
/// A route that blocks interaction with previous routes.
839
///
840 841 842 843 844 845
/// [ModalRoute]s cover the entire [Navigator]. They are not necessarily
/// [opaque], however; for example, a pop-up menu uses a [ModalRoute] but only
/// shows the menu in a small box overlapping the previous route.
///
/// The `T` type argument is the return value of the route. If there is no
/// return value, consider using `void` as the return value.
846
abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T> {
847
  /// Creates a route that blocks interaction with previous routes.
848
  ModalRoute({
849
    RouteSettings settings,
850 851 852 853 854 855 856 857 858
    ui.ImageFilter filter,
  }) : _filter = filter,
       super(settings: settings);

  /// The filter to add to the barrier.
  ///
  /// If given, this filter will be applied to the modal barrier using
  /// [BackdropFilter]. This allows blur effects, for example.
  final ui.ImageFilter _filter;
859

Hixie's avatar
Hixie committed
860 861
  // The API for general users of this class

862 863
  /// Returns the modal route most closely associated with the given context.
  ///
864
  /// Returns null if the given context is not associated with a modal route.
865 866 867 868
  ///
  /// Typical usage is as follows:
  ///
  /// ```dart
869
  /// ModalRoute route = ModalRoute.of(context);
870
  /// ```
871 872
  ///
  /// The given [BuildContext] will be rebuilt if the state of the route changes
873
  /// while it is visible (specifically, if [isCurrent] or [canPop] change value).
874
  @optionalTypeArgs
875
  static ModalRoute<T> of<T extends Object>(BuildContext context) {
876
    final _ModalScopeStatus widget = context.dependOnInheritedWidgetOfExactType<_ModalScopeStatus>();
877
    return widget?.route as ModalRoute<T>;
Hixie's avatar
Hixie committed
878 879
  }

880 881
  /// Schedule a call to [buildTransitions].
  ///
882
  /// Whenever you need to change internal state for a [ModalRoute] object, make
883
  /// the change in a function that you pass to [setState], as in:
884 885 886 887 888
  ///
  /// ```dart
  /// setState(() { myState = newValue });
  /// ```
  ///
889
  /// If you just change the state directly without calling [setState], then the
890 891 892 893 894 895 896 897 898 899 900 901 902 903
  /// route will not be scheduled for rebuilding, meaning that its rendering
  /// will not be updated.
  @protected
  void setState(VoidCallback fn) {
    if (_scopeKey.currentState != null) {
      _scopeKey.currentState._routeSetState(fn);
    } else {
      // The route isn't currently visible, so we don't have to call its setState
      // method, but we do still need to call the fn callback, otherwise the state
      // in the route won't be updated!
      fn();
    }
  }

Hans Muller's avatar
Hans Muller committed
904 905 906 907 908 909 910 911
  /// Returns a predicate that's true if the route has the specified name and if
  /// popping the route will not yield the same route, i.e. if the route's
  /// [willHandlePopInternally] property is false.
  ///
  /// This function is typically used with [Navigator.popUntil()].
  static RoutePredicate withName(String name) {
    return (Route<dynamic> route) {
      return !route.willHandlePopInternally
912 913
          && route is ModalRoute
          && route.settings.name == name;
Hans Muller's avatar
Hans Muller committed
914 915
    };
  }
916 917 918

  // The API for subclasses to override - used by _ModalScope

919
  /// Override this method to build the primary content of this route.
920
  ///
921 922 923 924 925 926 927 928 929 930
  /// The arguments have the following meanings:
  ///
  ///  * `context`: The context in which the route is being built.
  ///  * [animation]: The animation for this route's transition. When entering,
  ///    the animation runs forward from 0.0 to 1.0. When exiting, this animation
  ///    runs backwards from 1.0 to 0.0.
  ///  * [secondaryAnimation]: The animation for the route being pushed on top of
  ///    this route. This animation lets this route coordinate with the entrance
  ///    and exit transition of routes pushed on top of this route.
  ///
931 932 933 934 935 936 937 938
  /// This method is only called when the route is first built, and rarely
  /// thereafter. In particular, it is not automatically called again when the
  /// route's state changes unless it uses [ModalRoute.of]. For a builder that
  /// is called every time the route's state changes, consider
  /// [buildTransitions]. For widgets that change their behavior when the
  /// route's state changes, consider [ModalRoute.of] to obtain a reference to
  /// the route; this will cause the widget to be rebuilt each time the route
  /// changes state.
939 940 941 942 943
  ///
  /// In general, [buildPage] should be used to build the page contents, and
  /// [buildTransitions] for the widgets that change as the page is brought in
  /// and out of view. Avoid using [buildTransitions] for content that never
  /// changes; building such content once from [buildPage] is more efficient.
944
  Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation);
945

946 947
  /// Override this method to wrap the [child] with one or more transition
  /// widgets that define how the route arrives on and leaves the screen.
948
  ///
949 950 951 952
  /// By default, the child (which contains the widget returned by [buildPage])
  /// is not wrapped in any transition widgets.
  ///
  /// The [buildTransitions] method, in contrast to [buildPage], is called each
953 954
  /// time the [Route]'s state changes while it is visible (e.g. if the value of
  /// [canPop] changes on the active route).
955
  ///
956
  /// The [buildTransitions] method is typically used to define transitions
957 958 959 960 961 962 963 964 965 966 967 968 969
  /// that animate the new topmost route's comings and goings. When the
  /// [Navigator] pushes a route on the top of its stack, the new route's
  /// primary [animation] runs from 0.0 to 1.0. When the Navigator pops the
  /// topmost route, e.g. because the use pressed the back button, the
  /// primary animation runs from 1.0 to 0.0.
  ///
  /// The following example uses the primary animation to drive a
  /// [SlideTransition] that translates the top of the new route vertically
  /// from the bottom of the screen when it is pushed on the Navigator's
  /// stack. When the route is popped the SlideTransition translates the
  /// route from the top of the screen back to the bottom.
  ///
  /// ```dart
970
  /// PageRouteBuilder(
971 972 973 974 975
  ///   pageBuilder: (BuildContext context,
  ///       Animation<double> animation,
  ///       Animation<double> secondaryAnimation,
  ///       Widget child,
  ///   ) {
976 977 978 979
  ///     return Scaffold(
  ///       appBar: AppBar(title: Text('Hello')),
  ///       body: Center(
  ///         child: Text('Hello World'),
980 981 982 983 984 985 986 987 988
  ///       ),
  ///     );
  ///   },
  ///   transitionsBuilder: (
  ///       BuildContext context,
  ///       Animation<double> animation,
  ///       Animation<double> secondaryAnimation,
  ///       Widget child,
  ///    ) {
989 990
  ///     return SlideTransition(
  ///       position: Tween<Offset>(
991 992
  ///         begin: const Offset(0.0, 1.0),
  ///         end: Offset.zero,
993 994 995 996 997
  ///       ).animate(animation),
  ///       child: child, // child is the value returned by pageBuilder
  ///     );
  ///   },
  /// );
998
  /// ```
999
  ///
1000 1001
  /// We've used [PageRouteBuilder] to demonstrate the [buildTransitions] method
  /// here. The body of an override of the [buildTransitions] method would be
1002 1003
  /// defined in the same way.
  ///
1004
  /// When the [Navigator] pushes a route on the top of its stack, the
1005 1006 1007 1008 1009
  /// [secondaryAnimation] can be used to define how the route that was on
  /// the top of the stack leaves the screen. Similarly when the topmost route
  /// is popped, the secondaryAnimation can be used to define how the route
  /// below it reappears on the screen. When the Navigator pushes a new route
  /// on the top of its stack, the old topmost route's secondaryAnimation
1010
  /// runs from 0.0 to 1.0. When the Navigator pops the topmost route, the
1011 1012 1013
  /// secondaryAnimation for the route below it runs from 1.0 to 0.0.
  ///
  /// The example below adds a transition that's driven by the
1014
  /// [secondaryAnimation]. When this route disappears because a new route has
1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025
  /// been pushed on top of it, it translates in the opposite direction of
  /// the new route. Likewise when the route is exposed because the topmost
  /// route has been popped off.
  ///
  /// ```dart
  ///   transitionsBuilder: (
  ///       BuildContext context,
  ///       Animation<double> animation,
  ///       Animation<double> secondaryAnimation,
  ///       Widget child,
  ///   ) {
1026 1027
  ///     return SlideTransition(
  ///       position: AlignmentTween(
1028 1029
  ///         begin: const Offset(0.0, 1.0),
  ///         end: Offset.zero,
1030
  ///       ).animate(animation),
1031 1032
  ///       child: SlideTransition(
  ///         position: TweenOffset(
1033 1034
  ///           begin: Offset.zero,
  ///           end: const Offset(0.0, 1.0),
1035 1036 1037 1038 1039
  ///         ).animate(secondaryAnimation),
  ///         child: child,
  ///       ),
  ///     );
  ///   }
1040 1041 1042
  /// ```
  ///
  /// In practice the `secondaryAnimation` is used pretty rarely.
1043
  ///
1044
  /// The arguments to this method are as follows:
1045
  ///
1046 1047 1048
  ///  * `context`: The context in which the route is being built.
  ///  * [animation]: When the [Navigator] pushes a route on the top of its stack,
  ///    the new route's primary [animation] runs from 0.0 to 1.0. When the [Navigator]
1049
  ///    pops the topmost route this animation runs from 1.0 to 0.0.
1050 1051
  ///  * [secondaryAnimation]: When the Navigator pushes a new route
  ///    on the top of its stack, the old topmost route's [secondaryAnimation]
1052
  ///    runs from 0.0 to 1.0. When the [Navigator] pops the topmost route, the
1053
  ///    [secondaryAnimation] for the route below it runs from 1.0 to 0.0.
1054
  ///  * `child`, the page contents, as returned by [buildPage].
1055 1056 1057 1058 1059
  ///
  /// See also:
  ///
  ///  * [buildPage], which is used to describe the actual contents of the page,
  ///    and whose result is passed to the `child` argument of this method.
1060
  Widget buildTransitions(
1061 1062 1063 1064
    BuildContext context,
    Animation<double> animation,
    Animation<double> secondaryAnimation,
    Widget child,
1065
  ) {
1066 1067 1068
    return child;
  }

1069
  @override
1070 1071
  void install() {
    super.install();
1072 1073
    _animationProxy = ProxyAnimation(super.animation);
    _secondaryAnimationProxy = ProxyAnimation(super.secondaryAnimation);
1074 1075
  }

1076
  @override
1077
  TickerFuture didPush() {
1078 1079 1080
    if (_scopeKey.currentState != null) {
      navigator.focusScopeNode.setFirstFocus(_scopeKey.currentState.focusScopeNode);
    }
1081
    return super.didPush();
1082 1083
  }

1084 1085 1086 1087 1088 1089 1090 1091
  @override
  void didAdd() {
    if (_scopeKey.currentState != null) {
      navigator.focusScopeNode.setFirstFocus(_scopeKey.currentState.focusScopeNode);
    }
    super.didAdd();
  }

1092 1093
  // The API for subclasses to override - used by this class

1094
  /// {@template flutter.widgets.modalRoute.barrierDismissible}
Hixie's avatar
Hixie committed
1095
  /// Whether you can dismiss this route by tapping the modal barrier.
1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108
  ///
  /// The modal barrier is the scrim that is rendered behind each route, which
  /// generally prevents the user from interacting with the route below the
  /// current route, and normally partially obscures such routes.
  ///
  /// For example, when a dialog is on the screen, the page below the dialog is
  /// usually darkened by the modal barrier.
  ///
  /// If [barrierDismissible] is true, then tapping this barrier will cause the
  /// current route to be popped (see [Navigator.pop]) with null as the value.
  ///
  /// If [barrierDismissible] is false, then tapping the barrier has no effect.
  ///
1109
  /// If this getter would ever start returning a different value, the
1110
  /// [Route.changedInternalState] should be invoked so that the change can take
1111 1112
  /// effect.
  ///
1113 1114 1115 1116
  /// See also:
  ///
  ///  * [barrierColor], which controls the color of the scrim for this route.
  ///  * [ModalBarrier], the widget that implements this feature.
1117
  /// {@endtemplate}
1118
  bool get barrierDismissible;
1119

1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130
  /// Whether the semantics of the modal barrier are included in the
  /// semantics tree.
  ///
  /// The modal barrier is the scrim that is rendered behind each route, which
  /// generally prevents the user from interacting with the route below the
  /// current route, and normally partially obscures such routes.
  ///
  /// If [semanticsDismissible] is true, then modal barrier semantics are
  /// included in the semantics tree.
  ///
  /// If [semanticsDismissible] is false, then modal barrier semantics are
1131
  /// excluded from the semantics tree and tapping on the modal barrier
1132 1133 1134
  /// has no effect.
  bool get semanticsDismissible => true;

1135
  /// {@template flutter.widgets.modalRoute.barrierColor}
Hixie's avatar
Hixie committed
1136 1137
  /// The color to use for the modal barrier. If this is null, the barrier will
  /// be transparent.
1138
  ///
1139 1140 1141 1142 1143 1144 1145
  /// The modal barrier is the scrim that is rendered behind each route, which
  /// generally prevents the user from interacting with the route below the
  /// current route, and normally partially obscures such routes.
  ///
  /// For example, when a dialog is on the screen, the page below the dialog is
  /// usually darkened by the modal barrier.
  ///
1146 1147
  /// The color is ignored, and the barrier made invisible, when
  /// [ModalRoute.offstage] is true.
1148 1149 1150
  ///
  /// While the route is animating into position, the color is animated from
  /// transparent to the specified color.
1151
  /// {@endtemplate}
1152
  ///
1153
  /// If this getter would ever start returning a different color, the
1154
  /// [Route.changedInternalState] should be invoked so that the change can take
1155 1156
  /// effect.
  ///
1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170
  /// {@tool snippet}
  ///
  /// It is safe to use `navigator.context` here. For example, to make
  /// the barrier color use the theme's background color, one could say:
  ///
  /// ```dart
  /// Color get barrierColor => Theme.of(navigator.context).backgroundColor;
  /// ```
  ///
  /// The [Navigator] causes the [ModalRoute]'s modal barrier overlay entry
  /// to rebuild any time its dependencies change.
  ///
  /// {@end-tool}
  ///
1171 1172 1173 1174 1175
  /// See also:
  ///
  ///  * [barrierDismissible], which controls the behavior of the barrier when
  ///    tapped.
  ///  * [ModalBarrier], the widget that implements this feature.
Hixie's avatar
Hixie committed
1176
  Color get barrierColor;
1177

1178
  /// {@template flutter.widgets.modalRoute.barrierLabel}
1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189
  /// The semantic label used for a dismissible barrier.
  ///
  /// If the barrier is dismissible, this label will be read out if
  /// accessibility tools (like VoiceOver on iOS) focus on the barrier.
  ///
  /// The modal barrier is the scrim that is rendered behind each route, which
  /// generally prevents the user from interacting with the route below the
  /// current route, and normally partially obscures such routes.
  ///
  /// For example, when a dialog is on the screen, the page below the dialog is
  /// usually darkened by the modal barrier.
1190
  /// {@endtemplate}
1191
  ///
1192
  /// If this getter would ever start returning a different label, the
1193
  /// [Route.changedInternalState] should be invoked so that the change can take
1194 1195
  /// effect.
  ///
1196 1197 1198 1199 1200 1201 1202
  /// See also:
  ///
  ///  * [barrierDismissible], which controls the behavior of the barrier when
  ///    tapped.
  ///  * [ModalBarrier], the widget that implements this feature.
  String get barrierLabel;

1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214
  /// The curve that is used for animating the modal barrier in and out.
  ///
  /// The modal barrier is the scrim that is rendered behind each route, which
  /// generally prevents the user from interacting with the route below the
  /// current route, and normally partially obscures such routes.
  ///
  /// For example, when a dialog is on the screen, the page below the dialog is
  /// usually darkened by the modal barrier.
  ///
  /// While the route is animating into position, the color is animated from
  /// transparent to the specified [barrierColor].
  ///
1215
  /// If this getter would ever start returning a different curve, the
1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228
  /// [changedInternalState] should be invoked so that the change can take
  /// effect.
  ///
  /// It defaults to [Curves.ease].
  ///
  /// See also:
  ///
  ///  * [barrierColor], which determines the color that the modal transitions
  ///    to.
  ///  * [Curves] for a collection of common curves.
  ///  * [AnimatedModalBarrier], the widget that implements this feature.
  Curve get barrierCurve => Curves.ease;

1229
  /// {@template flutter.widgets.modalRoute.maintainState}
1230 1231 1232 1233 1234 1235 1236
  /// Whether the route should remain in memory when it is inactive.
  ///
  /// If this is true, then the route is maintained, so that any futures it is
  /// holding from the next route will properly resolve when the next route
  /// pops. If this is not necessary, this can be set to false to allow the
  /// framework to entirely discard the route's widget hierarchy when it is not
  /// visible.
1237
  /// {@endtemplate}
1238
  ///
1239 1240 1241
  /// If this getter would ever start returning a different value, the
  /// [changedInternalState] should be invoked so that the change can take
  /// effect.
1242 1243
  bool get maintainState;

1244 1245 1246

  // The API for _ModalScope and HeroController

1247 1248 1249 1250 1251 1252 1253
  /// Whether this route is currently offstage.
  ///
  /// On the first frame of a route's entrance transition, the route is built
  /// [Offstage] using an animation progress of 1.0. The route is invisible and
  /// non-interactive, but each widget has its final size and position. This
  /// mechanism lets the [HeroController] determine the final local of any hero
  /// widgets being animated as part of the transition.
1254 1255 1256
  ///
  /// The modal barrier, if any, is not rendered if [offstage] is true (see
  /// [barrierColor]).
1257 1258
  ///
  /// Whenever this changes value, [changedInternalState] is called.
1259 1260
  bool get offstage => _offstage;
  bool _offstage = false;
1261
  set offstage(bool value) {
1262 1263
    if (_offstage == value)
      return;
1264 1265 1266
    setState(() {
      _offstage = value;
    });
1267
    _animationProxy.parent = _offstage ? kAlwaysCompleteAnimation : super.animation;
1268
    _secondaryAnimationProxy.parent = _offstage ? kAlwaysDismissedAnimation : super.secondaryAnimation;
1269
    changedInternalState();
1270 1271
  }

1272
  /// The build context for the subtree containing the primary content of this route.
1273 1274
  BuildContext get subtreeContext => _subtreeKey.currentContext;

1275 1276 1277 1278 1279
  @override
  Animation<double> get animation => _animationProxy;
  ProxyAnimation _animationProxy;

  @override
1280 1281
  Animation<double> get secondaryAnimation => _secondaryAnimationProxy;
  ProxyAnimation _secondaryAnimationProxy;
1282

1283 1284
  final List<WillPopCallback> _willPopCallbacks = <WillPopCallback>[];

1285 1286 1287 1288 1289
  /// Returns [RoutePopDisposition.doNotPop] if any of callbacks added with
  /// [addScopedWillPopCallback] returns either false or null. If they all
  /// return true, the base [Route.willPop]'s result will be returned. The
  /// callbacks will be called in the order they were added, and will only be
  /// called if all previous callbacks returned true.
1290 1291 1292 1293 1294 1295 1296 1297
  ///
  /// Typically this method is not overridden because applications usually
  /// don't create modal routes directly, they use higher level primitives
  /// like [showDialog]. The scoped [WillPopCallback] list makes it possible
  /// for ModalRoute descendants to collectively define the value of `willPop`.
  ///
  /// See also:
  ///
1298 1299 1300 1301 1302
  ///  * [Form], which provides an `onWillPop` callback that uses this mechanism.
  ///  * [addScopedWillPopCallback], which adds a callback to the list this
  ///    method checks.
  ///  * [removeScopedWillPopCallback], which removes a callback from the list
  ///    this method checks.
1303
  @override
1304
  Future<RoutePopDisposition> willPop() async {
1305
    final _ModalScopeState<T> scope = _scopeKey.currentState;
1306
    assert(scope != null);
1307
    for (final WillPopCallback callback in List<WillPopCallback>.from(_willPopCallbacks)) {
1308
      if (await callback() != true)
1309
        return RoutePopDisposition.doNotPop;
1310
    }
1311
    return await super.willPop();
1312 1313 1314 1315
  }

  /// Enables this route to veto attempts by the user to dismiss it.
  ///
1316 1317 1318 1319 1320 1321
  /// This callback is typically added using a [WillPopScope] widget. That
  /// widget finds the enclosing [ModalRoute] and uses this function to register
  /// this callback:
  ///
  /// ```dart
  /// Widget build(BuildContext context) {
1322
  ///   return WillPopScope(
1323 1324 1325 1326 1327 1328 1329
  ///     onWillPop: askTheUserIfTheyAreSure,
  ///     child: ...,
  ///   );
  /// }
  /// ```
  ///
  /// This callback runs asynchronously and it's possible that it will be called
1330
  /// after its route has been disposed. The callback should check [State.mounted]
1331 1332 1333 1334 1335 1336 1337
  /// before doing anything.
  ///
  /// A typical application of this callback would be to warn the user about
  /// unsaved [Form] data if the user attempts to back out of the form. In that
  /// case, use the [Form.onWillPop] property to register the callback.
  ///
  /// To register a callback manually, look up the enclosing [ModalRoute] in a
1338
  /// [State.didChangeDependencies] callback:
1339 1340
  ///
  /// ```dart
1341 1342
  /// ModalRoute<dynamic> _route;
  ///
1343
  /// @override
1344 1345
  /// void didChangeDependencies() {
  ///  super.didChangeDependencies();
1346 1347 1348
  ///  _route?.removeScopedWillPopCallback(askTheUserIfTheyAreSure);
  ///  _route = ModalRoute.of(context);
  ///  _route?.addScopedWillPopCallback(askTheUserIfTheyAreSure);
1349 1350 1351
  /// }
  /// ```
  ///
1352 1353 1354 1355
  /// If you register a callback manually, be sure to remove the callback with
  /// [removeScopedWillPopCallback] by the time the widget has been disposed. A
  /// stateful widget can do this in its dispose method (continuing the previous
  /// example):
1356 1357 1358 1359 1360
  ///
  /// ```dart
  /// @override
  /// void dispose() {
  ///   _route?.removeScopedWillPopCallback(askTheUserIfTheyAreSure);
1361
  ///   _route = null;
1362 1363 1364 1365
  ///   super.dispose();
  /// }
  /// ```
  ///
1366 1367
  /// See also:
  ///
1368 1369 1370 1371 1372 1373
  ///  * [WillPopScope], which manages the registration and unregistration
  ///    process automatically.
  ///  * [Form], which provides an `onWillPop` callback that uses this mechanism.
  ///  * [willPop], which runs the callbacks added with this method.
  ///  * [removeScopedWillPopCallback], which removes a callback from the list
  ///    that [willPop] checks.
1374
  void addScopedWillPopCallback(WillPopCallback callback) {
1375 1376
    assert(_scopeKey.currentState != null, 'Tried to add a willPop callback to a route that is not currently in the tree.');
    _willPopCallbacks.add(callback);
1377 1378 1379 1380 1381 1382
  }

  /// Remove one of the callbacks run by [willPop].
  ///
  /// See also:
  ///
1383 1384 1385
  ///  * [Form], which provides an `onWillPop` callback that uses this mechanism.
  ///  * [addScopedWillPopCallback], which adds callback to the list
  ///    checked by [willPop].
1386
  void removeScopedWillPopCallback(WillPopCallback callback) {
1387 1388
    assert(_scopeKey.currentState != null, 'Tried to remove a willPop callback from a route that is not currently in the tree.');
    _willPopCallbacks.remove(callback);
1389 1390
  }

1391 1392
  /// True if one or more [WillPopCallback] callbacks exist.
  ///
1393 1394 1395 1396
  /// This method is used to disable the horizontal swipe pop gesture supported
  /// by [MaterialPageRoute] for [TargetPlatform.iOS] and
  /// [TargetPlatform.macOS]. If a pop might be vetoed, then the back gesture is
  /// disabled.
1397
  ///
1398 1399 1400 1401
  /// The [buildTransitions] method will not be called again if this changes,
  /// since it can change during the build as descendants of the route add or
  /// remove callbacks.
  ///
1402 1403
  /// See also:
  ///
1404 1405 1406 1407
  ///  * [addScopedWillPopCallback], which adds a callback.
  ///  * [removeScopedWillPopCallback], which removes a callback.
  ///  * [willHandlePopInternally], which reports on another reason why
  ///    a pop might be vetoed.
1408
  @protected
1409
  bool get hasScopedWillPopCallback {
1410 1411 1412 1413 1414 1415 1416
    return _willPopCallbacks.isNotEmpty;
  }

  @override
  void didChangePrevious(Route<dynamic> previousRoute) {
    super.didChangePrevious(previousRoute);
    changedInternalState();
1417
  }
1418

1419 1420 1421 1422
  @override
  void changedInternalState() {
    super.changedInternalState();
    setState(() { /* internal state already changed */ });
1423
    _modalBarrier.markNeedsBuild();
1424
    _modalScope.maintainState = maintainState;
1425 1426 1427
  }

  @override
1428 1429 1430 1431
  void changedExternalState() {
    super.changedExternalState();
    if (_scopeKey.currentState != null)
      _scopeKey.currentState._forceRebuildPage();
1432 1433 1434 1435
  }

  /// Whether this route can be popped.
  ///
1436 1437 1438
  /// When this changes, if the route is visible, the route will
  /// rebuild, and any widgets that used [ModalRoute.of] will be
  /// notified.
1439
  bool get canPop => !isFirst || willHandlePopInternally;
1440

1441 1442
  // Internals

1443 1444 1445
  final GlobalKey<_ModalScopeState<T>> _scopeKey = GlobalKey<_ModalScopeState<T>>();
  final GlobalKey _subtreeKey = GlobalKey();
  final PageStorageBucket _storageBucket = PageStorageBucket();
1446

1447
  // one of the builders
1448
  OverlayEntry _modalBarrier;
1449
  Widget _buildModalBarrier(BuildContext context) {
1450
    Widget barrier;
1451
    if (barrierColor != null && barrierColor.alpha != 0 && !offstage) { // changedInternalState is called if barrierColor or offstage updates
Hixie's avatar
Hixie committed
1452
      assert(barrierColor != _kTransparent);
1453 1454 1455
      final Animation<Color> color = animation.drive(
        ColorTween(
          begin: _kTransparent,
1456 1457
          end: barrierColor, // changedInternalState is called if barrierColor updates
        ).chain(CurveTween(curve: barrierCurve)), // changedInternalState is called if barrierCurve updates
1458
      );
1459
      barrier = AnimatedModalBarrier(
1460
        color: color,
1461 1462
        dismissible: barrierDismissible, // changedInternalState is called if barrierDismissible updates
        semanticsLabel: barrierLabel, // changedInternalState is called if barrierLabel updates
1463
        barrierSemanticsDismissible: semanticsDismissible,
Hixie's avatar
Hixie committed
1464 1465
      );
    } else {
1466
      barrier = ModalBarrier(
1467 1468
        dismissible: barrierDismissible, // changedInternalState is called if barrierDismissible updates
        semanticsLabel: barrierLabel, // changedInternalState is called if barrierLabel updates
1469
        barrierSemanticsDismissible: semanticsDismissible,
1470
      );
Hixie's avatar
Hixie committed
1471
    }
1472 1473 1474 1475 1476 1477
    if (_filter != null) {
      barrier = BackdropFilter(
        filter: _filter,
        child: barrier,
      );
    }
1478
    barrier = IgnorePointer(
1479
      ignoring: animation.status == AnimationStatus.reverse || // changedInternalState is called when animation.status updates
1480 1481
                animation.status == AnimationStatus.dismissed, // dismissed is possible when doing a manual pop gesture
      child: barrier,
1482
    );
1483 1484 1485 1486 1487 1488 1489 1490
    if (semanticsDismissible && barrierDismissible) {
      // To be sorted after the _modalScope.
      barrier = Semantics(
        sortKey: const OrdinalSortKey(1.0),
        child: barrier,
      );
    }
    return barrier;
1491 1492
  }

1493 1494 1495 1496
  // We cache the part of the modal scope that doesn't change from frame to
  // frame so that we minimize the amount of building that happens.
  Widget _modalScopeCache;

1497
  // one of the builders
1498
  Widget _buildModalScope(BuildContext context) {
1499 1500 1501 1502 1503 1504 1505 1506
    // To be sorted before the _modalBarrier.
    return _modalScopeCache ??= Semantics(
      sortKey: const OrdinalSortKey(0.0),
      child: _ModalScope<T>(
        key: _scopeKey,
        route: this,
        // _ModalScope calls buildTransitions() and buildChild(), defined above
      )
1507 1508 1509
    );
  }

1510 1511
  OverlayEntry _modalScope;

1512
  @override
1513
  Iterable<OverlayEntry> createOverlayEntries() sync* {
1514
    yield _modalBarrier = OverlayEntry(builder: _buildModalBarrier);
1515
    yield _modalScope = OverlayEntry(builder: _buildModalScope, maintainState: maintainState);
1516
  }
1517

1518
  @override
1519
  String toString() => '${objectRuntimeType(this, 'ModalRoute')}($settings, animation: $_animation)';
1520
}
Hixie's avatar
Hixie committed
1521 1522 1523

/// A modal route that overlays a widget over the current route.
abstract class PopupRoute<T> extends ModalRoute<T> {
1524 1525 1526
  /// Initializes the [PopupRoute].
  PopupRoute({
    RouteSettings settings,
1527 1528 1529 1530 1531
    ui.ImageFilter filter,
  }) : super(
         filter: filter,
         settings: settings,
       );
1532

1533
  @override
Hixie's avatar
Hixie committed
1534
  bool get opaque => false;
1535

1536 1537
  @override
  bool get maintainState => true;
Hixie's avatar
Hixie committed
1538
}
1539 1540 1541 1542

/// A [Navigator] observer that notifies [RouteAware]s of changes to the
/// state of their [Route].
///
1543 1544 1545
/// [RouteObserver] informs subscribers whenever a route of type `R` is pushed
/// on top of their own route of type `R` or popped from it. This is for example
/// useful to keep track of page transitions, e.g. a `RouteObserver<PageRoute>`
1546 1547 1548
/// will inform subscribed [RouteAware]s whenever the user navigates away from
/// the current page route to another page route.
///
1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561
/// To be informed about route changes of any type, consider instantiating a
/// `RouteObserver<Route>`.
///
/// ## Type arguments
///
/// When using more aggressive
/// [lints](http://dart-lang.github.io/linter/lints/), in particular lints such
/// as `always_specify_types`, the Dart analyzer will require that certain types
/// be given with their type arguments. Since the [Route] class and its
/// subclasses have a type argument, this includes the arguments passed to this
/// class. Consider using `dynamic` to specify the entire class of routes rather
/// than only specific subtypes. For example, to watch for all [PageRoute]
/// variants, the `RouteObserver<PageRoute<dynamic>>` type may be used.
1562
///
1563
/// {@tool snippet}
1564
///
1565
/// To make a [StatefulWidget] aware of its current [Route] state, implement
1566
/// [RouteAware] in its [State] and subscribe it to a [RouteObserver]:
1567 1568
///
/// ```dart
1569
/// // Register the RouteObserver as a navigation observer.
1570
/// final RouteObserver<PageRoute> routeObserver = RouteObserver<PageRoute>();
1571
/// void main() {
1572 1573
///   runApp(MaterialApp(
///     home: Container(),
1574 1575 1576 1577 1578
///     navigatorObservers: [routeObserver],
///   ));
/// }
///
/// class RouteAwareWidget extends StatefulWidget {
1579
///   State<RouteAwareWidget> createState() => RouteAwareWidgetState();
1580 1581 1582
/// }
///
/// // Implement RouteAware in a widget's state and subscribe it to the RouteObserver.
1583
/// class RouteAwareWidgetState extends State<RouteAwareWidget> with RouteAware {
1584
///
1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598
///   @override
///   void didChangeDependencies() {
///     super.didChangeDependencies();
///     routeObserver.subscribe(this, ModalRoute.of(context));
///   }
///
///   @override
///   void dispose() {
///     routeObserver.unsubscribe(this);
///     super.dispose();
///   }
///
///   @override
///   void didPush() {
1599
///     // Route was pushed onto navigator and is now topmost route.
1600 1601 1602 1603
///   }
///
///   @override
///   void didPopNext() {
1604
///     // Covering route was popped off the navigator.
1605 1606
///   }
///
1607
///   @override
1608
///   Widget build(BuildContext context) => Container();
1609
///
1610 1611
/// }
/// ```
1612
/// {@end-tool}
1613 1614
class RouteObserver<R extends Route<dynamic>> extends NavigatorObserver {
  final Map<R, Set<RouteAware>> _listeners = <R, Set<RouteAware>>{};
1615

1616 1617 1618 1619 1620
  /// Subscribe [routeAware] to be informed about changes to [route].
  ///
  /// Going forward, [routeAware] will be informed about qualifying changes
  /// to [route], e.g. when [route] is covered by another route or when [route]
  /// is popped off the [Navigator] stack.
1621
  void subscribe(RouteAware routeAware, R route) {
1622 1623
    assert(routeAware != null);
    assert(route != null);
1624
    final Set<RouteAware> subscribers = _listeners.putIfAbsent(route, () => <RouteAware>{});
1625
    if (subscribers.add(routeAware)) {
1626 1627 1628 1629
      routeAware.didPush();
    }
  }

1630 1631
  /// Unsubscribe [routeAware].
  ///
1632 1633
  /// [routeAware] is no longer informed about changes to its route. If the given argument was
  /// subscribed to multiple types, this will unregister it (once) from each type.
1634 1635
  void unsubscribe(RouteAware routeAware) {
    assert(routeAware != null);
1636
    for (final R route in _listeners.keys) {
1637 1638 1639
      final Set<RouteAware> subscribers = _listeners[route];
      subscribers?.remove(routeAware);
    }
1640 1641 1642 1643
  }

  @override
  void didPop(Route<dynamic> route, Route<dynamic> previousRoute) {
1644
    if (route is R && previousRoute is R) {
1645 1646 1647
      final List<RouteAware> previousSubscribers = _listeners[previousRoute]?.toList();

      if (previousSubscribers != null) {
1648
        for (final RouteAware routeAware in previousSubscribers) {
1649 1650 1651 1652 1653 1654 1655
          routeAware.didPopNext();
        }
      }

      final List<RouteAware> subscribers = _listeners[route]?.toList();

      if (subscribers != null) {
1656
        for (final RouteAware routeAware in subscribers) {
1657 1658 1659
          routeAware.didPop();
        }
      }
1660 1661 1662 1663 1664
    }
  }

  @override
  void didPush(Route<dynamic> route, Route<dynamic> previousRoute) {
1665
    if (route is R && previousRoute is R) {
1666 1667 1668
      final Set<RouteAware> previousSubscribers = _listeners[previousRoute];

      if (previousSubscribers != null) {
1669
        for (final RouteAware routeAware in previousSubscribers) {
1670 1671 1672
          routeAware.didPushNext();
        }
      }
1673 1674 1675 1676
    }
  }
}

1677 1678 1679 1680
/// An interface for objects that are aware of their current [Route].
///
/// This is used with [RouteObserver] to make a widget aware of changes to the
/// [Navigator]'s session history.
1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694
abstract class RouteAware {
  /// Called when the top route has been popped off, and the current route
  /// shows up.
  void didPopNext() { }

  /// Called when the current route has been pushed.
  void didPush() { }

  /// Called when the current route has been popped off.
  void didPop() { }

  /// Called when a new route has been pushed, and the current route is no
  /// longer visible.
  void didPushNext() { }
1695
}
1696 1697 1698 1699 1700 1701 1702 1703 1704 1705

class _DialogRoute<T> extends PopupRoute<T> {
  _DialogRoute({
    @required RoutePageBuilder pageBuilder,
    bool barrierDismissible = true,
    String barrierLabel,
    Color barrierColor = const Color(0x80000000),
    Duration transitionDuration = const Duration(milliseconds: 200),
    RouteTransitionsBuilder transitionBuilder,
    RouteSettings settings,
1706 1707 1708 1709 1710 1711 1712 1713
  }) : assert(barrierDismissible != null),
       _pageBuilder = pageBuilder,
       _barrierDismissible = barrierDismissible,
       _barrierLabel = barrierLabel,
       _barrierColor = barrierColor,
       _transitionDuration = transitionDuration,
       _transitionBuilder = transitionBuilder,
       super(settings: settings);
1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736

  final RoutePageBuilder _pageBuilder;

  @override
  bool get barrierDismissible => _barrierDismissible;
  final bool _barrierDismissible;

  @override
  String get barrierLabel => _barrierLabel;
  final String _barrierLabel;

  @override
  Color get barrierColor => _barrierColor;
  final Color _barrierColor;

  @override
  Duration get transitionDuration => _transitionDuration;
  final Duration _transitionDuration;

  final RouteTransitionsBuilder _transitionBuilder;

  @override
  Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
1737
    return Semantics(
1738 1739 1740 1741 1742 1743 1744 1745 1746
      child: _pageBuilder(context, animation, secondaryAnimation),
      scopesRoute: true,
      explicitChildNodes: true,
    );
  }

  @override
  Widget buildTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
    if (_transitionBuilder == null) {
1747 1748
      return FadeTransition(
          opacity: CurvedAnimation(
1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769
            parent: animation,
            curve: Curves.linear,
          ),
          child: child);
    } // Some default transition
    return _transitionBuilder(context, animation, secondaryAnimation, child);
  }
}

/// Displays a dialog above the current contents of the app.
///
/// This function allows for customization of aspects of the dialog popup.
///
/// This function takes a `pageBuilder` which is used to build the primary
/// content of the route (typically a dialog widget). Content below the dialog
/// is dimmed with a [ModalBarrier]. The widget returned by the `pageBuilder`
/// does not share a context with the location that `showGeneralDialog` is
/// originally called from. Use a [StatefulBuilder] or a custom
/// [StatefulWidget] if the dialog needs to update dynamically. The
/// `pageBuilder` argument can not be null.
///
1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781
/// The `context` argument is used to look up the [Navigator] for the
/// dialog. It is only used when the method is called. Its corresponding widget
/// can be safely removed from the tree before the dialog is closed.
///
/// The `useRootNavigator` argument is used to determine whether to push the
/// dialog to the [Navigator] furthest from or nearest to the given `context`.
/// By default, `useRootNavigator` is `true` and the dialog route created by
/// this method is pushed to the root navigator.
///
/// If the application has multiple [Navigator] objects, it may be necessary to
/// call `Navigator.of(context, rootNavigator: true).pop(result)` to close the
/// dialog rather than just `Navigator.pop(context, result)`.
1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801
///
/// The `barrierDismissible` argument is used to determine whether this route
/// can be dismissed by tapping the modal barrier. This argument defaults
/// to true. If `barrierDismissible` is true, a non-null `barrierLabel` must be
/// provided.
///
/// The `barrierLabel` argument is the semantic label used for a dismissible
/// barrier. This argument defaults to "Dismiss".
///
/// The `barrierColor` argument is the color used for the modal barrier. This
/// argument defaults to `Color(0x80000000)`.
///
/// The `transitionDuration` argument is used to determine how long it takes
/// for the route to arrive on or leave off the screen. This argument defaults
/// to 200 milliseconds.
///
/// The `transitionBuilder` argument is used to define how the route arrives on
/// and leaves off the screen. By default, the transition is a linear fade of
/// the page's contents.
///
1802 1803 1804
/// The `routeSettings` will be used in the construction of the dialog's route.
/// See [RouteSettings] for more details.
///
1805 1806 1807 1808
/// Returns a [Future] that resolves to the value (if any) that was passed to
/// [Navigator.pop] when the dialog was closed.
///
/// See also:
1809
///
1810 1811 1812 1813 1814
///  * [showDialog], which displays a Material-style dialog.
///  * [showCupertinoDialog], which displays an iOS-style dialog.
Future<T> showGeneralDialog<T>({
  @required BuildContext context,
  @required RoutePageBuilder pageBuilder,
1815
  bool barrierDismissible = false,
1816
  String barrierLabel,
1817 1818
  Color barrierColor = const Color(0x80000000),
  Duration transitionDuration = const Duration(milliseconds: 200),
1819
  RouteTransitionsBuilder transitionBuilder,
1820
  bool useRootNavigator = true,
1821
  RouteSettings routeSettings,
1822 1823
}) {
  assert(pageBuilder != null);
1824
  assert(useRootNavigator != null);
1825
  assert(!barrierDismissible || barrierLabel != null);
1826
  return Navigator.of(context, rootNavigator: useRootNavigator).push<T>(_DialogRoute<T>(
1827 1828 1829 1830 1831 1832
    pageBuilder: pageBuilder,
    barrierDismissible: barrierDismissible,
    barrierLabel: barrierLabel,
    barrierColor: barrierColor,
    transitionDuration: transitionDuration,
    transitionBuilder: transitionBuilder,
1833
    settings: routeSettings,
1834 1835 1836 1837 1838 1839 1840
  ));
}

/// Signature for the function that builds a route's primary contents.
/// Used in [PageRouteBuilder] and [showGeneralDialog].
///
/// See [ModalRoute.buildPage] for complete definition of the parameters.
1841
typedef RoutePageBuilder = Widget Function(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation);
1842 1843 1844 1845 1846

/// Signature for the function that builds a route's transitions.
/// Used in [PageRouteBuilder] and [showGeneralDialog].
///
/// See [ModalRoute.buildTransitions] for complete definition of the parameters.
1847
typedef RouteTransitionsBuilder = Widget Function(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child);