feat: add final project code

This commit is contained in:
Wlad Meixner 2022-09-30 10:19:50 +02:00
parent 7c8a0e786b
commit 3cd1060252
85 changed files with 1222 additions and 155 deletions

View File

@ -25,8 +25,15 @@ apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}
android {
compileSdkVersion flutter.compileSdkVersion
compileSdkVersion 33
ndkVersion flutter.ndkVersion
compileOptions {
@ -44,22 +51,28 @@ android {
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.example.grow_me_app"
applicationId "com.iamwlad.growme"
// You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
minSdkVersion flutter.minSdkVersion
minSdkVersion 33
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
}
}
signingConfigs {
release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
storePassword keystoreProperties['storePassword']
}
}
buildTypes {
release {
signingConfig signingConfigs.release
}
}
}
flutter {

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 B

View File

@ -1,12 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
<item>
<bitmap android:gravity="fill" android:src="@drawable/background"/>
</item>
<item>
<bitmap android:gravity="center" android:src="@drawable/splash"/>
</item>
</layer-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 B

View File

@ -1,12 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
<item>
<bitmap android:gravity="fill" android:src="@drawable/background"/>
</item>
<item>
<bitmap android:gravity="center" android:src="@drawable/splash"/>
</item>
</layer-list>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 544 B

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 442 B

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 721 B

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@ -5,6 +5,9 @@
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
<item name="android:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your

View File

@ -1,3 +1,4 @@
org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true
android.enableJetifier=true
protoc_platform=osx-x86_64

BIN
app/assets/icons/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 247 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

View File

@ -0,0 +1,132 @@
flutter_native_splash:
# This package generates native code to customize Flutter's default white native splash screen
# with background color and splash image.
# Customize the parameters below, and run the following command in the terminal:
# flutter pub run flutter_native_splash:create
# To restore Flutter's default white splash screen, run the following command in the terminal:
# flutter pub run flutter_native_splash:remove
# color or background_image is the only required parameter. Use color to set the background
# of your splash screen to a solid color. Use background_image to set the background of your
# splash screen to a png image. This is useful for gradients. The image will be stretch to the
# size of the app. Only one parameter can be used, color and background_image cannot both be set.
color: "#273825"
#background_image: "assets/background.png"
# Optional parameters are listed below. To enable a parameter, uncomment the line by removing
# the leading # character.
# The image parameter allows you to specify an image used in the splash screen. It must be a
# png file and should be sized for 4x pixel density.
image: assets/images/logo-splash.png
# The branding property allows you to specify an image used as branding in the splash screen.
# It must be a png file. It is supported for Android, iOS and the Web. For Android 12,
# see the Android 12 section below.
#branding:
# To position the branding image at the bottom of the screen you can use bottom, bottomRight,
# and bottomLeft. The default values is bottom if not specified or specified something else.
#branding_mode: bottom
# The color_dark, background_image_dark, image_dark, branding_dark are parameters that set the background
# and image when the device is in dark mode. If they are not specified, the app will use the
# parameters from above. If the image_dark parameter is specified, color_dark or
# background_image_dark must be specified. color_dark and background_image_dark cannot both be
# set.
#color_dark: "#042a49"
#background_image_dark: "assets/dark-background.png"
#image_dark: assets/splash-invert.png
#branding_dark: assets/dart_dark.png
# Android 12 handles the splash screen differently than previous versions. Please visit
# https://developer.android.com/guide/topics/ui/splash-screen
# Following are Android 12 specific parameter.
android_12:
# The image parameter sets the splash screen icon image. If this parameter is not specified,
# the app's launcher icon will be used instead.
# Please note that the splash screen will be clipped to a circle on the center of the screen.
# App icon with an icon background: This should be 960×960 pixels, and fit within a circle
# 640 pixels in diameter.
# App icon without an icon background: This should be 1152×1152 pixels, and fit within a circle
# 768 pixels in diameter.
#image: assets/android12splash.png
# Splash screen background color.
#color: "#42a5f5"
# App icon background color.
#icon_background_color: "#111111"
# The branding property allows you to specify an image used as branding in the splash screen.
#branding: assets/dart.png
# The image_dark, color_dark, icon_background_color_dark, and branding_dark set values that
# apply when the device is in dark mode. If they are not specified, the app will use the
# parameters from above.
#image_dark: assets/android12splash-invert.png
#color_dark: "#042a49"
#icon_background_color_dark: "#eeeeee"
# The android, ios and web parameters can be used to disable generating a splash screen on a given
# platform.
#android: false
#ios: false
#web: false
# Platform specific images can be specified with the following parameters, which will override
# the respective image parameter. You may specify all, selected, or none of these parameters:
#image_android: assets/splash-android.png
#image_dark_android: assets/splash-invert-android.png
#image_ios: assets/splash-ios.png
#image_dark_ios: assets/splash-invert-ios.png
#image_web: assets/splash-web.png
#image_dark_web: assets/splash-invert-web.png
#background_image_android: "assets/background-android.png"
#background_image_dark_android: "assets/dark-background-android.png"
#background_image_ios: "assets/background-ios.png"
#background_image_dark_ios: "assets/dark-background-ios.png"
#background_image_web: "assets/background-web.png"
#background_image_dark_web: "assets/dark-background-web.png"
#branding_android: assets/brand-android.png
#branding_dark_android: assets/dart_dark-android.png
#branding_ios: assets/brand-ios.png
#branding_dark_ios: assets/dart_dark-ios.png
# The position of the splash image can be set with android_gravity, ios_content_mode, and
# web_image_mode parameters. All default to center.
#
# android_gravity can be one of the following Android Gravity (see
# https://developer.android.com/reference/android/view/Gravity): bottom, center,
# center_horizontal, center_vertical, clip_horizontal, clip_vertical, end, fill, fill_horizontal,
# fill_vertical, left, right, start, or top.
#android_gravity: center
#
# ios_content_mode can be one of the following iOS UIView.ContentMode (see
# https://developer.apple.com/documentation/uikit/uiview/contentmode): scaleToFill,
# scaleAspectFit, scaleAspectFill, center, top, bottom, left, right, topLeft, topRight,
# bottomLeft, or bottomRight.
#ios_content_mode: center
#
# web_image_mode can be one of the following modes: center, contain, stretch, and cover.
#web_image_mode: center
# The screen orientation can be set in Android with the android_screen_orientation parameter.
# Valid parameters can be found here:
# https://developer.android.com/guide/topics/manifest/activity-element#screen
#android_screen_orientation: sensorLandscape
# To hide the notification bar, use the fullscreen parameter. Has no effect in web since web
# has no notification bar. Defaults to false.
# NOTE: Unlike Android, iOS will not automatically show the notification bar when the app loads.
# To show the notification bar, add the following code to your Flutter app:
# WidgetsFlutterBinding.ensureInitialized();
# SystemChrome.setEnabledSystemUIOverlays([SystemUiOverlay.bottom, SystemUiOverlay.top]);
#fullscreen: true
# If you have changed the name(s) of your info.plist file(s), you can specify the filename(s)
# with the info_plist_files parameter. Remove only the # characters in the three lines below,
# do not remove any spaces:
#info_plist_files:
# - 'ios/Runner/Info-Debug.plist'
# - 'ios/Runner/Info-Release.plist'

View File

@ -361,10 +361,13 @@
DEVELOPMENT_TEAM = 59GC8563ZF;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = GrowMe;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.lifestyle";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 0.5.1;
PRODUCT_BUNDLE_IDENTIFIER = de.iamwlad.growMeApp;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@ -491,10 +494,13 @@
DEVELOPMENT_TEAM = 59GC8563ZF;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = GrowMe;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.lifestyle";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 0.5.1;
PRODUCT_BUNDLE_IDENTIFIER = de.iamwlad.growMeApp;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@ -515,10 +521,13 @@
DEVELOPMENT_TEAM = 59GC8563ZF;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = GrowMe;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.lifestyle";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 0.5.1;
PRODUCT_BUNDLE_IDENTIFIER = de.iamwlad.growMeApp;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 226 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 564 B

After

Width:  |  Height:  |  Size: 555 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 827 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "background.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 B

View File

@ -1,23 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "LaunchImage.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

After

Width:  |  Height:  |  Size: 182 KiB

View File

@ -16,13 +16,19 @@
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
</imageView>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleToFill" image="LaunchBackground" translatesAutoresizingMaskIntoConstraints="NO" id="tWc-Dq-wcI"/>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4"></imageView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="leading" secondItem="Ze5-6b-2t3" secondAttribute="leading" id="3T2-ad-Qdv"/>
<constraint firstItem="tWc-Dq-wcI" firstAttribute="bottom" secondItem="Ze5-6b-2t3" secondAttribute="bottom" id="RPx-PI-7Xg"/>
<constraint firstItem="tWc-Dq-wcI" firstAttribute="top" secondItem="Ze5-6b-2t3" secondAttribute="top" id="SdS-ul-q2q"/>
<constraint firstAttribute="trailing" secondItem="tWc-Dq-wcI" secondAttribute="trailing" id="Swv-Gf-Rwn"/>
<constraint firstAttribute="trailing" secondItem="YRO-k0-Ey4" secondAttribute="trailing" id="TQA-XW-tRk"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="bottom" secondItem="Ze5-6b-2t3" secondAttribute="bottom" id="duK-uY-Gun"/>
<constraint firstItem="tWc-Dq-wcI" firstAttribute="leading" secondItem="Ze5-6b-2t3" secondAttribute="leading" id="kV7-tw-vXt"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="top" secondItem="Ze5-6b-2t3" secondAttribute="top" id="xPn-NY-SIU"/>
</constraints>
</view>
</viewController>
@ -32,6 +38,7 @@
</scene>
</scenes>
<resources>
<image name="LaunchImage" width="168" height="185"/>
<image name="LaunchImage" width="1931" height="792"/>
<image name="LaunchBackground" width="1" height="1"/>
</resources>
</document>

View File

@ -61,5 +61,7 @@
<string>Need Location permission</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Need Location permission</string>
<key>UIStatusBarHidden</key>
<false/>
</dict>
</plist>

View File

@ -0,0 +1,29 @@
{
"folders": [
{
"path": ".."
}
],
"settings": {
"workbench.colorCustomizations": {
"activityBar.activeBackground": "#2f7c47",
"activityBar.background": "#2f7c47",
"activityBar.foreground": "#e7e7e7",
"activityBar.inactiveForeground": "#e7e7e799",
"activityBarBadge.background": "#422c74",
"activityBarBadge.foreground": "#e7e7e7",
"commandCenter.border": "#e7e7e799",
"sash.hoverBorder": "#2f7c47",
"statusBar.background": "#215732",
"statusBar.foreground": "#e7e7e7",
"statusBarItem.hoverBackground": "#2f7c47",
"statusBarItem.remoteBackground": "#215732",
"statusBarItem.remoteForeground": "#e7e7e7",
"titleBar.activeBackground": "#215732",
"titleBar.activeForeground": "#e7e7e7",
"titleBar.inactiveBackground": "#21573299",
"titleBar.inactiveForeground": "#e7e7e799"
},
"peacock.color": "#215732"
}
}

52
app/lib/colors.dart Normal file
View File

@ -0,0 +1,52 @@
import 'package:flutter/material.dart';
const MaterialColor green = MaterialColor(0xFF273825, {
50: Color(0xFFE5E7E5),
100: Color(0xFFBEC3BE),
200: Color(0xFF939C92),
300: Color(0xFF687466),
400: Color(0xFF475646),
500: Color(0xFF273825),
600: Color(0xFF233221),
700: Color(0xFF1D2B1B),
800: Color(0xFF172416),
900: Color(0xFF0E170D),
});
const MaterialColor sand = MaterialColor(_sandPrimaryValue, <int, Color>{
50: Color(0xFFFCFBF9),
100: Color(0xFFF7F4F1),
200: Color(0xFFF2EDE7),
300: Color(0xFFEDE6DD),
400: Color(0xFFE9E0D6),
500: Color(_sandPrimaryValue),
600: Color(0xFFE2D7CA),
700: Color(0xFFDED2C3),
800: Color(0xFFDACDBD),
900: Color(0xFFD3C4B2),
});
const int _sandPrimaryValue = 0xFFE5DBCF;
const MaterialColor burnedEarth =
MaterialColor(_burnedearthPrimaryValue, <int, Color>{
50: Color(0xFFF7EDEA),
100: Color(0xFFEAD3CA),
200: Color(0xFFDDB6A7),
300: Color(0xFFCF9883),
400: Color(0xFFC48269),
500: Color(_burnedearthPrimaryValue),
600: Color(0xFFB36447),
700: Color(0xFFAB593D),
800: Color(0xFFA34F35),
900: Color(0xFF943D25),
});
const int _burnedearthPrimaryValue = 0xFFBA6C4E;
const MaterialColor burnedearthAccent =
MaterialColor(_burnedearthAccentValue, <int, Color>{
100: Color(0xFFFFDBD3),
200: Color(_burnedearthAccentValue),
400: Color(0xFFFF8A6D),
700: Color(0xFFFF7553),
});
const int _burnedearthAccentValue = 0xFFFFB2A0;

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:grow_me_app/colors.dart';
class BottomSheet extends StatelessWidget {
const BottomSheet({
@ -14,23 +15,32 @@ class BottomSheet extends StatelessWidget {
margin: const EdgeInsets.symmetric(horizontal: 30, vertical: 60),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(25.0),
color: Colors.white,
color: sand,
shape: BoxShape.rectangle,
border: Border.all(
color: Colors.grey.shade300, width: 1, style: BorderStyle.solid),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.4),
color: Colors.black.withOpacity(0.4),
spreadRadius: 3,
blurRadius: 25,
offset: const Offset(0, 10), // changes position of shadow
),
],
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 15),
child: child,
),
child: Column(children: [
Padding(
padding: const EdgeInsets.all(10),
child: DecoratedBox(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5.0),
color: sand.shade900,
),
child: const SizedBox(height: 10, width: 60),
)),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 15),
child: child,
)
]),
);
}
}

View File

@ -1,12 +1,67 @@
import 'package:flutter/material.dart';
import 'package:grow_me_app/colors.dart';
import 'package:grow_me_app/main.dart';
class Button extends StatelessWidget {
const Button(this.text, this.onTap, {super.key});
enum ButtonType {
normal,
outlined,
inverted,
}
class Button extends StatelessWidget {
Button(this.text, this.onTap, {super.key, this.type});
ButtonType? type = ButtonType.normal;
final void Function() onTap;
final String text;
Color _getTextColor() {
if (type == null) {
return sand;
}
switch (type!) {
case ButtonType.normal:
return sand;
case ButtonType.inverted:
case ButtonType.outlined:
return green;
}
}
Color _getBackgroundColor() {
if (type == null) {
return green;
}
switch (type!) {
case ButtonType.normal:
return green;
case ButtonType.inverted:
return sand;
case ButtonType.outlined:
return Colors.transparent;
}
}
BoxBorder? _getBorderStyle() {
if (type == null) {
return null;
}
switch (type!) {
case ButtonType.normal:
return null;
case ButtonType.inverted:
return null;
case ButtonType.outlined:
return Border.all(
color: green,
width: 2,
);
}
}
@override
Widget build(BuildContext context) {
return GestureDetector(
@ -16,14 +71,15 @@ class Button extends StatelessWidget {
padding: const EdgeInsets.all(8.0),
margin: const EdgeInsets.symmetric(horizontal: 8.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10.0),
color: primarySwatch,
shape: BoxShape.rectangle,
),
borderRadius: BorderRadius.circular(10.0),
color: _getBackgroundColor(),
shape: BoxShape.rectangle,
border: _getBorderStyle()),
child: Center(
child: Text(
text,
style: TextStyle(color: lightBackgroundColor),
textScaleFactor: 1.25,
style: TextStyle(color: _getTextColor()),
)),
),
);

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:grow_me_app/colors.dart';
import 'package:grow_me_app/main.dart';
class GrowMeCard extends StatelessWidget {
@ -14,10 +15,9 @@ class GrowMeCard extends StatelessWidget {
margin: const EdgeInsets.symmetric(horizontal: 8.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(25.0),
color: lightBackgroundColor,
color: sand,
shape: BoxShape.rectangle,
border: Border.all(
color: lightBackgroundColor, width: 2, style: BorderStyle.solid),
border: Border.all(color: sand, width: 2, style: BorderStyle.solid),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),

View File

@ -1,6 +1,7 @@
import 'dart:developer';
import 'package:grow_me_app/app.pb.dart';
import 'package:grow_me_app/colors.dart';
import 'package:grow_me_app/components/bottom_sheet.dart';
import 'package:grow_me_app/components/button.dart';
@ -77,10 +78,17 @@ class ConnectionSheetState extends State<ConnectionSheet> {
}
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(height: 10),
const Text("scanning for nearby devices"),
const Icon(Icons.bluetooth, size: 50, color: green),
const SizedBox(height: 10),
const Text(
"scanning for nearby devices",
textScaleFactor: 2,
textAlign: TextAlign.center,
),
const SizedBox(height: 50),
const LinearProgressIndicator(
value: null,
),
@ -146,12 +154,16 @@ class ConnectionSheetState extends State<ConnectionSheet> {
showCustomModalBottomSheet(
context,
Column(
mainAxisSize: MainAxisSize.min,
Flex(
direction: Axis.vertical,
children: <Widget>[
_scanningProgress(),
..._availableDeviceList(model),
const SizedBox(height: 25),
const SizedBox(height: 50),
// const Spacer(),
Button("Cancel", () {
Navigator.pop(context);
})
],
));
},

View File

@ -29,22 +29,53 @@ class DeviceActionSheet extends StatelessWidget {
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ListTile(
title: const Text("Rename"),
leading: const Icon(Icons.edit),
title: const Text(
"Edit goals",
textScaleFactor: 1.25,
),
leading: const Icon(
Icons.add_circle,
size: 33,
),
onTap: () {
Navigator.pop(context);
showEditDeviceModal(context, device);
}),
ListTile(
title: const Text("Debug"),
leading: const Icon(Icons.bug_report),
title: const Text(
"Rename",
textScaleFactor: 1.25,
),
leading: const Icon(
Icons.edit,
size: 33,
),
onTap: () {
Navigator.pop(context);
showEditDeviceModal(context, device);
}),
ListTile(
title: const Text(
"Debug",
textScaleFactor: 1.25,
),
leading: const Icon(
Icons.bug_report,
size: 33,
),
onTap: () {
Navigator.pop(context);
showDeviceDebugView(context, device);
}),
ListTile(
title: const Text("Remove"),
leading: const Icon(Icons.delete),
title: const Text(
"Remove",
textScaleFactor: 1.25,
),
leading: const Icon(
Icons.delete,
size: 33,
),
onTap: () {
model.removeById(device.description.remoteId);
Navigator.pop(context);

View File

@ -1,10 +1,11 @@
import 'package:carousel_slider/carousel_slider.dart';
import 'package:flutter/material.dart';
import 'package:grow_me_app/colors.dart';
import 'package:grow_me_app/components/button.dart';
import 'package:grow_me_app/components/card.dart';
import 'package:grow_me_app/components/device_action_sheet.dart';
import 'package:grow_me_app/components/device_debug.dart';
import 'package:grow_me_app/components/device_model.dart';
import 'package:grow_me_app/components/device_view.dart';
import 'package:provider/provider.dart';
class DeviceCarousel extends StatefulWidget {
@ -26,30 +27,9 @@ class DeviceCarouselState extends State<DeviceCarousel> {
margin: const EdgeInsets.symmetric(horizontal: 5.0),
child: GrowMeCard(
child: Padding(
padding: const EdgeInsets.all(25),
child: Column(
children: [
Text(device.description.name, textScaleFactor: 2),
const SizedBox(height: 15),
CircleAvatar(
backgroundColor: device.isConnected
? Colors.lightGreen
: Colors.grey[400],
backgroundImage: const AssetImage(
'assets/images/prototype-render.png'),
radius: 150.0,
),
...(!device.isConnected
? [
const SizedBox(height: 25),
const Text("Lost connection..."),
]
: [
const SizedBox(height: 25),
]),
const Spacer(),
DeviceActionSheet(device: device, model: model)
].toList(),
padding: const EdgeInsets.all(10),
child: DeviceView(
device: device,
))));
},
);
@ -64,6 +44,8 @@ class DeviceCarouselState extends State<DeviceCarousel> {
height: MediaQuery.of(context).size.height * 0.7,
initialPage: model.devices.isNotEmpty ? 1 : 0,
autoPlay: false,
scrollPhysics: const BouncingScrollPhysics(),
enlargeStrategy: CenterPageEnlargeStrategy.scale,
enlargeCenterPage: true,
clipBehavior: Clip.none,
enableInfiniteScroll: false),

View File

@ -26,7 +26,8 @@ class DeviceDebugViewState extends State<DeviceDebugView> {
..._motorStepValues.asMap().entries.map((entry) => Column(children: [
const SizedBox(height: 15),
Text("Motor ${entry.key + 1}"),
Row(
Flex(
direction: Axis.horizontal,
children: [
(widget.device.motorStatus == null
? Container()
@ -56,7 +57,6 @@ class DeviceDebugViewState extends State<DeviceDebugView> {
});
},
),
const Spacer(),
IconButton(
onPressed: () {
widget.device.moveMotor(

View File

@ -23,10 +23,32 @@ const Map<String, String> deviceCharacteristics = {
"info": "9c05490f-cc74-4fd2-8d16-fb228e3f2270"
};
enum MetricType { pomodor, sleep, water, steps }
class DeviceMetric {
MetricType type;
int progress = 0;
int target = 0;
String unit;
num get percentage {
return progress / target;
}
DeviceMetric(this.type, this.progress, this.target, this.unit);
}
class LinkedDevice {
BluetoothDevice? _device;
MotorStatus? _motorStatus;
KnownDevice description;
List<DeviceMetric?> metrics = [
DeviceMetric(MetricType.sleep, 0, 8, "h"),
DeviceMetric(MetricType.pomodor, 4, 10, "cycles"),
DeviceMetric(MetricType.water, 564, 2700, "ml"),
// empty metric slot
null
];
BluetoothDeviceState _status = BluetoothDeviceState.disconnected;
MotorStatus? get motorStatus {
@ -41,7 +63,7 @@ class LinkedDevice {
return _device;
}
void set device(BluetoothDevice? newDevice) {
set device(BluetoothDevice? newDevice) {
// NOTE: what should be done if device already set?
if (newDevice != null) {
newDevice.state.listen((event) {
@ -155,7 +177,7 @@ class DeviceModel extends ChangeNotifier {
LinkedDevice addFromDevice(BluetoothDevice device) {
var linkedDevice = LinkedDevice(
KnownDevice(
name: "Untitled",
name: "Hektor the Unnamed",
remoteId: device.id.toString(),
remoteName: device.name),
device,
@ -199,6 +221,10 @@ class DeviceModel extends ChangeNotifier {
_saveState();
}
void reconnect(LinkedDevice device) async {
_scanForKnownDevices();
}
void _scanForKnownDevices() async {
// try reconnecting devices
FlutterBlue flutterBlue = FlutterBlue.instance;
@ -232,7 +258,7 @@ class DeviceModel extends ChangeNotifier {
// Start scanning
try {
await flutterBlue.startScan(timeout: const Duration(seconds: 10));
await flutterBlue.startScan(timeout: const Duration(seconds: 2));
notifyListeners();
} catch (e) {

View File

@ -0,0 +1,160 @@
import 'package:carousel_slider/carousel_slider.dart';
import 'package:flutter/material.dart';
import 'package:grow_me_app/colors.dart';
import 'package:grow_me_app/components/button.dart';
import 'package:grow_me_app/components/card.dart';
import 'package:grow_me_app/components/device_action_sheet.dart';
import 'package:grow_me_app/components/device_model.dart';
import 'package:grow_me_app/components/edit_metric.dart';
import 'package:provider/provider.dart';
enum AppState {
DATA_NOT_FETCHED,
FETCHING_DATA,
DATA_READY,
NO_DATA,
AUTH_NOT_GRANTED,
DATA_ADDED,
DATA_NOT_ADDED,
STEPS_READY,
}
class MetricIcon extends StatelessWidget {
const MetricIcon(this.metric, this.onTap, {super.key});
final void Function(DeviceMetric?) onTap;
final DeviceMetric? metric;
IconData _icon() {
if (metric == null) {
return Icons.add;
}
switch (metric!.type) {
case MetricType.pomodor:
return Icons.timelapse;
case MetricType.sleep:
return Icons.bed_rounded;
case MetricType.water:
case MetricType.steps:
return Icons.water_drop_outlined;
}
}
Color _bgColor() {
if (metric == null) {
return sand.shade800;
}
if (metric!.percentage < 0.10) {
return burnedEarth.shade900;
} else if (metric!.percentage < 0.20) {
return burnedEarth.shade800;
} else if (metric!.percentage < 0.25) {
return burnedEarth.shade500;
} else if (metric!.percentage < 0.35) {
return green.shade100;
} else if (metric!.percentage < 0.50) {
return green.shade300;
} else if (metric!.percentage < 0.70) {
return green.shade500;
} else {
return green.shade700;
}
}
Color _textColor() {
if (metric == null) {
return green.shade400;
}
return sand;
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
onTap(metric);
},
child: Padding(
padding: const EdgeInsets.all(5),
child: DecoratedBox(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(50.0),
color: _bgColor(),
shape: BoxShape.rectangle,
),
child: Padding(
padding: const EdgeInsets.all(10),
child: Icon(
_icon(),
size: 33,
color: _textColor(),
)),
),
));
}
}
class DeviceView extends StatefulWidget {
const DeviceView({super.key, required this.device});
final LinkedDevice device;
@override
State<StatefulWidget> createState() => DeviceViewState();
}
class DeviceViewState extends State<DeviceView> {
@override
Widget build(BuildContext context) {
var device = widget.device;
return Consumer<DeviceModel>(builder: (context, model, child) {
return Column(
children: [
Text(
device.description.name,
textScaleFactor: 2,
textAlign: TextAlign.left,
),
const SizedBox(height: 25),
CircleAvatar(
backgroundColor: device.isConnected ? sand.shade900 : sand.shade900,
radius: 120.0,
child: const Padding(
padding: EdgeInsets.all(25),
child: Image(
image: AssetImage('assets/images/grow-me-medium.png'))),
),
const Spacer(),
const Text(
"Progress",
textScaleFactor: 1.25,
),
const SizedBox(height: 5),
Flex(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
direction: Axis.horizontal,
children: device.metrics
.map((m) => MetricIcon(m, (_) {
showEditMetricModal(context, device, m);
}))
.toList()),
const Spacer(),
!device.isConnected
? Button(
"Reconnect",
() {
model.reconnect(device);
},
type: ButtonType.outlined,
)
: Container(),
const SizedBox(height: 15),
DeviceActionSheet(device: device, model: model)
].toList(),
);
});
}
}

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_blue/flutter_blue.dart';
import 'package:grow_me_app/colors.dart';
import 'package:grow_me_app/components/bottom_sheet.dart';
import 'package:grow_me_app/components/button.dart';
import 'package:grow_me_app/components/device_model.dart';
@ -39,24 +40,29 @@ class EditDeviceViewState extends State<EditDeviceView> {
Widget build(BuildContext context) {
return Consumer<DeviceModel>(
builder: (context, model, value) => Column(
mainAxisSize: MainAxisSize.min,
// mainAxisSize: MainAxisSize.min,
children: [
const CircleAvatar(
backgroundColor: green,
radius: 100.0,
child: Padding(
padding: EdgeInsets.all(25),
child: Image(
image:
AssetImage('assets/images/grow-me-medium.png'))),
),
const SizedBox(height: 50),
const Text("Give your plant a name",
textScaleFactor: 1.5, textAlign: TextAlign.left),
const SizedBox(height: 15),
TextField(
controller: _deviceNameController,
decoration: const InputDecoration(
border: OutlineInputBorder(),
border: UnderlineInputBorder(),
hintText: 'Give your plant a name',
),
),
const SizedBox(height: 25),
const CircleAvatar(
backgroundColor: Colors.lightGreen,
backgroundImage:
AssetImage('assets/images/prototype-render.png'),
radius: 150.0,
),
const SizedBox(height: 50),
Button("Save", () {
widget.device.description.name =
_deviceNameController.value.text;
@ -74,7 +80,9 @@ class EditDeviceViewState extends State<EditDeviceView> {
Future showEditDeviceModal(BuildContext context, LinkedDevice device) {
return showCustomModalBottomSheet(
context,
EditDeviceView(
device: device,
));
Padding(
padding: const EdgeInsets.all(25),
child: EditDeviceView(
device: device,
)));
}

View File

@ -0,0 +1,382 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:async/async.dart';
import 'package:grow_me_app/components/bottom_sheet.dart';
import 'package:grow_me_app/components/device_model.dart';
import 'package:grow_me_app/components/device_view.dart';
import 'package:health/health.dart';
import 'package:permission_handler/permission_handler.dart';
class EditMetricView extends StatefulWidget {
const EditMetricView({
required this.device,
required this.metric,
super.key,
});
final LinkedDevice device;
final DeviceMetric? metric;
@override
EditMetricViewState createState() => EditMetricViewState();
}
class EditMetricViewState extends State<EditMetricView> {
List<HealthDataPoint> _healthDataList = [];
AppState _state = AppState.DATA_NOT_FETCHED;
int _nofSteps = 10;
double _mgdl = 10.0;
CancelableOperation? dataFetch;
// create a HealthFactory for use in the app
HealthFactory health = HealthFactory();
@override
void initState() {
super.initState();
if (widget.metric != null) {
if (widget.metric!.type == MetricType.steps) {
dataFetch = CancelableOperation.fromFuture(fetchStepData());
} else {
dataFetch = CancelableOperation.fromFuture(fetchData());
}
}
}
@override
void dispose() {
// your dispose part
super.dispose();
if (dataFetch != null && !dataFetch!.isCompleted) {
dataFetch!.cancel();
}
}
/// Fetch data points from the health plugin and show them in the app.
Future fetchData() async {
setState(() => _state = AppState.FETCHING_DATA);
// define the types to get
final types = [
HealthDataType.STEPS,
HealthDataType.WEIGHT,
HealthDataType.HEIGHT,
HealthDataType.BLOOD_GLUCOSE,
HealthDataType.WORKOUT,
HealthDataType.WATER,
HealthDataType.SLEEP_ASLEEP,
// Uncomment these lines on iOS - only available on iOS
// HealthDataType.AUDIOGRAM
];
// with coresponsing permissions
final permissions = [
HealthDataAccess.READ,
HealthDataAccess.READ,
HealthDataAccess.READ,
HealthDataAccess.READ,
HealthDataAccess.READ,
HealthDataAccess.READ,
HealthDataAccess.READ,
// HealthDataAccess.READ,
];
// get data within the last 24 hours
final now = DateTime.now();
final yesterday = now.subtract(const Duration(days: 1));
// requesting access to the data types before reading them
// note that strictly speaking, the [permissions] are not
// needed, since we only want READ access.
bool requested =
await health.requestAuthorization(types, permissions: permissions);
print('requested: $requested');
// If we are trying to read Step Count, Workout, Sleep or other data that requires
// the ACTIVITY_RECOGNITION permission, we need to request the permission first.
// This requires a special request authorization call.
//
// The location permission is requested for Workouts using the Distance information.
await Permission.activityRecognition.request();
await Permission.location.request();
if (requested) {
try {
// fetch health data
List<HealthDataPoint> healthData =
await health.getHealthDataFromTypes(yesterday, now, types);
// save all the new data points (only the first 100)
_healthDataList.addAll((healthData.length < 100)
? healthData
: healthData.sublist(0, 100));
} catch (error) {
print("Exception in getHealthDataFromTypes: $error");
}
// filter out duplicates
_healthDataList = HealthFactory.removeDuplicates(_healthDataList);
// print the results
_healthDataList.forEach((x) => print(x));
// update the UI to display the results
setState(() {
_state =
_healthDataList.isEmpty ? AppState.NO_DATA : AppState.DATA_READY;
});
} else {
print("Authorization not granted");
setState(() => _state = AppState.DATA_NOT_FETCHED);
}
}
/// Add some random health data.
Future addData() async {
final now = DateTime.now();
final earlier = now.subtract(Duration(minutes: 20));
final types = [
HealthDataType.STEPS,
HealthDataType.HEIGHT,
HealthDataType.BLOOD_GLUCOSE,
HealthDataType.WORKOUT, // Requires Google Fit on Android
// Uncomment these lines on iOS - only available on iOS
// HealthDataType.AUDIOGRAM,
];
final rights = [
HealthDataAccess.WRITE,
HealthDataAccess.WRITE,
HealthDataAccess.WRITE,
HealthDataAccess.WRITE,
// HealthDataAccess.WRITE
];
final permissions = [
HealthDataAccess.READ_WRITE,
HealthDataAccess.READ_WRITE,
HealthDataAccess.READ_WRITE,
HealthDataAccess.READ_WRITE,
// HealthDataAccess.READ_WRITE,
];
late bool perm;
bool? hasPermissions =
await HealthFactory.hasPermissions(types, permissions: rights);
if (hasPermissions == false) {
perm = await health.requestAuthorization(types, permissions: permissions);
}
// Store a count of steps taken
_nofSteps = Random().nextInt(10);
bool success = await health.writeHealthData(
_nofSteps.toDouble(), HealthDataType.STEPS, earlier, now);
// Store a height
success &=
await health.writeHealthData(1.93, HealthDataType.HEIGHT, earlier, now);
// Store a Blood Glucose measurement
_mgdl = Random().nextInt(10) * 1.0;
success &= await health.writeHealthData(
_mgdl, HealthDataType.BLOOD_GLUCOSE, now, now);
// Store a workout eg. running
success &= await health.writeWorkoutData(
HealthWorkoutActivityType.RUNNING, earlier, now,
// The following are optional parameters
// and the UNITS are functional on iOS ONLY!
totalEnergyBurned: 230,
totalEnergyBurnedUnit: HealthDataUnit.KILOCALORIE,
totalDistance: 1234,
totalDistanceUnit: HealthDataUnit.FOOT,
);
// Store an Audiogram
// Uncomment these on iOS - only available on iOS
// const frequencies = [125.0, 500.0, 1000.0, 2000.0, 4000.0, 8000.0];
// const leftEarSensitivities = [49.0, 54.0, 89.0, 52.0, 77.0, 35.0];
// const rightEarSensitivities = [76.0, 66.0, 90.0, 22.0, 85.0, 44.5];
// success &= await health.writeAudiogram(
// frequencies,
// leftEarSensitivities,
// rightEarSensitivities,
// now,
// now,
// metadata: {
// "HKExternalUUID": "uniqueID",
// "HKDeviceName": "bluetooth headphone",
// },
// );
setState(() {
_state = success ? AppState.DATA_ADDED : AppState.DATA_NOT_ADDED;
});
}
/// Fetch steps from the health plugin and show them in the app.
Future fetchStepData() async {
int? steps;
// get steps for today (i.e., since midnight)
final now = DateTime.now();
final midnight = DateTime(now.year, now.month, now.day);
bool requested = await health.requestAuthorization([HealthDataType.STEPS]);
if (requested) {
try {
steps = await health.getTotalStepsInInterval(midnight, now);
} catch (error) {
print("Caught exception in getTotalStepsInInterval: $error");
}
print('Total number of steps: $steps');
setState(() {
_nofSteps = (steps == null) ? 0 : steps;
_state = (steps == null) ? AppState.NO_DATA : AppState.STEPS_READY;
});
} else {
print("Authorization not granted - error in authorization");
setState(() => _state = AppState.DATA_NOT_FETCHED);
}
}
Widget _contentFetchingData() {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
padding: EdgeInsets.all(20),
child: CircularProgressIndicator(
strokeWidth: 10,
)),
Text('Fetching data...')
],
);
}
Widget _contentDataReady() {
return ListView.builder(
itemCount: _healthDataList.length,
itemBuilder: (_, index) {
HealthDataPoint p = _healthDataList[index];
if (p.value is AudiogramHealthValue) {
return ListTile(
title: Text("${p.typeString}: ${p.value}"),
trailing: Text('${p.unitString}'),
subtitle: Text('${p.dateFrom} - ${p.dateTo}'),
);
}
if (p.value is WorkoutHealthValue) {
return ListTile(
title: Text(
"${p.typeString}: ${(p.value as WorkoutHealthValue).totalEnergyBurned} ${(p.value as WorkoutHealthValue).totalEnergyBurnedUnit?.typeToString()}"),
trailing: Text(
'${(p.value as WorkoutHealthValue).workoutActivityType.typeToString()}'),
subtitle: Text('${p.dateFrom} - ${p.dateTo}'),
);
}
return ListTile(
title: Text("${p.typeString}: ${p.value}"),
trailing: Text('${p.unitString}'),
subtitle: Text('${p.dateFrom} - ${p.dateTo}'),
);
});
}
Widget _contentNoData() {
return Text('No Data to show');
}
Widget _contentNotFetched() {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
);
}
Widget _authorizationNotGranted() {
return const Text('Authorization not given. '
'For Android please check your OAUTH2 client ID is correct in Google Developer Console. '
'For iOS check your permissions in Apple Health.');
}
Widget _dataAdded() {
return Text('Data points inserted successfully!');
}
Widget _stepsFetched() {
return Text('Total number of steps: $_nofSteps');
}
Widget _dataNotAdded() {
return Text('Failed to add data');
}
Widget _content() {
if (_state == AppState.DATA_READY)
switch (widget.metric!.type) {
case MetricType.steps:
case MetricType.water:
return Text("Steps: $_nofSteps");
}
else if (_state == AppState.NO_DATA)
return _contentNoData();
else if (_state == AppState.FETCHING_DATA)
return _contentFetchingData();
else if (_state == AppState.AUTH_NOT_GRANTED)
return _authorizationNotGranted();
else if (_state == AppState.DATA_ADDED)
return _dataAdded();
else if (_state == AppState.STEPS_READY)
return _stepsFetched();
else if (_state == AppState.DATA_NOT_ADDED) return _dataNotAdded();
return _contentNotFetched();
}
@override
Widget build(BuildContext context) {
if (widget.metric == null) {
return Flex(direction: Axis.vertical, children: [
MetricIcon(widget.metric, (_) {}),
const SizedBox(height: 25),
const Text("Link to other metrics, coming soon!", textScaleFactor: 1.5),
const SizedBox(height: 25),
]);
}
return Column(children: [
MetricIcon(widget.metric, (_) {}),
const SizedBox(height: 25),
_content(),
const SizedBox(height: 25),
const Text(
"Progress",
textScaleFactor: 1.25,
),
const SizedBox(height: 5),
Text(
"${widget.metric!.progress}/${widget.metric!.target} ${widget.metric!.unit}",
textScaleFactor: 2,
),
const SizedBox(height: 25),
const Text(
"Further configuration options coming soon",
textScaleFactor: 1.5,
),
]);
}
}
Future showEditMetricModal(
BuildContext context, LinkedDevice device, DeviceMetric? metric) {
return showCustomModalBottomSheet(
context,
Padding(
padding: const EdgeInsets.all(25),
child: EditMetricView(
metric: metric,
device: device,
)));
}

View File

@ -1,3 +1,5 @@
import 'package:flutter_native_splash/flutter_native_splash.dart';
import 'package:grow_me_app/colors.dart';
import 'package:grow_me_app/components/card.dart';
import 'package:grow_me_app/components/connection_sheet.dart';
import 'package:grow_me_app/components/device_carousel.dart';
@ -8,7 +10,8 @@ import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);
SystemChrome.setPreferredOrientations(
[DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
@ -16,25 +19,6 @@ void main() {
create: (context) => DeviceModel(), child: const GrowMeApp()));
}
const primarySwatch = MaterialColor(0xFF273825, {
50: Color(0xFFE5E7E5),
100: Color(0xFFBEC3BE),
200: Color(0xFF939C92),
300: Color(0xFF687466),
400: Color(0xFF475646),
500: Color(0xFF273825),
600: Color(0xFF233221),
700: Color(0xFF1D2B1B),
800: Color(0xFF172416),
900: Color(0xFF0E170D),
});
const lightBackgroundColor = Color(0xFFE5DBCF);
const PrimaryAssentColor = Color(0x00263825);
const PrimaryDarkColor = Color(0xFF808080);
const ErroColor = Color(0xFF808080);
class GrowMeApp extends StatelessWidget {
const GrowMeApp({super.key});
@ -42,20 +26,23 @@ class GrowMeApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'GrowMe',
theme: ThemeData(
fontFamily: 'Manrope',
// This is the theme of your application.
//
// Try running your application with "flutter run". You'll see the
// application has a blue toolbar. Then, without quitting the app, try
// changing the primarySwatch below to Colors.green and then invoke
// "hot reload" (press "r" in the console where you ran "flutter run",
// or simply save your changes to "hot reload" in a Flutter IDE).
// Notice that the counter didn't reset back to zero; the application
// is not restarted.
primarySwatch: primarySwatch,
),
fontFamily: 'Manrope',
// This is the theme of your application.
//
// Try running your application with "flutter run". You'll see the
// application has a blue toolbar. Then, without quitting the app, try
// changing the primarySwatch below to Colors.green and then invoke
// "hot reload" (press "r" in the console where you ran "flutter run",
// or simply save your changes to "hot reload" in a Flutter IDE).
// Notice that the counter didn't reset back to zero; the application
// is not restarted.
primarySwatch: green,
textTheme: Theme.of(context)
.textTheme
.apply(bodyColor: green, displayColor: sand)),
home: const GrowMeHomePage(title: 'GrowMe'),
);
}
@ -94,24 +81,34 @@ class _MyHomePageState extends State<GrowMeHomePage> {
Widget _connectionCard() {
return GrowMeCard(
child: Padding(
padding: const EdgeInsets.all(25),
padding: const EdgeInsets.all(10),
child: Column(
children: [
const Spacer(),
const Icon(Icons.bluetooth),
const Icon(Icons.forest, size: 120, color: green),
const SizedBox(height: 15),
const Text(
"Grow your forest",
textScaleFactor: 2,
),
const SizedBox(height: 15),
const Text("Add new GrowMe products"),
const Text("Connect new GrowMe products"),
const Spacer(),
const ConnectionSheet(),
].toList(),
)));
}
@override
void initState() {
super.initState();
initialization();
}
void initialization() async {
FlutterNativeSplash.remove();
}
@override
Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance as done
@ -131,7 +128,7 @@ class _MyHomePageState extends State<GrowMeHomePage> {
automaticallyImplyLeading: true,
elevation: 0,
),
backgroundColor: primarySwatch,
backgroundColor: green,
body: Column(
children: [
const SizedBox(height: 50),

View File

@ -42,6 +42,9 @@ dependencies:
flutter_blue: ^0.8.0
carousel_slider: ^4.1.1
shared_preferences: ^2.0.15
workmanager: ^0.5.0
flutter_native_splash: ^2.2.10+1
flutter_launcher_icons: ^0.10.0
dev_dependencies:
flutter_test:
@ -91,3 +94,8 @@ flutter:
weight: 700
- asset: assets/fonts/Manrope-ExtraBold.ttf
weight: 800
flutter_icons:
image_path: "assets/icons/icon.png"
android: true
ios: true

View File

@ -1,6 +1,4 @@
<!DOCTYPE html>
<html>
<head>
<!DOCTYPE html><html><head>
<!--
If you are serving your web app in a path other than the root, change the
href value below to reflect the base path you are serving from.
@ -27,7 +25,7 @@
<link rel="apple-touch-icon" href="icons/Icon-192.png">
<!-- Favicon -->
<link rel="icon" type="image/png" href="favicon.png"/>
<link rel="icon" type="image/png" href="favicon.png">
<title>grow_me_app</title>
<link rel="manifest" href="manifest.json">
@ -37,9 +35,18 @@
var serviceWorkerVersion = null;
</script>
<!-- This script adds the flutter initialization JS code -->
<script src="flutter.js" defer></script>
<script src="flutter.js" defer=""></script>
<link rel="stylesheet" type="text/css" href="splash/style.css">
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport">
<script src="splash/splash.js"></script>
</head>
<body>
<body> <picture id="splash">
<source srcset="splash/img/light-1x.png 1x, splash/img/light-2x.png 2x, splash/img/light-3x.png 3x, splash/img/light-4x.png 4x" media="(prefers-color-scheme: light)">
<source srcset="splash/img/dark-1x.png 1x, splash/img/dark-2x.png 2x, splash/img/dark-3x.png 3x, splash/img/dark-4x.png 4x" media="(prefers-color-scheme: dark)">
<img class="center" aria-hidden="true" src="splash/img/light-1x.png" alt="">
</picture>
<script>
window.addEventListener('load', function(ev) {
// Download main.dart.js
@ -54,5 +61,6 @@
});
});
</script>
</body>
</html>
</body></html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 KiB

5
app/web/splash/splash.js Normal file
View File

@ -0,0 +1,5 @@
function removeSplashFromWeb() {
document.getElementById("splash")?.remove();
document.getElementById("splash-branding")?.remove();
document.body.style.background = "transparent";
}

63
app/web/splash/style.css Normal file
View File

@ -0,0 +1,63 @@
body {
margin:0;
height:100%;
background: #273825;
background-size: 100% 100%;
}
.center {
margin: 0;
position: absolute;
top: 50%;
left: 50%;
-ms-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
}
.contain {
display:block;
width:100%; height:100%;
object-fit: contain;
}
.stretch {
display:block;
width:100%; height:100%;
}
.cover {
display:block;
width:100%; height:100%;
object-fit: cover;
}
.bottom {
position: absolute;
bottom: 0;
left: 50%;
-ms-transform: translate(-50%, 0);
transform: translate(-50%, 0);
}
.bottomLeft {
position: absolute;
bottom: 0;
left: 0;
}
.bottomRight {
position: absolute;
bottom: 0;
right: 0;
}
@media (prefers-color-scheme: dark) {
body {
margin:0;
height:100%;
background: #273825;
background-size: 100% 100%;
}
}

View File

@ -0,0 +1,38 @@
{
"folders": [
{
"path": "."
}
],
"settings": {
"files.associations": {
"*.base": "makefile",
"*.ts.tpl": "typescript",
"*.tpl": "php",
"*.phtml": "php",
"wifi.h": "c",
"wifista.h": "c",
"*.ipp": "cpp"
},
"workbench.colorCustomizations": {
"activityBar.activeBackground": "#1f6fd0",
"activityBar.background": "#1f6fd0",
"activityBar.foreground": "#e7e7e7",
"activityBar.inactiveForeground": "#e7e7e799",
"activityBarBadge.background": "#ee90bb",
"activityBarBadge.foreground": "#15202b",
"commandCenter.border": "#e7e7e799",
"sash.hoverBorder": "#1f6fd0",
"statusBar.background": "#1857a4",
"statusBar.foreground": "#e7e7e7",
"statusBarItem.hoverBackground": "#1f6fd0",
"statusBarItem.remoteBackground": "#1857a4",
"statusBarItem.remoteForeground": "#e7e7e7",
"titleBar.activeBackground": "#1857a4",
"titleBar.activeForeground": "#e7e7e7",
"titleBar.inactiveBackground": "#1857a499",
"titleBar.inactiveForeground": "#e7e7e799"
},
"peacock.color": "#1857a4"
}
}

View File

@ -1,6 +1,6 @@
import os
Import("env")
os.system("python3 .pio/libdeps/esp32dev/Nanopb/generator/nanopb_generator.py proto/message.proto --strip-path")
os.system("python32 .pio/libdeps/esp32dev/Nanopb/generator/nanopb_generator.py proto/message.proto --strip-path")
os.system("mv ./proto/message.pb.c ./src/message.pb.c")
os.system("mv ./proto/message.pb.h ./include/message.pb.h")

View File

@ -10,7 +10,8 @@
padding: 0;
}
body {
background-color: #ecf0f3;
background-color: #273825;
color: #e5d9ce;
display: flex;
flex-direction: column;
@ -32,6 +33,5 @@
</head>
<body>
<img src="/logo.png" />
<h2>Coming Soon!</h2>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 380 KiB