mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Shrine gallery demo: support for landscape layout (#9025)
This commit is contained in:
parent
23981f5996
commit
9192f67252
@ -47,8 +47,8 @@ int _columnSpanAtIndex(int index) {
|
|||||||
|
|
||||||
// The Shrine home page arranges the product cards into two columns. The card
|
// The Shrine home page arranges the product cards into two columns. The card
|
||||||
// on every 4th and 5th row spans two columns.
|
// on every 4th and 5th row spans two columns.
|
||||||
class ShrineGridLayout extends SliverGridLayout {
|
class _ShrineGridLayout extends SliverGridLayout {
|
||||||
const ShrineGridLayout({
|
const _ShrineGridLayout({
|
||||||
@required this.rowStride,
|
@required this.rowStride,
|
||||||
@required this.columnStride,
|
@required this.columnStride,
|
||||||
@required this.tileHeight,
|
@required this.tileHeight,
|
||||||
@ -95,14 +95,14 @@ class ShrineGridLayout extends SliverGridLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ShrineGridDelegate extends SliverGridDelegate {
|
class _ShrineGridDelegate extends SliverGridDelegate {
|
||||||
static const double _kSpacing = 8.0;
|
static const double _kSpacing = 8.0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
SliverGridLayout getLayout(SliverConstraints constraints) {
|
SliverGridLayout getLayout(SliverConstraints constraints) {
|
||||||
final double tileWidth = (constraints.crossAxisExtent - _kSpacing) / 2.0;
|
final double tileWidth = (constraints.crossAxisExtent - _kSpacing) / 2.0;
|
||||||
final double tileHeight = 40.0 + 144.0 + 40.0;
|
final double tileHeight = 40.0 + 144.0 + 40.0;
|
||||||
return new ShrineGridLayout(
|
return new _ShrineGridLayout(
|
||||||
tileWidth: tileWidth,
|
tileWidth: tileWidth,
|
||||||
tileHeight: tileHeight,
|
tileHeight: tileHeight,
|
||||||
rowStride: tileHeight + _kSpacing,
|
rowStride: tileHeight + _kSpacing,
|
||||||
@ -114,9 +114,9 @@ class ShrineGridDelegate extends SliverGridDelegate {
|
|||||||
bool shouldRelayout(covariant SliverGridDelegate oldDelegate) => false;
|
bool shouldRelayout(covariant SliverGridDelegate oldDelegate) => false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Displays the Vendor's name and avatar.
|
// Displays the Vendor's name and avatar.
|
||||||
class VendorItem extends StatelessWidget {
|
class _VendorItem extends StatelessWidget {
|
||||||
VendorItem({ Key key, this.vendor }) : super(key: key) {
|
_VendorItem({ Key key, this.vendor }) : super(key: key) {
|
||||||
assert(vendor != null);
|
assert(vendor != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,10 +145,10 @@ class VendorItem extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Displays the product's price. If the product is in the shopping cart the background
|
// Displays the product's price. If the product is in the shopping cart then the
|
||||||
/// is highlighted.
|
// background is highlighted.
|
||||||
abstract class PriceItem extends StatelessWidget {
|
abstract class _PriceItem extends StatelessWidget {
|
||||||
PriceItem({ Key key, this.product }) : super(key: key) {
|
_PriceItem({ Key key, this.product }) : super(key: key) {
|
||||||
assert(product != null);
|
assert(product != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,8 +167,8 @@ abstract class PriceItem extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ProductPriceItem extends PriceItem {
|
class _ProductPriceItem extends _PriceItem {
|
||||||
ProductPriceItem({ Key key, Product product }) : super(key: key, product: product);
|
_ProductPriceItem({ Key key, Product product }) : super(key: key, product: product);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -180,8 +180,8 @@ class ProductPriceItem extends PriceItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class FeaturePriceItem extends PriceItem {
|
class _FeaturePriceItem extends _PriceItem {
|
||||||
FeaturePriceItem({ Key key, Product product }) : super(key: key, product: product);
|
_FeaturePriceItem({ Key key, Product product }) : super(key: key, product: product);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -193,34 +193,54 @@ class FeaturePriceItem extends PriceItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Layout the main left and right elements of a FeatureItem.
|
class _HeadingLayout extends MultiChildLayoutDelegate {
|
||||||
class FeatureLayout extends MultiChildLayoutDelegate {
|
_HeadingLayout();
|
||||||
FeatureLayout();
|
|
||||||
|
|
||||||
static final String left = 'left';
|
static final String price = 'price';
|
||||||
static final String right = 'right';
|
static final String image = 'image';
|
||||||
|
static final String title = 'title';
|
||||||
|
static final String description = 'description';
|
||||||
|
static final String vendor = 'vendor';
|
||||||
|
|
||||||
// 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
|
@override
|
||||||
void performLayout(Size size) {
|
void performLayout(Size size) {
|
||||||
|
final Size priceSize = layoutChild(price, new BoxConstraints.loose(size));
|
||||||
|
positionChild(price, new Offset(size.width - priceSize.width, 0.0));
|
||||||
|
|
||||||
final double halfWidth = size.width / 2.0;
|
final double halfWidth = size.width / 2.0;
|
||||||
layoutChild(left, new BoxConstraints.tightFor(width: halfWidth, height: size.height));
|
final double halfHeight = size.height / 2.0;
|
||||||
positionChild(left, Offset.zero);
|
final double halfUnit = unitSize / 2.0;
|
||||||
layoutChild(right, new BoxConstraints.expand(width: halfWidth + unitSize, height: size.height));
|
const double margin = 16.0;
|
||||||
positionChild(right, new Offset(halfWidth - unitSize, 0.0));
|
|
||||||
|
final Size imageSize = layoutChild(image, new BoxConstraints.loose(size));
|
||||||
|
final double imageX = imageSize.width < halfWidth - halfUnit
|
||||||
|
? halfWidth / 2.0 - imageSize.width / 2.0 - halfUnit
|
||||||
|
: halfWidth - imageSize.width;
|
||||||
|
positionChild(image, new Offset(imageX, halfHeight - imageSize.height / 2.0));
|
||||||
|
|
||||||
|
final double maxTitleWidth = halfWidth + unitSize - margin;
|
||||||
|
final BoxConstraints titleBoxConstraints = new BoxConstraints(maxWidth: maxTitleWidth);
|
||||||
|
final Size titleSize = layoutChild(title, titleBoxConstraints);
|
||||||
|
final double titleX = halfWidth - unitSize;
|
||||||
|
final double titleY = halfHeight - titleSize.height;
|
||||||
|
positionChild(title, new Offset(titleX, titleY));
|
||||||
|
|
||||||
|
final Size descriptionSize = layoutChild(description, titleBoxConstraints);
|
||||||
|
final double descriptionY = titleY + titleSize.height + margin;
|
||||||
|
positionChild(description, new Offset(titleX, descriptionY));
|
||||||
|
|
||||||
|
layoutChild(vendor, titleBoxConstraints);
|
||||||
|
final double vendorY = descriptionY + descriptionSize.height + margin;
|
||||||
|
positionChild(vendor, new Offset(titleX, vendorY));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool shouldRelayout(FeatureLayout oldDelegate) => false;
|
bool shouldRelayout(_HeadingLayout oldDelegate) => false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A card that highlights the "featured" catalog item.
|
// A card that highlights the "featured" catalog item.
|
||||||
class FeatureItem extends StatelessWidget {
|
class _Heading extends StatelessWidget {
|
||||||
FeatureItem({ Key key, this.product }) : super(key: key) {
|
_Heading({ Key key, this.product }) : super(key: key) {
|
||||||
assert(product.featureTitle != null);
|
assert(product.featureTitle != null);
|
||||||
assert(product.featureDescription != null);
|
assert(product.featureDescription != null);
|
||||||
}
|
}
|
||||||
@ -229,63 +249,39 @@ class FeatureItem extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final Size screenSize = MediaQuery.of(context).size;
|
||||||
final ShrineTheme theme = ShrineTheme.of(context);
|
final ShrineTheme theme = ShrineTheme.of(context);
|
||||||
return new AspectRatio(
|
return new SizedBox(
|
||||||
aspectRatio: 3.0 / 3.5,
|
height: screenSize.width > screenSize.height
|
||||||
|
? (screenSize.height - kToolbarHeight) * 0.85
|
||||||
|
: (screenSize.height - kToolbarHeight) * 0.70,
|
||||||
child: new Container(
|
child: new Container(
|
||||||
decoration: new BoxDecoration(
|
decoration: new BoxDecoration(
|
||||||
backgroundColor: theme.cardBackgroundColor,
|
backgroundColor: theme.cardBackgroundColor,
|
||||||
border: new Border(bottom: new BorderSide(color: theme.dividerColor)),
|
border: new Border(bottom: new BorderSide(color: theme.dividerColor)),
|
||||||
),
|
),
|
||||||
child: new Column(
|
child: new CustomMultiChildLayout(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
delegate: new _HeadingLayout(),
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
new SizedBox(
|
new LayoutId(
|
||||||
height: unitSize,
|
id: _HeadingLayout.price,
|
||||||
child: new Align(
|
child: new _FeaturePriceItem(product: product),
|
||||||
alignment: FractionalOffset.topRight,
|
|
||||||
child: new FeaturePriceItem(product: product),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
new Expanded(
|
new LayoutId(
|
||||||
child: new CustomMultiChildLayout(
|
id: _HeadingLayout.image,
|
||||||
delegate: new FeatureLayout(),
|
child: new Image.asset(product.imageAsset, fit: BoxFit.cover),
|
||||||
children: <Widget>[
|
),
|
||||||
new LayoutId(
|
new LayoutId(
|
||||||
id: FeatureLayout.left,
|
id: _HeadingLayout.title,
|
||||||
child: new ClipRect(
|
child: new Text(product.featureTitle, style: theme.featureTitleStyle),
|
||||||
child: new OverflowBox(
|
),
|
||||||
minWidth: 340.0,
|
new LayoutId(
|
||||||
maxWidth: 340.0,
|
id: _HeadingLayout.description,
|
||||||
minHeight: 340.0,
|
child: new Text(product.featureDescription, style: theme.featureStyle),
|
||||||
maxHeight: 340.0,
|
),
|
||||||
alignment: FractionalOffset.topRight,
|
new LayoutId(
|
||||||
child: new Image.asset(product.imageAsset, fit: BoxFit.cover),
|
id: _HeadingLayout.vendor,
|
||||||
),
|
child: new _VendorItem(vendor: product.vendor),
|
||||||
),
|
|
||||||
),
|
|
||||||
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),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -294,9 +290,10 @@ class FeatureItem extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A card that displays a product's image, price, and vendor.
|
// A card that displays a product's image, price, and vendor. The _ProductItem
|
||||||
class ProductItem extends StatelessWidget {
|
// cards appear in a grid below the heading.
|
||||||
ProductItem({ Key key, this.product, this.onPressed }) : super(key: key) {
|
class _ProductItem extends StatelessWidget {
|
||||||
|
_ProductItem({ Key key, this.product, this.onPressed }) : super(key: key) {
|
||||||
assert(product != null);
|
assert(product != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -312,7 +309,7 @@ class ProductItem extends StatelessWidget {
|
|||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
new Align(
|
new Align(
|
||||||
alignment: FractionalOffset.centerRight,
|
alignment: FractionalOffset.centerRight,
|
||||||
child: new ProductPriceItem(product: product),
|
child: new _ProductPriceItem(product: product),
|
||||||
),
|
),
|
||||||
new Container(
|
new Container(
|
||||||
width: 144.0,
|
width: 144.0,
|
||||||
@ -325,7 +322,7 @@ class ProductItem extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
new Padding(
|
new Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||||
child: new VendorItem(vendor: product.vendor),
|
child: new _VendorItem(vendor: product.vendor),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -339,18 +336,18 @@ class ProductItem extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The Shrine app's home page. Displays the featured item above all of the
|
// The Shrine app's home page. Displays the featured item above a grid
|
||||||
/// product items arranged in two columns.
|
// of the product items.
|
||||||
class ShrineHome extends StatefulWidget {
|
class ShrineHome extends StatefulWidget {
|
||||||
@override
|
@override
|
||||||
_ShrineHomeState createState() => new _ShrineHomeState();
|
_ShrineHomeState createState() => new _ShrineHomeState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ShrineHomeState extends State<ShrineHome> {
|
class _ShrineHomeState extends State<ShrineHome> {
|
||||||
static final GlobalKey<ScaffoldState> scaffoldKey = new GlobalKey<ScaffoldState>(debugLabel: 'Shrine Home');
|
static final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>(debugLabel: 'Shrine Home');
|
||||||
static final ShrineGridDelegate gridDelegate = new ShrineGridDelegate();
|
static final _ShrineGridDelegate gridDelegate = new _ShrineGridDelegate();
|
||||||
|
|
||||||
Future<Null> showOrderPage(Product product) async {
|
Future<Null> _showOrderPage(Product product) async {
|
||||||
final Order order = _shoppingCart[product] ?? new Order(product: product);
|
final Order order = _shoppingCart[product] ?? new Order(product: product);
|
||||||
final Order completedOrder = await Navigator.push(context, new ShrineOrderRoute(
|
final Order completedOrder = await Navigator.push(context, new ShrineOrderRoute(
|
||||||
order: order,
|
order: order,
|
||||||
@ -371,13 +368,13 @@ class _ShrineHomeState extends State<ShrineHome> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
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,
|
||||||
products: _products,
|
products: _products,
|
||||||
shoppingCart: _shoppingCart,
|
shoppingCart: _shoppingCart,
|
||||||
body: new CustomScrollView(
|
body: new CustomScrollView(
|
||||||
slivers: <Widget>[
|
slivers: <Widget>[
|
||||||
new SliverToBoxAdapter(
|
new SliverToBoxAdapter(
|
||||||
child: new FeatureItem(product: featured),
|
child: new _Heading(product: featured),
|
||||||
),
|
),
|
||||||
new SliverPadding(
|
new SliverPadding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
@ -385,9 +382,9 @@ class _ShrineHomeState extends State<ShrineHome> {
|
|||||||
gridDelegate: gridDelegate,
|
gridDelegate: gridDelegate,
|
||||||
delegate: new SliverChildListDelegate(
|
delegate: new SliverChildListDelegate(
|
||||||
_products.map((Product product) {
|
_products.map((Product product) {
|
||||||
return new ProductItem(
|
return new _ProductItem(
|
||||||
product: product,
|
product: product,
|
||||||
onPressed: () { showOrderPage(product); },
|
onPressed: () { _showOrderPage(product); },
|
||||||
);
|
);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
),
|
),
|
||||||
|
@ -9,10 +9,133 @@ import 'shrine_page.dart';
|
|||||||
import 'shrine_theme.dart';
|
import 'shrine_theme.dart';
|
||||||
import 'shrine_types.dart';
|
import 'shrine_types.dart';
|
||||||
|
|
||||||
/// Describes a product and vendor in detail, supports specifying
|
// Displays the product title's, description, and order quantity dropdown.
|
||||||
/// a order quantity (0-5). Appears at the top of the OrderPage.
|
class _ProductItem extends StatelessWidget {
|
||||||
class OrderItem extends StatelessWidget {
|
_ProductItem({ Key key, this.product, this.quantity, this.onChanged }) : super(key: key) {
|
||||||
OrderItem({ Key key, this.product, this.quantity, this.quantityChanged }) : super(key: key) {
|
assert(product != null);
|
||||||
|
assert(quantity != null);
|
||||||
|
assert(onChanged != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
final Product product;
|
||||||
|
final int quantity;
|
||||||
|
final ValueChanged<int> onChanged;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final ShrineTheme theme = ShrineTheme.of(context);
|
||||||
|
return new Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: <Widget>[
|
||||||
|
new Text(product.name, style: theme.featureTitleStyle),
|
||||||
|
const SizedBox(height: 24.0),
|
||||||
|
new Text(product.description, style: theme.featureStyle),
|
||||||
|
const 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 Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 8.0),
|
||||||
|
child: new Text('Quantity $value', style: theme.quantityMenuStyle),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
value: quantity,
|
||||||
|
onChanged: onChanged,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vendor name and description
|
||||||
|
class _VendorItem extends StatelessWidget {
|
||||||
|
_VendorItem({ Key key, this.vendor }) : super(key: key) {
|
||||||
|
assert(vendor != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
final Vendor vendor;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final ShrineTheme theme = ShrineTheme.of(context);
|
||||||
|
return new Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: <Widget>[
|
||||||
|
new SizedBox(
|
||||||
|
height: 24.0,
|
||||||
|
child: new Align(
|
||||||
|
alignment: FractionalOffset.bottomLeft,
|
||||||
|
child: new Text(vendor.name, style: theme.vendorTitleStyle),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16.0),
|
||||||
|
new Text(vendor.description, style: theme.vendorStyle),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Layout the order page's heading: the product's image, the
|
||||||
|
// title/description/dropdown product item, and the vendor item.
|
||||||
|
class _HeadingLayout extends MultiChildLayoutDelegate {
|
||||||
|
_HeadingLayout();
|
||||||
|
|
||||||
|
static final String image = 'image';
|
||||||
|
static final String icon = 'icon';
|
||||||
|
static final String product = 'product';
|
||||||
|
static final String vendor = 'vendor';
|
||||||
|
|
||||||
|
@override
|
||||||
|
void performLayout(Size size) {
|
||||||
|
const double margin = 56.0;
|
||||||
|
final bool landscape = size.width > size.height;
|
||||||
|
final double imageWidth = (landscape ? size.width / 2.0 : size.width) - margin * 2.0;
|
||||||
|
final BoxConstraints imageConstraints = new BoxConstraints(maxHeight: 224.0, maxWidth: imageWidth);
|
||||||
|
final Size imageSize = layoutChild(image, imageConstraints);
|
||||||
|
final double imageY = 0.0;
|
||||||
|
positionChild(image, new Offset(margin, imageY));
|
||||||
|
|
||||||
|
final double productWidth = landscape ? size.width / 2.0 : size.width - margin;
|
||||||
|
final BoxConstraints productConstraints = new BoxConstraints(maxWidth: productWidth);
|
||||||
|
final Size productSize = layoutChild(product, productConstraints);
|
||||||
|
final double productX = landscape ? size.width / 2.0 : margin;
|
||||||
|
final double productY = landscape ? 0.0 : imageY + imageSize.height + 16.0;
|
||||||
|
positionChild(product, new Offset(productX, productY));
|
||||||
|
|
||||||
|
final Size iconSize = layoutChild(icon, new BoxConstraints.loose(size));
|
||||||
|
positionChild(icon, new Offset(productX - iconSize.width - 16.0, productY + 8.0));
|
||||||
|
|
||||||
|
final double vendorWidth = landscape ? size.width - margin : productWidth;
|
||||||
|
layoutChild(vendor, new BoxConstraints(maxWidth: vendorWidth));
|
||||||
|
final double vendorX = landscape ? margin : productX;
|
||||||
|
final double vendorY = productY + productSize.height + 16.0;
|
||||||
|
positionChild(vendor, new Offset(vendorX, vendorY));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldRelayout(_HeadingLayout oldDelegate) => true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Describes a product and vendor in detail, supports specifying
|
||||||
|
// a order quantity (0-5). Appears at the top of the OrderPage.
|
||||||
|
class _Heading extends StatelessWidget {
|
||||||
|
_Heading({ Key key, this.product, this.quantity, this.quantityChanged }) : super(key: key) {
|
||||||
assert(product != null);
|
assert(product != null);
|
||||||
assert(quantity != null && quantity >= 0 && quantity <= 5);
|
assert(quantity != null && quantity >= 0 && quantity <= 5);
|
||||||
}
|
}
|
||||||
@ -23,92 +146,50 @@ class OrderItem extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final ShrineTheme theme = ShrineTheme.of(context);
|
final Size screenSize = MediaQuery.of(context).size;
|
||||||
return new Material(
|
return new SizedBox(
|
||||||
type: MaterialType.card,
|
height: (screenSize.height - kToolbarHeight) * 1.35,
|
||||||
elevation: 0,
|
child: new Material(
|
||||||
child: new Padding(
|
type: MaterialType.card,
|
||||||
padding: const EdgeInsets.only(left: 16.0, top: 18.0, right: 16.0, bottom: 24.0),
|
elevation: 0,
|
||||||
child: new Column(
|
child: new Padding(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
padding: const EdgeInsets.only(left: 16.0, top: 18.0, right: 16.0, bottom: 24.0),
|
||||||
children: <Widget>[
|
child: new CustomMultiChildLayout(
|
||||||
new Padding(
|
delegate: new _HeadingLayout(),
|
||||||
padding: const EdgeInsets.only(left: 56.0),
|
children: <Widget>[
|
||||||
child: new SizedBox(
|
new LayoutId(
|
||||||
width: 248.0,
|
id: _HeadingLayout.image,
|
||||||
height: 248.0,
|
|
||||||
child: new Hero(
|
child: new Hero(
|
||||||
tag: product.tag,
|
tag: product.tag,
|
||||||
child: new Image.asset(product.imageAsset, fit: BoxFit.contain),
|
child: new Image.asset(
|
||||||
|
product.imageAsset,
|
||||||
|
fit: BoxFit.contain,
|
||||||
|
alignment: FractionalOffset.center,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
new LayoutId(
|
||||||
const SizedBox(height: 24.0),
|
id: _HeadingLayout.icon,
|
||||||
new Row(
|
child: new Icon(
|
||||||
children: <Widget>[
|
Icons.info_outline,
|
||||||
new Padding(
|
size: 24.0,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
color: const Color(0xFFFFE0E0),
|
||||||
child: new Center(
|
|
||||||
child: new Icon(
|
|
||||||
Icons.info_outline,
|
|
||||||
size: 24.0,
|
|
||||||
color: const Color(0xFFFFE0E0),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
new Expanded(
|
|
||||||
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>[
|
|
||||||
const SizedBox(height: 24.0),
|
|
||||||
new Text(product.description, style: theme.featureStyle),
|
|
||||||
const 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 Padding(
|
|
||||||
padding: const EdgeInsets.only(left: 8.0),
|
|
||||||
child: new Text('Quantity $value', style: theme.quantityMenuStyle),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
value: quantity,
|
|
||||||
onChanged: quantityChanged,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16.0),
|
|
||||||
new SizedBox(
|
|
||||||
height: 24.0,
|
|
||||||
child: new Align(
|
|
||||||
alignment: FractionalOffset.bottomLeft,
|
|
||||||
child: new Text(product.vendor.name, style: theme.vendorTitleStyle),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16.0),
|
|
||||||
new Text(product.vendor.description, style: theme.vendorStyle),
|
|
||||||
const SizedBox(height: 24.0),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
new LayoutId(
|
||||||
],
|
id: _HeadingLayout.product,
|
||||||
|
child: new _ProductItem(
|
||||||
|
product: product,
|
||||||
|
quantity: quantity,
|
||||||
|
onChanged: quantityChanged,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
new LayoutId(
|
||||||
|
id: _HeadingLayout.vendor,
|
||||||
|
child: new _VendorItem(vendor: product.vendor),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -130,9 +211,9 @@ class OrderPage extends StatefulWidget {
|
|||||||
_OrderPageState createState() => new _OrderPageState();
|
_OrderPageState createState() => new _OrderPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Displays a product's OrderItem above photos of all of the other products
|
// Displays a product's heading above photos of all of the other products
|
||||||
/// arranged in two columns. Enables the user to specify a quantity and add an
|
// arranged in two columns. Enables the user to specify a quantity and add an
|
||||||
/// order to the shopping cart.
|
// order to the shopping cart.
|
||||||
class _OrderPageState extends State<OrderPage> {
|
class _OrderPageState extends State<OrderPage> {
|
||||||
GlobalKey<ScaffoldState> scaffoldKey;
|
GlobalKey<ScaffoldState> scaffoldKey;
|
||||||
|
|
||||||
@ -185,24 +266,20 @@ class _OrderPageState extends State<OrderPage> {
|
|||||||
),
|
),
|
||||||
body: new CustomScrollView(
|
body: new CustomScrollView(
|
||||||
slivers: <Widget>[
|
slivers: <Widget>[
|
||||||
new SliverList(
|
new SliverToBoxAdapter(
|
||||||
delegate: new SliverChildListDelegate(<Widget>[
|
child: new _Heading(
|
||||||
new OrderItem(
|
product: config.order.product,
|
||||||
product: config.order.product,
|
quantity: currentOrder.quantity,
|
||||||
quantity: currentOrder.quantity,
|
quantityChanged: (int value) { updateOrder(quantity: value); },
|
||||||
quantityChanged: (int value) { updateOrder(quantity: value); },
|
),
|
||||||
),
|
|
||||||
const SizedBox(height: 24.0),
|
|
||||||
]),
|
|
||||||
),
|
),
|
||||||
new SliverPadding(
|
new SliverPadding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.fromLTRB(8.0, 32.0, 8.0, 8.0),
|
||||||
sliver: new SliverGrid(
|
sliver: new SliverGrid(
|
||||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
|
||||||
crossAxisCount: 2,
|
maxCrossAxisExtent: 248.0,
|
||||||
mainAxisSpacing: 8.0,
|
mainAxisSpacing: 8.0,
|
||||||
crossAxisSpacing: 8.0,
|
crossAxisSpacing: 8.0,
|
||||||
childAspectRatio: 160.0 / 216.0, // width/height
|
|
||||||
),
|
),
|
||||||
delegate: new SliverChildListDelegate(
|
delegate: new SliverChildListDelegate(
|
||||||
config.products
|
config.products
|
||||||
@ -225,11 +302,11 @@ class _OrderPageState extends State<OrderPage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Displays a full-screen modal OrderPage.
|
// Displays a full-screen modal OrderPage.
|
||||||
///
|
//
|
||||||
/// The order field will be replaced each time the user reconfigures the order.
|
// 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
|
// When the user backs out of this route the completer's value will be the
|
||||||
/// final value of the order field.
|
// final value of the order field.
|
||||||
class ShrineOrderRoute extends ShrinePageRoute<Order> {
|
class ShrineOrderRoute extends ShrinePageRoute<Order> {
|
||||||
ShrineOrderRoute({
|
ShrineOrderRoute({
|
||||||
this.order,
|
this.order,
|
||||||
|
Loading…
Reference in New Issue
Block a user