feed.dart 9.12 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 Component {
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 17 18

  Widget build() {
    return new Material(
      type: MaterialType.canvas,
19
      child: new ScrollableList<FitnessItem>(
20
        padding: const EdgeDims.all(4.0),
21
        items: items,
22
        itemExtent: kFitnessItemHeight,
23 24 25 26 27 28 29 30 31 32 33 34
        itemBuilder: (item) => item.toRow(onDismissed: onDismissed)
      )
    );
  }
}

class DialogMenuItem extends ButtonBase {
  DialogMenuItem(this.children, { Key key, this.onPressed }) : super(key: key);

  List<Widget> children;
  Function onPressed;

35
  void syncConstructorArguments(DialogMenuItem source) {
36 37
    children = source.children;
    onPressed = source.onPressed;
38
    super.syncConstructorArguments(source);
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
  }

  Widget buildContent() {
    return new Listener(
      onGestureTap: (_) {
        if (onPressed != null)
          onPressed();
      },
      child: new Container(
        height: 48.0,
        child: new InkWell(
          child: new Padding(
            padding: const EdgeDims.symmetric(horizontal: 16.0),
            child: new Flex(children)
          )
54 55 56 57 58 59
        )
      )
    );
  }
}

60 61
class FeedFragment extends StatefulComponent {
  FeedFragment({ this.navigator, this.userData, this.onItemCreated, this.onItemDeleted });
62 63

  Navigator navigator;
64
  UserData userData;
65 66
  FitnessItemHandler onItemCreated;
  FitnessItemHandler onItemDeleted;
67

68
  FitnessMode _fitnessMode = FitnessMode.feed;
69 70 71 72 73 74 75

  void initState() {
    // if (debug)
    //   new Timer(new Duration(seconds: 1), dumpState);
    super.initState();
  }

76
  void syncConstructorArguments(FeedFragment source) {
77 78
    navigator = source.navigator;
    userData = source.userData;
79 80
    onItemCreated = source.onItemCreated;
    onItemDeleted = source.onItemDeleted;
81 82
  }

83
  AnimationStatus _snackBarStatus = AnimationStatus.dismissed;
84 85
  bool _isShowingSnackBar = false;

86
  EventDisposition _handleFitnessModeChange(FitnessMode value) {
87 88
    setState(() {
      _fitnessMode = value;
89
      _drawerShowing = false;
90
    });
91
    return EventDisposition.processed;
92 93 94
  }

  Drawer buildDrawer() {
Adam Barth's avatar
Adam Barth committed
95
    if (_drawerStatus == AnimationStatus.dismissed)
96 97 98 99
      return null;
    return new Drawer(
      showing: _drawerShowing,
      level: 3,
100
      onDismissed: _handleDrawerDismissed,
101 102
      navigator: navigator,
      children: [
103
        new DrawerHeader(child: new Text('Fitness')),
104
        new DrawerItem(
105
          icon: 'action/view_list',
106 107
          onPressed: () => _handleFitnessModeChange(FitnessMode.feed),
          selected: _fitnessMode == FitnessMode.feed,
108
          child: new Text('Feed')),
109
        new DrawerItem(
110 111 112
          icon: 'action/assessment',
          onPressed: () => _handleFitnessModeChange(FitnessMode.chart),
          selected: _fitnessMode == FitnessMode.chart,
113
          child: new Text('Chart')),
114 115 116 117
        new DrawerDivider(),
        new DrawerItem(
          icon: 'action/settings',
          onPressed: _handleShowSettings,
118
          child: new Text('Settings')),
119 120
        new DrawerItem(
          icon: 'action/help',
121 122
          child: new Text('Help & Feedback'))
      ]
123 124 125 126
    );
  }

  bool _drawerShowing = false;
Adam Barth's avatar
Adam Barth committed
127
  AnimationStatus _drawerStatus = AnimationStatus.dismissed;
128 129 130 131

  void _handleOpenDrawer() {
    setState(() {
      _drawerShowing = true;
Adam Barth's avatar
Adam Barth committed
132
      _drawerStatus = AnimationStatus.forward;
133 134 135
    });
  }

136
  void _handleDrawerDismissed() {
137
    setState(() {
138
      _drawerStatus = AnimationStatus.dismissed;
139 140 141
    });
  }

142
  EventDisposition _handleShowSettings() {
143 144
    navigator.pop();
    navigator.pushNamed('/settings');
145
    return EventDisposition.processed;
146 147 148 149 150
  }

  // TODO(jackson): We should be localizing
  String get fitnessModeTitle {
    switch(_fitnessMode) {
151 152
      case FitnessMode.feed: return "Feed";
      case FitnessMode.chart: return "Chart";
153 154 155 156 157 158 159 160 161 162 163 164
    }
  }

  Widget buildToolBar() {
    return new ToolBar(
      left: new IconButton(
        icon: "navigation/menu",
        onPressed: _handleOpenDrawer),
      center: new Text(fitnessModeTitle)
    );
  }

165
  FitnessItem _undoItem;
166

167 168
  void _handleItemDismissed(FitnessItem item) {
    onItemDeleted(item);
169
    setState(() {
170
      _undoItem = item;
171
      _isShowingSnackBar = true;
172
      _snackBarStatus = AnimationStatus.forward;
173 174 175
    });
  }

176 177 178 179 180 181
  Widget buildChart() {
    double startX;
    double endX;
    double startY;
    double endY;
    List<Point> dataSet = new List<Point>();
182
    for (FitnessItem item in userData.items) {
183 184 185
      if (item is Measurement) {
          double x = item.when.millisecondsSinceEpoch.toDouble();
          double y = item.weight;
186
          if (startX == null || startX > x)
187
            startX = x;
188
          if (endX == null || endX < x)
189 190 191 192 193 194 195 196
          endX = x;
          if (startY == null || startY > y)
            startY = y;
          if (endY == null || endY < y)
            endY = y;
          dataSet.add(new Point(x, y));
      }
    }
197
    if (userData.goalWeight != null && userData.goalWeight > 0.0) {
198 199 200
      startY = math.min(startY, userData.goalWeight);
      endY = math.max(endY, userData.goalWeight);
    }
201 202 203 204 205
    playfair.ChartData data = new playfair.ChartData(
      startX: startX,
      startY: startY,
      endX: endX,
      endY: endY,
206 207
      dataSet: dataSet,
      numHorizontalGridlines: 5,
208 209 210
      roundToPlaces: 1,
      indicatorLine: userData.goalWeight,
      indicatorText: "GOAL WEIGHT"
211 212 213 214
    );
    return new playfair.Chart(data: data);
  }

215 216
  Widget buildBody() {
    TextStyle style = Theme.of(this).text.title;
217 218
    if (userData == null)
      return new Material(type: MaterialType.canvas);
219
    if (userData.items.length == 0)
220 221 222 223 224 225 226
      return new Material(
        type: MaterialType.canvas,
        child: new Flex(
          [new Text("No data yet.\nAdd some!", style: style)],
          justifyContent: FlexJustifyContent.center
        )
      );
227
    switch (_fitnessMode) {
228
      case FitnessMode.feed:
229
        return new FitnessItemList(
230
          items: userData.items.reversed.toList(),
231
          onDismissed: _handleItemDismissed
232
        );
233
      case FitnessMode.chart:
234 235
        return new Material(
          type: MaterialType.canvas,
236 237 238 239
          child: new Container(
            padding: const EdgeDims.all(20.0),
            child: buildChart()
          )
240 241 242 243 244
        );
    }
  }

  void _handleUndo() {
245
    onItemCreated(_undoItem);
246
    setState(() {
247
      _undoItem = null;
248 249 250 251
      _isShowingSnackBar = false;
    });
  }

252
  Anchor _snackBarAnchor = new Anchor();
253
  Widget buildSnackBar() {
254
    if (_snackBarStatus == AnimationStatus.dismissed)
255 256
      return null;
    return new SnackBar(
Collin Jackson's avatar
Collin Jackson committed
257
      showing: _isShowingSnackBar,
258
      anchor: _snackBarAnchor,
259
      content: new Text("Item deleted."),
260 261
      actions: [new SnackBarAction(label: "UNDO", onPressed: _handleUndo)],
      onDismissed: () { setState(() { _snackBarStatus = AnimationStatus.dismissed; }); }
262 263 264
    );
  }

265
  void _handleActionButtonPressed() {
266 267 268
    showDialog(navigator, (navigator) => new AddItemDialog(navigator)).then((routeName) {
      if (routeName != null)
        navigator.pushNamed(routeName);
269 270 271 272 273 274
    });
  }

  Widget buildFloatingActionButton() {
    switch (_fitnessMode) {
      case FitnessMode.feed:
275 276 277 278 279
        return _snackBarAnchor.build(
          new FloatingActionButton(
            child: new Icon(type: 'content/add', size: 24),
            onPressed: _handleActionButtonPressed
          ));
280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300
      case FitnessMode.chart:
        return null;
    }
  }

  Widget build() {
    return new Scaffold(
      toolbar: buildToolBar(),
      body: buildBody(),
      snackBar: buildSnackBar(),
      floatingActionButton: buildFloatingActionButton(),
      drawer: buildDrawer()
    );
  }
}

class AddItemDialog extends StatefulComponent {
  AddItemDialog(this.navigator);

  Navigator navigator;

301
  void syncConstructorArguments(AddItemDialog source) {
302 303 304
    this.navigator = source.navigator;
  }

305 306 307 308 309 310 311
  // TODO(jackson): Internationalize
  static final Map<String, String> _labels = {
    '/measurements/new': 'Measure',
    '/meals/new': 'Eat',
  };

  String _addItemRoute = _labels.keys.first;
312 313 314 315 316 317

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

319
  Widget build() {
320
    List<Widget> menuItems = [];
321
    for(String routeName in _labels.keys) {
322
      menuItems.add(new DialogMenuItem([
323
        new Flexible(child: new Text(_labels[routeName])),
324 325 326
        new Radio(value: routeName, groupValue: _addItemRoute, onChanged: _handleAddItemRouteChanged),
      ], onPressed: () => _handleAddItemRouteChanged(routeName)));
    }
327
    return new Dialog(
328
      title: new Text("What are you doing?"),
329
      content: new Block(menuItems),
330 331 332 333 334 335 336
      onDismiss: navigator.pop,
      actions: [
        new FlatButton(
          child: new Text('CANCEL'),
          onPressed: navigator.pop
        ),
        new FlatButton(
337
          child: new Text('ADD'),
338
          onPressed: () {
339
            navigator.pop(_addItemRoute);
340 341 342 343
          }
        ),
      ]
    );
344 345
  }
}