card_collection.dart 13.1 KB
Newer Older
1 2 3 4
// Copyright 2015 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.

5
import 'package:flutter/material.dart';
6
import 'package:flutter/rendering.dart' show debugDumpRenderTree;
7

8
class CardModel {
9
  CardModel(this.value, this.height) {
10
    inputValue = new InputValue(text: 'Item $value');
11
  }
12 13
  int value;
  double height;
14
  int get color => ((value % 9) + 1) * 100;
15
  InputValue inputValue;
Hixie's avatar
Hixie committed
16
  Key get key => new ObjectKey(this);
17 18
}

19
class CardCollection extends StatefulWidget {
20
  @override
21
  CardCollectionState createState() => new CardCollectionState();
22 23
}

24
class CardCollectionState extends State<CardCollection> {
25

26
  static const TextStyle cardLabelStyle =
27
    const TextStyle(color: Colors.white, fontSize: 18.0, fontWeight: FontWeight.bold);
28

Hans Muller's avatar
Hans Muller committed
29
  // TODO(hansmuller): need a local image asset
30
  static const String _sunshineURL = "http://www.walltor.com/images/wallpaper/good-morning-sunshine-58540.jpg";
Hans Muller's avatar
Hans Muller committed
31

32
  static const double kCardMargins = 8.0;
33
  static const double kFixedCardHeight = 100.0;
34

35
  Map<int, Color> _primaryColor = Colors.deepPurple;
36 37
  List<CardModel> _cardModels;
  DismissDirection _dismissDirection = DismissDirection.horizontal;
38
  TextAlign _textAlign = TextAlign.center;
Hans Muller's avatar
Hans Muller committed
39
  bool _editable = false;
40
  bool _fixedSizeCards = false;
Hans Muller's avatar
Hans Muller committed
41
  bool _sunshine = false;
42
  bool _varyFontSizes = false;
43

44
  void _initVariableSizedCardModels() {
45
    final List<double> cardHeights = <double>[
46 47
      48.0, 63.0, 82.0, 146.0, 60.0, 55.0, 84.0, 96.0, 50.0,
      48.0, 63.0, 82.0, 146.0, 60.0, 55.0, 84.0, 96.0, 50.0,
48
      48.0, 63.0, 82.0, 146.0, 60.0, 55.0, 84.0, 96.0, 50.0,
49
    ];
Hixie's avatar
Hixie committed
50 51 52 53
    _cardModels = new List<CardModel>.generate(
      cardHeights.length,
      (int i) => new CardModel(i, cardHeights[i])
    );
54 55
  }

56 57
  void _initFixedSizedCardModels() {
    const int cardCount = 27;
Hixie's avatar
Hixie committed
58 59
    _cardModels = new List<CardModel>.generate(
      cardCount,
60
      (int i) => new CardModel(i, kFixedCardHeight),
Hixie's avatar
Hixie committed
61
    );
62 63 64 65 66 67 68 69 70
  }

  void _initCardModels() {
    if (_fixedSizeCards)
      _initFixedSizedCardModels();
    else
      _initVariableSizedCardModels();
  }

71
  @override
72 73
  void initState() {
    super.initState();
74 75 76
    _initCardModels();
  }

77
  void dismissCard(CardModel card) {
78
    if (_cardModels.contains(card)) {
79
      setState(() {
80
        _cardModels.remove(card);
81 82 83 84
      });
    }
  }

85 86
  Widget _buildDrawer() {
    return new Drawer(
87
      child: new IconTheme(
Adam Barth's avatar
Adam Barth committed
88
        data: const IconThemeData(color: Colors.black),
89
        child: new ListView(
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
          children: <Widget>[
            new DrawerHeader(child: new Center(child: new Text('Options'))),
            buildDrawerCheckbox("Make card labels editable", _editable, _toggleEditable),
            buildDrawerCheckbox("Fixed size cards", _fixedSizeCards, _toggleFixedSizeCards),
            buildDrawerCheckbox("Let the sun shine", _sunshine, _toggleSunshine),
            buildDrawerCheckbox("Vary font sizes", _varyFontSizes, _toggleVaryFontSizes, enabled: !_editable),
            new Divider(),
            buildDrawerColorRadioItem("Deep Purple", Colors.deepPurple, _primaryColor, _selectColor),
            buildDrawerColorRadioItem("Green", Colors.green, _primaryColor, _selectColor),
            buildDrawerColorRadioItem("Amber", Colors.amber, _primaryColor, _selectColor),
            buildDrawerColorRadioItem("Teal", Colors.teal, _primaryColor, _selectColor),
            new Divider(),
            buildDrawerDirectionRadioItem("Dismiss horizontally", DismissDirection.horizontal, _dismissDirection, _changeDismissDirection, icon: Icons.code),
            buildDrawerDirectionRadioItem("Dismiss left", DismissDirection.endToStart, _dismissDirection, _changeDismissDirection, icon: Icons.arrow_back),
            buildDrawerDirectionRadioItem("Dismiss right", DismissDirection.startToEnd, _dismissDirection, _changeDismissDirection, icon: Icons.arrow_forward),
            new Divider(),
            buildFontRadioItem("Left-align text", TextAlign.left, _textAlign, _changeTextAlign, icon: Icons.format_align_left, enabled: !_editable),
            buildFontRadioItem("Center-align text", TextAlign.center, _textAlign, _changeTextAlign, icon: Icons.format_align_center, enabled: !_editable),
            buildFontRadioItem("Right-align text", TextAlign.right, _textAlign, _changeTextAlign, icon: Icons.format_align_right, enabled: !_editable),
            new Divider(),
            new DrawerItem(
              icon: new Icon(Icons.dvr),
              onPressed: () { debugDumpApp(); debugDumpRenderTree(); },
113
              child: new Text('Dump App to Console'),
114
            ),
115 116 117
          ],
        ),
      ),
118
    );
119 120
  }

121
  String _dismissDirectionText(DismissDirection direction) {
122
    final String s = direction.toString();
123 124 125
    return "dismiss ${s.substring(s.indexOf('.') + 1)}";
  }

Hixie's avatar
Hixie committed
126 127 128 129 130 131
  void _toggleEditable() {
    setState(() {
      _editable = !_editable;
    });
  }

132 133 134 135 136 137 138
  void _toggleFixedSizeCards() {
    setState(() {
      _fixedSizeCards = !_fixedSizeCards;
      _initCardModels();
    });
  }

Hans Muller's avatar
Hans Muller committed
139 140 141 142 143 144
  void _toggleSunshine() {
    setState(() {
      _sunshine = !_sunshine;
    });
  }

145 146 147 148 149 150
  void _toggleVaryFontSizes() {
    setState(() {
      _varyFontSizes = !_varyFontSizes;
    });
  }

Hixie's avatar
Hixie committed
151
  void _selectColor(Map<int, Color> selection) {
152 153 154 155 156
    setState(() {
      _primaryColor = selection;
    });
  }

157
  void _changeDismissDirection(DismissDirection newDismissDirection) {
158 159 160
    setState(() {
      _dismissDirection = newDismissDirection;
    });
Hixie's avatar
Hixie committed
161 162
  }

163
  void _changeTextAlign(TextAlign newTextAlign) {
Hixie's avatar
Hixie committed
164
    setState(() {
165
      _textAlign = newTextAlign;
Hixie's avatar
Hixie committed
166
    });
167 168
  }

169
  Widget buildDrawerCheckbox(String label, bool value, void callback(), { bool enabled: true }) {
170
    return new DrawerItem(
171
      onPressed: enabled ? callback : null,
172 173
      child: new Row(
        children: <Widget>[
174
          new Expanded(child: new Text(label)),
175 176
          new Checkbox(
            value: value,
177 178 179 180
            onChanged: enabled ? (_) { callback(); } : null,
          ),
        ],
      ),
181 182
    );
  }
183

184
  Widget buildDrawerColorRadioItem(String label, Map<int, Color> itemValue, Map<int, Color> currentValue, ValueChanged<Map<int, Color>> onChanged, { IconData icon, bool enabled: true }) {
185
    return new DrawerItem(
Ian Hickson's avatar
Ian Hickson committed
186
      icon: new Icon(icon),
Hixie's avatar
Hixie committed
187
      onPressed: enabled ? () { onChanged(itemValue); } : null,
188 189
      child: new Row(
        children: <Widget>[
190
          new Expanded(child: new Text(label)),
191 192 193
          new Radio<Map<int, Color>>(
            value: itemValue,
            groupValue: currentValue,
194 195 196 197
            onChanged: enabled ? onChanged : null,
          ),
        ],
      ),
Hixie's avatar
Hixie committed
198 199 200
    );
  }

201
  Widget buildDrawerDirectionRadioItem(String label, DismissDirection itemValue, DismissDirection currentValue, ValueChanged<DismissDirection> onChanged, { IconData icon, bool enabled: true }) {
Hixie's avatar
Hixie committed
202
    return new DrawerItem(
Ian Hickson's avatar
Ian Hickson committed
203
      icon: new Icon(icon),
Hixie's avatar
Hixie committed
204
      onPressed: enabled ? () { onChanged(itemValue); } : null,
205 206
      child: new Row(
        children: <Widget>[
207
          new Expanded(child: new Text(label)),
208 209 210
          new Radio<DismissDirection>(
            value: itemValue,
            groupValue: currentValue,
211 212 213 214
            onChanged: enabled ? onChanged : null,
          ),
        ],
      ),
Hixie's avatar
Hixie committed
215 216 217
    );
  }

218
  Widget buildFontRadioItem(String label, TextAlign itemValue, TextAlign currentValue, ValueChanged<TextAlign> onChanged, { IconData icon, bool enabled: true }) {
Hixie's avatar
Hixie committed
219
    return new DrawerItem(
Ian Hickson's avatar
Ian Hickson committed
220
      icon: new Icon(icon),
Hixie's avatar
Hixie committed
221
      onPressed: enabled ? () { onChanged(itemValue); } : null,
222 223
      child: new Row(
        children: <Widget>[
224
          new Expanded(child: new Text(label)),
225
          new Radio<TextAlign>(
226 227
            value: itemValue,
            groupValue: currentValue,
228 229 230 231
            onChanged: enabled ? onChanged : null,
          ),
        ],
      ),
232 233 234
    );
  }

235 236 237
  Widget _buildAppBar(BuildContext context) {
    return new AppBar(
      actions: <Widget>[
238
        new Text(_dismissDirectionText(_dismissDirection))
Hans Muller's avatar
Hans Muller committed
239
      ],
240 241 242
      flexibleSpace: new Container(
        padding: const EdgeInsets.only(left: 72.0),
        height: 128.0,
243
        alignment: const FractionalOffset(0.0, 0.75),
244 245
        child: new Text('Swipe Away: ${_cardModels.length}', style: Theme.of(context).primaryTextTheme.title),
      ),
246 247 248
    );
  }

249
  Widget _buildCard(BuildContext context, int index) {
250 251
    final CardModel cardModel = _cardModels[index];
    final Widget card = new Dismissable(
252
      key: new ObjectKey(cardModel),
253
      direction: _dismissDirection,
254
      onDismissed: (DismissDirection direction) { dismissCard(cardModel); },
255
      child: new Card(
256
        color: _primaryColor[cardModel.color],
257
        child: new Container(
258
          height: cardModel.height,
259
          padding: const EdgeInsets.all(kCardMargins),
260
          child: _editable ?
Hixie's avatar
Hixie committed
261
            new Center(
262
              child: new TextField(
263
                key: new GlobalObjectKey(cardModel),
264 265 266 267
                onChanged: (InputValue value) {
                  setState(() {
                    cardModel.inputValue = value;
                  });
268 269
                },
              ),
270
            )
271
          : new DefaultTextStyle.merge(
272 273
              context: context,
              style: cardLabelStyle.copyWith(
274
                fontSize: _varyFontSizes ? 5.0 + index : null
275
              ),
276
              child: new Column(
277
                crossAxisAlignment: CrossAxisAlignment.stretch,
278 279
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
280 281 282 283 284 285
                  new Text(cardModel.inputValue.text, textAlign: _textAlign),
                ],
              ),
            ),
        ),
      ),
286 287
    );

288 289 290 291 292
    String backgroundMessage;
    switch(_dismissDirection) {
      case DismissDirection.horizontal:
        backgroundMessage = "Swipe in either direction";
        break;
293
      case DismissDirection.endToStart:
294 295
        backgroundMessage = "Swipe left to dismiss";
        break;
296
      case DismissDirection.startToEnd:
297 298 299 300 301 302
        backgroundMessage = "Swipe right to dismiss";
        break;
      default:
        backgroundMessage = "Unsupported dismissDirection";
    }

303
    // TODO(abarth): This icon is wrong in RTL.
Ian Hickson's avatar
Ian Hickson committed
304
    Widget leftArrowIcon =  new Icon(Icons.arrow_back, size: 36.0);
305
    if (_dismissDirection == DismissDirection.startToEnd)
306 307
      leftArrowIcon = new Opacity(opacity: 0.1, child: leftArrowIcon);

308
      // TODO(abarth): This icon is wrong in RTL.
Ian Hickson's avatar
Ian Hickson committed
309
    Widget rightArrowIcon =  new Icon(Icons.arrow_forward, size: 36.0);
310
    if (_dismissDirection == DismissDirection.endToStart)
311
      rightArrowIcon = new Opacity(opacity: 0.1, child: rightArrowIcon);
312

313 314 315
    final ThemeData theme = Theme.of(context);
    final TextStyle backgroundTextStyle = theme.primaryTextTheme.title;

316 317 318 319 320
    // The background Widget appears behind the Dismissable card when the card
    // moves to the left or right. The Positioned widget ensures that the
    // size of the background,card Stack will be based only on the card. The
    // Viewport ensures that when the card's resize animation occurs, the
    // background (text and icons) will just be clipped, not resized.
321
    final Widget background = new Positioned.fill(
322
      child: new Container(
323
        margin: const EdgeInsets.all(4.0),
324
        child: new SingleChildScrollView(
325 326
          child: new Container(
            height: cardModel.height,
327
            color: theme.primaryColor,
328 329 330
            child: new Row(
              children: <Widget>[
                leftArrowIcon,
331
                new Expanded(
332 333
                  child: new Text(backgroundMessage,
                    style: backgroundTextStyle,
334 335
                    textAlign: TextAlign.center,
                  ),
336
                ),
337 338 339 340 341 342
                rightArrowIcon,
              ],
            ),
          ),
        ),
      ),
343
    );
344

345 346
    return new IconTheme(
      key: cardModel.key,
Adam Barth's avatar
Adam Barth committed
347
      data: const IconThemeData(color: Colors.white),
348
      child: new Stack(children: <Widget>[background, card]),
349
    );
350 351
  }

352
  Shader _createShader(Rect bounds) {
Hans Muller's avatar
Hans Muller committed
353
    return new LinearGradient(
354 355
        begin: FractionalOffset.topLeft,
        end: FractionalOffset.bottomLeft,
Hixie's avatar
Hixie committed
356
        colors: <Color>[const Color(0x00FFFFFF), const Color(0xFFFFFFFF)],
357
        stops: <double>[0.1, 0.35],
Hans Muller's avatar
Hans Muller committed
358
    )
Adam Barth's avatar
Adam Barth committed
359
    .createShader(bounds);
Hans Muller's avatar
Hans Muller committed
360 361
  }

362
  @override
363
  Widget build(BuildContext context) {
364 365 366 367 368
    Widget cardCollection = new ListView.builder(
      itemExtent: _fixedSizeCards ? kFixedCardHeight : null,
      itemCount: _cardModels.length,
      itemBuilder: _buildCard,
    );
369

370 371 372
    if (_sunshine) {
      cardCollection = new Stack(
        children: <Widget>[
373
          new Column(children: <Widget>[new Image.network(_sunshineURL)]),
374 375
          new ShaderMask(child: cardCollection, shaderCallback: _createShader),
        ],
376 377
      );
    }
Hans Muller's avatar
Hans Muller committed
378

379
    final Widget body = new Container(
380
      padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 8.0),
381
      color: _primaryColor[50],
382
      child: cardCollection,
383 384
    );

385 386
    return new Theme(
      data: new ThemeData(
387
        primarySwatch: _primaryColor,
388 389
      ),
      child: new Scaffold(
390
        appBar: _buildAppBar(context),
391
        drawer: _buildDrawer(),
392 393
        body: body,
      ),
394 395 396 397 398
    );
  }
}

void main() {
Adam Barth's avatar
Adam Barth committed
399
  runApp(new MaterialApp(
400
    title: 'Cards',
401
    home: new CardCollection(),
402
  ));
403
}