// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import ' package:flutter_web/material.dart ' ;
import ' package:flutter_web/rendering.dart ' ;
class PestoDemo extends StatelessWidget {
const PestoDemo ( { Key key } ) : super ( key: key ) ;
static const String routeName = ' /pesto ' ;
@ override
Widget build ( BuildContext context ) = > PestoHome ( ) ;
}
const String _kSmallLogoImage = ' logos/pesto/logo_small.png ' ;
const double _kAppBarHeight = 128.0 ;
const double _kFabHalfSize =
28.0 ; // TODO(mpcomplete): needs to adapt to screen size
const double _kRecipePageMaxWidth = 500.0 ;
final Set < Recipe > _favoriteRecipes = Set < Recipe > ( ) ;
final ThemeData _kTheme = ThemeData (
brightness: Brightness . light ,
primarySwatch: Colors . teal ,
accentColor: Colors . redAccent ,
) ;
class PestoHome extends StatelessWidget {
@ override
Widget build ( BuildContext context ) {
return const RecipeGridPage ( recipes: kPestoRecipes ) ;
}
}
class PestoFavorites extends StatelessWidget {
@ override
Widget build ( BuildContext context ) {
return RecipeGridPage ( recipes: _favoriteRecipes . toList ( ) ) ;
}
}
class PestoStyle extends TextStyle {
const PestoStyle ( {
double fontSize = 12.0 ,
FontWeight fontWeight ,
Color color = Colors . black87 ,
double letterSpacing ,
double height ,
} ) : super (
inherit: false ,
color: color ,
fontFamily: ' Raleway ' ,
fontSize: fontSize ,
fontWeight: fontWeight ,
textBaseline: TextBaseline . alphabetic ,
letterSpacing: letterSpacing ,
height: height ,
) ;
}
// Displays a grid of recipe cards.
class RecipeGridPage extends StatefulWidget {
const RecipeGridPage ( { Key key , this . recipes } ) : super ( key: key ) ;
final List < Recipe > recipes ;
@ override
_RecipeGridPageState createState ( ) = > _RecipeGridPageState ( ) ;
}
class _RecipeGridPageState extends State < RecipeGridPage > {
final GlobalKey < ScaffoldState > scaffoldKey = GlobalKey < ScaffoldState > ( ) ;
@ override
Widget build ( BuildContext context ) {
final double statusBarHeight = MediaQuery . of ( context ) . padding . top ;
return Theme (
data: _kTheme . copyWith ( platform: Theme . of ( context ) . platform ) ,
child: Scaffold (
key: scaffoldKey ,
floatingActionButton: FloatingActionButton (
child: const Icon ( Icons . edit ) ,
onPressed: ( ) {
scaffoldKey . currentState . showSnackBar ( const SnackBar (
content: Text ( ' Not supported. ' ) ,
) ) ;
} ,
) ,
body: CustomScrollView (
semanticChildCount: widget . recipes . length ,
slivers: < Widget > [
_buildAppBar ( context , statusBarHeight ) ,
_buildBody ( context , statusBarHeight ) ,
] ,
) ,
) ,
) ;
}
Widget _buildAppBar ( BuildContext context , double statusBarHeight ) {
return SliverAppBar (
pinned: true ,
expandedHeight: _kAppBarHeight ,
actions: < Widget > [
IconButton (
icon: const Icon ( Icons . search ) ,
tooltip: ' Search ' ,
onPressed: ( ) {
scaffoldKey . currentState . showSnackBar ( const SnackBar (
content: Text ( ' Not supported. ' ) ,
) ) ;
} ,
) ,
] ,
flexibleSpace: LayoutBuilder (
builder: ( BuildContext context , BoxConstraints constraints ) {
final Size size = constraints . biggest ;
final double appBarHeight = size . height - statusBarHeight ;
final double t = ( appBarHeight - kToolbarHeight ) /
( _kAppBarHeight - kToolbarHeight ) ;
final double extraPadding =
Tween < double > ( begin: 10.0 , end: 24.0 ) . transform ( t ) ;
final double logoHeight = appBarHeight - 1.5 * extraPadding ;
return Padding (
padding: EdgeInsets . only (
top: statusBarHeight + 0.5 * extraPadding ,
bottom: extraPadding ,
) ,
child: Center (
child: PestoLogo ( height: logoHeight , t: t . clamp ( 0.0 , 1.0 ) ) ) ,
) ;
} ,
) ,
) ;
}
Widget _buildBody ( BuildContext context , double statusBarHeight ) {
final EdgeInsets mediaPadding = MediaQuery . of ( context ) . padding ;
final EdgeInsets padding = EdgeInsets . only (
top: 8.0 ,
left: 8.0 + mediaPadding . left ,
right: 8.0 + mediaPadding . right ,
bottom: 8.0 ) ;
return SliverPadding (
padding: padding ,
sliver: SliverGrid (
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent (
maxCrossAxisExtent: _kRecipePageMaxWidth ,
crossAxisSpacing: 8.0 ,
mainAxisSpacing: 8.0 ,
) ,
delegate: SliverChildBuilderDelegate (
( BuildContext context , int index ) {
final Recipe recipe = widget . recipes [ index ] ;
return RecipeCard (
recipe: recipe ,
onTap: ( ) {
showRecipePage ( context , recipe ) ;
} ,
) ;
} ,
childCount: widget . recipes . length ,
) ,
) ,
) ;
}
void showFavoritesPage ( BuildContext context ) {
Navigator . push (
context ,
MaterialPageRoute < void > (
settings: const RouteSettings ( name: ' /pesto/favorites ' ) ,
builder: ( BuildContext context ) = > PestoFavorites ( ) ,
) ) ;
}
void showRecipePage ( BuildContext context , Recipe recipe ) {
Navigator . push (
context ,
MaterialPageRoute < void > (
settings: const RouteSettings ( name: ' /pesto/recipe ' ) ,
builder: ( BuildContext context ) {
return Theme (
data: _kTheme . copyWith ( platform: Theme . of ( context ) . platform ) ,
child: RecipePage ( recipe: recipe ) ,
) ;
} ,
) ) ;
}
}
class PestoLogo extends StatefulWidget {
const PestoLogo ( { this . height , this . t } ) ;
final double height ;
final double t ;
@ override
_PestoLogoState createState ( ) = > _PestoLogoState ( ) ;
}
class _PestoLogoState extends State < PestoLogo > {
// Native sizes for logo and its image/text components.
static const double kLogoHeight = 162.0 ;
static const double kLogoWidth = 220.0 ;
static const double kImageHeight = 108.0 ;
static const double kTextHeight = 48.0 ;
final TextStyle titleStyle = const PestoStyle (
fontSize: kTextHeight ,
fontWeight: FontWeight . w900 ,
color: Colors . white ,
letterSpacing: 3.0 ) ;
final RectTween _textRectTween = RectTween (
begin: Rect . fromLTWH ( 0.0 , kLogoHeight , kLogoWidth , kTextHeight ) ,
end: Rect . fromLTWH ( 0.0 , kImageHeight , kLogoWidth , kTextHeight ) ) ;
final Curve _textOpacity = const Interval ( 0.4 , 1.0 , curve: Curves . easeInOut ) ;
final RectTween _imageRectTween = RectTween (
begin: Rect . fromLTWH ( 0.0 , 0.0 , kLogoWidth , kLogoHeight ) ,
end: Rect . fromLTWH ( 0.0 , 0.0 , kLogoWidth , kImageHeight ) ,
) ;
@ override
Widget build ( BuildContext context ) {
return Semantics (
namesRoute: true ,
child: Transform (
transform: Matrix4 . identity ( ) . . scale ( widget . height / kLogoHeight ) ,
alignment: Alignment . topCenter ,
child: SizedBox (
width: kLogoWidth ,
child: Stack (
overflow: Overflow . visible ,
children: < Widget > [
Positioned . fromRect (
rect: _imageRectTween . lerp ( widget . t ) ,
child: Image . asset (
' $ _kSmallLogoImage ' ,
fit: BoxFit . contain ,
) ,
) ,
Positioned . fromRect (
rect: _textRectTween . lerp ( widget . t ) ,
child: Opacity (
opacity: _textOpacity . transform ( widget . t ) ,
child: Text ( ' PESTO ' ,
style: titleStyle , textAlign: TextAlign . center ) ,
) ,
) ,
] ,
) ,
) ,
) ,
) ;
}
}
// A card with the recipe's image, author, and title.
class RecipeCard extends StatelessWidget {
const RecipeCard ( { Key key , this . recipe , this . onTap } ) : super ( key: key ) ;
final Recipe recipe ;
final VoidCallback onTap ;
TextStyle get titleStyle = >
const PestoStyle ( fontSize: 24.0 , fontWeight: FontWeight . w600 ) ;
TextStyle get authorStyle = >
const PestoStyle ( fontWeight: FontWeight . w500 , color: Colors . black54 ) ;
@ override
Widget build ( BuildContext context ) {
return GestureDetector (
onTap: onTap ,
child: Card (
child: Column (
crossAxisAlignment: CrossAxisAlignment . start ,
children: < Widget > [
Hero (
tag: ' ${ recipe . imagePath } ' ,
child: AspectRatio (
aspectRatio: 4.0 / 3.0 ,
child: Image . asset (
' ${ recipe . imagePath } ' ,
package: recipe . imagePackage ,
fit: BoxFit . cover ,
semanticLabel: recipe . name ,
) ,
) ,
) ,
Expanded (
child: Row (
children: < Widget > [
Padding (
padding: const EdgeInsets . all ( 16.0 ) ,
child: Image . asset (
' ${ recipe . ingredientsImagePath } ' ,
package: recipe . ingredientsImagePackage ,
width: 48.0 ,
height: 48.0 ,
) ,
) ,
Expanded (
child: Column (
crossAxisAlignment: CrossAxisAlignment . start ,
mainAxisAlignment: MainAxisAlignment . center ,
children: < Widget > [
Text ( recipe . name ,
style: titleStyle ,
softWrap: false ,
overflow: TextOverflow . ellipsis ) ,
Text ( recipe . author , style: authorStyle ) ,
] ,
) ,
) ,
] ,
) ,
) ,
] ,
) ,
) ,
) ;
}
}
// Displays one recipe. Includes the recipe sheet with a background image.
class RecipePage extends StatefulWidget {
const RecipePage ( { Key key , this . recipe } ) : super ( key: key ) ;
final Recipe recipe ;
@ override
_RecipePageState createState ( ) = > _RecipePageState ( ) ;
}
class _RecipePageState extends State < RecipePage > {
final GlobalKey < ScaffoldState > _scaffoldKey = GlobalKey < ScaffoldState > ( ) ;
final TextStyle menuItemStyle = const PestoStyle (
fontSize: 15.0 , color: Colors . black54 , height: 24.0 / 15.0 ) ;
double _getAppBarHeight ( BuildContext context ) = >
MediaQuery . of ( context ) . size . height * 0.3 ;
@ override
Widget build ( BuildContext context ) {
// The full page content with the recipe's image behind it. This
// adjusts based on the size of the screen. If the recipe sheet touches
// the edge of the screen, use a slightly different layout.
final double appBarHeight = _getAppBarHeight ( context ) ;
final Size screenSize = MediaQuery . of ( context ) . size ;
final bool fullWidth = screenSize . width < _kRecipePageMaxWidth ;
final bool isFavorite = _favoriteRecipes . contains ( widget . recipe ) ;
return Scaffold (
key: _scaffoldKey ,
body: Stack (
children: < Widget > [
Positioned (
top: 0.0 ,
left: 0.0 ,
right: 0.0 ,
height: appBarHeight + _kFabHalfSize ,
child: Hero (
tag: ' ${ widget . recipe . imagePath } ' ,
child: Image . asset (
' ${ widget . recipe . imagePath } ' ,
package: widget . recipe . imagePackage ,
fit: fullWidth ? BoxFit . fitWidth : BoxFit . cover ,
) ,
) ,
) ,
CustomScrollView (
slivers: < Widget > [
SliverAppBar (
expandedHeight: appBarHeight - _kFabHalfSize ,
backgroundColor: Colors . transparent ,
actions: < Widget > [
PopupMenuButton < String > (
onSelected: ( String item ) { } ,
itemBuilder: ( BuildContext context ) = >
< PopupMenuItem < String > > [
_buildMenuItem ( Icons . share , ' Tweet recipe ' ) ,
_buildMenuItem ( Icons . email , ' Email recipe ' ) ,
_buildMenuItem ( Icons . message , ' Message recipe ' ) ,
_buildMenuItem ( Icons . people , ' Share on Facebook ' ) ,
] ,
) ,
] ,
flexibleSpace: const FlexibleSpaceBar (
background: DecoratedBox (
decoration: BoxDecoration (
gradient: LinearGradient (
begin: Alignment ( 0.0 , - 1.0 ) ,
end: Alignment ( 0.0 , - 0.2 ) ,
colors: < Color > [ Color ( 0x60000000 ) , Color ( 0x00000000 ) ] ,
) ,
) ,
) ,
) ,
) ,
SliverToBoxAdapter (
child: Stack (
children: < Widget > [
Container (
padding: const EdgeInsets . only ( top: _kFabHalfSize ) ,
width: fullWidth ? null : _kRecipePageMaxWidth ,
child: RecipeSheet ( recipe: widget . recipe ) ,
) ,
Positioned (
right: 16.0 ,
child: FloatingActionButton (
child: Icon (
isFavorite ? Icons . favorite : Icons . favorite_border ) ,
onPressed: _toggleFavorite ,
) ,
) ,
] ,
) ) ,
] ,
) ,
] ,
) ,
) ;
}
PopupMenuItem < String > _buildMenuItem ( IconData icon , String label ) {
return PopupMenuItem < String > (
child: Row (
children: < Widget > [
Padding (
padding: const EdgeInsets . only ( right: 24.0 ) ,
child: Icon ( icon , color: Colors . black54 ) ) ,
Text ( label , style: menuItemStyle ) ,
] ,
) ,
) ;
}
void _toggleFavorite ( ) {
setState ( ( ) {
if ( _favoriteRecipes . contains ( widget . recipe ) )
_favoriteRecipes . remove ( widget . recipe ) ;
else
_favoriteRecipes . add ( widget . recipe ) ;
} ) ;
}
}
/// Displays the recipe's name and instructions.
class RecipeSheet extends StatelessWidget {
RecipeSheet ( { Key key , this . recipe } ) : super ( key: key ) ;
final TextStyle titleStyle = const PestoStyle ( fontSize: 34.0 ) ;
final TextStyle descriptionStyle = const PestoStyle (
fontSize: 15.0 , color: Colors . black54 , height: 24.0 / 15.0 ) ;
final TextStyle itemStyle =
const PestoStyle ( fontSize: 15.0 , height: 24.0 / 15.0 ) ;
final TextStyle itemAmountStyle = PestoStyle (
fontSize: 15.0 , color: _kTheme . primaryColor , height: 24.0 / 15.0 ) ;
final TextStyle headingStyle = const PestoStyle (
fontSize: 16.0 , fontWeight: FontWeight . bold , height: 24.0 / 15.0 ) ;
final Recipe recipe ;
@ override
Widget build ( BuildContext context ) {
return Material (
child: SafeArea (
top: false ,
bottom: false ,
child: Padding (
padding: const EdgeInsets . symmetric ( horizontal: 16.0 , vertical: 40.0 ) ,
child: Table (
columnWidths: const < int , TableColumnWidth > {
0 : FixedColumnWidth ( 64.0 )
} ,
children: < TableRow > [
TableRow ( children: < Widget > [
TableCell (
verticalAlignment: TableCellVerticalAlignment . middle ,
child: Image . asset ( ' ${ recipe . ingredientsImagePath } ' ,
package: recipe . ingredientsImagePackage ,
width: 32.0 ,
height: 32.0 ,
alignment: Alignment . centerLeft ,
fit: BoxFit . scaleDown ) ) ,
TableCell (
verticalAlignment: TableCellVerticalAlignment . middle ,
child: Text ( recipe . name , style: titleStyle ) ) ,
] ) ,
TableRow ( children: < Widget > [
const SizedBox ( ) ,
Padding (
padding: const EdgeInsets . only ( top: 8.0 , bottom: 4.0 ) ,
child: Text ( recipe . description , style: descriptionStyle ) ) ,
] ) ,
TableRow ( children: < Widget > [
const SizedBox ( ) ,
Padding (
padding: const EdgeInsets . only ( top: 24.0 , bottom: 4.0 ) ,
child: Text ( ' Ingredients ' , style: headingStyle ) ) ,
] ) ,
]
. . addAll ( recipe . ingredients
. map < TableRow > ( ( RecipeIngredient ingredient ) {
return _buildItemRow ( ingredient . amount , ingredient . description ) ;
} ) )
. . add ( TableRow ( children: < Widget > [
const SizedBox ( ) ,
Padding (
padding: const EdgeInsets . only ( top: 24.0 , bottom: 4.0 ) ,
child: Text ( ' Steps ' , style: headingStyle ) ) ,
] ) )
. . addAll ( recipe . steps . map < TableRow > ( ( RecipeStep step ) {
return _buildItemRow ( step . duration ? ? ' ' , step . description ) ;
} ) ) ,
) ,
) ,
) ,
) ;
}
TableRow _buildItemRow ( String left , String right ) {
return TableRow (
children: < Widget > [
Padding (
padding: const EdgeInsets . symmetric ( vertical: 4.0 ) ,
child: Text ( left , style: itemAmountStyle ) ,
) ,
Padding (
padding: const EdgeInsets . symmetric ( vertical: 4.0 ) ,
child: Text ( right , style: itemStyle ) ,
) ,
] ,
) ;
}
}
class Recipe {
const Recipe (
{ this . name ,
this . author ,
this . description ,
this . imagePath ,
this . imagePackage ,
this . ingredientsImagePath ,
this . ingredientsImagePackage ,
this . ingredients ,
this . steps } ) ;
final String name ;
final String author ;
final String description ;
final String imagePath ;
final String imagePackage ;
final String ingredientsImagePath ;
final String ingredientsImagePackage ;
final List < RecipeIngredient > ingredients ;
final List < RecipeStep > steps ;
}
class RecipeIngredient {
const RecipeIngredient ( { this . amount , this . description } ) ;
final String amount ;
final String description ;
}
class RecipeStep {
const RecipeStep ( { this . duration , this . description } ) ;
final String duration ;
final String description ;
}
const List < Recipe > kPestoRecipes = < Recipe > [
Recipe (
name: ' Roasted Chicken ' ,
author: ' Peter Carlsson ' ,
ingredientsImagePath: ' food/icons/main.png ' ,
description:
' The perfect dish to welcome your family and friends with on a crisp autumn night. Pair with roasted veggies to truly impress them. ' ,
imagePath: ' food/roasted_chicken.png ' ,
ingredients: < RecipeIngredient > [
RecipeIngredient ( amount: ' 1 whole ' , description: ' Chicken ' ) ,
RecipeIngredient ( amount: ' 1/2 cup ' , description: ' Butter ' ) ,
RecipeIngredient ( amount: ' 1 tbsp ' , description: ' Onion powder ' ) ,
RecipeIngredient ( amount: ' 1 tbsp ' , description: ' Freshly ground pepper ' ) ,
RecipeIngredient ( amount: ' 1 tsp ' , description: ' Salt ' ) ,
] ,
steps: < RecipeStep > [
RecipeStep ( duration: ' 1 min ' , description: ' Put in oven ' ) ,
RecipeStep ( duration: ' 1hr 45 min ' , description: ' Cook ' ) ,
] ,
) ,
Recipe (
name: ' Chopped Beet Leaves ' ,
author: ' Trevor Hansen ' ,
ingredientsImagePath: ' food/icons/veggie.png ' ,
description:
' This vegetable has more to offer than just its root. Beet greens can be tossed into a salad to add some variety or sauteed on its own with some oil and garlic. ' ,
imagePath: ' food/chopped_beet_leaves.png ' ,
ingredients: < RecipeIngredient > [
RecipeIngredient ( amount: ' 3 cups ' , description: ' Beet greens ' ) ,
] ,
steps: < RecipeStep > [
RecipeStep ( duration: ' 5 min ' , description: ' Chop ' ) ,
] ,
) ,
Recipe (
name: ' Pesto Pasta ' ,
author: ' Ali Connors ' ,
ingredientsImagePath: ' food/icons/main.png ' ,
description:
' With this pesto recipe, you can quickly whip up a meal to satisfy your savory needs. And if you \' re feeling festive, you can add bacon to taste. ' ,
imagePath: ' food/pesto_pasta.png ' ,
ingredients: < RecipeIngredient > [
RecipeIngredient ( amount: ' 1/4 cup ' , description: ' Pasta ' ) ,
RecipeIngredient ( amount: ' 2 cups ' , description: ' Fresh basil leaves ' ) ,
RecipeIngredient ( amount: ' 1/2 cup ' , description: ' Parmesan cheese ' ) ,
RecipeIngredient (
amount: ' 1/2 cup ' , description: ' Extra virgin olive oil ' ) ,
RecipeIngredient ( amount: ' 1/3 cup ' , description: ' Pine nuts ' ) ,
RecipeIngredient ( amount: ' 1/4 cup ' , description: ' Lemon juice ' ) ,
RecipeIngredient ( amount: ' 3 cloves ' , description: ' Garlic ' ) ,
RecipeIngredient ( amount: ' 1/4 tsp ' , description: ' Salt ' ) ,
RecipeIngredient ( amount: ' 1/8 tsp ' , description: ' Pepper ' ) ,
RecipeIngredient ( amount: ' 3 lbs ' , description: ' Bacon ' ) ,
] ,
steps: < RecipeStep > [
RecipeStep ( duration: ' 15 min ' , description: ' Blend ' ) ,
] ,
) ,
Recipe (
name: ' Cherry Pie ' ,
author: ' Sandra Adams ' ,
ingredientsImagePath: ' food/icons/main.png ' ,
description:
' Sometimes when you \' re craving some cheer in your life you can jumpstart your day with some cherry pie. Dessert for breakfast is perfectly acceptable. ' ,
imagePath: ' food/cherry_pie.png ' ,
ingredients: < RecipeIngredient > [
RecipeIngredient ( amount: ' 1 ' , description: ' Pie crust ' ) ,
RecipeIngredient (
amount: ' 4 cups ' , description: ' Fresh or frozen cherries ' ) ,
RecipeIngredient ( amount: ' 1 cup ' , description: ' Granulated sugar ' ) ,
RecipeIngredient ( amount: ' 4 tbsp ' , description: ' Cornstarch ' ) ,
RecipeIngredient ( amount: ' 1½ tbsp ' , description: ' Butter ' ) ,
] ,
steps: < RecipeStep > [
RecipeStep ( duration: ' 15 min ' , description: ' Mix ' ) ,
RecipeStep ( duration: ' 1hr 30 min ' , description: ' Bake ' ) ,
] ,
) ,
Recipe (
name: ' Spinach Salad ' ,
author: ' Peter Carlsson ' ,
ingredientsImagePath: ' food/icons/spicy.png ' ,
description:
' Everyone \' s favorite leafy green is back. Paired with fresh sliced onion, it \' s ready to tackle any dish, whether it be a salad or an egg scramble. ' ,
imagePath: ' food/spinach_onion_salad.png ' ,
ingredients: < RecipeIngredient > [
RecipeIngredient ( amount: ' 4 cups ' , description: ' Spinach ' ) ,
RecipeIngredient ( amount: ' 1 cup ' , description: ' Sliced onion ' ) ,
] ,
steps: < RecipeStep > [
RecipeStep ( duration: ' 5 min ' , description: ' Mix ' ) ,
] ,
) ,
Recipe (
name: ' Butternut Squash Soup ' ,
author: ' Ali Connors ' ,
ingredientsImagePath: ' food/icons/healthy.png ' ,
description:
' This creamy butternut squash soup will warm you on the chilliest of winter nights and bring a delightful pop of orange to the dinner table. ' ,
imagePath: ' food/butternut_squash_soup.png ' ,
ingredients: < RecipeIngredient > [
RecipeIngredient ( amount: ' 1 ' , description: ' Butternut squash ' ) ,
RecipeIngredient ( amount: ' 4 cups ' , description: ' Chicken stock ' ) ,
RecipeIngredient ( amount: ' 2 ' , description: ' Potatoes ' ) ,
RecipeIngredient ( amount: ' 1 ' , description: ' Onion ' ) ,
RecipeIngredient ( amount: ' 1 ' , description: ' Carrot ' ) ,
RecipeIngredient ( amount: ' 1 ' , description: ' Celery ' ) ,
RecipeIngredient ( amount: ' 1 tsp ' , description: ' Salt ' ) ,
RecipeIngredient ( amount: ' 1 tsp ' , description: ' Pepper ' ) ,
] ,
steps: < RecipeStep > [
RecipeStep ( duration: ' 10 min ' , description: ' Prep vegetables ' ) ,
RecipeStep ( duration: ' 5 min ' , description: ' Stir ' ) ,
RecipeStep ( duration: ' 1 hr 10 min ' , description: ' Cook ' )
] ,
) ,
Recipe (
name: ' Spanakopita ' ,
author: ' Trevor Hansen ' ,
ingredientsImagePath: ' food/icons/quick.png ' ,
description:
' You \' feta \' believe this is a crowd-pleaser! Flaky phyllo pastry surrounds a delicious mixture of spinach and cheeses to create the perfect appetizer. ' ,
imagePath: ' food/spanakopita.png ' ,
ingredients: < RecipeIngredient > [
RecipeIngredient ( amount: ' 1 lb ' , description: ' Spinach ' ) ,
RecipeIngredient ( amount: ' ½ cup ' , description: ' Feta cheese ' ) ,
RecipeIngredient ( amount: ' ½ cup ' , description: ' Cottage cheese ' ) ,
RecipeIngredient ( amount: ' 2 ' , description: ' Eggs ' ) ,
RecipeIngredient ( amount: ' 1 ' , description: ' Onion ' ) ,
RecipeIngredient ( amount: ' ½ lb ' , description: ' Phyllo dough ' ) ,
] ,
steps: < RecipeStep > [
RecipeStep ( duration: ' 5 min ' , description: ' Sauté vegetables ' ) ,
RecipeStep (
duration: ' 3 min ' ,
description: ' Stir vegetables and other filling ingredients ' ) ,
RecipeStep (
duration: ' 10 min ' ,
description: ' Fill phyllo squares half-full with filling and fold. ' ) ,
RecipeStep ( duration: ' 40 min ' , description: ' Bake ' )
] ,
) ,
] ;