Version 0.0 of a gallery demo of the Material Design "Shrine" app (#4327)
This commit is contained in:
parent
a464052191
commit
309b9f8010
@ -6,7 +6,7 @@ dependencies:
|
||||
path: ../../../packages/flutter
|
||||
flutter_driver:
|
||||
path: ../../../packages/flutter_driver
|
||||
flutter_gallery_assets: '0.0.16'
|
||||
flutter_gallery_assets: '0.0.18'
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
@ -37,5 +37,8 @@ assets:
|
||||
- packages/flutter_gallery_assets/landscape_9.jpg
|
||||
- packages/flutter_gallery_assets/landscape_10.jpg
|
||||
- packages/flutter_gallery_assets/landscape_11.jpg
|
||||
- packages/flutter_gallery_assets/shadow.png
|
||||
- lib/gallery/example_code.dart
|
||||
fonts:
|
||||
- family: AbrilFatface
|
||||
fonts:
|
||||
- asset: packages/flutter_gallery_assets/shrine/fonts/abrilfatface/AbrilFatface-Regular.ttf
|
||||
|
@ -25,6 +25,7 @@ export 'persistent_bottom_sheet_demo.dart';
|
||||
export 'progress_indicator_demo.dart';
|
||||
export 'scrollable_tabs_demo.dart';
|
||||
export 'selection_controls_demo.dart';
|
||||
export 'shrine_demo.dart';
|
||||
export 'slider_demo.dart';
|
||||
export 'snack_bar_demo.dart';
|
||||
export 'tabs_demo.dart';
|
||||
|
264
examples/flutter_gallery/lib/demo/shrine/shrine_data.dart
Normal file
264
examples/flutter_gallery/lib/demo/shrine/shrine_data.dart
Normal file
@ -0,0 +1,264 @@
|
||||
// Copyright 2016 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 'shrine_types.dart';
|
||||
|
||||
const Vendor _ali = const Vendor(
|
||||
name: 'Ali’s shop',
|
||||
avatarUrl: 'https://www.gstatic.com/angular/material-adaptive/shrine/ali-connors.png',
|
||||
description:
|
||||
'Ali Connor’s makes custom goods for folks of all shapes and sizes '
|
||||
'made by hand and sometimes by machine, but always with love and care. '
|
||||
'Custom orders are available upon request if you need something extra special.'
|
||||
);
|
||||
|
||||
const Vendor _sandra = const Vendor(
|
||||
name: 'Sandra’s shop',
|
||||
avatarUrl: 'https://www.gstatic.com/angular/material-adaptive/shrine/sandra-adams.jpg',
|
||||
description:
|
||||
'Sandra specializes in furniture, beauty and travel products with a classic vibe. '
|
||||
'Custom orders are available if you’re looking for a certain color or material.'
|
||||
);
|
||||
|
||||
const Vendor _trevor = const Vendor(
|
||||
name: 'Trevor’s shop',
|
||||
avatarUrl: 'https://www.gstatic.com/angular/material-adaptive/shrine/zach.jpg',
|
||||
description:
|
||||
'Trevor makes great stuff for awesome people like you. Super cool and extra '
|
||||
'awesome all of his shop’s goods are handmade with love. Custom orders are '
|
||||
'available upon request if you need something extra special.'
|
||||
);
|
||||
|
||||
const Vendor _peter = const Vendor(
|
||||
name: 'Peter’s shop',
|
||||
avatarUrl: 'https://www.gstatic.com/angular/material-adaptive/shrine/peter-carlsson.png',
|
||||
description:
|
||||
'Peter makes great stuff for awesome people like you. Super cool and extra '
|
||||
'awesome all of his shop’s goods are handmade with love. Custom orders are '
|
||||
'available upon request if you need something extra special.'
|
||||
);
|
||||
|
||||
const Vendor _stella = const Vendor(
|
||||
name: 'Stella’s shop',
|
||||
avatarUrl: 'https://www.gstatic.com/angular/material-adaptive/shrine/16c477b.jpg',
|
||||
description:
|
||||
'Stella sells awesome stuff at lovely prices. made by hand and sometimes by '
|
||||
'machine, but always with love and care. Custom orders are available upon request '
|
||||
'if you need something extra special.'
|
||||
);
|
||||
|
||||
const List<Product> _allProducts = const <Product> [
|
||||
const Product(
|
||||
name: 'Vintage Bluetooth Radio',
|
||||
imageUrl: 'https://www.gstatic.com/angular/material-adaptive/shrine/radio.png',
|
||||
categories: const <String>['furniture', 'latest'],
|
||||
price: 300.00,
|
||||
vendor: _sandra,
|
||||
description:
|
||||
'Isn’t it cool when things look old, but their not. Looks Old But Not makes '
|
||||
'awesome vintage goods that are super smart. This ol’ radio just got an upgrade. '
|
||||
'Connect to it with an app and jam out to some top forty.'
|
||||
),
|
||||
const Product(
|
||||
name: 'Sunglasses',
|
||||
imageUrl: 'https://www.gstatic.com/angular/material-adaptive/shrine/sunnies.png',
|
||||
categories: const <String>['travel', 'fashion', 'beauty'],
|
||||
price: 70.00,
|
||||
vendor: _trevor,
|
||||
description:
|
||||
'Be an optimist. Carry Sunglasses with you at all times. All Tints and '
|
||||
'Shades products come with polarized lenses and super duper UV protection '
|
||||
'so you can look at the sun for however long you want. Sunglasses make you '
|
||||
'look cool, wear them.'
|
||||
),
|
||||
const Product(
|
||||
name: 'Clock',
|
||||
imageUrl: 'https://www.gstatic.com/angular/material-adaptive/shrine/clock.png',
|
||||
categories: const <String>['furniture'],
|
||||
price: 120.00,
|
||||
vendor: _trevor,
|
||||
description:
|
||||
'Timekeeper Co makes clocks that tell time precisely. Clock is '
|
||||
'very simple to use, set the time using your phone, hang it, and viola! '
|
||||
'You’ll never be late again.'
|
||||
),
|
||||
const Product(
|
||||
name: 'Red popsicle',
|
||||
imageUrl: 'https://www.gstatic.com/angular/material-adaptive/shrine/popsicle.png',
|
||||
categories: const <String>['food', 'fashion'],
|
||||
price: 300.00,
|
||||
vendor: _stella,
|
||||
description:
|
||||
'Looks can be deceiving. This red popsicle comes in a wide variety of '
|
||||
'flavors, including strawberry, that burst as soon as they hit your mouth. '
|
||||
'Red popsicles melt slowly, so savor the flavor.'
|
||||
),
|
||||
const Product(
|
||||
name: 'Folding Chair',
|
||||
imageUrl: 'https://www.gstatic.com/angular/material-adaptive/shrine/lawn_chair.png',
|
||||
categories: const <String>['furniture'],
|
||||
price: 63.00,
|
||||
vendor: _stella,
|
||||
description:
|
||||
'Leave the tunnel and the rain is fallin amazing things happen when you wait'
|
||||
),
|
||||
const Product(
|
||||
name: 'Green comfort chair',
|
||||
imageUrl: 'https://www.gstatic.com/angular/material-adaptive/shrine/chair.png',
|
||||
categories: const <String>['furniture'],
|
||||
price: 36.00,
|
||||
vendor: _ali,
|
||||
description:
|
||||
'Leave the tunnel and the rain is fallin amazing things happen when you wait'
|
||||
),
|
||||
const Product(
|
||||
name: 'Better wearing heels',
|
||||
imageUrl: 'https://www.gstatic.com/angular/material-adaptive/shrine/heels.png',
|
||||
categories: const <String>['fashion'],
|
||||
price: 125.00,
|
||||
vendor: _peter,
|
||||
description:
|
||||
'Leave the tunnel and the rain is fallin amazing things happen when you wait'
|
||||
),
|
||||
const Product(
|
||||
name: 'Green Slip-ons',
|
||||
imageUrl: 'https://www.gstatic.com/angular/material-adaptive/shrine/green-shoes.png',
|
||||
categories: const <String>['travel', 'fashion'],
|
||||
price: 75.00,
|
||||
vendor: _sandra,
|
||||
description:
|
||||
'Feetsy has been making extraordinary slip-ons for decades. With each pair '
|
||||
'of shoes purchased Feetsy donates a pair to those in need. Buy yourself a pair, '
|
||||
'buy someone else a pair. Very Comfortable.'
|
||||
),
|
||||
const Product(
|
||||
name: 'Teapot',
|
||||
imageUrl: 'https://www.gstatic.com/angular/material-adaptive/shrine/teapot.png',
|
||||
categories: const <String>['furniture', 'fashion'],
|
||||
price: 210.00,
|
||||
vendor: _trevor,
|
||||
featureTitle: 'Beautiful little teapot',
|
||||
featureDescription:
|
||||
'Leave the tunnel and the rain is fallin amazing things happen when you wait',
|
||||
description:
|
||||
'Impress your guests with Teapot by Kitchen Stuff. Teapot holds extremely '
|
||||
'hot liquids and pours them from the spout. Use the handle, shown on the left, '
|
||||
'so your fingers don’t get burnt while pouring.'
|
||||
),
|
||||
const Product(
|
||||
name: 'Blue suede shoes',
|
||||
imageUrl: 'https://www.gstatic.com/angular/material-adaptive/shrine/chucks.png',
|
||||
categories: const <String>['travel', 'fashion'],
|
||||
price: 89.00,
|
||||
vendor: _trevor,
|
||||
description:
|
||||
'Who needs pants when you have shoes! Blue suede shoes were meant to go '
|
||||
'dancing in, so you may want to pick up a few of these. These things are stylish.'
|
||||
),
|
||||
const Product(
|
||||
name: 'Dipped Brush',
|
||||
imageUrl: 'https://www.gstatic.com/angular/material-adaptive/shrine/brush.png',
|
||||
categories: const <String>['fashion', 'beauty'],
|
||||
price: 25.00,
|
||||
vendor: _stella,
|
||||
description:
|
||||
'WeDipIt does it again. This handle dipped 4 inch brush is a perfect for '
|
||||
'painting 4 inch lines, or coloring in big areas with paint. Just be sure you '
|
||||
'don’t drop it in a bucket of red paint, then it won’t look dipped anymore.'
|
||||
),
|
||||
const Product(
|
||||
name: 'Perfect Goldfish Bowl',
|
||||
imageUrl: 'https://www.gstatic.com/angular/material-adaptive/shrine/fish_bowl.png',
|
||||
categories: const <String>['latest', 'furniture'],
|
||||
price: 25.00,
|
||||
vendor: _ali,
|
||||
description:
|
||||
'The Perfect Bowl Co makes the best bowls for just about anything you can '
|
||||
'think of. This Perfect Goldfish Bowl holds water and fish perfectly. Looks '
|
||||
'great in living rooms. Keep out of reach from cats.'
|
||||
),
|
||||
const Product(
|
||||
name: 'Red Lipstick Set',
|
||||
imageUrl: 'https://www.gstatic.com/angular/material-adaptive/shrine/lipstick.png',
|
||||
categories: const <String>['fashion', 'beauty'],
|
||||
price: 25.00,
|
||||
vendor: _sandra,
|
||||
description:
|
||||
'Trying to find the perfect shade to match your mood? Try no longer. Red '
|
||||
'Lipstick Set by StickLips has you covered for those nights when you need '
|
||||
'to get out, or even if you’re just headed to work.'
|
||||
),
|
||||
const Product(
|
||||
name: 'Backpack',
|
||||
imageUrl: 'https://www.gstatic.com/angular/material-adaptive/shrine/backpack.png',
|
||||
categories: const <String>['travel', 'fashion'],
|
||||
price: 25.00,
|
||||
vendor: _peter,
|
||||
description:
|
||||
'This backpack by Bags ‘n’ stuff can hold just about anything: a laptop, '
|
||||
'a pen, a protractor, notebooks, small animals, plugs for your devices, '
|
||||
'sunglasses, gym clothes, shoes, gloves, two kittens, and even lunch!'
|
||||
),
|
||||
const Product(
|
||||
name: 'Half Shield Helmet',
|
||||
imageUrl: 'https://www.gstatic.com/angular/material-adaptive/shrine/helmet.png',
|
||||
categories: const <String>['travel', 'fashion', 'latest'],
|
||||
price: 25.00,
|
||||
vendor: _ali,
|
||||
description:
|
||||
'Half Shield is the right helmet for those warm summer days on the road. '
|
||||
'Dot approved, these helmets have been rigorously tested. Keep that noggin '
|
||||
'protected.'
|
||||
),
|
||||
const Product(
|
||||
name: 'Beachball',
|
||||
imageUrl: 'https://www.gstatic.com/angular/material-adaptive/shrine/beachball.png',
|
||||
categories: const <String>['latest'],
|
||||
price: 17.00,
|
||||
vendor: _peter,
|
||||
description:
|
||||
'Are you at a baseball game and feeling bored? At a pool party and looking '
|
||||
'for a laugh? Do you need something to take your anger out on? Beachball, '
|
||||
'by inflatable fun, is the perfect outlet.'
|
||||
),
|
||||
const Product(
|
||||
name: 'Old Binoculars',
|
||||
imageUrl: 'https://www.gstatic.com/angular/material-adaptive/shrine/binoculars.png',
|
||||
categories: const <String>['travel', 'fashion', 'latest'],
|
||||
price: 25.00,
|
||||
vendor: _stella,
|
||||
description:
|
||||
'These Binoculars by See Through are amazing and can make things that are '
|
||||
'really far away seem like they’re right in front of you. Bring them to the '
|
||||
'beach. Now you can buy the cheap seats at the big game and feel like you’re '
|
||||
'right in the action.'
|
||||
),
|
||||
const Product(
|
||||
name: 'Lime Flippers',
|
||||
imageUrl: 'https://www.gstatic.com/angular/material-adaptive/shrine/flippers.png',
|
||||
categories: const <String>['travel', 'fashion', 'beauty'],
|
||||
price: 25.00,
|
||||
vendor: _peter,
|
||||
description:
|
||||
'Flippers are a nice tool to have when you’re being chased by an oversized '
|
||||
'sea turtle. Never get caught again with these fast water shoes. You’re like '
|
||||
'a fish, but more graceful.'
|
||||
),
|
||||
const Product(
|
||||
name: 'Surfboard',
|
||||
imageUrl: 'https://www.gstatic.com/angular/material-adaptive/shrine/surfboard.png',
|
||||
categories: const <String>[ 'travel', 'latest'],
|
||||
price: 25.00,
|
||||
vendor: _stella,
|
||||
description:
|
||||
'Who says you can’t walk on water? With Surfboard, by Surfboard Supply, '
|
||||
'you can fly on water. This beast is fast and handles like a porsche. '
|
||||
'Hang Ten Bro!'
|
||||
)
|
||||
];
|
||||
|
||||
List<Product> allProducts() {
|
||||
assert(_allProducts.every((Product product) => product.isValid()));
|
||||
return new List<Product>.unmodifiable(_allProducts);
|
||||
}
|
295
examples/flutter_gallery/lib/demo/shrine/shrine_home.dart
Normal file
295
examples/flutter_gallery/lib/demo/shrine/shrine_home.dart
Normal file
@ -0,0 +1,295 @@
|
||||
// Copyright 2016 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 'dart:async';
|
||||
import 'dart:collection' show HashSet;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'shrine_data.dart';
|
||||
import 'shrine_order.dart';
|
||||
import 'shrine_page.dart';
|
||||
import 'shrine_theme.dart';
|
||||
import 'shrine_types.dart';
|
||||
|
||||
const double unitSize = kToolBarHeight;
|
||||
|
||||
Map<Product, Order> shoppingCart = <Product, Order>{};
|
||||
|
||||
/// Displays the Vendor's name and avatar.
|
||||
class VendorItem extends StatelessWidget {
|
||||
VendorItem({ Key key, this.vendor }) : super(key: key) {
|
||||
assert(vendor != null);
|
||||
}
|
||||
|
||||
final Vendor vendor;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return new SizedBox(
|
||||
height: 24.0,
|
||||
child: new Row(
|
||||
children: <Widget>[
|
||||
new SizedBox(
|
||||
width: 24.0,
|
||||
child: new ClipRRect(
|
||||
xRadius: 12.0,
|
||||
yRadius: 12.0,
|
||||
child: new NetworkImage(
|
||||
fit: ImageFit.cover,
|
||||
src: vendor.avatarUrl
|
||||
)
|
||||
)
|
||||
),
|
||||
new SizedBox(width: 8.0),
|
||||
new Flexible(
|
||||
child: new Text(vendor.name, style: ShrineTheme.of(context).vendorItemStyle)
|
||||
)
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Displays the product's price. If the product is in the shopping cart the background
|
||||
/// is highlighted.
|
||||
class PriceItem extends StatelessWidget {
|
||||
PriceItem({ Key key, this.product }) : super(key: key) {
|
||||
assert(product != null);
|
||||
}
|
||||
|
||||
final Product product;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
BoxDecoration decoration;
|
||||
if (shoppingCart[product] != null)
|
||||
decoration = new BoxDecoration(backgroundColor: const Color(0xFFFFE0E0));
|
||||
|
||||
return new Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
|
||||
decoration: decoration,
|
||||
child: new Text(product.priceString, style: ShrineTheme.of(context).priceStyle)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Layout the main left and right elements of a FeatureItem.
|
||||
class FeatureLayout extends MultiChildLayoutDelegate {
|
||||
FeatureLayout();
|
||||
|
||||
static final String left = 'left';
|
||||
static final String right = 'right';
|
||||
|
||||
// Horizontally: the feature product image appears on the left and
|
||||
// occupies 50% of the available width; the feature product's
|
||||
// description apepars on the right and occupies 50% of the available
|
||||
// width + unitSize. The left and right widgets overlap and the right
|
||||
// widget is stacked on top.
|
||||
@override
|
||||
void performLayout(Size size) {
|
||||
final double halfWidth = size.width / 2.0;
|
||||
layoutChild(left, new BoxConstraints.tightFor(width: halfWidth, height: size.height));
|
||||
positionChild(left, Offset.zero);
|
||||
layoutChild(right, new BoxConstraints.expand(width: halfWidth + unitSize, height: size.height));
|
||||
positionChild(right, new Offset(halfWidth - unitSize, 0.0));
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRelayout(FeatureLayout oldDelegate) => false;
|
||||
}
|
||||
|
||||
/// A card that highlights the "featured" catalog item.
|
||||
class FeatureItem extends StatelessWidget {
|
||||
FeatureItem({ Key key, this.product }) : super(key: key) {
|
||||
assert(product.featureTitle != null);
|
||||
assert(product.featureDescription != null);
|
||||
}
|
||||
|
||||
final Product product;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final ShrineTheme theme = ShrineTheme.of(context);
|
||||
return new AspectRatio(
|
||||
aspectRatio: 3.0 / 3.5,
|
||||
child: new Material(
|
||||
type: MaterialType.card,
|
||||
elevation: 1,
|
||||
child: new Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: <Widget>[
|
||||
new SizedBox(
|
||||
height: unitSize,
|
||||
child: new Align(
|
||||
alignment: FractionalOffset.topRight,
|
||||
child: new PriceItem(product: product)
|
||||
)
|
||||
),
|
||||
new Flexible(
|
||||
child: new CustomMultiChildLayout(
|
||||
delegate: new FeatureLayout(),
|
||||
children: <Widget>[
|
||||
new LayoutId(
|
||||
id: FeatureLayout.left,
|
||||
child: new ClipRect(
|
||||
child: new OverflowBox(
|
||||
minWidth: 340.0,
|
||||
maxWidth: 340.0,
|
||||
minHeight: 340.0,
|
||||
maxHeight: 340.0,
|
||||
alignment: FractionalOffset.topRight,
|
||||
child: new NetworkImage(
|
||||
fit: ImageFit.cover,
|
||||
src: product.imageUrl
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
new LayoutId(
|
||||
id: FeatureLayout.right,
|
||||
child: new Padding(
|
||||
padding: const EdgeInsets.only(right: 16.0),
|
||||
child: new Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
new Padding(
|
||||
padding: const EdgeInsets.only(top: 18.0),
|
||||
child: new Text(product.featureTitle, style: theme.featureTitleStyle)
|
||||
),
|
||||
new Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16.0),
|
||||
child: new Text(product.featureDescription, style: theme.featureStyle)
|
||||
),
|
||||
new VendorItem(vendor: product.vendor)
|
||||
]
|
||||
)
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// A card that displays a product's image, price, and vendor.
|
||||
class ProductItem extends StatelessWidget {
|
||||
ProductItem({ Key key, this.product, this.onPressed }) : super(key: key) {
|
||||
assert(product != null);
|
||||
}
|
||||
|
||||
final Product product;
|
||||
final VoidCallback onPressed;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return new Card(
|
||||
child: new Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: new Column(
|
||||
children: <Widget>[
|
||||
new Align(
|
||||
alignment: FractionalOffset.centerRight,
|
||||
child: new PriceItem(product: product)
|
||||
),
|
||||
new SizedBox(
|
||||
width: 144.0,
|
||||
height: 144.0,
|
||||
child: new Stack(
|
||||
children: <Widget>[
|
||||
new Hero(
|
||||
tag: productHeroTag,
|
||||
key: new ObjectKey(product),
|
||||
child: new NetworkImage(
|
||||
fit: ImageFit.contain,
|
||||
src: product.imageUrl
|
||||
)
|
||||
),
|
||||
new Material(
|
||||
color: Theme.of(context).canvasColor.withAlpha(0x00),
|
||||
child: new InkWell(onTap: onPressed)
|
||||
),
|
||||
]
|
||||
)
|
||||
),
|
||||
new VendorItem(vendor: product.vendor)
|
||||
]
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// The Shrine app's home page. Displays the featured item above all of the
|
||||
/// product items arranged in two columns.
|
||||
class ShrineHome extends StatefulWidget {
|
||||
@override
|
||||
_ShrineHomeState createState() => new _ShrineHomeState();
|
||||
}
|
||||
|
||||
class _ShrineHomeState extends State<ShrineHome> {
|
||||
final List<Product> _products = allProducts();
|
||||
|
||||
void handleCompletedOrder(Order completedOrder) {
|
||||
assert(completedOrder.product != null);
|
||||
if (completedOrder.inCart && completedOrder.quantity > 0)
|
||||
shoppingCart[completedOrder.product] = completedOrder;
|
||||
else
|
||||
shoppingCart[completedOrder.product] = null;
|
||||
}
|
||||
|
||||
void showOrderPage(Product product) {
|
||||
final Order order = shoppingCart[product] ?? new Order(product: product);
|
||||
final Completer<Order> completer = new Completer<Order>();
|
||||
final Key productKey = new ObjectKey(product);
|
||||
final Set<Key> mostValuableKeys = new HashSet<Key>();
|
||||
mostValuableKeys.add(productKey);
|
||||
Navigator.push(context, new ShrineOrderRoute(
|
||||
order: order,
|
||||
settings: new RouteSettings(mostValuableKeys: mostValuableKeys),
|
||||
completer: completer,
|
||||
builder: (BuildContext context) {
|
||||
return new OrderPage(
|
||||
order: order,
|
||||
products: _products
|
||||
);
|
||||
}
|
||||
));
|
||||
completer.future.then(handleCompletedOrder);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final Product featured = _products.firstWhere((Product product) => product.featureDescription != null);
|
||||
return new ShrinePage(
|
||||
body: new ScrollableViewport(
|
||||
child: new Column(
|
||||
children: <Widget>[
|
||||
new Container(
|
||||
margin: new EdgeInsets.only(bottom: 8.0),
|
||||
child: new FeatureItem(product: featured)
|
||||
),
|
||||
new FixedColumnCountGrid(
|
||||
columnCount: 2,
|
||||
rowSpacing: 8.0,
|
||||
columnSpacing: 8.0,
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
tileAspectRatio: 160.0 / 216.0, // width/height
|
||||
children: _products.map((Product product) {
|
||||
return new ProductItem(
|
||||
product: product,
|
||||
onPressed: () { showOrderPage(product); }
|
||||
);
|
||||
}).toList()
|
||||
)
|
||||
]
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
227
examples/flutter_gallery/lib/demo/shrine/shrine_order.dart
Normal file
227
examples/flutter_gallery/lib/demo/shrine/shrine_order.dart
Normal file
@ -0,0 +1,227 @@
|
||||
// Copyright 2016 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 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../shrine_demo.dart' show ShrinePageRoute;
|
||||
import 'shrine_page.dart';
|
||||
import 'shrine_theme.dart';
|
||||
import 'shrine_types.dart';
|
||||
|
||||
/// Describes a product and vendor in detail, supports specifying
|
||||
/// a order quantity (0-5). Appears at the top of the OrderPage.
|
||||
class OrderItem extends StatelessWidget {
|
||||
OrderItem({ Key key, this.product, this.quantity, this.quantityChanged }) : super(key: key) {
|
||||
assert(product != null);
|
||||
assert(quantity != null && quantity >= 0 && quantity <= 5);
|
||||
}
|
||||
|
||||
final Product product;
|
||||
final int quantity;
|
||||
final ValueChanged<int> quantityChanged;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final ShrineTheme theme = ShrineTheme.of(context);
|
||||
return new Material(
|
||||
type: MaterialType.card,
|
||||
elevation: 0,
|
||||
child: new Padding(
|
||||
padding: const EdgeInsets.only(left: 16.0, top: 18.0, right: 16.0, bottom: 24.0),
|
||||
child: new Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
new Padding(
|
||||
padding: const EdgeInsets.only(left: 56.0),
|
||||
child: new SizedBox(
|
||||
width: 248.0,
|
||||
height: 248.0,
|
||||
child: new Hero(
|
||||
tag: productHeroTag,
|
||||
child: new NetworkImage(
|
||||
fit: ImageFit.contain,
|
||||
src: product.imageUrl
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
new SizedBox(height: 24.0),
|
||||
new Row(
|
||||
children: <Widget>[
|
||||
new Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: new Center(
|
||||
child: new Icon(
|
||||
icon: Icons.info_outline,
|
||||
size: 24.0,
|
||||
color: const Color(0xFFFFE0E0)
|
||||
)
|
||||
)
|
||||
),
|
||||
new Flexible(
|
||||
child: new Text(product.name, style: theme.featureTitleStyle)
|
||||
)
|
||||
]
|
||||
),
|
||||
new Padding(
|
||||
padding: const EdgeInsets.only(left: 56.0),
|
||||
child: new Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: <Widget>[
|
||||
new SizedBox(height: 24.0),
|
||||
new Text(product.description, style: theme.featureStyle),
|
||||
new SizedBox(height: 16.0),
|
||||
new Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0, bottom: 8.0, right: 88.0),
|
||||
child: new DropDownButtonHideUnderline(
|
||||
child: new Container(
|
||||
decoration: new BoxDecoration(
|
||||
border: new Border.all(
|
||||
color: const Color(0xFFD9D9D9)
|
||||
)
|
||||
),
|
||||
child: new DropDownButton<int>(
|
||||
items: <int>[0, 1, 2, 3, 4, 5].map((int value) {
|
||||
return new DropDownMenuItem<int>(
|
||||
value: value,
|
||||
child: new Text('Quantity $value', style: theme.quantityMenuStyle)
|
||||
);
|
||||
}).toList(),
|
||||
value: quantity,
|
||||
onChanged: quantityChanged
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
new SizedBox(height: 16.0),
|
||||
new SizedBox(
|
||||
height: 24.0,
|
||||
child: new Align(
|
||||
alignment: FractionalOffset.bottomLeft,
|
||||
child: new Text(product.vendor.name, style: theme.vendorTitleStyle)
|
||||
)
|
||||
),
|
||||
new SizedBox(height: 16.0),
|
||||
new Text(product.vendor.description, style: theme.vendorStyle),
|
||||
new SizedBox(height: 24.0)
|
||||
]
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class OrderPage extends StatefulWidget {
|
||||
OrderPage({ Key key, this.order, this.products }) : super(key: key) {
|
||||
assert(order != null);
|
||||
assert(products != null && products.length > 0);
|
||||
}
|
||||
|
||||
final Order order;
|
||||
final List<Product> products;
|
||||
|
||||
@override
|
||||
_OrderPageState createState() => new _OrderPageState();
|
||||
}
|
||||
|
||||
/// Displays a product's OrderItem above photos of all of the other products
|
||||
/// arranged in two columns. Enables the user to specify a quantity and add an
|
||||
/// order to the shopping cart.
|
||||
class _OrderPageState extends State<OrderPage> {
|
||||
final GlobalKey<ScaffoldState> scaffoldKey = new GlobalKey<ScaffoldState>(debugLabel: 'Order Page');
|
||||
|
||||
Order get currentOrder => ShrineOrderRoute.of(context).order;
|
||||
|
||||
set currentOrder(Order value) {
|
||||
ShrineOrderRoute.of(context).order = value;
|
||||
}
|
||||
|
||||
void updateOrder({ int quantity, bool inCart }) {
|
||||
Order newOrder = currentOrder.copyWith(quantity: quantity, inCart: inCart);
|
||||
if (currentOrder != newOrder) {
|
||||
setState(() {
|
||||
currentOrder = newOrder;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void showSnackBarMessage(String message) {
|
||||
scaffoldKey.currentState.showSnackBar(new SnackBar(content: new Text(message)));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return new ShrinePage(
|
||||
scaffoldKey: scaffoldKey,
|
||||
floatingActionButton: new FloatingActionButton(
|
||||
onPressed: () {
|
||||
updateOrder(inCart: true);
|
||||
showSnackBarMessage('There are ${currentOrder.quantity} items in the shopping cart');
|
||||
},
|
||||
backgroundColor: const Color(0xFF16F0F0),
|
||||
child: new Icon(
|
||||
icon: Icons.add_shopping_cart,
|
||||
color: Colors.black
|
||||
)
|
||||
),
|
||||
body: new Block(
|
||||
children: <Widget>[
|
||||
new OrderItem(
|
||||
product: config.order.product,
|
||||
quantity: currentOrder.quantity,
|
||||
quantityChanged: (int value) { updateOrder(quantity: value); }
|
||||
),
|
||||
new SizedBox(height: 24.0),
|
||||
new FixedColumnCountGrid(
|
||||
columnCount: 2,
|
||||
rowSpacing: 8.0,
|
||||
columnSpacing: 8.0,
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
tileAspectRatio: 160.0 / 216.0, // width/height
|
||||
children: config.products
|
||||
.where((Product product) => product != config.order.product)
|
||||
.map((Product product) {
|
||||
return new Card(
|
||||
elevation: 0,
|
||||
child: new NetworkImage(
|
||||
fit: ImageFit.contain,
|
||||
src: product.imageUrl
|
||||
)
|
||||
);
|
||||
}).toList()
|
||||
)
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// Displays a full-screen modal OrderPage.
|
||||
///
|
||||
/// The order field will be replaced each time the user reconfigures the order.
|
||||
/// When the user backs out of this route the completer's value will be the
|
||||
/// final value of the order field.
|
||||
class ShrineOrderRoute extends ShrinePageRoute<Order> {
|
||||
ShrineOrderRoute({
|
||||
this.order,
|
||||
WidgetBuilder builder,
|
||||
Completer<Order> completer,
|
||||
RouteSettings settings: const RouteSettings()
|
||||
}) : super(builder: builder, completer: completer, settings: settings) {
|
||||
assert(order != null);
|
||||
}
|
||||
|
||||
Order order;
|
||||
|
||||
@override
|
||||
Order get currentResult => order;
|
||||
|
||||
static ShrineOrderRoute of(BuildContext context) => ModalRoute.of(context);
|
||||
}
|
60
examples/flutter_gallery/lib/demo/shrine/shrine_page.dart
Normal file
60
examples/flutter_gallery/lib/demo/shrine/shrine_page.dart
Normal file
@ -0,0 +1,60 @@
|
||||
// Copyright 2016 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/material.dart';
|
||||
|
||||
import 'shrine_theme.dart';
|
||||
|
||||
enum ShrineAction {
|
||||
sortByPrice,
|
||||
sortByProduct,
|
||||
emptyCart
|
||||
}
|
||||
|
||||
/// Defines the Scaffold, AppBar, etc that the demo pages have in common.
|
||||
class ShrinePage extends StatelessWidget {
|
||||
ShrinePage({ Key key, this.scaffoldKey, this.body, this.floatingActionButton }) : super(key: key);
|
||||
|
||||
final Key scaffoldKey;
|
||||
final Widget body;
|
||||
final Widget floatingActionButton;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return new Scaffold(
|
||||
key: scaffoldKey,
|
||||
appBar: new AppBar(
|
||||
title: new Center(
|
||||
child: new Text('SHRINE', style: ShrineTheme.of(context).appBarTitleStyle)
|
||||
),
|
||||
backgroundColor: Theme.of(context).canvasColor,
|
||||
actions: <Widget>[ // TODO(hansmuller): implement the actions.
|
||||
new IconButton(
|
||||
icon: Icons.shopping_cart,
|
||||
tooltip: 'Shopping cart',
|
||||
onPressed: () { /* activate the button for now */ }
|
||||
),
|
||||
new PopupMenuButton<ShrineAction>(
|
||||
itemBuilder: (BuildContext context) => <PopupMenuItem<ShrineAction>>[
|
||||
new PopupMenuItem<ShrineAction>(
|
||||
value: ShrineAction.sortByPrice,
|
||||
child: new Text('Sort by price')
|
||||
),
|
||||
new PopupMenuItem<ShrineAction>(
|
||||
value: ShrineAction.sortByProduct,
|
||||
child: new Text('Sort by product')
|
||||
),
|
||||
new PopupMenuItem<ShrineAction>(
|
||||
value: ShrineAction.emptyCart,
|
||||
child: new Text('Empty shopping cart')
|
||||
)
|
||||
]
|
||||
)
|
||||
]
|
||||
),
|
||||
floatingActionButton: floatingActionButton,
|
||||
body: body
|
||||
);
|
||||
}
|
||||
}
|
47
examples/flutter_gallery/lib/demo/shrine/shrine_theme.dart
Normal file
47
examples/flutter_gallery/lib/demo/shrine/shrine_theme.dart
Normal file
@ -0,0 +1,47 @@
|
||||
// Copyright 2016 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/material.dart';
|
||||
|
||||
class ShrineStyle extends TextStyle {
|
||||
const ShrineStyle.roboto(double size, FontWeight weight, Color color)
|
||||
: super(inherit: false, color: color, fontSize: size, fontWeight: weight, textBaseline: TextBaseline.alphabetic);
|
||||
|
||||
const ShrineStyle.abrilFatface(double size, FontWeight weight, Color color)
|
||||
: super(inherit: false, color: color, fontFamily: 'AbrilFatface', fontSize: size, fontWeight: weight, textBaseline: TextBaseline.alphabetic);
|
||||
}
|
||||
|
||||
TextStyle robotoRegular12(Color color) => new ShrineStyle.roboto(12.0, FontWeight.w500, color);
|
||||
TextStyle robotoLight12(Color color) => new ShrineStyle.roboto(12.0, FontWeight.w300, color);
|
||||
TextStyle robotoRegular14(Color color) => new ShrineStyle.roboto(14.0, FontWeight.w500, color);
|
||||
TextStyle robotoMedium14(Color color) => new ShrineStyle.roboto(14.0, FontWeight.w600, color);
|
||||
TextStyle robotoLight14(Color color) => new ShrineStyle.roboto(14.0, FontWeight.w300, color);
|
||||
TextStyle robotoRegular20(Color color) => new ShrineStyle.roboto(20.0, FontWeight.w500, color);
|
||||
TextStyle abrilFatfaceRegular24(Color color) => new ShrineStyle.abrilFatface(24.0, FontWeight.w500, color);
|
||||
TextStyle abrilFatfaceRegular34(Color color) => new ShrineStyle.abrilFatface(34.0, FontWeight.w500, color);
|
||||
|
||||
/// The TextStyles and Colors used for titles, labels, and descriptions. This
|
||||
/// InheritedWidget is shared by all of the routes and widgets created for
|
||||
/// the Shrine app.
|
||||
class ShrineTheme extends InheritedWidget {
|
||||
ShrineTheme({ Key key, Widget child }) : super(key: key, child: child) {
|
||||
assert(child != null);
|
||||
}
|
||||
|
||||
final TextStyle appBarTitleStyle = robotoRegular20(Colors.black87);
|
||||
final TextStyle vendorItemStyle = robotoRegular12(const Color(0xFF81959D));
|
||||
final TextStyle priceStyle = robotoRegular14(Colors.black87);
|
||||
final TextStyle featureTitleStyle = abrilFatfaceRegular34(Colors.black87);
|
||||
final TextStyle featureStyle = robotoLight14(Colors.black54);
|
||||
final TextStyle orderTitleStyle = abrilFatfaceRegular24(Colors.black87);
|
||||
final TextStyle orderStyle = robotoLight14(Colors.black54);
|
||||
final TextStyle vendorTitleStyle = robotoMedium14(Colors.black87);
|
||||
final TextStyle vendorStyle = robotoLight14(Colors.black54);
|
||||
final TextStyle quantityMenuStyle = robotoLight14(Colors.black54);
|
||||
|
||||
static ShrineTheme of(BuildContext context) => context.inheritFromWidgetOfExactType(ShrineTheme);
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(ShrineTheme old) => false;
|
||||
}
|
103
examples/flutter_gallery/lib/demo/shrine/shrine_types.dart
Normal file
103
examples/flutter_gallery/lib/demo/shrine/shrine_types.dart
Normal file
@ -0,0 +1,103 @@
|
||||
// Copyright 2016 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 'dart:ui' show hashValues;
|
||||
|
||||
const String productHeroTag = 'Product';
|
||||
|
||||
class Vendor {
|
||||
const Vendor({
|
||||
this.name,
|
||||
this.description,
|
||||
this.avatarUrl
|
||||
});
|
||||
|
||||
final String name;
|
||||
final String description;
|
||||
final String avatarUrl;
|
||||
|
||||
bool isValid() {
|
||||
return name != null &&
|
||||
description != null &&
|
||||
avatarUrl != null;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => 'Vendor($name)';
|
||||
}
|
||||
|
||||
class Product {
|
||||
const Product({
|
||||
this.name,
|
||||
this.description,
|
||||
this.featureTitle,
|
||||
this.featureDescription,
|
||||
this.imageUrl,
|
||||
this.categories,
|
||||
this.price,
|
||||
this.vendor
|
||||
});
|
||||
|
||||
final String name;
|
||||
final String description;
|
||||
final String featureTitle;
|
||||
final String featureDescription;
|
||||
final String imageUrl;
|
||||
final List<String> categories;
|
||||
final double price;
|
||||
final Vendor vendor;
|
||||
|
||||
String get priceString => '\$${price.floor()}';
|
||||
|
||||
bool isValid() {
|
||||
return name != null &&
|
||||
description != null &&
|
||||
imageUrl != null &&
|
||||
categories != null &&
|
||||
categories.length > 0 &&
|
||||
price != null &&
|
||||
vendor.isValid();
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => 'Product($name)';
|
||||
}
|
||||
|
||||
class Order {
|
||||
Order({ this.product, this.quantity: 1, this.inCart: false }) {
|
||||
assert(product != null);
|
||||
assert(quantity != null && quantity >= 0);
|
||||
assert(inCart != null);
|
||||
}
|
||||
|
||||
final Product product;
|
||||
final int quantity;
|
||||
final bool inCart;
|
||||
|
||||
Order copyWith({ Product product, int quantity, bool inCart }) {
|
||||
return new Order(
|
||||
product: product ?? this.product,
|
||||
quantity: quantity ?? this.quantity,
|
||||
inCart: inCart ?? this.inCart
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(dynamic other) {
|
||||
if (identical(this, other))
|
||||
return true;
|
||||
if (other.runtimeType != runtimeType)
|
||||
return false;
|
||||
final Order typedOther = other;
|
||||
return product == typedOther.product &&
|
||||
quantity == typedOther.quantity &&
|
||||
inCart == typedOther.inCart;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => hashValues(product, quantity, inCart);
|
||||
|
||||
@override
|
||||
String toString() => 'Order($product, quantity=$quantity, inCart=$inCart)';
|
||||
}
|
46
examples/flutter_gallery/lib/demo/shrine_demo.dart
Normal file
46
examples/flutter_gallery/lib/demo/shrine_demo.dart
Normal file
@ -0,0 +1,46 @@
|
||||
// Copyright 2016 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 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'shrine/shrine_home.dart' show ShrineHome;
|
||||
import 'shrine/shrine_theme.dart' show ShrineTheme;
|
||||
|
||||
// This code would ordinarily be part of the MaterialApp's home. It's being
|
||||
// used by the ShrineDemo and by each route pushed from there because this
|
||||
// isn't a standalone app with its own main() and MaterialApp.
|
||||
Widget buildShrine(Widget child) {
|
||||
return new Theme(
|
||||
data: new ThemeData(primarySwatch: Colors.grey),
|
||||
child: new IconTheme(
|
||||
data: new IconThemeData(color: const Color(0xFF707070)),
|
||||
child: new ShrineTheme(
|
||||
child: child
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// In a standalone version of this app, MaterialPageRoute<T> could be used directly.
|
||||
class ShrinePageRoute<T> extends MaterialPageRoute<T> {
|
||||
ShrinePageRoute({
|
||||
WidgetBuilder builder,
|
||||
Completer<T> completer,
|
||||
RouteSettings settings: const RouteSettings()
|
||||
}) : super(builder: builder, completer: completer, settings: settings);
|
||||
|
||||
@override
|
||||
Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> forwardAnimation) {
|
||||
return buildShrine(super.buildPage(context, animation, forwardAnimation));
|
||||
}
|
||||
}
|
||||
|
||||
class ShrineDemo extends StatelessWidget {
|
||||
static const String routeName = '/shrine'; // Used by the Gallery app.
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => buildShrine(new ShrineHome());
|
||||
}
|
@ -9,6 +9,7 @@ import '../demo/all.dart';
|
||||
import 'home.dart';
|
||||
|
||||
final Map<String, WidgetBuilder> kRoutes = <String, WidgetBuilder>{
|
||||
ShrineDemo.routeName: (BuildContext context) => new ShrineDemo(),
|
||||
WeatherDemo.routeName: (BuildContext context) => new WeatherDemo(),
|
||||
FitnessDemo.routeName: (BuildContext context) => new FitnessDemo(),
|
||||
Calculator.routeName: (BuildContext context) => new Calculator(),
|
||||
|
@ -71,6 +71,7 @@ class GalleryHomeState extends State<GalleryHome> {
|
||||
leading: new Icon(icon: Icons.star),
|
||||
title: new Text('Demos'),
|
||||
children: <Widget>[
|
||||
new GalleryItem(title: 'Shrine', routeName: ShrineDemo.routeName),
|
||||
new GalleryItem(title: 'Weather', routeName: WeatherDemo.routeName),
|
||||
new GalleryItem(title: 'Fitness', routeName: FitnessDemo.routeName),
|
||||
new GalleryItem(title: 'Calculator', routeName: Calculator.routeName),
|
||||
|
@ -10,7 +10,7 @@ dependencies:
|
||||
path: ../../packages/flutter_sprites
|
||||
flutter_markdown:
|
||||
path: ../../packages/flutter_markdown
|
||||
flutter_gallery_assets: '0.0.16'
|
||||
flutter_gallery_assets: '0.0.18'
|
||||
|
||||
dev_dependencies:
|
||||
test: any # flutter_test provides the version constraints
|
||||
|
@ -183,7 +183,7 @@ class AppBar extends StatelessWidget {
|
||||
final double statusBarHeight = MediaQuery.of(context).padding.top;
|
||||
final ThemeData theme = Theme.of(context);
|
||||
|
||||
IconThemeData iconTheme = theme.primaryIconTheme;
|
||||
IconThemeData iconTheme = IconTheme.of(context) ?? theme.primaryIconTheme;
|
||||
TextStyle centerStyle = textTheme?.title ?? theme.primaryTextTheme.title;
|
||||
TextStyle sideStyle = textTheme?.body1 ?? theme.primaryTextTheme.body1;
|
||||
|
||||
|
@ -17,8 +17,9 @@ class Card extends StatelessWidget {
|
||||
/// Creates a material design card.
|
||||
const Card({
|
||||
Key key,
|
||||
this.child,
|
||||
this.color
|
||||
this.color,
|
||||
this.elevation: 2,
|
||||
this.child
|
||||
}) : super(key: key);
|
||||
|
||||
/// The widget below this widget in the tree.
|
||||
@ -27,6 +28,9 @@ class Card extends StatelessWidget {
|
||||
/// The color of material used for this card.
|
||||
final Color color;
|
||||
|
||||
/// The following elevations have defined shadows: 1, 2, 3, 4, 6, 8, 9, 12, 16, 24
|
||||
final int elevation;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return new Container(
|
||||
@ -34,7 +38,7 @@ class Card extends StatelessWidget {
|
||||
child: new Material(
|
||||
color: color,
|
||||
type: MaterialType.card,
|
||||
elevation: 2,
|
||||
elevation: elevation,
|
||||
child: child
|
||||
)
|
||||
);
|
||||
|
@ -32,6 +32,10 @@ abstract class Route<T> {
|
||||
/// Called after install() when the route is pushed onto the navigator.
|
||||
void didPush() { }
|
||||
|
||||
/// When this route is popped (see [Navigator.pop]) if the result isn't
|
||||
/// specified or if it's null, this value will be used instead.
|
||||
T get currentResult => null;
|
||||
|
||||
/// Called after install() when the route replaced another in the navigator.
|
||||
void didReplace(Route<dynamic> oldRoute) { }
|
||||
|
||||
@ -427,7 +431,7 @@ class NavigatorState extends State<Navigator> {
|
||||
assert(route._navigator == this);
|
||||
bool debugPredictedWouldPop;
|
||||
assert(() { debugPredictedWouldPop = !route.willHandlePopInternally; return true; });
|
||||
if (route.didPop(result)) {
|
||||
if (route.didPop(result ?? route.currentResult)) {
|
||||
assert(debugPredictedWouldPop);
|
||||
if (_history.length > 1) {
|
||||
setState(() {
|
||||
@ -564,11 +568,12 @@ class NavigatorTransaction {
|
||||
/// (if any) is notified using its didPop() method, and the previous route is
|
||||
/// notified using [Route.didChangeNext].
|
||||
///
|
||||
/// If non-null, [result] will be used as the result of the route. Routes
|
||||
/// such as dialogs or popup menus typically use this mechanism to return the
|
||||
/// value selected by the user to the widget that created their route. The
|
||||
/// type of [result], if provided, must match the type argument of the class
|
||||
/// of the current route. (In practice, this is usually "dynamic".)
|
||||
/// If non-null, [result] will be used as the result of the route, otherwise
|
||||
/// the route's [Route.currentValue] will be used. Routes such as dialogs or
|
||||
/// popup menus typically use this mechanism to return the value selected by
|
||||
/// the user to the widget that created their route. The type of [result],
|
||||
/// if provided, must match the type argument of the class of the current
|
||||
/// route. (In practice, this is usually "dynamic".)
|
||||
///
|
||||
/// Returns true if a route was popped; returns false if there are no further
|
||||
/// previous routes.
|
||||
|
Loading…
x
Reference in New Issue
Block a user