1
0
mirror of https://github.com/dzeiocom/OpenHealth.git synced 2025-04-23 03:12:14 +00:00

refactor: Create gradlew_recursive.sh (#8)

This commit is contained in:
Florian Bouillon 2022-01-03 00:33:25 +01:00 committed by GitHub
parent be6c0a2b8e
commit aee0022f21
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
73 changed files with 1481 additions and 1000 deletions

37
.github/scripts/gradlew_recursive.sh vendored Normal file
View File

@ -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

View File

@ -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 name: Android CI
on: on:
@ -7,8 +21,8 @@ on:
branches: [ master ] branches: [ master ]
jobs: jobs:
build:
build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@ -19,15 +33,21 @@ jobs:
java-version: '11' java-version: '11'
distribution: 'adopt' distribution: 'adopt'
cache: gradle cache: gradle
- name: Prepare project
- name: Grant execute permission for gradlew run: chmod +x .github/scripts/gradlew_recursive.sh
run: chmod +x gradlew - name: Build project
- name: Build with Gradle run: .github/scripts/gradlew_recursive.sh assembleDebug
run: ./gradlew build - 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 - name: ktlint
uses: ScaCap/action-ktlint@master uses: ScaCap/action-ktlint@master
with: with:
github_token: ${{ secrets.github_token }} github_token: ${{ secrets.github_token }}
android: true android: true
reporter: github-pr-review # Change reporter reporter: github-pr-review # Change reporter

12
.gitignore vendored
View File

@ -8,3 +8,15 @@
.externalNativeBuild .externalNativeBuild
.cxx .cxx
local.properties local.properties
# keystore
keystore.properties
private_key.pepk
upload_key.jks
# Fastlane
fastlane_secret_keys.json
# App
/app/debug
/app/release

12
.idea/misc.xml generated
View File

@ -13,28 +13,36 @@
<entry key="..\:/Git/Dzeio/OpenHealth/app/src/main/res/menu/activity_main_drawer.xml" value="0.10989583333333333" /> <entry key="..\:/Git/Dzeio/OpenHealth/app/src/main/res/menu/activity_main_drawer.xml" value="0.10989583333333333" />
<entry key="..\:/Git/Dzeio/OpenHealth/app/src/main/res/menu/fullscreen_dialog.xml" value="0.31197916666666664" /> <entry key="..\:/Git/Dzeio/OpenHealth/app/src/main/res/menu/fullscreen_dialog.xml" value="0.31197916666666664" />
<entry key="..\:/Git/Dzeio/OpenHealth/app/src/main/res/menu/main.xml" value="0.1875" /> <entry key="..\:/Git/Dzeio/OpenHealth/app/src/main/res/menu/main.xml" value="0.1875" />
<entry key="..\:/git/Dzeio/OpenHealth/app/src/debug/res/drawable/ic_logo_app.xml" value="0.3645" />
<entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/drawable-v24/ic_launcher_foreground.xml" value="0.3645" />
<entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/drawable/half_circle.xml" value="0.421" /> <entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/drawable/half_circle.xml" value="0.421" />
<entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/drawable/ic_baseline_add_24.xml" value="0.143" />
<entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/drawable/ic_ellipse_2.xml" value="0.4165" /> <entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/drawable/ic_ellipse_2.xml" value="0.4165" />
<entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/drawable/ic_logo_app.xml" value="0.163" /> <entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/drawable/ic_logo_app.xml" value="0.163" />
<entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/layout/activity_main.xml" value="0.5" /> <entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/drawable/ic_outline_hexagon_24.xml" value="0.143" />
<entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/layout/activity_main.xml" value="0.1" />
<entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/layout/app_bar_main.xml" value="0.5192107995846313" /> <entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/layout/app_bar_main.xml" value="0.5192107995846313" />
<entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/layout/content_main.xml" value="0.5135869565217391" /> <entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/layout/content_main.xml" value="0.5135869565217391" />
<entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/layout/dialog_add_weight.xml" value="0.2604166666666667" /> <entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/layout/dialog_add_weight.xml" value="0.2604166666666667" />
<entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/layout/dialog_edit_weight.xml" value="0.33" /> <entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/layout/dialog_edit_weight.xml" value="0.33" />
<entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/layout/dialog_register_weight.xml" value="0.5" /> <entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/layout/dialog_register_weight.xml" value="0.5" />
<entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/layout/dialog_water_edit_water.xml" value="0.2674772036474164" /> <entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/layout/dialog_water_edit_water.xml" value="0.2674772036474164" />
<entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/layout/fragment_extension.xml" value="0.35833333333333334" />
<entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/layout/fragment_extensions.xml" value="0.55" /> <entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/layout/fragment_extensions.xml" value="0.55" />
<entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/layout/fragment_gallery.xml" value="0.3109375" /> <entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/layout/fragment_gallery.xml" value="0.3109375" />
<entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/layout/fragment_home.xml" value="0.6845493562231759" /> <entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/layout/fragment_home.xml" value="0.5897435897435898" />
<entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/layout/fragment_import.xml" value="0.55" /> <entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/layout/fragment_import.xml" value="0.55" />
<entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/layout/fragment_list_weight.xml" value="0.33" /> <entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/layout/fragment_list_weight.xml" value="0.33" />
<entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/layout/fragment_main_water_home.xml" value="0.225" /> <entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/layout/fragment_main_water_home.xml" value="0.225" />
<entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/layout/fragment_water_home.xml" value="0.55" /> <entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/layout/fragment_water_home.xml" value="0.55" />
<entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/layout/layout_extension_item.xml" value="0.39947916666666666" />
<entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/layout/layout_item_list.xml" value="1.1" />
<entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/layout/layout_item_weight.xml" value="0.5" /> <entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/layout/layout_item_weight.xml" value="0.5" />
<entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/layout/nav_header_main.xml" value="0.3109375" /> <entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/layout/nav_header_main.xml" value="0.3109375" />
<entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/menu/activity_main_drawer.xml" value="0.39947916666666666" /> <entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/menu/activity_main_drawer.xml" value="0.39947916666666666" />
<entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/menu/fullscreen_dialog.xml" value="0.40185185185185185" /> <entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/menu/fullscreen_dialog.xml" value="0.40185185185185185" />
<entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/menu/main.xml" value="0.39947916666666666" /> <entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/menu/main.xml" value="0.39947916666666666" />
<entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml" value="0.3645" />
<entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/xml/preferences.xml" value="0.55" /> <entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/xml/preferences.xml" value="0.55" />
</map> </map>
</option> </option>

3
Gemfile Normal file
View File

@ -0,0 +1,3 @@
source "https://rubygems.org"
gem "fastlane"

214
Gemfile.lock Normal file
View File

@ -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

View File

@ -9,36 +9,87 @@ plugins {
} }
android { 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 compileSdk 31
defaultConfig { defaultConfig {
// App ID
applicationId "com.dzeio.openhealth" applicationId "com.dzeio.openhealth"
// Android 5 Lollipop
minSdk 21 minSdk 21
// Android 12
targetSdk 31 targetSdk 31
versionCode 1 versionCode 1
versionName "1.0"
// Semantic Versioning
versionName "1.0.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
} }
buildTypes { buildTypes {
release { release {
minifyEnabled false minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 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 { compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8
} }
kotlinOptions { kotlinOptions {
jvmTarget = '1.8' jvmTarget = '1.8'
} }
buildFeatures { buildFeatures {
viewBinding true viewBinding true
dataBinding true dataBinding true
} }
} }
dependencies { dependencies {
@ -81,6 +132,7 @@ dependencies {
// Samsung Health // Samsung Health
implementation files('libs/samsung-health-data-1.5.0.aar') implementation files('libs/samsung-health-data-1.5.0.aar')
implementation "com.google.code.gson:gson:2.8.9"
// ROOM // ROOM
def room_version = "2.4.0" def room_version = "2.4.0"

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -0,0 +1,47 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<group android:scaleX="0.15074074"
android:scaleY="0.15074074"
android:translateX="29.58"
android:translateY="29.58">
<path
android:pathData="M199.975,232V199.975H312V124.025H199.975V12H124.025V124.025H12V199.975H124.025V312H199.975V286.5"
android:strokeWidth="23"
android:fillColor="#00000000"
android:strokeLineCap="square">
<aapt:attr name="android:strokeColor">
<gradient
android:startY="12"
android:startX="12"
android:endY="312"
android:endX="312"
android:type="linear">
<item android:offset="0" android:color="#FF80E27E"/>
<item android:offset="1" android:color="#FF4CAF50"/>
</gradient>
</aapt:attr>
</path>
<path
android:pathData="M275,161.5H48M161.5,48V275"
android:strokeLineJoin="round"
android:strokeWidth="13"
android:fillColor="#00000000"
android:strokeLineCap="square">
<aapt:attr name="android:strokeColor">
<gradient
android:startY="24.7418"
android:startX="22.416"
android:endY="301.98"
android:endX="298.723"
android:type="linear">
<item android:offset="0" android:color="#FF80E27E"/>
<item android:offset="1" android:color="#FF4CAF50"/>
</gradient>
</aapt:attr>
</path>
</group>
</vector>

View File

@ -0,0 +1,42 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="324dp"
android:height="324dp"
android:viewportWidth="324"
android:viewportHeight="324">
<path
android:pathData="M199.975,232V199.975H312V124.025H199.975V12H124.025V124.025H12V199.975H124.025V312H199.975V286.5"
android:strokeWidth="23"
android:fillColor="#00000000"
android:strokeLineCap="square">
<aapt:attr name="android:strokeColor">
<gradient
android:startY="12"
android:startX="12"
android:endY="312"
android:endX="312"
android:type="linear">
<item android:offset="0" android:color="#FF80E27E"/>
<item android:offset="1" android:color="#FF4CAF50"/>
</gradient>
</aapt:attr>
</path>
<path
android:pathData="M275,161.5H48M161.5,48V275"
android:strokeLineJoin="round"
android:strokeWidth="13"
android:fillColor="#00000000"
android:strokeLineCap="square">
<aapt:attr name="android:strokeColor">
<gradient
android:startY="24.7418"
android:startX="22.416"
android:endY="301.98"
android:endX="298.723"
android:type="linear">
<item android:offset="0" android:color="#FF80E27E"/>
<item android:offset="1" android:color="#FF4CAF50"/>
</gradient>
</aapt:attr>
</path>
</vector>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#FFFFFF</color>
</resources>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">OpenHealth - Debug</string>
</resources>

View File

@ -6,8 +6,8 @@
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- Phone Services --> <!-- Phone Sensors -->
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION " /> <uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
<!-- Samsung Health--> <!-- Samsung Health-->
<queries> <queries>

View File

@ -1,6 +1,7 @@
package com.dzeio.openhealth package com.dzeio.openhealth
import android.app.Application import android.app.Application
import com.google.android.material.color.DynamicColors
import dagger.hilt.android.HiltAndroidApp import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp @HiltAndroidApp
@ -8,4 +9,11 @@ class Application : Application() {
companion object { companion object {
const val TAG = "OpenHealth" const val TAG = "OpenHealth"
} }
override fun onCreate() {
super.onCreate()
// Android Dynamics Colors
DynamicColors.applyToActivitiesIfAvailable(this)
}
} }

View File

@ -10,22 +10,17 @@ import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.fragment.NavHostFragment import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.fragment.findNavController
import androidx.navigation.ui.AppBarConfiguration import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.navigateUp import androidx.navigation.ui.navigateUp
import androidx.navigation.ui.setupActionBarWithNavController import androidx.navigation.ui.setupActionBarWithNavController
import androidx.navigation.ui.setupWithNavController
import androidx.work.WorkManager import androidx.work.WorkManager
import com.dzeio.openhealth.core.BaseActivity import com.dzeio.openhealth.core.BaseActivity
import com.dzeio.openhealth.databinding.ActivityMainBinding import com.dzeio.openhealth.databinding.ActivityMainBinding
import com.dzeio.openhealth.interfaces.NotificationChannels import com.dzeio.openhealth.interfaces.NotificationChannels
import com.dzeio.openhealth.services.WaterReminderService import com.dzeio.openhealth.services.WaterReminderService
import com.dzeio.openhealth.ui.home.HomeFragmentDirections import com.dzeio.openhealth.ui.home.HomeFragmentDirections
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint @AndroidEntryPoint
@ -45,6 +40,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
val navHostFragment = val navHostFragment =
supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
navController = navHostFragment.navController navController = navHostFragment.navController
appBarConfiguration = AppBarConfiguration( appBarConfiguration = AppBarConfiguration(
@ -69,9 +65,8 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
return true return true
} }
override fun onSupportNavigateUp(): Boolean { override fun onSupportNavigateUp(): Boolean =
return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp() navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
}
override fun onRequestPermissionsResult( override fun onRequestPermissionsResult(
requestCode: Int, requestCode: Int,
@ -94,14 +89,13 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
} }
private fun createNotificationChannel() { 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 = val notificationManager: NotificationManager =
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
for (channel in NotificationChannels.values()) { for (channel in NotificationChannels.values()) {
Log.d("MainActivity", channel.channelName)
try {
notificationManager.createNotificationChannel( notificationManager.createNotificationChannel(
NotificationChannel( NotificationChannel(
channel.id, channel.id,
@ -109,6 +103,9 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
channel.importance channel.importance
) )
) )
} catch (e: Exception) {
Log.e("MainActivity", "Error Creating Notification Channel", e)
}
} }
} }

View File

@ -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<Extension, LayoutExtensionItemBinding>() {
override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> LayoutExtensionItemBinding
get() = LayoutExtensionItemBinding::inflate
var onItemClick: ((weight: Extension) -> Unit)? = null
override fun onBindData(
holder: BaseViewHolder<LayoutExtensionItemBinding>,
item: Extension,
position: Int
) {
holder.binding.name.text = item.name
holder.binding.status.text = item.getStatus()
holder.binding.card.setOnClickListener {
onItemClick?.invoke(item)
}
}
}

View File

@ -1,14 +1,11 @@
package com.dzeio.openhealth.adapters package com.dzeio.openhealth.adapters
import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import com.dzeio.openhealth.core.BaseAdapter import com.dzeio.openhealth.core.BaseAdapter
import com.dzeio.openhealth.core.BaseViewHolder import com.dzeio.openhealth.core.BaseViewHolder
import com.dzeio.openhealth.data.water.Water 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.LayoutItemListBinding
import com.dzeio.openhealth.databinding.LayoutItemWeightBinding
class WaterAdapter() : BaseAdapter<Water, LayoutItemListBinding>() { class WaterAdapter() : BaseAdapter<Water, LayoutItemListBinding>() {
@ -23,7 +20,7 @@ class WaterAdapter() : BaseAdapter<Water, LayoutItemListBinding>() {
position: Int position: Int
) { ) {
holder.binding.value.text = "${item.value}ml" 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 { holder.binding.edit.setOnClickListener {
onItemClick?.invoke(item) onItemClick?.invoke(item)
} }

View File

@ -1,26 +1,25 @@
package com.dzeio.openhealth.adapters package com.dzeio.openhealth.adapters
import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import com.dzeio.openhealth.core.BaseAdapter import com.dzeio.openhealth.core.BaseAdapter
import com.dzeio.openhealth.core.BaseViewHolder import com.dzeio.openhealth.core.BaseViewHolder
import com.dzeio.openhealth.data.weight.Weight import com.dzeio.openhealth.data.weight.Weight
import com.dzeio.openhealth.databinding.LayoutItemWeightBinding import com.dzeio.openhealth.databinding.LayoutItemListBinding
class WeightAdapter() : BaseAdapter<Weight, LayoutItemWeightBinding>() { class WeightAdapter() : BaseAdapter<Weight, LayoutItemListBinding>() {
override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> LayoutItemWeightBinding override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> LayoutItemListBinding
get() = LayoutItemWeightBinding::inflate get() = LayoutItemListBinding::inflate
var onItemClick: ((weight: Weight) -> Unit)? = null var onItemClick: ((weight: Weight) -> Unit)? = null
override fun onBindData( override fun onBindData(
holder: BaseViewHolder<LayoutItemWeightBinding>, holder: BaseViewHolder<LayoutItemListBinding>,
item: Weight, item: Weight,
position: Int position: Int
) { ) {
holder.binding.weight.text = "${item.weight}kg" holder.binding.value.text = "${item.weight}kg"
holder.binding.datetime.text = item.formatTimestamp() holder.binding.datetime.text = item.formatTimestamp()
holder.binding.edit.setOnClickListener { holder.binding.edit.setOnClickListener {
onItemClick?.invoke(item) onItemClick?.invoke(item)

View File

@ -1,14 +1,14 @@
package com.dzeio.openhealth.data.water package com.dzeio.openhealth.data.water
import androidx.room.* import androidx.room.Dao
import androidx.room.Query
import com.dzeio.openhealth.core.BaseDao import com.dzeio.openhealth.core.BaseDao
import dagger.Provides
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@Dao @Dao
interface WaterDao : BaseDao<Water> { interface WaterDao : BaseDao<Water> {
@Query("SELECT * FROM Water ORDER BY timestamp") @Query("SELECT * FROM Water ORDER BY timestamp DESC")
fun getAll(): Flow<List<Water>> fun getAll(): Flow<List<Water>>
@Query("SELECT * FROM Water where id = :weightId") @Query("SELECT * FROM Water where id = :weightId")

View File

@ -7,8 +7,9 @@ import java.sql.Date
import java.text.DateFormat.getDateInstance import java.text.DateFormat.getDateInstance
@Entity() @Entity()
data class Weight ( data class Weight(
@PrimaryKey(autoGenerate = true) var id: Long = 0, @PrimaryKey(autoGenerate = true)
var id: Long = 0,
var weight: Float = 0f, var weight: Float = 0f,
@ColumnInfo(index = true) @ColumnInfo(index = true)
var timestamp: Long = System.currentTimeMillis(), var timestamp: Long = System.currentTimeMillis(),

View File

@ -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
*/

View File

@ -2,18 +2,103 @@ package com.dzeio.openhealth.extensions
import android.app.Activity import android.app.Activity
import android.content.Intent import android.content.Intent
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.dzeio.openhealth.data.weight.Weight import com.dzeio.openhealth.data.weight.Weight
/**
* Extension Schema
*
* Version: 1.0.0
*/
abstract class Extension { abstract class Extension {
enum class Data { data class ImportState<T>(
WEIGHT, val state: States = States.WIP,
STEPS val list: List<T> = 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<Data> = 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<Data>
/**
* 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<States> {
return MutableLiveData(States.DONE)
}
open fun importWeight(): LiveData<ImportState<Weight>> {
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<States> {
return MutableLiveData(States.DONE)
}
/**
* Activity Events
*/
/** /**
* Same as Activity/Fragment onRequestPermissionResult * Same as Activity/Fragment onRequestPermissionResult
@ -26,8 +111,4 @@ abstract class Extension {
* Same as Activity/Fragment onActivityResult * Same as Activity/Fragment onActivityResult
*/ */
open fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {} open fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {}
open fun <T> import(data: Data, cb: (item: T, end: Boolean) -> Unit) {}
open fun importWeight(callback: (weight: Weight, end: Boolean) -> Unit) {}
} }

View File

@ -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
}
}
}
}
}

View File

@ -7,69 +7,114 @@ import android.content.pm.PackageManager
import android.os.Build import android.os.Build
import android.util.Log import android.util.Log
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.dzeio.openhealth.data.weight.Weight import com.dzeio.openhealth.data.weight.Weight
import com.google.android.gms.auth.api.signin.GoogleSignIn import com.google.android.gms.auth.api.signin.GoogleSignIn
import com.google.android.gms.fitness.Fitness import com.google.android.gms.fitness.Fitness
import com.google.android.gms.fitness.FitnessOptions 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.data.DataType
import com.google.android.gms.fitness.request.DataReadRequest import com.google.android.gms.fitness.request.DataReadRequest
import java.text.DateFormat import java.text.DateFormat
import java.util.* import java.util.*
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
class GoogleFit( class GoogleFit() : Extension() {
private val activity: Activity,
) : Extension() {
companion object { companion object {
const val TAG = "GoogleFitConnector" 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<Data> {
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() private val fitnessOptions = FitnessOptions.builder()
.addDataType(DataType.TYPE_WEIGHT) .addDataType(DataType.TYPE_WEIGHT)
.addDataType(DataType.TYPE_STEP_COUNT_CUMULATIVE) // .addDataType(DataType.TYPE_STEP_COUNT_CUMULATIVE)
// .addDataType(DataType.TYPE_CALORIES_EXPENDED)
.build() .build()
private fun checkPermissionsAndRun(data: Data) { // private fun checkPermissionsAndRun(data: Data) {
if (permissionApproved()) { // if (permissionApproved()) {
signIn(data) // signIn(data)
} else { // } else {
Log.d(TAG, "Asking for permission") // Log.d(TAG, "Asking for permission")
// Ask for permission // // Ask for permission
ActivityCompat.requestPermissions( // ActivityCompat.requestPermissions(
activity, // activity,
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), // arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
data.ordinal // data.ordinal
) // )
} // }
} // }
private fun permissionApproved(): Boolean { private fun permissionApproved(): Boolean =
val approved = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
PackageManager.PERMISSION_GRANTED == ActivityCompat.checkSelfPermission( PackageManager.PERMISSION_GRANTED == ActivityCompat.checkSelfPermission(
activity, activity,
Manifest.permission.ACCESS_FINE_LOCATION) Manifest.permission.ACCESS_FINE_LOCATION
)
} else { } else {
true true
} }
return approved
private val connectLiveData: MutableLiveData<States> = MutableLiveData(States.WIP)
override fun connect(): LiveData<States> {
if (!permissionApproved()) {
ActivityCompat.requestPermissions(
activity,
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
87531
)
return connectLiveData
} }
private fun signIn(data: Data) { if (isConnected()) {
if (oAuthPermissionsApproved()) { connectLiveData.value = States.DONE
startImport(data)
} else { } else {
Log.d("GoogleFitImporter", "Signing In") Log.d("GoogleFitImporter", "Signing In")
GoogleSignIn.requestPermissions( GoogleSignIn.requestPermissions(
activity, activity,
data.ordinal, 124887,
getGoogleAccount(), fitnessOptions) 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) private fun getGoogleAccount() = GoogleSignIn.getAccountForExtension(activity, fitnessOptions)
@ -102,85 +147,116 @@ class GoogleFit(
else -> {} else -> {}
} }
runRequest(DataReadRequest.Builder() runRequest(
DataReadRequest.Builder()
.read(type) .read(type)
.setTimeRange(timeRange[0], timeRange[1], timeUnit) .setTimeRange(timeRange[0], timeRange[1], timeUnit)
.build(), data) .build(), data
)
} }
private fun runRequest(request: DataReadRequest, data: Data) { private fun runRequest(request: DataReadRequest, data: Data) {
Fitness.getHistoryClient(activity, GoogleSignIn.getAccountForExtension(activity, fitnessOptions)) Fitness.getHistoryClient(
activity,
GoogleSignIn.getAccountForExtension(activity, fitnessOptions)
)
.readData(request) .readData(request)
.addOnSuccessListener { response -> .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) { for (dataSet in response.dataSets) {
Log.i(TAG, "Data returned for Data type: ${dataSet.dataType.name} ${dataSet.dataPoints.size}") Log.i(
dataSet.dataPoints.forEachIndexed { index, dp -> TAG,
val isLast = (index + 1) == dataSet.dataPoints.size "Data returned for Data type: ${dataSet.dataType.name} ${dataSet.dataPoints.size} ${dataSet.dataSource.toDebugString()}"
)
dataSet.dataPoints.forEach { dp ->
// Global // Global
Log.i(TAG,"Importing Data point:") Log.i(TAG, "Importing Data point:")
Log.i(TAG,"\tType: ${dp.dataType.name}") Log.i(TAG, "\tType: ${dp.dataType.name}")
Log.i(TAG,"\tStart: ${dp.getStartTimeString()}") Log.i(
Log.i(TAG,"\tEnd: ${dp.getEndTimeString()}") TAG,
"\tStart: ${Date(dp.getStartTime(TimeUnit.SECONDS) * 1000L).toLocaleString()}"
)
Log.i(
TAG,
"\tEnd: ${Date(dp.getEndTime(TimeUnit.SECONDS) * 1000L).toLocaleString()}"
)
// Field Specifics // Field Specifics
for (field in dp.dataType.fields) { 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) { when (data) {
Data.WEIGHT -> { Data.WEIGHT -> {
val weight = Weight() val weight = Weight()
weight.timestamp = dp.getStartTime(TimeUnit.MILLISECONDS) weight.timestamp = dp.getStartTime(TimeUnit.MILLISECONDS)
weight.weight = dp.getValue(field).asFloat() 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 -> {} else -> {}
} }
} }
} }
when (data) {
Data.WEIGHT -> {
weightLiveData.value =
ImportState(
States.DONE, weightLiveData.value?.list
?: ArrayList()
)
}
else -> {}
}
} }
} }
.addOnFailureListener { e -> .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() /**
* Currently not usable
private fun DataPoint.getEndTimeString(): String = Date(this.getEndTime(TimeUnit.SECONDS) * 1000L).toLocaleString() */
override fun onRequestPermissionResult( override fun onRequestPermissionResult(
requestCode: Int, requestCode: Int,
permission: Array<String>, permission: Array<String>,
grantResult: IntArray grantResult: IntArray
) { ) {
signIn(Data.values()[requestCode]) connect()
// signIn(Data.values()[requestCode])
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
signIn(Data.values()[requestCode]) if (requestCode == 0) {
return
}
connectLiveData.value = States.DONE
//signIn(Data.values()[requestCode])
} }
private lateinit var weightCallback: (weight: Weight, end: Boolean) -> Unit private lateinit var weightLiveData: MutableLiveData<ImportState<Weight>>
override fun importWeight(callback: (weight: Weight, end: Boolean) -> Unit) { override fun importWeight(): LiveData<ImportState<Weight>> {
this.weightCallback = callback
checkPermissionsAndRun(Data.WEIGHT)
}
private lateinit var callback : (item: Any, end: Boolean) -> Unit weightLiveData = MutableLiveData(
ImportState(
States.WIP
)
)
override fun <T> import(data: Data, cb: (item: T, end: Boolean) -> Unit) { startImport(Data.WEIGHT)
callback = cb as (item: Any, end: Boolean) -> Unit
when (data) { // checkPermissionsAndRun(Data.WEIGHT)
Data.WEIGHT -> {
checkPermissionsAndRun(data) return weightLiveData
}
else -> {
Log.d(TAG, "PRRRRRRRRRRRRR")
}
}
} }
} }

View File

@ -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()
}
}

View File

@ -5,11 +5,12 @@ import android.content.Intent
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.util.Log import android.util.Log
import com.dzeio.openhealth.extensions.Extension
import com.dzeio.openhealth.data.weight.Weight 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.HealthConstants.StepCount
import com.samsung.android.sdk.healthdata.HealthDataStore
import com.samsung.android.sdk.healthdata.HealthDataStore.ConnectionListener import com.samsung.android.sdk.healthdata.HealthDataStore.ConnectionListener
import com.samsung.android.sdk.healthdata.HealthPermissionManager
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( class SamsungHealth(
private val context: Activity private val context: Activity
) : Extension() { ) {
companion object { companion object {
const val TAG = "SamsungHealthConnector" const val TAG = "SamsungHealthConnector"
@ -33,6 +34,7 @@ class SamsungHealth(
requestPermission() requestPermission()
} }
} }
override fun onConnectionFailed(p0: HealthConnectionErrorResult?) { override fun onConnectionFailed(p0: HealthConnectionErrorResult?) {
Log.d(TAG, "Health data service is not available.") 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 { private fun isPermissionAcquired(): Boolean {
val permKey = PermissionKey(StepCount.HEALTH_DATA_TYPE, PermissionType.READ) 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 * Connector
*/ */
override val sourceID: String = "SamsungHealth" val sourceID: String = "SamsungHealth"
override fun onRequestPermissionResult( fun onRequestPermissionResult(
requestCode: Int, requestCode: Int,
permission: Array<String>, permission: Array<String>,
grantResult: IntArray 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() store.connectService()
} }
} }

View File

@ -1,12 +1,10 @@
package com.dzeio.openhealth.interfaces package com.dzeio.openhealth.interfaces
import android.app.NotificationManager
enum class NotificationChannels( enum class NotificationChannels(
val id: String, val id: String,
val channelName: String, val channelName: String,
val importance: Int val importance: Int
) { ) {
// 3 is IMPORTANCE_DEFAULT // 3 is IMPORTANCE_DEFAULT
DEFAULT("default", "Default Channel", 3) DEFAULT("openhealth_default", "Default Channel", 3)
} }

View File

@ -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, FragmentExtensionBinding>(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()
}
}
}
}
}
}

View File

@ -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<String>().apply {
value = "This is slideshow Fragment"
}
val importProgress = MutableLiveData<Int>().apply {
value = 0
}
// If -1 progress is undetermined
// If 0 no progress bar
// Else progress bar
val importProgressTotal = MutableLiveData<Int>().apply {
value = 0
}
suspend fun importWeight(weight: Weight) = weightRepository.addWeight(weight)
suspend fun deleteFromSource(source: String) = weightRepository.deleteFromSource(source)
}

View File

@ -10,13 +10,13 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController
import com.dzeio.openhealth.extensions.Extension import androidx.recyclerview.widget.LinearLayoutManager
import com.dzeio.openhealth.extensions.GoogleFit import com.dzeio.openhealth.adapters.ExtensionAdapter
//import com.dzeio.openhealth.connectors.GoogleFit
import com.dzeio.openhealth.extensions.samsunghealth.SamsungHealth
import com.dzeio.openhealth.core.BaseFragment import com.dzeio.openhealth.core.BaseFragment
import com.dzeio.openhealth.databinding.FragmentExtensionsBinding import com.dzeio.openhealth.databinding.FragmentExtensionsBinding
import com.dzeio.openhealth.extensions.Extension
import com.dzeio.openhealth.extensions.GoogleFit
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint @AndroidEntryPoint
@ -24,92 +24,55 @@ class ExtensionsFragment :
BaseFragment<ExtensionsViewModel, FragmentExtensionsBinding>(ExtensionsViewModel::class.java) { BaseFragment<ExtensionsViewModel, FragmentExtensionsBinding>(ExtensionsViewModel::class.java) {
companion object { companion object {
const val TAG = "ImportFragment" const val TAG = "ExtensionsFragment"
} }
override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> FragmentExtensionsBinding = override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> FragmentExtensionsBinding =
FragmentExtensionsBinding::inflate FragmentExtensionsBinding::inflate
private lateinit var progressDialog: ProgressDialog private lateinit var activeExtension: Extension
private lateinit var fit: Extension
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
progressDialog = ProgressDialog(requireContext()) val recycler = binding.list
progressDialog.apply { val manager = LinearLayoutManager(requireContext())
setCancelable(false) recycler.layoutManager = manager
setTitle("Importing from source...")
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)
}
}
}
recycler.adapter = adapter
val list = arrayOf(
GoogleFit()
).toList()
list.forEach {
it.init(requireActivity())
} }
binding.importGoogleFit.setOnClickListener { adapter.set(list)
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)
}
if (end) {
Log.d("Importer", "Finished Importing")
progressDialog.dismiss()
return@importWeight
}
}
}
}
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
}
}
}
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
fit.onActivityResult(requestCode, resultCode, data) activeExtension.onActivityResult(requestCode, resultCode, data)
} }
@RequiresApi(Build.VERSION_CODES.O) @RequiresApi(Build.VERSION_CODES.O)
@ -126,7 +89,7 @@ class ExtensionsFragment :
grantResults[0] == PackageManager.PERMISSION_GRANTED -> { grantResults[0] == PackageManager.PERMISSION_GRANTED -> {
Log.d(TAG, "Granted") Log.d(TAG, "Granted")
fit.onRequestPermissionResult(requestCode, permissions, grantResults) activeExtension.onRequestPermissionResult(requestCode, permissions, grantResults)
} }
else -> { else -> {
// Permission denied. // Permission denied.

View File

@ -1,9 +1,9 @@
package com.dzeio.openhealth.ui.home package com.dzeio.openhealth.ui.home
import android.animation.ValueAnimator import android.animation.ValueAnimator
import android.graphics.BitmapFactory import android.graphics.Bitmap
import android.graphics.Canvas import android.graphics.Canvas
import android.graphics.Rect import android.graphics.RectF
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
@ -13,28 +13,21 @@ import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.dzeio.openhealth.Application import com.dzeio.openhealth.Application
import com.dzeio.openhealth.R
import com.dzeio.openhealth.core.BaseFragment import com.dzeio.openhealth.core.BaseFragment
import com.dzeio.openhealth.data.water.Water import com.dzeio.openhealth.data.water.Water
import com.dzeio.openhealth.databinding.FragmentHomeBinding
import com.dzeio.openhealth.data.weight.Weight import com.dzeio.openhealth.data.weight.Weight
import com.dzeio.openhealth.databinding.FragmentHomeBinding
import com.dzeio.openhealth.ui.weight.AddWeightDialog import com.dzeio.openhealth.ui.weight.AddWeightDialog
import com.dzeio.openhealth.utils.BitmapUtils
import com.dzeio.openhealth.utils.DrawUtils import com.dzeio.openhealth.utils.DrawUtils
import com.github.mikephil.charting.components.AxisBase import com.dzeio.openhealth.utils.GraphUtils
import com.github.mikephil.charting.components.Description
import com.github.mikephil.charting.components.XAxis
import com.github.mikephil.charting.data.Entry import com.github.mikephil.charting.data.Entry
import com.github.mikephil.charting.data.LineData import com.github.mikephil.charting.data.LineData
import com.github.mikephil.charting.data.LineDataSet import com.github.mikephil.charting.data.LineDataSet
import com.github.mikephil.charting.formatter.ValueFormatter
import com.google.android.material.color.MaterialColors import com.google.android.material.color.MaterialColors
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.*
import kotlin.math.min import kotlin.math.min
import kotlin.properties.Delegates import kotlin.properties.Delegates
@ -53,8 +46,6 @@ class HomeFragment : BaseFragment<HomeViewModel, FragmentHomeBinding>(HomeViewMo
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
viewModel.init()
binding.addWeight.setOnClickListener { binding.addWeight.setOnClickListener {
AddWeightDialog().show(requireActivity().supportFragmentManager, null) AddWeightDialog().show(requireActivity().supportFragmentManager, null)
} }
@ -112,14 +103,23 @@ class HomeFragment : BaseFragment<HomeViewModel, FragmentHomeBinding>(HomeViewMo
} }
binding.listWeight.setOnClickListener { binding.listWeight.setOnClickListener {
Log.d("T", "Trying to move")
findNavController().navigate(HomeFragmentDirections.actionNavHomeToNavListWeight()) findNavController().navigate(HomeFragmentDirections.actionNavHomeToNavListWeight())
} }
binding.gotoWaterHome.setOnClickListener { binding.gotoWaterHome.setOnClickListener {
findNavController().navigate(HomeFragmentDirections.actionNavHomeToNavWaterHome()) 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<Weight>) { private fun updateGraph(list: List<Weight>) {
@ -130,35 +130,9 @@ class HomeFragment : BaseFragment<HomeViewModel, FragmentHomeBinding>(HomeViewMo
} }
val dataSet = LineDataSet(entries, "Label") val dataSet = LineDataSet(entries, "Label")
binding.weightGraph.apply { 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 // Apply new dataset
data = LineData(dataSet) data = LineData(dataSet)
@ -181,6 +155,8 @@ class HomeFragment : BaseFragment<HomeViewModel, FragmentHomeBinding>(HomeViewMo
viewModel.fetchWeights().collectLatest { viewModel.fetchWeights().collectLatest {
updateGraph(it) updateGraph(it)
} }
updateWater(0)
updateWater(1234)
} }
viewModel.water.observe(viewLifecycleOwner) { viewModel.water.observe(viewLifecycleOwner) {
@ -192,32 +168,70 @@ class HomeFragment : BaseFragment<HomeViewModel, FragmentHomeBinding>(HomeViewMo
} }
} }
updateWater(0)
} }
private fun updateWater(water: Int) { private fun updateWater(water: Int) {
val oldValue = binding.fragmentHomeWaterCurrent.text.toString().replace("ml", "").toInt() val oldValue = binding.fragmentHomeWaterCurrent.text.toString().replace("ml", "").toInt()
binding.fragmentHomeWaterCurrent.text = "${water}ml" binding.fragmentHomeWaterCurrent.text = "${water}ml"
val graph = BitmapUtils.convertToMutable( var width = 1500
requireContext(), var height = 750
BitmapFactory.decodeResource(resources, R.drawable.ellipse)
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 -> // 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)) ValueAnimator.ofFloat(min(oldValue.toFloat(), intake), min(water.toFloat(), intake))
.apply { .apply {
duration = 300 duration = 300
addUpdateListener { addUpdateListener {
val canvas = Canvas(btmp)
DrawUtils.drawArc( DrawUtils.drawArc(
canvas, canvas,
100 * it.animatedValue as Float / intake, 100 * it.animatedValue as Float / intake,
rect,
MaterialColors.getColor( MaterialColors.getColor(
requireView(), requireView(),
com.google.android.material.R.attr.colorPrimary com.google.android.material.R.attr.colorPrimary
) ), 6f
) )
canvas.save() canvas.save()
binding.background.setImageBitmap(graph) binding.background.setImageBitmap(graph)
@ -225,5 +239,4 @@ class HomeFragment : BaseFragment<HomeViewModel, FragmentHomeBinding>(HomeViewMo
start() start()
} }
} }
}
} }

View File

@ -1,5 +1,6 @@
package com.dzeio.openhealth.ui.home package com.dzeio.openhealth.ui.home
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.dzeio.openhealth.core.BaseViewModel import com.dzeio.openhealth.core.BaseViewModel
@ -18,8 +19,23 @@ class HomeViewModel @Inject internal constructor(
private val waterRepository: WaterRepository private val waterRepository: WaterRepository
) : BaseViewModel() { ) : BaseViewModel() {
init {
viewModelScope.launch {
waterRepository.todayWater().collectLatest {
_water.value = it
}
}
}
/**
* @deprecated
*/
fun fetchWeights() = weightRepository.getWeights() fun fetchWeights() = weightRepository.getWeights()
/**
* @deprecated
*/
fun lastWeight() = weightRepository.lastWeight() fun lastWeight() = weightRepository.lastWeight()
fun fetchWeight(id: Long) = weightRepository.getWeight(id) fun fetchWeight(id: Long) = weightRepository.getWeight(id)
@ -28,17 +44,11 @@ class HomeViewModel @Inject internal constructor(
suspend fun addWeight(weight: Weight) = weightRepository.addWeight(weight) suspend fun addWeight(weight: Weight) = weightRepository.addWeight(weight)
fun fetchTodayWater() = waterRepository.todayWater() suspend fun fetchTodayWater() = waterRepository.todayWater()
val water: MutableLiveData<Water?> = MutableLiveData(null) private val _water = MutableLiveData<Water?>(null)
val water: LiveData<Water?> = _water
fun init() {
viewModelScope.launch {
waterRepository.todayWater().collectLatest {
water.postValue(it)
}
}
}
fun updateWater(water: Water) { fun updateWater(water: Water) {
viewModelScope.launch { viewModelScope.launch {
@ -49,7 +59,7 @@ class HomeViewModel @Inject internal constructor(
fun deleteWater(item: Water) { fun deleteWater(item: Water) {
viewModelScope.launch { viewModelScope.launch {
waterRepository.deleteWater(item) waterRepository.deleteWater(item)
water.postValue(null) _water.postValue(null)
} }
} }
} }

View File

@ -1,7 +1,9 @@
package com.dzeio.openhealth.ui.water package com.dzeio.openhealth.ui.water
import android.app.Dialog import android.app.Dialog
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.Window import android.view.Window
@ -13,8 +15,12 @@ import androidx.navigation.fragment.navArgs
import com.dzeio.openhealth.R import com.dzeio.openhealth.R
import com.dzeio.openhealth.core.BaseFullscreenDialog import com.dzeio.openhealth.core.BaseFullscreenDialog
import com.dzeio.openhealth.databinding.DialogWaterEditWaterBinding 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 com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import java.util.*
@AndroidEntryPoint @AndroidEntryPoint
class EditWaterDialog : class EditWaterDialog :
@ -40,13 +46,53 @@ class EditWaterDialog :
viewModel.water.observe(viewLifecycleOwner) { viewModel.water.observe(viewLifecycleOwner) {
binding.editTextNumber.setText(it.value.toString()) binding.editTextNumber.setText(it.value.toString())
binding.date.text = it.formatTimestamp()
} }
binding.editTextNumber.doOnTextChanged { text, start, before, count -> 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) viewModel.init(args.id)
} }
private fun save() { private fun save() {

View File

@ -1,30 +1,20 @@
package com.dzeio.openhealth.ui.water package com.dzeio.openhealth.ui.water
import android.graphics.Color
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.dzeio.openhealth.adapters.WaterAdapter import com.dzeio.openhealth.adapters.WaterAdapter
import com.dzeio.openhealth.adapters.WeightAdapter
import com.dzeio.openhealth.core.BaseFragment import com.dzeio.openhealth.core.BaseFragment
import com.dzeio.openhealth.databinding.FragmentListWeightBinding
import com.dzeio.openhealth.databinding.FragmentMainWaterHomeBinding 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.dzeio.openhealth.utils.GraphUtils
import com.github.mikephil.charting.data.BarData import com.github.mikephil.charting.data.BarData
import com.github.mikephil.charting.data.BarDataSet import com.github.mikephil.charting.data.BarDataSet
import com.github.mikephil.charting.data.BarEntry import com.github.mikephil.charting.data.BarEntry
import com.github.mikephil.charting.data.Entry
import com.google.android.material.color.MaterialColors import com.google.android.material.color.MaterialColors
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.collectLatest
import java.time.Instant
import java.time.temporal.ChronoUnit
import java.util.* import java.util.*
@AndroidEntryPoint @AndroidEntryPoint
@ -60,6 +50,9 @@ class WaterHomeFragment :
chart, MaterialColors.getColor( chart, MaterialColors.getColor(
requireView(), requireView(),
com.google.android.material.R.attr.colorPrimary 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.time = Date(0)
epoch.add(Calendar.MILLISECOND, it.timestamp.toInt()) epoch.add(Calendar.MILLISECOND, it.timestamp.toInt())
return@map BarEntry( return@map BarEntry(
epoch.get(Calendar.DATE).toFloat(), (epoch.timeInMillis / 1000 / 60 / 60).toFloat(),
it.value.toFloat() it.value.toFloat()
) )
}, },

View File

@ -1,24 +1,23 @@
package com.dzeio.openhealth.utils package com.dzeio.openhealth.utils
import android.graphics.* import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.RectF
object DrawUtils { object DrawUtils {
/** /**
* Fuck Graphics * Fuck Graphics
*/ */
fun drawArc(canvas: Canvas, percent: Float, pColor: Int) { fun drawArc(canvas: Canvas, percent: Float, rect: RectF, pColor: Int, strokeWidth: Float = 1f) {
canvas.width
val spacing = 120f
val r1 = RectF( val r1 = RectF(
spacing, canvas.realSize(true, rect.left),
spacing, canvas.realSize(false, rect.top),
canvas.width - spacing, canvas.realSize(true, rect.right),
canvas.height * 2 - spacing * 3 canvas.realSize(false, rect.bottom, 2)
) )
val paint = Paint() val paint = Paint().apply {
paint.apply { this.strokeWidth = canvas.realSize(true, strokeWidth)
strokeWidth = 200f
style = Paint.Style.STROKE style = Paint.Style.STROKE
color = pColor color = pColor
isAntiAlias = true isAntiAlias = true
@ -27,4 +26,33 @@ object DrawUtils {
canvas.drawArc(r1, 180f, 180 * percent / 100f, false, paint) 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
}
} }

View File

@ -1,35 +1,34 @@
package com.dzeio.openhealth.utils package com.dzeio.openhealth.utils
import android.graphics.Color
import com.github.mikephil.charting.charts.BarChart import com.github.mikephil.charting.charts.BarChart
import com.github.mikephil.charting.charts.BarLineChartBase 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.charts.LineChart
import com.github.mikephil.charting.components.AxisBase import com.github.mikephil.charting.components.AxisBase
import com.github.mikephil.charting.components.Description import com.github.mikephil.charting.components.Description
import com.github.mikephil.charting.components.XAxis import com.github.mikephil.charting.components.XAxis
import com.github.mikephil.charting.data.BarLineScatterCandleBubbleData 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.Entry
import com.github.mikephil.charting.data.LineData
import com.github.mikephil.charting.formatter.ValueFormatter import com.github.mikephil.charting.formatter.ValueFormatter
import com.github.mikephil.charting.interfaces.datasets.IBarLineScatterCandleBubbleDataSet 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.text.SimpleDateFormat
import java.util.* import java.util.*
object GraphUtils { object GraphUtils {
fun lineChartSetup(chart: LineChart, mainColor: Int) { fun lineChartSetup(chart: LineChart, mainColor: Int, textColor: Int) {
barLineChartSetup(chart, mainColor) barLineChartSetup(chart, mainColor, textColor)
} }
fun barChartSetup(chart: BarChart, mainColor: Int) { fun barChartSetup(chart: BarChart, mainColor: Int, textColor: Int) {
barLineChartSetup(chart, mainColor) barLineChartSetup(chart, mainColor, textColor)
} }
fun <T : BarLineScatterCandleBubbleData<out IBarLineScatterCandleBubbleDataSet<out Entry>>?> barLineChartSetup(chart: BarLineChartBase<T>, mainColor: Int) { private fun <T : BarLineScatterCandleBubbleData<out IBarLineScatterCandleBubbleDataSet<out Entry>>?> barLineChartSetup(
chart: BarLineChartBase<T>,
mainColor: Int,
textColor: Int
) {
chart.apply { chart.apply {
// Setup // Setup
@ -49,18 +48,25 @@ object GraphUtils {
position = XAxis.XAxisPosition.BOTTOM position = XAxis.XAxisPosition.BOTTOM
setDrawGridLines(false) setDrawGridLines(false)
setLabelCount(3, true) setLabelCount(3, true)
textColor = Color.WHITE this.textColor = textColor
//setDrawGridLines(false)
//setDrawZeroLine(false)
setDrawAxisLine(false)
disableGridDashedLine()
invalidateOutline()
} }
axisLeft.apply { axisLeft.apply {
axisLineColor = mainColor axisLineColor = mainColor
textColor = Color.WHITE this.textColor = textColor
// setDrawZeroLine(false)
setLabelCount(0, true) setLabelCount(0, true)
setDrawGridLines(false)
} }
axisRight.apply { axisRight.apply {
textColor = Color.WHITE this.textColor = textColor
} }
setNoDataTextColor(Color.WHITE) setNoDataTextColor(textColor)
isAutoScaleMinMaxEnabled = true isAutoScaleMinMaxEnabled = true
@ -70,8 +76,8 @@ object GraphUtils {
description = Description().apply { isEnabled = false } description = Description().apply { isEnabled = false }
isScaleXEnabled = true isScaleXEnabled = true
setPinchZoom(false) setPinchZoom(false)
//setDrawGridBackground(false) setDrawGridBackground(false)
//setDrawBorders(false) setDrawBorders(false)
} }
} }
} }

View File

@ -2,7 +2,8 @@
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path <path
android:pathData="M12.0001,1.7144L20.9078,6.8572V17.1429L12.0001,22.2858L3.0924,17.1429V6.8572L12.0001,1.7144Z" android:pathData="M12.0001,1.7144L20.9078,6.8572V17.1429L12.0001,22.2858L3.0924,17.1429V6.8572L12.0001,1.7144Z"
android:strokeWidth="2" android:strokeWidth="2"

View File

@ -1,28 +1,21 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/drawer_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:openDrawer="start"
tools:context=".MainActivity"> tools:context=".MainActivity">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<com.google.android.material.appbar.AppBarLayout <com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent" android:layout_width="match_parent"
style="?attr/appBarLayoutStyle" style="@style/Widget.Material3.AppBarLayout"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
<com.google.android.material.appbar.MaterialToolbar <com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar" android:id="@+id/toolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
style="?attr/toolbarStyle"
app:titleCentered="true" app:titleCentered="true"
style="@style/ThemeOverlay.Material3.Toolbar.Surface"
android:layout_height="?attr/actionBarSize" /> android:layout_height="?attr/actionBarSize" />
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>
@ -50,6 +43,4 @@
</androidx.core.widget.NestedScrollView> </androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>
</androidx.drawerlayout.widget.DrawerLayout>

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
@ -8,8 +9,20 @@
android:id="@+id/editTextNumber" android:id="@+id/editTextNumber"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:ems="10" android:ems="10"
android:inputType="number" android:inputType="number"
tools:layout_editor_absoluteX="101dp" app:layout_constraintEnd_toEndOf="parent"
tools:layout_editor_absoluteY="107dp" /> app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:text="2021-12-21"
app:layout_constraintEnd_toEndOf="@+id/editTextNumber"
app:layout_constraintStart_toStartOf="@+id/editTextNumber"
app:layout_constraintTop_toBottomOf="@+id/editTextNumber" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_margin="16dp"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:weightSum="2">
<Button
android:id="@+id/import_button"
style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_weight="1"
android:text="Force Import" />
<Button
android:id="@+id/export_button"
style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_weight="1"
android:text="Force Export" />
</LinearLayout>
<TextView
android:id="@+id/textView2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/extension_informations" />
</LinearLayout>

View File

@ -1,122 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".ui.extensions.ExtensionsFragment"> android:id="@+id/list"
tools:listitem="@layout/layout_extension_item"
tools:context=".ui.extensions.ExtensionsFragment" />
<com.google.android.material.card.MaterialCardView
style="?attr/materialCardViewFilledStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:id="@+id/import_google_fit"
android:layout_marginEnd="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginHorizontal="16dp"
android:layout_marginVertical="16dp"
android:src="@drawable/ic_logo_fit" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="54dp"
android:layout_marginVertical="16dp"
android:gravity="center_vertical"
android:layout_weight="1"
android:orientation="vertical">
<TextView
style="@style/TextAppearance.Material3.TitleMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Google Fit" />
<TextView
style="@style/TextAppearance.Material3.BodyMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Last Sync: Yesterday" />
</LinearLayout>
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginHorizontal="16dp"
android:layout_marginVertical="16dp"
android:src="@drawable/ic_baseline_extension_24" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/import_samsung_health"
style="?attr/materialCardViewOutlinedStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginHorizontal="16dp"
android:layout_marginVertical="16dp"
android:src="@drawable/logo_shealth" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="54dp"
android:layout_marginVertical="16dp"
android:gravity="center_vertical"
android:layout_weight="1"
android:orientation="vertical">
<TextView
style="@style/TextAppearance.Material3.TitleMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Samsung Health" />
<TextView
style="@style/TextAppearance.Material3.BodyMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Currently Unavailable" />
</LinearLayout>
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginHorizontal="16dp"
android:layout_marginVertical="16dp"
android:src="@drawable/ic_baseline_extension_24" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</LinearLayout>

View File

@ -39,8 +39,8 @@
style="@style/TextAppearance.Material3.TitleMedium" style="@style/TextAppearance.Material3.TitleMedium"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Weight" android:layout_weight="1"
android:layout_weight="1" /> android:text="Weight" />
<LinearLayout <LinearLayout
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -75,7 +75,7 @@
android:id="@+id/fragment_home_water_remove" android:id="@+id/fragment_home_water_remove"
android:layout_width="18dp" android:layout_width="18dp"
android:layout_height="18dp" android:layout_height="18dp"
android:layout_marginEnd="4dp" android:layout_marginEnd="16dp"
android:layout_weight="1" android:layout_weight="1"
android:src="@drawable/ic_outline_hexagon_24" android:src="@drawable/ic_outline_hexagon_24"
app:layout_constraintBottom_toBottomOf="@+id/linearLayout" app:layout_constraintBottom_toBottomOf="@+id/linearLayout"
@ -86,11 +86,12 @@
android:id="@+id/linearLayout" android:id="@+id/linearLayout"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:orientation="vertical" android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/background"> app:layout_constraintTop_toTopOf="parent">
<TextView <TextView
android:id="@+id/fragment_home_water_current" android:id="@+id/fragment_home_water_current"
@ -116,7 +117,7 @@
android:id="@+id/fragment_home_water_add" android:id="@+id/fragment_home_water_add"
android:layout_width="18dp" android:layout_width="18dp"
android:layout_height="18dp" android:layout_height="18dp"
android:layout_marginStart="4dp" android:layout_marginStart="16dp"
android:layout_weight="1" android:layout_weight="1"
android:src="@drawable/ic_baseline_add_24" android:src="@drawable/ic_baseline_add_24"
app:layout_constraintBottom_toBottomOf="@+id/linearLayout" app:layout_constraintBottom_toBottomOf="@+id/linearLayout"
@ -128,9 +129,9 @@
android:id="@+id/background" android:id="@+id/background"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
app:layout_constraintDimensionRatio="2:1"
android:src="@drawable/ic_outline_hexagon_24" android:src="@drawable/ic_outline_hexagon_24"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="2:1"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />

View File

@ -10,5 +10,5 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:padding="16dp" android:padding="16dp"
tools:listitem="@layout/layout_item_weight" tools:listitem="@layout/layout_item_list"
tools:context=".ui.weight.ListWeightFragment" /> tools:context=".ui.weight.ListWeightFragment" />

View File

@ -56,7 +56,6 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:padding="16dp" android:padding="16dp"
tools:listitem="@layout/layout_item_list" />
tools:listitem="@layout/layout_item_weight" />
</LinearLayout> </LinearLayout>

View File

@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
style="?attr/materialCardViewFilledStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:id="@+id/card"
android:layout_marginEnd="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:layout_width="40dp"
android:id="@+id/logo"
android:layout_height="40dp"
android:layout_marginHorizontal="16dp"
android:layout_marginVertical="16dp"
android:src="@drawable/ic_logo_fit" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="54dp"
android:layout_marginVertical="16dp"
android:gravity="center_vertical"
android:layout_weight="1"
android:orientation="vertical">
<TextView
style="@style/TextAppearance.Material3.TitleMedium"
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Google Fit" />
<TextView
android:id="@+id/status"
style="@style/TextAppearance.Material3.BodyMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Last Sync: Yesterday" />
</LinearLayout>
<ImageView
android:id="@+id/icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginHorizontal="16dp"
android:layout_marginVertical="16dp"
android:src="@drawable/ic_baseline_extension_24" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>

View File

@ -1,59 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
style="?attr/materialCardViewFilledStyle"
xmlns:tools="http://schemas.android.com/tools"
android:layout_marginBottom="8dp"
android:clickable="true"
android:focusable="true"
android:layout_width="match_parent"
android:id="@+id/edit"
android:layout_height="wrap_content">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_margin="16dp"
android:layout_height="match_parent"
android:orientation="horizontal">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
tools:layout_editor_absoluteX="16dp"
tools:layout_editor_absoluteY="0dp">
<TextView
android:id="@+id/weight"
style="@style/TextAppearance.Material3.TitleMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="xkg"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/datetime"
style="@style/TextAppearance.Material3.BodyMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="Taken: yyyy-mm-dd"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/weight" />
</androidx.constraintlayout.widget.ConstraintLayout>
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/ic_baseline_edit_24"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>

View File

@ -46,12 +46,8 @@
android:label="@string/menu_import" android:label="@string/menu_import"
tools:layout="@layout/fragment_extensions" > tools:layout="@layout/fragment_extensions" >
<action <action
android:id="@+id/action_nav_import_to_nav_settings" android:id="@+id/action_nav_extensions_to_nav_extension"
app:destination="@id/nav_settings" app:destination="@id/nav_extension" />
app:enterAnim="@android:anim/slide_in_left"
app:exitAnim="@android:anim/slide_out_right"
app:popEnterAnim="@android:anim/slide_in_left"
app:popExitAnim="@android:anim/slide_out_right" />
</fragment> </fragment>
<fragment <fragment
@ -66,13 +62,6 @@
app:exitAnim="@android:anim/slide_out_right" app:exitAnim="@android:anim/slide_out_right"
app:popEnterAnim="@android:anim/slide_in_left" app:popEnterAnim="@android:anim/slide_in_left"
app:popExitAnim="@android:anim/slide_out_right" /> app:popExitAnim="@android:anim/slide_out_right" />
<action
android:id="@+id/action_nav_list_weight_to_nav_settings"
app:destination="@id/nav_settings"
app:enterAnim="@android:anim/slide_in_left"
app:exitAnim="@android:anim/slide_out_right"
app:popEnterAnim="@android:anim/slide_in_left"
app:popExitAnim="@android:anim/slide_out_right" />
</fragment> </fragment>
<fragment <fragment
@ -93,15 +82,12 @@
android:label="@string/nav_water_home" android:label="@string/nav_water_home"
tools:layout="@layout/fragment_main_water_home"> tools:layout="@layout/fragment_main_water_home">
<action <action
android:id="@+id/action_nav_water_home_to_nav_settings" android:id="@+id/action_nav_water_home_to_nav_water_edit"
app:destination="@id/nav_settings" app:destination="@id/nav_water_edit"
app:enterAnim="@android:anim/slide_in_left" app:enterAnim="@android:anim/slide_in_left"
app:exitAnim="@android:anim/slide_out_right" app:exitAnim="@android:anim/slide_out_right"
app:popEnterAnim="@android:anim/slide_in_left" app:popEnterAnim="@android:anim/slide_in_left"
app:popExitAnim="@android:anim/slide_out_right" /> app:popExitAnim="@android:anim/slide_out_right" />
<action
android:id="@+id/action_nav_water_home_to_nav_water_edit"
app:destination="@id/nav_water_edit" />
</fragment> </fragment>
<fragment <fragment
@ -124,4 +110,19 @@
app:argType="long" /> app:argType="long" />
</fragment> </fragment>
<fragment
android:id="@+id/nav_extension"
android:name="com.dzeio.openhealth.ui.extension.ExtensionFragment"
tools:layout="@layout/fragment_extension"
app:enterAnim="@android:anim/slide_in_left"
app:exitAnim="@android:anim/slide_out_right"
app:popEnterAnim="@android:anim/slide_in_left"
app:popExitAnim="@android:anim/slide_out_right">
<argument
android:name="extension"
app:argType="string" />
</fragment>
</navigation> </navigation>

View File

@ -16,4 +16,5 @@
<string name="nav_list_water">Water Intake</string> <string name="nav_list_water">Water Intake</string>
<string name="nav_water_home">Water Intake</string> <string name="nav_water_home">Water Intake</string>
<string name="menu_extensions">Extensions</string> <string name="menu_extensions">Extensions</string>
<string name="extension_informations">Imports are done at app startup\nExports are done when new inputs are give to the app</string>
</resources> </resources>

View File

@ -1,29 +1,10 @@
<resources xmlns:tools="http://schemas.android.com/tools"> <resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.OpenHealth" parent="Theme.Material3.DynamicColors.DayNight">
<!-- &lt;!&ndash; Primary brand color. &ndash;&gt;-->
<!-- <item name="colorPrimary">@color/purple_500</item>-->
<!-- <item name="colorPrimaryVariant">@color/purple_700</item>-->
<!-- <item name="colorOnPrimary">@color/white</item>-->
<!-- &lt;!&ndash; Secondary brand color. &ndash;&gt;-->
<!-- <item name="colorSecondary">@color/teal_200</item>-->
<!-- <item name="colorSecondaryVariant">@color/teal_700</item>-->
<!-- <item name="colorOnSecondary">@color/black</item>-->
<!-- &lt;!&ndash; Status bar color. &ndash;&gt;-->
<!-- <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>-->
<!-- Customize your theme here. -->
<item name="android:windowTranslucentStatus">true</item> <style name="Theme.OpenHealth" parent="Theme.Material3.DayNight.NoActionBar" />
<item name="materialAlertDialogTheme">@style/ThemeOverlay.Material3.MaterialAlertDialog</item>
</style>
<style name="Theme.OpenHealth.NoActionBar"> <style name="Theme.OpenHealth.NoActionBar" parent="Theme.OpenHealth">
<item name="windowActionBar">false</item> <item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item> <item name="windowNoTitle">true</item>
</style> </style>
<style name="Theme.OpenHealth.AppBarOverlay" parent="ThemeOverlay.MaterialComponents.Dark.ActionBar" />
<style name="Theme.OpenHealth.PopupOverlay" parent="ThemeOverlay.Material3.MaterialAlertDialog" />
</resources> </resources>

2
fastlane/Appfile Normal file
View File

@ -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

47
fastlane/Fastfile Normal file
View File

@ -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

56
fastlane/README.md Normal file
View File

@ -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).

View File

@ -0,0 +1 @@
Open Health

25
fastlane/report.xml Normal file

File diff suppressed because one or more lines are too long

12
fastlane_secret_keys.json Normal file
View File

@ -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"
}

4
keystore.properties Normal file
View File

@ -0,0 +1,4 @@
storeFile=../upload_key.jks
keyAlias=release_key
keyPassword=Babacarflo22
storePassword=Babacarflo22

BIN
private_key.pepk Normal file

Binary file not shown.

BIN
upload_key.jks Normal file

Binary file not shown.