feed.dart 8.69 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 22
        items: items,
        itemHeight: kFitnessItemHeight,
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
        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;

  void syncFields(DialogMenuItem source) {
    children = source.children;
    onPressed = source.onPressed;
    super.syncFields(source);
  }

  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 65 66
  List<FitnessItem> userData;
  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 syncFields(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 103 104
      navigator: navigator,
      children: [
        new DrawerHeader(children: [new Text('Fitness')]),
        new DrawerItem(
105
          icon: 'action/view_list',
106 107 108
          onPressed: () => _handleFitnessModeChange(FitnessMode.feed),
          selected: _fitnessMode == FitnessMode.feed,
          children: [new Text('Feed')]),
109
        new DrawerItem(
110 111 112 113
          icon: 'action/assessment',
          onPressed: () => _handleFitnessModeChange(FitnessMode.chart),
          selected: _fitnessMode == FitnessMode.chart,
          children: [new Text('Chart')]),
114 115 116 117 118 119 120 121 122 123 124 125 126
        new DrawerDivider(),
        new DrawerItem(
          icon: 'action/settings',
          onPressed: _handleShowSettings,
          children: [new Text('Settings')]),
        new DrawerItem(
          icon: 'action/help',
          children: [new Text('Help & Feedback')])
     ]
    );
  }

  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 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
  Widget buildChart() {
    double startX;
    double endX;
    double startY;
    double endY;
    List<Point> dataSet = new List<Point>();
    for (FitnessItem item in userData) {
      if (item is Measurement) {
          double x = item.when.millisecondsSinceEpoch.toDouble();
          double y = item.weight;
          if (startX == null)
            startX = x;
          endX = x;
          if (startY == null || startY > y)
            startY = y;
          if (endY == null || endY < y)
            endY = y;
          dataSet.add(new Point(x, y));
      }
    }
    playfair.ChartData data = new playfair.ChartData(
      startX: startX,
      startY: startY,
      endX: endX,
      endY: endY,
201 202 203
      dataSet: dataSet,
      numHorizontalGridlines: 5,
      roundToPlaces: 1
204 205 206 207
    );
    return new playfair.Chart(data: data);
  }

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

  void _handleUndo() {
236
    onItemCreated(_undoItem);
237
    setState(() {
238
      _undoItem = null;
239 240 241 242
      _isShowingSnackBar = false;
    });
  }

243
  Anchor _snackBarAnchor = new Anchor();
244
  Widget buildSnackBar() {
245
    if (_snackBarStatus == AnimationStatus.dismissed)
246 247
      return null;
    return new SnackBar(
Collin Jackson's avatar
Collin Jackson committed
248
      showing: _isShowingSnackBar,
249
      anchor: _snackBarAnchor,
250
      content: new Text("Item deleted."),
251 252
      actions: [new SnackBarAction(label: "UNDO", onPressed: _handleUndo)],
      onDismissed: () { setState(() { _snackBarStatus = AnimationStatus.dismissed; }); }
253 254 255
    );
  }

256
  void _handleActionButtonPressed() {
257 258 259
    showDialog(navigator, (navigator) => new AddItemDialog(navigator)).then((routeName) {
      if (routeName != null)
        navigator.pushNamed(routeName);
260 261 262 263 264 265
    });
  }

  Widget buildFloatingActionButton() {
    switch (_fitnessMode) {
      case FitnessMode.feed:
266 267 268 269 270
        return _snackBarAnchor.build(
          new FloatingActionButton(
            child: new Icon(type: 'content/add', size: 24),
            onPressed: _handleActionButtonPressed
          ));
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295
      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;

  void syncFields(AddItemDialog source) {
    this.navigator = source.navigator;
  }

296 297 298 299 300 301 302
  // TODO(jackson): Internationalize
  static final Map<String, String> _labels = {
    '/measurements/new': 'Measure',
    '/meals/new': 'Eat',
  };

  String _addItemRoute = _labels.keys.first;
303 304 305 306 307 308

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

310
  Widget build() {
311
    List<Widget> menuItems = [];
312
    for(String routeName in _labels.keys) {
313
      menuItems.add(new DialogMenuItem([
314
        new Flexible(child: new Text(_labels[routeName])),
315 316 317
        new Radio(value: routeName, groupValue: _addItemRoute, onChanged: _handleAddItemRouteChanged),
      ], onPressed: () => _handleAddItemRouteChanged(routeName)));
    }
318
    return new Dialog(
319 320
      title: new Text("What are you doing?"),
      content: new ScrollableBlock(menuItems),
321 322 323 324 325 326 327
      onDismiss: navigator.pop,
      actions: [
        new FlatButton(
          child: new Text('CANCEL'),
          onPressed: navigator.pop
        ),
        new FlatButton(
328
          child: new Text('ADD'),
329
          onPressed: () {
330
            navigator.pop(_addItemRoute);
331 332 333 334
          }
        ),
      ]
    );
335 336
  }
}