// 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. part of fitness; class FitnessItemList extends Component { FitnessItemList({ Key key, this.items, this.onDismissed }) : super(key: key) { assert(items != null); assert(onDismissed != null); } final List<FitnessItem> items; final FitnessItemHandler onDismissed; Widget build() { return new Material( type: MaterialType.canvas, child: new ScrollableList<FitnessItem>( padding: const EdgeDims.all(4.0), items: items, itemExtent: kFitnessItemHeight, 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 syncConstructorArguments(DialogMenuItem source) { children = source.children; onPressed = source.onPressed; super.syncConstructorArguments(source); } Widget buildContent() { return new GestureDetector( onTap: onPressed, child: new Container( height: 48.0, child: new InkWell( child: new Padding( padding: const EdgeDims.symmetric(horizontal: 16.0), child: new Row(children) ) ) ) ); } } class FeedFragment extends StatefulComponent { FeedFragment({ this.navigator, this.userData, this.onItemCreated, this.onItemDeleted }); Navigator navigator; UserData userData; FitnessItemHandler onItemCreated; FitnessItemHandler onItemDeleted; FitnessMode _fitnessMode = FitnessMode.feed; void initState() { // if (debug) // new Timer(new Duration(seconds: 1), dumpState); super.initState(); } void syncConstructorArguments(FeedFragment source) { navigator = source.navigator; userData = source.userData; onItemCreated = source.onItemCreated; onItemDeleted = source.onItemDeleted; } AnimationStatus _snackBarStatus = AnimationStatus.dismissed; bool _isShowingSnackBar = false; EventDisposition _handleFitnessModeChange(FitnessMode value) { setState(() { _fitnessMode = value; _drawerShowing = false; }); return EventDisposition.processed; } Drawer buildDrawer() { if (_drawerStatus == AnimationStatus.dismissed) return null; return new Drawer( showing: _drawerShowing, level: 3, onDismissed: _handleDrawerDismissed, navigator: navigator, children: [ new DrawerHeader(child: new Text('Fitness')), new DrawerItem( icon: 'action/view_list', onPressed: () => _handleFitnessModeChange(FitnessMode.feed), selected: _fitnessMode == FitnessMode.feed, child: new Text('Feed')), new DrawerItem( icon: 'action/assessment', onPressed: () => _handleFitnessModeChange(FitnessMode.chart), selected: _fitnessMode == FitnessMode.chart, child: new Text('Chart')), new DrawerDivider(), new DrawerItem( icon: 'action/settings', onPressed: _handleShowSettings, child: new Text('Settings')), new DrawerItem( icon: 'action/help', child: new Text('Help & Feedback')) ] ); } bool _drawerShowing = false; AnimationStatus _drawerStatus = AnimationStatus.dismissed; void _handleOpenDrawer() { setState(() { _drawerShowing = true; _drawerStatus = AnimationStatus.forward; }); } void _handleDrawerDismissed() { setState(() { _drawerStatus = AnimationStatus.dismissed; }); } EventDisposition _handleShowSettings() { navigator.pop(); navigator.pushNamed('/settings'); return EventDisposition.processed; } // TODO(jackson): We should be localizing String get fitnessModeTitle { switch(_fitnessMode) { case FitnessMode.feed: return "Feed"; case FitnessMode.chart: return "Chart"; } } Widget buildToolBar() { return new ToolBar( left: new IconButton( icon: "navigation/menu", onPressed: _handleOpenDrawer), center: new Text(fitnessModeTitle) ); } FitnessItem _undoItem; void _handleItemDismissed(FitnessItem item) { onItemDeleted(item); setState(() { _undoItem = item; _isShowingSnackBar = true; _snackBarStatus = AnimationStatus.forward; }); } Widget buildChart() { double startX; double endX; double startY; double endY; List<Point> dataSet = new List<Point>(); for (FitnessItem item in userData.items) { if (item is Measurement) { double x = item.when.millisecondsSinceEpoch.toDouble(); double y = item.weight; if (startX == null || startX > x) startX = x; if (endX == null || endX < x) endX = x; if (startY == null || startY > y) startY = y; if (endY == null || endY < y) endY = y; dataSet.add(new Point(x, y)); } } if (userData.goalWeight != null && userData.goalWeight > 0.0) { startY = math.min(startY, userData.goalWeight); endY = math.max(endY, userData.goalWeight); } playfair.ChartData data = new playfair.ChartData( startX: startX, startY: startY, endX: endX, endY: endY, dataSet: dataSet, numHorizontalGridlines: 5, roundToPlaces: 1, indicatorLine: userData.goalWeight, indicatorText: "GOAL WEIGHT" ); return new playfair.Chart(data: data); } Widget buildBody() { TextStyle style = Theme.of(this).text.title; if (userData == null) return new Material(type: MaterialType.canvas); if (userData.items.length == 0) return new Material( type: MaterialType.canvas, child: new Row( [new Text("No data yet.\nAdd some!", style: style)], justifyContent: FlexJustifyContent.center ) ); switch (_fitnessMode) { case FitnessMode.feed: return new FitnessItemList( items: userData.items.reversed.toList(), onDismissed: _handleItemDismissed ); case FitnessMode.chart: return new Material( type: MaterialType.canvas, child: new Container( padding: const EdgeDims.all(20.0), child: buildChart() ) ); } } void _handleUndo() { onItemCreated(_undoItem); setState(() { _undoItem = null; _isShowingSnackBar = false; }); } Anchor _snackBarAnchor = new Anchor(); Widget buildSnackBar() { if (_snackBarStatus == AnimationStatus.dismissed) return null; return new SnackBar( showing: _isShowingSnackBar, anchor: _snackBarAnchor, content: new Text("Item deleted."), actions: [new SnackBarAction(label: "UNDO", onPressed: _handleUndo)], onDismissed: () { setState(() { _snackBarStatus = AnimationStatus.dismissed; }); } ); } void _handleActionButtonPressed() { showDialog(navigator, (navigator) => new AddItemDialog(navigator)).then((routeName) { if (routeName != null) navigator.pushNamed(routeName); }); } Widget buildFloatingActionButton() { switch (_fitnessMode) { case FitnessMode.feed: return _snackBarAnchor.build( new FloatingActionButton( child: new Icon(type: 'content/add', size: 24), onPressed: _handleActionButtonPressed )); 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 syncConstructorArguments(AddItemDialog source) { this.navigator = source.navigator; } // TODO(jackson): Internationalize static final Map<String, String> _labels = { '/measurements/new': 'Measure', '/meals/new': 'Eat', }; String _addItemRoute = _labels.keys.first; void _handleAddItemRouteChanged(String routeName) { setState(() { _addItemRoute = routeName; }); } Widget build() { List<Widget> menuItems = []; for(String routeName in _labels.keys) { menuItems.add(new DialogMenuItem([ new Flexible(child: new Text(_labels[routeName])), new Radio(value: routeName, groupValue: _addItemRoute, onChanged: _handleAddItemRouteChanged), ], onPressed: () => _handleAddItemRouteChanged(routeName))); } return new Dialog( title: new Text("What are you doing?"), content: new Block(menuItems), onDismiss: navigator.pop, actions: [ new FlatButton( child: new Text('CANCEL'), onPressed: navigator.pop ), new FlatButton( child: new Text('ADD'), onPressed: () { navigator.pop(_addItemRoute); } ), ] ); } }