diff --git a/sky/sdk/example/fitness/lib/home.dart b/sky/sdk/example/fitness/lib/home.dart index b0b434d355b0ad7125c0f57c79062161d30d9b6c..df616c7cba39b5d50fa025b97251f3ef5e94f107 100644 --- a/sky/sdk/example/fitness/lib/home.dart +++ b/sky/sdk/example/fitness/lib/home.dart @@ -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 measurements; + final MeasurementHandler onDismissed; + + Widget build() { + return new Material( + type: MaterialType.canvas, + child: new ScrollableList( + 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 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 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( diff --git a/sky/sdk/example/fitness/lib/main.dart b/sky/sdk/example/fitness/lib/main.dart index 7712fed2918e24d55e08db2e264bca872e9903f1..c24705ed49174530965da1c2b7dd842dfde6de29 100644 --- a/sky/sdk/example/fitness/lib/main.dart +++ b/sky/sdk/example/fitness/lib/main.dart @@ -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 _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() { diff --git a/sky/sdk/example/fitness/lib/measurement.dart b/sky/sdk/example/fitness/lib/measurement.dart index cc1986542cb70b6bbfc5ebc496e073d54a29fde5..6487fd50150849a93ccd08afed0ff9ee673c1afe 100644 --- a/sky/sdk/example/fitness/lib/measurement.dart +++ b/sky/sdk/example/fitness/lib/measurement.dart @@ -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() + ); + } +} diff --git a/sky/sdk/lib/widgets/snack_bar.dart b/sky/sdk/lib/widgets/snack_bar.dart index 074816db908d11dcd3109a12cefde0e34248228e..00455f5f5e025affc6f04cd5e7bcdca3dc67da8e 100644 --- a/sky/sdk/lib/widgets/snack_bar.dart +++ b/sky/sdk/lib/widgets/snack_bar.dart @@ -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), diff --git a/sky/sdk/lib/widgets/tool_bar.dart b/sky/sdk/lib/widgets/tool_bar.dart index 3ed8bf9879ed0bceb291f83371e5df7a2ad136a1..8fec03cd805fc12834a40c61c62f5c33cfd85c21 100644 --- a/sky/sdk/lib/widgets/tool_bar.dart +++ b/sky/sdk/lib/widgets/tool_bar.dart @@ -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