feed.dart 7.55 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
    return new ScrollableList<FitnessItem>(
      padding: const EdgeDims.all(4.0),
      items: items,
      itemExtent: kFitnessItemHeight,
21
      itemBuilder: (BuildContext context, FitnessItem item, int index) => 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
class FeedFragment extends StatefulComponent {
Ian Hickson's avatar
Ian Hickson committed
47
  FeedFragment({ this.userData, this.onItemCreated, this.onItemDeleted });
48

49 50 51
  final UserData userData;
  final FitnessItemHandler onItemCreated;
  final FitnessItemHandler onItemDeleted;
52

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

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

Adam Barth's avatar
Adam Barth committed
60
  void _handleFitnessModeChange(FitnessMode value) {
61 62 63
    setState(() {
      _fitnessMode = value;
    });
Ian Hickson's avatar
Ian Hickson committed
64
    Navigator.of(context).pop();
65 66
  }

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

Adam Barth's avatar
Adam Barth committed
94
  void _handleShowSettings() {
Ian Hickson's avatar
Ian Hickson committed
95 96
    Navigator.of(context)..pop()
                         ..pushNamed('/settings');
97 98 99 100 101
  }

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

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

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

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

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

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

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

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

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

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

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

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

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