Commit 85a60a16 authored by Adam Barth's avatar Adam Barth

Merge pull request #696 from abarth/fix_drawer

Fix a large number of Drawer bugs
parents 17533e1d 1d195cb9
...@@ -55,6 +55,7 @@ class FeedFragment extends StatefulComponent { ...@@ -55,6 +55,7 @@ class FeedFragment extends StatefulComponent {
class FeedFragmentState extends State<FeedFragment> { class FeedFragmentState extends State<FeedFragment> {
FitnessMode _fitnessMode = FitnessMode.feed; FitnessMode _fitnessMode = FitnessMode.feed;
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
void _handleFitnessModeChange(FitnessMode value) { void _handleFitnessModeChange(FitnessMode value) {
setState(() { setState(() {
...@@ -63,9 +64,8 @@ class FeedFragmentState extends State<FeedFragment> { ...@@ -63,9 +64,8 @@ class FeedFragmentState extends State<FeedFragment> {
Navigator.pop(context); Navigator.pop(context);
} }
void _showDrawer() { Widget _buildDrawer() {
showDrawer( return new Drawer(
context: context,
child: new Block(<Widget>[ child: new Block(<Widget>[
new DrawerHeader(child: new Text('Fitness')), new DrawerHeader(child: new Text('Fitness')),
new DrawerItem( new DrawerItem(
...@@ -106,7 +106,7 @@ class FeedFragmentState extends State<FeedFragment> { ...@@ -106,7 +106,7 @@ class FeedFragmentState extends State<FeedFragment> {
return new ToolBar( return new ToolBar(
left: new IconButton( left: new IconButton(
icon: "navigation/menu", icon: "navigation/menu",
onPressed: _showDrawer), onPressed: () => _scaffoldKey.currentState?.openDrawer()),
center: new Text(fitnessModeTitle) center: new Text(fitnessModeTitle)
); );
} }
...@@ -207,9 +207,11 @@ class FeedFragmentState extends State<FeedFragment> { ...@@ -207,9 +207,11 @@ class FeedFragmentState extends State<FeedFragment> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return new Scaffold( return new Scaffold(
key: _scaffoldKey,
toolBar: buildToolBar(), toolBar: buildToolBar(),
body: buildBody(), body: buildBody(),
floatingActionButton: buildFloatingActionButton() floatingActionButton: buildFloatingActionButton(),
drawer: _buildDrawer()
); );
} }
} }
......
...@@ -6,19 +6,25 @@ import 'package:flutter/material.dart'; ...@@ -6,19 +6,25 @@ import 'package:flutter/material.dart';
import 'demo/widget_demo.dart'; import 'demo/widget_demo.dart';
class GalleryPage extends StatelessComponent { class GalleryPage extends StatefulComponent {
GalleryPage({ this.demos, this.active, this.onThemeChanged }); GalleryPage({ this.demos, this.active, this.onThemeChanged });
final List<WidgetDemo> demos; final List<WidgetDemo> demos;
final WidgetDemo active; final WidgetDemo active;
final ValueChanged<ThemeData> onThemeChanged; final ValueChanged<ThemeData> onThemeChanged;
void _showDrawer(BuildContext context) { _GalleryPageState createState() => new _GalleryPageState();
}
class _GalleryPageState extends State<GalleryPage> {
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
Widget _buildDrawer() {
List<Widget> items = <Widget>[ List<Widget> items = <Widget>[
new DrawerHeader(child: new Text('Material demos')), new DrawerHeader(child: new Text('Material demos')),
]; ];
for (WidgetDemo demo in demos) { for (WidgetDemo demo in config.demos) {
items.add(new DrawerItem( items.add(new DrawerItem(
onPressed: () { onPressed: () {
Navigator.pushNamed(context, demo.routeName); Navigator.pushNamed(context, demo.routeName);
...@@ -27,12 +33,12 @@ class GalleryPage extends StatelessComponent { ...@@ -27,12 +33,12 @@ class GalleryPage extends StatelessComponent {
)); ));
} }
showDrawer(context: context, child: new Block(items)); return new Drawer(child: new Block(items));
} }
Widget _body(BuildContext context) { Widget _buildBody() {
if (active != null) if (config.active != null)
return active.builder(context); return config.active.builder(context);
return new Material( return new Material(
child: new Center( child: new Center(
child: new Text('Select a demo from the drawer') child: new Text('Select a demo from the drawer')
...@@ -40,22 +46,24 @@ class GalleryPage extends StatelessComponent { ...@@ -40,22 +46,24 @@ class GalleryPage extends StatelessComponent {
); );
} }
Widget _tabBar(BuildContext context) { Widget _buildTabBar() {
final WidgetBuilder builder = active?.tabBarBuilder; final WidgetBuilder builder = config.active?.tabBarBuilder;
return builder != null ? builder(context) : null; return builder != null ? builder(context) : null;
} }
Widget build(BuildContext context) { Widget build(BuildContext context) {
return new Scaffold( return new Scaffold(
key: _scaffoldKey,
toolBar: new ToolBar( toolBar: new ToolBar(
left: new IconButton( left: new IconButton(
icon: 'navigation/menu', icon: 'navigation/menu',
onPressed: () { _showDrawer(context); } onPressed: () { _scaffoldKey.currentState?.openDrawer(); }
), ),
center: new Text(active?.title ?? 'Material gallery'), center: new Text(config.active?.title ?? 'Material gallery'),
tabBar: _tabBar(context) tabBar: _buildTabBar()
), ),
body: _body(context) drawer: _buildDrawer(),
body: _buildBody()
); );
} }
} }
...@@ -21,7 +21,7 @@ class StockHome extends StatefulComponent { ...@@ -21,7 +21,7 @@ class StockHome extends StatefulComponent {
class StockHomeState extends State<StockHome> { class StockHomeState extends State<StockHome> {
final GlobalKey scaffoldKey = new GlobalKey(); final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
final TabBarSelection _tabBarSelection = new TabBarSelection(); final TabBarSelection _tabBarSelection = new TabBarSelection();
bool _isSearching = false; bool _isSearching = false;
String _searchQuery; String _searchQuery;
...@@ -70,9 +70,8 @@ class StockHomeState extends State<StockHome> { ...@@ -70,9 +70,8 @@ class StockHomeState extends State<StockHome> {
); );
} }
void _showDrawer() { Widget _buildDrawer(BuildContext context) {
showDrawer( return new Drawer(
context: context,
child: new Block(<Widget>[ child: new Block(<Widget>[
new DrawerHeader(child: new Text('Stocks')), new DrawerHeader(child: new Text('Stocks')),
new DrawerItem( new DrawerItem(
...@@ -151,7 +150,7 @@ class StockHomeState extends State<StockHome> { ...@@ -151,7 +150,7 @@ class StockHomeState extends State<StockHome> {
elevation: 0, elevation: 0,
left: new IconButton( left: new IconButton(
icon: "navigation/menu", icon: "navigation/menu",
onPressed: _showDrawer onPressed: () => _scaffoldKey.currentState?.openDrawer()
), ),
center: new FadeTransition( center: new FadeTransition(
opacity: new AnimatedValue<double>( opacity: new AnimatedValue<double>(
...@@ -200,7 +199,7 @@ class StockHomeState extends State<StockHome> { ...@@ -200,7 +199,7 @@ class StockHomeState extends State<StockHome> {
stock.percentChange = 100.0 * (1.0 / stock.lastSale); stock.percentChange = 100.0 * (1.0 / stock.lastSale);
stock.lastSale += 1.0; stock.lastSale += 1.0;
}); });
scaffoldKey.currentState.showSnackBar(new SnackBar( _scaffoldKey.currentState.showSnackBar(new SnackBar(
content: new Text("Purchased ${stock.symbol} for ${stock.lastSale}"), content: new Text("Purchased ${stock.symbol} for ${stock.lastSale}"),
actions: <SnackBarAction>[ actions: <SnackBarAction>[
new SnackBarAction(label: "BUY MORE", onPressed: () { _buyStock(stock, arrowKey); }) new SnackBarAction(label: "BUY MORE", onPressed: () { _buyStock(stock, arrowKey); })
...@@ -219,7 +218,7 @@ class StockHomeState extends State<StockHome> { ...@@ -219,7 +218,7 @@ class StockHomeState extends State<StockHome> {
Navigator.pushNamed(context, '/stock/${stock.symbol}', mostValuableKeys: mostValuableKeys); Navigator.pushNamed(context, '/stock/${stock.symbol}', mostValuableKeys: mostValuableKeys);
}, },
onShow: (Stock stock, Key arrowKey) { onShow: (Stock stock, Key arrowKey) {
scaffoldKey.currentState.showBottomSheet((BuildContext context) => new StockSymbolBottomSheet(stock: stock)); _scaffoldKey.currentState.showBottomSheet((BuildContext context) => new StockSymbolBottomSheet(stock: stock));
} }
); );
} }
...@@ -285,9 +284,10 @@ class StockHomeState extends State<StockHome> { ...@@ -285,9 +284,10 @@ class StockHomeState extends State<StockHome> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return new Scaffold( return new Scaffold(
key: scaffoldKey, key: _scaffoldKey,
toolBar: _isSearching ? buildSearchBar() : buildToolBar(), toolBar: _isSearching ? buildSearchBar() : buildToolBar(),
floatingActionButton: buildFloatingActionButton(), floatingActionButton: buildFloatingActionButton(),
drawer: _buildDrawer(context),
body: new SizeObserver( body: new SizeObserver(
onSizeChanged: _handleSizeChanged, onSizeChanged: _handleSizeChanged,
child: new TabBarView<StockHomeTab>( child: new TabBarView<StockHomeTab>(
......
...@@ -46,6 +46,8 @@ class CardCollectionState extends State<CardCollection> { ...@@ -46,6 +46,8 @@ class CardCollectionState extends State<CardCollection> {
InvalidatorCallback _invalidator; InvalidatorCallback _invalidator;
Size _cardCollectionSize = new Size(200.0, 200.0); Size _cardCollectionSize = new Size(200.0, 200.0);
GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
void _initVariableSizedCardModels() { void _initVariableSizedCardModels() {
List<double> cardHeights = <double>[ List<double> cardHeights = <double>[
48.0, 63.0, 82.0, 146.0, 60.0, 55.0, 84.0, 96.0, 50.0, 48.0, 63.0, 82.0, 146.0, 60.0, 55.0, 84.0, 96.0, 50.0,
...@@ -117,9 +119,8 @@ class CardCollectionState extends State<CardCollection> { ...@@ -117,9 +119,8 @@ class CardCollectionState extends State<CardCollection> {
} }
} }
void _showDrawer() { Widget _buildDrawer() {
showDrawer( return new Drawer(
context: context,
child: new IconTheme( child: new IconTheme(
data: const IconThemeData(color: IconThemeColor.black), data: const IconThemeData(color: IconThemeColor.black),
child: new Block(<Widget>[ child: new Block(<Widget>[
...@@ -265,9 +266,9 @@ class CardCollectionState extends State<CardCollection> { ...@@ -265,9 +266,9 @@ class CardCollectionState extends State<CardCollection> {
); );
} }
Widget buildToolBar() { Widget _buildToolBar() {
return new ToolBar( return new ToolBar(
left: new IconButton(icon: "navigation/menu", onPressed: _showDrawer), left: new IconButton(icon: "navigation/menu", onPressed: () => _scaffoldKey.currentState?.openDrawer()),
right: <Widget>[ right: <Widget>[
new Text(_dismissDirectionText(_dismissDirection)) new Text(_dismissDirectionText(_dismissDirection))
], ],
...@@ -281,7 +282,7 @@ class CardCollectionState extends State<CardCollection> { ...@@ -281,7 +282,7 @@ class CardCollectionState extends State<CardCollection> {
); );
} }
Widget buildCard(BuildContext context, int index) { Widget _buildCard(BuildContext context, int index) {
if (index >= _cardModels.length) if (index >= _cardModels.length)
return null; return null;
...@@ -393,19 +394,18 @@ class CardCollectionState extends State<CardCollection> { ...@@ -393,19 +394,18 @@ class CardCollectionState extends State<CardCollection> {
} }
Widget build(BuildContext context) { Widget build(BuildContext context) {
Widget cardCollection; Widget cardCollection;
if (_fixedSizeCards) { if (_fixedSizeCards) {
cardCollection = new ScrollableList<CardModel> ( cardCollection = new ScrollableList<CardModel> (
snapOffsetCallback: _snapToCenter ? _toSnapOffset : null, snapOffsetCallback: _snapToCenter ? _toSnapOffset : null,
snapAlignmentOffset: _cardCollectionSize.height / 2.0, snapAlignmentOffset: _cardCollectionSize.height / 2.0,
items: _cardModels, items: _cardModels,
itemBuilder: (BuildContext context, CardModel card, int index) => buildCard(context, card.value), itemBuilder: (BuildContext context, CardModel card, int index) => _buildCard(context, card.value),
itemExtent: _cardModels[0].height itemExtent: _cardModels[0].height
); );
} else { } else {
cardCollection = new ScrollableMixedWidgetList( cardCollection = new ScrollableMixedWidgetList(
builder: buildCard, builder: _buildCard,
token: _cardModels.length, token: _cardModels.length,
snapOffsetCallback: _snapToCenter ? _toSnapOffset : null, snapOffsetCallback: _snapToCenter ? _toSnapOffset : null,
snapAlignmentOffset: _cardCollectionSize.height / 2.0, snapAlignmentOffset: _cardCollectionSize.height / 2.0,
...@@ -446,7 +446,8 @@ class CardCollectionState extends State<CardCollection> { ...@@ -446,7 +446,8 @@ class CardCollectionState extends State<CardCollection> {
primarySwatch: _primaryColor primarySwatch: _primaryColor
), ),
child: new Scaffold( child: new Scaffold(
toolBar: buildToolBar(), toolBar: _buildToolBar(),
drawer: _buildDrawer(),
body: body body: body
) )
); );
......
...@@ -41,6 +41,8 @@ class PageableListAppState extends State<PageableListApp> { ...@@ -41,6 +41,8 @@ class PageableListAppState extends State<PageableListApp> {
ScrollDirection scrollDirection = ScrollDirection.horizontal; ScrollDirection scrollDirection = ScrollDirection.horizontal;
bool itemsWrap = false; bool itemsWrap = false;
GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
void updatePageSize(Size newSize) { void updatePageSize(Size newSize) {
setState(() { setState(() {
pageSize = newSize; pageSize = newSize;
...@@ -83,9 +85,8 @@ class PageableListAppState extends State<PageableListApp> { ...@@ -83,9 +85,8 @@ class PageableListAppState extends State<PageableListApp> {
}); });
} }
void _showDrawer() { Widget _buildDrawer() {
showDrawer( return new Drawer(
context: context,
child: new Block(<Widget>[ child: new Block(<Widget>[
new DrawerHeader(child: new Text('Options')), new DrawerHeader(child: new Text('Options')),
new DrawerItem( new DrawerItem(
...@@ -111,9 +112,9 @@ class PageableListAppState extends State<PageableListApp> { ...@@ -111,9 +112,9 @@ class PageableListAppState extends State<PageableListApp> {
); );
} }
Widget buildToolBar() { Widget _buildToolBar() {
return new ToolBar( return new ToolBar(
left: new IconButton(icon: "navigation/menu", onPressed: _showDrawer), left: new IconButton(icon: "navigation/menu", onPressed: () => _scaffoldKey.currentState?.openDrawer()),
center: new Text('PageableList'), center: new Text('PageableList'),
right: <Widget>[ right: <Widget>[
new Text(scrollDirection == ScrollDirection.horizontal ? "horizontal" : "vertical") new Text(scrollDirection == ScrollDirection.horizontal ? "horizontal" : "vertical")
...@@ -121,7 +122,7 @@ class PageableListAppState extends State<PageableListApp> { ...@@ -121,7 +122,7 @@ class PageableListAppState extends State<PageableListApp> {
); );
} }
Widget buildBody(BuildContext context) { Widget _buildBody(BuildContext context) {
Widget list = new PageableList<CardModel>( Widget list = new PageableList<CardModel>(
items: cardModels, items: cardModels,
itemsWrap: itemsWrap, itemsWrap: itemsWrap,
...@@ -141,8 +142,9 @@ class PageableListAppState extends State<PageableListApp> { ...@@ -141,8 +142,9 @@ class PageableListAppState extends State<PageableListApp> {
return new IconTheme( return new IconTheme(
data: const IconThemeData(color: IconThemeColor.white), data: const IconThemeData(color: IconThemeColor.white),
child: new Scaffold( child: new Scaffold(
toolBar: buildToolBar(), toolBar: _buildToolBar(),
body: buildBody(context) drawer: _buildDrawer(),
body: _buildBody(context)
) )
); );
} }
......
...@@ -22,111 +22,48 @@ import 'material.dart'; ...@@ -22,111 +22,48 @@ import 'material.dart';
// The right nav can vary depending on content. // The right nav can vary depending on content.
const double _kWidth = 304.0; const double _kWidth = 304.0;
const double _kEdgeDragWidth = 20.0;
const double _kMinFlingVelocity = 365.0; const double _kMinFlingVelocity = 365.0;
const Duration _kBaseSettleDuration = const Duration(milliseconds: 246); const Duration _kBaseSettleDuration = const Duration(milliseconds: 246);
class _Drawer extends StatelessComponent { class Drawer extends StatelessComponent {
_Drawer({ Key key, this.route }) : super(key: key); Drawer({
Key key,
final _DrawerRoute route; this.elevation: 16,
this.child
Widget build(BuildContext context) { }) : super(key: key);
return new Focus(
key: new GlobalObjectKey(route),
child: new ConstrainedBox(
constraints: const BoxConstraints.expand(width: _kWidth),
child: new Material(
elevation: route.elevation,
child: route.child
)
)
);
}
}
enum _DrawerState {
showing,
popped,
closed,
}
class _DrawerRoute extends OverlayRoute {
_DrawerRoute({ this.child, this.elevation });
final Widget child;
final int elevation; final int elevation;
final Widget child;
List<WidgetBuilder> get builders => <WidgetBuilder>[ _build ]; Widget build(BuildContext context) {
return new ConstrainedBox(
final GlobalKey<_DrawerControllerState> _drawerKey = new GlobalKey<_DrawerControllerState>(); constraints: const BoxConstraints.expand(width: _kWidth),
_DrawerState _state = _DrawerState.showing; child: new Material(
elevation: elevation,
Widget _build(BuildContext context) { child: child
return new RepaintBoundary(
child: new _DrawerController(
key: _drawerKey,
settleDuration: _kBaseSettleDuration,
onClosed: () {
_DrawerState previousState = _state;
_state = _DrawerState.closed;
switch (previousState) {
case _DrawerState.showing:
Navigator.pop(context);
break;
case _DrawerState.popped:
finished();
break;
case _DrawerState.closed:
assert(false);
break;
}
},
child: new _Drawer(route: this)
) )
); );
} }
bool didPop(dynamic result) {
// we don't call the superclass because we want to control the timing of the
// call to finished().
switch (_state) {
case _DrawerState.showing:
_drawerKey.currentState?._close();
_state = _DrawerState.popped;
break;
case _DrawerState.popped:
assert(false);
break;
case _DrawerState.closed:
finished();
break;
}
return true;
}
} }
class _DrawerController extends StatefulComponent { class DrawerController extends StatefulComponent {
_DrawerController({ DrawerController({
Key key, GlobalKey key,
this.settleDuration,
this.onClosed,
this.child this.child
}) : super(key: key); }) : super(key: key);
final Duration settleDuration;
final Widget child; final Widget child;
final VoidCallback onClosed;
_DrawerControllerState createState() => new _DrawerControllerState(); DrawerControllerState createState() => new DrawerControllerState();
} }
class _DrawerControllerState extends State<_DrawerController> { class DrawerControllerState extends State<DrawerController> {
void initState() { void initState() {
super.initState(); super.initState();
_performance = new Performance(duration: config.settleDuration) _performance = new Performance(duration: _kBaseSettleDuration)
..addListener(_performanceChanged) ..addListener(_performanceChanged)
..addStatusListener(_performanceStatusChanged) ..addStatusListener(_performanceStatusChanged);
..play();
} }
void dispose() { void dispose() {
...@@ -143,15 +80,41 @@ class _DrawerControllerState extends State<_DrawerController> { ...@@ -143,15 +80,41 @@ class _DrawerControllerState extends State<_DrawerController> {
}); });
} }
LocalHistoryEntry _historyEntry;
void _ensureHistoryEntry() {
if (_historyEntry == null) {
ModalRoute route = ModalRoute.of(context);
if (route != null) {
_historyEntry = new LocalHistoryEntry(onRemove: _handleHistoryEntryRemoved);
route.addLocalHistoryEntry(_historyEntry);
}
}
}
void _performanceStatusChanged(PerformanceStatus status) { void _performanceStatusChanged(PerformanceStatus status) {
if (status == PerformanceStatus.dismissed && config.onClosed != null) switch (status) {
config.onClosed(); case PerformanceStatus.forward:
_ensureHistoryEntry();
break;
case PerformanceStatus.reverse:
_historyEntry?.remove();
_historyEntry = null;
break;
case PerformanceStatus.dismissed:
break;
case PerformanceStatus.completed:
break;
}
} }
Performance _performance; void _handleHistoryEntryRemoved() {
double _width; _historyEntry = null;
close();
}
final AnimatedColorValue _color = new AnimatedColorValue(Colors.transparent, end: Colors.black54); Performance _performance;
double _width = _kEdgeDragWidth;
void _handleSizeChanged(Size newSize) { void _handleSizeChanged(Size newSize) {
setState(() { setState(() {
...@@ -161,6 +124,7 @@ class _DrawerControllerState extends State<_DrawerController> { ...@@ -161,6 +124,7 @@ class _DrawerControllerState extends State<_DrawerController> {
void _handlePointerDown(_) { void _handlePointerDown(_) {
_performance.stop(); _performance.stop();
_ensureHistoryEntry();
} }
void _move(double delta) { void _move(double delta) {
...@@ -168,57 +132,77 @@ class _DrawerControllerState extends State<_DrawerController> { ...@@ -168,57 +132,77 @@ class _DrawerControllerState extends State<_DrawerController> {
} }
void _settle(Offset velocity) { void _settle(Offset velocity) {
if (_performance.isDismissed)
return;
if (velocity.dx.abs() >= _kMinFlingVelocity) { if (velocity.dx.abs() >= _kMinFlingVelocity) {
_performance.fling(velocity: velocity.dx / _width); _performance.fling(velocity: velocity.dx / _width);
} else if (_performance.progress < 0.5) { } else if (_performance.progress < 0.5) {
_close(); close();
} else { } else {
_performance.fling(velocity: 1.0); open();
} }
} }
void _close() { void open() {
_performance.fling(velocity: 1.0);
}
void close() {
_performance.fling(velocity: -1.0); _performance.fling(velocity: -1.0);
} }
final AnimatedColorValue _color = new AnimatedColorValue(Colors.transparent, end: Colors.black54);
Widget build(BuildContext context) { Widget build(BuildContext context) {
_performance.updateVariable(_color); HitTestBehavior behavior;
return new GestureDetector( Widget child;
onHorizontalDragUpdate: _move, if (_performance.status == PerformanceStatus.dismissed) {
onHorizontalDragEnd: _settle, behavior = HitTestBehavior.translucent;
child: new Stack(<Widget>[ child = new Align(
new GestureDetector( alignment: const FractionalOffset(0.0, 0.5),
onTap: _close, widthFactor: 1.0,
child: new DecoratedBox( child: new Container(width: _kEdgeDragWidth)
decoration: new BoxDecoration( );
backgroundColor: _color.value } else {
), _performance.updateVariable(_color);
child: new Container() child = new RepaintBoundary(
) child: new Stack(<Widget>[
), new GestureDetector(
new Positioned( onTap: close,
top: 0.0, child: new DecoratedBox(
left: 0.0, decoration: new BoxDecoration(
bottom: 0.0, backgroundColor: _color.value
child: new Listener( ),
onPointerDown: _handlePointerDown, child: new Container()
child: new Align( )
alignment: const FractionalOffset(1.0, 0.5), ),
widthFactor: _performance.progress, new Align(
child: new SizeObserver( alignment: const FractionalOffset(0.0, 0.5),
onSizeChanged: _handleSizeChanged, child: new Listener(
child: new RepaintBoundary( onPointerDown: _handlePointerDown,
child: config.child child: new Align(
alignment: const FractionalOffset(1.0, 0.5),
widthFactor: _performance.progress,
child: new SizeObserver(
onSizeChanged: _handleSizeChanged,
child: new RepaintBoundary(
child: new Focus(
key: new GlobalObjectKey(config.key),
child: config.child
)
)
) )
) )
) )
) )
) ])
]) );
}
return new GestureDetector(
onHorizontalDragUpdate: _move,
onHorizontalDragEnd: _settle,
behavior: behavior,
child: child
); );
} }
} }
void showDrawer({ BuildContext context, Widget child, int elevation: 16 }) {
Navigator.push(context, new _DrawerRoute(child: child, elevation: elevation));
}
...@@ -15,20 +15,30 @@ import 'bottom_sheet.dart'; ...@@ -15,20 +15,30 @@ import 'bottom_sheet.dart';
import 'material.dart'; import 'material.dart';
import 'snack_bar.dart'; import 'snack_bar.dart';
import 'tool_bar.dart'; import 'tool_bar.dart';
import 'drawer.dart';
const double _kFloatingActionButtonMargin = 16.0; // TODO(hmuller): should be device dependent const double _kFloatingActionButtonMargin = 16.0; // TODO(hmuller): should be device dependent
enum _Child { body, toolBar, bottomSheet, snackBar, floatingActionButton } enum _Child {
body,
toolBar,
bottomSheet,
snackBar,
floatingActionButton,
drawer,
}
class _ScaffoldLayout extends MultiChildLayoutDelegate { class _ScaffoldLayout extends MultiChildLayoutDelegate {
void performLayout(Size size, BoxConstraints constraints) { void performLayout(Size size, BoxConstraints constraints) {
BoxConstraints looseConstraints = constraints.loosen();
// This part of the layout has the same effect as putting the toolbar and // This part of the layout has the same effect as putting the toolbar and
// body in a column and making the body flexible. What's different is that // body in a column and making the body flexible. What's different is that
// in this case the toolbar appears -after- the body in the stacking order, // in this case the toolbar appears -after- the body in the stacking order,
// so the toolbar's shadow is drawn on top of the body. // so the toolbar's shadow is drawn on top of the body.
final BoxConstraints toolBarConstraints = constraints.loosen().tightenWidth(size.width); final BoxConstraints toolBarConstraints = looseConstraints.tightenWidth(size.width);
Size toolBarSize = Size.zero; Size toolBarSize = Size.zero;
if (isChild(_Child.toolBar)) { if (isChild(_Child.toolBar)) {
...@@ -52,7 +62,7 @@ class _ScaffoldLayout extends MultiChildLayoutDelegate { ...@@ -52,7 +62,7 @@ class _ScaffoldLayout extends MultiChildLayoutDelegate {
// non-zero height then it's inset from the parent's right and bottom edges // non-zero height then it's inset from the parent's right and bottom edges
// by _kFloatingActionButtonMargin. // by _kFloatingActionButtonMargin.
final BoxConstraints fullWidthConstraints = constraints.loosen().tightenWidth(size.width); final BoxConstraints fullWidthConstraints = looseConstraints.tightenWidth(size.width);
Size bottomSheetSize = Size.zero; Size bottomSheetSize = Size.zero;
Size snackBarSize = Size.zero; Size snackBarSize = Size.zero;
...@@ -67,7 +77,7 @@ class _ScaffoldLayout extends MultiChildLayoutDelegate { ...@@ -67,7 +77,7 @@ class _ScaffoldLayout extends MultiChildLayoutDelegate {
} }
if (isChild(_Child.floatingActionButton)) { if (isChild(_Child.floatingActionButton)) {
final Size fabSize = layoutChild(_Child.floatingActionButton, constraints.loosen()); final Size fabSize = layoutChild(_Child.floatingActionButton, looseConstraints);
final double fabX = size.width - fabSize.width - _kFloatingActionButtonMargin; final double fabX = size.width - fabSize.width - _kFloatingActionButtonMargin;
double fabY = size.height - fabSize.height - _kFloatingActionButtonMargin; double fabY = size.height - fabSize.height - _kFloatingActionButtonMargin;
if (snackBarSize.height > 0.0) if (snackBarSize.height > 0.0)
...@@ -76,6 +86,11 @@ class _ScaffoldLayout extends MultiChildLayoutDelegate { ...@@ -76,6 +86,11 @@ class _ScaffoldLayout extends MultiChildLayoutDelegate {
fabY = math.min(fabY, size.height - bottomSheetSize.height - fabSize.height / 2.0); fabY = math.min(fabY, size.height - bottomSheetSize.height - fabSize.height / 2.0);
positionChild(_Child.floatingActionButton, new Point(fabX, fabY)); positionChild(_Child.floatingActionButton, new Point(fabX, fabY));
} }
if (isChild(_Child.drawer)) {
layoutChild(_Child.drawer, looseConstraints);
positionChild(_Child.drawer, Point.origin);
}
} }
} }
...@@ -86,12 +101,14 @@ class Scaffold extends StatefulComponent { ...@@ -86,12 +101,14 @@ class Scaffold extends StatefulComponent {
Key key, Key key,
this.toolBar, this.toolBar,
this.body, this.body,
this.floatingActionButton this.floatingActionButton,
this.drawer
}) : super(key: key); }) : super(key: key);
final ToolBar toolBar; final ToolBar toolBar;
final Widget body; final Widget body;
final Widget floatingActionButton; final Widget floatingActionButton;
final Widget drawer;
static ScaffoldState of(BuildContext context) => context.ancestorStateOfType(ScaffoldState); static ScaffoldState of(BuildContext context) => context.ancestorStateOfType(ScaffoldState);
...@@ -100,6 +117,14 @@ class Scaffold extends StatefulComponent { ...@@ -100,6 +117,14 @@ class Scaffold extends StatefulComponent {
class ScaffoldState extends State<Scaffold> { class ScaffoldState extends State<Scaffold> {
// DRAWER API
final GlobalKey<DrawerControllerState> _drawerKey = new GlobalKey<DrawerControllerState>();
void openDrawer() {
_drawerKey.currentState.open();
}
// SNACKBAR API // SNACKBAR API
Queue<ScaffoldFeatureController<SnackBar>> _snackBars = new Queue<ScaffoldFeatureController<SnackBar>>(); Queue<ScaffoldFeatureController<SnackBar>> _snackBars = new Queue<ScaffoldFeatureController<SnackBar>>();
...@@ -270,6 +295,16 @@ class ScaffoldState extends State<Scaffold> { ...@@ -270,6 +295,16 @@ class ScaffoldState extends State<Scaffold> {
_addIfNonNull(children, config.floatingActionButton, _Child.floatingActionButton); _addIfNonNull(children, config.floatingActionButton, _Child.floatingActionButton);
if (config.drawer != null) {
children.add(new LayoutId(
id: _Child.drawer,
child: new DrawerController(
key: _drawerKey,
child: config.drawer
)
));
}
return new CustomMultiChildLayout(children, delegate: _scaffoldLayout); return new CustomMultiChildLayout(children, delegate: _scaffoldLayout);
} }
......
...@@ -146,7 +146,7 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> { ...@@ -146,7 +146,7 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> {
super.dispose(); super.dispose();
} }
final ProxyPerformance forwardPerformance = new ProxyPerformance(); final ProxyPerformance forwardPerformance = new ProxyPerformance();
void didPushNext(Route nextRoute) { void didPushNext(Route nextRoute) {
...@@ -183,7 +183,7 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> { ...@@ -183,7 +183,7 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> {
forwardPerformance, forwardPerformance,
buildTransition( buildTransition(
context, context,
performance, performance,
child child
) )
); );
...@@ -202,6 +202,7 @@ class LocalHistoryEntry { ...@@ -202,6 +202,7 @@ class LocalHistoryEntry {
LocalHistoryRoute _owner; LocalHistoryRoute _owner;
void remove() { void remove() {
_owner.removeLocalHistoryEntry(this); _owner.removeLocalHistoryEntry(this);
assert(_owner == null);
} }
void _notifyRemoved() { void _notifyRemoved() {
if (onRemove != null) if (onRemove != null)
...@@ -432,4 +433,4 @@ abstract class PageRoute<T> extends ModalRoute<T> { ...@@ -432,4 +433,4 @@ abstract class PageRoute<T> extends ModalRoute<T> {
NamedRouteSettings settings: const NamedRouteSettings() NamedRouteSettings settings: const NamedRouteSettings()
}) : super(completer: completer, settings: settings); }) : super(completer: completer, settings: settings);
bool get opaque => true; bool get opaque => true;
} }
\ No newline at end of file
...@@ -11,20 +11,25 @@ void main() { ...@@ -11,20 +11,25 @@ void main() {
test('Drawer control test', () { test('Drawer control test', () {
testWidgets((WidgetTester tester) { testWidgets((WidgetTester tester) {
GlobalKey<ScaffoldState> scaffoldKey = new GlobalKey<ScaffoldState>();
BuildContext context; BuildContext context;
tester.pumpWidget( tester.pumpWidget(
new MaterialApp( new MaterialApp(
routes: <String, RouteBuilder>{ routes: <String, RouteBuilder>{
'/': (RouteArguments args) { '/': (RouteArguments args) {
context = args.context; context = args.context;
return new Container(); return new Scaffold(
key: scaffoldKey,
drawer: new Text('drawer'),
body: new Container()
);
} }
} }
) )
); );
tester.pump(); // no effect tester.pump(); // no effect
expect(tester.findText('drawer'), isNull); expect(tester.findText('drawer'), isNull);
showDrawer(context: context, child: new Text('drawer')); scaffoldKey.currentState.openDrawer();
tester.pump(); // drawer should be starting to animate in tester.pump(); // drawer should be starting to animate in
expect(tester.findText('drawer'), isNotNull); expect(tester.findText('drawer'), isNotNull);
tester.pump(new Duration(seconds: 1)); // animation done tester.pump(new Duration(seconds: 1)); // animation done
...@@ -39,21 +44,24 @@ void main() { ...@@ -39,21 +44,24 @@ void main() {
test('Drawer tap test', () { test('Drawer tap test', () {
testWidgets((WidgetTester tester) { testWidgets((WidgetTester tester) {
BuildContext context; GlobalKey<ScaffoldState> scaffoldKey = new GlobalKey<ScaffoldState>();
tester.pumpWidget(new Container()); // throw away the old App and its Navigator tester.pumpWidget(new Container()); // throw away the old App and its Navigator
tester.pumpWidget( tester.pumpWidget(
new MaterialApp( new MaterialApp(
routes: <String, RouteBuilder>{ routes: <String, RouteBuilder>{
'/': (RouteArguments args) { '/': (RouteArguments args) {
context = args.context; return new Scaffold(
return new Container(); key: scaffoldKey,
drawer: new Text('drawer'),
body: new Container()
);
} }
} }
) )
); );
tester.pump(); // no effect tester.pump(); // no effect
expect(tester.findText('drawer'), isNull); expect(tester.findText('drawer'), isNull);
showDrawer(context: context, child: new Text('drawer')); scaffoldKey.currentState.openDrawer();
tester.pump(); // drawer should be starting to animate in tester.pump(); // drawer should be starting to animate in
expect(tester.findText('drawer'), isNotNull); expect(tester.findText('drawer'), isNotNull);
tester.pump(new Duration(seconds: 1)); // animation done tester.pump(new Duration(seconds: 1)); // animation done
...@@ -64,7 +72,9 @@ void main() { ...@@ -64,7 +72,9 @@ void main() {
tester.pump(new Duration(seconds: 1)); // ditto tester.pump(new Duration(seconds: 1)); // ditto
expect(tester.findText('drawer'), isNotNull); expect(tester.findText('drawer'), isNotNull);
tester.tapAt(const Point(750.0, 100.0)); // on the mask tester.tapAt(const Point(750.0, 100.0)); // on the mask
tester.pump(); // drawer should be starting to animate away tester.pump();
tester.pump(new Duration(milliseconds: 10));
// drawer should be starting to animate away
expect(tester.findText('drawer'), isNotNull); expect(tester.findText('drawer'), isNotNull);
tester.pump(new Duration(seconds: 1)); // animation done tester.pump(new Duration(seconds: 1)); // animation done
expect(tester.findText('drawer'), isNull); expect(tester.findText('drawer'), isNull);
......
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