feat: add final project code
@ -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 {
|
||||
|
||||
BIN
app/android/app/src/main/res/drawable-hdpi/splash.png
Normal file
|
After Width: | Height: | Size: 61 KiB |
BIN
app/android/app/src/main/res/drawable-mdpi/splash.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
app/android/app/src/main/res/drawable-v21/background.png
Normal file
|
After Width: | Height: | Size: 70 B |
@ -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>
|
||||
|
||||
BIN
app/android/app/src/main/res/drawable-xhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 94 KiB |
BIN
app/android/app/src/main/res/drawable-xxhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 182 KiB |
BIN
app/android/app/src/main/res/drawable-xxxhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 303 KiB |
BIN
app/android/app/src/main/res/drawable/background.png
Normal file
|
After Width: | Height: | Size: 70 B |
@ -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>
|
||||
|
||||
|
Before Width: | Height: | Size: 544 B After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 442 B After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 721 B After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 10 KiB |
18
app/android/app/src/main/res/values-v31/styles.xml
Normal 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>
|
||||
@ -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
|
||||
|
||||
@ -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
|
After Width: | Height: | Size: 220 KiB |
BIN
app/assets/images/flower-full-bloom.png
Normal file
|
After Width: | Height: | Size: 247 KiB |
BIN
app/assets/images/grow-me-medium.png
Normal file
|
After Width: | Height: | Size: 188 KiB |
BIN
app/assets/images/grow-me-nada-niente.png
Normal file
|
After Width: | Height: | Size: 137 KiB |
BIN
app/assets/images/grow-me-nada.png
Normal file
|
After Width: | Height: | Size: 155 KiB |
BIN
app/assets/images/logo-splash.png
Normal file
|
After Width: | Height: | Size: 52 KiB |
132
app/flutter_native_splash.yaml
Normal 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'
|
||||
@ -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";
|
||||
|
||||
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 226 KiB |
|
Before Width: | Height: | Size: 564 B After Width: | Height: | Size: 555 B |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 827 B |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 9.4 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 7.2 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 8.3 KiB |
21
app/ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json
vendored
Normal 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
|
||||
}
|
||||
}
|
||||
BIN
app/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png
vendored
Normal file
|
After Width: | Height: | Size: 70 B |
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 68 B After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 68 B After Width: | Height: | Size: 94 KiB |
|
Before Width: | Height: | Size: 68 B After Width: | Height: | Size: 182 KiB |
@ -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>
|
||||
|
||||
@ -61,5 +61,7 @@
|
||||
<string>Need Location permission</string>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string>Need Location permission</string>
|
||||
<key>UIStatusBarHidden</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
29
app/lib/app.code-workspace
Normal 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
@ -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;
|
||||
@ -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,
|
||||
)
|
||||
]),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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()),
|
||||
)),
|
||||
),
|
||||
);
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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);
|
||||
})
|
||||
],
|
||||
));
|
||||
},
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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) {
|
||||
|
||||
160
app/lib/components/device_view.dart
Normal 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(),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -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,
|
||||
)));
|
||||
}
|
||||
|
||||
382
app/lib/components/edit_metric.dart
Normal 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,
|
||||
)));
|
||||
}
|
||||
@ -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),
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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>
|
||||
BIN
app/web/splash/img/dark-1x.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
app/web/splash/img/dark-2x.png
Normal file
|
After Width: | Height: | Size: 94 KiB |
BIN
app/web/splash/img/dark-3x.png
Normal file
|
After Width: | Height: | Size: 182 KiB |
BIN
app/web/splash/img/dark-4x.png
Normal file
|
After Width: | Height: | Size: 303 KiB |
BIN
app/web/splash/img/light-1x.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
app/web/splash/img/light-2x.png
Normal file
|
After Width: | Height: | Size: 94 KiB |
BIN
app/web/splash/img/light-3x.png
Normal file
|
After Width: | Height: | Size: 182 KiB |
BIN
app/web/splash/img/light-4x.png
Normal file
|
After Width: | Height: | Size: 303 KiB |
5
app/web/splash/splash.js
Normal 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
@ -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%;
|
||||
}
|
||||
}
|
||||
38
growme/growme.embedded.code-workspace
Normal 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"
|
||||
}
|
||||
}
|
||||
@ -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")
|
||||
|
||||
@ -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>
|
||||
|
||||
BIN
web/logo.png
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 380 KiB |