shrine_order.dart 8.13 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
// 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_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(
41
                  tag: product.tag,
42
                  child: new Image.asset(product.imageAsset, fit: ImageFit.contain)
43 44 45 46 47 48 49 50 51 52
                )
              )
            ),
            new SizedBox(height: 24.0),
            new Row(
              children: <Widget>[
                new Padding(
                  padding: const EdgeInsets.symmetric(horizontal: 16.0),
                  child: new Center(
                    child: new Icon(
Ian Hickson's avatar
Ian Hickson committed
53
                      Icons.info_outline,
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
                      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),
74
                    child: new DropdownButtonHideUnderline(
75 76 77 78 79 80
                      child: new Container(
                        decoration: new BoxDecoration(
                          border: new Border.all(
                            color: const Color(0xFFD9D9D9)
                          )
                        ),
81
                        child: new DropdownButton<int>(
82
                          items: <int>[0, 1, 2, 3, 4, 5].map((int value) {
83
                            return new DropdownMenuItem<int>(
84
                              value: value,
85 86 87 88
                              child: new Padding(
                                padding: const EdgeInsets.only(left: 8.0),
                                child: new Text('Quantity $value', style: theme.quantityMenuStyle)
                              )
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
                            );
                          }).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 {
119
  OrderPage({ Key key, this.order, this.products, this.shoppingCart }) : super(key: key) {
120 121
    assert(order != null);
    assert(products != null && products.length > 0);
122
    assert(shoppingCart != null);
123 124 125 126
  }

  final Order order;
  final List<Product> products;
127
  final Map<Product, Order> shoppingCart;
128 129 130 131 132 133 134 135 136

  @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> {
137
  static final GlobalKey<ScaffoldState> scaffoldKey = new GlobalKey<ScaffoldState>(debugLabel: 'Shrine Order');
138
  static final GlobalKey<ScrollableState> scrollableKey = new GlobalKey<ScrollableState>();
139 140 141 142 143 144 145 146 147 148 149

  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(() {
150
        config.shoppingCart[newOrder.product] = newOrder;
151 152 153 154 155 156 157 158 159 160 161 162 163
        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,
164
      scrollableKey: scrollableKey,
165 166
      products: config.products,
      shoppingCart: config.shoppingCart,
167 168 169
      floatingActionButton: new FloatingActionButton(
        onPressed: () {
          updateOrder(inCart: true);
170
          final int n = currentOrder.quantity;
171
          final String item = currentOrder.product.name;
172
          showSnackBarMessage(
173
            'There ${ n == 1 ? "is one $item item" : "are $n $item items" } in the shopping cart.'
174
          );
175 176 177
        },
        backgroundColor: const Color(0xFF16F0F0),
        child: new Icon(
Ian Hickson's avatar
Ian Hickson committed
178
          Icons.add_shopping_cart,
179 180 181 182
          color: Colors.black
        )
      ),
      body: new Block(
183
        scrollableKey: scrollableKey,
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199
        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) {
200 201 202 203 204 205 206 207 208
                return new RepaintBoundary(
                  child: new Card(
                    elevation: 1,
                    child: new Image.asset(
                      product.imageAsset,
                      fit: ImageFit.contain
                    )
                  )
                );
209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227
            }).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,
    RouteSettings settings: const RouteSettings()
228
  }) : super(builder: builder, settings: settings) {
229 230 231 232 233 234 235 236 237 238
    assert(order != null);
  }

  Order order;

  @override
  Order get currentResult => order;

  static ShrineOrderRoute of(BuildContext context) => ModalRoute.of(context);
}