提交 c12d5d9d 编写于 作者: C Collin Jackson

Merge pull request #166 from collinjackson/fitness

Measurement tracking for fitness app.

There is an issue with the input control that prevents actually creating a measurement. Working on this with eseidel.

R=eseidel
...@@ -4,6 +4,9 @@ ...@@ -4,6 +4,9 @@
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/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';
...@@ -11,9 +14,11 @@ import 'package:sky/widgets/drawer_item.dart'; ...@@ -11,9 +14,11 @@ import 'package:sky/widgets/drawer_item.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';
import 'package:sky/widgets/ink_well.dart';
import 'package:sky/widgets/material.dart'; import 'package:sky/widgets/material.dart';
import 'package:sky/widgets/navigator.dart'; import 'package:sky/widgets/navigator.dart';
import 'package:sky/widgets/scaffold.dart'; import 'package:sky/widgets/scaffold.dart';
import 'package:sky/widgets/scrollable_list.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/theme.dart';
import 'package:sky/widgets/tool_bar.dart'; import 'package:sky/widgets/tool_bar.dart';
...@@ -22,12 +27,79 @@ import 'package:sky/widgets/widget.dart'; ...@@ -22,12 +27,79 @@ import 'package:sky/widgets/widget.dart';
import 'fitness_types.dart'; import 'fitness_types.dart';
import 'measurement.dart'; import 'measurement.dart';
class MeasurementList extends Component {
MeasurementList({ String key, this.measurements, this.onDismissed }) : super(key: key);
final List<Measurement> measurements;
final MeasurementHandler onDismissed;
Widget build() {
return new Material(
type: MaterialType.canvas,
child: new ScrollableList<Measurement>(
items: measurements,
itemHeight: MeasurementRow.kHeight,
itemBuilder: (measurement) => new MeasurementRow(
measurement: measurement,
onDismissed: onDismissed
)
)
);
}
}
class MeasurementRow extends Component {
MeasurementRow({ Measurement measurement, this.onDismissed }) : this.measurement = measurement, super(key: measurement.when.toString());
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 { class HomeFragment extends StatefulComponent {
HomeFragment(this.navigator, this.userData); HomeFragment({ this.navigator, this.userData, this.onMeasurementCreated, this.onMeasurementDeleted });
Navigator navigator; Navigator navigator;
List<Measurement> userData; List<Measurement> userData;
MeasurementHandler onMeasurementCreated;
MeasurementHandler onMeasurementDeleted;
FitnessMode _fitnessMode = FitnessMode.measure; FitnessMode _fitnessMode = FitnessMode.measure;
...@@ -40,6 +112,8 @@ class HomeFragment extends StatefulComponent { ...@@ -40,6 +112,8 @@ class HomeFragment extends StatefulComponent {
void syncFields(HomeFragment source) { void syncFields(HomeFragment source) {
navigator = source.navigator; navigator = source.navigator;
userData = source.userData; userData = source.userData;
onMeasurementCreated = source.onMeasurementCreated;
onMeasurementDeleted = source.onMeasurementDeleted;
} }
bool _isShowingSnackBar = false; bool _isShowingSnackBar = false;
...@@ -121,10 +195,26 @@ class HomeFragment extends StatefulComponent { ...@@ -121,10 +195,26 @@ class HomeFragment extends StatefulComponent {
); );
} }
// TODO(jackson): Pull from file
Measurement _undoMeasurement;
void _handleMeasurementDismissed(Measurement measurement) {
onMeasurementDeleted(measurement);
setState(() {
_undoMeasurement = measurement;
_isShowingSnackBar = true;
});
}
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.measure:
if (userData.length > 0)
return new MeasurementList(
measurements: userData,
onDismissed: _handleMeasurementDismissed
);
return new Material( return new Material(
type: MaterialType.canvas, type: MaterialType.canvas,
child: new Flex( child: new Flex(
...@@ -143,7 +233,9 @@ class HomeFragment extends StatefulComponent { ...@@ -143,7 +233,9 @@ class HomeFragment extends StatefulComponent {
} }
void _handleUndo() { void _handleUndo() {
onMeasurementCreated(_undoMeasurement);
setState(() { setState(() {
_undoMeasurement = null;
_isShowingSnackBar = false; _isShowingSnackBar = false;
}); });
} }
...@@ -152,17 +244,11 @@ class HomeFragment extends StatefulComponent { ...@@ -152,17 +244,11 @@ class HomeFragment extends StatefulComponent {
if (!_isShowingSnackBar) if (!_isShowingSnackBar)
return null; return null;
return new SnackBar( return new SnackBar(
content: new Text("Measurement added!"), content: new Text("Measurement deleted."),
actions: [new SnackBarAction(label: "UNDO", onPressed: _handleUndo)] actions: [new SnackBarAction(label: "UNDO", onPressed: _handleUndo)]
); );
} }
void _handleMeasurementAdded() {
setState(() {
_isShowingSnackBar = true;
});
}
void _handleRunStarted() { void _handleRunStarted() {
setState(() { setState(() {
_isRunning = true; _isRunning = true;
...@@ -180,7 +266,7 @@ class HomeFragment extends StatefulComponent { ...@@ -180,7 +266,7 @@ class HomeFragment extends StatefulComponent {
case FitnessMode.measure: case FitnessMode.measure:
return new FloatingActionButton( return new FloatingActionButton(
child: new Icon(type: 'content/add', size: 24), child: new Icon(type: 'content/add', size: 24),
onPressed: _handleMeasurementAdded onPressed: () => navigator.pushNamed("/measurements/new")
); );
case FitnessMode.run: case FitnessMode.run:
return new FloatingActionButton( return new FloatingActionButton(
......
...@@ -22,7 +22,19 @@ class FitnessApp extends App { ...@@ -22,7 +22,19 @@ class FitnessApp extends App {
_navigationState = new NavigationState([ _navigationState = new NavigationState([
new Route( new Route(
name: '/', name: '/',
builder: (navigator, route) => new HomeFragment(navigator, _userData) builder: (navigator, route) => new HomeFragment(
navigator: navigator,
userData: _userData,
onMeasurementCreated: _handleMeasurementCreated,
onMeasurementDeleted: _handleMeasurementDeleted
)
),
new Route(
name: '/measurements/new',
builder: (navigator, route) => new MeasurementFragment(
navigator: navigator,
onCreated: _handleMeasurementCreated
)
), ),
new Route( new Route(
name: '/settings', name: '/settings',
...@@ -42,6 +54,19 @@ class FitnessApp extends App { ...@@ -42,6 +54,19 @@ class FitnessApp extends App {
} }
} }
void _handleMeasurementCreated(Measurement measurement) {
setState(() {
_userData.add(measurement);
_userData.sort((a, b) => a.when.compareTo(b.when));
});
}
void _handleMeasurementDeleted(Measurement measurement) {
setState(() {
_userData.remove(measurement);
});
}
BackupMode backupSetting = BackupMode.disabled; BackupMode backupSetting = BackupMode.disabled;
void settingsUpdater({ BackupMode backup }) { void settingsUpdater({ BackupMode backup }) {
...@@ -52,7 +77,8 @@ class FitnessApp extends App { ...@@ -52,7 +77,8 @@ class FitnessApp extends App {
} }
final List<Measurement> _userData = [ final List<Measurement> _userData = [
new Measurement(when: new DateTime.now(), weight: 400.0) 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() {
......
...@@ -2,10 +2,112 @@ ...@@ -2,10 +2,112 @@
// 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/editing/input.dart';
import 'package:sky/widgets/basic.dart';
import 'package:sky/widgets/flat_button.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/tool_bar.dart';
typedef void MeasurementHandler(Measurement measurement);
class Measurement { class Measurement {
Measurement({ this.when, this.weight });
final DateTime when; final DateTime when;
final double weight; final double weight;
Measurement({ this.when, this.weight }); // TODO(jackson): Internationalize
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 MeasurementFragment extends StatefulComponent {
MeasurementFragment({ this.navigator, this.onCreated });
Navigator navigator;
MeasurementHandler onCreated;
void syncFields(MeasurementFragment source) {
navigator = source.navigator;
onCreated = source.onCreated;
}
String _weight = "";
String _errorMessage = null;
void _handleSave() {
double parsedWeight;
try {
parsedWeight = double.parse(_weight);
} on FormatException {
setState(() {
_errorMessage = "Save failed";
});
return;
}
onCreated(new Measurement(when: new DateTime.now(), weight: parsedWeight));
navigator.pop();
}
Widget buildToolBar() {
return new ToolBar(
left: new IconButton(
icon: "navigation/close",
onPressed: navigator.pop),
center: new Text('New Measurement'),
right: [new InkWell(
child: new Listener(
onGestureTap: (_) => _handleSave(),
child: new Text('SAVE')
)
)]
);
}
void _handleWeightChanged(String weight) {
setState(() {
_weight = weight;
});
}
Widget buildMeasurementPane() {
Measurement measurement = new Measurement(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(measurement.displayDate),
new Input(
focused: false,
placeholder: 'Enter weight',
onChanged: _handleWeightChanged
),
])
)
)
);
}
Widget buildSnackBar() {
if (_errorMessage == null)
return null;
return new SnackBar(content: new Text(_errorMessage));
}
Widget build() {
return new Scaffold(
toolbar: buildToolBar(),
body: buildMeasurementPane(),
snackBar: buildSnackBar()
);
}
}
...@@ -53,7 +53,9 @@ class SnackBar extends Component { ...@@ -53,7 +53,9 @@ class SnackBar extends Component {
) )
) )
) )
]..addAll(actions); ];
if (actions != null)
children.addAll(actions);
return new Material( return new Material(
level: 2, level: 2,
color: const Color(0xFF323232), color: const Color(0xFF323232),
......
...@@ -31,12 +31,14 @@ class ToolBar extends Component { ...@@ -31,12 +31,14 @@ class ToolBar extends Component {
Widget build() { Widget build() {
Color toolbarColor = backgroundColor; Color toolbarColor = backgroundColor;
IconThemeData iconThemeData; IconThemeData iconThemeData;
TextStyle defaultTextStyle = typography.white.title; TextStyle centerStyle = typography.white.title;
TextStyle sideStyle = typography.white.body1;
if (toolbarColor == null) { if (toolbarColor == null) {
ThemeData themeData = Theme.of(this); ThemeData themeData = Theme.of(this);
toolbarColor = themeData.primaryColor; toolbarColor = themeData.primaryColor;
if (themeData.primaryColorBrightness == ThemeBrightness.light) { if (themeData.primaryColorBrightness == ThemeBrightness.light) {
defaultTextStyle = typography.black.title; centerStyle = typography.black.title;
sideStyle = typography.black.body2;
iconThemeData = const IconThemeData(color: IconThemeColor.black); iconThemeData = const IconThemeData(color: IconThemeColor.black);
} else { } else {
iconThemeData = const IconThemeData(color: IconThemeColor.white); iconThemeData = const IconThemeData(color: IconThemeColor.white);
...@@ -50,7 +52,7 @@ class ToolBar extends Component { ...@@ -50,7 +52,7 @@ class ToolBar extends Component {
children.add( children.add(
new Flexible( new Flexible(
child: new Padding( child: new Padding(
child: center, child: new DefaultTextStyle(child: center, style: centerStyle),
padding: new EdgeDims.only(left: 24.0) padding: new EdgeDims.only(left: 24.0)
) )
) )
...@@ -61,7 +63,7 @@ class ToolBar extends Component { ...@@ -61,7 +63,7 @@ class ToolBar extends Component {
Widget content = new Container( Widget content = new Container(
child: new DefaultTextStyle( child: new DefaultTextStyle(
style: defaultTextStyle, style: sideStyle,
child: new Flex( child: new Flex(
[new Container(child: new Flex(children), height: kToolBarHeight)], [new Container(child: new Flex(children), height: kToolBarHeight)],
alignItems: FlexAlignItems.end alignItems: FlexAlignItems.end
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册