mirror of
https://github.com/flutter/flutter
synced 2024-10-13 19:52:53 +00:00
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
This commit is contained in:
commit
40b2a4e53f
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue