1
0
mirror of https://github.com/dzeiocom/OpenHealth.git synced 2025-04-22 10:52:13 +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
on:
@ -7,8 +21,8 @@ on:
branches: [ master ]
jobs:
build:
build:
runs-on: ubuntu-latest
steps:
@ -19,15 +33,21 @@ jobs:
java-version: '11'
distribution: 'adopt'
cache: gradle
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew build
- name: Prepare project
run: chmod +x .github/scripts/gradlew_recursive.sh
- name: Build project
run: .github/scripts/gradlew_recursive.sh assembleDebug
- name: Zip artifacts
run: zip -r assemble.zip . -i '**/build/*.apk' '**/build/*.aab' '**/build/*.aar' '**/build/*.so'
- name: Upload artifacts
uses: actions/upload-artifact@v1
with:
name: assemble
path: assemble.zip
- name: ktlint
uses: ScaCap/action-ktlint@master
with:
github_token: ${{ secrets.github_token }}
android: true
reporter: github-pr-review # Change reporter

12
.gitignore vendored
View File

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

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/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/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/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_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/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_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_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_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_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_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/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/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/mipmap-anydpi-v26/ic_launcher.xml" value="0.3645" />
<entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/xml/preferences.xml" value="0.55" />
</map>
</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 {
signingConfigs {
release {
def keystorePropertiesFile = rootProject.file("./keystore.properties")
def keystoreProperties = new Properties()
try {
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
} catch (FileNotFoundException ignored) {
keystoreProperties = null
}
if (keystoreProperties != null) {
storePassword keystoreProperties["storePassword"]
keyPassword keystoreProperties["keyPassword"]
keyAlias keystoreProperties["keyAlias"]
storeFile file(keystoreProperties["storeFile"])
}
}
}
compileSdk 31
defaultConfig {
// App ID
applicationId "com.dzeio.openhealth"
// Android 5 Lollipop
minSdk 21
// Android 12
targetSdk 31
versionCode 1
versionName "1.0"
// Semantic Versioning
versionName "1.0.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
}
debug {
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
applicationIdSuffix ".dev"
versionNameSuffix '-dev'
debuggable true
// make it debuggable
renderscriptDebuggable true
// Optimization Level
renderscriptOptimLevel 0
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
buildFeatures {
viewBinding true
dataBinding true
}
}
dependencies {
@ -81,6 +132,7 @@ dependencies {
// Samsung Health
implementation files('libs/samsung-health-data-1.5.0.aar')
implementation "com.google.code.gson:gson:2.8.9"
// ROOM
def room_version = "2.4.0"

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,7 +6,7 @@
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- Phone Services -->
<!-- Phone Sensors -->
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
<!-- Samsung Health-->

View File

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

View File

@ -10,22 +10,17 @@ import android.util.Log
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuItem
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavController
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.fragment.findNavController
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.navigateUp
import androidx.navigation.ui.setupActionBarWithNavController
import androidx.navigation.ui.setupWithNavController
import androidx.work.WorkManager
import com.dzeio.openhealth.core.BaseActivity
import com.dzeio.openhealth.databinding.ActivityMainBinding
import com.dzeio.openhealth.interfaces.NotificationChannels
import com.dzeio.openhealth.services.WaterReminderService
import com.dzeio.openhealth.ui.home.HomeFragmentDirections
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
@ -45,6 +40,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
navController = navHostFragment.navController
appBarConfiguration = AppBarConfiguration(
@ -69,9 +65,8 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
return true
}
override fun onSupportNavigateUp(): Boolean {
return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
}
override fun onSupportNavigateUp(): Boolean =
navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
override fun onRequestPermissionsResult(
requestCode: Int,
@ -94,14 +89,13 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
}
private fun createNotificationChannel() {
// Create the NotificationChannel, but only on API 26+ because
// the NotificationChannel class is new and not in the support library
val notificationManager: NotificationManager =
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
for (channel in NotificationChannels.values()) {
Log.d("MainActivity", channel.channelName)
try {
notificationManager.createNotificationChannel(
NotificationChannel(
channel.id,
@ -109,6 +103,9 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
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
import android.util.Log
import android.view.LayoutInflater
import android.view.ViewGroup
import com.dzeio.openhealth.core.BaseAdapter
import com.dzeio.openhealth.core.BaseViewHolder
import com.dzeio.openhealth.data.water.Water
import com.dzeio.openhealth.data.weight.Weight
import com.dzeio.openhealth.databinding.LayoutItemListBinding
import com.dzeio.openhealth.databinding.LayoutItemWeightBinding
class WaterAdapter() : BaseAdapter<Water, LayoutItemListBinding>() {
@ -23,7 +20,7 @@ class WaterAdapter() : BaseAdapter<Water, LayoutItemListBinding>() {
position: Int
) {
holder.binding.value.text = "${item.value}ml"
holder.binding.datetime.text = item.formatTimestamp()
holder.binding.datetime.text = "${item.formatTimestamp()} ${item.timestamp}"
holder.binding.edit.setOnClickListener {
onItemClick?.invoke(item)
}

View File

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

View File

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

View File

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

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.Looper
import android.util.Log
import com.dzeio.openhealth.extensions.Extension
import com.dzeio.openhealth.data.weight.Weight
import com.samsung.android.sdk.healthdata.*
import com.samsung.android.sdk.healthdata.HealthConnectionErrorResult
import com.samsung.android.sdk.healthdata.HealthConstants.StepCount
import com.samsung.android.sdk.healthdata.HealthDataStore
import com.samsung.android.sdk.healthdata.HealthDataStore.ConnectionListener
import com.samsung.android.sdk.healthdata.HealthPermissionManager
import com.samsung.android.sdk.healthdata.HealthPermissionManager.*
@ -18,7 +19,7 @@ import com.samsung.android.sdk.healthdata.HealthPermissionManager.*
*/
class SamsungHealth(
private val context: Activity
) : Extension() {
) {
companion object {
const val TAG = "SamsungHealthConnector"
@ -33,6 +34,7 @@ class SamsungHealth(
requestPermission()
}
}
override fun onConnectionFailed(p0: HealthConnectionErrorResult?) {
Log.d(TAG, "Health data service is not available.")
}
@ -86,23 +88,25 @@ class SamsungHealth(
}
}
private val reporter = StepCountReporter(store, stepCountObserver, Handler(Looper.getMainLooper()))
private val reporter =
StepCountReporter(store, stepCountObserver, Handler(Looper.getMainLooper()))
/**
* Connector
*/
override val sourceID: String = "SamsungHealth"
val sourceID: String = "SamsungHealth"
override fun onRequestPermissionResult(
fun onRequestPermissionResult(
requestCode: Int,
permission: Array<String>,
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()
}
}

View File

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

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

View File

@ -1,9 +1,9 @@
package com.dzeio.openhealth.ui.home
import android.animation.ValueAnimator
import android.graphics.BitmapFactory
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Rect
import android.graphics.RectF
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
@ -13,28 +13,21 @@ import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import androidx.preference.PreferenceManager
import com.dzeio.openhealth.Application
import com.dzeio.openhealth.R
import com.dzeio.openhealth.core.BaseFragment
import com.dzeio.openhealth.data.water.Water
import com.dzeio.openhealth.databinding.FragmentHomeBinding
import com.dzeio.openhealth.data.weight.Weight
import com.dzeio.openhealth.databinding.FragmentHomeBinding
import com.dzeio.openhealth.ui.weight.AddWeightDialog
import com.dzeio.openhealth.utils.BitmapUtils
import com.dzeio.openhealth.utils.DrawUtils
import com.github.mikephil.charting.components.AxisBase
import com.github.mikephil.charting.components.Description
import com.github.mikephil.charting.components.XAxis
import com.dzeio.openhealth.utils.GraphUtils
import com.github.mikephil.charting.data.Entry
import com.github.mikephil.charting.data.LineData
import com.github.mikephil.charting.data.LineDataSet
import com.github.mikephil.charting.formatter.ValueFormatter
import com.google.android.material.color.MaterialColors
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.*
import kotlin.math.min
import kotlin.properties.Delegates
@ -53,8 +46,6 @@ class HomeFragment : BaseFragment<HomeViewModel, FragmentHomeBinding>(HomeViewMo
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.init()
binding.addWeight.setOnClickListener {
AddWeightDialog().show(requireActivity().supportFragmentManager, null)
}
@ -112,14 +103,23 @@ class HomeFragment : BaseFragment<HomeViewModel, FragmentHomeBinding>(HomeViewMo
}
binding.listWeight.setOnClickListener {
Log.d("T", "Trying to move")
findNavController().navigate(HomeFragmentDirections.actionNavHomeToNavListWeight())
}
binding.gotoWaterHome.setOnClickListener {
findNavController().navigate(HomeFragmentDirections.actionNavHomeToNavWaterHome())
}
GraphUtils.lineChartSetup(
binding.weightGraph,
MaterialColors.getColor(
requireView(),
com.google.android.material.R.attr.colorPrimary
), MaterialColors.getColor(
requireView(),
com.google.android.material.R.attr.colorOnBackground
)
)
}
private fun updateGraph(list: List<Weight>) {
@ -130,35 +130,9 @@ class HomeFragment : BaseFragment<HomeViewModel, FragmentHomeBinding>(HomeViewMo
}
val dataSet = LineDataSet(entries, "Label")
binding.weightGraph.apply {
// Setup
isAutoScaleMinMaxEnabled = true
legend.isEnabled = false
isDragEnabled = true
isScaleYEnabled = false
description = Description().apply { isEnabled = false }
isScaleXEnabled = true
setPinchZoom(false)
setDrawGridBackground(false)
setDrawBorders(false)
axisLeft.setLabelCount(0, true)
xAxis.apply {
valueFormatter = object : ValueFormatter() {
override fun getAxisLabel(value: Float, axis: AxisBase?): String {
return SimpleDateFormat(
"yyyy-MM-dd",
Locale.getDefault()
).format(Date(value.toLong()))
//return super.getAxisLabel(value, axis)
}
}
position = XAxis.XAxisPosition.BOTTOM
setDrawGridLines(false)
setLabelCount(3, true)
}
// Apply new dataset
data = LineData(dataSet)
@ -181,6 +155,8 @@ class HomeFragment : BaseFragment<HomeViewModel, FragmentHomeBinding>(HomeViewMo
viewModel.fetchWeights().collectLatest {
updateGraph(it)
}
updateWater(0)
updateWater(1234)
}
viewModel.water.observe(viewLifecycleOwner) {
@ -192,32 +168,70 @@ class HomeFragment : BaseFragment<HomeViewModel, FragmentHomeBinding>(HomeViewMo
}
}
updateWater(0)
}
private fun updateWater(water: Int) {
val oldValue = binding.fragmentHomeWaterCurrent.text.toString().replace("ml", "").toInt()
binding.fragmentHomeWaterCurrent.text = "${water}ml"
val graph = BitmapUtils.convertToMutable(
requireContext(),
BitmapFactory.decodeResource(resources, R.drawable.ellipse)
var width = 1500
var height = 750
if (binding.background.width != 0) {
width = binding.background.width
height = binding.background.height
}
val graph = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
Log.d("Test2", "$width $height")
val canvas = Canvas(graph)
val rect = RectF(
10f,
15f,
90f,
85f
)
graph?.let { btmp ->
// DrawUtils.drawRect(
// canvas,
// RectF(
// 0f,
// 0f,
// 100f,
// 100f
// ),
// MaterialColors.getColor(
// requireView(),
// com.google.android.material.R.attr.colorOnPrimary
// ),
// 3f
// )
DrawUtils.drawArc(
canvas,
100f,
rect,
MaterialColors.getColor(
requireView(),
com.google.android.material.R.attr.colorOnPrimary
),
3f
)
Log.d("Test", "${min(oldValue.toFloat(), intake)} ${min(water.toFloat(), intake)}")
ValueAnimator.ofFloat(min(oldValue.toFloat(), intake), min(water.toFloat(), intake))
.apply {
duration = 300
addUpdateListener {
val canvas = Canvas(btmp)
DrawUtils.drawArc(
canvas,
100 * it.animatedValue as Float / intake,
rect,
MaterialColors.getColor(
requireView(),
com.google.android.material.R.attr.colorPrimary
)
), 6f
)
canvas.save()
binding.background.setImageBitmap(graph)
@ -226,4 +240,3 @@ class HomeFragment : BaseFragment<HomeViewModel, FragmentHomeBinding>(HomeViewMo
}
}
}
}

View File

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

View File

@ -1,7 +1,9 @@
package com.dzeio.openhealth.ui.water
import android.app.Dialog
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.Window
@ -13,8 +15,12 @@ import androidx.navigation.fragment.navArgs
import com.dzeio.openhealth.R
import com.dzeio.openhealth.core.BaseFullscreenDialog
import com.dzeio.openhealth.databinding.DialogWaterEditWaterBinding
import com.google.android.material.datepicker.CalendarConstraints
import com.google.android.material.datepicker.DateValidatorPointBackward
import com.google.android.material.datepicker.MaterialDatePicker
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
import java.util.*
@AndroidEntryPoint
class EditWaterDialog :
@ -40,13 +46,53 @@ class EditWaterDialog :
viewModel.water.observe(viewLifecycleOwner) {
binding.editTextNumber.setText(it.value.toString())
binding.date.text = it.formatTimestamp()
}
binding.editTextNumber.doOnTextChanged { text, start, before, count ->
newValue = text.toString().toInt()
val value = text.toString()
newValue = if (value == "") 0
else text.toString().toInt()
}
binding.date.setOnClickListener {
val water = viewModel.water.value!!
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val date = Date(water.timestamp)
val datePicker = MaterialDatePicker.Builder.datePicker()
.setTitleText("Select Date")
.setSelection(water.timestamp)
.setCalendarConstraints(
CalendarConstraints.Builder()
.setValidator(DateValidatorPointBackward.now())
.setEnd(Date().time)
.build()
)
.build()
val fragManager = requireActivity().supportFragmentManager
datePicker.addOnPositiveButtonClickListener { tsp ->
water.timestamp = tsp
binding.date.setText(water.formatTimestamp())
}
datePicker.show(fragManager, "dialog")
Log.d("Tag", "${date.year + 1900}, ${date.month}, ${date.day}")
// val dg = DatePickerDialog(requireActivity())
// dg.setOnDateSetListener { _, year, month, day ->
//
// }
// dg.updateDate(date.year + 1900, date.month, date.day)
// dg.show()
} else {
TODO("VERSION.SDK_INT < N")
}
}
viewModel.init(args.id)
}
private fun save() {

View File

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

View File

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

View File

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

View File

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

View File

@ -1,28 +1,21 @@
<?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:tools="http://schemas.android.com/tools"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:openDrawer="start"
tools:context=".MainActivity">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
style="?attr/appBarLayoutStyle"
style="@style/Widget.Material3.AppBarLayout"
android:layout_height="wrap_content">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
style="?attr/toolbarStyle"
app:titleCentered="true"
style="@style/ThemeOverlay.Material3.Toolbar.Surface"
android:layout_height="?attr/actionBarSize" />
</com.google.android.material.appbar.AppBarLayout>
@ -51,5 +44,3 @@
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</androidx.drawerlayout.widget.DrawerLayout>

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<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"
android:layout_width="match_parent"
android:layout_height="match_parent">
@ -8,8 +9,20 @@
android:id="@+id/editTextNumber"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:ems="10"
android:inputType="number"
tools:layout_editor_absoluteX="101dp"
tools:layout_editor_absoluteY="107dp" />
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<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>

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"?>
<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"
android:orientation="vertical"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
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>
android:id="@+id/list"
tools:listitem="@layout/layout_extension_item"
tools:context=".ui.extensions.ExtensionsFragment" />

View File

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

View File

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

View File

@ -56,7 +56,6 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
tools:listitem="@layout/layout_item_weight" />
tools:listitem="@layout/layout_item_list" />
</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"
tools:layout="@layout/fragment_extensions" >
<action
android:id="@+id/action_nav_import_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" />
android:id="@+id/action_nav_extensions_to_nav_extension"
app:destination="@id/nav_extension" />
</fragment>
<fragment
@ -66,13 +62,6 @@
app:exitAnim="@android:anim/slide_out_right"
app:popEnterAnim="@android:anim/slide_in_left"
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
@ -93,15 +82,12 @@
android:label="@string/nav_water_home"
tools:layout="@layout/fragment_main_water_home">
<action
android:id="@+id/action_nav_water_home_to_nav_settings"
app:destination="@id/nav_settings"
android:id="@+id/action_nav_water_home_to_nav_water_edit"
app:destination="@id/nav_water_edit"
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" />
<action
android:id="@+id/action_nav_water_home_to_nav_water_edit"
app:destination="@id/nav_water_edit" />
</fragment>
<fragment
@ -124,4 +110,19 @@
app:argType="long" />
</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>

View File

@ -16,4 +16,5 @@
<string name="nav_list_water">Water Intake</string>
<string name="nav_water_home">Water Intake</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>

View File

@ -1,29 +1,10 @@
<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>
<item name="materialAlertDialogTheme">@style/ThemeOverlay.Material3.MaterialAlertDialog</item>
</style>
<style name="Theme.OpenHealth" parent="Theme.Material3.DayNight.NoActionBar" />
<style name="Theme.OpenHealth.NoActionBar">
<style name="Theme.OpenHealth.NoActionBar" parent="Theme.OpenHealth">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>
<style name="Theme.OpenHealth.AppBarOverlay" parent="ThemeOverlay.MaterialComponents.Dark.ActionBar" />
<style name="Theme.OpenHealth.PopupOverlay" parent="ThemeOverlay.Material3.MaterialAlertDialog" />
</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.