From 92667ef0b45cee534207c4e05656dacf3ca27c82 Mon Sep 17 00:00:00 2001 From: Avior Date: Thu, 23 Dec 2021 23:20:26 +0000 Subject: [PATCH 1/6] Create gradlew_recursive.sh --- .github/scripts/gradlew_recursive.sh | 37 ++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 .github/scripts/gradlew_recursive.sh 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 + From c3cafb44679f9bba2be9886ff5829d5ae50f9a15 Mon Sep 17 00:00:00 2001 From: Avior Date: Thu, 23 Dec 2021 23:23:15 +0000 Subject: [PATCH 2/6] Update build.yml --- .github/workflows/build.yml | 56 ++++++++++++++++++++++++------------- 1 file changed, 37 insertions(+), 19 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 863d685..536a6d2 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,31 @@ 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: 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 From d0ff6fa7d15bf960c9be64849c16f721cf958240 Mon Sep 17 00:00:00 2001 From: Avior Date: Fri, 24 Dec 2021 00:27:36 +0100 Subject: [PATCH 3/6] u From 1db57a40ac081c23ad36afee4b48b8c99f897d46 Mon Sep 17 00:00:00 2001 From: Avior Date: Thu, 23 Dec 2021 23:31:36 +0000 Subject: [PATCH 4/6] Update build.yml --- .github/workflows/build.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 536a6d2..59e4be4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -33,6 +33,8 @@ jobs: 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 From f47248732c5340506dda34e25e18aeb958e42d82 Mon Sep 17 00:00:00 2001 From: Avior Date: Mon, 3 Jan 2022 00:19:17 +0100 Subject: [PATCH 5/6] Update Signed-off-by: Avior --- .gitignore | 12 + .idea/misc.xml | 12 +- Gemfile | 3 + Gemfile.lock | 214 ++++++++++ app/build.gradle | 58 ++- app/src/debug/ic_launcher-playstore.png | Bin 0 -> 13376 bytes .../drawable-v24/ic_launcher_foreground.xml | 47 +++ app/src/debug/res/drawable/ic_logo_app.xml | 42 ++ .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + app/src/debug/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 2411 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 4358 bytes app/src/debug/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 1428 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 2686 bytes .../debug/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 3109 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 5703 bytes .../debug/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 3910 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 9416 bytes .../debug/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 5776 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 13275 bytes .../res/values/ic_launcher_background.xml | 4 + app/src/debug/res/values/strings.xml | 4 + app/src/main/AndroidManifest.xml | 4 +- .../java/com/dzeio/openhealth/Application.kt | 8 + .../java/com/dzeio/openhealth/MainActivity.kt | 31 +- .../openhealth/adapters/ExtensionAdapter.kt | 28 ++ .../dzeio/openhealth/adapters/WaterAdapter.kt | 5 +- .../openhealth/adapters/WeightAdapter.kt | 13 +- .../dzeio/openhealth/data/water/WaterDao.kt | 6 +- .../dzeio/openhealth/data/weight/Weight.kt | 5 +- .../dzeio/openhealth/extensions/DataType.kt | 16 - .../dzeio/openhealth/extensions/Extension.kt | 99 ++++- .../openhealth/extensions/ExtensionFactory.kt | 16 + .../dzeio/openhealth/extensions/GoogleFit.kt | 220 +++++++---- .../openhealth/extensions/GoogleFit.kt.old | 374 ------------------ .../extensions/samsunghealth/SamsungHealth.kt | 24 +- .../interfaces/NotificationChannels.kt | 4 +- .../ui/extension/ExtensionFragment.kt | 59 +++ .../ui/extension/ExtensionViewModel.kt | 30 ++ .../ui/extensions/ExtensionsFragment.kt | 107 ++--- .../dzeio/openhealth/ui/home/HomeFragment.kt | 149 +++---- .../dzeio/openhealth/ui/home/HomeViewModel.kt | 30 +- .../openhealth/ui/water/EditWaterDialog.kt | 50 ++- .../openhealth/ui/water/WaterHomeFragment.kt | 15 +- .../com/dzeio/openhealth/utils/DrawUtils.kt | 50 ++- .../com/dzeio/openhealth/utils/GraphUtils.kt | 40 +- .../res/drawable/ic_outline_hexagon_24.xml | 3 +- app/src/main/res/layout/activity_main.xml | 69 ++-- .../res/layout/dialog_water_edit_water.xml | 17 +- .../main/res/layout/fragment_extension.xml | 39 ++ .../main/res/layout/fragment_extensions.xml | 121 +----- app/src/main/res/layout/fragment_home.xml | 13 +- .../main/res/layout/fragment_list_weight.xml | 2 +- .../res/layout/fragment_main_water_home.xml | 3 +- .../main/res/layout/layout_extension_item.xml | 63 +++ .../main/res/layout/layout_item_weight.xml | 59 --- .../main/res/navigation/mobile_navigation.xml | 37 +- app/src/main/res/values/strings.xml | 1 + app/src/main/res/values/themes.xml | 23 +- fastlane/Appfile | 2 + fastlane/Fastfile | 47 +++ fastlane/README.md | 56 +++ .../android/en-GB/full_description.txt | 0 .../android/en-GB/short_description.txt | 0 fastlane/metadata/android/en-GB/title.txt | 1 + fastlane/metadata/android/en-GB/video.txt | 0 fastlane/report.xml | 25 ++ fastlane_secret_keys.json | 12 + keystore.properties | 4 + private_key.pepk | Bin 0 -> 1776 bytes upload_key.jks | Bin 0 -> 2617 bytes 71 files changed, 1405 insertions(+), 981 deletions(-) create mode 100644 Gemfile create mode 100644 Gemfile.lock create mode 100644 app/src/debug/ic_launcher-playstore.png create mode 100644 app/src/debug/res/drawable-v24/ic_launcher_foreground.xml create mode 100644 app/src/debug/res/drawable/ic_logo_app.xml create mode 100644 app/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 app/src/debug/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 app/src/debug/res/mipmap-hdpi/ic_launcher.png create mode 100644 app/src/debug/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 app/src/debug/res/mipmap-mdpi/ic_launcher.png create mode 100644 app/src/debug/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 app/src/debug/res/mipmap-xhdpi/ic_launcher.png create mode 100644 app/src/debug/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 app/src/debug/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 app/src/debug/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 app/src/debug/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 app/src/debug/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 app/src/debug/res/values/ic_launcher_background.xml create mode 100644 app/src/debug/res/values/strings.xml create mode 100644 app/src/main/java/com/dzeio/openhealth/adapters/ExtensionAdapter.kt delete mode 100644 app/src/main/java/com/dzeio/openhealth/extensions/DataType.kt create mode 100644 app/src/main/java/com/dzeio/openhealth/extensions/ExtensionFactory.kt delete mode 100644 app/src/main/java/com/dzeio/openhealth/extensions/GoogleFit.kt.old create mode 100644 app/src/main/java/com/dzeio/openhealth/ui/extension/ExtensionFragment.kt create mode 100644 app/src/main/java/com/dzeio/openhealth/ui/extension/ExtensionViewModel.kt create mode 100644 app/src/main/res/layout/fragment_extension.xml create mode 100644 app/src/main/res/layout/layout_extension_item.xml delete mode 100644 app/src/main/res/layout/layout_item_weight.xml create mode 100644 fastlane/Appfile create mode 100644 fastlane/Fastfile create mode 100644 fastlane/README.md create mode 100644 fastlane/metadata/android/en-GB/full_description.txt create mode 100644 fastlane/metadata/android/en-GB/short_description.txt create mode 100644 fastlane/metadata/android/en-GB/title.txt create mode 100644 fastlane/metadata/android/en-GB/video.txt create mode 100644 fastlane/report.xml create mode 100644 fastlane_secret_keys.json create mode 100644 keystore.properties create mode 100644 private_key.pepk create mode 100644 upload_key.jks 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 0000000000000000000000000000000000000000..70f398c6d5bf4f42d2718cc1cd76bcb2160edf17 GIT binary patch literal 13376 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 Z*)itR%PyW=k7|OC7c8BBWp2pV{{zY|Pb&Zb literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..1321a4771ce4a0f8d32c3b31467a952d93b29855 GIT binary patch literal 2411 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

8NJCxS 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 A>;M1& literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..e9628671a4a0e3d56f2c05d3b25d28b91730539f GIT binary patch literal 1428 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 WxR})Zp@?t)0p=!F=prLb`2PU^nCfi+ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..24fde3b1405e298bc9bb9e8c94e33004dcbc529a GIT binary patch literal 5703 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..b3bffc04aad87ab741de792202b1d0435891447a GIT binary patch literal 3910 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..32e2771343e548b7a22296e126f609f29e5fd944 GIT binary patch literal 9416 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..693ce32c653fa0c54a21cd1d714b6d1075ef2ac7 GIT binary patch literal 5776 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..8c77946c3176ebcd9c5d942cf1dfe0237d0f81b9 GIT binary patch literal 13275 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 + + #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 @@ + + + + + +