mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00

Anywhere that accepted IconData now accepts either an Icon or an ImageIcon. Places that used to take an IconData in an `icon` argument, notably IconButton and DrawerItem, now take a Widget in that slot. You can wrap the value that used to be passed in in an Icon constructor to get the same result. Icon itself now takes the icon as a positional argument, for brevity. ThemeData now has an iconTheme as well as a primaryIconTheme, the same way it has had a textTheme and primaryTextTheme for a while. IconTheme.of() always returns a value now (though that value itself may have nulls in it). It defaults to the ThemeData.iconTheme. IconThemeData.fallback() is a new method that returns an icon theme data structure with all fields filled in. IconTheme.merge() is a new constructor that takes a context and creates a widget that mixes in the new values with the inherited values. Most places that introduced an IconTheme widget now use IconTheme.merge. IconThemeData.merge and IconThemeData.copyWith act in a way analogous to the similarly-named members of TextStyle. ImageIcon is introduced. It acts like Icon but takes an ImageProvider instead of an IconData. Also: Fix the analyzer to actually check the stocks app.
237 lines
7.9 KiB
Dart
237 lines
7.9 KiB
Dart
// 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 Image(
|
|
image: new AssetImage(product.imageAsset),
|
|
fit: ImageFit.contain
|
|
)
|
|
)
|
|
)
|
|
),
|
|
new SizedBox(height: 24.0),
|
|
new Row(
|
|
children: <Widget>[
|
|
new Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
|
child: new Center(
|
|
child: new 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, this.shoppingCart }) : super(key: key) {
|
|
assert(order != null);
|
|
assert(products != null && products.length > 0);
|
|
assert(shoppingCart != null);
|
|
}
|
|
|
|
final Order order;
|
|
final List<Product> products;
|
|
final Map<Product, Order> shoppingCart;
|
|
|
|
@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> {
|
|
static 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(() {
|
|
config.shoppingCart[newOrder.product] = newOrder;
|
|
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,
|
|
products: config.products,
|
|
shoppingCart: config.shoppingCart,
|
|
floatingActionButton: new FloatingActionButton(
|
|
onPressed: () {
|
|
updateOrder(inCart: true);
|
|
final int n = currentOrder.quantity;
|
|
final String item = currentOrder.product.name;
|
|
showSnackBarMessage(
|
|
'There ${ n == 1 ? "is one $item item" : "are $n $item items" } in the shopping cart.'
|
|
);
|
|
},
|
|
backgroundColor: const Color(0xFF16F0F0),
|
|
child: new 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 Image(
|
|
image: new AssetImage(product.imageAsset),
|
|
fit: ImageFit.contain
|
|
)
|
|
);
|
|
}).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);
|
|
}
|