Commit 082730e9 authored by Hans Muller's avatar Hans Muller Committed by GitHub

Tapping status bar scrolls to top on IOS (#5425)

parent ba53d192
...@@ -128,15 +128,18 @@ class TravelDestinationItem extends StatelessWidget { ...@@ -128,15 +128,18 @@ class TravelDestinationItem extends StatelessWidget {
} }
class CardsDemo extends StatelessWidget { class CardsDemo extends StatelessWidget {
static final GlobalKey<ScrollableState> _scrollableKey = new GlobalKey<ScrollableState>();
static const String routeName = '/cards'; static const String routeName = '/cards';
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return new Scaffold( return new Scaffold(
scrollableKey: _scrollableKey,
appBar: new AppBar( appBar: new AppBar(
title: new Text('Travel stream') title: new Text('Travel stream')
), ),
body: new ScrollableList( body: new ScrollableList(
scrollableKey: _scrollableKey,
itemExtent: TravelDestinationItem.height, itemExtent: TravelDestinationItem.height,
padding: const EdgeInsets.only(top: 8.0, left: 8.0, right: 8.0), padding: const EdgeInsets.only(top: 8.0, left: 8.0, right: 8.0),
children: destinations.map((TravelDestination destination) { children: destinations.map((TravelDestination destination) {
......
...@@ -8,8 +8,9 @@ import 'package:flutter/widgets.dart'; ...@@ -8,8 +8,9 @@ import 'package:flutter/widgets.dart';
const double kColorItemHeight = 48.0; const double kColorItemHeight = 48.0;
class ColorSwatch { class ColorSwatch {
const ColorSwatch({ this.name, this.colors, this.accentColors, this.threshold: 900}); ColorSwatch({ this.name, this.colors, this.accentColors, this.threshold: 900});
final GlobalKey<ScrollableState> scrollableKey = new GlobalKey<ScrollableState>();
final String name; final String name;
final Map<int, Color> colors; final Map<int, Color> colors;
final Map<int, Color> accentColors; final Map<int, Color> accentColors;
...@@ -18,26 +19,26 @@ class ColorSwatch { ...@@ -18,26 +19,26 @@ class ColorSwatch {
bool get isValid => this.name != null && this.colors != null && threshold != null; bool get isValid => this.name != null && this.colors != null && threshold != null;
} }
const List<ColorSwatch> colorSwatches = const <ColorSwatch>[ final List<ColorSwatch> colorSwatches = <ColorSwatch>[
const ColorSwatch(name: 'RED', colors: Colors.red, accentColors: Colors.redAccent, threshold: 300), new ColorSwatch(name: 'RED', colors: Colors.red, accentColors: Colors.redAccent, threshold: 300),
const ColorSwatch(name: 'PINK', colors: Colors.pink, accentColors: Colors.pinkAccent, threshold: 200), new ColorSwatch(name: 'PINK', colors: Colors.pink, accentColors: Colors.pinkAccent, threshold: 200),
const ColorSwatch(name: 'PURPLE', colors: Colors.purple, accentColors: Colors.purpleAccent, threshold: 200), new ColorSwatch(name: 'PURPLE', colors: Colors.purple, accentColors: Colors.purpleAccent, threshold: 200),
const ColorSwatch(name: 'DEEP PURPLE', colors: Colors.deepPurple, accentColors: Colors.deepPurpleAccent, threshold: 200), new ColorSwatch(name: 'DEEP PURPLE', colors: Colors.deepPurple, accentColors: Colors.deepPurpleAccent, threshold: 200),
const ColorSwatch(name: 'INDIGO', colors: Colors.indigo, accentColors: Colors.indigoAccent, threshold: 200), new ColorSwatch(name: 'INDIGO', colors: Colors.indigo, accentColors: Colors.indigoAccent, threshold: 200),
const ColorSwatch(name: 'BLUE', colors: Colors.blue, accentColors: Colors.blueAccent, threshold: 400), new ColorSwatch(name: 'BLUE', colors: Colors.blue, accentColors: Colors.blueAccent, threshold: 400),
const ColorSwatch(name: 'LIGHT BLUE', colors: Colors.lightBlue, accentColors: Colors.lightBlueAccent, threshold: 500), new ColorSwatch(name: 'LIGHT BLUE', colors: Colors.lightBlue, accentColors: Colors.lightBlueAccent, threshold: 500),
const ColorSwatch(name: 'CYAN', colors: Colors.cyan, accentColors: Colors.cyanAccent, threshold: 600), new ColorSwatch(name: 'CYAN', colors: Colors.cyan, accentColors: Colors.cyanAccent, threshold: 600),
const ColorSwatch(name: 'TEAL', colors: Colors.teal, accentColors: Colors.tealAccent, threshold: 400), new ColorSwatch(name: 'TEAL', colors: Colors.teal, accentColors: Colors.tealAccent, threshold: 400),
const ColorSwatch(name: 'GREEN', colors: Colors.green, accentColors: Colors.greenAccent, threshold: 500), new ColorSwatch(name: 'GREEN', colors: Colors.green, accentColors: Colors.greenAccent, threshold: 500),
const ColorSwatch(name: 'LIGHT GREEN', colors: Colors.lightGreen, accentColors: Colors.lightGreenAccent, threshold: 600), new ColorSwatch(name: 'LIGHT GREEN', colors: Colors.lightGreen, accentColors: Colors.lightGreenAccent, threshold: 600),
const ColorSwatch(name: 'LIME', colors: Colors.lime, accentColors: Colors.limeAccent, threshold: 800), new ColorSwatch(name: 'LIME', colors: Colors.lime, accentColors: Colors.limeAccent, threshold: 800),
const ColorSwatch(name: 'YELLOW', colors: Colors.yellow, accentColors: Colors.yellowAccent), new ColorSwatch(name: 'YELLOW', colors: Colors.yellow, accentColors: Colors.yellowAccent),
const ColorSwatch(name: 'AMBER', colors: Colors.amber, accentColors: Colors.amberAccent), new ColorSwatch(name: 'AMBER', colors: Colors.amber, accentColors: Colors.amberAccent),
const ColorSwatch(name: 'ORANGE', colors: Colors.orange, accentColors: Colors.orangeAccent, threshold: 700), new ColorSwatch(name: 'ORANGE', colors: Colors.orange, accentColors: Colors.orangeAccent, threshold: 700),
const ColorSwatch(name: 'DEEP ORANGE', colors: Colors.deepOrange, accentColors: Colors.deepOrangeAccent, threshold: 400), new ColorSwatch(name: 'DEEP ORANGE', colors: Colors.deepOrange, accentColors: Colors.deepOrangeAccent, threshold: 400),
const ColorSwatch(name: 'BROWN', colors: Colors.brown, threshold: 200), new ColorSwatch(name: 'BROWN', colors: Colors.brown, threshold: 200),
const ColorSwatch(name: 'GREY', colors: Colors.grey, threshold: 500), new ColorSwatch(name: 'GREY', colors: Colors.grey, threshold: 500),
const ColorSwatch(name: 'BLUE GREY', colors: Colors.blueGrey, threshold: 500) new ColorSwatch(name: 'BLUE GREY', colors: Colors.blueGrey, threshold: 500)
]; ];
...@@ -102,20 +103,43 @@ class ColorSwatchTabView extends StatelessWidget { ...@@ -102,20 +103,43 @@ class ColorSwatchTabView extends StatelessWidget {
} }
return new ScrollableList( return new ScrollableList(
scrollableKey: swatch.scrollableKey,
itemExtent: kColorItemHeight, itemExtent: kColorItemHeight,
children: colorItems children: colorItems
); );
} }
} }
class ColorsDemo extends StatelessWidget { class ColorsDemo extends StatefulWidget {
ColorsDemo({ Key key }) : super(key: key);
static const String routeName = '/colors'; static const String routeName = '/colors';
@override
_ColorsDemoState createState() => new _ColorsDemoState();
}
class _ColorsDemoState extends State<ColorsDemo> {
ColorSwatch _selectedSwatch;
@override
void initState() {
super.initState();
_selectedSwatch = colorSwatches.first;
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return new TabBarSelection<ColorSwatch>( return new TabBarSelection<ColorSwatch>(
values: colorSwatches, values: colorSwatches,
onChanged: (ColorSwatch value) {
setState(() {
_selectedSwatch = value;
});
},
child: new Scaffold( child: new Scaffold(
scrollableKey: _selectedSwatch.scrollableKey,
appBar: new AppBar( appBar: new AppBar(
elevation: 0, elevation: 0,
title: new Text('Colors'), title: new Text('Colors'),
......
...@@ -80,7 +80,8 @@ class ContactsDemo extends StatefulWidget { ...@@ -80,7 +80,8 @@ class ContactsDemo extends StatefulWidget {
} }
class ContactsDemoState extends State<ContactsDemo> { class ContactsDemoState extends State<ContactsDemo> {
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>(); static final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
static final GlobalKey<ScrollableState> _scrollableKey = new GlobalKey<ScrollableState>();
final double _appBarHeight = 256.0; final double _appBarHeight = 256.0;
AppBarBehavior _appBarBehavior = AppBarBehavior.under; AppBarBehavior _appBarBehavior = AppBarBehavior.under;
...@@ -94,6 +95,7 @@ class ContactsDemoState extends State<ContactsDemo> { ...@@ -94,6 +95,7 @@ class ContactsDemoState extends State<ContactsDemo> {
), ),
child: new Scaffold( child: new Scaffold(
key: _scaffoldKey, key: _scaffoldKey,
scrollableKey: _scrollableKey,
appBarBehavior: _appBarBehavior, appBarBehavior: _appBarBehavior,
appBar: new AppBar( appBar: new AppBar(
expandedHeight: _appBarHeight, expandedHeight: _appBarHeight,
...@@ -151,6 +153,7 @@ class ContactsDemoState extends State<ContactsDemo> { ...@@ -151,6 +153,7 @@ class ContactsDemoState extends State<ContactsDemo> {
), ),
body: new Block( body: new Block(
padding: new EdgeInsets.only(top: _appBarHeight + statusBarHeight), padding: new EdgeInsets.only(top: _appBarHeight + statusBarHeight),
scrollableKey: _scrollableKey,
children: <Widget>[ children: <Widget>[
new _ContactCategory( new _ContactCategory(
icon: Icons.call, icon: Icons.call,
......
...@@ -133,7 +133,8 @@ class GridListDemo extends StatefulWidget { ...@@ -133,7 +133,8 @@ class GridListDemo extends StatefulWidget {
} }
class GridListDemoState extends State<GridListDemo> { class GridListDemoState extends State<GridListDemo> {
GridDemoTileStyle tileStyle = GridDemoTileStyle.twoLine; static final GlobalKey<ScrollableState> _scrollableKey = new GlobalKey<ScrollableState>();
GridDemoTileStyle _tileStyle = GridDemoTileStyle.twoLine;
List<Photo> photos = <Photo>[ List<Photo> photos = <Photo>[
new Photo( new Photo(
...@@ -200,7 +201,7 @@ class GridListDemoState extends State<GridListDemo> { ...@@ -200,7 +201,7 @@ class GridListDemoState extends State<GridListDemo> {
void changeTileStyle(GridDemoTileStyle value) { void changeTileStyle(GridDemoTileStyle value) {
setState(() { setState(() {
tileStyle = value; _tileStyle = value;
}); });
} }
...@@ -211,6 +212,7 @@ class GridListDemoState extends State<GridListDemo> { ...@@ -211,6 +212,7 @@ class GridListDemoState extends State<GridListDemo> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final Orientation orientation = MediaQuery.of(context).orientation; final Orientation orientation = MediaQuery.of(context).orientation;
return new Scaffold( return new Scaffold(
scrollableKey: _scrollableKey,
appBar: new AppBar( appBar: new AppBar(
title: new Text('Grid list'), title: new Text('Grid list'),
actions: <Widget>[ actions: <Widget>[
...@@ -237,6 +239,7 @@ class GridListDemoState extends State<GridListDemo> { ...@@ -237,6 +239,7 @@ class GridListDemoState extends State<GridListDemo> {
children: <Widget>[ children: <Widget>[
new Flexible( new Flexible(
child: new ScrollableGrid( child: new ScrollableGrid(
scrollableKey: _scrollableKey,
delegate: new FixedColumnCountGridDelegate( delegate: new FixedColumnCountGridDelegate(
columnCount: (orientation == Orientation.portrait) ? 2 : 3, columnCount: (orientation == Orientation.portrait) ? 2 : 3,
rowSpacing: 4.0, rowSpacing: 4.0,
...@@ -247,7 +250,7 @@ class GridListDemoState extends State<GridListDemo> { ...@@ -247,7 +250,7 @@ class GridListDemoState extends State<GridListDemo> {
children: photos.map((Photo photo) { children: photos.map((Photo photo) {
return new GridDemoPhotoItem( return new GridDemoPhotoItem(
photo: photo, photo: photo,
tileStyle: tileStyle, tileStyle: _tileStyle,
onBannerTap: (Photo photo) { onBannerTap: (Photo photo) {
setState(() { setState(() {
photo.isFavorite = !photo.isFavorite; photo.isFavorite = !photo.isFavorite;
......
...@@ -38,7 +38,8 @@ class LeaveBehindDemo extends StatefulWidget { ...@@ -38,7 +38,8 @@ class LeaveBehindDemo extends StatefulWidget {
} }
class LeaveBehindDemoState extends State<LeaveBehindDemo> { class LeaveBehindDemoState extends State<LeaveBehindDemo> {
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>(); static final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
static final GlobalKey<ScrollableState> _scrollableKey = new GlobalKey<ScrollableState>();
DismissDirection _dismissDirection = DismissDirection.horizontal; DismissDirection _dismissDirection = DismissDirection.horizontal;
List<LeaveBehindItem> leaveBehindItems; List<LeaveBehindItem> leaveBehindItems;
...@@ -131,6 +132,7 @@ class LeaveBehindDemoState extends State<LeaveBehindDemo> { ...@@ -131,6 +132,7 @@ class LeaveBehindDemoState extends State<LeaveBehindDemo> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return new Scaffold( return new Scaffold(
key: _scaffoldKey, key: _scaffoldKey,
scrollableKey: _scrollableKey,
appBar: new AppBar( appBar: new AppBar(
title: new Text('Swipe items to dismiss'), title: new Text('Swipe items to dismiss'),
actions: <Widget>[ actions: <Widget>[
...@@ -161,7 +163,10 @@ class LeaveBehindDemoState extends State<LeaveBehindDemo> { ...@@ -161,7 +163,10 @@ class LeaveBehindDemoState extends State<LeaveBehindDemo> {
) )
] ]
), ),
body: new Block(children: leaveBehindItems.map(buildItem).toList()) body: new Block(
scrollableKey: _scrollableKey,
children: leaveBehindItems.map(buildItem).toList()
)
); );
} }
} }
...@@ -14,7 +14,8 @@ class ListDemo extends StatefulWidget { ...@@ -14,7 +14,8 @@ class ListDemo extends StatefulWidget {
} }
class ListDemoState extends State<ListDemo> { class ListDemoState extends State<ListDemo> {
final GlobalKey<ScaffoldState> scaffoldKey = new GlobalKey<ScaffoldState>(); static final GlobalKey<ScaffoldState> scaffoldKey = new GlobalKey<ScaffoldState>();
static final GlobalKey<ScrollableState> _scrollableKey = new GlobalKey<ScrollableState>();
PersistentBottomSheetController<Null> _bottomSheet; PersistentBottomSheetController<Null> _bottomSheet;
MaterialListType _itemType = MaterialListType.threeLine; MaterialListType _itemType = MaterialListType.threeLine;
...@@ -171,6 +172,7 @@ class ListDemoState extends State<ListDemo> { ...@@ -171,6 +172,7 @@ class ListDemoState extends State<ListDemo> {
return new Scaffold( return new Scaffold(
key: scaffoldKey, key: scaffoldKey,
scrollableKey: _scrollableKey,
appBar: new AppBar( appBar: new AppBar(
title: new Text('Scrolling list\n$itemTypeText$layoutText'), title: new Text('Scrolling list\n$itemTypeText$layoutText'),
actions: <Widget>[ actions: <Widget>[
...@@ -193,6 +195,7 @@ class ListDemoState extends State<ListDemo> { ...@@ -193,6 +195,7 @@ class ListDemoState extends State<ListDemo> {
), ),
body: new Scrollbar( body: new Scrollbar(
child: new MaterialList( child: new MaterialList(
scrollableKey: _scrollableKey,
type: _itemType, type: _itemType,
padding: new EdgeInsets.symmetric(vertical: _dense ? 4.0 : 8.0), padding: new EdgeInsets.symmetric(vertical: _dense ? 4.0 : 8.0),
children: listItems children: listItems
......
...@@ -90,6 +90,7 @@ class OverscrollDemoState extends State<OverscrollDemo> { ...@@ -90,6 +90,7 @@ class OverscrollDemoState extends State<OverscrollDemo> {
return new Scaffold( return new Scaffold(
key: _scaffoldKey, key: _scaffoldKey,
scrollableKey: _scrollableKey,
appBar: new AppBar( appBar: new AppBar(
title: new Text('$indicatorTypeText'), title: new Text('$indicatorTypeText'),
actions: <Widget>[ actions: <Widget>[
......
...@@ -51,6 +51,8 @@ class PestoDemo extends StatefulWidget { ...@@ -51,6 +51,8 @@ class PestoDemo extends StatefulWidget {
class _PestoDemoState extends State<PestoDemo> { class _PestoDemoState extends State<PestoDemo> {
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>(); final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
static final GlobalKey<ScrollableState> _homeScrollableKey = new GlobalKey<ScrollableState>();
static final GlobalKey<ScrollableState> _favoritesScrollableKey = new GlobalKey<ScrollableState>();
final TextStyle favoritesMessageStyle = _textStyle(16.0); final TextStyle favoritesMessageStyle = _textStyle(16.0);
final TextStyle userStyle = _textStyle(12.0, FontWeight.bold); final TextStyle userStyle = _textStyle(12.0, FontWeight.bold);
final TextStyle emailStyle = _textStyle(12.0).copyWith(color: Colors.black54); final TextStyle emailStyle = _textStyle(12.0).copyWith(color: Colors.black54);
...@@ -61,6 +63,7 @@ class _PestoDemoState extends State<PestoDemo> { ...@@ -61,6 +63,7 @@ class _PestoDemoState extends State<PestoDemo> {
data: _kTheme, data: _kTheme,
child: new Scaffold( child: new Scaffold(
key: _scaffoldKey, key: _scaffoldKey,
scrollableKey: config.showFavorites ? _favoritesScrollableKey : _homeScrollableKey,
appBarBehavior: AppBarBehavior.under, appBarBehavior: AppBarBehavior.under,
appBar: _buildAppBar(context), appBar: _buildAppBar(context),
drawer: _buildDrawer(context), drawer: _buildDrawer(context),
...@@ -180,6 +183,7 @@ class _PestoDemoState extends State<PestoDemo> { ...@@ -180,6 +183,7 @@ class _PestoDemoState extends State<PestoDemo> {
} }
return new ScrollableGrid( return new ScrollableGrid(
scrollableKey: config.showFavorites ? _favoritesScrollableKey : _homeScrollableKey,
delegate: new MaxTileWidthGridDelegate( delegate: new MaxTileWidthGridDelegate(
maxTileWidth: 500.0, maxTileWidth: 500.0,
rowSpacing: 8.0, rowSpacing: 8.0,
...@@ -282,7 +286,8 @@ class _RecipePage extends StatefulWidget { ...@@ -282,7 +286,8 @@ class _RecipePage extends StatefulWidget {
} }
class _RecipePageState extends State<_RecipePage> { class _RecipePageState extends State<_RecipePage> {
static final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>(); final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
final GlobalKey<ScrollableState> _scrollableKey = new GlobalKey<ScrollableState>();
final TextStyle menuItemStyle = _textStyle(15.0).copyWith(color: Colors.black54, height: 24.0/15.0); final TextStyle menuItemStyle = _textStyle(15.0).copyWith(color: Colors.black54, height: 24.0/15.0);
double _getAppBarHeight(BuildContext context) => MediaQuery.of(context).size.height * 0.3; double _getAppBarHeight(BuildContext context) => MediaQuery.of(context).size.height * 0.3;
...@@ -291,6 +296,7 @@ class _RecipePageState extends State<_RecipePage> { ...@@ -291,6 +296,7 @@ class _RecipePageState extends State<_RecipePage> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return new Scaffold( return new Scaffold(
key: _scaffoldKey, key: _scaffoldKey,
scrollableKey: _scrollableKey,
appBarBehavior: AppBarBehavior.scroll, appBarBehavior: AppBarBehavior.scroll,
appBar: new AppBar( appBar: new AppBar(
expandedHeight: _getAppBarHeight(context), expandedHeight: _getAppBarHeight(context),
...@@ -347,6 +353,7 @@ class _RecipePageState extends State<_RecipePage> { ...@@ -347,6 +353,7 @@ class _RecipePageState extends State<_RecipePage> {
new ClampOverscrolls( new ClampOverscrolls(
value: true, value: true,
child: new ScrollableViewport( child: new ScrollableViewport(
scrollableKey: _scrollableKey,
child: new RepaintBoundary( child: new RepaintBoundary(
child: new Padding( child: new Padding(
padding: new EdgeInsets.only(top: appBarHeight), padding: new EdgeInsets.only(top: appBarHeight),
......
...@@ -293,6 +293,7 @@ class ShrineHome extends StatefulWidget { ...@@ -293,6 +293,7 @@ class ShrineHome extends StatefulWidget {
class _ShrineHomeState extends State<ShrineHome> { class _ShrineHomeState extends State<ShrineHome> {
static final GlobalKey<ScaffoldState> scaffoldKey = new GlobalKey<ScaffoldState>(debugLabel: 'Order page'); static final GlobalKey<ScaffoldState> scaffoldKey = new GlobalKey<ScaffoldState>(debugLabel: 'Order page');
static final GlobalKey<ScrollableState> scrollableKey = new GlobalKey<ScrollableState>();
static final GridDelegate gridDelegate = new ShrineGridDelegate(); static final GridDelegate gridDelegate = new ShrineGridDelegate();
void handleCompletedOrder(Order completedOrder) { void handleCompletedOrder(Order completedOrder) {
...@@ -323,9 +324,11 @@ class _ShrineHomeState extends State<ShrineHome> { ...@@ -323,9 +324,11 @@ class _ShrineHomeState extends State<ShrineHome> {
final Product featured = _products.firstWhere((Product product) => product.featureDescription != null); final Product featured = _products.firstWhere((Product product) => product.featureDescription != null);
return new ShrinePage( return new ShrinePage(
scaffoldKey: scaffoldKey, scaffoldKey: scaffoldKey,
scrollableKey: scrollableKey,
products: _products, products: _products,
shoppingCart: _shoppingCart, shoppingCart: _shoppingCart,
body: new ScrollableViewport( body: new ScrollableViewport(
scrollableKey: scrollableKey,
child: new RepaintBoundary( child: new RepaintBoundary(
child: new Column( child: new Column(
children: <Widget>[ children: <Widget>[
......
...@@ -137,6 +137,7 @@ class OrderPage extends StatefulWidget { ...@@ -137,6 +137,7 @@ class OrderPage extends StatefulWidget {
/// order to the shopping cart. /// order to the shopping cart.
class _OrderPageState extends State<OrderPage> { class _OrderPageState extends State<OrderPage> {
static final GlobalKey<ScaffoldState> scaffoldKey = new GlobalKey<ScaffoldState>(debugLabel: 'Order page'); static final GlobalKey<ScaffoldState> scaffoldKey = new GlobalKey<ScaffoldState>(debugLabel: 'Order page');
static final GlobalKey<ScrollableState> scrollableKey = new GlobalKey<ScrollableState>();
Order get currentOrder => ShrineOrderRoute.of(context).order; Order get currentOrder => ShrineOrderRoute.of(context).order;
...@@ -162,6 +163,7 @@ class _OrderPageState extends State<OrderPage> { ...@@ -162,6 +163,7 @@ class _OrderPageState extends State<OrderPage> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return new ShrinePage( return new ShrinePage(
scaffoldKey: scaffoldKey, scaffoldKey: scaffoldKey,
scrollableKey: scrollableKey,
products: config.products, products: config.products,
shoppingCart: config.shoppingCart, shoppingCart: config.shoppingCart,
floatingActionButton: new FloatingActionButton( floatingActionButton: new FloatingActionButton(
...@@ -180,6 +182,7 @@ class _OrderPageState extends State<OrderPage> { ...@@ -180,6 +182,7 @@ class _OrderPageState extends State<OrderPage> {
) )
), ),
body: new Block( body: new Block(
scrollableKey: scrollableKey,
children: <Widget>[ children: <Widget>[
new OrderItem( new OrderItem(
product: config.order.product, product: config.order.product,
......
...@@ -17,6 +17,7 @@ class ShrinePage extends StatefulWidget { ...@@ -17,6 +17,7 @@ class ShrinePage extends StatefulWidget {
ShrinePage({ ShrinePage({
Key key, Key key,
this.scaffoldKey, this.scaffoldKey,
this.scrollableKey,
this.body, this.body,
this.floatingActionButton, this.floatingActionButton,
this.products, this.products,
...@@ -27,6 +28,7 @@ class ShrinePage extends StatefulWidget { ...@@ -27,6 +28,7 @@ class ShrinePage extends StatefulWidget {
} }
final GlobalKey<ScaffoldState> scaffoldKey; final GlobalKey<ScaffoldState> scaffoldKey;
final GlobalKey<ScrollableState> scrollableKey;
final Widget body; final Widget body;
final Widget floatingActionButton; final Widget floatingActionButton;
final List<Product> products; final List<Product> products;
...@@ -86,6 +88,7 @@ class ShrinePageState extends State<ShrinePage> { ...@@ -86,6 +88,7 @@ class ShrinePageState extends State<ShrinePage> {
final ShrineTheme theme = ShrineTheme.of(context); final ShrineTheme theme = ShrineTheme.of(context);
return new Scaffold( return new Scaffold(
key: config.scaffoldKey, key: config.scaffoldKey,
scrollableKey: config.scrollableKey,
appBar: new AppBar( appBar: new AppBar(
elevation: _appBarElevation, elevation: _appBarElevation,
backgroundColor: theme.appBarBackgroundColor, backgroundColor: theme.appBarBackgroundColor,
......
...@@ -9,6 +9,7 @@ import 'package:flutter/material.dart'; ...@@ -9,6 +9,7 @@ import 'package:flutter/material.dart';
class _Page { class _Page {
_Page({ this.label }); _Page({ this.label });
final GlobalKey<ScrollableState> scrollableKey = new GlobalKey<ScrollableState>();
final String label; final String label;
String get id => label[0]; String get id => label[0];
} }
...@@ -111,14 +112,35 @@ class _CardDataItem extends StatelessWidget { ...@@ -111,14 +112,35 @@ class _CardDataItem extends StatelessWidget {
} }
} }
class TabsDemo extends StatelessWidget { class TabsDemo extends StatefulWidget {
TabsDemo({ Key key }) : super(key: key);
static const String routeName = '/tabs'; static const String routeName = '/tabs';
@override
_TabsDemoState createState() => new _TabsDemoState();
}
class _TabsDemoState extends State<TabsDemo> {
_Page _selectedPage;
@override
void initState() {
super.initState();
_selectedPage = _allPages.keys.first;
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return new TabBarSelection<_Page>( return new TabBarSelection<_Page>(
values: _allPages.keys.toList(), values: _allPages.keys.toList(),
onChanged: (_Page value) {
setState(() {
_selectedPage = value;
});
},
child: new Scaffold( child: new Scaffold(
scrollableKey: _selectedPage.scrollableKey,
appBar: new AppBar( appBar: new AppBar(
title: new Text('Tabs and scrolling'), title: new Text('Tabs and scrolling'),
bottom: new TabBar<_Page>( bottom: new TabBar<_Page>(
...@@ -130,6 +152,7 @@ class TabsDemo extends StatelessWidget { ...@@ -130,6 +152,7 @@ class TabsDemo extends StatelessWidget {
body: new TabBarView<_Page>( body: new TabBarView<_Page>(
children: _allPages.keys.map((_Page page) { children: _allPages.keys.map((_Page page) {
return new ScrollableList( return new ScrollableList(
scrollableKey: _selectedPage.scrollableKey,
padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0), padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0),
itemExtent: _CardDataItem.height, itemExtent: _CardDataItem.height,
children: _allPages[page].map((_CardData data) { children: _allPages[page].map((_CardData data) {
......
...@@ -97,7 +97,8 @@ class GalleryHome extends StatefulWidget { ...@@ -97,7 +97,8 @@ class GalleryHome extends StatefulWidget {
} }
class GalleryHomeState extends State<GalleryHome> { class GalleryHomeState extends State<GalleryHome> {
final Key _homeKey = new ValueKey<String>("Gallery Home"); static final Key _homeKey = new ValueKey<String>("Gallery Home");
static final GlobalKey<ScrollableState> _scrollableKey = new GlobalKey<ScrollableState>();
final List<Widget> _listItems = <Widget>[]; final List<Widget> _listItems = <Widget>[];
@override @override
...@@ -135,6 +136,7 @@ class GalleryHomeState extends State<GalleryHome> { ...@@ -135,6 +136,7 @@ class GalleryHomeState extends State<GalleryHome> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return new Scaffold( return new Scaffold(
key: _homeKey, key: _homeKey,
scrollableKey: _scrollableKey,
drawer: new GalleryDrawer( drawer: new GalleryDrawer(
useLightTheme: config.useLightTheme, useLightTheme: config.useLightTheme,
onThemeChanged: config.onThemeChanged, onThemeChanged: config.onThemeChanged,
...@@ -157,7 +159,10 @@ class GalleryHomeState extends State<GalleryHome> { ...@@ -157,7 +159,10 @@ class GalleryHomeState extends State<GalleryHome> {
) )
), ),
appBarBehavior: AppBarBehavior.under, appBarBehavior: AppBarBehavior.under,
body: new Block(children: _listItems) body: new Block(
scrollableKey: _scrollableKey,
children: _listItems
)
); );
} }
} }
...@@ -16,6 +16,7 @@ import 'icon_button.dart'; ...@@ -16,6 +16,7 @@ import 'icon_button.dart';
import 'icons.dart'; import 'icons.dart';
import 'material.dart'; import 'material.dart';
import 'snack_bar.dart'; import 'snack_bar.dart';
import 'theme.dart';
const double _kFloatingActionButtonMargin = 16.0; // TODO(hmuller): should be device dependent const double _kFloatingActionButtonMargin = 16.0; // TODO(hmuller): should be device dependent
const Duration _kFloatingActionButtonSegue = const Duration(milliseconds: 200); const Duration _kFloatingActionButtonSegue = const Duration(milliseconds: 200);
...@@ -52,12 +53,18 @@ enum _ScaffoldSlot { ...@@ -52,12 +53,18 @@ enum _ScaffoldSlot {
snackBar, snackBar,
floatingActionButton, floatingActionButton,
drawer, drawer,
statusBar,
} }
class _ScaffoldLayout extends MultiChildLayoutDelegate { class _ScaffoldLayout extends MultiChildLayoutDelegate {
_ScaffoldLayout({ this.padding, this.appBarBehavior: AppBarBehavior.anchor }); _ScaffoldLayout({
this.padding,
this.statusBarHeight,
this.appBarBehavior: AppBarBehavior.anchor
});
final EdgeInsets padding; final EdgeInsets padding;
final double statusBarHeight;
final AppBarBehavior appBarBehavior; final AppBarBehavior appBarBehavior;
@override @override
...@@ -120,6 +127,11 @@ class _ScaffoldLayout extends MultiChildLayoutDelegate { ...@@ -120,6 +127,11 @@ class _ScaffoldLayout extends MultiChildLayoutDelegate {
positionChild(_ScaffoldSlot.floatingActionButton, new Offset(fabX, fabY)); positionChild(_ScaffoldSlot.floatingActionButton, new Offset(fabX, fabY));
} }
if (hasChild(_ScaffoldSlot.statusBar)) {
layoutChild(_ScaffoldSlot.statusBar, fullWidthConstraints.tighten(height: statusBarHeight));
positionChild(_ScaffoldSlot.statusBar, Offset.zero);
}
if (hasChild(_ScaffoldSlot.drawer)) { if (hasChild(_ScaffoldSlot.drawer)) {
layoutChild(_ScaffoldSlot.drawer, new BoxConstraints.tight(size)); layoutChild(_ScaffoldSlot.drawer, new BoxConstraints.tight(size));
positionChild(_ScaffoldSlot.drawer, Offset.zero); positionChild(_ScaffoldSlot.drawer, Offset.zero);
...@@ -270,9 +282,7 @@ class Scaffold extends StatefulWidget { ...@@ -270,9 +282,7 @@ class Scaffold extends StatefulWidget {
this.scrollableKey, this.scrollableKey,
this.appBarBehavior: AppBarBehavior.anchor, this.appBarBehavior: AppBarBehavior.anchor,
this.resizeToAvoidBottomPadding: true this.resizeToAvoidBottomPadding: true
}) : super(key: key) { }) : super(key: key);
assert(scrollableKey != null ? (appBarBehavior != AppBarBehavior.anchor) : true);
}
/// An app bar to display at the top of the scaffold. /// An app bar to display at the top of the scaffold.
final AppBar appBar; final AppBar appBar;
...@@ -298,7 +308,7 @@ class Scaffold extends StatefulWidget { ...@@ -298,7 +308,7 @@ class Scaffold extends StatefulWidget {
/// ///
/// Used to control scroll-linked effects, such as the collapse of the /// Used to control scroll-linked effects, such as the collapse of the
/// [appBar]. /// [appBar].
final Key scrollableKey; final GlobalKey<ScrollableState> scrollableKey;
/// How the [appBar] should respond to scrolling. /// How the [appBar] should respond to scrolling.
/// ///
...@@ -663,6 +673,19 @@ class ScaffoldState extends State<Scaffold> { ...@@ -663,6 +673,19 @@ class ScaffoldState extends State<Scaffold> {
return appBar; return appBar;
} }
// On iOS, tapping the status bar scrolls the app's primary scrollable to the top.
void _handleStatusBarTap() {
ScrollableState scrollable = config.scrollableKey?.currentState;
if (scrollable == null || scrollable.scrollBehavior is! ExtentScrollBehavior)
return;
ExtentScrollBehavior behavior = scrollable.scrollBehavior;
scrollable.scrollTo(
behavior.minScrollOffset,
duration: const Duration(milliseconds: 300)
);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
EdgeInsets padding = MediaQuery.of(context).padding; EdgeInsets padding = MediaQuery.of(context).padding;
...@@ -729,6 +752,16 @@ class ScaffoldState extends State<Scaffold> { ...@@ -729,6 +752,16 @@ class ScaffoldState extends State<Scaffold> {
) )
)); ));
if (Theme.of(context).platform == TargetPlatform.iOS) {
children.add(new LayoutId(
id: _ScaffoldSlot.statusBar,
child: new GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: _handleStatusBarTap
)
));
}
if (config.drawer != null) { if (config.drawer != null) {
children.add(new LayoutId( children.add(new LayoutId(
id: _ScaffoldSlot.drawer, id: _ScaffoldSlot.drawer,
...@@ -739,12 +772,12 @@ class ScaffoldState extends State<Scaffold> { ...@@ -739,12 +772,12 @@ class ScaffoldState extends State<Scaffold> {
)); ));
} }
EdgeInsets appPadding = (config.appBarBehavior != AppBarBehavior.anchor) ? EdgeInsets appPadding = (config.appBarBehavior != AppBarBehavior.anchor) ? EdgeInsets.zero : padding;
EdgeInsets.zero : padding;
Widget application = new CustomMultiChildLayout( Widget application = new CustomMultiChildLayout(
children: children, children: children,
delegate: new _ScaffoldLayout( delegate: new _ScaffoldLayout(
padding: appPadding, padding: appPadding,
statusBarHeight: padding.top,
appBarBehavior: config.appBarBehavior appBarBehavior: config.appBarBehavior
) )
); );
......
...@@ -128,4 +128,72 @@ void main() { ...@@ -128,4 +128,72 @@ void main() {
RenderBox renderBox = tester.renderObject(find.byKey(appBarKey)); RenderBox renderBox = tester.renderObject(find.byKey(appBarKey));
expect(renderBox.size.height, equals(appBarHeight)); expect(renderBox.size.height, equals(appBarHeight));
}); });
testWidgets('Tapping the status bar scrolls to top on iOS', (WidgetTester tester) async {
final GlobalKey<ScrollableState> scrollableKey = new GlobalKey<ScrollableState>();
final Key appBarKey = new UniqueKey();
await tester.pumpWidget(
new MaterialApp(
theme: new ThemeData(platform: TargetPlatform.iOS),
home: new MediaQuery(
data: new MediaQueryData(padding: const EdgeInsets.only(top: 25.0)), // status bar
child: new Scaffold(
scrollableKey: scrollableKey,
appBar: new AppBar(
key: appBarKey,
title: new Text('Title')
),
body: new Block(
scrollableKey: scrollableKey,
initialScrollOffset: 500.0,
children: new List<Widget>.generate(20,
(int index) => new SizedBox(height: 100.0, child: new Text('$index'))
)
)
)
)
)
);
expect(scrollableKey.currentState.scrollOffset, equals(500.0));
await tester.tapAt(const Point(100.0, 10.0));
await tester.pump();
await tester.pump(new Duration(seconds: 1));
expect(scrollableKey.currentState.scrollOffset, equals(0.0));
});
testWidgets('Tapping the status bar does not scroll to top on Android', (WidgetTester tester) async {
final GlobalKey<ScrollableState> scrollableKey = new GlobalKey<ScrollableState>();
final Key appBarKey = new UniqueKey();
await tester.pumpWidget(
new MaterialApp(
theme: new ThemeData(platform: TargetPlatform.android),
home: new MediaQuery(
data: new MediaQueryData(padding: const EdgeInsets.only(top: 25.0)), // status bar
child: new Scaffold(
scrollableKey: scrollableKey,
appBar: new AppBar(
key: appBarKey,
title: new Text('Title')
),
body: new Block(
scrollableKey: scrollableKey,
initialScrollOffset: 500.0,
children: new List<Widget>.generate(20,
(int index) => new SizedBox(height: 100.0, child: new Text('$index'))
)
)
)
)
)
);
expect(scrollableKey.currentState.scrollOffset, equals(500.0));
await tester.tapAt(const Point(100.0, 10.0));
await tester.pump();
await tester.pump(new Duration(seconds: 1));
expect(scrollableKey.currentState.scrollOffset, equals(500.0));
});
} }
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