refactor: Create gradlew_recursive.sh (#8)
37
.github/scripts/gradlew_recursive.sh
vendored
Normal 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
|
||||
|
34
.github/workflows/build.yml
vendored
@ -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
@ -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
@ -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>
|
||||
|
214
Gemfile.lock
Normal 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
|
@ -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"
|
||||
|
BIN
app/src/debug/ic_launcher-playstore.png
Normal file
After Width: | Height: | Size: 13 KiB |
47
app/src/debug/res/drawable-v24/ic_launcher_foreground.xml
Normal 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>
|
42
app/src/debug/res/drawable/ic_logo_app.xml
Normal 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>
|
5
app/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml
Normal 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>
|
@ -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>
|
BIN
app/src/debug/res/mipmap-hdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
app/src/debug/res/mipmap-hdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 4.3 KiB |
BIN
app/src/debug/res/mipmap-mdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
app/src/debug/res/mipmap-mdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
app/src/debug/res/mipmap-xhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 3.0 KiB |
BIN
app/src/debug/res/mipmap-xhdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 5.6 KiB |
BIN
app/src/debug/res/mipmap-xxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 3.8 KiB |
BIN
app/src/debug/res/mipmap-xxhdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 9.2 KiB |
BIN
app/src/debug/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 5.6 KiB |
BIN
app/src/debug/res/mipmap-xxxhdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 13 KiB |
4
app/src/debug/res/values/ic_launcher_background.xml
Normal file
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#FFFFFF</color>
|
||||
</resources>
|
4
app/src/debug/res/values/strings.xml
Normal file
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">OpenHealth - Debug</string>
|
||||
</resources>
|
@ -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-->
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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")
|
||||
|
@ -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(),
|
||||
|
@ -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
|
||||
*/
|
@ -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) {}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
}
|
@ -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()
|
||||
}
|
||||
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
@ -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.
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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() {
|
||||
|
@ -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()
|
||||
)
|
||||
},
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
||||
|
@ -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>
|
@ -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>
|
39
app/src/main/res/layout/fragment_extension.xml
Normal 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>
|
@ -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" />
|
@ -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" />
|
||||
|
@ -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" />
|
@ -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>
|
||||
|
63
app/src/main/res/layout/layout_extension_item.xml
Normal 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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -1,29 +1,10 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- Base application theme. -->
|
||||
<style name="Theme.OpenHealth" parent="Theme.Material3.DynamicColors.DayNight">
|
||||
<!-- <!– Primary brand color. –>-->
|
||||
<!-- <item name="colorPrimary">@color/purple_500</item>-->
|
||||
<!-- <item name="colorPrimaryVariant">@color/purple_700</item>-->
|
||||
<!-- <item name="colorOnPrimary">@color/white</item>-->
|
||||
<!-- <!– Secondary brand color. –>-->
|
||||
<!-- <item name="colorSecondary">@color/teal_200</item>-->
|
||||
<!-- <item name="colorSecondaryVariant">@color/teal_700</item>-->
|
||||
<!-- <item name="colorOnSecondary">@color/black</item>-->
|
||||
<!-- <!– Status bar color. –>-->
|
||||
<!-- <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
@ -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
@ -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
@ -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).
|
1
fastlane/metadata/android/en-GB/title.txt
Normal file
@ -0,0 +1 @@
|
||||
Open Health
|
0
fastlane/metadata/android/en-GB/video.txt
Normal file
25
fastlane/report.xml
Normal file
12
fastlane_secret_keys.json
Normal 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
@ -0,0 +1,4 @@
|
||||
storeFile=../upload_key.jks
|
||||
keyAlias=release_key
|
||||
keyPassword=Babacarflo22
|
||||
storePassword=Babacarflo22
|