Commit 49c47876 authored by Adam Barth's avatar Adam Barth

Convert Drawer to using navigator

This patch converts drawer to using the "openDialog" pattern for managing its
state. Currently, the drawer entrance and exit animation aren't integrated with
the navigator's animation system because the drawer's animations can be stopped
and reversed, which the navigator can't yet understand. That means dismissing
the drawer via the system back button causes the drawer to be removed
instanteously.

Fixes #715
Fixes #1187
parent 49aba0cc
......@@ -66,19 +66,14 @@ class FeedFragmentState extends State<FeedFragment> {
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<FeedFragment> {
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<FeedFragment> {
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<FeedFragment> {
toolbar: buildToolBar(),
body: buildBody(),
snackBar: buildSnackBar(),
floatingActionButton: buildFloatingActionButton(),
drawer: buildDrawer()
floatingActionButton: buildFloatingActionButton()
);
}
}
......
......@@ -56,22 +56,6 @@ class StockHomeState extends State<StockHome> {
});
}
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<StockHome> {
);
}
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<StockHome> {
new DrawerItem(
icon: 'action/help',
child: new Text('Help & Feedback'))
]
])
);
}
......@@ -154,7 +132,7 @@ class StockHomeState extends State<StockHome> {
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<StockHome> {
toolbar: _isSearching ? buildSearchBar() : buildToolBar(),
body: buildTabNavigator(),
snackBar: buildSnackBar(),
floatingActionButton: buildFloatingActionButton(),
drawer: buildDrawer()
floatingActionButton: buildFloatingActionButton()
);
}
}
......@@ -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<CardCollectionApp> {
class CardCollectionState extends State<CardCollection> {
static const TextStyle cardLabelStyle =
const TextStyle(color: Colors.white, fontSize: 18.0, fontWeight: bold);
......@@ -37,9 +40,7 @@ class CardCollectionAppState extends State<CardCollectionApp> {
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<CardCollectionApp> {
}
}
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<CardCollectionApp> {
});
}
_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 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 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(); })
])
);
}
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<CardCollectionApp> {
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),
}
));
}
......@@ -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<PageableListApp> {
});
}
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<PageableListApp> {
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<PageableListApp> {
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),
}
));
}
......@@ -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);
}
......
......@@ -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<Widget> 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<Drawer> {
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<Drawer> {
position: new AnimatedValue<Point>(_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<Drawer> {
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));
}
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