Commit 1f768ce2 authored by Collin Jackson's avatar Collin Jackson

First pass at adding meal tracking to fitness app

parent 6b748e65
...@@ -4,13 +4,12 @@ ...@@ -4,13 +4,12 @@
import 'package:sky/painting/text_style.dart'; import 'package:sky/painting/text_style.dart';
import 'package:sky/widgets/basic.dart'; import 'package:sky/widgets/basic.dart';
import 'package:sky/widgets/card.dart'; import 'package:sky/widgets/dialog.dart';
import 'package:sky/widgets/default_text_style.dart';
import 'package:sky/widgets/dismissable.dart';
import 'package:sky/widgets/drawer.dart'; import 'package:sky/widgets/drawer.dart';
import 'package:sky/widgets/drawer_divider.dart'; import 'package:sky/widgets/drawer_divider.dart';
import 'package:sky/widgets/drawer_header.dart'; import 'package:sky/widgets/drawer_header.dart';
import 'package:sky/widgets/drawer_item.dart'; import 'package:sky/widgets/drawer_item.dart';
import 'package:sky/widgets/flat_button.dart';
import 'package:sky/widgets/floating_action_button.dart'; import 'package:sky/widgets/floating_action_button.dart';
import 'package:sky/widgets/icon_button.dart'; import 'package:sky/widgets/icon_button.dart';
import 'package:sky/widgets/icon.dart'; import 'package:sky/widgets/icon.dart';
...@@ -24,22 +23,24 @@ import 'package:sky/widgets/tool_bar.dart'; ...@@ -24,22 +23,24 @@ import 'package:sky/widgets/tool_bar.dart';
import 'package:sky/widgets/widget.dart'; import 'package:sky/widgets/widget.dart';
import 'fitness_types.dart'; import 'fitness_types.dart';
import 'fitness_item.dart';
import 'measurement.dart'; import 'measurement.dart';
import 'meal.dart';
class MeasurementList extends Component { class FitnessItemList extends Component {
MeasurementList({ String key, this.measurements, this.onDismissed }) : super(key: key); FitnessItemList({ String key, this.items, this.onDismissed }) : super(key: key);
final List<Measurement> measurements; final List<FitnessItem> items;
final MeasurementHandler onDismissed; final FitnessItemHandler onDismissed;
Widget build() { Widget build() {
return new Material( return new Material(
type: MaterialType.canvas, type: MaterialType.canvas,
child: new ScrollableList<Measurement>( child: new ScrollableList<FitnessItem>(
items: measurements, items: items,
itemHeight: MeasurementRow.kHeight, itemHeight: kFitnessItemHeight,
itemBuilder: (measurement) => new MeasurementRow( itemBuilder: (item) => new MeasurementRow(
measurement: measurement, measurement: item as Measurement,
onDismissed: onDismissed onDismissed: onDismissed
) )
) )
...@@ -47,60 +48,16 @@ class MeasurementList extends Component { ...@@ -47,60 +48,16 @@ class MeasurementList extends Component {
} }
} }
class MeasurementRow extends Component { class FeedFragment extends StatefulComponent {
MeasurementRow({ Measurement measurement, this.onDismissed }) : this.measurement = measurement, super(key: measurement.when.toString()); FeedFragment({ this.navigator, this.userData, this.onItemCreated, this.onItemDeleted });
final Measurement measurement;
final MeasurementHandler onDismissed;
static const double kHeight = 79.0;
Widget build() {
List<Widget> children = [
new Flexible(
child: new Text(
measurement.displayWeight,
style: const TextStyle(textAlign: TextAlign.right)
)
),
new Flexible(
child: new Text(
measurement.displayDate,
style: Theme.of(this).text.caption.copyWith(textAlign: TextAlign.right)
)
)
];
return new Dismissable(
key: measurement.when.toString(),
onDismissed: () => onDismissed(measurement),
child: new Card(
child: new Container(
height: kHeight,
padding: const EdgeDims.all(8.0),
child: new Flex(
children,
alignItems: FlexAlignItems.baseline,
textBaseline: DefaultTextStyle.of(this).textBaseline
)
)
)
);
}
}
class HomeFragment extends StatefulComponent {
HomeFragment({ this.navigator, this.userData, this.onMeasurementCreated, this.onMeasurementDeleted });
Navigator navigator; Navigator navigator;
List<Measurement> userData; List<FitnessItem> userData;
MeasurementHandler onMeasurementCreated; FitnessItemHandler onItemCreated;
MeasurementHandler onMeasurementDeleted; FitnessItemHandler onItemDeleted;
FitnessMode _fitnessMode = FitnessMode.measure; FitnessMode _fitnessMode = FitnessMode.feed;
void initState() { void initState() {
// if (debug) // if (debug)
...@@ -108,19 +65,19 @@ class HomeFragment extends StatefulComponent { ...@@ -108,19 +65,19 @@ class HomeFragment extends StatefulComponent {
super.initState(); super.initState();
} }
void syncFields(HomeFragment source) { void syncFields(FeedFragment source) {
navigator = source.navigator; navigator = source.navigator;
userData = source.userData; userData = source.userData;
onMeasurementCreated = source.onMeasurementCreated; onItemCreated = source.onItemCreated;
onMeasurementDeleted = source.onMeasurementDeleted; onItemDeleted = source.onItemDeleted;
} }
bool _isShowingSnackBar = false; bool _isShowingSnackBar = false;
bool _isRunning = false;
void _handleFitnessModeChange(FitnessMode value) { void _handleFitnessModeChange(FitnessMode value) {
setState(() { setState(() {
_fitnessMode = value; _fitnessMode = value;
_drawerShowing = false;
}); });
} }
...@@ -135,15 +92,15 @@ class HomeFragment extends StatefulComponent { ...@@ -135,15 +92,15 @@ class HomeFragment extends StatefulComponent {
children: [ children: [
new DrawerHeader(children: [new Text('Fitness')]), new DrawerHeader(children: [new Text('Fitness')]),
new DrawerItem( new DrawerItem(
icon: 'action/assessment', icon: 'action/list',
onPressed: () => _handleFitnessModeChange(FitnessMode.measure), onPressed: () => _handleFitnessModeChange(FitnessMode.feed),
selected: _fitnessMode == FitnessMode.measure, selected: _fitnessMode == FitnessMode.feed,
children: [new Text('Measure')]), children: [new Text('Feed')]),
new DrawerItem( new DrawerItem(
icon: 'maps/directions_run', icon: 'action/assessment',
onPressed: () => _handleFitnessModeChange(FitnessMode.run), onPressed: () => _handleFitnessModeChange(FitnessMode.chart),
selected: _fitnessMode == FitnessMode.run, selected: _fitnessMode == FitnessMode.chart,
children: [new Text('Run')]), children: [new Text('Chart')]),
new DrawerDivider(), new DrawerDivider(),
new DrawerItem( new DrawerItem(
icon: 'action/settings', icon: 'action/settings',
...@@ -180,8 +137,8 @@ class HomeFragment extends StatefulComponent { ...@@ -180,8 +137,8 @@ class HomeFragment extends StatefulComponent {
// TODO(jackson): We should be localizing // TODO(jackson): We should be localizing
String get fitnessModeTitle { String get fitnessModeTitle {
switch(_fitnessMode) { switch(_fitnessMode) {
case FitnessMode.measure: return "Measure"; case FitnessMode.feed: return "Feed";
case FitnessMode.run: return "Run"; case FitnessMode.chart: return "Chart";
} }
} }
...@@ -194,13 +151,12 @@ class HomeFragment extends StatefulComponent { ...@@ -194,13 +151,12 @@ class HomeFragment extends StatefulComponent {
); );
} }
// TODO(jackson): Pull from file FitnessItem _undoItem;
Measurement _undoMeasurement;
void _handleMeasurementDismissed(Measurement measurement) { void _handleItemDismissed(FitnessItem item) {
onMeasurementDeleted(measurement); onItemDeleted(item);
setState(() { setState(() {
_undoMeasurement = measurement; _undoItem = item;
_isShowingSnackBar = true; _isShowingSnackBar = true;
}); });
} }
...@@ -208,33 +164,33 @@ class HomeFragment extends StatefulComponent { ...@@ -208,33 +164,33 @@ class HomeFragment extends StatefulComponent {
Widget buildBody() { Widget buildBody() {
TextStyle style = Theme.of(this).text.title; TextStyle style = Theme.of(this).text.title;
switch (_fitnessMode) { switch (_fitnessMode) {
case FitnessMode.measure: case FitnessMode.feed:
if (userData.length > 0) if (userData.length > 0)
return new MeasurementList( return new FitnessItemList(
measurements: userData, items: userData,
onDismissed: _handleMeasurementDismissed onDismissed: _handleItemDismissed
); );
return new Material( return new Material(
type: MaterialType.canvas, type: MaterialType.canvas,
child: new Flex( child: new Flex(
[new Text("No measurements yet.\nAdd a new one!", style: style)], [new Text("No data yet.\nAdd some!", style: style)],
justifyContent: FlexJustifyContent.center justifyContent: FlexJustifyContent.center
) )
); );
case FitnessMode.run: case FitnessMode.chart:
return new Material( return new Material(
type: MaterialType.canvas, type: MaterialType.canvas,
child: new Flex([ child: new Flex([
new Text(_isRunning ? "Go go go!" : "Start a new run!", style: style) new Text("Charts are coming soon!", style: style)
], justifyContent: FlexJustifyContent.center) ], justifyContent: FlexJustifyContent.center)
); );
} }
} }
void _handleUndo() { void _handleUndo() {
onMeasurementCreated(_undoMeasurement); onItemCreated(_undoItem);
setState(() { setState(() {
_undoMeasurement = null; _undoItem = null;
_isShowingSnackBar = false; _isShowingSnackBar = false;
}); });
} }
...@@ -243,48 +199,71 @@ class HomeFragment extends StatefulComponent { ...@@ -243,48 +199,71 @@ class HomeFragment extends StatefulComponent {
if (!_isShowingSnackBar) if (!_isShowingSnackBar)
return null; return null;
return new SnackBar( return new SnackBar(
content: new Text("Measurement deleted."), content: new Text("Item deleted."),
actions: [new SnackBarAction(label: "UNDO", onPressed: _handleUndo)] actions: [new SnackBarAction(label: "UNDO", onPressed: _handleUndo)]
); );
} }
void _handleRunStarted() { bool _isShowingDialog = false;
setState(() {
_isRunning = true; Widget buildDialog() {
}); return new Dialog(
title: new Text("New item"),
content: new Text("What are you trying to do?"),
onDismiss: navigator.pop,
actions: [
new FlatButton(
child: new Text('CANCEL'),
onPressed: navigator.pop
),
new FlatButton(
child: new Text('EAT'),
onPressed: () {
navigator.pop();
navigator.pushNamed("/meals/new");
}
),
new FlatButton(
child: new Text('MEASURE'),
onPressed: () {
navigator.pop();
navigator.pushNamed("/measurements/new");
}
),
]
);
} }
void _handleRunStopped() { void _handleActionButtonPressed() {
setState(() { setState(() {
_isRunning = false; _isShowingDialog = true;
}); });
} }
Widget buildFloatingActionButton() { Widget buildFloatingActionButton() {
switch (_fitnessMode) { switch (_fitnessMode) {
case FitnessMode.measure: case FitnessMode.feed:
return new FloatingActionButton( return new FloatingActionButton(
child: new Icon(type: 'content/add', size: 24), child: new Icon(type: 'content/add', size: 24),
onPressed: () => navigator.pushNamed("/measurements/new") onPressed: _handleActionButtonPressed
);
case FitnessMode.run:
return new FloatingActionButton(
child: new Icon(
type: _isRunning ? 'av/stop' : 'maps/directions_run',
size: 24
),
onPressed: _isRunning ? _handleRunStopped : _handleRunStarted
); );
case FitnessMode.chart:
return null;
} }
} }
Widget build() { Widget build() {
return new Scaffold( List<Widget> layers = [
toolbar: buildToolBar(), new Scaffold(
body: buildBody(), toolbar: buildToolBar(),
snackBar: buildSnackBar(), body: buildBody(),
floatingActionButton: buildFloatingActionButton(), snackBar: buildSnackBar(),
drawer: buildDrawer() floatingActionButton: buildFloatingActionButton(),
); drawer: buildDrawer()
)
];
if (_isShowingDialog)
layers.add(buildDialog());
return new Stack(layers);
} }
} }
// 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.
import 'package:sky/widgets/basic.dart';
import 'package:sky/widgets/card.dart';
import 'package:sky/widgets/dismissable.dart';
typedef void FitnessItemHandler(FitnessItem item);
const double kFitnessItemHeight = 79.0;
class FitnessItem {
FitnessItem({ this.when }) {
assert(when != null);
}
final DateTime when;
// TODO(jackson): Internationalize
String get displayDate => "${when.year.toString()}-${when.month.toString().padLeft(2,'0')}-${when.day.toString().padLeft(2,'0')}";
}
abstract class FitnessItemRow extends Component {
FitnessItemRow({ FitnessItem item, this.onDismissed })
: this.item = item,
super(key: item.when.toString());
final FitnessItem item;
final FitnessItemHandler onDismissed;
Widget buildContent();
Widget build() {
return new Dismissable(
onDismissed: () => onDismissed(item),
child: new Card(
child: new Container(
height: kFitnessItemHeight,
padding: const EdgeDims.all(8.0),
child: buildContent()
)
)
);
}
}
...@@ -2,5 +2,5 @@ ...@@ -2,5 +2,5 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
enum FitnessMode { measure, run } enum FitnessMode { feed, chart }
enum BackupMode { enabled, disabled } enum BackupMode { enabled, disabled }
...@@ -9,31 +9,44 @@ import 'package:sky/widgets/theme.dart'; ...@@ -9,31 +9,44 @@ import 'package:sky/widgets/theme.dart';
import 'package:sky/widgets/widget.dart'; import 'package:sky/widgets/widget.dart';
import 'package:sky/widgets/task_description.dart'; import 'package:sky/widgets/task_description.dart';
import 'meal.dart';
import 'measurement.dart'; import 'measurement.dart';
import 'home.dart'; import 'feed.dart';
import 'settings.dart'; import 'settings.dart';
import 'fitness_item.dart';
import 'fitness_types.dart'; import 'fitness_types.dart';
class FitnessApp extends App { class FitnessApp extends App {
NavigationState _navigationState; NavigationState _navigationState;
final List<FitnessItem> _userData = [
new Measurement(weight: 180.0, when: new DateTime.now().add(const Duration(days: -1))),
new Measurement(weight: 160.0, when: new DateTime.now()),
];
void initState() { void initState() {
_navigationState = new NavigationState([ _navigationState = new NavigationState([
new Route( new Route(
name: '/', name: '/',
builder: (navigator, route) => new HomeFragment( builder: (navigator, route) => new FeedFragment(
navigator: navigator, navigator: navigator,
userData: _userData, userData: _userData,
onMeasurementCreated: _handleMeasurementCreated, onItemCreated: _handleItemCreated,
onMeasurementDeleted: _handleMeasurementDeleted onItemDeleted: _handleItemDeleted
)
),
new Route(
name: '/meals/new',
builder: (navigator, route) => new MealFragment(
navigator: navigator,
onCreated: _handleItemCreated
) )
), ),
new Route( new Route(
name: '/measurements/new', name: '/measurements/new',
builder: (navigator, route) => new MeasurementFragment( builder: (navigator, route) => new MeasurementFragment(
navigator: navigator, navigator: navigator,
onCreated: _handleMeasurementCreated onCreated: _handleItemCreated
) )
), ),
new Route( new Route(
...@@ -54,16 +67,16 @@ class FitnessApp extends App { ...@@ -54,16 +67,16 @@ class FitnessApp extends App {
} }
} }
void _handleMeasurementCreated(Measurement measurement) { void _handleItemCreated(FitnessItem item) {
setState(() { setState(() {
_userData.add(measurement); _userData.add(item);
_userData.sort((a, b) => a.when.compareTo(b.when)); _userData.sort((a, b) => a.when.compareTo(b.when));
}); });
} }
void _handleMeasurementDeleted(Measurement measurement) { void _handleItemDeleted(FitnessItem item) {
setState(() { setState(() {
_userData.remove(measurement); _userData.remove(item);
}); });
} }
...@@ -76,11 +89,6 @@ class FitnessApp extends App { ...@@ -76,11 +89,6 @@ class FitnessApp extends App {
}); });
} }
final List<Measurement> _userData = [
new Measurement(weight: 180.0, when: new DateTime.now().add(const Duration(days: -1))),
new Measurement(weight: 160.0, when: new DateTime.now()),
];
Widget build() { Widget build() {
return new Theme( return new Theme(
data: new ThemeData( data: new ThemeData(
......
// Copyright 2014 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.
import 'package:sky/painting/text_style.dart';
import 'package:sky/editing/input.dart';
import 'package:sky/widgets/basic.dart';
import 'package:sky/widgets/default_text_style.dart';
import 'package:sky/widgets/icon_button.dart';
import 'package:sky/widgets/ink_well.dart';
import 'package:sky/widgets/material.dart';
import 'package:sky/widgets/navigator.dart';
import 'package:sky/widgets/scaffold.dart';
import 'package:sky/widgets/scrollable_viewport.dart';
import 'package:sky/widgets/snack_bar.dart';
import 'package:sky/widgets/theme.dart';
import 'package:sky/widgets/tool_bar.dart';
import 'fitness_item.dart';
class Meal extends FitnessItem {
Meal({ DateTime when, this.description }) : super(when: when);
final String description;
}
class MealRow extends FitnessItemRow {
MealRow({ Meal meal, FitnessItemHandler onDismissed })
: super(item: meal, onDismissed: onDismissed);
Widget buildContent() {
Meal meal = item;
List<Widget> children = [
new Flexible(
child: new Text(
meal.description,
style: const TextStyle(textAlign: TextAlign.right)
)
),
new Flexible(
child: new Text(
meal.displayDate,
style: Theme.of(this).text.caption.copyWith(textAlign: TextAlign.right)
)
)
];
return new Flex(
children,
alignItems: FlexAlignItems.baseline,
textBaseline: DefaultTextStyle.of(this).textBaseline
);
}
}
class MealFragment extends StatefulComponent {
MealFragment({ this.navigator, this.onCreated });
Navigator navigator;
FitnessItemHandler onCreated;
void syncFields(MealFragment source) {
navigator = source.navigator;
onCreated = source.onCreated;
}
String _description = "";
void _handleSave() {
onCreated(new Meal(when: new DateTime.now(), description: _description));
navigator.pop();
}
Widget buildToolBar() {
return new ToolBar(
left: new IconButton(
icon: "navigation/close",
onPressed: navigator.pop),
center: new Text('New Meal'),
right: [new InkWell(
child: new Listener(
onGestureTap: (_) => _handleSave(),
child: new Text('SAVE')
)
)]
);
}
void _handleDescriptionChanged(String description) {
setState(() {
_description = description;
});
}
Widget buildBody() {
Meal meal = new Meal(when: new DateTime.now());
return new Material(
type: MaterialType.canvas,
child: new ScrollableViewport(
child: new Container(
padding: const EdgeDims.all(20.0),
child: new Block([
new Text(meal.displayDate),
new Input(
focused: false,
placeholder: 'Describe meal',
onChanged: _handleDescriptionChanged
),
])
)
)
);
}
Widget build() {
return new Scaffold(
toolbar: buildToolBar(),
body: buildBody()
);
}
}
...@@ -2,8 +2,10 @@ ...@@ -2,8 +2,10 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'package:sky/painting/text_style.dart';
import 'package:sky/editing/input.dart'; import 'package:sky/editing/input.dart';
import 'package:sky/widgets/basic.dart'; import 'package:sky/widgets/basic.dart';
import 'package:sky/widgets/default_text_style.dart';
import 'package:sky/widgets/icon_button.dart'; import 'package:sky/widgets/icon_button.dart';
import 'package:sky/widgets/ink_well.dart'; import 'package:sky/widgets/ink_well.dart';
import 'package:sky/widgets/material.dart'; import 'package:sky/widgets/material.dart';
...@@ -11,19 +13,46 @@ import 'package:sky/widgets/navigator.dart'; ...@@ -11,19 +13,46 @@ import 'package:sky/widgets/navigator.dart';
import 'package:sky/widgets/scaffold.dart'; import 'package:sky/widgets/scaffold.dart';
import 'package:sky/widgets/scrollable_viewport.dart'; import 'package:sky/widgets/scrollable_viewport.dart';
import 'package:sky/widgets/snack_bar.dart'; import 'package:sky/widgets/snack_bar.dart';
import 'package:sky/widgets/theme.dart';
import 'package:sky/widgets/tool_bar.dart'; import 'package:sky/widgets/tool_bar.dart';
typedef void MeasurementHandler(Measurement measurement); import 'fitness_item.dart';
class Measurement { class Measurement extends FitnessItem {
Measurement({ this.when, this.weight }); Measurement({ DateTime when, this.weight }) : super(when: when);
final DateTime when;
final double weight; final double weight;
// TODO(jackson): Internationalize // TODO(jackson): Internationalize
String get displayWeight => "${weight.toStringAsFixed(2)} lbs"; String get displayWeight => "${weight.toStringAsFixed(2)} lbs";
String get displayDate => "${when.year.toString()}-${when.month.toString().padLeft(2,'0')}-${when.day.toString().padLeft(2,'0')}"; }
class MeasurementRow extends FitnessItemRow {
MeasurementRow({ Measurement measurement, FitnessItemHandler onDismissed })
: super(item: measurement, onDismissed: onDismissed);
Widget buildContent() {
Measurement measurement = item;
List<Widget> children = [
new Flexible(
child: new Text(
measurement.displayWeight,
style: const TextStyle(textAlign: TextAlign.right)
)
),
new Flexible(
child: new Text(
measurement.displayDate,
style: Theme.of(this).text.caption.copyWith(textAlign: TextAlign.right)
)
)
];
return new Flex(
children,
alignItems: FlexAlignItems.baseline,
textBaseline: DefaultTextStyle.of(this).textBaseline
);
}
} }
class MeasurementFragment extends StatefulComponent { class MeasurementFragment extends StatefulComponent {
...@@ -31,7 +60,7 @@ class MeasurementFragment extends StatefulComponent { ...@@ -31,7 +60,7 @@ class MeasurementFragment extends StatefulComponent {
MeasurementFragment({ this.navigator, this.onCreated }); MeasurementFragment({ this.navigator, this.onCreated });
Navigator navigator; Navigator navigator;
MeasurementHandler onCreated; FitnessItemHandler onCreated;
void syncFields(MeasurementFragment source) { void syncFields(MeasurementFragment source) {
navigator = source.navigator; navigator = source.navigator;
...@@ -76,7 +105,7 @@ class MeasurementFragment extends StatefulComponent { ...@@ -76,7 +105,7 @@ class MeasurementFragment extends StatefulComponent {
}); });
} }
Widget buildMeasurementPane() { Widget buildBody() {
Measurement measurement = new Measurement(when: new DateTime.now()); Measurement measurement = new Measurement(when: new DateTime.now());
return new Material( return new Material(
type: MaterialType.canvas, type: MaterialType.canvas,
...@@ -105,7 +134,7 @@ class MeasurementFragment extends StatefulComponent { ...@@ -105,7 +134,7 @@ class MeasurementFragment extends StatefulComponent {
Widget build() { Widget build() {
return new Scaffold( return new Scaffold(
toolbar: buildToolBar(), toolbar: buildToolBar(),
body: buildMeasurementPane(), body: buildBody(),
snackBar: buildSnackBar() snackBar: buildSnackBar()
); );
} }
......
...@@ -27,8 +27,6 @@ class SettingsFragment extends Component { ...@@ -27,8 +27,6 @@ class SettingsFragment extends Component {
final BackupMode backup; final BackupMode backup;
final SettingsUpdater updater; final SettingsUpdater updater;
bool showModeDialog = false;
void _handleBackupChanged(bool value) { void _handleBackupChanged(bool value) {
if (updater != null) if (updater != null)
updater(backup: value ? BackupMode.enabled : BackupMode.disabled); updater(backup: value ? BackupMode.enabled : BackupMode.disabled);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment