feed.dart 7.53 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
part of fitness;
6

7
class FitnessItemList extends StatelessComponent {
8 9 10 11
  FitnessItemList({ Key key, this.items, this.onDismissed }) : super(key: key) {
    assert(items != null);
    assert(onDismissed != null);
  }
12

13 14
  final List<FitnessItem> items;
  final FitnessItemHandler onDismissed;
15

16
  Widget build(BuildContext context) {
17 18 19 20 21
    return new ScrollableList<FitnessItem>(
      padding: const EdgeDims.all(4.0),
      items: items,
      itemExtent: kFitnessItemHeight,
      itemBuilder: (_, item) => item.toRow(onDismissed: onDismissed)
22 23 24 25
    );
  }
}

26
class DialogMenuItem extends StatelessComponent {
27 28 29 30 31
  DialogMenuItem(this.children, { Key key, this.onPressed }) : super(key: key);

  List<Widget> children;
  Function onPressed;

32
  Widget build(BuildContext context) {
33 34 35 36 37 38 39
    return new Container(
      height: 48.0,
      child: new InkWell(
        onTap: onPressed,
        child: new Padding(
          padding: const EdgeDims.symmetric(horizontal: 16.0),
          child: new Row(children)
40 41 42 43 44 45
        )
      )
    );
  }
}

46 47
class FeedFragment extends StatefulComponent {
  FeedFragment({ this.navigator, this.userData, this.onItemCreated, this.onItemDeleted });
48

49 50 51 52
  final NavigatorState navigator;
  final UserData userData;
  final FitnessItemHandler onItemCreated;
  final FitnessItemHandler onItemDeleted;
53

54 55
  FeedFragmentState createState() => new FeedFragmentState();
}
56

57
class FeedFragmentState extends State<FeedFragment> {
58
  final GlobalKey<PlaceholderState> _snackBarPlaceholderKey = new GlobalKey<PlaceholderState>();
59
  FitnessMode _fitnessMode = FitnessMode.feed;
60

Adam Barth's avatar
Adam Barth committed
61
  void _handleFitnessModeChange(FitnessMode value) {
62 63 64
    setState(() {
      _fitnessMode = value;
    });
65
    config.navigator.pop();
66 67
  }

68 69
  void _showDrawer() {
    showDrawer(
Adam Barth's avatar
Adam Barth committed
70
      context: context,
71
      child: new Block([
72
        new DrawerHeader(child: new Text('Fitness')),
73
        new DrawerItem(
74
          icon: 'action/view_list',
75 76
          onPressed: () => _handleFitnessModeChange(FitnessMode.feed),
          selected: _fitnessMode == FitnessMode.feed,
77
          child: new Text('Feed')),
78
        new DrawerItem(
79 80 81
          icon: 'action/assessment',
          onPressed: () => _handleFitnessModeChange(FitnessMode.chart),
          selected: _fitnessMode == FitnessMode.chart,
82
          child: new Text('Chart')),
83 84 85 86
        new DrawerDivider(),
        new DrawerItem(
          icon: 'action/settings',
          onPressed: _handleShowSettings,
87
          child: new Text('Settings')),
88 89
        new DrawerItem(
          icon: 'action/help',
90
          child: new Text('Help & Feedback'))
91
      ])
92 93 94
    );
  }

Adam Barth's avatar
Adam Barth committed
95
  void _handleShowSettings() {
96 97
    config.navigator.pop();
    config.navigator.pushNamed('/settings');
98 99 100 101 102
  }

  // TODO(jackson): We should be localizing
  String get fitnessModeTitle {
    switch(_fitnessMode) {
103 104
      case FitnessMode.feed: return "Feed";
      case FitnessMode.chart: return "Chart";
105 106 107 108 109 110 111
    }
  }

  Widget buildToolBar() {
    return new ToolBar(
      left: new IconButton(
        icon: "navigation/menu",
112
        onPressed: _showDrawer),
113 114 115 116
      center: new Text(fitnessModeTitle)
    );
  }

117
  void _handleItemDismissed(FitnessItem item) {
118
    config.onItemDeleted(item);
119
    showSnackBar(
Adam Barth's avatar
Adam Barth committed
120
      context: context,
121 122 123 124 125 126 127
      placeholderKey: _snackBarPlaceholderKey,
      content: new Text("Item deleted."),
      actions: [new SnackBarAction(label: "UNDO", onPressed: () {
        config.onItemCreated(item);
        config.navigator.pop();
      })]
    );
128 129
  }

130 131 132 133 134 135
  Widget buildChart() {
    double startX;
    double endX;
    double startY;
    double endY;
    List<Point> dataSet = new List<Point>();
136
    for (FitnessItem item in config.userData.items) {
137 138 139
      if (item is Measurement) {
          double x = item.when.millisecondsSinceEpoch.toDouble();
          double y = item.weight;
140
          if (startX == null || startX > x)
141
            startX = x;
142
          if (endX == null || endX < x)
143 144 145 146 147 148 149 150
          endX = x;
          if (startY == null || startY > y)
            startY = y;
          if (endY == null || endY < y)
            endY = y;
          dataSet.add(new Point(x, y));
      }
    }
151 152 153
    if (config.userData.goalWeight != null && config.userData.goalWeight > 0.0) {
      startY = math.min(startY, config.userData.goalWeight);
      endY = math.max(endY, config.userData.goalWeight);
154
    }
155 156 157 158 159
    playfair.ChartData data = new playfair.ChartData(
      startX: startX,
      startY: startY,
      endX: endX,
      endY: endY,
160 161
      dataSet: dataSet,
      numHorizontalGridlines: 5,
162
      roundToPlaces: 1,
163
      indicatorLine: config.userData.goalWeight,
164
      indicatorText: "GOAL WEIGHT"
165 166 167 168
    );
    return new playfair.Chart(data: data);
  }

169
  Widget buildBody() {
170 171
    TextStyle style = Theme.of(context).text.title;
    if (config.userData == null)
172 173 174 175 176
      return new Container();
    if (config.userData.items.length == 0) {
      return new Row(
        [new Text("No data yet.\nAdd some!", style: style)],
        justifyContent: FlexJustifyContent.center
177
      );
178
    }
179
    switch (_fitnessMode) {
180
      case FitnessMode.feed:
181
        return new FitnessItemList(
182
          items: config.userData.items.reversed.toList(),
183
          onDismissed: _handleItemDismissed
184
        );
185
      case FitnessMode.chart:
186 187 188
        return new Container(
          padding: const EdgeDims.all(20.0),
          child: buildChart()
189 190 191 192
        );
    }
  }

193
  void _handleActionButtonPressed() {
Adam Barth's avatar
Adam Barth committed
194
    showDialog(context: context, child: new AddItemDialog()).then((routeName) {
195
      if (routeName != null)
196
        config.navigator.pushNamed(routeName);
197 198 199 200 201 202
    });
  }

  Widget buildFloatingActionButton() {
    switch (_fitnessMode) {
      case FitnessMode.feed:
Adam Barth's avatar
Adam Barth committed
203 204 205 206
        return new FloatingActionButton(
          child: new Icon(type: 'content/add', size: 24),
          onPressed: _handleActionButtonPressed
        );
207 208 209 210 211
      case FitnessMode.chart:
        return null;
    }
  }

212
  Widget build(BuildContext context) {
213
    return new Scaffold(
Adam Barth's avatar
Adam Barth committed
214
      toolBar: buildToolBar(),
215
      body: buildBody(),
216
      snackBar: new Placeholder(key: _snackBarPlaceholderKey),
217
      floatingActionButton: buildFloatingActionButton()
218 219 220 221 222
    );
  }
}

class AddItemDialog extends StatefulComponent {
223 224
  AddItemDialogState createState() => new AddItemDialogState();
}
225

226
class AddItemDialogState extends State<AddItemDialog> {
227 228 229 230 231 232 233
  // TODO(jackson): Internationalize
  static final Map<String, String> _labels = {
    '/measurements/new': 'Measure',
    '/meals/new': 'Eat',
  };

  String _addItemRoute = _labels.keys.first;
234 235 236 237 238 239

  void _handleAddItemRouteChanged(String routeName) {
    setState(() {
        _addItemRoute = routeName;
    });
  }
240

241
  Widget build(BuildContext context) {
242
    List<Widget> menuItems = [];
243
    for(String routeName in _labels.keys) {
244
      menuItems.add(new DialogMenuItem([
245
        new Flexible(child: new Text(_labels[routeName])),
246 247 248
        new Radio(value: routeName, groupValue: _addItemRoute, onChanged: _handleAddItemRouteChanged),
      ], onPressed: () => _handleAddItemRouteChanged(routeName)));
    }
249
    return new Dialog(
250
      title: new Text("What are you doing?"),
251
      content: new Block(menuItems),
Adam Barth's avatar
Adam Barth committed
252 253 254
      onDismiss: () {
        Navigator.of(context).pop();
      },
255 256 257
      actions: [
        new FlatButton(
          child: new Text('CANCEL'),
Adam Barth's avatar
Adam Barth committed
258 259 260
          onPressed: () {
            Navigator.of(context).pop();
          }
261 262
        ),
        new FlatButton(
263
          child: new Text('ADD'),
264
          onPressed: () {
Adam Barth's avatar
Adam Barth committed
265
            Navigator.of(context).pop(_addItemRoute);
266 267 268 269
          }
        ),
      ]
    );
270 271
  }
}