
These widgets were designed in CSS where you need to specify a layout model for your children. This patch updates them to the modern style of just taking a unique child. Fixes #755
346 lines
9.1 KiB
Dart
346 lines
9.1 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 Component {
|
|
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() {
|
|
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 ButtonBase {
|
|
DialogMenuItem(this.children, { Key key, this.onPressed }) : super(key: key);
|
|
|
|
List<Widget> children;
|
|
Function onPressed;
|
|
|
|
void syncConstructorArguments(DialogMenuItem source) {
|
|
children = source.children;
|
|
onPressed = source.onPressed;
|
|
super.syncConstructorArguments(source);
|
|
}
|
|
|
|
Widget buildContent() {
|
|
return new Listener(
|
|
onGestureTap: (_) {
|
|
if (onPressed != null)
|
|
onPressed();
|
|
},
|
|
child: new Container(
|
|
height: 48.0,
|
|
child: new InkWell(
|
|
child: new Padding(
|
|
padding: const EdgeDims.symmetric(horizontal: 16.0),
|
|
child: new Flex(children)
|
|
)
|
|
)
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
class FeedFragment extends StatefulComponent {
|
|
FeedFragment({ this.navigator, this.userData, this.onItemCreated, this.onItemDeleted });
|
|
|
|
Navigator navigator;
|
|
UserData userData;
|
|
FitnessItemHandler onItemCreated;
|
|
FitnessItemHandler onItemDeleted;
|
|
|
|
FitnessMode _fitnessMode = FitnessMode.feed;
|
|
|
|
void initState() {
|
|
// if (debug)
|
|
// new Timer(new Duration(seconds: 1), dumpState);
|
|
super.initState();
|
|
}
|
|
|
|
void syncConstructorArguments(FeedFragment source) {
|
|
navigator = source.navigator;
|
|
userData = source.userData;
|
|
onItemCreated = source.onItemCreated;
|
|
onItemDeleted = source.onItemDeleted;
|
|
}
|
|
|
|
AnimationStatus _snackBarStatus = AnimationStatus.dismissed;
|
|
bool _isShowingSnackBar = false;
|
|
|
|
EventDisposition _handleFitnessModeChange(FitnessMode value) {
|
|
setState(() {
|
|
_fitnessMode = value;
|
|
_drawerShowing = false;
|
|
});
|
|
return EventDisposition.processed;
|
|
}
|
|
|
|
Drawer buildDrawer() {
|
|
if (_drawerStatus == AnimationStatus.dismissed)
|
|
return null;
|
|
return new Drawer(
|
|
showing: _drawerShowing,
|
|
level: 3,
|
|
onDismissed: _handleDrawerDismissed,
|
|
navigator: navigator,
|
|
children: [
|
|
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'))
|
|
]
|
|
);
|
|
}
|
|
|
|
bool _drawerShowing = false;
|
|
AnimationStatus _drawerStatus = AnimationStatus.dismissed;
|
|
|
|
void _handleOpenDrawer() {
|
|
setState(() {
|
|
_drawerShowing = true;
|
|
_drawerStatus = AnimationStatus.forward;
|
|
});
|
|
}
|
|
|
|
void _handleDrawerDismissed() {
|
|
setState(() {
|
|
_drawerStatus = AnimationStatus.dismissed;
|
|
});
|
|
}
|
|
|
|
EventDisposition _handleShowSettings() {
|
|
navigator.pop();
|
|
navigator.pushNamed('/settings');
|
|
return EventDisposition.processed;
|
|
}
|
|
|
|
// 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: _handleOpenDrawer),
|
|
center: new Text(fitnessModeTitle)
|
|
);
|
|
}
|
|
|
|
FitnessItem _undoItem;
|
|
|
|
void _handleItemDismissed(FitnessItem item) {
|
|
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 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 (userData.goalWeight > 0.0) {
|
|
startY = math.min(startY, userData.goalWeight);
|
|
endY = math.max(endY, userData.goalWeight);
|
|
}
|
|
playfair.ChartData data = new playfair.ChartData(
|
|
startX: startX,
|
|
startY: startY,
|
|
endX: endX,
|
|
endY: endY,
|
|
dataSet: dataSet,
|
|
numHorizontalGridlines: 5,
|
|
roundToPlaces: 1,
|
|
indicatorLine: userData.goalWeight,
|
|
indicatorText: "GOAL WEIGHT"
|
|
);
|
|
return new playfair.Chart(data: data);
|
|
}
|
|
|
|
Widget buildBody() {
|
|
TextStyle style = Theme.of(this).text.title;
|
|
if (userData == null)
|
|
return new Material(type: MaterialType.canvas);
|
|
if (userData.items.length == 0)
|
|
return new Material(
|
|
type: MaterialType.canvas,
|
|
child: new Flex(
|
|
[new Text("No data yet.\nAdd some!", style: style)],
|
|
justifyContent: FlexJustifyContent.center
|
|
)
|
|
);
|
|
switch (_fitnessMode) {
|
|
case FitnessMode.feed:
|
|
return new FitnessItemList(
|
|
items: 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() {
|
|
onItemCreated(_undoItem);
|
|
setState(() {
|
|
_undoItem = null;
|
|
_isShowingSnackBar = false;
|
|
});
|
|
}
|
|
|
|
Anchor _snackBarAnchor = new Anchor();
|
|
Widget buildSnackBar() {
|
|
if (_snackBarStatus == AnimationStatus.dismissed)
|
|
return null;
|
|
return new SnackBar(
|
|
showing: _isShowingSnackBar,
|
|
anchor: _snackBarAnchor,
|
|
content: new Text("Item deleted."),
|
|
actions: [new SnackBarAction(label: "UNDO", onPressed: _handleUndo)],
|
|
onDismissed: () { setState(() { _snackBarStatus = AnimationStatus.dismissed; }); }
|
|
);
|
|
}
|
|
|
|
void _handleActionButtonPressed() {
|
|
showDialog(navigator, (navigator) => new AddItemDialog(navigator)).then((routeName) {
|
|
if (routeName != null)
|
|
navigator.pushNamed(routeName);
|
|
});
|
|
}
|
|
|
|
Widget buildFloatingActionButton() {
|
|
switch (_fitnessMode) {
|
|
case FitnessMode.feed:
|
|
return _snackBarAnchor.build(
|
|
new FloatingActionButton(
|
|
child: new Icon(type: 'content/add', size: 24),
|
|
onPressed: _handleActionButtonPressed
|
|
));
|
|
case FitnessMode.chart:
|
|
return null;
|
|
}
|
|
}
|
|
|
|
Widget build() {
|
|
return new Scaffold(
|
|
toolbar: buildToolBar(),
|
|
body: buildBody(),
|
|
snackBar: buildSnackBar(),
|
|
floatingActionButton: buildFloatingActionButton(),
|
|
drawer: buildDrawer()
|
|
);
|
|
}
|
|
}
|
|
|
|
class AddItemDialog extends StatefulComponent {
|
|
AddItemDialog(this.navigator);
|
|
|
|
Navigator navigator;
|
|
|
|
void syncConstructorArguments(AddItemDialog source) {
|
|
this.navigator = source.navigator;
|
|
}
|
|
|
|
// 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() {
|
|
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: navigator.pop,
|
|
actions: [
|
|
new FlatButton(
|
|
child: new Text('CANCEL'),
|
|
onPressed: navigator.pop
|
|
),
|
|
new FlatButton(
|
|
child: new Text('ADD'),
|
|
onPressed: () {
|
|
navigator.pop(_addItemRoute);
|
|
}
|
|
),
|
|
]
|
|
);
|
|
}
|
|
}
|