Commit 309d25d4 authored by Hixie's avatar Hixie

Move Material page animations to Material layer.

PageRoute is now MaterialPageRoute.

This also changes the following:

- Now the HeroController is a Navigator observer, rather than a feature
  of HeroPageRoutes, which are gone. This means heroes can work between
  any kind of ModalRoute now.

- ModalPageRoute is moved from modal_barrier.dart to routes.dart.

- It allows routes to opt-out of their modal barrier being a shortcut to
  popping the route.

- Features of PageRoute that aren't Material-specific get promoted to
  ModalRoute features: storage, the subtree key, offstageness...

The AnimatedModalBarrier is still a ModalRoute feature.
parent 8bc9e1b8
...@@ -33,9 +33,11 @@ export 'src/material/material.dart'; ...@@ -33,9 +33,11 @@ export 'src/material/material.dart';
export 'src/material/material_app.dart'; export 'src/material/material_app.dart';
export 'src/material/material_button.dart'; export 'src/material/material_button.dart';
export 'src/material/material_list.dart'; export 'src/material/material_list.dart';
export 'src/material/popup_menu_item.dart'; export 'src/material/page.dart';
export 'src/material/popup_menu.dart'; export 'src/material/popup_menu.dart';
export 'src/material/popup_menu_item.dart';
export 'src/material/progress_indicator.dart'; export 'src/material/progress_indicator.dart';
export 'src/material/radial_reaction.dart';
export 'src/material/radio.dart'; export 'src/material/radio.dart';
export 'src/material/raised_button.dart'; export 'src/material/raised_button.dart';
export 'src/material/scaffold.dart'; export 'src/material/scaffold.dart';
...@@ -44,11 +46,10 @@ export 'src/material/shadows.dart'; ...@@ -44,11 +46,10 @@ export 'src/material/shadows.dart';
export 'src/material/snack_bar.dart'; export 'src/material/snack_bar.dart';
export 'src/material/switch.dart'; export 'src/material/switch.dart';
export 'src/material/tabs.dart'; export 'src/material/tabs.dart';
export 'src/material/theme_data.dart';
export 'src/material/theme.dart'; export 'src/material/theme.dart';
export 'src/material/theme_data.dart';
export 'src/material/title.dart'; export 'src/material/title.dart';
export 'src/material/tool_bar.dart'; export 'src/material/tool_bar.dart';
export 'src/material/typography.dart'; export 'src/material/typography.dart';
export 'src/material/radial_reaction.dart';
export 'widgets.dart'; export 'widgets.dart';
...@@ -168,7 +168,7 @@ class Performance extends PerformanceView { ...@@ -168,7 +168,7 @@ class Performance extends PerformanceView {
/// Returns a [PerformanceView] for this performance, /// Returns a [PerformanceView] for this performance,
/// so that a pointer to this object can be passed around without /// so that a pointer to this object can be passed around without
/// allowing users of that pointer to mutate the AnimationPerformance state. /// allowing users of that pointer to mutate the Performance state.
PerformanceView get view => this; PerformanceView get view => this;
/// The length of time this performance should last /// The length of time this performance should last
......
...@@ -124,7 +124,9 @@ class _DialogRoute extends ModalRoute { ...@@ -124,7 +124,9 @@ class _DialogRoute extends ModalRoute {
Duration get transitionDuration => const Duration(milliseconds: 150); Duration get transitionDuration => const Duration(milliseconds: 150);
Color get barrierColor => Colors.black54; Color get barrierColor => Colors.black54;
Widget buildModalWidget(BuildContext context) { Widget buildPage(BuildContext context) => child;
Widget buildTransition(BuildContext context, PerformanceView performance, Widget child) {
return new FadeTransition( return new FadeTransition(
performance: performance, performance: performance,
opacity: new AnimatedValue<double>(0.0, end: 1.0, curve: Curves.easeOut), opacity: new AnimatedValue<double>(0.0, end: 1.0, curve: Curves.easeOut),
......
...@@ -8,6 +8,7 @@ import 'package:flutter/rendering.dart'; ...@@ -8,6 +8,7 @@ import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'page.dart';
import 'theme.dart'; import 'theme.dart';
import 'title.dart'; import 'title.dart';
...@@ -95,13 +96,12 @@ class _MaterialAppState extends State<MaterialApp> { ...@@ -95,13 +96,12 @@ class _MaterialAppState extends State<MaterialApp> {
final HeroController _heroController = new HeroController(); final HeroController _heroController = new HeroController();
Route _generateRoute(NamedRouteSettings settings) { Route _generateRoute(NamedRouteSettings settings) {
return new HeroPageRoute( return new MaterialPageRoute(
builder: (BuildContext context) { builder: (BuildContext context) {
RouteBuilder builder = config.routes[settings.name] ?? config.onGenerateRoute(settings.name); RouteBuilder builder = config.routes[settings.name] ?? config.onGenerateRoute(settings.name);
return builder(new RouteArguments(context: context)); return builder(new RouteArguments(context: context));
}, },
settings: settings, settings: settings
heroController: _heroController
); );
} }
...@@ -118,7 +118,8 @@ class _MaterialAppState extends State<MaterialApp> { ...@@ -118,7 +118,8 @@ class _MaterialAppState extends State<MaterialApp> {
title: config.title, title: config.title,
child: new Navigator( child: new Navigator(
key: _navigator, key: _navigator,
onGenerateRoute: _generateRoute onGenerateRoute: _generateRoute,
observer: _heroController
) )
) )
) )
......
...@@ -3,16 +3,10 @@ ...@@ -3,16 +3,10 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'package:flutter/animation.dart'; import 'package:flutter/animation.dart';
import 'package:flutter/widgets.dart';
import 'basic.dart'; class _MaterialPageTransition extends TransitionWithChild {
import 'framework.dart'; _MaterialPageTransition({
import 'modal_barrier.dart';
import 'navigator.dart';
import 'page_storage.dart';
import 'transitions.dart';
class _PageTransition extends TransitionWithChild {
_PageTransition({
Key key, Key key,
PerformanceView performance, PerformanceView performance,
Widget child Widget child
...@@ -33,6 +27,7 @@ class _PageTransition extends TransitionWithChild { ...@@ -33,6 +27,7 @@ class _PageTransition extends TransitionWithChild {
..translate(_position.value.x, _position.value.y); ..translate(_position.value.x, _position.value.y);
return new Transform( return new Transform(
transform: transform, transform: transform,
// TODO(ianh): tell the transform to be un-transformed for hit testing
child: new Opacity( child: new Opacity(
opacity: _opacity.value, opacity: _opacity.value,
child: child child: child
...@@ -41,82 +36,38 @@ class _PageTransition extends TransitionWithChild { ...@@ -41,82 +36,38 @@ class _PageTransition extends TransitionWithChild {
} }
} }
class _Page extends StatefulComponent { class MaterialPageRoute extends ModalRoute {
_Page({ MaterialPageRoute({
Key key, this.builder,
this.route NamedRouteSettings settings: const NamedRouteSettings()
}) : super(key: key); }) : super(settings: settings) {
assert(builder != null);
final PageRoute route; assert(opaque);
}
_PageState createState() => new _PageState(); final WidgetBuilder builder;
}
class _PageState extends State<_Page> { Duration get transitionDuration => const Duration(milliseconds: 150);
final GlobalKey _subtreeKey = new GlobalKey();
Widget build(BuildContext context) { bool get opaque => true;
if (config.route._offstage) {
return new OffStage(
child: new PageStorage(
key: _subtreeKey,
bucket: config.route._storageBucket,
child: _invokeBuilder()
)
);
}
return new _PageTransition(
performance: config.route.performance,
child: new PageStorage(
key: _subtreeKey,
bucket: config.route._storageBucket,
child: _invokeBuilder()
)
);
}
Widget _invokeBuilder() { Widget buildPage(BuildContext context) {
Widget result = config.route.builder(context); Widget result = builder(context);
assert(() { assert(() {
if (result == null) if (result == null)
debugPrint('The builder for route \'${config.route.name}\' returned null. Route builders must never return null.'); debugPrint('The builder for route \'${settings.name}\' returned null. Route builders must never return null.');
assert(result != null && 'A route builder returned null. See the previous log message for details.' is String); assert(result != null && 'A route builder returned null. See the previous log message for details.' is String);
return true; return true;
}); });
return result; return result;
} }
}
class PageRoute extends ModalRoute { Widget buildTransition(BuildContext context, PerformanceView performance, Widget child) {
PageRoute({ return new _MaterialPageTransition(
this.builder, performance: performance,
this.settings: const NamedRouteSettings() child: child
}) { );
assert(builder != null);
assert(opaque);
}
final WidgetBuilder builder;
final NamedRouteSettings settings;
final GlobalKey<_PageState> pageKey = new GlobalKey<_PageState>();
bool get opaque => true;
String get name => settings.name;
Duration get transitionDuration => const Duration(milliseconds: 150);
Widget buildModalWidget(BuildContext context) => new _Page(key: pageKey, route: this);
final PageStorageBucket _storageBucket = new PageStorageBucket();
bool get offstage => _offstage;
bool _offstage = false;
void set offstage (bool value) {
if (_offstage == value)
return;
_offstage = value;
pageKey.currentState?.setState(() { });
} }
String get debugLabel => '${super.debugLabel}($name)'; String get debugLabel => '${super.debugLabel}(${settings.name})';
} }
...@@ -110,7 +110,7 @@ class _MenuRoute extends ModalRoute { ...@@ -110,7 +110,7 @@ class _MenuRoute extends ModalRoute {
bool get opaque => false; bool get opaque => false;
Duration get transitionDuration => _kMenuDuration; Duration get transitionDuration => _kMenuDuration;
Widget buildModalWidget(BuildContext context) => new _PopupMenu(route: this); Widget buildPage(BuildContext context) => new _PopupMenu(route: this);
} }
Future showMenu({ BuildContext context, ModalPosition position, List<PopupMenuItem> items, int level: 4 }) { Future showMenu({ BuildContext context, ModalPosition position, List<PopupMenuItem> items, int level: 4 }) {
......
...@@ -10,68 +10,46 @@ import 'framework.dart'; ...@@ -10,68 +10,46 @@ import 'framework.dart';
import 'heroes.dart'; import 'heroes.dart';
import 'navigator.dart'; import 'navigator.dart';
import 'overlay.dart'; import 'overlay.dart';
import 'page.dart'; import 'routes.dart';
class HeroPageRoute extends PageRoute {
HeroPageRoute({
WidgetBuilder builder,
NamedRouteSettings settings: const NamedRouteSettings(),
this.heroController
}) : super(builder: builder, settings: settings);
final HeroController heroController;
NavigatorState _navigator;
void didPush(OverlayState overlay, OverlayEntry insertionPoint) {
super.didPush(overlay, insertionPoint);
// TODO(abarth): Pass the NavigatorState explicitly.
if (overlay != null) {
_navigator = Navigator.of(overlay.context);
heroController?.didPush(_navigator, this);
}
}
void didPop(dynamic result) {
super.didPop(result);
if (_navigator != null) {
heroController?.didPop(_navigator, this);
_navigator = null;
}
}
}
class HeroController { class HeroController extends NavigatorObserver {
HeroController() { HeroController() {
_party = new HeroParty(onQuestFinished: _handleQuestFinished); _party = new HeroParty(onQuestFinished: _handleQuestFinished);
} }
HeroParty _party; HeroParty _party;
PerformanceView _performance; PerformanceView _performance;
HeroPageRoute _from; ModalRoute _from;
HeroPageRoute _to; ModalRoute _to;
final List<OverlayEntry> _overlayEntries = new List<OverlayEntry>(); final List<OverlayEntry> _overlayEntries = new List<OverlayEntry>();
void didPush(NavigatorState navigator, HeroPageRoute route) { void didPushModal(Route route) {
assert(navigator != null);
assert(route != null); assert(route != null);
assert(route.performance != null); if (route is ModalRoute) { // as opposed to StateRoute, say
Route from = navigator.currentRoute; assert(route.performance != null);
if (from is HeroPageRoute) Route from = navigator.currentRoute;
_from = from; if (from is ModalRoute) // as opposed to the many other types of routes, or null
_to = route; _from = from;
_performance = route.performance; _to = route;
_checkForHeroQuest(); _performance = route.performance;
_checkForHeroQuest();
}
} }
void didPop(NavigatorState navigator, HeroPageRoute route) { void didPopModal(Route route) {
assert(navigator != null);
assert(route != null); assert(route != null);
assert(route.performance != null); if (route is ModalRoute) { // as opposed to StateRoute, say
Route to = navigator.currentRoute; assert(route.performance != null);
if (to is HeroPageRoute) { Route to = navigator.currentRoute;
_to = to; if (to is ModalRoute) { // as opposed to the many other types of routes
_from = route; _to = to;
_performance = route.performance; _from = route;
_checkForHeroQuest(); _performance = route.performance;
_checkForHeroQuest();
}
} }
} }
...@@ -123,10 +101,9 @@ class HeroController { ...@@ -123,10 +101,9 @@ class HeroController {
Set<Key> mostValuableKeys = _getMostValuableKeys(); Set<Key> mostValuableKeys = _getMostValuableKeys();
Map<Object, HeroHandle> heroesFrom = _party.isEmpty ? Map<Object, HeroHandle> heroesFrom = _party.isEmpty ?
Hero.of(_from.pageKey.currentContext, mostValuableKeys) : _party.getHeroesToAnimate(); Hero.of(_from.subtreeContext, mostValuableKeys) : _party.getHeroesToAnimate();
BuildContext context = _to.pageKey.currentContext; Map<Object, HeroHandle> heroesTo = Hero.of(_to.subtreeContext, mostValuableKeys);
Map<Object, HeroHandle> heroesTo = Hero.of(context, mostValuableKeys);
_to.offstage = false; _to.offstage = false;
PerformanceView performance = _performance; PerformanceView performance = _performance;
...@@ -136,7 +113,6 @@ class HeroController { ...@@ -136,7 +113,6 @@ class HeroController {
curve = new Interval(performance.progress, 1.0, curve: curve); curve = new Interval(performance.progress, 1.0, curve: curve);
} }
NavigatorState navigator = Navigator.of(context);
_party.animate(heroesFrom, heroesTo, _getAnimationArea(navigator.context), curve); _party.animate(heroesFrom, heroesTo, _getAnimationArea(navigator.context), curve);
_removeHeroesFromOverlay(); _removeHeroesFromOverlay();
Iterable<Widget> heroes = _party.getWidgets(navigator.context, performance); Iterable<Widget> heroes = _party.getWidgets(navigator.context, performance);
......
...@@ -2,32 +2,30 @@ ...@@ -2,32 +2,30 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async';
import 'package:flutter/animation.dart'; import 'package:flutter/animation.dart';
import 'basic.dart'; import 'basic.dart';
import 'focus.dart';
import 'framework.dart'; import 'framework.dart';
import 'navigator.dart'; import 'navigator.dart';
import 'routes.dart';
import 'status_transitions.dart';
import 'transitions.dart'; import 'transitions.dart';
const Color _kTransparent = const Color(0x00000000); const Color kTransparent = const Color(0x00000000);
class ModalBarrier extends StatelessComponent { class ModalBarrier extends StatelessComponent {
ModalBarrier({ ModalBarrier({
Key key, Key key,
this.color: _kTransparent this.color: kTransparent,
this.dismissable: true
}) : super(key: key); }) : super(key: key);
final Color color; final Color color;
final bool dismissable;
Widget build(BuildContext context) { Widget build(BuildContext context) {
return new Listener( return new Listener(
onPointerDown: (_) { onPointerDown: (_) {
Navigator.of(context).pop(); if (dismissable)
Navigator.of(context).pop();
}, },
child: new ConstrainedBox( child: new ConstrainedBox(
constraints: const BoxConstraints.expand(), constraints: const BoxConstraints.expand(),
...@@ -45,11 +43,13 @@ class AnimatedModalBarrier extends StatelessComponent { ...@@ -45,11 +43,13 @@ class AnimatedModalBarrier extends StatelessComponent {
AnimatedModalBarrier({ AnimatedModalBarrier({
Key key, Key key,
this.color, this.color,
this.performance this.performance,
this.dismissable: true
}) : super(key: key); }) : super(key: key);
final AnimatedColorValue color; final AnimatedColorValue color;
final PerformanceView performance; final PerformanceView performance;
final bool dismissable;
Widget build(BuildContext context) { Widget build(BuildContext context) {
return new BuilderTransition( return new BuilderTransition(
...@@ -58,69 +58,12 @@ class AnimatedModalBarrier extends StatelessComponent { ...@@ -58,69 +58,12 @@ class AnimatedModalBarrier extends StatelessComponent {
builder: (BuildContext context) { builder: (BuildContext context) {
return new IgnorePointer( return new IgnorePointer(
ignoring: performance.status == PerformanceStatus.reverse, ignoring: performance.status == PerformanceStatus.reverse,
child: new ModalBarrier(color: color.value) child: new ModalBarrier(
color: color.value,
dismissable: dismissable
)
); );
} }
); );
} }
} }
class _ModalScope extends StatusTransitionComponent {
_ModalScope({
Key key,
ModalRoute route,
this.child
}) : route = route, super(key: key, performance: route.performance);
final ModalRoute route;
final Widget child;
Widget build(BuildContext context) {
Widget focus = new Focus(
key: new GlobalObjectKey(route),
child: new IgnorePointer(
ignoring: route.performance.status == PerformanceStatus.reverse,
child: child
)
);
ModalPosition position = route.position;
if (position == null)
return focus;
return new Positioned(
top: position.top,
right: position.right,
bottom: position.bottom,
left: position.left,
child: focus
);
}
}
class ModalPosition {
const ModalPosition({ this.top, this.right, this.bottom, this.left });
final double top;
final double right;
final double bottom;
final double left;
}
abstract class ModalRoute extends TransitionRoute {
ModalRoute({ Completer completer }) : super(completer: completer);
ModalPosition get position => null;
Color get barrierColor => _kTransparent;
Widget buildModalWidget(BuildContext context);
Widget _buildModalBarrier(BuildContext context) {
return new AnimatedModalBarrier(
color: new AnimatedColorValue(_kTransparent, end: barrierColor, curve: Curves.ease),
performance: performance
);
}
Widget _buildModalScope(BuildContext context) {
return new _ModalScope(route: this, child: buildModalWidget(context));
}
List<WidgetBuilder> get builders => <WidgetBuilder>[ _buildModalBarrier, _buildModalScope ];
}
...@@ -21,17 +21,26 @@ class NamedRouteSettings { ...@@ -21,17 +21,26 @@ class NamedRouteSettings {
typedef Route RouteFactory(NamedRouteSettings settings); typedef Route RouteFactory(NamedRouteSettings settings);
class NavigatorObserver {
NavigatorState _navigator;
NavigatorState get navigator => _navigator;
void didPopModal(Route route) { }
void didPushModal(Route route) { }
}
class Navigator extends StatefulComponent { class Navigator extends StatefulComponent {
Navigator({ Navigator({
Key key, Key key,
this.onGenerateRoute, this.onGenerateRoute,
this.onUnknownRoute this.onUnknownRoute,
this.observer
}) : super(key: key) { }) : super(key: key) {
assert(onGenerateRoute != null); assert(onGenerateRoute != null);
} }
final RouteFactory onGenerateRoute; final RouteFactory onGenerateRoute;
final RouteFactory onUnknownRoute; final RouteFactory onUnknownRoute;
final NavigatorObserver observer;
static const String defaultRouteName = '/'; static const String defaultRouteName = '/';
...@@ -57,9 +66,24 @@ class NavigatorState extends State<Navigator> { ...@@ -57,9 +66,24 @@ class NavigatorState extends State<Navigator> {
void initState() { void initState() {
super.initState(); super.initState();
assert(config.observer == null || config.observer.navigator == null);
config.observer?._navigator = this;
push(config.onGenerateRoute(new NamedRouteSettings(name: Navigator.defaultRouteName))); push(config.onGenerateRoute(new NamedRouteSettings(name: Navigator.defaultRouteName)));
} }
void didUpdateConfig(Navigator oldConfig) {
if (oldConfig.observer != config.observer) {
oldConfig.observer?._navigator = null;
assert(config.observer == null || config.observer.navigator == null);
config.observer?._navigator = this;
}
}
void dispose() {
config.observer?._navigator = null;
super.dispose();
}
bool get hasPreviousRoute => _modal.length > 1; bool get hasPreviousRoute => _modal.length > 1;
OverlayState get overlay => _overlayKey.currentState; OverlayState get overlay => _overlayKey.currentState;
...@@ -75,11 +99,7 @@ class NavigatorState extends State<Navigator> { ...@@ -75,11 +99,7 @@ class NavigatorState extends State<Navigator> {
return null; return null;
} }
Route get currentRoute => _ephemeral.isNotEmpty ? _ephemeral.last : _modal.last; Route get currentRoute => _ephemeral.isNotEmpty ? _ephemeral.last : _modal.isNotEmpty ? _modal.last : null;
Route _removeCurrentRoute() {
return _ephemeral.isNotEmpty ? _ephemeral.removeLast() : _modal.removeLast();
}
void pushNamed(String name, { Set<Key> mostValuableKeys }) { void pushNamed(String name, { Set<Key> mostValuableKeys }) {
assert(name != null); assert(name != null);
...@@ -90,9 +110,10 @@ class NavigatorState extends State<Navigator> { ...@@ -90,9 +110,10 @@ class NavigatorState extends State<Navigator> {
push(config.onGenerateRoute(settings) ?? config.onUnknownRoute(settings)); push(config.onGenerateRoute(settings) ?? config.onUnknownRoute(settings));
} }
void push(Route route) { void push(Route route, { Set<Key> mostValuableKeys }) {
_popAllEphemeralRoutes(); _popAllEphemeralRoutes();
route.didPush(overlay, _currentOverlay); route.didPush(overlay, _currentOverlay);
config.observer?.didPushModal(route);
_modal.add(route); _modal.add(route);
} }
...@@ -110,7 +131,13 @@ class NavigatorState extends State<Navigator> { ...@@ -110,7 +131,13 @@ class NavigatorState extends State<Navigator> {
} }
void pop([dynamic result]) { void pop([dynamic result]) {
_removeCurrentRoute().didPop(result); if (_ephemeral.isNotEmpty) {
_ephemeral.removeLast().didPop(result);
} else {
Route route = _modal.removeLast();
route.didPop(result);
config.observer?.didPopModal(route);
}
} }
Widget build(BuildContext context) { Widget build(BuildContext context) {
......
...@@ -7,9 +7,13 @@ import 'dart:async'; ...@@ -7,9 +7,13 @@ import 'dart:async';
import 'package:flutter/animation.dart'; import 'package:flutter/animation.dart';
import 'basic.dart'; import 'basic.dart';
import 'focus.dart';
import 'framework.dart'; import 'framework.dart';
import 'modal_barrier.dart';
import 'navigator.dart'; import 'navigator.dart';
import 'overlay.dart'; import 'overlay.dart';
import 'page_storage.dart';
import 'status_transitions.dart';
class StateRoute extends Route { class StateRoute extends Route {
StateRoute({ this.onPop }); StateRoute({ this.onPop });
...@@ -29,7 +33,7 @@ class OverlayRoute extends Route { ...@@ -29,7 +33,7 @@ class OverlayRoute extends Route {
List<WidgetBuilder> get builders => const <WidgetBuilder>[]; List<WidgetBuilder> get builders => const <WidgetBuilder>[];
List<OverlayEntry> get overlayEntries => _overlayEntries; List<OverlayEntry> get overlayEntries => _overlayEntries;
final List<OverlayEntry> _overlayEntries = new List<OverlayEntry>(); final List<OverlayEntry> _overlayEntries = <OverlayEntry>[];
void didPush(OverlayState overlay, OverlayEntry insertionPoint) { void didPush(OverlayState overlay, OverlayEntry insertionPoint) {
for (WidgetBuilder builder in builders) { for (WidgetBuilder builder in builders) {
...@@ -99,3 +103,128 @@ abstract class TransitionRoute extends OverlayRoute { ...@@ -99,3 +103,128 @@ abstract class TransitionRoute extends OverlayRoute {
String get debugLabel => '$runtimeType'; String get debugLabel => '$runtimeType';
String toString() => '$runtimeType(performance: $_performance)'; String toString() => '$runtimeType(performance: $_performance)';
} }
class _ModalScope extends StatusTransitionComponent {
_ModalScope({
Key key,
this.subtreeKey,
this.storageBucket,
PerformanceView performance,
this.route
}) : super(key: key, performance: performance);
final GlobalKey subtreeKey;
final PageStorageBucket storageBucket;
final ModalRoute route;
Widget build(BuildContext context) {
Widget contents = new PageStorage(
key: subtreeKey,
bucket: storageBucket,
child: route.buildPage(context)
);
if (route.offstage) {
contents = new OffStage(child: contents);
} else {
contents = new Focus(
key: new GlobalObjectKey(route),
child: new IgnorePointer(
ignoring: performance.status == PerformanceStatus.reverse,
child: route.buildTransition(context, performance, contents)
)
);
}
ModalPosition position = route.position;
if (position == null)
return contents;
return new Positioned(
top: position.top,
right: position.right,
bottom: position.bottom,
left: position.left,
child: contents
);
}
}
class ModalPosition {
const ModalPosition({ this.top, this.right, this.bottom, this.left });
final double top;
final double right;
final double bottom;
final double left;
}
abstract class ModalRoute extends TransitionRoute {
ModalRoute({
Completer completer,
this.settings: const NamedRouteSettings()
}) : super(completer: completer);
final NamedRouteSettings settings;
// The API for subclasses to override - used by _ModalScope
ModalPosition get position => null;
Widget buildPage(BuildContext context);
Widget buildTransition(BuildContext context, PerformanceView performance, Widget child) {
return child;
}
// The API for subclasses to override - used by this class
Color get barrierColor => kTransparent;
// The API for _ModalScope and HeroController
bool get offstage => _offstage;
bool _offstage = false;
void set offstage (bool value) {
if (_offstage == value)
return;
_offstage = value;
_scopeKey.currentState?.setState(() {
// _offstage is the value we're setting, but since there might not be a
// state, we set it outside of this callback (which will only be called if
// there's a state currently built).
// _scopeKey is the key for the _ModalScope built in _buildModalScope().
// When we mark that state dirty, it'll rebuild itself, and use our
// offstage (via their config.route.offstage) when building.
});
}
BuildContext get subtreeContext => _subtreeKey.currentContext;
// Internals
final GlobalKey<StatusTransitionState> _scopeKey = new GlobalKey<StatusTransitionState>();
final GlobalKey _subtreeKey = new GlobalKey();
final PageStorageBucket _storageBucket = new PageStorageBucket();
Widget _buildModalBarrier(BuildContext context) {
return new AnimatedModalBarrier(
color: new AnimatedColorValue(kTransparent, end: barrierColor, curve: Curves.ease),
performance: performance,
dismissable: false
);
}
Widget _buildModalScope(BuildContext context) {
return new _ModalScope(
key: _scopeKey,
subtreeKey: _subtreeKey,
storageBucket: _storageBucket,
performance: performance,
route: this
);
}
List<WidgetBuilder> get builders => <WidgetBuilder>[
_buildModalBarrier,
_buildModalScope
];
}
...@@ -18,10 +18,10 @@ abstract class StatusTransitionComponent extends StatefulComponent { ...@@ -18,10 +18,10 @@ abstract class StatusTransitionComponent extends StatefulComponent {
Widget build(BuildContext context); Widget build(BuildContext context);
_StatusTransitionState createState() => new _StatusTransitionState(); StatusTransitionState createState() => new StatusTransitionState();
} }
class _StatusTransitionState extends State<StatusTransitionComponent> { class StatusTransitionState extends State<StatusTransitionComponent> {
void initState() { void initState() {
super.initState(); super.initState();
config.performance.addStatusListener(_performanceStatusChanged); config.performance.addStatusListener(_performanceStatusChanged);
......
...@@ -27,7 +27,6 @@ export 'src/widgets/modal_barrier.dart'; ...@@ -27,7 +27,6 @@ export 'src/widgets/modal_barrier.dart';
export 'src/widgets/navigator.dart'; export 'src/widgets/navigator.dart';
export 'src/widgets/overlay.dart'; export 'src/widgets/overlay.dart';
export 'src/widgets/page_storage.dart'; export 'src/widgets/page_storage.dart';
export 'src/widgets/page.dart';
export 'src/widgets/placeholder.dart'; export 'src/widgets/placeholder.dart';
export 'src/widgets/routes.dart'; export 'src/widgets/routes.dart';
export 'src/widgets/scrollable.dart'; export 'src/widgets/scrollable.dart';
......
...@@ -37,8 +37,8 @@ void main() { ...@@ -37,8 +37,8 @@ void main() {
// Tap on the the bottom sheet itself to dismiss it // Tap on the the bottom sheet itself to dismiss it
tester.tap(tester.findText('BottomSheet')); tester.tap(tester.findText('BottomSheet'));
tester.pump(); // bottom sheet dismiss animation starts tester.pump(); // bottom sheet dismiss animation starts
tester.pump(new Duration(seconds: 1)); // animation done tester.pump(new Duration(seconds: 1)); // last frame of animation (sheet is entirely off-screen, but still present)
tester.pump(new Duration(seconds: 1)); // rebuild frame tester.pump(new Duration(seconds: 1)); // frame after the animation (sheet has been removed)
expect(showBottomSheetThenCalled, isTrue); expect(showBottomSheetThenCalled, isTrue);
expect(tester.findText('BottomSheet'), isNull); expect(tester.findText('BottomSheet'), isNull);
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'package:flutter/animation.dart'; import 'package:flutter/animation.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/material.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'widget_tester.dart'; import 'widget_tester.dart';
...@@ -36,9 +36,9 @@ void main() { ...@@ -36,9 +36,9 @@ void main() {
key: navigatorKey, key: navigatorKey,
onGenerateRoute: (NamedRouteSettings settings) { onGenerateRoute: (NamedRouteSettings settings) {
if (settings.name == '/') if (settings.name == '/')
return new PageRoute(builder: (_) => new Container(child: new ThePositiveNumbers())); return new MaterialPageRoute(builder: (_) => new Container(child: new ThePositiveNumbers()));
else if (settings.name == '/second') else if (settings.name == '/second')
return new PageRoute(builder: (_) => new Container(child: new ThePositiveNumbers())); return new MaterialPageRoute(builder: (_) => new Container(child: new ThePositiveNumbers()));
return null; return null;
} }
)); ));
......
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