Commit 88e74693 authored by Hixie's avatar Hixie

Clean up the Navigator API again.

* Split didPush() into didPush() and install(), so that we can install
  the overlays without triggering the push logic. This will be used in a
  subsequent patch to implement route replacement.

* Split didPop() into didPop() and dispose(), so that we can remove
  overlays without triggering the pop logic. Also for a subsequent patch
  that implements replacement.

* Clean up _navigator on the routes when the Navigator itself is
  disposed.

* Drop the forwarding logic on willPushNext() -- now didPushNext() --
  and didPopNext(), since we no longer have StateRoutes to get in the
  way.

* Implement isCurrent more broadly and without having to keep track of
  state.

* Provide some toString()s on NamedRouteSettings and ModalRoutes.

* Make OverlayState.initState() use the insertAl functionality.

* Make OverlayRoute.builders abstract since that way you'll catch when
  you forget to do it. If you don't want overlays, don't inherit from
  this class.

* Made handleStatusChanged() on TransitionRoute public so that it can be
  overridden by subclasses.
parent e260d7b7
......@@ -10,32 +10,61 @@ abstract class Route<T> {
NavigatorState get navigator => _navigator;
NavigatorState _navigator;
List<OverlayEntry> get overlayEntries;
void didPush(OverlayState overlay, OverlayEntry insertionPoint) { }
List<OverlayEntry> get overlayEntries => const <OverlayEntry>[];
/// Called when the route is inserted into the navigator.
/// Use this to install any overlays.
void install(OverlayState overlay, OverlayEntry insertionPoint) { }
/// Called after install() when the route is pushed onto the navigator.
void didPush() { }
/// A request was made to pop this route. If the route can handle it
/// internally (e.g. because it has its own stack of internal state) then
/// return false, otherwise return true. Returning false will prevent the
/// default behavior of NavigatorState.pop().
///
/// If this is called, the Navigator will not call dispose(). It is the
/// responsibility of the Route to later call dispose().
bool didPop(T result) => true;
/// The given route has been pushed onto the navigator after this route.
/// Return true if the route before this one should be notified also. The
/// first route to return false will be the one passed to the
/// NavigatorObserver's didPush() as the previousRoute.
bool willPushNext(Route nextRoute) => false;
void didPushNext(Route nextRoute) { }
/// The given route, which came after this one, has been popped off the
/// navigator. Return true if the route before this one should be notified
/// also. The first route to return false will be the one passed to the
/// NavigatorObserver's didPush() as the previousRoute.
bool didPopNext(Route nextRoute) => false;
/// navigator.
void didPopNext(Route nextRoute) { }
/// The route should remove its overlays and free any other resources.
///
/// A call to didPop() implies that the Route should call dispose() itself,
/// but it is possible for dispose() to be called directly (e.g. if the route
/// is replaced, or if the navigator itself is disposed).
void dispose() { }
/// Whether this route is the top-most route on the navigator.
bool get isCurrent {
if (_navigator == null)
return false;
assert(_navigator._history.contains(this));
return _navigator._history.last == this;
}
}
class NamedRouteSettings {
const NamedRouteSettings({ this.name, this.mostValuableKeys });
final String name;
final Set<Key> mostValuableKeys;
String toString() {
String result = '"$name"';
if (mostValuableKeys != null && mostValuableKeys.isNotEmpty) {
result += '; keys:';
for (Key key in mostValuableKeys)
result += ' $key';
}
return result;
}
}
typedef Route RouteFactory(NamedRouteSettings settings);
......@@ -93,8 +122,15 @@ class NavigatorState extends State<Navigator> {
}
void dispose() {
assert(!_debugLocked);
assert(() { _debugLocked = true; return true; });
config.observer?._navigator = null;
for (Route route in _history) {
route.dispose();
route._navigator = null;
}
super.dispose();
assert(() { _debugLocked = false; return true; });
}
OverlayState get overlay => _overlayKey.currentState;
......@@ -125,13 +161,14 @@ class NavigatorState extends State<Navigator> {
assert(route != null);
assert(route._navigator == null);
setState(() {
int index = _history.length-1;
while (index >= 0 && _history[index].willPushNext(route))
index -= 1;
Route oldRoute = _history.isNotEmpty ? _history.last : null;
route._navigator = this;
route.didPush(overlay, _currentOverlay);
config.observer?.didPush(route, index >= 0 ? _history[index] : null);
route.install(overlay, _currentOverlay);
_history.add(route);
route.didPush();
if (oldRoute != null)
oldRoute.didPushNext(route);
config.observer?.didPush(route, oldRoute);
});
assert(() { _debugLocked = false; return true; });
}
......@@ -153,14 +190,12 @@ class NavigatorState extends State<Navigator> {
if (route.didPop(result)) {
if (_history.length > 1) {
setState(() {
// We use setState to guarantee that we'll rebuild, since the routes can't
// do that for themselves, even if they have changed their own state (e.g.
// ModalScope.isCurrent).
// We use setState to guarantee that we'll rebuild, since the routes
// can't do that for themselves, even if they have changed their own
// state (e.g. ModalScope.isCurrent).
_history.removeLast();
int index = _history.length-1;
while (index >= 0 && _history[index].didPopNext(route))
index -= 1;
config.observer?.didPop(route, index >= 0 ? _history[index] : null);
_history.last.didPopNext(route);
config.observer?.didPop(route, _history.last);
route._navigator = null;
});
} else {
......
......@@ -52,8 +52,7 @@ class OverlayState extends State<Overlay> {
void initState() {
super.initState();
for (OverlayEntry entry in config.initialEntries)
insert(entry);
insertAll(config.initialEntries);
}
void insert(OverlayEntry entry, { OverlayEntry above }) {
......
......@@ -18,12 +18,13 @@ import 'status_transitions.dart';
const _kTransparent = const Color(0x00000000);
abstract class OverlayRoute<T> extends Route<T> {
List<WidgetBuilder> get builders => const <WidgetBuilder>[];
List<WidgetBuilder> get builders;
List<OverlayEntry> get overlayEntries => _overlayEntries;
final List<OverlayEntry> _overlayEntries = <OverlayEntry>[];
void didPush(OverlayState overlay, OverlayEntry insertionPoint) {
void install(OverlayState overlay, OverlayEntry insertionPoint) {
assert(_overlayEntries.isEmpty);
for (WidgetBuilder builder in builders)
_overlayEntries.add(new OverlayEntry(builder: builder));
overlay?.insertAll(_overlayEntries, above: insertionPoint);
......@@ -47,6 +48,10 @@ abstract class OverlayRoute<T> extends Route<T> {
entry.remove();
_overlayEntries.clear();
}
void dispose() {
finished();
}
}
abstract class TransitionRoute<T> extends OverlayRoute<T> {
......@@ -79,7 +84,7 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> {
T _result;
void _handleStatusChanged(PerformanceStatus status) {
void handleStatusChanged(PerformanceStatus status) {
switch (status) {
case PerformanceStatus.completed:
if (overlayEntries.isNotEmpty)
......@@ -98,11 +103,15 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> {
}
}
void didPush(OverlayState overlay, OverlayEntry insertionPoint) {
_performance = createPerformance()
..addStatusListener(_handleStatusChanged)
..forward();
super.didPush(overlay, insertionPoint);
void install(OverlayState overlay, OverlayEntry insertionPoint) {
_performance = createPerformance();
super.install(overlay, insertionPoint);
}
void didPush() {
_performance.addStatusListener(handleStatusChanged);
_performance.forward();
super.didPush();
}
bool didPop(T result) {
......@@ -117,6 +126,11 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> {
_transitionCompleter?.complete(_result);
}
void dispose() {
_performance.stop();
super.dispose();
}
String get debugLabel => '$runtimeType';
String toString() => '$runtimeType(performance: $_performance)';
}
......@@ -269,9 +283,6 @@ abstract class ModalRoute<T> extends LocalHistoryRoute<T> {
return widget?.route;
}
bool get isCurrent => _isCurrent;
bool _isCurrent = false;
// The API for subclasses to override - used by _ModalScope
......@@ -314,33 +325,6 @@ abstract class ModalRoute<T> extends LocalHistoryRoute<T> {
// Internals
void didPush(OverlayState overlay, OverlayEntry insertionPoint) {
assert(!_isCurrent);
_isCurrent = true;
super.didPush(overlay, insertionPoint);
}
bool didPop(T result) {
assert(_isCurrent);
if (super.didPop(result)) {
_isCurrent = false;
return true;
}
return false;
}
bool willPushNext(Route nextRoute) {
assert(_isCurrent);
_isCurrent = false;
return false;
}
bool didPopNext(Route nextRoute) {
assert(!_isCurrent);
_isCurrent = true;
return false;
}
final GlobalKey<StatusTransitionState> _scopeKey = new GlobalKey<StatusTransitionState>();
final GlobalKey _subtreeKey = new GlobalKey();
final PageStorageBucket _storageBucket = new PageStorageBucket();
......@@ -375,15 +359,16 @@ abstract class ModalRoute<T> extends LocalHistoryRoute<T> {
_buildModalScope
];
String toString() => '$runtimeType($settings, performance: $_performance)';
}
/// A modal route that overlays a widget over the current route.
abstract class PopupRoute<T> extends ModalRoute<T> {
PopupRoute({ Completer<T> completer }) : super(completer: completer);
bool get opaque => false;
bool willPushNext(Route nextRoute) {
void didPushNext(Route nextRoute) {
assert(nextRoute is! PageRoute);
return super.willPushNext(nextRoute);
super.didPushNext(nextRoute);
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment