22// Use of this source code is governed by a BSD-style license that can be
33// found in the LICENSE file.
44
5+ import 'package:sky/animation/animation_performance.dart' ;
6+ import 'package:sky/animation/curves.dart' ;
7+ import 'package:sky/widgets/animated_component.dart' ;
8+ import 'package:sky/widgets/animation_builder.dart' ;
59import 'package:sky/widgets/basic.dart' ;
10+ import 'package:vector_math/vector_math.dart' ;
611
712typedef Widget Builder (Navigator navigator, RouteBase route);
813
@@ -27,31 +32,125 @@ class RouteState extends RouteBase {
2732 RouteBase route;
2833 Function callback;
2934
30- Widget build (Navigator navigator, _ ) => route. build (navigator, this ) ;
35+ Widget build (Navigator navigator, RouteBase route ) => null ;
3136
3237 void popState () {
3338 if (callback != null )
3439 callback (this );
3540 }
3641}
3742
43+ // TODO(jackson): Refactor this into its own file
44+ // and support multiple transition types
45+ const Duration _kTransitionDuration = const Duration (milliseconds: 200 );
46+ const Point _kTransitionStartPoint = const Point (0.0 , 100.0 );
47+ enum TransitionDirection { forward, reverse }
48+ class Transition extends AnimatedComponent {
49+ Transition ({
50+ String key,
51+ this .content,
52+ this .direction,
53+ this .onDismissed,
54+ this .interactive
55+ }) : super (key: key);
56+ Widget content;
57+ TransitionDirection direction;
58+ bool interactive;
59+ Function onDismissed;
60+
61+ AnimatedType <Point > _position;
62+ AnimatedType <double > _opacity;
63+ AnimationPerformance _performance;
64+
65+ void initState () {
66+ _position = new AnimatedType <Point >(
67+ _kTransitionStartPoint,
68+ end: Point .origin,
69+ curve: easeOut
70+ );
71+ _opacity = new AnimatedType <double >(0.0 , end: 1.0 )
72+ ..curve = easeOut;
73+ _performance = new AnimationPerformance ()
74+ ..duration = _kTransitionDuration
75+ ..variable = new AnimatedList ([_position, _opacity])
76+ ..addListener (_checkDismissed);
77+ if (direction == TransitionDirection .reverse)
78+ _performance.progress = 1.0 ;
79+ watch (_performance);
80+ _start ();
81+ }
82+
83+ void _start () {
84+ _dismissed = false ;
85+ switch (direction) {
86+ case TransitionDirection .forward:
87+ _performance.play ();
88+ break ;
89+ case TransitionDirection .reverse:
90+ _performance.reverse ();
91+ break ;
92+ }
93+ }
94+
95+ void syncFields (Transition source) {
96+ content = source.content;
97+ if (direction != source.direction) {
98+ direction = source.direction;
99+ _start ();
100+ }
101+ interactive = source.interactive;
102+ onDismissed = source.onDismissed;
103+ super .syncFields (source);
104+ }
105+
106+ bool _dismissed = false ;
107+ void _checkDismissed () {
108+ if (! _dismissed &&
109+ direction == TransitionDirection .reverse &&
110+ _performance.isDismissed) {
111+ if (onDismissed != null )
112+ onDismissed ();
113+ _dismissed = true ;
114+ }
115+ }
116+
117+ Widget build () {
118+ Matrix4 transform = new Matrix4 .identity ()
119+ ..translate (_position.value.x, _position.value.y);
120+ // TODO(jackson): Hit testing should ignore transform
121+ // TODO(jackson): Block input unless content is interactive
122+ return new Transform (
123+ transform: transform,
124+ child: new Opacity (
125+ opacity: _opacity.value,
126+ child: content
127+ )
128+ );
129+ }
130+ }
131+
132+ class HistoryEntry {
133+ HistoryEntry (this .route);
134+ final RouteBase route;
135+ // TODO(jackson): Keep track of the requested transition
136+ }
137+
38138class NavigationState {
39139
40140 NavigationState (List <Route > routes) {
41141 for (Route route in routes) {
42142 if (route.name != null )
43143 namedRoutes[route.name] = route;
44144 }
45- history.add (routes[0 ]);
145+ history.add (new HistoryEntry ( routes[0 ]) );
46146 }
47147
48- List <RouteBase > history = new List <RouteBase >();
148+ List <HistoryEntry > history = new List <HistoryEntry >();
49149 int historyIndex = 0 ;
50150 Map <String , RouteBase > namedRoutes = new Map <String , RouteBase >();
51151
52- RouteBase get currentRoute => history[historyIndex];
152+ RouteBase get currentRoute => history[historyIndex].route ;
53153 bool hasPrevious () => historyIndex > 0 ;
54- bool hasNext () => history.length > historyIndex + 1 ;
55154
56155 void pushNamed (String name) {
57156 Route route = namedRoutes[name];
@@ -60,16 +159,15 @@ class NavigationState {
60159 }
61160
62161 void push (RouteBase route) {
63- // Discard future history
64- history.removeRange (historyIndex + 1 , history.length);
65- historyIndex = history.length;
66- history.add (route);
162+ HistoryEntry historyEntry = new HistoryEntry (route);
163+ history.insert (historyIndex + 1 , historyEntry);
164+ historyIndex++ ;
67165 }
68166
69167 void pop () {
70168 if (historyIndex > 0 ) {
71- history[historyIndex]. popState () ;
72- history. removeLast ();
169+ HistoryEntry entry = history[historyIndex];
170+ entry.route. popState ();
73171 historyIndex-- ;
74172 }
75173 }
@@ -115,6 +213,31 @@ class Navigator extends StatefulComponent {
115213 }
116214
117215 Widget build () {
118- return state.currentRoute.build (this , state.currentRoute);
216+ List <Widget > visibleRoutes = new List <Widget >();
217+ for (int i = 0 ; i < state.history.length; i++ ) {
218+ // TODO(jackson): Avoid building routes that are not visible
219+ HistoryEntry historyEntry = state.history[i];
220+ Widget content = historyEntry.route.build (this , historyEntry.route);
221+ if (i == 0 ) {
222+ visibleRoutes.add (content);
223+ continue ;
224+ }
225+ if (content == null )
226+ continue ;
227+ String key = historyEntry.route.key;
228+ Transition transition = new Transition (
229+ key: key,
230+ content: content,
231+ direction: (i <= state.historyIndex) ? TransitionDirection .forward : TransitionDirection .reverse,
232+ interactive: (i == state.historyIndex),
233+ onDismissed: () {
234+ setState (() {
235+ state.history.remove (historyEntry);
236+ });
237+ }
238+ );
239+ visibleRoutes.add (transition);
240+ }
241+ return new Stack (visibleRoutes);
119242 }
120243}
0 commit comments