
This patch converts drawer to using the "openDialog" pattern for managing its state. Currently, the drawer entrance and exit animation aren't integrated with the navigator's animation system because the drawer's animations can be stopped and reversed, which the navigator can't yet understand. That means dismissing the drawer via the system back button causes the drawer to be removed instanteously. Fixes #715 Fixes #1187
299 lines
8.2 KiB
Dart
299 lines
8.2 KiB
Dart
// 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 StatelessComponent {
|
|
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(BuildContext context) {
|
|
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 StatelessComponent {
|
|
DialogMenuItem(this.children, { Key key, this.onPressed }) : super(key: key);
|
|
|
|
List<Widget> children;
|
|
Function onPressed;
|
|
|
|
Widget build(BuildContext context) {
|
|
return new Container(
|
|
height: 48.0,
|
|
child: new InkWell(
|
|
onTap: onPressed,
|
|
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 });
|
|
|
|
final NavigatorState navigator;
|
|
final UserData userData;
|
|
final FitnessItemHandler onItemCreated;
|
|
final FitnessItemHandler onItemDeleted;
|
|
|
|
FeedFragmentState createState() => new FeedFragmentState();
|
|
}
|
|
|
|
class FeedFragmentState extends State<FeedFragment> {
|
|
FitnessMode _fitnessMode = FitnessMode.feed;
|
|
|
|
AnimationStatus _snackBarStatus = AnimationStatus.dismissed;
|
|
bool _isShowingSnackBar = false;
|
|
|
|
void _handleFitnessModeChange(FitnessMode value) {
|
|
setState(() {
|
|
_fitnessMode = value;
|
|
});
|
|
config.navigator.pop();
|
|
}
|
|
|
|
void _showDrawer() {
|
|
showDrawer(
|
|
navigator: config.navigator,
|
|
child: new Block([
|
|
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'))
|
|
])
|
|
);
|
|
}
|
|
|
|
void _handleShowSettings() {
|
|
config.navigator.pop();
|
|
config.navigator.pushNamed('/settings');
|
|
}
|
|
|
|
// 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: _showDrawer),
|
|
center: new Text(fitnessModeTitle)
|
|
);
|
|
}
|
|
|
|
FitnessItem _undoItem;
|
|
|
|
void _handleItemDismissed(FitnessItem item) {
|
|
config.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 config.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 (config.userData.goalWeight != null && config.userData.goalWeight > 0.0) {
|
|
startY = math.min(startY, config.userData.goalWeight);
|
|
endY = math.max(endY, config.userData.goalWeight);
|
|
}
|
|
playfair.ChartData data = new playfair.ChartData(
|
|
startX: startX,
|
|
startY: startY,
|
|
endX: endX,
|
|
endY: endY,
|
|
dataSet: dataSet,
|
|
numHorizontalGridlines: 5,
|
|
roundToPlaces: 1,
|
|
indicatorLine: config.userData.goalWeight,
|
|
indicatorText: "GOAL WEIGHT"
|
|
);
|
|
return new playfair.Chart(data: data);
|
|
}
|
|
|
|
Widget buildBody() {
|
|
TextStyle style = Theme.of(context).text.title;
|
|
if (config.userData == null)
|
|
return new Material(type: MaterialType.canvas);
|
|
if (config.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: config.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() {
|
|
config.onItemCreated(_undoItem);
|
|
setState(() {
|
|
_undoItem = null;
|
|
_isShowingSnackBar = false;
|
|
});
|
|
}
|
|
|
|
Widget buildSnackBar() {
|
|
if (_snackBarStatus == AnimationStatus.dismissed)
|
|
return null;
|
|
return new SnackBar(
|
|
showing: _isShowingSnackBar,
|
|
content: new Text("Item deleted."),
|
|
actions: [new SnackBarAction(label: "UNDO", onPressed: _handleUndo)],
|
|
onDismissed: () { setState(() { _snackBarStatus = AnimationStatus.dismissed; }); }
|
|
);
|
|
}
|
|
|
|
void _handleActionButtonPressed() {
|
|
showDialog(config.navigator, (NavigatorState navigator) => new AddItemDialog(navigator)).then((routeName) {
|
|
if (routeName != null)
|
|
config.navigator.pushNamed(routeName);
|
|
});
|
|
}
|
|
|
|
Widget buildFloatingActionButton() {
|
|
switch (_fitnessMode) {
|
|
case FitnessMode.feed:
|
|
return new FloatingActionButton(
|
|
child: new Icon(type: 'content/add', size: 24),
|
|
onPressed: _handleActionButtonPressed
|
|
);
|
|
case FitnessMode.chart:
|
|
return null;
|
|
}
|
|
}
|
|
|
|
Widget build(BuildContext context) {
|
|
return new Scaffold(
|
|
toolbar: buildToolBar(),
|
|
body: buildBody(),
|
|
snackBar: buildSnackBar(),
|
|
floatingActionButton: buildFloatingActionButton()
|
|
);
|
|
}
|
|
}
|
|
|
|
class AddItemDialog extends StatefulComponent {
|
|
AddItemDialog(this.navigator);
|
|
|
|
final NavigatorState navigator;
|
|
|
|
AddItemDialogState createState() => new AddItemDialogState();
|
|
}
|
|
|
|
class AddItemDialogState extends State<AddItemDialog> {
|
|
// 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(BuildContext context) {
|
|
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: config.navigator.pop,
|
|
actions: [
|
|
new FlatButton(
|
|
child: new Text('CANCEL'),
|
|
onPressed: config.navigator.pop
|
|
),
|
|
new FlatButton(
|
|
child: new Text('ADD'),
|
|
onPressed: () {
|
|
config.navigator.pop(_addItemRoute);
|
|
}
|
|
),
|
|
]
|
|
);
|
|
}
|
|
}
|