diff --git a/.github/scripts/gradlew_recursive.sh b/.github/scripts/gradlew_recursive.sh
new file mode 100644
index 0000000..d575c49
--- /dev/null
+++ b/.github/scripts/gradlew_recursive.sh
@@ -0,0 +1,37 @@
+#!/bin/bash
+
+# Copyright (C) 2020 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+set -xe
+
+# Default Gradle settings are not optimal for Android builds, override them
+# here to make the most out of the GitHub Actions build servers
+GRADLE_OPTS="$GRADLE_OPTS -Xms4g -Xmx4g"
+GRADLE_OPTS="$GRADLE_OPTS -XX:+HeapDumpOnOutOfMemoryError"
+GRADLE_OPTS="$GRADLE_OPTS -Dorg.gradle.daemon=false"
+GRADLE_OPTS="$GRADLE_OPTS -Dorg.gradle.workers.max=2"
+GRADLE_OPTS="$GRADLE_OPTS -Dkotlin.incremental=false"
+GRADLE_OPTS="$GRADLE_OPTS -Dkotlin.compiler.execution.strategy=in-process"
+GRADLE_OPTS="$GRADLE_OPTS -Dfile.encoding=UTF-8"
+export GRADLE_OPTS
+
+# Crawl all gradlew files which indicate an Android project
+# You may edit this if your repo has a different project structure
+for GRADLEW in `find . -name "gradlew"` ; do
+ SAMPLE=$(dirname "${GRADLEW}")
+ # Tell Gradle that this is a CI environment and disable parallel compilation
+ bash "$GRADLEW" -p "$SAMPLE" -Pci --no-parallel --stacktrace $@
+done
+
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 863d685..59e4be4 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -1,3 +1,17 @@
+# Copyright (C) 2020 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
name: Android CI
on:
@@ -7,27 +21,33 @@ on:
branches: [ master ]
jobs:
- build:
+ build:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
- - name: set up JDK 11
- uses: actions/setup-java@v2
- with:
- java-version: '11'
- distribution: 'adopt'
- cache: gradle
+ - uses: actions/checkout@v2
+ - name: set up JDK 11
+ uses: actions/setup-java@v2
+ with:
+ java-version: '11'
+ distribution: 'adopt'
+ cache: gradle
+ - name: Prepare project
+ run: chmod +x .github/scripts/gradlew_recursive.sh
+ - name: Build project
+ run: .github/scripts/gradlew_recursive.sh assembleDebug
+ - name: Zip artifacts
+ run: zip -r assemble.zip . -i '**/build/*.apk' '**/build/*.aab' '**/build/*.aar' '**/build/*.so'
+ - name: Upload artifacts
+ uses: actions/upload-artifact@v1
+ with:
+ name: assemble
+ path: assemble.zip
+ - name: ktlint
+ uses: ScaCap/action-ktlint@master
+ with:
+ github_token: ${{ secrets.github_token }}
+ android: true
+ reporter: github-pr-review # Change reporter
- - name: Grant execute permission for gradlew
- run: chmod +x gradlew
- - name: Build with Gradle
- run: ./gradlew build
-
- - name: ktlint
- uses: ScaCap/action-ktlint@master
- with:
- github_token: ${{ secrets.github_token }}
- android: true
- reporter: github-pr-review # Change reporter
diff --git a/.gitignore b/.gitignore
index 10cfdbf..82d4574 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,15 @@
.externalNativeBuild
.cxx
local.properties
+
+# keystore
+keystore.properties
+private_key.pepk
+upload_key.jks
+
+# Fastlane
+fastlane_secret_keys.json
+
+# App
+/app/debug
+/app/release
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 5014ce6..d182631 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -13,28 +13,36 @@
+
+
+
-
+
+
+
-
+
+
+
+
diff --git a/Gemfile b/Gemfile
new file mode 100644
index 0000000..adc90d9
--- /dev/null
+++ b/Gemfile
@@ -0,0 +1,3 @@
+source "https://rubygems.org"
+
+gem "fastlane"
\ No newline at end of file
diff --git a/Gemfile.lock b/Gemfile.lock
new file mode 100644
index 0000000..10eb92d
--- /dev/null
+++ b/Gemfile.lock
@@ -0,0 +1,214 @@
+GEM
+ remote: https://rubygems.org/
+ specs:
+ CFPropertyList (3.0.5)
+ rexml
+ addressable (2.8.0)
+ public_suffix (>= 2.0.2, < 5.0)
+ artifactory (3.0.15)
+ atomos (0.1.3)
+ aws-eventstream (1.2.0)
+ aws-partitions (1.543.0)
+ aws-sdk-core (3.125.0)
+ aws-eventstream (~> 1, >= 1.0.2)
+ aws-partitions (~> 1, >= 1.525.0)
+ aws-sigv4 (~> 1.1)
+ jmespath (~> 1.0)
+ aws-sdk-kms (1.53.0)
+ aws-sdk-core (~> 3, >= 3.125.0)
+ aws-sigv4 (~> 1.1)
+ aws-sdk-s3 (1.110.0)
+ aws-sdk-core (~> 3, >= 3.125.0)
+ aws-sdk-kms (~> 1)
+ aws-sigv4 (~> 1.4)
+ aws-sigv4 (1.4.0)
+ aws-eventstream (~> 1, >= 1.0.2)
+ babosa (1.0.4)
+ claide (1.0.3)
+ colored (1.2)
+ colored2 (3.1.2)
+ commander (4.6.0)
+ highline (~> 2.0.0)
+ declarative (0.0.20)
+ digest-crc (0.6.4)
+ rake (>= 12.0.0, < 14.0.0)
+ domain_name (0.5.20190701)
+ unf (>= 0.0.5, < 1.0.0)
+ dotenv (2.7.6)
+ emoji_regex (3.2.3)
+ excon (0.89.0)
+ faraday (1.8.0)
+ faraday-em_http (~> 1.0)
+ faraday-em_synchrony (~> 1.0)
+ faraday-excon (~> 1.1)
+ faraday-httpclient (~> 1.0.1)
+ faraday-net_http (~> 1.0)
+ faraday-net_http_persistent (~> 1.1)
+ faraday-patron (~> 1.0)
+ faraday-rack (~> 1.0)
+ multipart-post (>= 1.2, < 3)
+ ruby2_keywords (>= 0.0.4)
+ faraday-cookie_jar (0.0.7)
+ faraday (>= 0.8.0)
+ http-cookie (~> 1.0.0)
+ faraday-em_http (1.0.0)
+ faraday-em_synchrony (1.0.0)
+ faraday-excon (1.1.0)
+ faraday-httpclient (1.0.1)
+ faraday-net_http (1.0.1)
+ faraday-net_http_persistent (1.2.0)
+ faraday-patron (1.0.0)
+ faraday-rack (1.0.0)
+ faraday_middleware (1.2.0)
+ faraday (~> 1.0)
+ fastimage (2.2.6)
+ fastlane (2.199.0)
+ CFPropertyList (>= 2.3, < 4.0.0)
+ addressable (>= 2.8, < 3.0.0)
+ artifactory (~> 3.0)
+ aws-sdk-s3 (~> 1.0)
+ babosa (>= 1.0.3, < 2.0.0)
+ bundler (>= 1.12.0, < 3.0.0)
+ colored
+ commander (~> 4.6)
+ dotenv (>= 2.1.1, < 3.0.0)
+ emoji_regex (>= 0.1, < 4.0)
+ excon (>= 0.71.0, < 1.0.0)
+ faraday (~> 1.0)
+ faraday-cookie_jar (~> 0.0.6)
+ faraday_middleware (~> 1.0)
+ fastimage (>= 2.1.0, < 3.0.0)
+ gh_inspector (>= 1.1.2, < 2.0.0)
+ google-apis-androidpublisher_v3 (~> 0.3)
+ google-apis-playcustomapp_v1 (~> 0.1)
+ google-cloud-storage (~> 1.31)
+ highline (~> 2.0)
+ json (< 3.0.0)
+ jwt (>= 2.1.0, < 3)
+ mini_magick (>= 4.9.4, < 5.0.0)
+ multipart-post (~> 2.0.0)
+ naturally (~> 2.2)
+ optparse (~> 0.1.1)
+ plist (>= 3.1.0, < 4.0.0)
+ rubyzip (>= 2.0.0, < 3.0.0)
+ security (= 0.1.3)
+ simctl (~> 1.6.3)
+ terminal-notifier (>= 2.0.0, < 3.0.0)
+ terminal-table (>= 1.4.5, < 2.0.0)
+ tty-screen (>= 0.6.3, < 1.0.0)
+ tty-spinner (>= 0.8.0, < 1.0.0)
+ word_wrap (~> 1.0.0)
+ xcodeproj (>= 1.13.0, < 2.0.0)
+ xcpretty (~> 0.3.0)
+ xcpretty-travis-formatter (>= 0.0.3)
+ gh_inspector (1.1.3)
+ google-apis-androidpublisher_v3 (0.14.0)
+ google-apis-core (>= 0.4, < 2.a)
+ google-apis-core (0.4.1)
+ addressable (~> 2.5, >= 2.5.1)
+ googleauth (>= 0.16.2, < 2.a)
+ httpclient (>= 2.8.1, < 3.a)
+ mini_mime (~> 1.0)
+ representable (~> 3.0)
+ retriable (>= 2.0, < 4.a)
+ rexml
+ webrick
+ google-apis-iamcredentials_v1 (0.9.0)
+ google-apis-core (>= 0.4, < 2.a)
+ google-apis-playcustomapp_v1 (0.6.0)
+ google-apis-core (>= 0.4, < 2.a)
+ google-apis-storage_v1 (0.10.0)
+ google-apis-core (>= 0.4, < 2.a)
+ google-cloud-core (1.6.0)
+ google-cloud-env (~> 1.0)
+ google-cloud-errors (~> 1.0)
+ google-cloud-env (1.5.0)
+ faraday (>= 0.17.3, < 2.0)
+ google-cloud-errors (1.2.0)
+ google-cloud-storage (1.35.0)
+ addressable (~> 2.8)
+ digest-crc (~> 0.4)
+ google-apis-iamcredentials_v1 (~> 0.1)
+ google-apis-storage_v1 (~> 0.1)
+ google-cloud-core (~> 1.6)
+ googleauth (>= 0.16.2, < 2.a)
+ mini_mime (~> 1.0)
+ googleauth (1.1.0)
+ faraday (>= 0.17.3, < 2.0)
+ jwt (>= 1.4, < 3.0)
+ memoist (~> 0.16)
+ multi_json (~> 1.11)
+ os (>= 0.9, < 2.0)
+ signet (>= 0.16, < 2.a)
+ highline (2.0.3)
+ http-cookie (1.0.4)
+ domain_name (~> 0.5)
+ httpclient (2.8.3)
+ jmespath (1.4.0)
+ json (2.6.1)
+ jwt (2.3.0)
+ memoist (0.16.2)
+ mini_magick (4.11.0)
+ mini_mime (1.1.2)
+ multi_json (1.15.0)
+ multipart-post (2.0.0)
+ nanaimo (0.3.0)
+ naturally (2.2.1)
+ optparse (0.1.1)
+ os (1.1.4)
+ plist (3.6.0)
+ public_suffix (4.0.6)
+ rake (13.0.6)
+ representable (3.1.1)
+ declarative (< 0.1.0)
+ trailblazer-option (>= 0.1.1, < 0.2.0)
+ uber (< 0.2.0)
+ retriable (3.1.2)
+ rexml (3.2.5)
+ rouge (2.0.7)
+ ruby2_keywords (0.0.5)
+ rubyzip (2.3.2)
+ security (0.1.3)
+ signet (0.16.0)
+ addressable (~> 2.8)
+ faraday (>= 0.17.3, < 2.0)
+ jwt (>= 1.5, < 3.0)
+ multi_json (~> 1.10)
+ simctl (1.6.8)
+ CFPropertyList
+ naturally
+ terminal-notifier (2.0.0)
+ terminal-table (1.8.0)
+ unicode-display_width (~> 1.1, >= 1.1.1)
+ trailblazer-option (0.1.2)
+ tty-cursor (0.7.1)
+ tty-screen (0.8.1)
+ tty-spinner (0.9.3)
+ tty-cursor (~> 0.7)
+ uber (0.1.0)
+ unf (0.1.4)
+ unf_ext
+ unf_ext (0.0.8-x64-mingw32)
+ unicode-display_width (1.8.0)
+ webrick (1.7.0)
+ word_wrap (1.0.0)
+ xcodeproj (1.21.0)
+ CFPropertyList (>= 2.3.3, < 4.0)
+ atomos (~> 0.1.3)
+ claide (>= 1.0.2, < 2.0)
+ colored2 (~> 3.1)
+ nanaimo (~> 0.3.0)
+ rexml (~> 3.2.4)
+ xcpretty (0.3.0)
+ rouge (~> 2.0.7)
+ xcpretty-travis-formatter (1.0.1)
+ xcpretty (~> 0.2, >= 0.0.7)
+
+PLATFORMS
+ x64-mingw32
+
+DEPENDENCIES
+ fastlane
+
+BUNDLED WITH
+ 2.2.32
diff --git a/app/build.gradle b/app/build.gradle
index 45554c7..0ec3f07 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -9,36 +9,87 @@ plugins {
}
android {
+
+ signingConfigs {
+
+ release {
+
+ def keystorePropertiesFile = rootProject.file("./keystore.properties")
+ def keystoreProperties = new Properties()
+ try {
+ keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
+ } catch (FileNotFoundException ignored) {
+ keystoreProperties = null
+ }
+
+ if (keystoreProperties != null) {
+ storePassword keystoreProperties["storePassword"]
+ keyPassword keystoreProperties["keyPassword"]
+ keyAlias keystoreProperties["keyAlias"]
+ storeFile file(keystoreProperties["storeFile"])
+ }
+
+ }
+
+ }
+
compileSdk 31
defaultConfig {
+ // App ID
applicationId "com.dzeio.openhealth"
+
+ // Android 5 Lollipop
minSdk 21
+
+ // Android 12
targetSdk 31
+
versionCode 1
- versionName "1.0"
+
+ // Semantic Versioning
+ versionName "1.0.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
+
release {
- minifyEnabled false
+ minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ signingConfig signingConfigs.release
}
+
+ debug {
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ applicationIdSuffix ".dev"
+ versionNameSuffix '-dev'
+ debuggable true
+
+ // make it debuggable
+ renderscriptDebuggable true
+
+ // Optimization Level
+ renderscriptOptimLevel 0
+ }
+
}
+
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
+
kotlinOptions {
jvmTarget = '1.8'
}
+
buildFeatures {
viewBinding true
dataBinding true
-
}
+
}
dependencies {
@@ -81,6 +132,7 @@ dependencies {
// Samsung Health
implementation files('libs/samsung-health-data-1.5.0.aar')
+ implementation "com.google.code.gson:gson:2.8.9"
// ROOM
def room_version = "2.4.0"
diff --git a/app/src/debug/ic_launcher-playstore.png b/app/src/debug/ic_launcher-playstore.png
new file mode 100644
index 0000000..70f398c
Binary files /dev/null and b/app/src/debug/ic_launcher-playstore.png differ
diff --git a/app/src/debug/res/drawable-v24/ic_launcher_foreground.xml b/app/src/debug/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000..5ec3d6a
--- /dev/null
+++ b/app/src/debug/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/debug/res/drawable/ic_logo_app.xml b/app/src/debug/res/drawable/ic_logo_app.xml
new file mode 100644
index 0000000..357dd1c
--- /dev/null
+++ b/app/src/debug/res/drawable/ic_logo_app.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..7353dbd
--- /dev/null
+++ b/app/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/debug/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/debug/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..7353dbd
--- /dev/null
+++ b/app/src/debug/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/debug/res/mipmap-hdpi/ic_launcher.png b/app/src/debug/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..1321a47
Binary files /dev/null and b/app/src/debug/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/app/src/debug/res/mipmap-hdpi/ic_launcher_round.png b/app/src/debug/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000..c334358
Binary files /dev/null and b/app/src/debug/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/app/src/debug/res/mipmap-mdpi/ic_launcher.png b/app/src/debug/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..e962867
Binary files /dev/null and b/app/src/debug/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/app/src/debug/res/mipmap-mdpi/ic_launcher_round.png b/app/src/debug/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000..3d45a57
Binary files /dev/null and b/app/src/debug/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/app/src/debug/res/mipmap-xhdpi/ic_launcher.png b/app/src/debug/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..9a1fb34
Binary files /dev/null and b/app/src/debug/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/app/src/debug/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/debug/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..24fde3b
Binary files /dev/null and b/app/src/debug/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/app/src/debug/res/mipmap-xxhdpi/ic_launcher.png b/app/src/debug/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..b3bffc0
Binary files /dev/null and b/app/src/debug/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/app/src/debug/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/debug/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..32e2771
Binary files /dev/null and b/app/src/debug/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/app/src/debug/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/debug/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..693ce32
Binary files /dev/null and b/app/src/debug/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/app/src/debug/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/debug/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..8c77946
Binary files /dev/null and b/app/src/debug/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/app/src/debug/res/values/ic_launcher_background.xml b/app/src/debug/res/values/ic_launcher_background.xml
new file mode 100644
index 0000000..c5d5899
--- /dev/null
+++ b/app/src/debug/res/values/ic_launcher_background.xml
@@ -0,0 +1,4 @@
+
+
+ #FFFFFF
+
\ No newline at end of file
diff --git a/app/src/debug/res/values/strings.xml b/app/src/debug/res/values/strings.xml
new file mode 100644
index 0000000..5863d12
--- /dev/null
+++ b/app/src/debug/res/values/strings.xml
@@ -0,0 +1,4 @@
+
+
+ OpenHealth - Debug
+
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 634afa5..600b804 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -6,8 +6,8 @@
-
-
+
+
diff --git a/app/src/main/java/com/dzeio/openhealth/Application.kt b/app/src/main/java/com/dzeio/openhealth/Application.kt
index d845fc2..bb2ee43 100644
--- a/app/src/main/java/com/dzeio/openhealth/Application.kt
+++ b/app/src/main/java/com/dzeio/openhealth/Application.kt
@@ -1,6 +1,7 @@
package com.dzeio.openhealth
import android.app.Application
+import com.google.android.material.color.DynamicColors
import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp
@@ -8,4 +9,11 @@ class Application : Application() {
companion object {
const val TAG = "OpenHealth"
}
+
+ override fun onCreate() {
+ super.onCreate()
+
+ // Android Dynamics Colors
+ DynamicColors.applyToActivitiesIfAvailable(this)
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/dzeio/openhealth/MainActivity.kt b/app/src/main/java/com/dzeio/openhealth/MainActivity.kt
index 977af20..e8c9d8c 100644
--- a/app/src/main/java/com/dzeio/openhealth/MainActivity.kt
+++ b/app/src/main/java/com/dzeio/openhealth/MainActivity.kt
@@ -10,22 +10,17 @@ import android.util.Log
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuItem
-import androidx.core.content.ContextCompat
-import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavController
import androidx.navigation.fragment.NavHostFragment
-import androidx.navigation.fragment.findNavController
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.navigateUp
import androidx.navigation.ui.setupActionBarWithNavController
-import androidx.navigation.ui.setupWithNavController
import androidx.work.WorkManager
import com.dzeio.openhealth.core.BaseActivity
import com.dzeio.openhealth.databinding.ActivityMainBinding
import com.dzeio.openhealth.interfaces.NotificationChannels
import com.dzeio.openhealth.services.WaterReminderService
import com.dzeio.openhealth.ui.home.HomeFragmentDirections
-import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
@@ -45,6 +40,7 @@ class MainActivity : BaseActivity() {
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
+
navController = navHostFragment.navController
appBarConfiguration = AppBarConfiguration(
@@ -69,9 +65,8 @@ class MainActivity : BaseActivity() {
return true
}
- override fun onSupportNavigateUp(): Boolean {
- return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
- }
+ override fun onSupportNavigateUp(): Boolean =
+ navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
override fun onRequestPermissionsResult(
requestCode: Int,
@@ -94,21 +89,23 @@ class MainActivity : BaseActivity() {
}
private fun createNotificationChannel() {
- // Create the NotificationChannel, but only on API 26+ because
- // the NotificationChannel class is new and not in the support library
-
val notificationManager: NotificationManager =
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
for (channel in NotificationChannels.values()) {
- notificationManager.createNotificationChannel(
- NotificationChannel(
- channel.id,
- channel.channelName,
- channel.importance
+ Log.d("MainActivity", channel.channelName)
+ try {
+ notificationManager.createNotificationChannel(
+ NotificationChannel(
+ channel.id,
+ channel.channelName,
+ channel.importance
+ )
)
- )
+ } catch (e: Exception) {
+ Log.e("MainActivity", "Error Creating Notification Channel", e)
+ }
}
}
diff --git a/app/src/main/java/com/dzeio/openhealth/adapters/ExtensionAdapter.kt b/app/src/main/java/com/dzeio/openhealth/adapters/ExtensionAdapter.kt
new file mode 100644
index 0000000..9b3d94a
--- /dev/null
+++ b/app/src/main/java/com/dzeio/openhealth/adapters/ExtensionAdapter.kt
@@ -0,0 +1,28 @@
+package com.dzeio.openhealth.adapters
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import com.dzeio.openhealth.core.BaseAdapter
+import com.dzeio.openhealth.core.BaseViewHolder
+import com.dzeio.openhealth.databinding.LayoutExtensionItemBinding
+import com.dzeio.openhealth.extensions.Extension
+
+class ExtensionAdapter() : BaseAdapter() {
+
+ override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> LayoutExtensionItemBinding
+ get() = LayoutExtensionItemBinding::inflate
+
+ var onItemClick: ((weight: Extension) -> Unit)? = null
+
+ override fun onBindData(
+ holder: BaseViewHolder,
+ item: Extension,
+ position: Int
+ ) {
+ holder.binding.name.text = item.name
+ holder.binding.status.text = item.getStatus()
+ holder.binding.card.setOnClickListener {
+ onItemClick?.invoke(item)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/dzeio/openhealth/adapters/WaterAdapter.kt b/app/src/main/java/com/dzeio/openhealth/adapters/WaterAdapter.kt
index bdeaf07..2669c3a 100644
--- a/app/src/main/java/com/dzeio/openhealth/adapters/WaterAdapter.kt
+++ b/app/src/main/java/com/dzeio/openhealth/adapters/WaterAdapter.kt
@@ -1,14 +1,11 @@
package com.dzeio.openhealth.adapters
-import android.util.Log
import android.view.LayoutInflater
import android.view.ViewGroup
import com.dzeio.openhealth.core.BaseAdapter
import com.dzeio.openhealth.core.BaseViewHolder
import com.dzeio.openhealth.data.water.Water
-import com.dzeio.openhealth.data.weight.Weight
import com.dzeio.openhealth.databinding.LayoutItemListBinding
-import com.dzeio.openhealth.databinding.LayoutItemWeightBinding
class WaterAdapter() : BaseAdapter() {
@@ -23,7 +20,7 @@ class WaterAdapter() : BaseAdapter() {
position: Int
) {
holder.binding.value.text = "${item.value}ml"
- holder.binding.datetime.text = item.formatTimestamp()
+ holder.binding.datetime.text = "${item.formatTimestamp()} ${item.timestamp}"
holder.binding.edit.setOnClickListener {
onItemClick?.invoke(item)
}
diff --git a/app/src/main/java/com/dzeio/openhealth/adapters/WeightAdapter.kt b/app/src/main/java/com/dzeio/openhealth/adapters/WeightAdapter.kt
index f88f3ab..783ccba 100644
--- a/app/src/main/java/com/dzeio/openhealth/adapters/WeightAdapter.kt
+++ b/app/src/main/java/com/dzeio/openhealth/adapters/WeightAdapter.kt
@@ -1,26 +1,25 @@
package com.dzeio.openhealth.adapters
-import android.util.Log
import android.view.LayoutInflater
import android.view.ViewGroup
import com.dzeio.openhealth.core.BaseAdapter
import com.dzeio.openhealth.core.BaseViewHolder
import com.dzeio.openhealth.data.weight.Weight
-import com.dzeio.openhealth.databinding.LayoutItemWeightBinding
+import com.dzeio.openhealth.databinding.LayoutItemListBinding
-class WeightAdapter() : BaseAdapter() {
+class WeightAdapter() : BaseAdapter() {
- override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> LayoutItemWeightBinding
- get() = LayoutItemWeightBinding::inflate
+ override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> LayoutItemListBinding
+ get() = LayoutItemListBinding::inflate
var onItemClick: ((weight: Weight) -> Unit)? = null
override fun onBindData(
- holder: BaseViewHolder,
+ holder: BaseViewHolder,
item: Weight,
position: Int
) {
- holder.binding.weight.text = "${item.weight}kg"
+ holder.binding.value.text = "${item.weight}kg"
holder.binding.datetime.text = item.formatTimestamp()
holder.binding.edit.setOnClickListener {
onItemClick?.invoke(item)
diff --git a/app/src/main/java/com/dzeio/openhealth/data/water/WaterDao.kt b/app/src/main/java/com/dzeio/openhealth/data/water/WaterDao.kt
index 09a361a..87995a9 100644
--- a/app/src/main/java/com/dzeio/openhealth/data/water/WaterDao.kt
+++ b/app/src/main/java/com/dzeio/openhealth/data/water/WaterDao.kt
@@ -1,14 +1,14 @@
package com.dzeio.openhealth.data.water
-import androidx.room.*
+import androidx.room.Dao
+import androidx.room.Query
import com.dzeio.openhealth.core.BaseDao
-import dagger.Provides
import kotlinx.coroutines.flow.Flow
@Dao
interface WaterDao : BaseDao {
- @Query("SELECT * FROM Water ORDER BY timestamp")
+ @Query("SELECT * FROM Water ORDER BY timestamp DESC")
fun getAll(): Flow>
@Query("SELECT * FROM Water where id = :weightId")
diff --git a/app/src/main/java/com/dzeio/openhealth/data/weight/Weight.kt b/app/src/main/java/com/dzeio/openhealth/data/weight/Weight.kt
index 8199d34..032eafb 100644
--- a/app/src/main/java/com/dzeio/openhealth/data/weight/Weight.kt
+++ b/app/src/main/java/com/dzeio/openhealth/data/weight/Weight.kt
@@ -7,8 +7,9 @@ import java.sql.Date
import java.text.DateFormat.getDateInstance
@Entity()
-data class Weight (
- @PrimaryKey(autoGenerate = true) var id: Long = 0,
+data class Weight(
+ @PrimaryKey(autoGenerate = true)
+ var id: Long = 0,
var weight: Float = 0f,
@ColumnInfo(index = true)
var timestamp: Long = System.currentTimeMillis(),
diff --git a/app/src/main/java/com/dzeio/openhealth/extensions/DataType.kt b/app/src/main/java/com/dzeio/openhealth/extensions/DataType.kt
deleted file mode 100644
index e7777d6..0000000
--- a/app/src/main/java/com/dzeio/openhealth/extensions/DataType.kt
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.dzeio.openhealth.extensions
-
-enum class DataType {
- WEIGHT
-}
-
-/**
- * STEP_COUNT_CUMULATIVE
- * ACTIVITY_SEGMENT
- * SLEEP_SEGMENT
- * CALORIES_EXPENDED
- * BASAL_METABOLIC_RATE
- * POWER_SAMPLE
- * HEART_RATE_BPM
- * LOCATION_SAMPLE
- */
\ No newline at end of file
diff --git a/app/src/main/java/com/dzeio/openhealth/extensions/Extension.kt b/app/src/main/java/com/dzeio/openhealth/extensions/Extension.kt
index a42b04a..89dff24 100644
--- a/app/src/main/java/com/dzeio/openhealth/extensions/Extension.kt
+++ b/app/src/main/java/com/dzeio/openhealth/extensions/Extension.kt
@@ -2,18 +2,103 @@ package com.dzeio.openhealth.extensions
import android.app.Activity
import android.content.Intent
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
import com.dzeio.openhealth.data.weight.Weight
+/**
+ * Extension Schema
+ *
+ * Version: 1.0.0
+ */
abstract class Extension {
- enum class Data {
- WEIGHT,
- STEPS
+ data class ImportState(
+ val state: States = States.WIP,
+ val list: List = ArrayList()
+ )
+
+ enum class States {
+ WIP,
+ DONE,
+ CANCELLED
}
- abstract val sourceID: String
+ enum class Data {
+ /**
+ * Special case to handle basic errors from other activities
+ */
+ NOTHING,
+ WEIGHT,
+ STEPS
- open fun init(activity: Activity): Array = arrayOf()
+ /**
+ * STEP_COUNT_CUMULATIVE
+ * ACTIVITY_SEGMENT
+ * SLEEP_SEGMENT
+ * CALORIES_EXPENDED
+ * BASAL_METABOLIC_RATE
+ * POWER_SAMPLE
+ * HEART_RATE_BPM
+ * LOCATION_SAMPLE
+ */
+ }
+
+ /**
+ * the Source ID
+ *
+ * DO NOT CHANGE IT AFTER THE EXTENSION IS IN PRODUCTION
+ */
+ abstract val id: String
+
+ /**
+ * The Extension Display Name
+ */
+ abstract val name: String
+
+ /**
+ * Initialize hte Extension
+ *
+ * It is run Before any functions is launched and after events handlers are set
+ */
+ abstract fun init(activity: Activity): Array
+
+ /**
+ * A status shown on the extension list page
+ */
+ open fun getStatus(): String {
+ return "No Status set..."
+ }
+
+ /**
+ * Function that will check
+ */
+ abstract fun isAvailable(): Boolean
+
+ /**
+ * idk
+ */
+ abstract fun isConnected(): Boolean
+
+ open fun connect(): LiveData {
+ return MutableLiveData(States.DONE)
+ }
+
+ open fun importWeight(): LiveData> {
+ return MutableLiveData(ImportState(States.DONE))
+ }
+
+ /**
+ * function run when outgoing sync is enabled and new value is added
+ * or manual export is launched
+ */
+ open fun exportWeight(weight: Weight): LiveData {
+ return MutableLiveData(States.DONE)
+ }
+
+ /**
+ * Activity Events
+ */
/**
* Same as Activity/Fragment onRequestPermissionResult
@@ -26,8 +111,4 @@ abstract class Extension {
* Same as Activity/Fragment onActivityResult
*/
open fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {}
-
- open fun import(data: Data, cb: (item: T, end: Boolean) -> Unit) {}
-
- open fun importWeight(callback: (weight: Weight, end: Boolean) -> Unit) {}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/dzeio/openhealth/extensions/ExtensionFactory.kt b/app/src/main/java/com/dzeio/openhealth/extensions/ExtensionFactory.kt
new file mode 100644
index 0000000..19c6f6d
--- /dev/null
+++ b/app/src/main/java/com/dzeio/openhealth/extensions/ExtensionFactory.kt
@@ -0,0 +1,16 @@
+package com.dzeio.openhealth.extensions
+
+class ExtensionFactory {
+ companion object {
+ fun getExtension(extension: String): Extension? {
+ return when (extension) {
+ "GoogleFit" -> {
+ GoogleFit()
+ }
+ else -> {
+ null
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/dzeio/openhealth/extensions/GoogleFit.kt b/app/src/main/java/com/dzeio/openhealth/extensions/GoogleFit.kt
index 1cbf5e4..8bce6ec 100644
--- a/app/src/main/java/com/dzeio/openhealth/extensions/GoogleFit.kt
+++ b/app/src/main/java/com/dzeio/openhealth/extensions/GoogleFit.kt
@@ -7,69 +7,114 @@ import android.content.pm.PackageManager
import android.os.Build
import android.util.Log
import androidx.core.app.ActivityCompat
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
import com.dzeio.openhealth.data.weight.Weight
import com.google.android.gms.auth.api.signin.GoogleSignIn
import com.google.android.gms.fitness.Fitness
import com.google.android.gms.fitness.FitnessOptions
-import com.google.android.gms.fitness.data.DataPoint
import com.google.android.gms.fitness.data.DataType
import com.google.android.gms.fitness.request.DataReadRequest
import java.text.DateFormat
import java.util.*
import java.util.concurrent.TimeUnit
-class GoogleFit(
- private val activity: Activity,
-) : Extension() {
+class GoogleFit() : Extension() {
companion object {
const val TAG = "GoogleFitConnector"
}
- override val sourceID: String = "GoogleFit"
+ private lateinit var activity: Activity
+
+ override val id = "GoogleFit"
+ override val name = "Google Fit"
+
+ override fun init(activity: Activity): Array {
+ this.activity = activity
+ return arrayOf(
+ Data.WEIGHT
+ )
+ }
+
+ override fun getStatus(): String {
+ return if (isConnected()) "Connected" else "Not Connected"
+ }
+
+ override fun isAvailable(): Boolean {
+ return true
+ }
+
+ override fun isConnected(): Boolean =
+ GoogleSignIn.hasPermissions(getGoogleAccount(), fitnessOptions)
private val fitnessOptions = FitnessOptions.builder()
.addDataType(DataType.TYPE_WEIGHT)
- .addDataType(DataType.TYPE_STEP_COUNT_CUMULATIVE)
+// .addDataType(DataType.TYPE_STEP_COUNT_CUMULATIVE)
+// .addDataType(DataType.TYPE_CALORIES_EXPENDED)
.build()
- private fun checkPermissionsAndRun(data: Data) {
- if (permissionApproved()) {
- signIn(data)
- } else {
- Log.d(TAG, "Asking for permission")
- // Ask for permission
- ActivityCompat.requestPermissions(
- activity,
- arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
- data.ordinal
- )
- }
- }
+// private fun checkPermissionsAndRun(data: Data) {
+// if (permissionApproved()) {
+// signIn(data)
+// } else {
+// Log.d(TAG, "Asking for permission")
+// // Ask for permission
+// ActivityCompat.requestPermissions(
+// activity,
+// arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
+// data.ordinal
+// )
+// }
+// }
- private fun permissionApproved(): Boolean {
- val approved = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ private fun permissionApproved(): Boolean =
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
PackageManager.PERMISSION_GRANTED == ActivityCompat.checkSelfPermission(
activity,
- Manifest.permission.ACCESS_FINE_LOCATION)
+ Manifest.permission.ACCESS_FINE_LOCATION
+ )
} else {
true
}
- return approved
- }
- private fun signIn(data: Data) {
- if (oAuthPermissionsApproved()) {
- startImport(data)
+ private val connectLiveData: MutableLiveData = MutableLiveData(States.WIP)
+
+ override fun connect(): LiveData {
+
+ if (!permissionApproved()) {
+ ActivityCompat.requestPermissions(
+ activity,
+ arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
+ 87531
+ )
+ return connectLiveData
+ }
+
+ if (isConnected()) {
+ connectLiveData.value = States.DONE
} else {
Log.d("GoogleFitImporter", "Signing In")
GoogleSignIn.requestPermissions(
activity,
- data.ordinal,
- getGoogleAccount(), fitnessOptions)
+ 124887,
+ getGoogleAccount(), fitnessOptions
+ )
}
+ return connectLiveData
}
- private fun oAuthPermissionsApproved() = GoogleSignIn.hasPermissions(getGoogleAccount(), fitnessOptions)
+// private fun signIn(data: Data) {
+// if (isConnected()) {
+// startImport(data)
+// } else {
+// Log.d("GoogleFitImporter", "Signing In")
+// GoogleSignIn.requestPermissions(
+// activity,
+// data.ordinal,
+// getGoogleAccount(), fitnessOptions
+// )
+// }
+// }
private fun getGoogleAccount() = GoogleSignIn.getAccountForExtension(activity, fitnessOptions)
@@ -102,85 +147,116 @@ class GoogleFit(
else -> {}
}
- runRequest(DataReadRequest.Builder()
- .read(type)
- .setTimeRange(timeRange[0], timeRange[1], timeUnit)
- .build(), data)
+ runRequest(
+ DataReadRequest.Builder()
+ .read(type)
+ .setTimeRange(timeRange[0], timeRange[1], timeUnit)
+ .build(), data
+ )
}
private fun runRequest(request: DataReadRequest, data: Data) {
- Fitness.getHistoryClient(activity, GoogleSignIn.getAccountForExtension(activity, fitnessOptions))
+ Fitness.getHistoryClient(
+ activity,
+ GoogleSignIn.getAccountForExtension(activity, fitnessOptions)
+ )
.readData(request)
.addOnSuccessListener { response ->
- Log.d(TAG, "Received response! ${response.dataSets.size} ${response.buckets.size} ${response.status}")
+ Log.d(
+ TAG,
+ "Received response! ${response.dataSets.size} ${response.buckets.size} ${response.status}"
+ )
for (dataSet in response.dataSets) {
- Log.i(TAG, "Data returned for Data type: ${dataSet.dataType.name} ${dataSet.dataPoints.size}")
- dataSet.dataPoints.forEachIndexed { index, dp ->
- val isLast = (index + 1) == dataSet.dataPoints.size
+ Log.i(
+ TAG,
+ "Data returned for Data type: ${dataSet.dataType.name} ${dataSet.dataPoints.size} ${dataSet.dataSource.toDebugString()}"
+ )
+ dataSet.dataPoints.forEach { dp ->
// Global
- Log.i(TAG,"Importing Data point:")
- Log.i(TAG,"\tType: ${dp.dataType.name}")
- Log.i(TAG,"\tStart: ${dp.getStartTimeString()}")
- Log.i(TAG,"\tEnd: ${dp.getEndTimeString()}")
+ Log.i(TAG, "Importing Data point:")
+ Log.i(TAG, "\tType: ${dp.dataType.name}")
+ Log.i(
+ TAG,
+ "\tStart: ${Date(dp.getStartTime(TimeUnit.SECONDS) * 1000L).toLocaleString()}"
+ )
+ Log.i(
+ TAG,
+ "\tEnd: ${Date(dp.getEndTime(TimeUnit.SECONDS) * 1000L).toLocaleString()}"
+ )
// Field Specifics
for (field in dp.dataType.fields) {
- Log.i(TAG,"\tField: ${field.name} Value: ${dp.getValue(field)}")
+ Log.i(TAG, "\tField: ${field.name} Value: ${dp.getValue(field)}")
when (data) {
Data.WEIGHT -> {
val weight = Weight()
weight.timestamp = dp.getStartTime(TimeUnit.MILLISECONDS)
weight.weight = dp.getValue(field).asFloat()
- weightCallback(weight, isLast)
+ val list = weightLiveData.value?.list?.toMutableList()
+ ?: ArrayList()
+ list.add(weight)
+ weightLiveData.value =
+
+ ImportState(States.WIP, list)
}
else -> {}
}
}
}
+ when (data) {
+ Data.WEIGHT -> {
+ weightLiveData.value =
+ ImportState(
+ States.DONE, weightLiveData.value?.list
+ ?: ArrayList()
+ )
+ }
+ else -> {}
+ }
}
}
.addOnFailureListener { e ->
- Log.e(TAG,"There was an error reading data from Google Fit", e)
+ Log.e(TAG, "There was an error reading data from Google Fit", e)
}
}
- private fun DataPoint.getStartTimeString(): String = Date(this.getStartTime(TimeUnit.SECONDS) * 1000L).toLocaleString()
-
- private fun DataPoint.getEndTimeString(): String = Date(this.getEndTime(TimeUnit.SECONDS) * 1000L).toLocaleString()
-
+ /**
+ * Currently not usable
+ */
override fun onRequestPermissionResult(
requestCode: Int,
permission: Array,
grantResult: IntArray
) {
- signIn(Data.values()[requestCode])
+ connect()
+ // signIn(Data.values()[requestCode])
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
- signIn(Data.values()[requestCode])
- }
-
- private lateinit var weightCallback: (weight: Weight, end: Boolean) -> Unit
-
- override fun importWeight(callback: (weight: Weight, end: Boolean) -> Unit) {
- this.weightCallback = callback
- checkPermissionsAndRun(Data.WEIGHT)
- }
-
- private lateinit var callback : (item: Any, end: Boolean) -> Unit
-
- override fun import(data: Data, cb: (item: T, end: Boolean) -> Unit) {
- callback = cb as (item: Any, end: Boolean) -> Unit
- when (data) {
- Data.WEIGHT -> {
- checkPermissionsAndRun(data)
- }
- else -> {
- Log.d(TAG, "PRRRRRRRRRRRRR")
- }
+ if (requestCode == 0) {
+ return
}
+ connectLiveData.value = States.DONE
+ //signIn(Data.values()[requestCode])
+ }
+
+ private lateinit var weightLiveData: MutableLiveData>
+
+ override fun importWeight(): LiveData> {
+
+ weightLiveData = MutableLiveData(
+ ImportState(
+ States.WIP
+ )
+ )
+
+ startImport(Data.WEIGHT)
+
+// checkPermissionsAndRun(Data.WEIGHT)
+
+ return weightLiveData
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/dzeio/openhealth/extensions/GoogleFit.kt.old b/app/src/main/java/com/dzeio/openhealth/extensions/GoogleFit.kt.old
deleted file mode 100644
index 54254cb..0000000
--- a/app/src/main/java/com/dzeio/openhealth/extensions/GoogleFit.kt.old
+++ /dev/null
@@ -1,374 +0,0 @@
-package com.dzeio.openhealth.extensions
-
-import android.Manifest
-import android.app.Activity
-import android.content.pm.PackageManager
-import android.os.Build
-import android.util.Log
-import androidx.annotation.RequiresApi
-import androidx.core.app.ActivityCompat
-import com.dzeio.openhealth.data.weight.Weight
-import com.google.android.gms.auth.api.signin.GoogleSignIn
-import com.google.android.gms.fitness.Fitness
-import com.google.android.gms.fitness.FitnessOptions
-import com.google.android.gms.fitness.data.DataPoint
-import com.google.android.gms.fitness.data.DataSet
-import com.google.android.gms.fitness.data.DataSource
-import com.google.android.gms.fitness.data.DataType
-import com.google.android.gms.fitness.request.DataReadRequest
-import com.google.android.gms.fitness.request.DataSourcesRequest
-import com.google.android.gms.fitness.request.OnDataPointListener
-import com.google.android.gms.fitness.request.SensorRequest
-import java.text.DateFormat
-import java.text.SimpleDateFormat
-import java.util.*
-import java.util.concurrent.TimeUnit
-
-enum class ActionRequestCode {
- FIND_DATA_SOURCES
-}
-
-class GoogleFit(
- private val activity: Activity,
-) {
- companion object {
- const val TAG = "GoogleFitConnector"
- }
-// private val fitnessOptions = FitnessOptions.builder()
-// .addDataType(DataType.TYPE_ACTIVITY_SEGMENT, FitnessOptions.ACCESS_READ)
-// .addDataType(DataType.TYPE_ACTIVITY_SEGMENT, FitnessOptions.ACCESS_WRITE)
-//
-// .addDataType(DataType.TYPE_HEART_RATE_BPM, FitnessOptions.ACCESS_READ)
-// .addDataType(DataType.TYPE_HEART_RATE_BPM, FitnessOptions.ACCESS_WRITE)
-//
-// .addDataType(DataType.TYPE_HEIGHT, FitnessOptions.ACCESS_READ)
-// .addDataType(DataType.TYPE_HEIGHT, FitnessOptions.ACCESS_WRITE)
-//
-// .addDataType(DataType.TYPE_WEIGHT, FitnessOptions.ACCESS_READ)
-// .addDataType(DataType.TYPE_WEIGHT, FitnessOptions.ACCESS_WRITE)
-// .build()
-
- private val fitnessOptions = FitnessOptions.builder()
-// .addDataType(DataType.TYPE_STEP_COUNT_CUMULATIVE)
-// .addDataType(DataType.TYPE_ACTIVITY_SEGMENT)
-// .addDataType(DataType.TYPE_SLEEP_SEGMENT)
-// .addDataType(DataType.TYPE_CALORIES_EXPENDED)
-// .addDataType(DataType.TYPE_BASAL_METABOLIC_RATE)
-// .addDataType(DataType.TYPE_POWER_SAMPLE)
-// .addDataType(DataType.TYPE_HEART_RATE_BPM)
-// .addDataType(DataType.TYPE_LOCATION_SAMPLE)
- .addDataType(DataType.TYPE_WEIGHT)
- .build()
-
- private val runningQOrLater =
- Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
-
- // [START dataPointListener_variable_reference]
- // Need to hold a reference to this listener, as it's passed into the "unregister"
- // method in order to stop all sensors from sending data to this listener.
- private var dataPointListener: OnDataPointListener? = null
-
- fun import() {
- checkPermissionsAndRun(ActionRequestCode.FIND_DATA_SOURCES)
- }
-
- private fun checkPermissionsAndRun(actionRequestCode: ActionRequestCode) {
- if (permissionApproved()) {
- signIn(actionRequestCode)
- } else {
- requestRuntimePermissions(actionRequestCode)
- }
- }
-
- private fun requestRuntimePermissions(requestCode: ActionRequestCode) {
- val shouldProvideRationale =
- ActivityCompat.shouldShowRequestPermissionRationale(activity, Manifest.permission.ACCESS_FINE_LOCATION)
-
- // Provide an additional rationale to the user. This would happen if the user denied the
- // request previously, but didn't check the "Don't ask again" checkbox.
- requestCode.let {
- if (shouldProvideRationale) {
- Log.i(TAG, "Displaying permission rationale to provide additional context.")
-// ProgressDialog.show(activity, "Waiting for authorization...", "")
- ActivityCompat.requestPermissions(activity,
- arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
- requestCode.ordinal)
- } else {
- Log.i(TAG, "Requesting permission")
- // Request permission. It's possible this can be auto answered if device policy
- // sets the permission in a given state or the user denied the permission
- // previously and checked "Never ask again".
- ActivityCompat.requestPermissions(activity,
- arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
- requestCode.ordinal)
- }
- }
- }
-
- private fun permissionApproved(): Boolean {
- val approved = if (runningQOrLater) {
- PackageManager.PERMISSION_GRANTED == ActivityCompat.checkSelfPermission(
- activity,
- Manifest.permission.ACCESS_FINE_LOCATION)
- } else {
- true
- }
- return approved
- }
-
- fun signIn(requestCode: ActionRequestCode) {
- if (oAuthPermissionsApproved()) {
- performActionForRequestCode(requestCode)
- } else {
- requestCode.let {
- GoogleSignIn.requestPermissions(
- activity,
- it.ordinal,
- getGoogleAccount(), fitnessOptions)
- }
- }
- }
-
- private fun oAuthPermissionsApproved() = GoogleSignIn.hasPermissions(getGoogleAccount(), fitnessOptions)
-
- /**
- * Gets a Google account for use in creating the Fitness client. This is achieved by either
- * using the last signed-in account, or if necessary, prompting the user to sign in.
- * `getAccountForExtension` is recommended over `getLastSignedInAccount` as the latter can
- * return `null` if there has been no sign in before.
- */
- private fun getGoogleAccount() = GoogleSignIn.getAccountForExtension(activity, fitnessOptions)
-
- /**
- * Runs the desired method, based on the specified request code. The request code is typically
- * passed to the Fit sign-in flow, and returned with the success callback. This allows the
- * caller to specify which method, post-sign-in, should be called.
- *
- * @param requestCode The code corresponding to the action to perform.
- */
- fun performActionForRequestCode(requestCode: ActionRequestCode) = when (requestCode) {
- ActionRequestCode.FIND_DATA_SOURCES -> findFitnessDataSources()
- }
-
- /** Finds available data sources and attempts to register on a specific [DataType]. */
- private fun findFitnessDataSources() { // [START find_data_sources]
- // Note: Fitness.SensorsApi.findDataSources() requires the ACCESS_FINE_LOCATION permission.
- Fitness.getSensorsClient(activity, getGoogleAccount())
- .findDataSources(
- DataSourcesRequest.Builder()
- .setDataTypes(DataType.TYPE_LOCATION_SAMPLE)
- .setDataSourceTypes(DataSource.TYPE_RAW)
- .build())
- .addOnSuccessListener { dataSources ->
- for (dataSource in dataSources) {
- Log.i(TAG, "Data source found: $dataSource")
- Log.i(TAG, "Data Source type: " + dataSource.dataType.name)
- // Let's register a listener to receive Activity data!
- if (dataSource.dataType == DataType.TYPE_LOCATION_SAMPLE && dataPointListener == null) {
- Log.i(TAG, "Data source for LOCATION_SAMPLE found! Registering.")
- registerFitnessDataListener(dataSource, DataType.TYPE_LOCATION_SAMPLE)
- }
- }
- }
- .addOnFailureListener { e -> Log.e(TAG, "failed", e) }
- // [END find_data_sources]
- }
-
- /**
- * Registers a listener with the Sensors API for the provided [DataSource] and [DataType] combo.
- */
- private fun registerFitnessDataListener(dataSource: DataSource, dataType: DataType) {
- // [START register_data_listener]
- dataPointListener = OnDataPointListener { dataPoint ->
- for (field in dataPoint.dataType.fields) {
- val value = dataPoint.getValue(field)
- Log.i(TAG, "Detected DataPoint field: ${field.name}")
- Log.i(TAG, "Detected DataPoint value: $value")
- }
- }
- Fitness.getSensorsClient(activity, getGoogleAccount())
- .add(
- SensorRequest.Builder()
- .setDataSource(dataSource) // Optional but recommended for custom data sets.
- .setDataType(dataType) // Can't be omitted.
- .setSamplingRate(10, TimeUnit.SECONDS)
- .build(),
- dataPointListener!!
- )
- .addOnCompleteListener { task ->
- if (task.isSuccessful) {
- Log.i(TAG, "Listener registered!")
- } else {
- Log.e(TAG, "Listener not registered.", task.exception)
- }
- }
- // [END register_data_listener]
- }
-
- @RequiresApi(Build.VERSION_CODES.O)
- fun getHistory() {
-
- val calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
- val now = Date()
- calendar.time = now
- val endTime = calendar.timeInMillis
- calendar.set(Calendar.YEAR, 2013) // Set year to 2013 to be sure to get data from when Google Fit Started to today
- val startTime = calendar.timeInMillis
- val readRequest = DataReadRequest.Builder()
- .aggregate(DataType.AGGREGATE_CALORIES_EXPENDED)
- .bucketByActivityType(1, TimeUnit.SECONDS)
- .setTimeRange(startTime, endTime, TimeUnit.SECONDS)
- .build()
-
- Fitness.getHistoryClient(activity, GoogleSignIn.getAccountForExtension(activity, fitnessOptions))
- .readData(readRequest)
- .addOnSuccessListener { response ->
- // The aggregate query puts datasets into buckets, so flatten into a
- // single list of datasets
- for (dataSet in response.buckets.flatMap { it.dataSets }) {
- dumpDataSet(dataSet)
- }
- }
- .addOnFailureListener { e ->
- Log.w(TAG,"There was an error reading data from Google Fit", e)
- }
-
- }
-
- fun importWeight(callback : () -> Unit) {
-
- val calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
- val now = Date()
- calendar.time = now
- val endTime = calendar.timeInMillis
- calendar.set(Calendar.YEAR, 2013) // Set year to 2013 to be sure to get data from when Google Fit Started to today
- val startTime = calendar.timeInMillis
-
- val dateFormat = DateFormat.getDateInstance()
- Log.i(TAG, "Range Start: ${dateFormat.format(startTime)}")
- Log.i(TAG, "Range End: ${dateFormat.format(endTime)}")
-
-
- runRequest(DataReadRequest.Builder()
- // The data request can specify multiple data types to return, effectively
- // combining multiple data queries into one call.
- // In this example, it's very unlikely that the request is for several hundred
- // datapoints each consisting of a few steps and a timestamp. The more likely
- // scenario is wanting to see how many steps were walked per day, for 7 days.
-// .aggregate()
- .read(DataType.TYPE_WEIGHT)
-
- // Analogous to a "Group By" in SQL, defines how data should be aggregated.
- // bucketByTime allows for a time span, whereas bucketBySession would allow
- // bucketing by "sessions", which would need to be defined in code.
-// .bucketByTime(1, TimeUnit.MINUTES)
-// .bucketByActivityType(1, TimeUnit.SECONDS)
-// .bucketBySession()
- .setTimeRange(startTime, endTime, TimeUnit.MILLISECONDS)
- .build(), callback)
-
- }
-
- private fun runRequest(request: DataReadRequest, callback: () -> Unit) {
- Fitness.getHistoryClient(activity, GoogleSignIn.getAccountForExtension(activity, fitnessOptions))
- .readData(request)
- .addOnSuccessListener { response ->
- // The aggregate query puts datasets into buckets, so flatten into a
- // single list of datasets
- Log.d(TAG, "Received response! ${response.dataSets.size} ${response.buckets.size}")
- for (dataSet in response.dataSets) {
- dumpDataSet(dataSet)
- }
- for (dataSet in response.buckets.flatMap { it.dataSets }) {
- dumpDataSet(dataSet)
- }
- callback.invoke()
- }
- .addOnFailureListener { e ->
- Log.w(TAG,"There was an error reading data from Google Fit", e)
- callback.invoke()
- }
- }
-
- private fun dumpDataSet(dataSet: DataSet) {
- Log.i(TAG, "Data returned for Data type: ${dataSet.dataType.name} ${dataSet.dataPoints.size}")
- for (dp in dataSet.dataPoints) {
- val weight = Weight()
- Log.i(TAG,"Data point:")
- Log.i(TAG,"\tType: ${dp.dataType.name}")
- Log.i(TAG,"\tStart: ${dp.getStartTimeString()}")
- Log.i(TAG,"\tEnd: ${dp.getEndTimeString()}")
- weight.timestamp = dp.getStartTime(TimeUnit.SECONDS)
- weight.source = "GoogleFit"
- for (field in dp.dataType.fields) {
- weight.weight = dp.getValue(field).asFloat()
- Log.i(TAG,"\tField: ${field.name.toString()} Value: ${dp.getValue(field)}")
- }
- // AppDatabase.getInstance(activity).weightDao().insert(weight)
- }
- }
-
- fun DataPoint.getStartTimeString(): String =
- SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSSSS", Locale.FRANCE)
- .format(Date(this.getStartTime(TimeUnit.SECONDS) * 1000L))
-
- fun DataPoint.getEndTimeString(): String =
- SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSSSS", Locale.FRANCE)
- .format(Date(this.getEndTime(TimeUnit.SECONDS) * 1000L))
-
-
- /** Unregisters the listener with the Sensors API. */
- private fun unregisterFitnessDataListener() {
- if (dataPointListener == null) {
- // This code only activates one listener at a time. If there's no listener, there's
- // nothing to unregister.
- return
- }
- // [START unregister_data_listener]
- // Waiting isn't actually necessary as the unregister call will complete regardless,
- // even if called from within onStop, but a callback can still be added in order to
- // inspect the results.
- Fitness.getSensorsClient(activity, getGoogleAccount())
- .remove(dataPointListener!!)
- .addOnCompleteListener { task ->
- if (task.isSuccessful && task.result!!) {
- Log.i(TAG, "Listener was removed!")
- } else {
- Log.i(TAG, "Listener was not removed.")
- }
- }
- // [END unregister_data_listener]
- }
-
- /** Returns a [DataReadRequest] for all step count changes in the past week. */
- private fun queryFitnessData(): DataReadRequest {
- // [START build_read_data_request]
- // Setting a start and end date using a range of 1 week before this moment.
- val calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
- val now = Date()
- calendar.time = now
- val endTime = calendar.timeInMillis
- calendar.add(Calendar.YEAR, -1)
- val startTime = calendar.timeInMillis
-
- val dateFormat = DateFormat.getDateInstance()
- Log.i(TAG, "Range Start: ${dateFormat.format(startTime)}")
- Log.i(TAG, "Range End: ${dateFormat.format(endTime)}")
-
- return DataReadRequest.Builder()
- // The data request can specify multiple data types to return, effectively
- // combining multiple data queries into one call.
- // In this example, it's very unlikely that the request is for several hundred
- // datapoints each consisting of a few steps and a timestamp. The more likely
- // scenario is wanting to see how many steps were walked per day, for 7 days.
- .aggregate(DataType.TYPE_STEP_COUNT_DELTA)
- // Analogous to a "Group By" in SQL, defines how data should be aggregated.
- // bucketByTime allows for a time span, whereas bucketBySession would allow
- // bucketing by "sessions", which would need to be defined in code.
- .bucketByTime(1, TimeUnit.SECONDS)
-// .bucketBySession()
- .setTimeRange(startTime, endTime, TimeUnit.MILLISECONDS)
- .build()
- }
-
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/dzeio/openhealth/extensions/samsunghealth/SamsungHealth.kt b/app/src/main/java/com/dzeio/openhealth/extensions/samsunghealth/SamsungHealth.kt
index 14edd14..e301f7c 100644
--- a/app/src/main/java/com/dzeio/openhealth/extensions/samsunghealth/SamsungHealth.kt
+++ b/app/src/main/java/com/dzeio/openhealth/extensions/samsunghealth/SamsungHealth.kt
@@ -5,11 +5,12 @@ import android.content.Intent
import android.os.Handler
import android.os.Looper
import android.util.Log
-import com.dzeio.openhealth.extensions.Extension
import com.dzeio.openhealth.data.weight.Weight
-import com.samsung.android.sdk.healthdata.*
+import com.samsung.android.sdk.healthdata.HealthConnectionErrorResult
import com.samsung.android.sdk.healthdata.HealthConstants.StepCount
+import com.samsung.android.sdk.healthdata.HealthDataStore
import com.samsung.android.sdk.healthdata.HealthDataStore.ConnectionListener
+import com.samsung.android.sdk.healthdata.HealthPermissionManager
import com.samsung.android.sdk.healthdata.HealthPermissionManager.*
@@ -18,7 +19,7 @@ import com.samsung.android.sdk.healthdata.HealthPermissionManager.*
*/
class SamsungHealth(
private val context: Activity
-) : Extension() {
+) {
companion object {
const val TAG = "SamsungHealthConnector"
@@ -33,6 +34,7 @@ class SamsungHealth(
requestPermission()
}
}
+
override fun onConnectionFailed(p0: HealthConnectionErrorResult?) {
Log.d(TAG, "Health data service is not available.")
}
@@ -43,7 +45,7 @@ class SamsungHealth(
}
- private val store : HealthDataStore = HealthDataStore(context, listener)
+ private val store: HealthDataStore = HealthDataStore(context, listener)
private fun isPermissionAcquired(): Boolean {
val permKey = PermissionKey(StepCount.HEALTH_DATA_TYPE, PermissionType.READ)
@@ -86,23 +88,25 @@ class SamsungHealth(
}
}
- private val reporter = StepCountReporter(store, stepCountObserver, Handler(Looper.getMainLooper()))
+ private val reporter =
+ StepCountReporter(store, stepCountObserver, Handler(Looper.getMainLooper()))
/**
* Connector
*/
- override val sourceID: String = "SamsungHealth"
+ val sourceID: String = "SamsungHealth"
- override fun onRequestPermissionResult(
+ fun onRequestPermissionResult(
requestCode: Int,
permission: Array,
grantResult: IntArray
- ) {}
+ ) {
+ }
- override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {}
+ fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {}
- override fun importWeight(callback: (weight: Weight, end: Boolean) -> Unit) {
+ fun importWeight(callback: (weight: Weight, end: Boolean) -> Unit) {
store.connectService()
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/dzeio/openhealth/interfaces/NotificationChannels.kt b/app/src/main/java/com/dzeio/openhealth/interfaces/NotificationChannels.kt
index 77ee1c0..b64a600 100644
--- a/app/src/main/java/com/dzeio/openhealth/interfaces/NotificationChannels.kt
+++ b/app/src/main/java/com/dzeio/openhealth/interfaces/NotificationChannels.kt
@@ -1,12 +1,10 @@
package com.dzeio.openhealth.interfaces
-import android.app.NotificationManager
-
enum class NotificationChannels(
val id: String,
val channelName: String,
val importance: Int
) {
// 3 is IMPORTANCE_DEFAULT
- DEFAULT("default", "Default Channel", 3)
+ DEFAULT("openhealth_default", "Default Channel", 3)
}
\ No newline at end of file
diff --git a/app/src/main/java/com/dzeio/openhealth/ui/extension/ExtensionFragment.kt b/app/src/main/java/com/dzeio/openhealth/ui/extension/ExtensionFragment.kt
new file mode 100644
index 0000000..2171953
--- /dev/null
+++ b/app/src/main/java/com/dzeio/openhealth/ui/extension/ExtensionFragment.kt
@@ -0,0 +1,59 @@
+package com.dzeio.openhealth.ui.extension
+
+import android.app.ProgressDialog
+import android.os.Bundle
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.lifecycle.lifecycleScope
+import androidx.navigation.fragment.navArgs
+import com.dzeio.openhealth.core.BaseFragment
+import com.dzeio.openhealth.databinding.FragmentExtensionBinding
+import com.dzeio.openhealth.extensions.Extension
+import com.dzeio.openhealth.extensions.ExtensionFactory
+import dagger.hilt.android.AndroidEntryPoint
+import java.lang.Exception
+
+@AndroidEntryPoint
+class ExtensionFragment :
+ BaseFragment(ExtensionViewModel::class.java) {
+
+ override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> FragmentExtensionBinding =
+ FragmentExtensionBinding::inflate
+
+
+ private val args: ExtensionFragmentArgs by navArgs()
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ val extension = ExtensionFactory.getExtension(args.extension)
+ ?: throw Exception("No Extension found!")
+
+ extension.init(requireActivity())
+
+ binding.importButton.setOnClickListener {
+ val dialog = ProgressDialog(requireContext())
+ dialog.setTitle("Importing...")
+ dialog.setMessage("Imported 0 values")
+ dialog.show()
+ val data = extension.importWeight()
+ data.observe(viewLifecycleOwner) { state ->
+ Log.d("ExtensionFragment", state.state.name)
+ Log.d("ExtensionFragment", state.list.size.toString())
+ dialog.setMessage("Imported ${state.list.size} values")
+ if (state.state == Extension.States.DONE) {
+ dialog.setMessage("Finishing Import...")
+ lifecycleScope.launchWhenStarted {
+ state.list.forEach {
+ it.source = extension.id
+ viewModel.importWeight(it)
+ }
+ dialog.dismiss()
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/dzeio/openhealth/ui/extension/ExtensionViewModel.kt b/app/src/main/java/com/dzeio/openhealth/ui/extension/ExtensionViewModel.kt
new file mode 100644
index 0000000..74597fc
--- /dev/null
+++ b/app/src/main/java/com/dzeio/openhealth/ui/extension/ExtensionViewModel.kt
@@ -0,0 +1,30 @@
+package com.dzeio.openhealth.ui.extension
+
+import androidx.lifecycle.MutableLiveData
+import com.dzeio.openhealth.core.BaseViewModel
+import com.dzeio.openhealth.data.weight.Weight
+import com.dzeio.openhealth.data.weight.WeightRepository
+import dagger.hilt.android.lifecycle.HiltViewModel
+import javax.inject.Inject
+
+@HiltViewModel
+class ExtensionViewModel @Inject internal constructor(
+ private val weightRepository: WeightRepository
+) : BaseViewModel() {
+
+ val text = MutableLiveData().apply {
+ value = "This is slideshow Fragment"
+ }
+ val importProgress = MutableLiveData().apply {
+ value = 0
+ }
+ // If -1 progress is undetermined
+ // If 0 no progress bar
+ // Else progress bar
+ val importProgressTotal = MutableLiveData().apply {
+ value = 0
+ }
+
+ suspend fun importWeight(weight: Weight) = weightRepository.addWeight(weight)
+ suspend fun deleteFromSource(source: String) = weightRepository.deleteFromSource(source)
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/dzeio/openhealth/ui/extensions/ExtensionsFragment.kt b/app/src/main/java/com/dzeio/openhealth/ui/extensions/ExtensionsFragment.kt
index 9cdd70b..cdb3831 100644
--- a/app/src/main/java/com/dzeio/openhealth/ui/extensions/ExtensionsFragment.kt
+++ b/app/src/main/java/com/dzeio/openhealth/ui/extensions/ExtensionsFragment.kt
@@ -10,13 +10,13 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.RequiresApi
-import androidx.lifecycle.lifecycleScope
-import com.dzeio.openhealth.extensions.Extension
-import com.dzeio.openhealth.extensions.GoogleFit
-//import com.dzeio.openhealth.connectors.GoogleFit
-import com.dzeio.openhealth.extensions.samsunghealth.SamsungHealth
+import androidx.navigation.fragment.findNavController
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.dzeio.openhealth.adapters.ExtensionAdapter
import com.dzeio.openhealth.core.BaseFragment
import com.dzeio.openhealth.databinding.FragmentExtensionsBinding
+import com.dzeio.openhealth.extensions.Extension
+import com.dzeio.openhealth.extensions.GoogleFit
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
@@ -24,92 +24,55 @@ class ExtensionsFragment :
BaseFragment(ExtensionsViewModel::class.java) {
companion object {
- const val TAG = "ImportFragment"
+ const val TAG = "ExtensionsFragment"
}
override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> FragmentExtensionsBinding =
FragmentExtensionsBinding::inflate
- private lateinit var progressDialog: ProgressDialog
-
- private lateinit var fit: Extension
+ private lateinit var activeExtension: Extension
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- progressDialog = ProgressDialog(requireContext())
+ val recycler = binding.list
- progressDialog.apply {
- setCancelable(false)
- setTitle("Importing from source...")
- }
+ val manager = LinearLayoutManager(requireContext())
+ recycler.layoutManager = manager
- binding.importGoogleFit.setOnClickListener {
- importFromGoogleFit()
- }
- binding.importSamsungHealth.setOnClickListener {
- importFromSamsungHealth()
- }
- }
-
- private fun importFromGoogleFit() {
- progressDialog.show()
- fit = GoogleFit(requireActivity())
-
- var imported = 0
-
- lifecycleScope.launchWhenStarted {
- viewModel.deleteFromSource(fit.sourceID)
- }.invokeOnCompletion {
- //progressDialog.show()
- fit.importWeight { weight, end ->
- Log.d("Importer", "Importing $weight")
- weight.source = fit.sourceID
- progressDialog.setTitle("Importing from source... ${++imported}")
- lifecycleScope.launchWhenStarted {
- viewModel.importWeight(weight)
+ val adapter = ExtensionAdapter()
+ adapter.onItemClick = {
+ activeExtension = it
+ Log.d(it.id, it.name)
+ if (it.isConnected()) {
+ Log.d(it.id, "Continue!")
+ findNavController().navigate(
+ ExtensionsFragmentDirections.actionNavExtensionsToNavExtension(
+ it.id
+ )
+ )
+ } else {
+ val ls = it.connect()
+ ls.observe(viewLifecycleOwner) { st ->
+ Log.d("States", st.name)
}
- if (end) {
- Log.d("Importer", "Finished Importing")
- progressDialog.dismiss()
- return@importWeight
- }
-
}
}
+ recycler.adapter = adapter
+ val list = arrayOf(
+ GoogleFit()
+ ).toList()
- }
-
- private fun importFromSamsungHealth() {
- progressDialog.show()
- fit = SamsungHealth(requireActivity())
-
- var imported = 0
-
- lifecycleScope.launchWhenStarted {
- viewModel.deleteFromSource(fit.sourceID)
- }.invokeOnCompletion {
- //progressDialog.show()
- fit.importWeight { weight, end ->
- Log.d("Importer", "Importing $weight")
- weight.source = fit.sourceID
- progressDialog.setTitle("Importing from source... ${++imported}")
- lifecycleScope.launchWhenStarted {
- viewModel.importWeight(weight)
- }
- if (end) {
- Log.d("Importer", "Finished Importing")
- progressDialog.dismiss()
- return@importWeight
- }
-
- }
+ list.forEach {
+ it.init(requireActivity())
}
+
+ adapter.set(list)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
- fit.onActivityResult(requestCode, resultCode, data)
+ activeExtension.onActivityResult(requestCode, resultCode, data)
}
@RequiresApi(Build.VERSION_CODES.O)
@@ -126,7 +89,7 @@ class ExtensionsFragment :
grantResults[0] == PackageManager.PERMISSION_GRANTED -> {
Log.d(TAG, "Granted")
- fit.onRequestPermissionResult(requestCode, permissions, grantResults)
+ activeExtension.onRequestPermissionResult(requestCode, permissions, grantResults)
}
else -> {
// Permission denied.
diff --git a/app/src/main/java/com/dzeio/openhealth/ui/home/HomeFragment.kt b/app/src/main/java/com/dzeio/openhealth/ui/home/HomeFragment.kt
index 1b8ff51..c6326e7 100644
--- a/app/src/main/java/com/dzeio/openhealth/ui/home/HomeFragment.kt
+++ b/app/src/main/java/com/dzeio/openhealth/ui/home/HomeFragment.kt
@@ -1,9 +1,9 @@
package com.dzeio.openhealth.ui.home
import android.animation.ValueAnimator
-import android.graphics.BitmapFactory
+import android.graphics.Bitmap
import android.graphics.Canvas
-import android.graphics.Rect
+import android.graphics.RectF
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
@@ -13,28 +13,21 @@ import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import androidx.preference.PreferenceManager
import com.dzeio.openhealth.Application
-import com.dzeio.openhealth.R
import com.dzeio.openhealth.core.BaseFragment
import com.dzeio.openhealth.data.water.Water
-import com.dzeio.openhealth.databinding.FragmentHomeBinding
import com.dzeio.openhealth.data.weight.Weight
+import com.dzeio.openhealth.databinding.FragmentHomeBinding
import com.dzeio.openhealth.ui.weight.AddWeightDialog
-import com.dzeio.openhealth.utils.BitmapUtils
import com.dzeio.openhealth.utils.DrawUtils
-import com.github.mikephil.charting.components.AxisBase
-import com.github.mikephil.charting.components.Description
-import com.github.mikephil.charting.components.XAxis
+import com.dzeio.openhealth.utils.GraphUtils
import com.github.mikephil.charting.data.Entry
import com.github.mikephil.charting.data.LineData
import com.github.mikephil.charting.data.LineDataSet
-import com.github.mikephil.charting.formatter.ValueFormatter
import com.google.android.material.color.MaterialColors
import dagger.hilt.android.AndroidEntryPoint
-import kotlinx.coroutines.flow.*
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
-import java.text.DateFormat
-import java.text.SimpleDateFormat
-import java.util.*
import kotlin.math.min
import kotlin.properties.Delegates
@@ -53,8 +46,6 @@ class HomeFragment : BaseFragment(HomeViewMo
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- viewModel.init()
-
binding.addWeight.setOnClickListener {
AddWeightDialog().show(requireActivity().supportFragmentManager, null)
}
@@ -112,14 +103,23 @@ class HomeFragment : BaseFragment(HomeViewMo
}
binding.listWeight.setOnClickListener {
- Log.d("T", "Trying to move")
-
findNavController().navigate(HomeFragmentDirections.actionNavHomeToNavListWeight())
}
binding.gotoWaterHome.setOnClickListener {
findNavController().navigate(HomeFragmentDirections.actionNavHomeToNavWaterHome())
}
+
+ GraphUtils.lineChartSetup(
+ binding.weightGraph,
+ MaterialColors.getColor(
+ requireView(),
+ com.google.android.material.R.attr.colorPrimary
+ ), MaterialColors.getColor(
+ requireView(),
+ com.google.android.material.R.attr.colorOnBackground
+ )
+ )
}
private fun updateGraph(list: List) {
@@ -130,35 +130,9 @@ class HomeFragment : BaseFragment(HomeViewMo
}
val dataSet = LineDataSet(entries, "Label")
+
binding.weightGraph.apply {
- // Setup
- isAutoScaleMinMaxEnabled = true
- legend.isEnabled = false
- isDragEnabled = true
- isScaleYEnabled = false
- description = Description().apply { isEnabled = false }
- isScaleXEnabled = true
- setPinchZoom(false)
- setDrawGridBackground(false)
- setDrawBorders(false)
- axisLeft.setLabelCount(0, true)
-
- xAxis.apply {
- valueFormatter = object : ValueFormatter() {
- override fun getAxisLabel(value: Float, axis: AxisBase?): String {
- return SimpleDateFormat(
- "yyyy-MM-dd",
- Locale.getDefault()
- ).format(Date(value.toLong()))
- //return super.getAxisLabel(value, axis)
- }
- }
- position = XAxis.XAxisPosition.BOTTOM
- setDrawGridLines(false)
- setLabelCount(3, true)
- }
-
// Apply new dataset
data = LineData(dataSet)
@@ -181,6 +155,8 @@ class HomeFragment : BaseFragment(HomeViewMo
viewModel.fetchWeights().collectLatest {
updateGraph(it)
}
+ updateWater(0)
+ updateWater(1234)
}
viewModel.water.observe(viewLifecycleOwner) {
@@ -192,38 +168,75 @@ class HomeFragment : BaseFragment(HomeViewMo
}
}
- updateWater(0)
-
}
private fun updateWater(water: Int) {
val oldValue = binding.fragmentHomeWaterCurrent.text.toString().replace("ml", "").toInt()
binding.fragmentHomeWaterCurrent.text = "${water}ml"
- val graph = BitmapUtils.convertToMutable(
- requireContext(),
- BitmapFactory.decodeResource(resources, R.drawable.ellipse)
+ var width = 1500
+ var height = 750
+
+ if (binding.background.width != 0) {
+ width = binding.background.width
+ height = binding.background.height
+ }
+
+ val graph = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
+ Log.d("Test2", "$width $height")
+
+ val canvas = Canvas(graph)
+ val rect = RectF(
+ 10f,
+ 15f,
+ 90f,
+ 85f
)
- graph?.let { btmp ->
- ValueAnimator.ofFloat(min(oldValue.toFloat(), intake), min(water.toFloat(), intake))
- .apply {
- duration = 300
- addUpdateListener {
- val canvas = Canvas(btmp)
- DrawUtils.drawArc(
- canvas,
- 100 * it.animatedValue as Float / intake,
- MaterialColors.getColor(
- requireView(),
- com.google.android.material.R.attr.colorPrimary
- )
- )
- canvas.save()
- binding.background.setImageBitmap(graph)
- }
- start()
+// DrawUtils.drawRect(
+// canvas,
+// RectF(
+// 0f,
+// 0f,
+// 100f,
+// 100f
+// ),
+// MaterialColors.getColor(
+// requireView(),
+// com.google.android.material.R.attr.colorOnPrimary
+// ),
+// 3f
+// )
+
+ DrawUtils.drawArc(
+ canvas,
+ 100f,
+ rect,
+ MaterialColors.getColor(
+ requireView(),
+ com.google.android.material.R.attr.colorOnPrimary
+ ),
+ 3f
+ )
+
+ Log.d("Test", "${min(oldValue.toFloat(), intake)} ${min(water.toFloat(), intake)}")
+ ValueAnimator.ofFloat(min(oldValue.toFloat(), intake), min(water.toFloat(), intake))
+ .apply {
+ duration = 300
+ addUpdateListener {
+ DrawUtils.drawArc(
+ canvas,
+ 100 * it.animatedValue as Float / intake,
+ rect,
+ MaterialColors.getColor(
+ requireView(),
+ com.google.android.material.R.attr.colorPrimary
+ ), 6f
+ )
+ canvas.save()
+ binding.background.setImageBitmap(graph)
}
- }
+ start()
+ }
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/dzeio/openhealth/ui/home/HomeViewModel.kt b/app/src/main/java/com/dzeio/openhealth/ui/home/HomeViewModel.kt
index 3c07b95..b1f824a 100644
--- a/app/src/main/java/com/dzeio/openhealth/ui/home/HomeViewModel.kt
+++ b/app/src/main/java/com/dzeio/openhealth/ui/home/HomeViewModel.kt
@@ -1,5 +1,6 @@
package com.dzeio.openhealth.ui.home
+import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.dzeio.openhealth.core.BaseViewModel
@@ -18,8 +19,23 @@ class HomeViewModel @Inject internal constructor(
private val waterRepository: WaterRepository
) : BaseViewModel() {
+ init {
+ viewModelScope.launch {
+ waterRepository.todayWater().collectLatest {
+ _water.value = it
+ }
+ }
+ }
+
+ /**
+ * @deprecated
+ */
fun fetchWeights() = weightRepository.getWeights()
+
+ /**
+ * @deprecated
+ */
fun lastWeight() = weightRepository.lastWeight()
fun fetchWeight(id: Long) = weightRepository.getWeight(id)
@@ -28,17 +44,11 @@ class HomeViewModel @Inject internal constructor(
suspend fun addWeight(weight: Weight) = weightRepository.addWeight(weight)
- fun fetchTodayWater() = waterRepository.todayWater()
+ suspend fun fetchTodayWater() = waterRepository.todayWater()
- val water: MutableLiveData = MutableLiveData(null)
+ private val _water = MutableLiveData(null)
+ val water: LiveData = _water
- fun init() {
- viewModelScope.launch {
- waterRepository.todayWater().collectLatest {
- water.postValue(it)
- }
- }
- }
fun updateWater(water: Water) {
viewModelScope.launch {
@@ -49,7 +59,7 @@ class HomeViewModel @Inject internal constructor(
fun deleteWater(item: Water) {
viewModelScope.launch {
waterRepository.deleteWater(item)
- water.postValue(null)
+ _water.postValue(null)
}
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/dzeio/openhealth/ui/water/EditWaterDialog.kt b/app/src/main/java/com/dzeio/openhealth/ui/water/EditWaterDialog.kt
index 6b02880..2b683d7 100644
--- a/app/src/main/java/com/dzeio/openhealth/ui/water/EditWaterDialog.kt
+++ b/app/src/main/java/com/dzeio/openhealth/ui/water/EditWaterDialog.kt
@@ -1,7 +1,9 @@
package com.dzeio.openhealth.ui.water
import android.app.Dialog
+import android.os.Build
import android.os.Bundle
+import android.util.Log
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.Window
@@ -13,8 +15,12 @@ import androidx.navigation.fragment.navArgs
import com.dzeio.openhealth.R
import com.dzeio.openhealth.core.BaseFullscreenDialog
import com.dzeio.openhealth.databinding.DialogWaterEditWaterBinding
+import com.google.android.material.datepicker.CalendarConstraints
+import com.google.android.material.datepicker.DateValidatorPointBackward
+import com.google.android.material.datepicker.MaterialDatePicker
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
+import java.util.*
@AndroidEntryPoint
class EditWaterDialog :
@@ -40,13 +46,53 @@ class EditWaterDialog :
viewModel.water.observe(viewLifecycleOwner) {
binding.editTextNumber.setText(it.value.toString())
+ binding.date.text = it.formatTimestamp()
}
binding.editTextNumber.doOnTextChanged { text, start, before, count ->
- newValue = text.toString().toInt()
+ val value = text.toString()
+ newValue = if (value == "") 0
+ else text.toString().toInt()
+ }
+
+ binding.date.setOnClickListener {
+ val water = viewModel.water.value!!
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ val date = Date(water.timestamp)
+ val datePicker = MaterialDatePicker.Builder.datePicker()
+ .setTitleText("Select Date")
+ .setSelection(water.timestamp)
+ .setCalendarConstraints(
+ CalendarConstraints.Builder()
+ .setValidator(DateValidatorPointBackward.now())
+ .setEnd(Date().time)
+ .build()
+ )
+ .build()
+
+ val fragManager = requireActivity().supportFragmentManager
+
+ datePicker.addOnPositiveButtonClickListener { tsp ->
+
+ water.timestamp = tsp
+ binding.date.setText(water.formatTimestamp())
+
+ }
+ datePicker.show(fragManager, "dialog")
+ Log.d("Tag", "${date.year + 1900}, ${date.month}, ${date.day}")
+// val dg = DatePickerDialog(requireActivity())
+// dg.setOnDateSetListener { _, year, month, day ->
+//
+// }
+// dg.updateDate(date.year + 1900, date.month, date.day)
+// dg.show()
+ } else {
+ TODO("VERSION.SDK_INT < N")
+ }
+
+
}
viewModel.init(args.id)
-
}
private fun save() {
diff --git a/app/src/main/java/com/dzeio/openhealth/ui/water/WaterHomeFragment.kt b/app/src/main/java/com/dzeio/openhealth/ui/water/WaterHomeFragment.kt
index bd9d61b..7d78361 100644
--- a/app/src/main/java/com/dzeio/openhealth/ui/water/WaterHomeFragment.kt
+++ b/app/src/main/java/com/dzeio/openhealth/ui/water/WaterHomeFragment.kt
@@ -1,30 +1,20 @@
package com.dzeio.openhealth.ui.water
-import android.graphics.Color
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
-import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import com.dzeio.openhealth.adapters.WaterAdapter
-import com.dzeio.openhealth.adapters.WeightAdapter
import com.dzeio.openhealth.core.BaseFragment
-import com.dzeio.openhealth.databinding.FragmentListWeightBinding
import com.dzeio.openhealth.databinding.FragmentMainWaterHomeBinding
-import com.dzeio.openhealth.ui.home.HomeViewModel
-import com.dzeio.openhealth.ui.weight.ListWeightFragmentDirections
import com.dzeio.openhealth.utils.GraphUtils
import com.github.mikephil.charting.data.BarData
import com.github.mikephil.charting.data.BarDataSet
import com.github.mikephil.charting.data.BarEntry
-import com.github.mikephil.charting.data.Entry
import com.google.android.material.color.MaterialColors
import dagger.hilt.android.AndroidEntryPoint
-import kotlinx.coroutines.flow.collectLatest
-import java.time.Instant
-import java.time.temporal.ChronoUnit
import java.util.*
@AndroidEntryPoint
@@ -60,6 +50,9 @@ class WaterHomeFragment :
chart, MaterialColors.getColor(
requireView(),
com.google.android.material.R.attr.colorPrimary
+ ), MaterialColors.getColor(
+ requireView(),
+ com.google.android.material.R.attr.colorOnBackground
)
)
@@ -73,7 +66,7 @@ class WaterHomeFragment :
epoch.time = Date(0)
epoch.add(Calendar.MILLISECOND, it.timestamp.toInt())
return@map BarEntry(
- epoch.get(Calendar.DATE).toFloat(),
+ (epoch.timeInMillis / 1000 / 60 / 60).toFloat(),
it.value.toFloat()
)
},
diff --git a/app/src/main/java/com/dzeio/openhealth/utils/DrawUtils.kt b/app/src/main/java/com/dzeio/openhealth/utils/DrawUtils.kt
index 1000ece..35fcde5 100644
--- a/app/src/main/java/com/dzeio/openhealth/utils/DrawUtils.kt
+++ b/app/src/main/java/com/dzeio/openhealth/utils/DrawUtils.kt
@@ -1,24 +1,23 @@
package com.dzeio.openhealth.utils
-import android.graphics.*
+import android.graphics.Canvas
+import android.graphics.Paint
+import android.graphics.RectF
object DrawUtils {
/**
* Fuck Graphics
*/
- fun drawArc(canvas: Canvas, percent: Float, pColor: Int) {
- canvas.width
- val spacing = 120f
+ fun drawArc(canvas: Canvas, percent: Float, rect: RectF, pColor: Int, strokeWidth: Float = 1f) {
val r1 = RectF(
- spacing,
- spacing,
- canvas.width - spacing,
- canvas.height * 2 - spacing * 3
+ canvas.realSize(true, rect.left),
+ canvas.realSize(false, rect.top),
+ canvas.realSize(true, rect.right),
+ canvas.realSize(false, rect.bottom, 2)
)
- val paint = Paint()
- paint.apply {
- strokeWidth = 200f
+ val paint = Paint().apply {
+ this.strokeWidth = canvas.realSize(true, strokeWidth)
style = Paint.Style.STROKE
color = pColor
isAntiAlias = true
@@ -27,4 +26,33 @@ object DrawUtils {
canvas.drawArc(r1, 180f, 180 * percent / 100f, false, paint)
}
+ /**
+ * Fuck Graphics
+ */
+ fun drawRect(canvas: Canvas, rect: RectF, pColor: Int, strokeWidth: Float = 1f) {
+ val r1 = RectF(
+ canvas.realSize(true, rect.left),
+ canvas.realSize(false, rect.top),
+ canvas.realSize(true, rect.right),
+ canvas.realSize(false, rect.bottom)
+ )
+ val paint = Paint().apply {
+ this.strokeWidth = canvas.realSize(true, strokeWidth)
+ style = Paint.Style.STROKE
+ color = pColor
+ isAntiAlias = true
+ }
+ canvas.drawRect(r1, paint)
+ }
+
+ private fun Canvas.realSize(isWidth: Boolean, value: Float): Float {
+ val it = if (isWidth) this.width else this.height
+ return it * value / 100
+ }
+
+ private fun Canvas.realSize(isWidth: Boolean, value: Float, multiplier: Int): Float {
+ val it = (if (isWidth) this.width else this.height) * multiplier
+ return it * value / 100
+ }
+
}
\ No newline at end of file
diff --git a/app/src/main/java/com/dzeio/openhealth/utils/GraphUtils.kt b/app/src/main/java/com/dzeio/openhealth/utils/GraphUtils.kt
index c80c759..2027296 100644
--- a/app/src/main/java/com/dzeio/openhealth/utils/GraphUtils.kt
+++ b/app/src/main/java/com/dzeio/openhealth/utils/GraphUtils.kt
@@ -1,35 +1,34 @@
package com.dzeio.openhealth.utils
-import android.graphics.Color
import com.github.mikephil.charting.charts.BarChart
import com.github.mikephil.charting.charts.BarLineChartBase
-import com.github.mikephil.charting.charts.Chart
import com.github.mikephil.charting.charts.LineChart
import com.github.mikephil.charting.components.AxisBase
import com.github.mikephil.charting.components.Description
import com.github.mikephil.charting.components.XAxis
import com.github.mikephil.charting.data.BarLineScatterCandleBubbleData
-import com.github.mikephil.charting.data.ChartData
import com.github.mikephil.charting.data.Entry
-import com.github.mikephil.charting.data.LineData
import com.github.mikephil.charting.formatter.ValueFormatter
import com.github.mikephil.charting.interfaces.datasets.IBarLineScatterCandleBubbleDataSet
-import com.github.mikephil.charting.interfaces.datasets.IDataSet
-import com.google.android.material.color.MaterialColors
import java.text.SimpleDateFormat
import java.util.*
object GraphUtils {
- fun lineChartSetup(chart: LineChart, mainColor: Int) {
- barLineChartSetup(chart, mainColor)
+ fun lineChartSetup(chart: LineChart, mainColor: Int, textColor: Int) {
+ barLineChartSetup(chart, mainColor, textColor)
}
- fun barChartSetup(chart: BarChart, mainColor: Int) {
- barLineChartSetup(chart, mainColor)
+ fun barChartSetup(chart: BarChart, mainColor: Int, textColor: Int) {
+ barLineChartSetup(chart, mainColor, textColor)
}
- fun >?> barLineChartSetup(chart: BarLineChartBase, mainColor: Int) {
+ private fun >?> barLineChartSetup(
+ chart: BarLineChartBase,
+ mainColor: Int,
+ textColor: Int
+ ) {
+
chart.apply {
// Setup
@@ -49,18 +48,25 @@ object GraphUtils {
position = XAxis.XAxisPosition.BOTTOM
setDrawGridLines(false)
setLabelCount(3, true)
- textColor = Color.WHITE
+ this.textColor = textColor
+ //setDrawGridLines(false)
+ //setDrawZeroLine(false)
+ setDrawAxisLine(false)
+ disableGridDashedLine()
+ invalidateOutline()
}
axisLeft.apply {
axisLineColor = mainColor
- textColor = Color.WHITE
+ this.textColor = textColor
+// setDrawZeroLine(false)
setLabelCount(0, true)
+ setDrawGridLines(false)
}
axisRight.apply {
- textColor = Color.WHITE
+ this.textColor = textColor
}
- setNoDataTextColor(Color.WHITE)
+ setNoDataTextColor(textColor)
isAutoScaleMinMaxEnabled = true
@@ -70,8 +76,8 @@ object GraphUtils {
description = Description().apply { isEnabled = false }
isScaleXEnabled = true
setPinchZoom(false)
- //setDrawGridBackground(false)
- //setDrawBorders(false)
+ setDrawGridBackground(false)
+ setDrawBorders(false)
}
}
}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_outline_hexagon_24.xml b/app/src/main/res/drawable/ic_outline_hexagon_24.xml
index c36a3e4..e27f182 100644
--- a/app/src/main/res/drawable/ic_outline_hexagon_24.xml
+++ b/app/src/main/res/drawable/ic_outline_hexagon_24.xml
@@ -2,7 +2,8 @@
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
- android:viewportHeight="24">
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
-
-
+
+
+
+
+
+
-
+
+
-
-
-
-
-
-
+ app:layout_constraintLeft_toLeftOf="parent"
+ app:layout_constraintRight_toRightOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
-
- app:layout_constraintLeft_toLeftOf="parent"
- app:layout_constraintRight_toRightOf="parent"
- app:layout_constraintTop_toTopOf="parent"
+
- app:defaultNavHost="true"
- app:navGraph="@navigation/mobile_navigation" />
-
-
-
-
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/dialog_water_edit_water.xml b/app/src/main/res/layout/dialog_water_edit_water.xml
index 6862567..a78d44d 100644
--- a/app/src/main/res/layout/dialog_water_edit_water.xml
+++ b/app/src/main/res/layout/dialog_water_edit_water.xml
@@ -1,5 +1,6 @@
@@ -8,8 +9,20 @@
android:id="@+id/editTextNumber"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:layout_marginTop="32dp"
android:ems="10"
android:inputType="number"
- tools:layout_editor_absoluteX="101dp"
- tools:layout_editor_absoluteY="107dp" />
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent" />
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_extension.xml b/app/src/main/res/layout/fragment_extension.xml
new file mode 100644
index 0000000..d5f2a80
--- /dev/null
+++ b/app/src/main/res/layout/fragment_extension.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_extensions.xml b/app/src/main/res/layout/fragment_extensions.xml
index 1db13a2..a4d34d8 100644
--- a/app/src/main/res/layout/fragment_extensions.xml
+++ b/app/src/main/res/layout/fragment_extensions.xml
@@ -1,122 +1,9 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
+ android:id="@+id/list"
+ tools:listitem="@layout/layout_extension_item"
+ tools:context=".ui.extensions.ExtensionsFragment" />
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml
index ebf6d85..24c0506 100644
--- a/app/src/main/res/layout/fragment_home.xml
+++ b/app/src/main/res/layout/fragment_home.xml
@@ -39,8 +39,8 @@
style="@style/TextAppearance.Material3.TitleMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:text="Weight"
- android:layout_weight="1" />
+ android:layout_weight="1"
+ android:text="Weight" />
+ app:layout_constraintTop_toTopOf="parent">
diff --git a/app/src/main/res/layout/fragment_list_weight.xml b/app/src/main/res/layout/fragment_list_weight.xml
index c53d1bb..4974c2b 100644
--- a/app/src/main/res/layout/fragment_list_weight.xml
+++ b/app/src/main/res/layout/fragment_list_weight.xml
@@ -10,5 +10,5 @@
android:layout_height="wrap_content"
android:padding="16dp"
- tools:listitem="@layout/layout_item_weight"
+ tools:listitem="@layout/layout_item_list"
tools:context=".ui.weight.ListWeightFragment" />
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_main_water_home.xml b/app/src/main/res/layout/fragment_main_water_home.xml
index eef3ebf..d8e49b1 100644
--- a/app/src/main/res/layout/fragment_main_water_home.xml
+++ b/app/src/main/res/layout/fragment_main_water_home.xml
@@ -56,7 +56,6 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
-
- tools:listitem="@layout/layout_item_weight" />
+ tools:listitem="@layout/layout_item_list" />
diff --git a/app/src/main/res/layout/layout_extension_item.xml b/app/src/main/res/layout/layout_extension_item.xml
new file mode 100644
index 0000000..cbd65d3
--- /dev/null
+++ b/app/src/main/res/layout/layout_extension_item.xml
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_item_weight.xml b/app/src/main/res/layout/layout_item_weight.xml
deleted file mode 100644
index d850e1d..0000000
--- a/app/src/main/res/layout/layout_item_weight.xml
+++ /dev/null
@@ -1,59 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/navigation/mobile_navigation.xml b/app/src/main/res/navigation/mobile_navigation.xml
index c648752..5aae0db 100644
--- a/app/src/main/res/navigation/mobile_navigation.xml
+++ b/app/src/main/res/navigation/mobile_navigation.xml
@@ -46,12 +46,8 @@
android:label="@string/menu_import"
tools:layout="@layout/fragment_extensions" >
+ android:id="@+id/action_nav_extensions_to_nav_extension"
+ app:destination="@id/nav_extension" />
-
-
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index e1aca61..d435a88 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -16,4 +16,5 @@
Water Intake
Water Intake
Extensions
+ Imports are done at app startup\nExports are done when new inputs are give to the app
\ No newline at end of file
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
index 854d44b..8512bac 100644
--- a/app/src/main/res/values/themes.xml
+++ b/app/src/main/res/values/themes.xml
@@ -1,29 +1,10 @@
-
-
+
-
-
-
-
-
\ No newline at end of file
diff --git a/fastlane/Appfile b/fastlane/Appfile
new file mode 100644
index 0000000..0d200ce
--- /dev/null
+++ b/fastlane/Appfile
@@ -0,0 +1,2 @@
+json_key_file("./fastlane_secret_keys.json") # Path to the json secret file - Follow https://docs.fastlane.tools/actions/supply/#setup to get one
+package_name("com.dzeio.openhealth") # e.g. com.krausefx.app
diff --git a/fastlane/Fastfile b/fastlane/Fastfile
new file mode 100644
index 0000000..2cd81af
--- /dev/null
+++ b/fastlane/Fastfile
@@ -0,0 +1,47 @@
+opt_out_usage
+# This file contains the fastlane.tools configuration
+# You can find the documentation at https://docs.fastlane.tools
+#
+# For a list of all available actions, check out
+#
+# https://docs.fastlane.tools/actions
+#
+# For a list of all available plugins, check out
+#
+# https://docs.fastlane.tools/plugins/available-plugins
+#
+
+# Uncomment the line if you want fastlane to automatically update itself
+# update_fastlane
+
+default_platform(:android)
+
+platform :android do
+ desc "Runs all the tests"
+ lane :test do
+ gradle(task: "test")
+ end
+
+ desc "Assemble Release Variant"
+ lane :build do
+ gradle(task: "clean assembleRelease")
+ end
+
+
+ desc "Submit a new Beta Build to Crashlytics Beta"
+ lane :beta do
+ gradle(task: "clean assembleRelease")
+ crashlytics
+
+ # sh "your_script.sh"
+ # You can also use other beta testing services here
+ end
+
+ desc "Deploy a new version to the Google Play"
+ lane :deploy do
+ gradle(task: "clean assembleRelease")
+ upload_to_play_store(
+ track: ""
+ )
+ end
+end
diff --git a/fastlane/README.md b/fastlane/README.md
new file mode 100644
index 0000000..6437248
--- /dev/null
+++ b/fastlane/README.md
@@ -0,0 +1,56 @@
+fastlane documentation
+----
+
+# Installation
+
+Make sure you have the latest version of the Xcode command line tools installed:
+
+```sh
+xcode-select --install
+```
+
+For _fastlane_ installation instructions, see [Installing _fastlane_](https://docs.fastlane.tools/#installing-fastlane)
+
+# Available Actions
+
+## Android
+
+### android test
+
+```sh
+[bundle exec] fastlane android test
+```
+
+Runs all the tests
+
+### android build
+
+```sh
+[bundle exec] fastlane android build
+```
+
+Assemble Release Variant
+
+### android beta
+
+```sh
+[bundle exec] fastlane android beta
+```
+
+Submit a new Beta Build to Crashlytics Beta
+
+### android deploy
+
+```sh
+[bundle exec] fastlane android deploy
+```
+
+Deploy a new version to the Google Play
+
+----
+
+This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run.
+
+More information about _fastlane_ can be found on [fastlane.tools](https://fastlane.tools).
+
+The documentation of _fastlane_ can be found on [docs.fastlane.tools](https://docs.fastlane.tools).
diff --git a/fastlane/metadata/android/en-GB/full_description.txt b/fastlane/metadata/android/en-GB/full_description.txt
new file mode 100644
index 0000000..e69de29
diff --git a/fastlane/metadata/android/en-GB/short_description.txt b/fastlane/metadata/android/en-GB/short_description.txt
new file mode 100644
index 0000000..e69de29
diff --git a/fastlane/metadata/android/en-GB/title.txt b/fastlane/metadata/android/en-GB/title.txt
new file mode 100644
index 0000000..1c28f05
--- /dev/null
+++ b/fastlane/metadata/android/en-GB/title.txt
@@ -0,0 +1 @@
+Open Health
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-GB/video.txt b/fastlane/metadata/android/en-GB/video.txt
new file mode 100644
index 0000000..e69de29
diff --git a/fastlane/report.xml b/fastlane/report.xml
new file mode 100644
index 0000000..2143662
--- /dev/null
+++ b/fastlane/report.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/fastlane_secret_keys.json b/fastlane_secret_keys.json
new file mode 100644
index 0000000..480aa39
--- /dev/null
+++ b/fastlane_secret_keys.json
@@ -0,0 +1,12 @@
+{
+ "type": "service_account",
+ "project_id": "openhealth-334622",
+ "private_key_id": "b7fe3bc2980ea797341e966d571c3c552138ef2b",
+ "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC6eAWYc1Qb2DBR\nBwuKj4Z6oXDvWeiHueSJT8HyMNHjyRDRiOgZTj1APvW9P8tsamIAhlB04gpdI73b\nUmp2wM9JbzDRyJC82x/IkfciJbXvWLa32fddWlxf2lfosWHNiGxfiIU7dqcHvaVL\niIVmNg0HB+1NIA30jn0M/EBEOf/v7u9fclGV5I1PQJdfSebWMrC1wPVHeWgawFef\nqTust152fsVKEkF42EQgMs44GYUdQnPus+bW9/JyPxDmcehVJmLTwjKD7DFwDID5\nYWnthvlQItvnxPCUB6UUxnO7CtV2eDzQEiwwCS6BWj76ydWeh2azbJALxtH48qFZ\n+REMHKSJAgMBAAECggEAXLgsdBs4oeXUVJ4Pr5Thdh3LhcCrnr2g9WQa2L5Mx5ql\nicMtQdQFIeqMj89ma+DUHVWsMQpqw9hvYdyvwp/qEqY+3LmBut6shbOK8shUmJCA\nvpeb6Cfz0dfEqZh2PNiOpsxAD4rW0EMNK6tVRbcvsCTRau268rVdWfUeUa6TZG6N\nHikyOx1klUzTO+vrL5CZFa2l7yzQNsUcljUC4fllpTMPqv/04zbAkzF9677G8zmQ\nXokBtCYlWVlzf5VWW4n2EnS76YnZOMeADiY6vdUlp9PyC6hbPHa0T8/JHBhANtlL\ny0NhkTENjRMqW+LJfgjzwIwIH/1tx4eqVJJnUNfEDwKBgQDvIqhAQ3VsRD3Vc9mN\nZdPkUgLjrbjkLKYL7A8szh1TQMrJatSYn4NZQptm8jBYqb66vAaJyaEDevt0WPv/\n9SQRIZvYaPZRsMoLo4M/xfzwY8dK/Q58r4e3E98LF0ekJJ3nRfhsguNK5Fr0Q4UB\nqFg5Nj4U/mG4bW0EO7qajPvS2wKBgQDHnoZC68EWOFzk9VsHKF+0XlDzgKmj5QLS\n45mL+ApbWYUT/iH5CA+IcelstRmSTyVpA2fzsXSp5N8TFYohAh5H54H7lR+fGNZV\nMT3ycNmjMvXs1yIJ0UbxFzmw08w1am34cbP+hqFVM+uvMtzSiol9RK8+SKiQI9eE\n5Z0jOjZ5awKBgDFIESh9Pnu7bIrKvzDWpV5OUG4fZRUQ5n9afJ4dNAnuNlxf+cQi\nS21fvqruimwbP0U4bpiCxv3yoFOP6w8KtA4bwQROTUT0jA7t+aRw5vmbdnzLveqQ\nOgXOwI6Gk6sOKMR6tQGXz8OlX+Eq8QQwb04LEaw96GGbm3Xd4UzsdRE1AoGAUiH6\nkgxYbOER7664HnDRN/BalGYK5oFysPyuj7Wl5UInDDvTFJjpczWTWoQFGnrwJI4f\nNlh8bO7bjgmdxMkPVnx9sdsAoMBiZ7kUCO2/znNIVoOJ4Mo3yzjIJuZuLkg1KTT3\nXzFbrifnwDVIQGR5/43EIPdaS7xDj8294uGvyjMCgYA+JewmU5+PxxNB4ez/+o2T\n6GyIFtPvBsIQpZaP4Ydo9ey//rfYehZ/NIjxtcKyoNOdHjYpMBwnwjXO7r00HB39\nrJ7ZrjY0AVyc9v603Xd7ZzhpQQvUNiYysXnmge2OKqJknL5En+SsIR7TZ+1LLKtn\nQD1seeucMVgNa8Vod40zlw==\n-----END PRIVATE KEY-----\n",
+ "client_email": "fastlane@openhealth-334622.iam.gserviceaccount.com",
+ "client_id": "105571318626472827244",
+ "auth_uri": "https://accounts.google.com/o/oauth2/auth",
+ "token_uri": "https://oauth2.googleapis.com/token",
+ "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
+ "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/fastlane%40openhealth-334622.iam.gserviceaccount.com"
+}
diff --git a/keystore.properties b/keystore.properties
new file mode 100644
index 0000000..484e01a
--- /dev/null
+++ b/keystore.properties
@@ -0,0 +1,4 @@
+storeFile=../upload_key.jks
+keyAlias=release_key
+keyPassword=Babacarflo22
+storePassword=Babacarflo22
\ No newline at end of file
diff --git a/private_key.pepk b/private_key.pepk
new file mode 100644
index 0000000..46150cf
Binary files /dev/null and b/private_key.pepk differ
diff --git a/upload_key.jks b/upload_key.jks
new file mode 100644
index 0000000..ab9a426
Binary files /dev/null and b/upload_key.jks differ