Merge pull request #2185 from eseidelGoogle/delete_fitness
Remove examples/fitness
9
examples/fitness/.gitignore
vendored
@ -1,9 +0,0 @@
|
||||
.DS_Store
|
||||
.atom/
|
||||
.idea
|
||||
.packages
|
||||
.pub/
|
||||
build/
|
||||
ios/.generated/
|
||||
packages
|
||||
pubspec.lock
|
||||
@ -1 +0,0 @@
|
||||
# fitness
|
||||
@ -1,62 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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.
|
||||
-->
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.domokit.fitness" android:versionCode="4" android:versionName="0.0.4">
|
||||
|
||||
<uses-sdk android:minSdkVersion="14" android:targetSdkVersion="21" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<!-- for GCM -->
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
|
||||
<!-- Supposedly this permission prevents other apps from receiving our
|
||||
messages, but it doesn't seem to have any effect. -->
|
||||
<permission android:name="org.domokit.fitness.permission.C2D_MESSAGE"
|
||||
android:protectionLevel="signature" />
|
||||
<uses-permission android:name="org.domokit.fitness.permission.C2D_MESSAGE" />
|
||||
<!-- end for GCM -->
|
||||
|
||||
<application android:icon="@mipmap/ic_launcher" android:label="Fitness" android:name="org.domokit.sky.shell.SkyApplication">
|
||||
<activity android:configChanges="orientation|keyboardHidden|keyboard|screenSize" android:hardwareAccelerated="true" android:launchMode="singleTask" android:name="org.domokit.sky.shell.SkyActivity" android:theme="@android:style/Theme.Black.NoTitleBar">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<service
|
||||
android:name="org.domokit.sky.shell.UpdateService"
|
||||
android:exported="false"
|
||||
android:process=":remote"/>
|
||||
|
||||
<!-- for GCM -->
|
||||
<receiver
|
||||
android:name="com.google.android.gms.gcm.GcmReceiver"
|
||||
android:exported="true"
|
||||
android:permission="com.google.android.c2dm.permission.SEND" >
|
||||
<intent-filter>
|
||||
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
|
||||
<category android:name="org.domokit.sky.shell" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<service
|
||||
android:name="org.domokit.gcm.GcmListenerService"
|
||||
android:exported="false" >
|
||||
<intent-filter>
|
||||
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
<service
|
||||
android:name="org.domokit.gcm.InstanceIDListenerService"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="com.google.android.gms.iid.InstanceID"/>
|
||||
</intent-filter>
|
||||
</service>
|
||||
<service
|
||||
android:name="org.domokit.gcm.RegistrationIntentService"
|
||||
android:exported="false">
|
||||
</service>
|
||||
</application>
|
||||
</manifest>
|
||||
@ -1,10 +0,0 @@
|
||||
Icon image comes from:
|
||||
https://openclipart.org/detail/22309/apple-icon
|
||||
and is public domain.
|
||||
|
||||
Icon resources were generated using:
|
||||
http://romannurik.github.io/AndroidAssetStudio/icons-launcher.html
|
||||
with settings:
|
||||
http://romannurik.github.io/AndroidAssetStudio/icons-launcher.html#foreground.type=image&foreground.space.trim=1&foreground.space.pad=0&foreColor=607d8b%2C0&crop=0&backgroundShape=none&backColor=ffffff%2C100&effects=none
|
||||
which produces art under CC 3.0:
|
||||
http://creativecommons.org/licenses/by/3.0/
|
||||
@ -1,24 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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.
|
||||
-->
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.domokit.mine_digger">
|
||||
|
||||
<uses-sdk android:minSdkVersion="14" android:targetSdkVersion="21" />
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
|
||||
<application android:name="org.domokit.sky.shell.SkyApplication" android:label="Mine Digger">
|
||||
<activity android:name="org.domokit.sky.shell.SkyActivity"
|
||||
android:launchMode="singleTask"
|
||||
android:theme="@android:style/Theme.Black.NoTitleBar"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize"
|
||||
android:hardwareAccelerated="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
||||
@ -1,3 +0,0 @@
|
||||
Still barely works
|
||||
Fixed crash when entering an invalid number
|
||||
Made date list look less-awful.
|
||||
@ -1 +0,0 @@
|
||||
Adds very basic charting support
|
||||
@ -1 +0,0 @@
|
||||
Now supports setting goal weight.
|
||||
|
Before Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 7.9 KiB |
|
Before Width: | Height: | Size: 12 KiB |
@ -1,15 +0,0 @@
|
||||
name: fitness
|
||||
version: 0.0.1
|
||||
update-url: http://localhost:9888/
|
||||
material-design-icons:
|
||||
- name: action/assessment
|
||||
- name: action/help
|
||||
- name: action/settings
|
||||
- name: action/view_list
|
||||
- name: av/stop
|
||||
- name: content/add
|
||||
- name: maps/directions_run
|
||||
- name: navigation/arrow_back
|
||||
- name: navigation/close
|
||||
- name: navigation/menu
|
||||
- name: navigation/more_vert
|
||||
1
examples/fitness/ios/.gitignore
vendored
@ -1 +0,0 @@
|
||||
.generated/
|
||||
@ -1,142 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-Small@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-Small@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-Small-40@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-Small-40@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-60@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-60@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-Small.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-Small@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-Small-40.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-Small-40@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-76.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-76@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "83.5x83.5",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-83.5@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "16x16",
|
||||
"idiom" : "mac",
|
||||
"filename" : "icon_16x16.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "16x16",
|
||||
"idiom" : "mac",
|
||||
"filename" : "icon_16x16@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "32x32",
|
||||
"idiom" : "mac",
|
||||
"filename" : "icon_32x32.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "32x32",
|
||||
"idiom" : "mac",
|
||||
"filename" : "icon_32x32@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "128x128",
|
||||
"idiom" : "mac",
|
||||
"filename" : "icon_128x128.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "128x128",
|
||||
"idiom" : "mac",
|
||||
"filename" : "icon_128x128@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "256x256",
|
||||
"idiom" : "mac",
|
||||
"filename" : "icon_256x256.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "256x256",
|
||||
"idiom" : "mac",
|
||||
"filename" : "icon_256x256@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "512x512",
|
||||
"idiom" : "mac",
|
||||
"filename" : "icon_512x512.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "512x512",
|
||||
"idiom" : "mac",
|
||||
"filename" : "icon_512x512@2x.png",
|
||||
"scale" : "2x"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 7.5 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 869 B |
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 425 B |
|
Before Width: | Height: | Size: 952 B |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 952 B |
|
Before Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 56 KiB |
@ -1,47 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>Runner</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>io.flutter.example.fitness</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>Fitness</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>arm64</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
@ -1,27 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="9531" systemVersion="15C50" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" initialViewController="01J-lp-oVM">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9529"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="EHf-IW-A2E">
|
||||
<objects>
|
||||
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="Llm-lL-Icb"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="xb3-aO-Qok"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="53" y="375"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
@ -1,87 +0,0 @@
|
||||
// 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.
|
||||
|
||||
// Forked from https://github.com/dart-lang/sdk/blob/master/samples-dev/swarm/swarm_ui_lib/util/DateUtils.dart
|
||||
class DateUtils {
|
||||
|
||||
static const WEEKDAYS = const ['Monday', 'Tuesday', 'Wednesday', 'Thursday',
|
||||
'Friday', 'Saturday', 'Sunday'];
|
||||
|
||||
static const YESTERDAY = 'Yesterday';
|
||||
|
||||
static const MS_IN_WEEK = DateTime.DAYS_PER_WEEK * Duration.MILLISECONDS_PER_DAY;
|
||||
|
||||
// TODO(jmesserly): locale specific date format
|
||||
static String _twoDigits(int n) {
|
||||
if (n >= 10)
|
||||
return '$n';
|
||||
return '0$n';
|
||||
}
|
||||
|
||||
/// Formats a time in H:MM A format
|
||||
static String toHourMinutesString(Duration duration) {
|
||||
assert(duration.inDays == 0);
|
||||
int hours = duration.inHours;
|
||||
String a;
|
||||
if (hours >= 12) {
|
||||
a = 'pm';
|
||||
if (hours != 12)
|
||||
hours -= 12;
|
||||
} else {
|
||||
a = 'am';
|
||||
if (hours == 0)
|
||||
hours += 12;
|
||||
}
|
||||
String twoDigits(int n) {
|
||||
if (n >= 10)
|
||||
return '$n';
|
||||
return '0$n';
|
||||
}
|
||||
String mm = twoDigits(duration.inMinutes.remainder(Duration.MINUTES_PER_HOUR));
|
||||
return '$hours:$mm $a';
|
||||
}
|
||||
|
||||
/// A date/time formatter that takes into account the current date/time:
|
||||
/// - if it's from today, just show the time
|
||||
/// - if it's from yesterday, just show 'Yesterday'
|
||||
/// - if it's from the same week, just show the weekday
|
||||
/// - otherwise, show just the date
|
||||
static String toRecentTimeString(DateTime then) {
|
||||
bool datesAreEqual(DateTime d1, DateTime d2) {
|
||||
return (d1.year == d2.year) &&
|
||||
(d1.month == d2.month) &&
|
||||
(d1.day == d2.day);
|
||||
}
|
||||
|
||||
final now = new DateTime.now();
|
||||
if (datesAreEqual(then, now)) {
|
||||
return toHourMinutesString(new Duration(
|
||||
days: 0,
|
||||
hours: then.hour,
|
||||
minutes: then.minute,
|
||||
seconds: then.second,
|
||||
milliseconds: then.millisecond)
|
||||
);
|
||||
}
|
||||
|
||||
final today = new DateTime(now.year, now.month, now.day, 0, 0, 0, 0);
|
||||
Duration delta = today.difference(then);
|
||||
if (delta.inMilliseconds < Duration.MILLISECONDS_PER_DAY) {
|
||||
return YESTERDAY;
|
||||
} else if (delta.inMilliseconds < MS_IN_WEEK) {
|
||||
return WEEKDAYS[then.weekday];
|
||||
} else {
|
||||
String twoDigitMonth = _twoDigits(then.month);
|
||||
String twoDigitDay = _twoDigits(then.day);
|
||||
return '${then.year}-$twoDigitMonth-$twoDigitDay';
|
||||
}
|
||||
}
|
||||
|
||||
static String toDateString(DateTime then) {
|
||||
// TODO(jmesserly): locale specific date format
|
||||
String twoDigitMonth = _twoDigits(then.month);
|
||||
String twoDigitDay = _twoDigits(then.day);
|
||||
return '${then.year}-$twoDigitMonth-$twoDigitDay';
|
||||
}
|
||||
}
|
||||
@ -1,260 +0,0 @@
|
||||
// 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 ScrollableList(
|
||||
padding: const EdgeDims.all(4.0),
|
||||
itemExtent: kFitnessItemHeight,
|
||||
children: items.map((FitnessItem 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: children)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class FeedFragment extends StatefulComponent {
|
||||
FeedFragment({ this.userData, this.onItemCreated, this.onItemDeleted });
|
||||
|
||||
final UserData userData;
|
||||
final FitnessItemHandler onItemCreated;
|
||||
final FitnessItemHandler onItemDeleted;
|
||||
|
||||
FeedFragmentState createState() => new FeedFragmentState();
|
||||
}
|
||||
|
||||
class FeedFragmentState extends State<FeedFragment> {
|
||||
FitnessMode _fitnessMode = FitnessMode.feed;
|
||||
|
||||
void _handleFitnessModeChange(FitnessMode value) {
|
||||
setState(() {
|
||||
_fitnessMode = value;
|
||||
});
|
||||
Navigator.pop(context);
|
||||
}
|
||||
|
||||
Widget _buildDrawer() {
|
||||
return new Drawer(
|
||||
child: new Block(children: <Widget>[
|
||||
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() {
|
||||
Navigator.popAndPushNamed(context, '/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(
|
||||
center: new Text(fitnessModeTitle)
|
||||
);
|
||||
}
|
||||
|
||||
void _handleItemDismissed(FitnessItem item) {
|
||||
config.onItemDeleted(item);
|
||||
Scaffold.of(context).showSnackBar(new SnackBar(
|
||||
content: new Text("Item deleted."),
|
||||
action: new SnackBarAction(
|
||||
label: "UNDO",
|
||||
onPressed: () {
|
||||
config.onItemCreated(item);
|
||||
}
|
||||
)
|
||||
));
|
||||
}
|
||||
|
||||
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 Container();
|
||||
if (config.userData.items.length == 0) {
|
||||
return new Row(
|
||||
children: <Widget>[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 Container(
|
||||
padding: const EdgeDims.all(20.0),
|
||||
child: buildChart()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _handleActionButtonPressed() {
|
||||
showDialog(context: context, child: new AddItemDialog()).then((routeName) {
|
||||
if (routeName != null)
|
||||
Navigator.pushNamed(context, routeName);
|
||||
});
|
||||
}
|
||||
|
||||
Widget buildFloatingActionButton() {
|
||||
switch (_fitnessMode) {
|
||||
case FitnessMode.feed:
|
||||
return new FloatingActionButton(
|
||||
child: new Icon(icon: 'content/add'),
|
||||
onPressed: _handleActionButtonPressed
|
||||
);
|
||||
case FitnessMode.chart:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Widget build(BuildContext context) {
|
||||
return new Scaffold(
|
||||
toolBar: buildToolBar(),
|
||||
body: buildBody(),
|
||||
floatingActionButton: buildFloatingActionButton(),
|
||||
drawer: _buildDrawer()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AddItemDialog extends StatefulComponent {
|
||||
AddItemDialogState createState() => new AddItemDialogState();
|
||||
}
|
||||
|
||||
class AddItemDialogState extends State<AddItemDialog> {
|
||||
// TODO(jackson): Internationalize
|
||||
static final Map<String, String> _labels = <String, String>{
|
||||
'/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 = <Widget>[];
|
||||
for (String routeName in _labels.keys) {
|
||||
menuItems.add(new DialogMenuItem(<Widget>[
|
||||
new Flexible(child: new Text(_labels[routeName])),
|
||||
new Radio<String>(value: routeName, groupValue: _addItemRoute, onChanged: _handleAddItemRouteChanged),
|
||||
], onPressed: () => _handleAddItemRouteChanged(routeName)));
|
||||
}
|
||||
return new Dialog(
|
||||
title: new Text("What are you doing?"),
|
||||
content: new Block(children: menuItems),
|
||||
actions: <Widget>[
|
||||
new FlatButton(
|
||||
child: new Text('CANCEL'),
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
),
|
||||
new FlatButton(
|
||||
child: new Text('ADD'),
|
||||
onPressed: () {
|
||||
Navigator.pop(context, _addItemRoute);
|
||||
}
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,61 +0,0 @@
|
||||
// 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;
|
||||
|
||||
typedef void FitnessItemHandler(FitnessItem item);
|
||||
|
||||
// TODO(eseidel): This should be a constant on a SingleLineTile class
|
||||
// https://www.google.com/design/spec/components/lists.html#lists-specs
|
||||
const double kFitnessItemHeight = 48.0;
|
||||
|
||||
abstract class FitnessItem {
|
||||
FitnessItem.fromJson(Map json) : when = DateTime.parse(json['when']);
|
||||
|
||||
FitnessItem({ this.when }) {
|
||||
assert(when != null);
|
||||
}
|
||||
final DateTime when;
|
||||
|
||||
Map toJson() => { 'when' : when.toIso8601String() };
|
||||
|
||||
// TODO(jackson): Internationalize
|
||||
String get displayDate => DateUtils.toDateString(when);
|
||||
|
||||
FitnessItemRow toRow({ FitnessItemHandler onDismissed });
|
||||
}
|
||||
|
||||
abstract class FitnessItemRow extends StatelessComponent {
|
||||
|
||||
FitnessItemRow({ FitnessItem item, this.onDismissed })
|
||||
: this.item = item,
|
||||
super(key: new ValueKey<DateTime>(item.when)) {
|
||||
assert(onDismissed != null);
|
||||
}
|
||||
|
||||
final FitnessItem item;
|
||||
final FitnessItemHandler onDismissed;
|
||||
|
||||
Widget buildContent(BuildContext context);
|
||||
|
||||
Widget build(BuildContext context) {
|
||||
return new Dismissable(
|
||||
onDismissed: () => onDismissed(item),
|
||||
child: new Container(
|
||||
height: kFitnessItemHeight,
|
||||
// TODO(eseidel): Padding top should be 16px for a single-line tile:
|
||||
// https://www.google.com/design/spec/components/lists.html#lists-specs
|
||||
padding: const EdgeDims.all(10.0),
|
||||
// TODO(eseidel): This line should be drawn by the list as it should
|
||||
// stay put even when the tile is dismissed!
|
||||
decoration: new BoxDecoration(
|
||||
border: new Border(
|
||||
bottom: new BorderSide(color: Theme.of(context).dividerColor)
|
||||
)
|
||||
),
|
||||
child: buildContent(context)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,8 +0,0 @@
|
||||
// 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;
|
||||
|
||||
enum FitnessMode { feed, chart }
|
||||
enum BackupMode { enabled, disabled }
|
||||
@ -1,165 +0,0 @@
|
||||
// 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.
|
||||
|
||||
library fitness;
|
||||
|
||||
import 'package:playfair/playfair.dart' as playfair;
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'user_data.dart';
|
||||
import 'date_utils.dart';
|
||||
import 'dart:async';
|
||||
import 'dart:math' as math;
|
||||
|
||||
part 'feed.dart';
|
||||
part 'fitness_item.dart';
|
||||
part 'fitness_types.dart';
|
||||
part 'meal.dart';
|
||||
part 'measurement.dart';
|
||||
part 'settings.dart';
|
||||
|
||||
abstract class UserData {
|
||||
BackupMode get backupMode;
|
||||
double get goalWeight;
|
||||
List<FitnessItem> get items;
|
||||
}
|
||||
|
||||
class UserDataImpl extends UserData {
|
||||
UserDataImpl();
|
||||
|
||||
List<FitnessItem> _items = <FitnessItem>[];
|
||||
|
||||
BackupMode _backupMode;
|
||||
BackupMode get backupMode => _backupMode;
|
||||
void set backupMode(BackupMode value) {
|
||||
_backupMode = value;
|
||||
}
|
||||
|
||||
double _goalWeight;
|
||||
double get goalWeight => _goalWeight;
|
||||
void set goalWeight(double value) {
|
||||
_goalWeight = value;
|
||||
}
|
||||
|
||||
List<FitnessItem> get items => _items;
|
||||
|
||||
void sort() {
|
||||
_items.sort((FitnessItem a, FitnessItem b) => a.when.compareTo(b.when));
|
||||
}
|
||||
|
||||
void add(FitnessItem item) {
|
||||
_items.add(item);
|
||||
sort();
|
||||
}
|
||||
|
||||
void remove(FitnessItem item) {
|
||||
_items.remove(item);
|
||||
}
|
||||
|
||||
Future save() => saveFitnessData(this);
|
||||
|
||||
UserDataImpl.fromJson(Map json) {
|
||||
json['items'].forEach((item) {
|
||||
_items.add(new Measurement.fromJson(item));
|
||||
});
|
||||
try {
|
||||
_backupMode = BackupMode.values.firstWhere((BackupMode mode) {
|
||||
return mode.toString() == json['backupMode'];
|
||||
});
|
||||
} catch(e) {
|
||||
print("Failed to load backup mode: $e");
|
||||
}
|
||||
_goalWeight = json['goalWeight'];
|
||||
}
|
||||
|
||||
Map toJson() {
|
||||
Map json = new Map();
|
||||
json['items'] = _items.map((FitnessItem item) => item.toJson()).toList();
|
||||
json['backupMode'] = _backupMode.toString();
|
||||
json['goalWeight'] = _goalWeight;
|
||||
return json;
|
||||
}
|
||||
}
|
||||
|
||||
class FitnessApp extends StatefulComponent {
|
||||
FitnessAppState createState() => new FitnessAppState();
|
||||
}
|
||||
|
||||
class FitnessAppState extends State<FitnessApp> {
|
||||
UserDataImpl _userData;
|
||||
|
||||
void initState() {
|
||||
super.initState();
|
||||
loadFitnessData().then((UserData data) {
|
||||
setState(() => _userData = data);
|
||||
}).catchError((e) {
|
||||
print("Failed to load data: $e");
|
||||
setState(() => _userData = new UserDataImpl());
|
||||
});
|
||||
}
|
||||
|
||||
void _handleItemCreated(FitnessItem item) {
|
||||
setState(() {
|
||||
_userData.add(item);
|
||||
_userData.save();
|
||||
});
|
||||
}
|
||||
|
||||
void _handleItemDeleted(FitnessItem item) {
|
||||
setState(() {
|
||||
_userData.remove(item);
|
||||
_userData.save();
|
||||
});
|
||||
}
|
||||
|
||||
void settingsUpdater({ BackupMode backup, double goalWeight }) {
|
||||
setState(() {
|
||||
if (backup != null)
|
||||
_userData.backupMode = backup;
|
||||
if (goalWeight != null)
|
||||
_userData.goalWeight = goalWeight;
|
||||
_userData.save();
|
||||
});
|
||||
}
|
||||
|
||||
Widget build(BuildContext context) {
|
||||
return new MaterialApp(
|
||||
theme: new ThemeData(
|
||||
brightness: ThemeBrightness.light,
|
||||
primarySwatch: Colors.indigo,
|
||||
accentColor: Colors.pinkAccent[200]
|
||||
),
|
||||
title: 'Fitness',
|
||||
routes: <String, RouteBuilder>{
|
||||
'/': (RouteArguments args) {
|
||||
return new FeedFragment(
|
||||
userData: _userData,
|
||||
onItemCreated: _handleItemCreated,
|
||||
onItemDeleted: _handleItemDeleted
|
||||
);
|
||||
},
|
||||
'/meals/new': (RouteArguments args) {
|
||||
return new MealFragment(
|
||||
onCreated: _handleItemCreated
|
||||
);
|
||||
},
|
||||
'/measurements/new': (RouteArguments args) {
|
||||
return new MeasurementFragment(
|
||||
onCreated: _handleItemCreated
|
||||
);
|
||||
},
|
||||
'/settings': (RouteArguments args) {
|
||||
return new SettingsFragment(
|
||||
userData: _userData,
|
||||
updater: settingsUpdater
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
main() {
|
||||
runApp(new FitnessApp());
|
||||
}
|
||||
@ -1,107 +0,0 @@
|
||||
// Copyright 2014 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 Meal extends FitnessItem {
|
||||
Meal({ DateTime when, this.description }) : super(when: when);
|
||||
|
||||
final String description;
|
||||
|
||||
FitnessItemRow toRow({ FitnessItemHandler onDismissed }) {
|
||||
return new MealRow(meal: this, onDismissed: onDismissed);
|
||||
}
|
||||
}
|
||||
|
||||
class MealRow extends FitnessItemRow {
|
||||
MealRow({ Meal meal, FitnessItemHandler onDismissed })
|
||||
: super(item: meal, onDismissed: onDismissed);
|
||||
|
||||
Widget buildContent(BuildContext context) {
|
||||
Meal meal = item;
|
||||
List<Widget> children = <Widget>[
|
||||
new Flexible(
|
||||
child: new Text(
|
||||
meal.description,
|
||||
style: const TextStyle(textAlign: TextAlign.right)
|
||||
)
|
||||
),
|
||||
new Flexible(
|
||||
child: new Text(
|
||||
meal.displayDate,
|
||||
style: Theme.of(context).text.caption.copyWith(textAlign: TextAlign.right)
|
||||
)
|
||||
)
|
||||
];
|
||||
return new Row(
|
||||
children: children,
|
||||
alignItems: FlexAlignItems.baseline,
|
||||
textBaseline: DefaultTextStyle.of(context).textBaseline
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MealFragment extends StatefulComponent {
|
||||
MealFragment({ this.onCreated });
|
||||
|
||||
FitnessItemHandler onCreated;
|
||||
|
||||
MealFragmentState createState() => new MealFragmentState();
|
||||
}
|
||||
|
||||
class MealFragmentState extends State<MealFragment> {
|
||||
InputValue _description = InputValue.empty;
|
||||
|
||||
void _handleSave() {
|
||||
config.onCreated(new Meal(when: new DateTime.now(), description: _description.text));
|
||||
Navigator.pop(context);
|
||||
}
|
||||
|
||||
Widget buildToolBar() {
|
||||
return new ToolBar(
|
||||
left: new IconButton(
|
||||
icon: "navigation/close",
|
||||
onPressed: () => Navigator.pop(context)
|
||||
),
|
||||
center: new Text('New Meal'),
|
||||
right: <Widget>[
|
||||
// TODO(abarth): Should this be a FlatButton?
|
||||
new InkWell(
|
||||
onTap: _handleSave,
|
||||
child: new Text('SAVE')
|
||||
)
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
void _handleDescriptionChanged(InputValue description) {
|
||||
setState(() {
|
||||
_description = description;
|
||||
});
|
||||
}
|
||||
|
||||
static final GlobalKey descriptionKey = new GlobalKey();
|
||||
|
||||
Widget buildBody() {
|
||||
Meal meal = new Meal(when: new DateTime.now());
|
||||
return new Block(children: <Widget>[
|
||||
new Text(meal.displayDate),
|
||||
new Input(
|
||||
key: descriptionKey,
|
||||
autofocus: true,
|
||||
hintText: 'Describe meal',
|
||||
onChanged: _handleDescriptionChanged
|
||||
),
|
||||
],
|
||||
padding: const EdgeDims.all(20.0)
|
||||
);
|
||||
}
|
||||
|
||||
Widget build(BuildContext context) {
|
||||
return new Scaffold(
|
||||
toolBar: buildToolBar(),
|
||||
body: buildBody()
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,161 +0,0 @@
|
||||
// Copyright 2014 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 Measurement extends FitnessItem {
|
||||
Measurement({ DateTime when, this.weight }) : super(when: when);
|
||||
Measurement.fromJson(Map json) : weight = json['weight'], super.fromJson(json);
|
||||
|
||||
final double weight;
|
||||
|
||||
// TODO(jackson): Internationalize
|
||||
String get displayWeight => "${weight.toStringAsFixed(1)} lbs";
|
||||
|
||||
@override
|
||||
Map toJson() {
|
||||
Map json = super.toJson();
|
||||
json['weight'] = weight;
|
||||
json['type'] = runtimeType.toString();
|
||||
return json;
|
||||
}
|
||||
|
||||
FitnessItemRow toRow({ FitnessItemHandler onDismissed }) {
|
||||
return new MeasurementRow(measurement: this, onDismissed: onDismissed);
|
||||
}
|
||||
}
|
||||
|
||||
class MeasurementRow extends FitnessItemRow {
|
||||
MeasurementRow({ Measurement measurement, FitnessItemHandler onDismissed })
|
||||
: super(item: measurement, onDismissed: onDismissed);
|
||||
|
||||
Widget buildContent(BuildContext context) {
|
||||
Measurement measurement = item;
|
||||
List<Widget> children = <Widget>[
|
||||
new Flexible(
|
||||
child: new Text(
|
||||
measurement.displayWeight,
|
||||
style: Theme.of(context).text.subhead
|
||||
)
|
||||
),
|
||||
new Flexible(
|
||||
child: new Text(
|
||||
measurement.displayDate,
|
||||
style: Theme.of(context).text.caption.copyWith(textAlign: TextAlign.right)
|
||||
)
|
||||
)
|
||||
];
|
||||
return new Row(
|
||||
children: children,
|
||||
alignItems: FlexAlignItems.baseline,
|
||||
textBaseline: DefaultTextStyle.of(context).textBaseline
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MeasurementFragment extends StatefulComponent {
|
||||
MeasurementFragment({ this.onCreated });
|
||||
|
||||
final FitnessItemHandler onCreated;
|
||||
|
||||
MeasurementFragmentState createState() => new MeasurementFragmentState();
|
||||
}
|
||||
|
||||
class MeasurementFragmentState extends State<MeasurementFragment> {
|
||||
InputValue _weight = InputValue.empty;
|
||||
DateTime _when = new DateTime.now();
|
||||
|
||||
void _handleSave() {
|
||||
double parsedWeight;
|
||||
try {
|
||||
parsedWeight = double.parse(_weight.text);
|
||||
} on FormatException catch(e) {
|
||||
print("Exception $e");
|
||||
Scaffold.of(context).showSnackBar(new SnackBar(
|
||||
content: new Text('Save failed')
|
||||
));
|
||||
}
|
||||
config.onCreated(new Measurement(when: _when, weight: parsedWeight));
|
||||
Navigator.pop(context);
|
||||
}
|
||||
|
||||
Widget buildToolBar() {
|
||||
return new ToolBar(
|
||||
left: new IconButton(
|
||||
icon: "navigation/close",
|
||||
onPressed: () => Navigator.pop(context)
|
||||
),
|
||||
center: new Text('New Measurement'),
|
||||
right: <Widget>[
|
||||
// TODO(abarth): Should this be a FlatButton?
|
||||
new InkWell(
|
||||
onTap: _handleSave,
|
||||
child: new Text('SAVE')
|
||||
)
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
void _handleWeightChanged(InputValue weight) {
|
||||
setState(() {
|
||||
_weight = weight;
|
||||
});
|
||||
}
|
||||
|
||||
static final GlobalKey weightKey = new GlobalKey();
|
||||
|
||||
Future _handleDatePressed() async {
|
||||
DateTime value = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: _when,
|
||||
firstDate: new DateTime(2015, 8),
|
||||
lastDate: new DateTime(2101)
|
||||
);
|
||||
if (value != _when) {
|
||||
setState(() {
|
||||
_when = value;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Widget buildBody(BuildContext context) {
|
||||
Measurement measurement = new Measurement(when: _when);
|
||||
// TODO(jackson): Revisit the layout of this pane to be more maintainable
|
||||
return new Container(
|
||||
padding: const EdgeDims.all(20.0),
|
||||
child: new Column(
|
||||
children: <Widget>[
|
||||
new GestureDetector(
|
||||
onTap: _handleDatePressed,
|
||||
child: new Container(
|
||||
height: 50.0,
|
||||
child: new Column(
|
||||
children: <Widget>[
|
||||
new Text('Measurement Date'),
|
||||
new Text(measurement.displayDate, style: Theme.of(context).text.caption),
|
||||
],
|
||||
alignItems: FlexAlignItems.start
|
||||
)
|
||||
)
|
||||
),
|
||||
new Input(
|
||||
key: weightKey,
|
||||
autofocus: true,
|
||||
hintText: 'Enter weight',
|
||||
keyboardType: KeyboardType.number,
|
||||
onChanged: _handleWeightChanged
|
||||
),
|
||||
],
|
||||
alignItems: FlexAlignItems.stretch
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
Widget build(BuildContext context) {
|
||||
return new Scaffold(
|
||||
toolBar: buildToolBar(),
|
||||
body: buildBody(context)
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,137 +0,0 @@
|
||||
// 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 _SettingsDialog extends StatefulComponent {
|
||||
_SettingsDialogState createState() => new _SettingsDialogState();
|
||||
}
|
||||
|
||||
class _SettingsDialogState extends State<_SettingsDialog> {
|
||||
final GlobalKey weightGoalKey = new GlobalKey();
|
||||
|
||||
InputValue _goalWeight = InputValue.empty;
|
||||
|
||||
void _handleGoalWeightChanged(InputValue goalWeight) {
|
||||
setState(() {
|
||||
_goalWeight = goalWeight;
|
||||
});
|
||||
}
|
||||
|
||||
void _handleGoalWeightSubmitted(InputValue goalWeight) {
|
||||
_goalWeight = goalWeight;
|
||||
_handleSavePressed();
|
||||
}
|
||||
|
||||
void _handleSavePressed() {
|
||||
double goalWeight;
|
||||
try {
|
||||
goalWeight = double.parse(_goalWeight.text);
|
||||
} on FormatException {
|
||||
goalWeight = 0.0;
|
||||
}
|
||||
Navigator.pop(context, goalWeight);
|
||||
}
|
||||
|
||||
Widget build(BuildContext context) {
|
||||
return new Dialog(
|
||||
title: new Text("Goal Weight"),
|
||||
content: new Input(
|
||||
key: weightGoalKey,
|
||||
value: _goalWeight,
|
||||
autofocus: true,
|
||||
hintText: 'Goal weight in lbs',
|
||||
keyboardType: KeyboardType.number,
|
||||
onChanged: _handleGoalWeightChanged,
|
||||
onSubmitted: _handleGoalWeightSubmitted
|
||||
),
|
||||
actions: <Widget>[
|
||||
new FlatButton(
|
||||
child: new Text('CANCEL'),
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
),
|
||||
new FlatButton(
|
||||
child: new Text('SAVE'),
|
||||
onPressed: _handleSavePressed
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
typedef void SettingsUpdater({
|
||||
BackupMode backup,
|
||||
double goalWeight
|
||||
});
|
||||
|
||||
class SettingsFragment extends StatefulComponent {
|
||||
SettingsFragment({ this.userData, this.updater });
|
||||
|
||||
final UserData userData;
|
||||
final SettingsUpdater updater;
|
||||
|
||||
SettingsFragmentState createState() => new SettingsFragmentState();
|
||||
}
|
||||
|
||||
class SettingsFragmentState extends State<SettingsFragment> {
|
||||
void _handleBackupChanged(bool value) {
|
||||
assert(config.updater != null);
|
||||
config.updater(backup: value ? BackupMode.enabled : BackupMode.disabled);
|
||||
}
|
||||
|
||||
Widget buildToolBar() {
|
||||
return new ToolBar(
|
||||
center: new Text('Settings')
|
||||
);
|
||||
}
|
||||
|
||||
String get goalWeightText {
|
||||
if (config.userData.goalWeight == null || config.userData.goalWeight == 0.0)
|
||||
return "None";
|
||||
return "${config.userData.goalWeight}";
|
||||
}
|
||||
|
||||
Future _handleGoalWeightPressed() async {
|
||||
double goalWeight = await showDialog(
|
||||
context: context,
|
||||
child: new _SettingsDialog()
|
||||
);
|
||||
config.updater(goalWeight: goalWeight);
|
||||
}
|
||||
|
||||
Widget buildSettingsPane(BuildContext context) {
|
||||
return new Block(children: <Widget>[
|
||||
new DrawerItem(
|
||||
onPressed: () { _handleBackupChanged(!(config.userData.backupMode == BackupMode.enabled)); },
|
||||
child: new Row(
|
||||
children: <Widget>[
|
||||
new Flexible(child: new Text('Back up data to the cloud')),
|
||||
new Switch(value: config.userData.backupMode == BackupMode.enabled, onChanged: _handleBackupChanged),
|
||||
]
|
||||
)
|
||||
),
|
||||
new DrawerItem(
|
||||
onPressed: () => _handleGoalWeightPressed(),
|
||||
child: new Column(
|
||||
children: <Widget>[
|
||||
new Text('Goal Weight'),
|
||||
new Text(goalWeightText, style: Theme.of(context).text.caption),
|
||||
],
|
||||
alignItems: FlexAlignItems.start
|
||||
)
|
||||
),
|
||||
],
|
||||
padding: const EdgeDims.symmetric(vertical: 20.0)
|
||||
);
|
||||
}
|
||||
|
||||
Widget build(BuildContext context) {
|
||||
return new Scaffold(
|
||||
toolBar: buildToolBar(),
|
||||
body: buildSettingsPane(context)
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,40 +0,0 @@
|
||||
// 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.
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
import 'main.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
String cachedDataFilePath = null;
|
||||
|
||||
Future<String> dataFilePath() async {
|
||||
if (cachedDataFilePath == null) {
|
||||
String dataDir = await getFilesDir();
|
||||
cachedDataFilePath = path.join(dataDir, 'data.json');
|
||||
}
|
||||
return cachedDataFilePath;
|
||||
}
|
||||
|
||||
Future<UserData> loadFitnessData() async {
|
||||
String dataPath = await dataFilePath();
|
||||
print("Loading from $dataPath");
|
||||
JsonDecoder decoder = new JsonDecoder();
|
||||
Map data = await decoder.convert(await new File(dataPath).readAsString());
|
||||
return new UserDataImpl.fromJson(data);
|
||||
}
|
||||
|
||||
// Intentionally synchronous for execution just before shutdown.
|
||||
Future saveFitnessData(UserDataImpl data) async {
|
||||
String dataPath = await dataFilePath();
|
||||
print("Saving to $dataPath");
|
||||
JsonEncoder encoder = new JsonEncoder();
|
||||
String contents = await encoder.convert(data);
|
||||
File dataFile = await new File(dataPath).writeAsString(contents);
|
||||
print("Success! $dataFile");
|
||||
}
|
||||
@ -1,7 +0,0 @@
|
||||
name: fitness
|
||||
dependencies:
|
||||
path: ^1.3.6
|
||||
flutter:
|
||||
path: ../../packages/flutter
|
||||
playfair:
|
||||
path: ../../packages/playfair
|
||||