提交 3f55a95d 编写于 作者: C Collin Jackson

Add basic measurement functionality to fitness app

上级 a548d390
......@@ -4,6 +4,9 @@
import 'package:sky/painting/text_style.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_divider.dart';
import 'package:sky/widgets/drawer_header.dart';
......@@ -11,9 +14,11 @@ import 'package:sky/widgets/drawer_item.dart';
import 'package:sky/widgets/floating_action_button.dart';
import 'package:sky/widgets/icon_button.dart';
import 'package:sky/widgets/icon.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_list.dart';
import 'package:sky/widgets/snack_bar.dart';
import 'package:sky/widgets/theme.dart';
import 'package:sky/widgets/tool_bar.dart';
......@@ -22,12 +27,79 @@ import 'package:sky/widgets/widget.dart';
import 'fitness_types.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 {
HomeFragment(this.navigator, this.userData);
HomeFragment({ this.navigator, this.userData, this.onMeasurementCreated, this.onMeasurementDeleted });
Navigator navigator;
List<Measurement> userData;
MeasurementHandler onMeasurementCreated;
MeasurementHandler onMeasurementDeleted;
FitnessMode _fitnessMode = FitnessMode.measure;
......@@ -40,6 +112,8 @@ class HomeFragment extends StatefulComponent {
void syncFields(HomeFragment source) {
navigator = source.navigator;
userData = source.userData;
onMeasurementCreated = source.onMeasurementCreated;
onMeasurementDeleted = source.onMeasurementDeleted;
}
bool _isShowingSnackBar = false;
......@@ -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() {
TextStyle style = Theme.of(this).text.title;
switch (_fitnessMode) {
case FitnessMode.measure:
if (userData.length > 0)
return new MeasurementList(
measurements: userData,
onDismissed: _handleMeasurementDismissed
);
return new Material(
type: MaterialType.canvas,
child: new Flex(
......@@ -143,7 +233,9 @@ class HomeFragment extends StatefulComponent {
}
void _handleUndo() {
onMeasurementCreated(_undoMeasurement);
setState(() {
_undoMeasurement = null;
_isShowingSnackBar = false;
});
}
......@@ -152,17 +244,11 @@ class HomeFragment extends StatefulComponent {
if (!_isShowingSnackBar)
return null;
return new SnackBar(
content: new Text("Measurement added!"),
content: new Text("Measurement deleted."),
actions: [new SnackBarAction(label: "UNDO", onPressed: _handleUndo)]
);
}
void _handleMeasurementAdded() {
setState(() {
_isShowingSnackBar = true;
});
}
void _handleRunStarted() {
setState(() {
_isRunning = true;
......@@ -180,7 +266,7 @@ class HomeFragment extends StatefulComponent {
case FitnessMode.measure:
return new FloatingActionButton(
child: new Icon(type: 'content/add', size: 24),
onPressed: _handleMeasurementAdded
onPressed: () => navigator.pushNamed("/measurements/new")
);
case FitnessMode.run:
return new FloatingActionButton(
......
......@@ -22,7 +22,19 @@ class FitnessApp extends App {
_navigationState = new NavigationState([
new Route(
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(
name: '/settings',
......@@ -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;
void settingsUpdater({ BackupMode backup }) {
......@@ -52,7 +77,8 @@ class FitnessApp extends App {
}
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() {
......
......@@ -2,10 +2,112 @@
// Use of this source code is governed by a BSD-style license that can be
// 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 {
Measurement({ this.when, this.weight });
final DateTime when;
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 {
)
)
)
]..addAll(actions);
];
if (actions != null)
children.addAll(actions);
return new Material(
level: 2,
color: const Color(0xFF323232),
......
......@@ -31,12 +31,14 @@ class ToolBar extends Component {
Widget build() {
Color toolbarColor = backgroundColor;
IconThemeData iconThemeData;
TextStyle defaultTextStyle = typography.white.title;
TextStyle centerStyle = typography.white.title;
TextStyle sideStyle = typography.white.body1;
if (toolbarColor == null) {
ThemeData themeData = Theme.of(this);
toolbarColor = themeData.primaryColor;
if (themeData.primaryColorBrightness == ThemeBrightness.light) {
defaultTextStyle = typography.black.title;
centerStyle = typography.black.title;
sideStyle = typography.black.body2;
iconThemeData = const IconThemeData(color: IconThemeColor.black);
} else {
iconThemeData = const IconThemeData(color: IconThemeColor.white);
......@@ -50,7 +52,7 @@ class ToolBar extends Component {
children.add(
new Flexible(
child: new Padding(
child: center,
child: new DefaultTextStyle(child: center, style: centerStyle),
padding: new EdgeDims.only(left: 24.0)
)
)
......@@ -61,7 +63,7 @@ class ToolBar extends Component {
Widget content = new Container(
child: new DefaultTextStyle(
style: defaultTextStyle,
style: sideStyle,
child: new Flex(
[new Container(child: new Flex(children), height: kToolBarHeight)],
alignItems: FlexAlignItems.end
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册