mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Rearrange the Pesto internals (#5466)
This commit is contained in:
parent
d3fd8ddd6f
commit
a010d6eb08
@ -6,76 +6,106 @@ import 'dart:math';
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class PestoDemo extends StatelessWidget {
|
||||||
|
PestoDemo({ Key key }) : super(key: key);
|
||||||
|
|
||||||
|
static const String routeName = '/pesto';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => new PestoHome();
|
||||||
|
}
|
||||||
|
|
||||||
const String _kUserName = 'Jonathan';
|
const String _kUserName = 'Jonathan';
|
||||||
const String _kUserEmail = 'jonathan@example.com';
|
const String _kUserEmail = 'jonathan@example.com';
|
||||||
const String _kUserImage = 'packages/flutter_gallery_assets/pesto/avatar.jpg';
|
const String _kUserImage = 'packages/flutter_gallery_assets/pesto/avatar.jpg';
|
||||||
|
|
||||||
const String _kSmallLogoImage = 'packages/flutter_gallery_assets/pesto/logo_small.png';
|
const String _kSmallLogoImage = 'packages/flutter_gallery_assets/pesto/logo_small.png';
|
||||||
const String _kMediumLogoImage = 'packages/flutter_gallery_assets/pesto/logo_medium.png';
|
const String _kMediumLogoImage = 'packages/flutter_gallery_assets/pesto/logo_medium.png';
|
||||||
|
const double _kAppBarHeight = 128.0;
|
||||||
|
const double _kRecipePageMaxWidth = 500.0;
|
||||||
|
|
||||||
|
final Set<Recipe> _favoriteRecipes = new Set<Recipe>();
|
||||||
|
|
||||||
final ThemeData _kTheme = new ThemeData(
|
final ThemeData _kTheme = new ThemeData(
|
||||||
brightness: Brightness.light,
|
brightness: Brightness.light,
|
||||||
primarySwatch: Colors.teal,
|
primarySwatch: Colors.teal,
|
||||||
accentColor: Colors.redAccent[200]
|
accentColor: Colors.redAccent[200]
|
||||||
);
|
);
|
||||||
const String _kFontFace = 'Raleway';
|
|
||||||
const double _kAppBarHeight = 128.0;
|
|
||||||
const double _kRecipePageMaxWidth = 500.0;
|
|
||||||
|
|
||||||
Set<Recipe> favoriteRecipes = new Set<Recipe>();
|
class PestoHome extends StatelessWidget {
|
||||||
|
static final GlobalKey<ScrollableState> scrollableKey = new GlobalKey<ScrollableState>();
|
||||||
// Helper for common Pesto text style properties.
|
|
||||||
TextStyle _textStyle(double size, [FontWeight fontWeight]) {
|
|
||||||
return new TextStyle(
|
|
||||||
inherit: false,
|
|
||||||
fontSize: size,
|
|
||||||
fontWeight: fontWeight,
|
|
||||||
fontFamily: 'Raleway',
|
|
||||||
color: Colors.black87,
|
|
||||||
textBaseline: TextBaseline.alphabetic
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class PestoDemo extends StatefulWidget {
|
|
||||||
PestoDemo({ Key key, this.showFavorites: false }) : super(key: key);
|
|
||||||
|
|
||||||
static const String routeName = '/pesto';
|
|
||||||
|
|
||||||
final bool showFavorites;
|
|
||||||
|
|
||||||
@override
|
|
||||||
_PestoDemoState createState() => new _PestoDemoState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _PestoDemoState extends State<PestoDemo> {
|
|
||||||
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 userStyle = _textStyle(12.0, FontWeight.bold);
|
|
||||||
final TextStyle emailStyle = _textStyle(12.0).copyWith(color: Colors.black54);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
return new RecipeGridPage(recipes: kPestoRecipes, scrollableKey: scrollableKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PestoFavorites extends StatelessWidget {
|
||||||
|
static final GlobalKey<ScrollableState> scrollableKey = new GlobalKey<ScrollableState>();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return new RecipeGridPage(recipes: _favoriteRecipes.toList(), scrollableKey: scrollableKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PestoStyle extends TextStyle {
|
||||||
|
const PestoStyle({
|
||||||
|
double fontSize: 12.0,
|
||||||
|
FontWeight fontWeight,
|
||||||
|
Color color: Colors.black87,
|
||||||
|
double height
|
||||||
|
}) : super(
|
||||||
|
inherit: false,
|
||||||
|
color: color,
|
||||||
|
fontFamily: 'Raleway',
|
||||||
|
fontSize: fontSize,
|
||||||
|
fontWeight: fontWeight,
|
||||||
|
textBaseline: TextBaseline.alphabetic,
|
||||||
|
height: height
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Displays a grid of recipe cards.
|
||||||
|
class RecipeGridPage extends StatefulWidget {
|
||||||
|
RecipeGridPage({ Key key, this.recipes, this.scrollableKey }) : super(key: key);
|
||||||
|
|
||||||
|
final List<Recipe> recipes;
|
||||||
|
final GlobalKey<ScrollableState> scrollableKey;
|
||||||
|
|
||||||
|
@override
|
||||||
|
_RecipeGridPageState createState() => new _RecipeGridPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RecipeGridPageState extends State<RecipeGridPage> {
|
||||||
|
final GlobalKey<ScaffoldState> scaffoldKey = new GlobalKey<ScaffoldState>();
|
||||||
|
final TextStyle favoritesMessageStyle = const PestoStyle(fontSize: 16.0);
|
||||||
|
final TextStyle userStyle = const PestoStyle(fontWeight: FontWeight.bold);
|
||||||
|
final TextStyle emailStyle = const PestoStyle(color: Colors.black54);
|
||||||
|
|
||||||
|
bool showFavorites = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final double statusBarHeight = MediaQuery.of(context).padding.top;
|
||||||
return new Theme(
|
return new Theme(
|
||||||
data: _kTheme,
|
data: _kTheme,
|
||||||
child: new Scaffold(
|
child: new Scaffold(
|
||||||
key: _scaffoldKey,
|
key: scaffoldKey,
|
||||||
scrollableKey: config.showFavorites ? _favoritesScrollableKey : _homeScrollableKey,
|
scrollableKey: config.scrollableKey,
|
||||||
appBarBehavior: AppBarBehavior.under,
|
appBarBehavior: AppBarBehavior.under,
|
||||||
appBar: _buildAppBar(context),
|
appBar: buildAppBar(context, statusBarHeight),
|
||||||
drawer: _buildDrawer(context),
|
drawer: buildDrawer(context),
|
||||||
floatingActionButton: new FloatingActionButton(
|
floatingActionButton: new FloatingActionButton(
|
||||||
child: new Icon(Icons.edit),
|
child: new Icon(Icons.edit),
|
||||||
onPressed: () { }
|
onPressed: () { }
|
||||||
),
|
),
|
||||||
body: _buildBody(context)
|
body: buildBody(context, statusBarHeight)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildAppBar(BuildContext context) {
|
Widget buildAppBar(BuildContext context, double statusBarHeight) {
|
||||||
final double statusBarHeight = MediaQuery.of(context).padding.top;
|
|
||||||
return new AppBar(
|
return new AppBar(
|
||||||
expandedHeight: _kAppBarHeight,
|
expandedHeight: _kAppBarHeight,
|
||||||
actions: <Widget>[
|
actions: <Widget>[
|
||||||
@ -83,7 +113,7 @@ class _PestoDemoState extends State<PestoDemo> {
|
|||||||
icon: new Icon(Icons.search),
|
icon: new Icon(Icons.search),
|
||||||
tooltip: 'Search',
|
tooltip: 'Search',
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_scaffoldKey.currentState.showSnackBar(new SnackBar(
|
scaffoldKey.currentState.showSnackBar(new SnackBar(
|
||||||
content: new Text('Not supported.')
|
content: new Text('Not supported.')
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@ -111,7 +141,7 @@ class _PestoDemoState extends State<PestoDemo> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildDrawer(BuildContext context) {
|
Widget buildDrawer(BuildContext context) {
|
||||||
return new Drawer(
|
return new Drawer(
|
||||||
child: new Block(
|
child: new Block(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
@ -139,19 +169,19 @@ class _PestoDemoState extends State<PestoDemo> {
|
|||||||
),
|
),
|
||||||
new DrawerItem(
|
new DrawerItem(
|
||||||
child: new Text('Home'),
|
child: new Text('Home'),
|
||||||
selected: !config.showFavorites,
|
selected: !showFavorites,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.popUntil(context, ModalRoute.withName('/pesto'));
|
Navigator.popUntil(context, ModalRoute.withName('/pesto'));
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
new DrawerItem(
|
new DrawerItem(
|
||||||
child: new Text('Favorites'),
|
child: new Text('Favorites'),
|
||||||
selected: config.showFavorites,
|
selected: showFavorites,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (config.showFavorites)
|
if (showFavorites)
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
else
|
else
|
||||||
_showFavorites(context);
|
showFavoritesPage(context);
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
new Divider(),
|
new Divider(),
|
||||||
@ -166,12 +196,10 @@ class _PestoDemoState extends State<PestoDemo> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildBody(BuildContext context) {
|
Widget buildBody(BuildContext context, double statusBarHeight) {
|
||||||
final double statusBarHeight = MediaQuery.of(context).padding.top;
|
|
||||||
List<Recipe> recipes = config.showFavorites ? favoriteRecipes.toList() : kRecipes;
|
|
||||||
final EdgeInsets padding = new EdgeInsets.fromLTRB(8.0, 8.0 + _kAppBarHeight + statusBarHeight, 8.0, 8.0);
|
final EdgeInsets padding = new EdgeInsets.fromLTRB(8.0, 8.0 + _kAppBarHeight + statusBarHeight, 8.0, 8.0);
|
||||||
|
|
||||||
if (config.showFavorites && recipes.isEmpty) {
|
if (config.recipes.isEmpty) {
|
||||||
return new Padding(
|
return new Padding(
|
||||||
padding: padding,
|
padding: padding,
|
||||||
child: new Text('Save your favorite recipes to see them here.', style: favoritesMessageStyle)
|
child: new Text('Save your favorite recipes to see them here.', style: favoritesMessageStyle)
|
||||||
@ -179,50 +207,48 @@ class _PestoDemoState extends State<PestoDemo> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return new ScrollableGrid(
|
return new ScrollableGrid(
|
||||||
scrollableKey: config.showFavorites ? _favoritesScrollableKey : _homeScrollableKey,
|
scrollableKey: config.scrollableKey,
|
||||||
delegate: new MaxTileWidthGridDelegate(
|
delegate: new MaxTileWidthGridDelegate(
|
||||||
maxTileWidth: 500.0,
|
maxTileWidth: _kRecipePageMaxWidth,
|
||||||
rowSpacing: 8.0,
|
rowSpacing: 8.0,
|
||||||
columnSpacing: 8.0,
|
columnSpacing: 8.0,
|
||||||
padding: padding
|
padding: padding
|
||||||
),
|
),
|
||||||
children: recipes.map(
|
children: config.recipes.map((Recipe recipe) {
|
||||||
(Recipe recipe) => new _RecipeCard(
|
return new RecipeCard(
|
||||||
recipe: recipe,
|
recipe: recipe,
|
||||||
onTap: () { _showRecipe(context, recipe); }
|
onTap: () { showRecipePage(context, recipe); }
|
||||||
)
|
);
|
||||||
)
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showFavorites(BuildContext context) {
|
void showFavoritesPage(BuildContext context) {
|
||||||
Navigator.push(context, new MaterialPageRoute<Null>(
|
Navigator.push(context, new MaterialPageRoute<Null>(
|
||||||
settings: const RouteSettings(name: "/pesto/favorites"),
|
settings: const RouteSettings(name: "/pesto/favorites"),
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) => new PestoFavorites()
|
||||||
return new PestoDemo(showFavorites: true);
|
|
||||||
}
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showRecipe(BuildContext context, Recipe recipe) {
|
void showRecipePage(BuildContext context, Recipe recipe) {
|
||||||
Navigator.push(context, new MaterialPageRoute<Null>(
|
Navigator.push(context, new MaterialPageRoute<Null>(
|
||||||
settings: const RouteSettings(name: "/pesto/recipe"),
|
settings: const RouteSettings(name: "/pesto/recipe"),
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return new Theme(
|
return new Theme(
|
||||||
data: _kTheme,
|
data: _kTheme,
|
||||||
child: new _RecipePage(recipe: recipe)
|
child: new RecipePage(recipe: recipe)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A short recipe card to be displayed in a grid.
|
// A card with the recipe's image, author, and title.
|
||||||
class _RecipeCard extends StatelessWidget {
|
class RecipeCard extends StatelessWidget {
|
||||||
final TextStyle titleStyle = _textStyle(24.0, FontWeight.w600);
|
final TextStyle titleStyle = const PestoStyle(fontSize: 24.0, fontWeight: FontWeight.w600);
|
||||||
final TextStyle authorStyle = _textStyle(12.0, FontWeight.w500).copyWith(color: Colors.black54);
|
final TextStyle authorStyle = const PestoStyle(fontWeight: FontWeight.w500, color: Colors.black54);
|
||||||
|
|
||||||
_RecipeCard({ Key key, this.recipe, this.onTap }) : super(key: key);
|
RecipeCard({ Key key, this.recipe, this.onTap }) : super(key: key);
|
||||||
|
|
||||||
final Recipe recipe;
|
final Recipe recipe;
|
||||||
final VoidCallback onTap;
|
final VoidCallback onTap;
|
||||||
@ -270,10 +296,9 @@ class _RecipeCard extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A page displaying a single recipe. Includes the recipe sheet with a
|
// Displays one recipe. Includes the recipe sheet with a background image.
|
||||||
/// background image.
|
class RecipePage extends StatefulWidget {
|
||||||
class _RecipePage extends StatefulWidget {
|
RecipePage({ Key key, this.recipe }) : super(key: key);
|
||||||
_RecipePage({ Key key, this.recipe }) : super(key: key);
|
|
||||||
|
|
||||||
final Recipe recipe;
|
final Recipe recipe;
|
||||||
|
|
||||||
@ -281,10 +306,10 @@ class _RecipePage extends StatefulWidget {
|
|||||||
_RecipePageState createState() => new _RecipePageState();
|
_RecipePageState createState() => new _RecipePageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _RecipePageState extends State<_RecipePage> {
|
class _RecipePageState extends State<RecipePage> {
|
||||||
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
|
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
|
||||||
final GlobalKey<ScrollableState> _scrollableKey = new GlobalKey<ScrollableState>();
|
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 = new PestoStyle(fontSize: 15.0, 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;
|
||||||
|
|
||||||
@ -326,7 +351,7 @@ class _RecipePageState extends State<_RecipePage> {
|
|||||||
// adjusts based on the size of the screen. If the recipe sheet touches
|
// adjusts based on the size of the screen. If the recipe sheet touches
|
||||||
// the edge of the screen, use a slightly different layout.
|
// the edge of the screen, use a slightly different layout.
|
||||||
Widget _buildContainer(BuildContext context) {
|
Widget _buildContainer(BuildContext context) {
|
||||||
final bool isFavorite = favoriteRecipes.contains(config.recipe);
|
final bool isFavorite = _favoriteRecipes.contains(config.recipe);
|
||||||
final Size screenSize = MediaQuery.of(context).size;
|
final Size screenSize = MediaQuery.of(context).size;
|
||||||
final bool fullWidth = (screenSize.width < _kRecipePageMaxWidth);
|
final bool fullWidth = (screenSize.width < _kRecipePageMaxWidth);
|
||||||
final double appBarHeight = _getAppBarHeight(context);
|
final double appBarHeight = _getAppBarHeight(context);
|
||||||
@ -359,7 +384,7 @@ class _RecipePageState extends State<_RecipePage> {
|
|||||||
padding: new EdgeInsets.only(top: fabHalfSize),
|
padding: new EdgeInsets.only(top: fabHalfSize),
|
||||||
child: new SizedBox(
|
child: new SizedBox(
|
||||||
width: fullWidth ? null : _kRecipePageMaxWidth,
|
width: fullWidth ? null : _kRecipePageMaxWidth,
|
||||||
child: new _RecipeSheet(recipe: config.recipe)
|
child: new RecipeSheet(recipe: config.recipe)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
new Positioned(
|
new Positioned(
|
||||||
@ -395,23 +420,23 @@ class _RecipePageState extends State<_RecipePage> {
|
|||||||
|
|
||||||
void _toggleFavorite() {
|
void _toggleFavorite() {
|
||||||
setState(() {
|
setState(() {
|
||||||
if (favoriteRecipes.contains(config.recipe))
|
if (_favoriteRecipes.contains(config.recipe))
|
||||||
favoriteRecipes.remove(config.recipe);
|
_favoriteRecipes.remove(config.recipe);
|
||||||
else
|
else
|
||||||
favoriteRecipes.add(config.recipe);
|
_favoriteRecipes.add(config.recipe);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The sheet with the recipe name and instructions.
|
/// Displays the recipe's name and instructions.
|
||||||
class _RecipeSheet extends StatelessWidget {
|
class RecipeSheet extends StatelessWidget {
|
||||||
final TextStyle titleStyle = _textStyle(34.0);
|
final TextStyle titleStyle = const PestoStyle(fontSize: 34.0);
|
||||||
final TextStyle descriptionStyle = _textStyle(15.0).copyWith(color: Colors.black54, height: 24.0/15.0);
|
final TextStyle descriptionStyle = const PestoStyle(fontSize: 15.0, color: Colors.black54, height: 24.0/15.0);
|
||||||
final TextStyle itemStyle = _textStyle(15.0).copyWith(height: 24.0/15.0);
|
final TextStyle itemStyle = const PestoStyle(fontSize: 15.0, height: 24.0/15.0);
|
||||||
final TextStyle itemAmountStyle = _textStyle(15.0).copyWith(color: _kTheme.primaryColor, height: 24.0/15.0);
|
final TextStyle itemAmountStyle = new PestoStyle(fontSize: 15.0, color: _kTheme.primaryColor, height: 24.0/15.0);
|
||||||
final TextStyle headingStyle = _textStyle(15.0).copyWith(fontSize: 16.0, fontWeight: FontWeight.bold, height: 24.0/15.0);
|
final TextStyle headingStyle = const PestoStyle(fontSize: 16.0, fontWeight: FontWeight.bold, height: 24.0/15.0);
|
||||||
|
|
||||||
_RecipeSheet({ Key key, this.recipe }) : super(key: key);
|
RecipeSheet({ Key key, this.recipe }) : super(key: key);
|
||||||
|
|
||||||
final Recipe recipe;
|
final Recipe recipe;
|
||||||
|
|
||||||
@ -501,8 +526,6 @@ class _RecipeSheet extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Data models for the UI.
|
|
||||||
|
|
||||||
class Recipe {
|
class Recipe {
|
||||||
const Recipe({
|
const Recipe({
|
||||||
this.name,
|
this.name,
|
||||||
@ -537,7 +560,7 @@ class RecipeStep {
|
|||||||
final String description;
|
final String description;
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<Recipe> kRecipes = <Recipe>[
|
final List<Recipe> kPestoRecipes = <Recipe>[
|
||||||
const Recipe(
|
const Recipe(
|
||||||
name: 'Pesto Bruchetta',
|
name: 'Pesto Bruchetta',
|
||||||
author: 'Peter Carlsson',
|
author: 'Peter Carlsson',
|
||||||
|
Loading…
Reference in New Issue
Block a user