diff --git a/examples/fitness/lib/feed.dart b/examples/fitness/lib/feed.dart index a3c0275f7e5..221b7056f84 100644 --- a/examples/fitness/lib/feed.dart +++ b/examples/fitness/lib/feed.dart @@ -66,19 +66,14 @@ class FeedFragmentState extends State { void _handleFitnessModeChange(FitnessMode value) { setState(() { _fitnessMode = value; - _drawerShowing = false; }); + config.navigator.pop(); } - Drawer buildDrawer() { - if (_drawerStatus == AnimationStatus.dismissed) - return null; - return new Drawer( - showing: _drawerShowing, - level: 3, - onDismissed: _handleDrawerDismissed, + void _showDrawer() { + showDrawer( navigator: config.navigator, - children: [ + child: new Block([ new DrawerHeader(child: new Text('Fitness')), new DrawerItem( icon: 'action/view_list', @@ -98,26 +93,10 @@ class FeedFragmentState extends State { new DrawerItem( icon: 'action/help', child: new Text('Help & Feedback')) - ] + ]) ); } - bool _drawerShowing = false; - AnimationStatus _drawerStatus = AnimationStatus.dismissed; - - void _handleOpenDrawer() { - setState(() { - _drawerShowing = true; - _drawerStatus = AnimationStatus.forward; - }); - } - - void _handleDrawerDismissed() { - setState(() { - _drawerStatus = AnimationStatus.dismissed; - }); - } - void _handleShowSettings() { config.navigator.pop(); config.navigator.pushNamed('/settings'); @@ -135,7 +114,7 @@ class FeedFragmentState extends State { return new ToolBar( left: new IconButton( icon: "navigation/menu", - onPressed: _handleOpenDrawer), + onPressed: _showDrawer), center: new Text(fitnessModeTitle) ); } @@ -262,8 +241,7 @@ class FeedFragmentState extends State { toolbar: buildToolBar(), body: buildBody(), snackBar: buildSnackBar(), - floatingActionButton: buildFloatingActionButton(), - drawer: buildDrawer() + floatingActionButton: buildFloatingActionButton() ); } } diff --git a/examples/stocks/lib/stock_home.dart b/examples/stocks/lib/stock_home.dart index 523b6684681..80e399ce8b7 100644 --- a/examples/stocks/lib/stock_home.dart +++ b/examples/stocks/lib/stock_home.dart @@ -56,22 +56,6 @@ class StockHomeState extends State { }); } - bool _drawerShowing = false; - AnimationStatus _drawerStatus = AnimationStatus.dismissed; - - void _handleOpenDrawer() { - setState(() { - _drawerShowing = true; - _drawerStatus = AnimationStatus.forward; - }); - } - - void _handleDrawerDismissed() { - setState(() { - _drawerStatus = AnimationStatus.dismissed; - }); - } - bool _autorefresh = false; void _handleAutorefreshChanged(bool value) { setState(() { @@ -91,16 +75,10 @@ class StockHomeState extends State { ); } - Drawer buildDrawer() { - if (_drawerStatus == AnimationStatus.dismissed) - return null; - assert(_drawerShowing); // TODO(mpcomplete): this is always true - return new Drawer( - level: 3, - showing: _drawerShowing, - onDismissed: _handleDrawerDismissed, + void _showDrawer() { + showDrawer( navigator: config.navigator, - children: [ + child: new Block([ new DrawerHeader(child: new Text('Stocks')), new DrawerItem( icon: 'action/assessment', @@ -141,7 +119,7 @@ class StockHomeState extends State { new DrawerItem( icon: 'action/help', child: new Text('Help & Feedback')) - ] + ]) ); } @@ -154,7 +132,7 @@ class StockHomeState extends State { return new ToolBar( left: new IconButton( icon: "navigation/menu", - onPressed: _handleOpenDrawer + onPressed: _showDrawer ), center: new Text('Stocks'), right: [ @@ -276,8 +254,7 @@ class StockHomeState extends State { toolbar: _isSearching ? buildSearchBar() : buildToolBar(), body: buildTabNavigator(), snackBar: buildSnackBar(), - floatingActionButton: buildFloatingActionButton(), - drawer: buildDrawer() + floatingActionButton: buildFloatingActionButton() ); } } diff --git a/examples/widgets/card_collection.dart b/examples/widgets/card_collection.dart index ba5a53c9756..0e7a06c4f52 100644 --- a/examples/widgets/card_collection.dart +++ b/examples/widgets/card_collection.dart @@ -4,7 +4,6 @@ import 'dart:sky' as sky; -import 'package:sky/animation.dart'; import 'package:sky/material.dart'; import 'package:sky/painting.dart'; import 'package:sky/widgets.dart'; @@ -18,11 +17,15 @@ class CardModel { Key get key => new ObjectKey(this); } -class CardCollectionApp extends StatefulComponent { - CardCollectionAppState createState() => new CardCollectionAppState(); +class CardCollection extends StatefulComponent { + CardCollection({ this.navigator }); + + final NavigatorState navigator; + + CardCollectionState createState() => new CardCollectionState(); } -class CardCollectionAppState extends State { +class CardCollectionState extends State { static const TextStyle cardLabelStyle = const TextStyle(color: Colors.white, fontSize: 18.0, fontWeight: bold); @@ -37,9 +40,7 @@ class CardCollectionAppState extends State { DismissDirection _dismissDirection = DismissDirection.horizontal; bool _snapToCenter = false; bool _fixedSizeCards = false; - bool _drawerShowing = false; bool _sunshine = false; - AnimationStatus _drawerStatus = AnimationStatus.dismissed; InvalidatorCallback _invalidator; Size _cardCollectionSize = new Size(200.0, 200.0); @@ -114,17 +115,23 @@ class CardCollectionAppState extends State { } } - void _handleOpenDrawer() { - setState(() { - _drawerShowing = true; - _drawerStatus = AnimationStatus.forward; - }); - } - - void _handleDrawerDismissed() { - setState(() { - _drawerStatus = AnimationStatus.dismissed; - }); + void _showDrawer() { + showDrawer( + navigator: config.navigator, + child: new IconTheme( + data: const IconThemeData(color: IconThemeColor.black), + child: new Block([ + new DrawerHeader(child: new Text('Options')), + buildDrawerCheckbox("Snap fling scrolls to center", _snapToCenter, _toggleSnapToCenter), + buildDrawerCheckbox("Fixed size cards", _fixedSizeCards, _toggleFixedSizeCards), + buildDrawerCheckbox("Let the sun shine", _sunshine, _toggleSunshine), + new DrawerDivider(), + buildDrawerRadioItem(DismissDirection.horizontal, 'action/code'), + buildDrawerRadioItem(DismissDirection.left, 'navigation/arrow_back'), + buildDrawerRadioItem(DismissDirection.right, 'navigation/arrow_forward'), + ]) + ) + ); } String _dismissDirectionText(DismissDirection direction) { @@ -151,65 +158,41 @@ class CardCollectionAppState extends State { }); } - _changeDismissDirection(DismissDirection newDismissDirection) { + void _changeDismissDirection(DismissDirection newDismissDirection) { setState(() { _dismissDirection = newDismissDirection; - _drawerStatus = AnimationStatus.dismissed; }); + config.navigator.pop(); } - Widget buildDrawer() { - if (_drawerStatus == AnimationStatus.dismissed) - return null; + Widget buildDrawerCheckbox(String label, bool value, Function callback) { + return new DrawerItem( + onPressed: callback, + child: new Row([ + new Flexible(child: new Text(label)), + new Checkbox(value: value, onChanged: (_) { callback(); }) + ]) + ); + } - Widget buildDrawerCheckbox(String label, bool value, Function callback) { - return new DrawerItem( - onPressed: callback, - child: new Row([ - new Flexible(child: new Text(label)), - new Checkbox(value: value, onChanged: (_) { callback(); }) - ]) - ); - } - - Widget buildDrawerRadioItem(DismissDirection direction, String icon) { - return new DrawerItem( - icon: icon, - onPressed: () { _changeDismissDirection(direction); }, - child: new Row([ - new Flexible(child: new Text(_dismissDirectionText(direction))), - new Radio( - value: direction, - onChanged: _changeDismissDirection, - groupValue: _dismissDirection - ) - ]) - ); - } - - return new IconTheme( - data: const IconThemeData(color: IconThemeColor.black), - child: new Drawer( - level: 3, - showing: _drawerShowing, - onDismissed: _handleDrawerDismissed, - children: [ - new DrawerHeader(child: new Text('Options')), - buildDrawerCheckbox("Snap fling scrolls to center", _snapToCenter, _toggleSnapToCenter), - buildDrawerCheckbox("Fixed size cards", _fixedSizeCards, _toggleFixedSizeCards), - buildDrawerCheckbox("Let the sun shine", _sunshine, _toggleSunshine), - new DrawerDivider(), - buildDrawerRadioItem(DismissDirection.horizontal, 'action/code'), - buildDrawerRadioItem(DismissDirection.left, 'navigation/arrow_back'), - buildDrawerRadioItem(DismissDirection.right, 'navigation/arrow_forward'), - ] - ) + Widget buildDrawerRadioItem(DismissDirection direction, String icon) { + return new DrawerItem( + icon: icon, + onPressed: () { _changeDismissDirection(direction); }, + child: new Row([ + new Flexible(child: new Text(_dismissDirectionText(direction))), + new Radio( + value: direction, + onChanged: _changeDismissDirection, + groupValue: _dismissDirection + ) + ]) ); } Widget buildToolBar() { return new ToolBar( - left: new IconButton(icon: "navigation/menu", onPressed: _handleOpenDrawer), + left: new IconButton(icon: "navigation/menu", onPressed: _showDrawer), center: new Text('Swipe Away'), right: [ new Text(_dismissDirectionText(_dismissDirection)) @@ -358,24 +341,23 @@ class CardCollectionAppState extends State { body = new Stack([body, indicator]); } - return new Theme( - data: new ThemeData( - brightness: ThemeBrightness.light, - primarySwatch: Colors.blue, - accentColor: Colors.redAccent[200] - ), - child: new Title( - title: 'Cards', - child: new Scaffold( - toolbar: buildToolBar(), - drawer: buildDrawer(), - body: body - ) - ) + return new Scaffold( + toolbar: buildToolBar(), + body: body ); } } void main() { - runApp(new CardCollectionApp()); + runApp(new App( + title: 'Cards', + theme: new ThemeData( + brightness: ThemeBrightness.light, + primarySwatch: Colors.blue, + accentColor: Colors.redAccent[200] + ), + routes: { + '/': (NavigatorState navigator, Route route) => new CardCollection(navigator: navigator), + } + )); } diff --git a/examples/widgets/pageable_list.dart b/examples/widgets/pageable_list.dart index e9c079cffe0..c00da4b488e 100644 --- a/examples/widgets/pageable_list.dart +++ b/examples/widgets/pageable_list.dart @@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:sky/animation.dart'; import 'package:sky/material.dart'; import 'package:sky/painting.dart'; import 'package:sky/widgets.dart'; @@ -17,6 +16,10 @@ class CardModel { } class PageableListApp extends StatefulComponent { + PageableListApp({ this.navigator }); + + final NavigatorState navigator; + PageableListAppState createState() => new PageableListAppState(); } @@ -85,31 +88,10 @@ class PageableListAppState extends State { }); } - bool _drawerShowing = false; - AnimationStatus _drawerStatus = AnimationStatus.dismissed; - - void _handleOpenDrawer() { - setState(() { - _drawerShowing = true; - _drawerStatus = AnimationStatus.forward; - }); - } - - void _handleDrawerDismissed() { - setState(() { - _drawerStatus = AnimationStatus.dismissed; - }); - } - - Drawer buildDrawer() { - if (_drawerStatus == AnimationStatus.dismissed) - return null; - - return new Drawer( - level: 3, - showing: _drawerShowing, - onDismissed: _handleDrawerDismissed, - children: [ + void _showDrawer() { + showDrawer( + navigator: config.navigator, + child: new Block([ new DrawerHeader(child: new Text('Options')), new DrawerItem( icon: 'navigation/more_horiz', @@ -130,14 +112,13 @@ class PageableListAppState extends State { new Checkbox(value: itemsWrap) ]) ) - ] + ]) ); - } Widget buildToolBar() { return new ToolBar( - left: new IconButton(icon: "navigation/menu", onPressed: _handleOpenDrawer), + left: new IconButton(icon: "navigation/menu", onPressed: _showDrawer), center: new Text('PageableList'), right: [ new Text(scrollDirection == ScrollDirection.horizontal ? "horizontal" : "vertical") @@ -167,25 +148,24 @@ class PageableListAppState extends State { Widget build(BuildContext context) { return new IconTheme( data: const IconThemeData(color: IconThemeColor.white), - child: new Theme( - data: new ThemeData( - brightness: ThemeBrightness.light, - primarySwatch: Colors.blue, - accentColor: Colors.redAccent[200] - ), - child: new Title( - title: 'PageableList', - child: new Scaffold( - drawer: buildDrawer(), - toolbar: buildToolBar(), - body: buildBody(context) - ) - ) + child: new Scaffold( + toolbar: buildToolBar(), + body: buildBody(context) ) ); } } void main() { - runApp(new PageableListApp()); + runApp(new App( + title: 'PageableList', + theme: new ThemeData( + brightness: ThemeBrightness.light, + primarySwatch: Colors.blue, + accentColor: Colors.redAccent[200] + ), + routes: { + '/': (NavigatorState navigator, Route route) => new PageableListApp(navigator: navigator), + } + )); } diff --git a/packages/flutter/lib/src/animation/animation_performance.dart b/packages/flutter/lib/src/animation/animation_performance.dart index 8111754442f..6eb2ee82543 100644 --- a/packages/flutter/lib/src/animation/animation_performance.dart +++ b/packages/flutter/lib/src/animation/animation_performance.dart @@ -79,9 +79,6 @@ class AnimationPerformance implements WatchableAnimationPerformance { /// If non-null, animate with this timing instead of a linear timing AnimationTiming timing; - /// If non-null, animate with this force instead of a zero-to-one timeline. - Force attachedForce; - /// The progress of this performance along the timeline /// /// Note: Setting this value stops the current animation. @@ -136,12 +133,6 @@ class AnimationPerformance implements WatchableAnimationPerformance { /// Start running this animation in the most recently direction Future resume() { - if (attachedForce != null) { - return fling( - velocity: _direction == Direction.forward ? 1.0 : -1.0, - force: attachedForce - ); - } return _animateTo(_direction == Direction.forward ? 1.0 : 0.0); } diff --git a/packages/flutter/lib/src/widgets/drawer.dart b/packages/flutter/lib/src/widgets/drawer.dart index edee253af6f..5d980b391ea 100644 --- a/packages/flutter/lib/src/widgets/drawer.dart +++ b/packages/flutter/lib/src/widgets/drawer.dart @@ -14,6 +14,7 @@ import 'package:sky/src/widgets/navigator.dart'; import 'package:sky/src/widgets/scrollable.dart'; import 'package:sky/src/widgets/theme.dart'; import 'package:sky/src/widgets/transitions.dart'; +import 'package:sky/src/widgets/focus.dart'; // TODO(eseidel): Draw width should vary based on device size: // http://www.google.com/design/spec/layout/structure.html#structure-side-nav @@ -36,22 +37,16 @@ const Duration _kThemeChangeDuration = const Duration(milliseconds: 200); const Point _kOpenPosition = Point.origin; const Point _kClosedPosition = const Point(-_kWidth, 0.0); -typedef void DrawerDismissedCallback(); - class Drawer extends StatefulComponent { Drawer({ Key key, - this.children, - this.showing: false, - this.level: 0, - this.onDismissed, + this.child, + this.level: 3, this.navigator }) : super(key: key); - final List children; - final bool showing; + final Widget child; final int level; - final DrawerDismissedCallback onDismissed; final NavigatorState navigator; DrawerState createState() => new DrawerState(); @@ -60,44 +55,24 @@ class Drawer extends StatefulComponent { class DrawerState extends State { void initState() { super.initState(); - _performance = new AnimationPerformance(duration: _kBaseSettleDuration); - _performance.addStatusListener((AnimationStatus status) { - if (status == AnimationStatus.dismissed) - _handleDismissed(); - }); - // Use a spring force for animating the drawer. We can't use curves for - // this because we need a linear curve in order to track the user's finger - // while dragging. - _performance.attachedForce = kDefaultSpringForce; - if (config.navigator != null) { - // TODO(ianh): This is crazy. We should convert drawer to use a pattern like openDialog(). - // https://github.com/domokit/sky_engine/pull/1186 - scheduleMicrotask(() { - config.navigator.pushState(this, (_) => _performance.reverse()); + _performance = new AnimationPerformance(duration: _kBaseSettleDuration) + ..addStatusListener((AnimationStatus status) { + if (status == AnimationStatus.dismissed) + config.navigator.pop(); }); - } - _performance.play(_direction); + _open(); } AnimationPerformance _performance; - Direction get _direction => config.showing ? Direction.forward : Direction.reverse; - - void didUpdateConfig(Drawer oldConfig) { - if (config.showing != oldConfig.showing) - _performance.play(_direction); - } - Widget build(BuildContext context) { - var mask = new GestureDetector( + Widget mask = new GestureDetector( + onTap: _close, child: new ColorTransition( performance: _performance.view, - color: new AnimatedColorValue(Colors.transparent, end: const Color(0x7F000000)), + color: new AnimatedColorValue(Colors.transparent, end: Colors.black54), child: new Container() - ), - onTap: () { - _performance.reverse(); - } + ) ); Widget content = new SlideTransition( @@ -105,12 +80,12 @@ class DrawerState extends State { position: new AnimatedValue(_kClosedPosition, end: _kOpenPosition), child: new AnimatedContainer( curve: ease, - duration: const Duration(milliseconds: 200), + duration: _kThemeChangeDuration, decoration: new BoxDecoration( backgroundColor: Theme.of(context).canvasColor, boxShadow: shadows[config.level]), width: _kWidth, - child: new Block(config.children) + child: config.child ) ); @@ -118,33 +93,64 @@ class DrawerState extends State { onHorizontalDragStart: _performance.stop, onHorizontalDragUpdate: _handleDragUpdate, onHorizontalDragEnd: _handleDragEnd, - child: new Stack([ mask, content ]) + child: new Stack([ + mask, + new Positioned( + top: 0.0, + left: 0.0, + bottom: 0.0, + child: content + ) + ]) ); } - void _handleDismissed() { - if (config.navigator != null && - config.navigator.currentRoute is StateRoute && - (config.navigator.currentRoute as StateRoute).owner == this) // TODO(ianh): remove cast once analyzer is cleverer - config.navigator.pop(); - if (config.onDismissed != null) - config.onDismissed(); - } - bool get _isMostlyClosed => _performance.progress < 0.5; - void _settle() { _isMostlyClosed ? _performance.reverse() : _performance.play(); } - void _handleDragUpdate(double delta) { _performance.progress += delta / _kWidth; } + void _open() { + _performance.fling(velocity: 1.0); + } + + void _close() { + _performance.fling(velocity: -1.0); + } + void _handleDragEnd(Offset velocity) { if (velocity.dx.abs() >= _kMinFlingVelocity) { _performance.fling(velocity: velocity.dx * _kFlingVelocityScale); + } else if (_isMostlyClosed) { + _close(); } else { - _settle(); + _open(); } } - +} + +class DrawerRoute extends Route { + DrawerRoute({ this.child, this.level }); + + final Widget child; + final int level; + + bool get opaque => false; + + Widget build(NavigatorState navigator, WatchableAnimationPerformance nextRoutePerformance) { + return new Focus( + key: new GlobalObjectKey(this), + autofocus: true, + child: new Drawer( + child: child, + level: level, + navigator: navigator + ) + ); + } +} + +void showDrawer({ NavigatorState navigator, Widget child, int level: 3 }) { + navigator.push(new DrawerRoute(child: child, level: level)); }