1
0
mirror of https://github.com/dzeiocom/OpenHealth.git synced 2025-04-22 10:52:13 +00:00

feat: Global update

This commit is contained in:
Florian Bouillon 2022-08-22 00:20:11 +02:00
parent 17f3a850c7
commit 5049c8afa4
75 changed files with 1646 additions and 1211 deletions

1
.bundle/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
vendor

2
.bundle/config Normal file
View File

@ -0,0 +1,2 @@
---
BUNDLE_PATH: ".bundle/vendor"

3
.gitignore vendored
View File

@ -20,3 +20,6 @@ fastlane_secret_keys.json
# App
/app/debug
/app/release
# Fastlane / Ruby
/vendor

View File

@ -8,23 +8,23 @@ GEM
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-partitions (1.615.0)
aws-sdk-core (3.131.6)
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)
jmespath (~> 1, >= 1.6.1)
aws-sdk-kms (1.58.0)
aws-sdk-core (~> 3, >= 3.127.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.110.0)
aws-sdk-core (~> 3, >= 3.125.0)
aws-sdk-s3 (1.114.0)
aws-sdk-core (~> 3, >= 3.127.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.4)
aws-sigv4 (1.4.0)
aws-sigv4 (1.5.1)
aws-eventstream (~> 1, >= 1.0.2)
babosa (1.0.4)
claide (1.0.3)
claide (1.1.0)
colored (1.2)
colored2 (3.1.2)
commander (4.6.0)
@ -34,19 +34,20 @@ GEM
rake (>= 12.0.0, < 14.0.0)
domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0)
dotenv (2.7.6)
dotenv (2.8.1)
emoji_regex (3.2.3)
excon (0.89.0)
faraday (1.8.0)
excon (0.92.4)
faraday (1.10.0)
faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0)
faraday-excon (~> 1.1)
faraday-httpclient (~> 1.0.1)
faraday-httpclient (~> 1.0)
faraday-multipart (~> 1.0)
faraday-net_http (~> 1.0)
faraday-net_http_persistent (~> 1.1)
faraday-net_http_persistent (~> 1.0)
faraday-patron (~> 1.0)
faraday-rack (~> 1.0)
multipart-post (>= 1.2, < 3)
faraday-retry (~> 1.0)
ruby2_keywords (>= 0.0.4)
faraday-cookie_jar (0.0.7)
faraday (>= 0.8.0)
@ -55,14 +56,17 @@ GEM
faraday-em_synchrony (1.0.0)
faraday-excon (1.1.0)
faraday-httpclient (1.0.1)
faraday-multipart (1.0.4)
multipart-post (~> 2)
faraday-net_http (1.0.1)
faraday-net_http_persistent (1.2.0)
faraday-patron (1.0.0)
faraday-rack (1.0.0)
faraday-retry (1.0.3)
faraday_middleware (1.2.0)
faraday (~> 1.0)
fastimage (2.2.6)
fastlane (2.199.0)
fastlane (2.208.0)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.8, < 3.0.0)
artifactory (~> 3.0)
@ -102,9 +106,9 @@ GEM
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)
google-apis-androidpublisher_v3 (0.25.0)
google-apis-core (>= 0.7, < 2.a)
google-apis-core (0.7.0)
addressable (~> 2.5, >= 2.5.1)
googleauth (>= 0.16.2, < 2.a)
httpclient (>= 2.8.1, < 3.a)
@ -113,40 +117,40 @@ GEM
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-apis-iamcredentials_v1 (0.13.0)
google-apis-core (>= 0.7, < 2.a)
google-apis-playcustomapp_v1 (0.10.0)
google-apis-core (>= 0.7, < 2.a)
google-apis-storage_v1 (0.17.0)
google-apis-core (>= 0.7, < 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-env (1.6.0)
faraday (>= 0.17.3, < 3.0)
google-cloud-errors (1.2.0)
google-cloud-storage (1.35.0)
google-cloud-storage (1.38.0)
addressable (~> 2.8)
digest-crc (~> 0.4)
google-apis-iamcredentials_v1 (~> 0.1)
google-apis-storage_v1 (~> 0.1)
google-apis-storage_v1 (~> 0.17.0)
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)
googleauth (1.2.0)
faraday (>= 0.17.3, < 3.a)
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)
http-cookie (1.0.5)
domain_name (~> 0.5)
httpclient (2.8.3)
jmespath (1.6.1)
json (2.6.1)
jwt (2.3.0)
json (2.6.2)
jwt (2.4.1)
memoist (0.16.2)
mini_magick (4.11.0)
mini_mime (1.1.2)
@ -157,9 +161,9 @@ GEM
optparse (0.1.1)
os (1.1.4)
plist (3.6.0)
public_suffix (4.0.6)
public_suffix (4.0.7)
rake (13.0.6)
representable (3.1.1)
representable (3.2.0)
declarative (< 0.1.0)
trailblazer-option (>= 0.1.1, < 0.2.0)
uber (< 0.2.0)
@ -169,9 +173,9 @@ GEM
ruby2_keywords (0.0.5)
rubyzip (2.3.2)
security (0.1.3)
signet (0.16.0)
signet (0.17.0)
addressable (~> 2.8)
faraday (>= 0.17.3, < 2.0)
faraday (>= 0.17.5, < 3.a)
jwt (>= 1.5, < 3.0)
multi_json (~> 1.10)
simctl (1.6.8)
@ -188,12 +192,12 @@ GEM
uber (0.1.0)
unf (0.1.4)
unf_ext
unf_ext (0.0.8)
unf_ext (0.0.8-x64-mingw32)
unf_ext (0.0.8.2)
unf_ext (0.0.8.2-x64-mingw32)
unicode-display_width (1.8.0)
webrick (1.7.0)
word_wrap (1.0.0)
xcodeproj (1.21.0)
xcodeproj (1.22.0)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0)
@ -213,4 +217,4 @@ DEPENDENCIES
fastlane
BUNDLED WITH
2.2.32
2.3.18

View File

@ -25,7 +25,7 @@ No Ads are served through this app.
Permissions requests are for specifics usage and are only requests the first time they are needed:
| Permission | Why is it requested |
| :--------------------: | :--------------------------------------------------------------- |
| :--------------------: |:-----------------------------------------------------------------|
| ACCESS_FINE_LOCATION | Google Fit Extension Requirement (maybe not, still have to test) |
| ACCESS_COARSE_LOCATION | Same as above |
| ACTIVITY_RECOGNITION | Device Steps Usage |

View File

@ -1,166 +0,0 @@
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'kotlin-kapt'
id 'dagger.hilt.android.plugin'
// Safe Navigation
id 'androidx.navigation.safeargs'
}
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 32
defaultConfig {
// App ID
applicationId "com.dzeio.openhealth"
// Android 5 Lollipop
minSdk 21
// Android 12
targetSdk 32
// Semantic Versioning
versionName "1.0.0"
versionCode 1
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
// Languages
def locales = ["en", "fr"]
buildConfigField "String[]", "LOCALES", "new String[]{\""+locales.join("\",\"")+"\"}"
resConfigs locales
}
buildTypes {
release {
// Slimmer version
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
}
}
// Compile using JAVA 8
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
// Enable View Binding and Data Binding
buildFeatures {
viewBinding true
dataBinding true
}
}
dependencies {
// Dzeio Charts
implementation project(path: ":charts")
// Core dependencies
implementation 'androidx.core:core-ktx:1.8.0'
implementation 'androidx.appcompat:appcompat:1.4.2'
implementation 'javax.inject:javax.inject:1'
implementation 'com.google.android.material:material:1.7.0-alpha03'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.5.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.0'
// Coroutines
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.3"
// Settings
implementation "androidx.preference:preference-ktx:1.2.0"
// DataStore
implementation "androidx.datastore:datastore:1.0.0"
// Navigation
implementation 'androidx.navigation:navigation-fragment-ktx:2.5.0'
implementation 'androidx.navigation:navigation-ui-ktx:2.5.0'
// Paging
implementation "androidx.paging:paging-runtime:3.1.1"
implementation "androidx.paging:paging-runtime-ktx:3.1.1"
// Services
implementation 'androidx.work:work-runtime-ktx:2.7.1'
// Tests
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
// Graph
implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0'
// Graphs test 2
implementation 'com.github.HackPlan:AndroidCharts:1.0.4'
// Hilt
implementation 'com.google.dagger:hilt-android:2.43'
kapt 'com.google.dagger:hilt-compiler:2.43'
// Google Fit
implementation "com.google.android.gms:play-services-fitness:21.1.0"
implementation "com.google.android.gms:play-services-auth:20.2.0"
// Samsung Health
implementation files('libs/samsung-health-data-1.5.0.aar')
implementation 'com.google.code.gson:gson:2.9.0'
// ROOM
implementation "androidx.room:room-runtime:2.4.2"
kapt "androidx.room:room-compiler:2.4.2"
implementation "androidx.room:room-ktx:2.4.2"
testImplementation "androidx.room:room-testing:2.4.2"
// Futures
implementation 'com.google.guava:guava:31.1-jre'
implementation "androidx.concurrent:concurrent-futures:1.1.0"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-guava:1.6.3'
}

174
app/build.gradle.kts Normal file
View File

@ -0,0 +1,174 @@
import java.util.Properties
plugins {
id("com.android.application")
kotlin("android")
kotlin("kapt")
id("dagger.hilt.android.plugin")
// Safe Navigation
id("androidx.navigation.safeargs")
}
android {
signingConfigs {
create("release") {
val keystoreProperties = Properties().apply {
load(rootProject.file("./keystore.properties").reader())
}
if (keystoreProperties.isNotEmpty()) {
storePassword = keystoreProperties["storePassword"] as String
keyPassword = keystoreProperties["keyPassword"] as String
keyAlias = keystoreProperties["keyAlias"] as String
storeFile = file(keystoreProperties["storeFile"] as String)
}
}
}
compileSdk = 33
defaultConfig {
// App ID
applicationId = "com.dzeio.openhealth"
// Android 5 Lollipop
minSdk = 21
// Android 12
targetSdk = 33
// Semantic Versioning
versionName = "1.0.0"
versionCode = 1
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
// Languages
val locales = listOf("en", "fr")
buildConfigField(
"String[]",
"LOCALES",
"new String[]{\"" + locales.joinToString("\",\"") + "\"}"
)
resourceConfigurations += locales
}
buildTypes {
getByName("release") {
// Slimmer version
isMinifyEnabled = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
signingConfig = signingConfigs.getByName("release")
}
getByName("debug") {
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
applicationIdSuffix = ".dev"
versionNameSuffix = "-dev"
isDebuggable = true
// make it debuggable
isRenderscriptDebuggable = true
// Optimization Level
renderscriptOptimLevel = 0
}
}
// Compile using JAVA 8
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
// Enable View Binding and Data Binding
buildFeatures {
viewBinding = true
dataBinding = true
}
namespace = "com.dzeio.openhealth"
}
dependencies {
// Dzeio Charts
implementation(project(":charts"))
// implementation(project(":CrashHandler"))
implementation("com.dzeio:crashhandler:1.0.1")
// Core dependencies
implementation("androidx.core:core-ktx:1.8.0")
implementation("androidx.appcompat:appcompat:1.6.0-alpha05")
implementation("javax.inject:javax.inject:1")
implementation("com.google.android.material:material:1.7.0-alpha03")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.5.1")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1")
// implementation("com.github.Aviortheking:crashhandler:0.2.3")
// Coroutines
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4")
// Settings
implementation("androidx.preference:preference-ktx:1.2.0")
// DataStore
implementation("androidx.datastore:datastore:1.0.0")
// Navigation
implementation("androidx.navigation:navigation-fragment-ktx:2.5.1")
implementation("androidx.navigation:navigation-ui-ktx:2.5.1")
// Paging
implementation("androidx.paging:paging-runtime:3.1.1")
implementation("androidx.paging:paging-runtime-ktx:3.1.1")
// Services
implementation("androidx.work:work-runtime-ktx:2.7.1")
// Tests
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.3")
androidTestImplementation("androidx.test.espresso:espresso-core:3.4.0")
// Graph
implementation("com.github.PhilJay:MPAndroidChart:v3.1.0")
// Graphs test 2
implementation("com.github.HackPlan:AndroidCharts:1.0.4")
// Hilt
implementation("com.google.dagger:hilt-android:2.43.2")
kapt("com.google.dagger:hilt-compiler:2.43.2")
// Google Fit
implementation("com.google.android.gms:play-services-fitness:21.1.0")
implementation("com.google.android.gms:play-services-auth:20.2.0")
// Samsung Health
implementation(files("libs/samsung-health-data-1.5.0.aar"))
implementation("com.google.code.gson:gson:2.9.1")
// ROOM
implementation("androidx.room:room-runtime:2.4.3")
kapt("androidx.room:room-compiler:2.4.3")
implementation("androidx.room:room-ktx:2.4.3")
testImplementation("androidx.room:room-testing:2.4.3")
// Futures
implementation("com.google.guava:guava:31.1-jre")
implementation("androidx.concurrent:concurrent-futures:1.1.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-guava:1.6.4")
}

View File

@ -1,6 +1,6 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
# proguardFiles setting in build.gradle.kts.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

View File

@ -1,7 +1,7 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:width="80dp"
android:height="80dp"
android:viewportWidth="108"
android:viewportHeight="108">
<group android:scaleX="0.15074074"

View File

@ -2,4 +2,5 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/vector_logo_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

View File

@ -1,6 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.dzeio.openhealth">
<manifest xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Notifications -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<!-- Google Fit -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
@ -15,11 +18,11 @@
</queries>
<application
android:name=".Application"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:name=".Application"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:localeConfig="@xml/locales"
android:supportsRtl="true"
android:theme="@style/Theme.OpenHealth">
@ -44,14 +47,22 @@
</intent-filter>
</activity>
<!-- Activity for error handling -->
<activity android:name=".ui.ErrorActivity"
android:theme="@style/Theme.OpenHealth"
android:exported="false" />
<service
android:name=".services.OpenHealthService"
android:permission="android.permission.ACTIVITY_RECOGNITION" />
<service
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
android:enabled="false"
android:exported="false">
<meta-data
android:name="autoStoreLocales"
android:value="true" />
</service>
<activity android:name=".ui.ErrorActivity"
android:theme="@style/Theme.OpenHealth.NoActionBar"
android:exported="false" />
</application>
</manifest>

View File

@ -1,16 +1,13 @@
package com.dzeio.openhealth
import android.app.Application
import android.content.Intent
import android.content.SharedPreferences
import android.os.Process
import android.util.Log
import android.content.Context
import androidx.preference.PreferenceManager
import com.dzeio.crashhandler.CrashHandler
import com.dzeio.openhealth.ui.ErrorActivity
import com.dzeio.openhealth.utils.LocaleUtils
import com.google.android.material.color.DynamicColors
import dagger.hilt.android.HiltAndroidApp
import java.util.Locale
import kotlin.system.exitProcess
@HiltAndroidApp
class Application : Application() {
@ -20,42 +17,24 @@ class Application : Application() {
override fun onCreate() {
// Application Error Handling
Thread.setDefaultUncaughtExceptionHandler { paramThread, paramThrowable ->
//Log error to logcat if it wasn't done before
Log.e(TAG, "En error was detected in the Thread ${paramThread.id}, trying to go to ErrorActivity")
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
// send use to the Error Activity
val intent = Intent(applicationContext, ErrorActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
intent.putExtra("error", paramThrowable.stackTraceToString())
intent.putExtra("threadId", paramThread.id)
startActivity(intent)
Log.e(TAG, "Activity should have started")
Process.killProcess(Process.myPid())
exitProcess(10)
}
CrashHandler.Builder()
.withActivity(ErrorActivity::class.java)
.withPrefs(prefs)
.witheErrorReporterCrashKey(R.string.error_reporter_crash)
.withPrefsKey(Settings.CRASH_LAST_TIME)
.withPrefix("${BuildConfig.APPLICATION_ID} v${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})")
.build()
.setup(this)
// Android Dynamics Colors
DynamicColors.applyToActivitiesIfAvailable(this)
// Change application Language based on setting
val preferences: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
val lang = preferences.getString("global_language", Locale.getDefault().language)
if (lang == null) {
Log.e(TAG, "lang is null")
} else {
val locale = Locale(lang)
Locale.setDefault(locale)
}
// val overrideConfiguration = baseContext.resources.configuration
// overrideConfiguration.locale = locale
// val context: Context = createConfigurationContext(overrideConfiguration)
// val resources: Resources = context.resources
super.onCreate()
}
override fun attachBaseContext(base: Context) {
super.attachBaseContext(LocaleUtils.onAttach(base))
}
}

View File

@ -0,0 +1,19 @@
package com.dzeio.openhealth
object Settings {
/**
* get the last time the Application has crashed
*
* type: Long
*/
const val CRASH_LAST_TIME = "com.dzeio.open-health.crash.last_time"
/**
* The software override application language
*
* note: also change it in `preferences.xml`
*/
const val APP_LANGUAGE = "com.dzeio.open-health.app.language"
}

View File

@ -10,8 +10,6 @@ import android.util.Log
import androidx.preference.PreferenceManager
import com.dzeio.openhealth.Application
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.runBlocking
class StepSource(
@ -78,8 +76,10 @@ class StepSource(
timeSinceLastRecord = timeSinceLastBoot
runBlocking {
if (events != null) {
events.send(diff)
}
}
callback?.invoke(diff)
}
}

View File

@ -120,9 +120,9 @@ class OpenHealthService : Service() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) PendingIntent.FLAG_IMMUTABLE else 0
val intent = NavDeepLinkBuilder(this)
.setGraph(R.navigation.mobile_navigation)
.setDestination(R.id.nav_home)
// .setDestination(R.id.nav_home)
// Will nav to water home when there will be a way to add it there
// .setDestination(R.id.nav_water_home)
.setDestination(R.id.nav_steps_home)
.createTaskStackBuilder()
.getPendingIntent(0, flag)

View File

@ -17,7 +17,7 @@ import kotlin.system.exitProcess
class ErrorActivity : BaseActivity<ActivityErrorBinding>() {
companion object {
const val TAG = "${Application.TAG}/ErrorActivity"
const val TAG = "${Application.TAG}/ErrorActivit"
}
override val bindingInflater: (LayoutInflater) -> ActivityErrorBinding =

View File

@ -1,6 +1,5 @@
package com.dzeio.openhealth.ui
import android.app.ActivityManager
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
@ -12,7 +11,9 @@ import android.util.Log
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuItem
import android.view.WindowManager
import androidx.core.view.WindowCompat
import androidx.core.view.updatePadding
import androidx.navigation.NavController
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.AppBarConfiguration
@ -26,8 +27,8 @@ import com.dzeio.openhealth.core.BaseActivity
import com.dzeio.openhealth.databinding.ActivityMainBinding
import com.dzeio.openhealth.interfaces.NotificationChannels
import com.dzeio.openhealth.services.OpenHealthService
import com.dzeio.openhealth.utils.ServiceUtils
import com.dzeio.openhealth.workers.WaterReminderWorker
import com.google.android.material.color.MaterialColors
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
@ -55,30 +56,54 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
setSupportActionBar(binding.toolbar)
window.navigationBarColor = MaterialColors.getColor(
window.decorView,
com.google.android.material.R.attr.colorSecondaryContainer
)
window.statusBarColor = MaterialColors.getColor(
window.decorView,
com.google.android.material.R.attr.colorSecondaryContainer
)
// Comportement chelou API 28-
// Comportement normal 31+
// do not do the cool status/navigation bars for API 29 & 30
if (Build.VERSION.SDK_INT != Build.VERSION_CODES.R && Build.VERSION.SDK_INT != Build.VERSION_CODES.Q) {
// allow to put the content behind the status bar & Navigation bar (one of them at least lul)
// ALSO: make the status/navigation bars semi-transparent
window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION)
window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
// Make the color of the navigation bar semi-transparent
// window.navigationBarColor = Color.TRANSPARENT
// Make the color of the status bar transparent
// window.statusBarColor = Color.TRANSPARENT
// Apply the previous changes
// window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
// Update toolbar height with the statusbar size included
// ALSO: make both the status/navigation bars transparent (WHYYYYYYY)
val toolbarHeight = binding.toolbar.layoutParams.height
window.decorView.setOnApplyWindowInsetsListener { _, insets ->
val statusBarSize = insets.systemWindowInsetTop
// Add padding to the toolbar (YaY I know how something works)
binding.toolbar.updatePadding(top = statusBarSize)
binding.toolbar.layoutParams.height = toolbarHeight + statusBarSize
return@setOnApplyWindowInsetsListener insets
}
// normally makes sure icons are at the correct color but idk if it works
when (this.resources.configuration.uiMode.and(Configuration.UI_MODE_NIGHT_MASK)) {
Configuration.UI_MODE_NIGHT_YES -> {
WindowCompat.getInsetsController(window, window.decorView).apply {
// force to display the bars in light color
isAppearanceLightNavigationBars = true
isAppearanceLightStatusBars = false
isAppearanceLightStatusBars = false // WHY
}
}
Configuration.UI_MODE_NIGHT_NO, Configuration.UI_MODE_NIGHT_UNDEFINED -> {
WindowCompat.getInsetsController(window, window.decorView).apply {
// force to display the bars in dark color
isAppearanceLightNavigationBars = false
isAppearanceLightStatusBars = true
isAppearanceLightStatusBars = true // WHY
}
}
}
}
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
@ -108,20 +133,9 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
WaterReminderWorker.setup(this)
// StepCountService.setup(this)
this.betterStartService(OpenHealthService::class.java)
ServiceUtils.startService(this, OpenHealthService::class.java)
}
private fun <T> betterStartService(service: Class<T>) {
val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
for (runninService in activityManager.getRunningServices(Integer.MAX_VALUE)) {
if (service.name.equals(runninService.service.className)) {
Log.w(TAG, "Service already existing, not starting again")
return
}
}
Log.i(TAG, "Starting service ${service.name}")
Intent(this, service).also { intent -> startService(intent) }
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
super.onCreateOptionsMenu(menu)

View File

@ -31,9 +31,9 @@ class BrowseFragment :
private lateinit var button: MaterialCardView
private val activityResult = registerForActivityResult(
ActivityResultContracts.RequestPermission()
ActivityResultContracts.RequestMultiplePermissions()
) {
if (!it) {
if (it.containsValue(false)) {
// TODO: Show a popup with choice to change it
Toast.makeText(requireContext(), R.string.permission_declined, Toast.LENGTH_LONG).show()
return@registerForActivityResult
@ -55,14 +55,29 @@ class BrowseFragment :
binding.steps.setOnClickListener {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val hasPermission = PermissionsManager.hasPermission(
val activityPermission = PermissionsManager.hasPermission(
requireContext(),
Manifest.permission.ACTIVITY_RECOGNITION
)
if (!hasPermission) {
val notificationPermission = Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && PermissionsManager.hasPermission(
requireContext(),
Manifest.permission.POST_NOTIFICATIONS
)
val permissionsToAsk = arrayListOf<String>()
if (!activityPermission) {
permissionsToAsk.add(Manifest.permission.ACTIVITY_RECOGNITION)
}
if (!notificationPermission && Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
permissionsToAsk.add(Manifest.permission.POST_NOTIFICATIONS)
}
if (permissionsToAsk.isNotEmpty()) {
button = binding.steps
activityResult.launch(Manifest.permission.ACTIVITY_RECOGNITION)
activityResult.launch(permissionsToAsk.toTypedArray())
return@setOnClickListener
}
}

View File

@ -5,9 +5,7 @@ import android.content.SharedPreferences
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.RectF
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
@ -37,9 +35,8 @@ class HomeFragment : BaseFragment<HomeViewModel, FragmentHomeBinding>(HomeViewMo
PreferenceManager.getDefaultSharedPreferences(requireContext())
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
override fun onStart() {
super.onStart()
// Bindings
binding.addWeight.setOnClickListener {
AddWeightDialog().show(requireActivity().supportFragmentManager, null)
@ -56,10 +53,12 @@ class HomeFragment : BaseFragment<HomeViewModel, FragmentHomeBinding>(HomeViewMo
viewModel.updateWater(water)
}
}
val waterUnit =
UnitFactory.volume(settings.getString("water_unit", "milliliter") ?: "Milliliter")
binding.fragmentHomeWaterTotal.text =
String.format(
resources.getString(viewModel.waterUnit.unit),
resources.getString(waterUnit.unit),
viewModel.dailyWaterIntake
)
@ -94,6 +93,21 @@ class HomeFragment : BaseFragment<HomeViewModel, FragmentHomeBinding>(HomeViewMo
com.google.android.material.R.attr.colorOnBackground
)
)
lifecycleScope.launchWhenStarted {
viewModel.fetchWeights().collectLatest {
updateGraph(it)
}
updateWater(0)
}
viewModel.water.observe(viewLifecycleOwner) {
if (it != null) {
updateWater(it.value)
} else {
updateWater(0)
}
}
}
private fun updateGraph(list: List<Weight>) {
@ -119,25 +133,6 @@ class HomeFragment : BaseFragment<HomeViewModel, FragmentHomeBinding>(HomeViewMo
// }
}
override fun onStart() {
super.onStart()
lifecycleScope.launchWhenStarted {
viewModel.fetchWeights().collectLatest {
updateGraph(it)
}
updateWater(0)
}
viewModel.water.observe(viewLifecycleOwner) {
if (it != null) {
updateWater(it.value)
} else {
updateWater(0)
}
}
}
private var oldValue = 0f
private fun updateWater(newValue: Int) {
@ -151,6 +146,12 @@ class HomeFragment : BaseFragment<HomeViewModel, FragmentHomeBinding>(HomeViewMo
(newValue * waterUnit.modifier).toInt()
)
binding.fragmentHomeWaterTotal.text =
String.format(
resources.getString(waterUnit.unit),
viewModel.dailyWaterIntake
)
var width = 1500
var height = 750

View File

@ -1,7 +1,7 @@
package com.dzeio.openhealth.ui.settings
import android.content.SharedPreferences
import android.content.res.Configuration
import android.os.Build
import android.os.Bundle
import android.text.InputType
import android.util.Log
@ -9,13 +9,20 @@ import androidx.preference.EditTextPreference
import androidx.preference.ListPreference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceManager
import com.dzeio.openhealth.Application
import com.dzeio.openhealth.BuildConfig
import com.dzeio.openhealth.R
import com.dzeio.openhealth.Settings
import com.dzeio.openhealth.units.UnitFactory
import com.dzeio.openhealth.utils.LocaleUtils
import java.util.Locale
class SettingsFragment : PreferenceFragmentCompat() {
private companion object {
const val TAG = "${Application.TAG}/SttngsFrgmnt"
}
val settings: SharedPreferences by lazy {
PreferenceManager.getDefaultSharedPreferences(requireContext())
}
@ -71,8 +78,8 @@ class SettingsFragment : PreferenceFragmentCompat() {
}
}
val languagesPreference = findPreference<ListPreference>("global_language")
Log.d("TAG", Locale.getDefault().language)
val languagesPreference = findPreference<ListPreference>(Settings.APP_LANGUAGE)
Log.d(TAG, Locale.getDefault().language)
languagesPreference?.apply {
entries = BuildConfig.LOCALES
entryValues = BuildConfig.LOCALES
@ -81,17 +88,10 @@ class SettingsFragment : PreferenceFragmentCompat() {
// Update App Locale
languagesPreference?.setOnPreferenceChangeListener { _, newValue ->
val locale = Locale(newValue as String)
Locale.setDefault(locale)
val config = Configuration()
config.locale = locale
requireActivity().baseContext.resources.updateConfiguration(
config,
requireActivity().baseContext.resources.displayMetrics
)
LocaleUtils.setLanguage(requireContext(), newValue as String)
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
requireActivity().recreate()
}
return@setOnPreferenceChangeListener true
}

View File

@ -13,9 +13,6 @@ import com.dzeio.openhealth.core.BaseFragment
import com.dzeio.openhealth.databinding.FragmentStepsHomeBinding
import com.google.android.material.color.MaterialColors
import dagger.hilt.android.AndroidEntryPoint
import java.text.DateFormat
import java.util.Date
import java.util.Locale
@AndroidEntryPoint
class StepsHomeFragment :
@ -50,66 +47,64 @@ class StepsHomeFragment :
val chart = binding.chart
val serie = BarSerie()
val serie = BarSerie(chart)
chart.series = arrayListOf(serie)
viewModel.items.observe(viewLifecycleOwner) { list ->
adapter.set(list)
// chart.debug = true
chart.xAxis.entriesDisplayed = 10
// chart.numberOfLabels = 2
chart.debug = true
// chart.animation.enabled = false
chart.animation.refreshRate = 60
chart.animation.duration = 300
// chart.animation.refreshRate = 60
// chart.animation.duration = 300
chart.scroller.zoomEnabled = false
// chart.scroller.zoomEnabled = false
chart.xAxis.labels.color = MaterialColors.getColor(
requireView(),
com.google.android.material.R.attr.colorOnBackground
)
// chart.xAxis.labels.color = MaterialColors.getColor(
// requireView(),
// com.google.android.material.R.attr.colorOnBackground
// )
chart.xAxis.labels.size = 32f
// chart.xAxis.labels.size = 32f
chart.yAxis.apply {
color = MaterialColors.getColor(
requireView(),
com.google.android.material.R.attr.colorPrimary
)
textPaint.color = MaterialColors.getColor(
setYMin(0f)
textLabel.color = MaterialColors.getColor(
requireView(),
com.google.android.material.R.attr.colorOnBackground
)
linePaint.color = MaterialColors.getColor(
requireView(),
com.google.android.material.R.attr.colorOutline
)
onValueFormat = onValueFormat@{ value, short ->
if (short) {
return@onValueFormat value.toInt().toString()
} else {
return@onValueFormat "${value.toInt()} steps"
}
}
// linePaint.color = MaterialColors.getColor(
// requireView(),
// com.google.android.material.R.attr.colorOutline
// )
// onValueFormat = onValueFormat@{ value, short ->
// if (short) {
// return@onValueFormat value.toInt().toString()
// } else {
// return@onValueFormat "${value.toInt()} steps"
// }
// }
}
serie.datas = list.reversed().map {
serie.entries = list.reversed().map {
return@map Entry(it.timestamp.toDouble(), it.value.toFloat())
} as ArrayList<Entry>
chart.xAxis.onValueFormat = onValueFormat@{
val formatter = DateFormat.getDateTimeInstance(
DateFormat.SHORT,
DateFormat.SHORT,
Locale.getDefault()
)
return@onValueFormat formatter.format(Date(it.toLong()))
chart.xAxis.apply {
increment = (1000 * 60 * 60).toDouble()
displayCount = 24 * 7
x = serie.entries.first().x
}
// chart.yAxis.max = (total / list.size).toInt()
// chart.xAxis.onValueFormat = onValueFormat@{
// val formatter = DateFormat.getDateTimeInstance(
// DateFormat.SHORT,
// DateFormat.SHORT,
// Locale.getDefault()
// )
// return@onValueFormat formatter.format(Date(it.toLong()))
// }
chart.refresh()
}

View File

@ -0,0 +1,76 @@
package com.dzeio.openhealth.utils
import android.app.LocaleManager
import android.content.Context
import android.content.res.Configuration
import android.os.Build
import android.os.LocaleList
import android.util.Log
import androidx.preference.PreferenceManager
import com.dzeio.openhealth.Settings
import java.util.Locale
/**
* Utils object for [Locale]
*
* @see https://github.com/gunhansancar/ChangeLanguageExample/blob/master/app/src/main/java/com/gunhansancar/changelanguageexample/helper/LocaleHelper.java
*/
object LocaleUtils {
fun onAttach(context: Context): Context {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
val lang = getPersistedData(context)
return setLanguage(context, lang)
}
return context
}
fun getLanguage(context: Context): String {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
return context.getSystemService(LocaleManager::class.java)
.applicationLocales.get(0).language
}
return getPersistedData(context)
}
fun setLanguage(context: Context, language: String): Context {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
context.getSystemService(LocaleManager::class.java)
.applicationLocales =
LocaleList.forLanguageTags(language)
}
persist(context, language)
return updateResources(context, language)
}
private fun getPersistedData(context: Context): String {
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
return preferences.getString(Settings.APP_LANGUAGE, Locale.getDefault().language)
?: Locale.getDefault().language
}
private fun persist(context: Context, language: String?) {
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
preferences.edit().putString(Settings.APP_LANGUAGE, language).apply()
}
private fun updateResources(context: Context, language: String): Context {
Log.d("LocaleUtils", language)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val locale = Locale(language)
Locale.setDefault(locale)
val configuration = context.resources.configuration
configuration.setLocale(locale)
configuration.setLayoutDirection(locale)
return context.createConfigurationContext(configuration)
}
val locale = Locale(language)
Locale.setDefault(locale)
val resources = context.resources
val configuration: Configuration = resources.configuration
configuration.locale = locale
configuration.setLayoutDirection(locale)
resources.updateConfiguration(configuration, resources.displayMetrics)
return context
}
}

View File

@ -2,10 +2,20 @@ package com.dzeio.openhealth.utils
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import androidx.core.content.ContextCompat
object PermissionsManager {
fun hasPermission(context: Context, permission: String): Boolean = context.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED
fun hasPermission(context: Context, permission: String): Boolean =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
context.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED
} else {
ContextCompat.checkSelfPermission(
context,
permission
) == PackageManager.PERMISSION_GRANTED
}
fun hasPermission(context: Context, permissions: Array<String>): Boolean {
for (permission in permissions) {

View File

@ -0,0 +1,21 @@
package com.dzeio.openhealth.utils
import android.app.ActivityManager
import android.content.Context
import android.content.Intent
import android.util.Log
import com.dzeio.openhealth.ui.MainActivity
object ServiceUtils {
fun <T> startService(context: Context, service: Class<T>) {
val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
for (runninService in activityManager.getRunningServices(Integer.MAX_VALUE)) {
if (service.name.equals(runninService.service.className)) {
Log.w(MainActivity.TAG, "Service already existing, not starting again")
return
}
}
Log.i(MainActivity.TAG, "Starting service ${service.name}")
Intent(context, service).also { intent -> context.startService(intent) }
}
}

View File

@ -2,6 +2,7 @@
<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:fitsSystemWindows="false"
android:layout_width="match_parent"
android:layout_height="match_parent">
@ -14,28 +15,32 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:fitsSystemWindows="false"
tools:context=".ui.MainActivity">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/app_bar"
style="@style/Widget.Material3.AppBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:layout_height="wrap_content"
android:fitsSystemWindows="false">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
style="@style/ThemeOverlay.Material3.Toolbar.Surface"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:titleCentered="true" />
android:fitsSystemWindows="false"
android:background="@android:color/transparent"
app:titleCentered="true"
app:layout_collapseMode="pin"
android:elevation="0dp" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:id="@+id/scrollView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/scrollView"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
@ -59,6 +64,7 @@
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottom_nav"
android:layout_width="match_parent"
android:fitsSystemWindows="false"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"

View File

@ -109,9 +109,8 @@
style="?attr/materialCardViewFilledStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"
android:focusable="true"
android:layout_marginBottom="16dp">
android:layout_marginBottom="16dp"
app:cardBackgroundColor="?attr/colorOnSurfaceInverse">
<LinearLayout
android:layout_width="match_parent"
@ -124,11 +123,12 @@
<ImageView
android:layout_width="40dp"
android:layout_height="40dp"
android:paddingVertical="8dp"
android:layout_marginEnd="16dp"
android:src="@drawable/ic_outline_favorite_24"
android:background="@drawable/shape_circle"
app:tint="?colorOnPrimary" />
android:paddingVertical="8dp"
android:src="@drawable/ic_outline_favorite_24"
app:backgroundTint="@android:color/transparent"
app:tint="?attr/colorSurfaceInverse" />
<LinearLayout
android:layout_width="match_parent"
@ -138,7 +138,15 @@
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Heart Rate" />
android:text="Heart Rate"
android:textColor="?attr/colorSurfaceInverse" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Coming soon"
android:textColor="?attr/colorSurfaceInverse" />
</LinearLayout>
</LinearLayout>
@ -209,10 +217,9 @@
<com.google.android.material.card.MaterialCardView
android:id="@+id/height"
style="?attr/materialCardViewFilledStyle"
app:cardBackgroundColor="?attr/colorOnSurfaceInverse"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"
android:focusable="true"
android:layout_marginBottom="16dp">
<LinearLayout
@ -230,7 +237,8 @@
android:layout_marginEnd="16dp"
android:src="@drawable/ic_baseline_height_24"
android:background="@drawable/shape_circle"
app:tint="?colorOnPrimary" />
app:backgroundTint="@android:color/transparent"
app:tint="?attr/colorSurfaceInverse" />
<LinearLayout
android:layout_width="match_parent"
@ -240,12 +248,14 @@
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="?attr/colorSurfaceInverse"
android:paddingBottom="4dp"
android:text="Height" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="?attr/colorSurfaceInverse"
android:text="Coming soon" />
</LinearLayout>

View File

@ -17,9 +17,8 @@
style="?attr/materialCardViewFilledStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_weight="1">
<LinearLayout
@ -29,31 +28,24 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="54dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginVertical="8dp"
android:gravity="center_vertical"
android:orientation="horizontal"
android:weightSum="2">
android:orientation="horizontal">
<TextView
style="@style/TextAppearance.Material3.TitleMedium"
android:layout_width="wrap_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/water_intake" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="end">
<ImageView
android:id="@+id/goto_water_home"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/ic_outline_hexagon_24" />
</LinearLayout>
</LinearLayout>
@ -90,17 +82,23 @@
<TextView
android:id="@+id/fragment_home_water_current"
style="@style/TextAppearance.Material3.LabelMedium"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="@string/unit_volume_milliliter_unit"
android:textAlignment="center" />
android:textAlignment="center"
android:textColor="?attr/colorPrimary" />
<com.google.android.material.divider.MaterialDivider
android:layout_width="match_parent"
android:layout_height="2dp"
/>
<TextView
android:id="@+id/fragment_home_water_total"
style="@style/TextAppearance.Material3.LabelMedium"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textColor="?attr/colorOnBackground"
android:text="@string/unit_volume_milliliter_unit"
android:textAlignment="center" />
@ -135,6 +133,113 @@
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
style="?attr/materialCardViewFilledStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_weight="1">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginVertical="12dp"
android:layout_marginHorizontal="16dp"
android:gravity="center_vertical"
android:orientation="horizontal"
android:weightSum="2">
<TextView
style="@style/TextAppearance.Material3.TitleMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/steps_taken" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="end">
<ImageView
android:id="@+id/nav_steps_home"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/ic_outline_hexagon_24" />
</LinearLayout>
</LinearLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginBottom="8dp"
android:gravity="bottom"
android:weightSum="3">
<LinearLayout
android:id="@+id/linearLayout2"
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="parent">
<TextView
android:id="@+id/steps_current"
style="@style/TextAppearance.Material3.LabelMedium"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="@string/unit_volume_milliliter_unit"
android:textAlignment="center"
android:textColor="?attr/colorPrimary" />
<com.google.android.material.divider.MaterialDivider
android:layout_width="match_parent"
android:layout_height="2dp"
/>
<TextView
android:id="@+id/steps_total"
style="@style/TextAppearance.Material3.LabelMedium"
android:layout_width="match_parent"
android:textColor="?attr/colorOnBackground"
android:layout_height="match_parent"
android:text="@string/unit_volume_milliliter_unit"
android:textAlignment="center" />
</LinearLayout>
<ImageView
android:id="@+id/steps_background"
android:layout_width="0dp"
android:layout_height="0dp"
tools: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" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<!-- Unused Currently -->
<!-- <com.google.android.material.card.MaterialCardView-->
<!-- style="?attr/materialCardViewFilledStyle"-->
@ -153,9 +258,8 @@
style="?attr/materialCardViewFilledStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp">
android:layout_marginHorizontal="16dp"
android:layout_marginTop="16dp">
<LinearLayout
android:layout_width="match_parent"
@ -164,7 +268,8 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="54dp"
android:layout_marginVertical="12dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:gravity="center_vertical"
android:orientation="horizontal"

View File

@ -2,4 +2,5 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/vector_logo_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

View File

@ -170,7 +170,7 @@
app:destination="@id/nav_list_weight" />
<action
android:id="@+id/action_nav_browse_to_stepsHomeFragment"
app:destination="@id/stepsHomeFragment" />
app:destination="@id/nav_steps_home" />
</fragment>
@ -180,7 +180,7 @@
android:label="@string/menu_activity"
tools:layout="@layout/fragment_activity" />
<fragment
android:id="@+id/stepsHomeFragment"
android:id="@+id/nav_steps_home"
android:name="com.dzeio.openhealth.ui.steps.StepsHomeFragment"
android:label="@string/menu_steps"
tools:layout="@layout/fragment_steps_home" />

View File

@ -47,4 +47,7 @@
<string name="report_email">Envoyer par Email</string>
<string name="report_github">Envoyer depuis Github</string>
<string name="quit">Quitter</string>
<string name="steps_taken">Pas pris</string>
<string name="error_reporter_crash">Erreur lors de la géneration d\'un rapport d\'erreur</string>
</resources>

View File

@ -1,3 +1,4 @@
<resources>
<style name="Theme.OpenHealth" parent="Theme.Material3.DynamicColors.DayNight" />
</resources>

View File

@ -59,4 +59,6 @@
<string name="report_github">Report on Github</string>
<string name="report_email">Report via Email</string>
<string name="quit">Quit</string>
<string name="steps_taken">Steps taken</string>
<string name="error_reporter_crash">An error occurred while making the error report</string>
</resources>

View File

@ -1,18 +1,10 @@
<resources>
<style name="Theme.OpenHealth" parent="Theme.Material3.DayNight.NoActionBar">
<item name="android:navigationBarColor">@android:color/transparent</item>
</style>
<style name="Theme.OpenHealth" parent="Theme.Material3.DynamicColors.DayNight" />
<style name="Theme.OpenHealth.NoActionBar" parent="Theme.OpenHealth">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
<item name="android:statusBarColor">@android:color/transparent</item>
</style>
<style name="ShapeAppearance.OpenHealth.Corner.Medium" parent="">
<item name="cornerFamily">rounded</item>
<item name="cornerSize">16dp</item>
</style>
<style name="Theme.OpenHealth.SplashScreen" parent="Theme.OpenHealth.NoActionBar">

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<locale-config xmlns:android="http://schemas.android.com/apk/res/android">
<locale android:name="en"/>
<locale android:name="fr"/>
</locale-config>

View File

@ -10,7 +10,7 @@
android:title="Gender" />
<ListPreference
android:key="global_language"
android:key="com.dzeio.open-health.app.language"
android:title="@string/languages" />
</PreferenceCategory>
<PreferenceCategory android:title="Weight Settings">

View File

@ -1,20 +0,0 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
dependencies {
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.40.5'
// Safe Navigation
classpath 'androidx.navigation:navigation-safe-args-gradle-plugin:2.5.0'
}
}
plugins {
id 'com.android.application' version '7.2.1' apply false
id 'com.android.library' version '7.2.1' apply false
id 'org.jetbrains.kotlin.android' version '1.6.10' apply false
}
task clean(type: Delete) {
delete rootProject.buildDir
delete project.buildDir
}

20
build.gradle.kts Normal file
View File

@ -0,0 +1,20 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
dependencies {
classpath("com.google.dagger:hilt-android-gradle-plugin:2.40.5")
// Safe Navigation
classpath("androidx.navigation:navigation-safe-args-gradle-plugin:2.5.1")
}
}
plugins {
id("com.android.application") version "7.2.2" apply false
id("com.android.library") version "7.2.2" apply false
id("org.jetbrains.kotlin.android") version "1.6.10" apply false
}
task("clean") {
delete(rootProject.buildDir)
delete(project.buildDir)
}

View File

@ -1,64 +0,0 @@
plugins {
id 'com.android.library'
id 'org.jetbrains.kotlin.android'
}
android {
compileSdk 32
defaultConfig {
// Android 5 Lollipop
minSdk 21
// Android 12
targetSdk 32
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
// Languages
def locales = ["en", "fr"]
buildConfigField "String[]", "LOCALES", "new String[]{\""+locales.join("\",\"")+"\"}"
resConfigs locales
}
buildTypes {
release {
// Slimmer version
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
debug {
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
debuggable true
// make it debuggable
renderscriptDebuggable true
// Optimization Level
renderscriptOptimLevel 0
}
}
// Compile using JAVA 8
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
implementation 'androidx.core:core-ktx:1.8.0'
implementation 'androidx.appcompat:appcompat:1.4.2'
implementation 'com.google.android.material:material:1.6.1'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}

38
charts/build.gradle.kts Normal file
View File

@ -0,0 +1,38 @@
plugins {
id("com.android.library")
kotlin("android")
}
android {
compileSdk = 33
defaultConfig {
minSdk = 21
targetSdk = 33
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
namespace = "com.dzeio.charts"
}
dependencies {
implementation("com.google.android.material:material:1.6.1")
}

View File

@ -1,6 +1,6 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
# proguardFiles setting in build.gradle.kts.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

View File

@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.dzeio.charts">
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>

View File

@ -12,113 +12,52 @@ import android.view.View
import com.dzeio.charts.axis.XAxis
import com.dzeio.charts.axis.YAxis
import com.dzeio.charts.components.ChartScroll
import com.dzeio.charts.series.SerieAbstract
import kotlin.math.max
import kotlin.math.min
import com.dzeio.charts.series.SerieInterface
class ChartView @JvmOverloads constructor(context: Context?, attrs: AttributeSet? = null) :
View(context, attrs) {
View(context, attrs), ChartViewInterface {
companion object {
const val TAG = "DzeioCharts/ChartView"
private companion object {
const val TAG = "Charts/ChartView"
}
var debug = false
private val rect = RectF()
val xAxis = XAxis<Float>()
val yAxis = YAxis<Float>(this)
val animation = Animation()
val scroller = ChartScroll(this).apply {
setOnChartMoved { movementX, movementY ->
// Log.d(TAG, "scrolled: $movementX")
movementOffset = movementX / 100
refresh()
}
setOnZoomChanged {
Log.d(TAG, "New Zoom: $it")
zoom = (it * 1.2).toFloat()
refresh()
}
private val debugStrokePaint = Paint().apply {
style = Paint.Style.STROKE
strokeWidth = 8f
color = Color.parseColor("#654321")
}
private val animator: Runnable = object : Runnable {
override fun run() {
var needNewFrame = false
for (serie in series) {
val result = serie.onUpdate()
if (result) {
needNewFrame = true
}
}
if (needNewFrame) {
postDelayed(this, animation.getDelay().toLong())
}
override var debug: Boolean = false
override val xAxis = XAxis(this)
override val yAxis = YAxis(this)
override var series: ArrayList<SerieInterface> = arrayListOf()
override fun refresh() {
invalidate()
}
}
/**
* global padding
*/
var padding: Float = 8f
var series: ArrayList<SerieAbstract> = arrayListOf()
set(value) {
for (serie in value) {
serie.view = this
}
field = value
}
/**
* Number of entries displayed at the same time
*/
private var zoom = 100f
var movementOffset: Float = 0f
private val rectF = RectF()
private val otherUseRectF = RectF()
fun refresh() {
for (serie in series) {
serie.prepareData()
}
rectF.set(
padding,
padding,
measuredWidth - padding - yAxis.getWidth() - padding,
height - padding
)
removeCallbacks(animator)
post(animator)
}
private val fgPaint: Paint = Paint().also {
it.isAntiAlias = true
it.color = Color.parseColor("#123456")
}
override fun onDraw(canvas: Canvas) {
if (yAxis.legendEnabled) {
yAxis.display(canvas, measuredWidth, height)
if (debug) {
// draw corners
canvas.drawRect(rect.apply {
set(8f, 8f, width - 8f, height - 8f)
}, debugStrokePaint)
}
// chart draw rectangle
rect.apply {
set(0f, 0f, width.toFloat(), height.toFloat())
}
for (serie in series) {
serie.displayData(canvas, rectF)
serie.onDraw(canvas, rect)
}
// canvas.drawRect(
// measuredWidth - padding - yAxis.getWidth(),
// 0f,
// measuredWidth - padding,
// height - padding,
// fgPaint
// )
super.onDraw(canvas)
}
@ -127,40 +66,19 @@ class ChartView @JvmOverloads constructor(context: Context?, attrs: AttributeSet
return scroller.onTouchEvent(event)
}
fun getXOffset(): Int {
// Log.d(
// TAG,
// "baseOffset: ${xAxis.baseOffset}, mOffset: $movementOffset = ${xAxis.baseOffset + movementOffset}"
// )
// Log.d(
// TAG,
// "longestOffset: ${longestSerie()}, displayedEntries: ${getDisplayedEntries()} = ${longestSerie() - getDisplayedEntries()}"
// )
return min(
max(0f, xAxis.baseOffset + movementOffset).toInt(),
max(0, getCalculatedMax() - getDisplayedEntries())
)
}
val scroller = ChartScroll(this).apply {
var lastMovement = 0.0
setOnChartMoved { movementX, _ ->
fun getDisplayedEntries(): Int {
// Log.d(TAG, "Number of entries displayed ${list.size}, ${xAxis.entriesDisplayed} + (($zoom - 100) * 10) = ${xAxis.entriesDisplayed + ((zoom - 100) * 10).toInt()}")
return max(0, xAxis.entriesDisplayed + ((zoom - 100) * 10).toInt())
Log.d(TAG, "scrolled: ${(movementX - lastMovement) * (xAxis.increment / 10)}")
xAxis.x = xAxis.x + (movementX - lastMovement) * (xAxis.increment / 10)
lastMovement = movementX.toDouble()
refresh()
}
fun getCalculatedMax(): Int {
var size = 0
for (serie in series) {
if (serie.datas.size > size) size = serie.datas.size
}
return size
}
fun getXMax(displayedOnly: Boolean = false): Float {
var max = 0f
for (serie in series) {
val res = serie.getYMax(displayedOnly)
if (max < res) max = res
}
return max
// setOnZoomChanged {
// Log.d(TAG, "New Zoom: $it")
// zoom = (it * 1.2).toFloat()
// refresh()
// }
}
}

View File

@ -0,0 +1,166 @@
package com.dzeio.charts
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.RectF
import android.util.AttributeSet
import android.util.Log
import android.view.MotionEvent
import android.view.View
import com.dzeio.charts.axis.XAxis
import com.dzeio.charts.axis.YAxis
import com.dzeio.charts.components.ChartScroll
import com.dzeio.charts.series.SerieAbstract
import kotlin.math.max
import kotlin.math.min
class ChartView @JvmOverloads constructor(context: Context?, attrs: AttributeSet? = null) :
View(context, attrs) {
companion object {
const val TAG = "DzeioCharts/ChartView"
}
override var debug = false
override val xAxis = XAxis<Float>()
override val yAxis = YAxis(this)
override val animation = Animation()
override val scroller = ChartScroll(this).apply {
setOnChartMoved { movementX, _ ->
// Log.d(TAG, "scrolled: $movementX")
movementOffset = movementX / 100
refresh()
}
setOnZoomChanged {
Log.d(TAG, "New Zoom: $it")
zoom = (it * 1.2).toFloat()
refresh()
}
}
override val animator: Runnable = object : Runnable {
override fun run() {
var needNewFrame = false
for (serie in series) {
val result = serie.onUpdate()
if (result) {
needNewFrame = true
}
}
if (needNewFrame) {
postDelayed(this, animation.getDelay().toLong())
}
invalidate()
}
}
/**
* global padding
*/
override var padding: Float = 8f
override var series: ArrayList<SerieAbstract> = arrayListOf()
set(value) {
for (serie in value) {
serie.view = this
}
field = value
}
/**
* Number of entries displayed at the same time
*/
override var zoom = 100f
override var movementOffset: Float = 0f
override val rectF = RectF()
override val otherUseRectF = RectF()
override fun refresh() {
for (serie in series) {
serie.prepareData()
}
rectF.set(
padding,
padding,
measuredWidth - padding - yAxis.getWidth() - padding,
height - padding
)
removeCallbacks(animator)
post(animator)
}
override val fgPaint: Paint = Paint().also {
it.isAntiAlias = true
it.color = Color.parseColor("#123456")
}
override fun onDraw(canvas: Canvas) {
if (yAxis.legendEnabled) {
yAxis.display(canvas, measuredWidth, height)
}
for (serie in series) {
serie.displayData(canvas, rectF)
}
// canvas.drawRect(
// measuredWidth - padding - yAxis.getWidth(),
// 0f,
// measuredWidth - padding,
// height - padding,
// fgPaint
// )
super.onDraw(canvas)
}
override fun onTouchEvent(event: MotionEvent): Boolean {
performClick()
return scroller.onTouchEvent(event)
}
override fun getXOffset(): Int {
// Log.d(
// TAG,
// "baseOffset: ${xAxis.baseOffset}, mOffset: $movementOffset = ${xAxis.baseOffset + movementOffset}"
// )
// Log.d(
// TAG,
// "longestOffset: ${longestSerie()}, displayedEntries: ${getDisplayedEntries()} = ${longestSerie() - getDisplayedEntries()}"
// )
return min(
max(0f, xAxis.baseOffset + movementOffset).toInt(),
max(0, getCalculatedMax() - getDisplayedEntries())
)
}
override fun getDisplayedEntries(): Int {
// Log.d(TAG, "Number of entries displayed ${list.size}, ${xAxis.entriesDisplayed} + (($zoom - 100) * 10) = ${xAxis.entriesDisplayed + ((zoom - 100) * 10).toInt()}")
return max(0, xAxis.entriesDisplayed + ((zoom - 100) * 10).toInt())
}
override fun getCalculatedMax(): Int {
var size = 0
for (serie in series) {
if (serie.datas.size > size) size = serie.datas.size
}
return size
}
override fun getXMax(displayedOnly: Boolean): Float {
var max = 0f
for (serie in series) {
val res = serie.getYMax(displayedOnly)
if (max < res) max = res
}
return max
}
}

View File

@ -0,0 +1,35 @@
package com.dzeio.charts
import com.dzeio.charts.axis.XAxisInterface
import com.dzeio.charts.axis.YAxisInterface
import com.dzeio.charts.series.SerieInterface
interface ChartViewInterface {
/**
* Make the whole view in debug mode
*
* add debug texts, logs, and more
*/
var debug: Boolean
/**
* Hold metadata about the X axis
*/
val xAxis: XAxisInterface
/**
* Hold informations about the Y axis
*/
val yAxis: YAxisInterface
/**
* handle the series
*/
var series: ArrayList<SerieInterface>
/**
* refresh EVERYTHING
*/
fun refresh()
}

View File

@ -1,25 +1,56 @@
package com.dzeio.charts.axis
import com.dzeio.charts.XAxisLabels
import android.graphics.RectF
import com.dzeio.charts.ChartViewInterface
import com.dzeio.charts.Entry
class XAxis<T> {
class XAxis(
private val view: ChartViewInterface
) : XAxisInterface {
override var x: Double = 0.0
set(value) {
val max = getXMax() - increment * displayCount
if (value > max) {
field = max
return
}
var max: T? = null
var min: T? = null
val min = getXMin()
if (value < min) {
field = min
return
}
val labels = XAxisLabels()
field = value
}
override var increment: Double = 1.0
override var displayCount: Int = 10
override var labelCount: Int = 3
/**
* Number of entries displayed in the chart at the same time
*/
var entriesDisplayed = 5
override fun getPositionOnRect(entry: Entry, rect: RectF): Double {
return translatePositionToRect(entry.x, rect)
}
/**
* Offset in the list
*/
var baseOffset = 0
fun translatePositionToRect(value: Double, rect: RectF): Double {
val rectItem = rect.width() / displayCount // item size in graph
return rectItem * value / increment
}
var onValueFormat: (it: T) -> String = onValueFormat@{
return@onValueFormat it.toString()
override fun getXOffset(rect: RectF): Double {
return translatePositionToRect(x, rect)
}
override fun getXMax(): Double {
return view.series.maxOf { serie ->
serie.entries.maxOf { entry -> entry.x }
}
}
override fun getXMin(): Double {
return view.series.minOf { serie ->
serie.entries.minOf { entry -> entry.x }
}
}
}

View File

@ -0,0 +1,25 @@
package com.dzeio.charts.axis
import com.dzeio.charts.XAxisLabels
class XAxis<T> {
var max: T? = null
var min: T? = null
val labels = XAxisLabels()
/**
* Number of entries displayed in the chart at the same time
*/
var entriesDisplayed = 5
/**
* Offset in the list
*/
var baseOffset = 0
var onValueFormat: (it: T) -> String = onValueFormat@{
return@onValueFormat it.toString()
}
}

View File

@ -0,0 +1,35 @@
package com.dzeio.charts.axis
import android.graphics.RectF
import com.dzeio.charts.Entry
sealed interface XAxisInterface {
/**
* set X position
*/
var x: Double
/**
* X increment
*/
var increment: Double
/**
* indicate the max number of entries are displayed
*/
var displayCount: Int
/**
* indicate the number of labels displayed
*/
var labelCount: Int
fun getPositionOnRect(entry: Entry, rect: RectF): Double
fun getXOffset(rect: RectF): Double
fun getXMax(): Double
fun getXMin(): Double
}

View File

@ -3,72 +3,85 @@ package com.dzeio.charts.axis
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Rect
import androidx.annotation.ColorInt
import com.dzeio.charts.ChartView
import com.dzeio.charts.utils.drawDottedLine
import android.graphics.RectF
import com.dzeio.charts.ChartViewInterface
class YAxis<T>(
private val chartView: ChartView
) {
var max: T? = null
var min: T? = null
class YAxis(
private val view: ChartViewInterface
) : YAxisInterface {
/**
* Number of labels displayed on the sidebar
*/
var labelCount: Int = 4
override var enabled = false
@ColorInt
var color = Color.parseColor("#FC496D")
var textPaint: Paint = Paint().also {
it.isAntiAlias = true
it.color = color
it.textSize = 30f
it.textAlign = Paint.Align.RIGHT
}
var linePaint = Paint().apply {
override val textLabel = Paint().apply {
isAntiAlias = true
color = Color.parseColor("#FC496D")
textSize = 30f
textAlign = Paint.Align.RIGHT
}
var legendEnabled = true
override var labelCount: Int = 3
private val rect: Rect = Rect()
private var min: Float? = null
private var max: Float? = null
fun getWidth(): Int {
var maxWidth = 0
val max = chartView.getXMax(true)
val vIncrement = max / labelCount
for (i in 0 until labelCount) {
val text = onValueFormat(vIncrement * (labelCount - i), true)
textPaint.getTextBounds(text, 0, text.length, rect)
if (rect.width() > maxWidth) maxWidth = rect.width()
}
return maxWidth + chartView.padding.toInt()
override fun setYMin(yMin: Float?): YAxisInterface {
min = yMin
return this
}
/**
* Function to display the YAxis sidebar on the right
*
* it migh also display content over the Graph
*/
fun display(canvas: Canvas, width: Int, height: Int) {
val max = chartView.getXMax(true)
val increment = (height - chartView.padding * 2) / labelCount
val vIncrement = max / labelCount
for (i in 0 until labelCount) {
val text = onValueFormat(vIncrement * (labelCount - i), true)
textPaint.getTextBounds(text, 0, text.length, rect)
val posY = increment * i.toFloat()
canvas.drawDottedLine(0f, posY + chartView.padding, width - rect.width().toFloat() - chartView.padding, posY, 40f, linePaint)
canvas.drawText(text,
width.toFloat() - chartView.padding, posY + chartView.padding, textPaint)
// canvas.drawDottedLine(0f, posY, measuredWidth.toFloat(), posY, 10f, linePaint)
override fun setYMax(yMax: Float?): YAxisInterface {
max = yMax
return this
}
override fun getYMax(): Float {
if (max != null) {
return max!!
}
if (view.series.isEmpty()) {
return 100f
}
return view.series
.maxOf { serie ->
if (serie.getDisplayedEntries().isEmpty()) {
return@maxOf 0f
}
return@maxOf serie.getDisplayedEntries().maxOf { entry -> entry.y }
}
}
var onValueFormat: (value: Float, shortVersion: Boolean) -> String = { it, _ -> it.toString() }
override fun getYMin(): Float {
if (min != null) {
return min!!
}
if (view.series.isEmpty()) {
return 0f
}
return view.series
.minOf { serie ->
if (serie.getDisplayedEntries().isEmpty()) {
return@minOf 0f
}
return@minOf serie.getDisplayedEntries().minOf { entry -> entry.y }
}
}
override fun onDraw(canvas: Canvas, drawLocation: RectF) {
val min = getYMin()
val max = getYMax() - min
val top = drawLocation.top
val bottom = drawLocation.bottom
val increment = (bottom - top) / labelCount
val valueIncrement = (max - min) / labelCount
for (index in 0 until labelCount) {
canvas.drawText(
(valueIncrement * (labelCount + 1)).toString(),
bottom - (index + 1) * increment,
drawLocation.right,
textLabel
)
}
TODO("IDK if it works tbh")
}
}

View File

@ -0,0 +1,74 @@
package com.dzeio.charts.axis
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Rect
import androidx.annotation.ColorInt
import com.dzeio.charts.ChartView
import com.dzeio.charts.utils.drawDottedLine
class YAxis<T>(
private val chartView: ChartView
) {
var max: T? = null
var min: T? = null
/**
* Number of labels displayed on the sidebar
*/
var labelCount: Int = 4
@ColorInt
var color = Color.parseColor("#FC496D")
var textPaint: Paint = Paint().also {
it.isAntiAlias = true
it.color = color
it.textSize = 30f
it.textAlign = Paint.Align.RIGHT
}
var linePaint = Paint().apply {
isAntiAlias = true
}
var legendEnabled = true
private val rect: Rect = Rect()
fun getWidth(): Int {
var maxWidth = 0
val max = chartView.getXMax(true)
val vIncrement = max / labelCount
for (i in 0 until labelCount) {
val text = onValueFormat(vIncrement * (labelCount - i), true)
textPaint.getTextBounds(text, 0, text.length, rect)
if (rect.width() > maxWidth) maxWidth = rect.width()
}
return maxWidth + chartView.padding.toInt()
}
/**
* Function to display the YAxis sidebar on the right
*
* it migh also display content over the Graph
*/
fun display(canvas: Canvas, width: Int, height: Int) {
val max = chartView.getXMax(true)
val increment = (height - chartView.padding * 2) / labelCount
val vIncrement = max / labelCount
for (i in 0 until labelCount) {
val text = onValueFormat(vIncrement * (labelCount - i), true)
textPaint.getTextBounds(text, 0, text.length, rect)
val posY = increment * i.toFloat()
canvas.drawDottedLine(0f, posY + chartView.padding, width - rect.width().toFloat() - chartView.padding, posY, 40f, linePaint)
canvas.drawText(text,
width.toFloat() - chartView.padding, posY + chartView.padding, textPaint)
// canvas.drawDottedLine(0f, posY, measuredWidth.toFloat(), posY, 10f, linePaint)
}
}
var onValueFormat: (value: Float, shortVersion: Boolean) -> String = { it, _ -> it.toString() }
}

View File

@ -0,0 +1,58 @@
package com.dzeio.charts.axis
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.RectF
sealed interface YAxisInterface {
/**
* whether or not this axis is displayed
*/
var enabled: Boolean
/**
* override Y minimum
*
* @param yMin is set the min will ba at the value, if null it is calculated
*/
fun setYMin(yMin: Float?): YAxisInterface
/**
* override Y maximum
*
* @param yMax is set the max will ba at the value, if null it is calculated
*/
fun setYMax(yMax: Float?): YAxisInterface
/**
* get Y maximum
*
* @return the maximum value Y can get (for displayed values)
*/
fun getYMax(): Float
/**
* get Y minimum
*
* @return the minimum value Y can get (for displayed values)
*/
fun getYMin(): Float
/**
* get/set the number of label of this Y axis
*
* the first/last labels are at the bottom/top of the chart
*/
var labelCount: Int
/**
* text label paint
*/
val textLabel: Paint
/**
* function that draw our legend
*/
fun onDraw(canvas: Canvas, drawLocation: RectF)
}

View File

@ -0,0 +1,6 @@
package com.dzeio.charts.axis
enum class YAxisPosition {
LEFT,
RIGHT
}

View File

@ -58,7 +58,7 @@ class ChartScroll(view: View) {
return super.onScale(detector)
}
override fun onScaleEnd(detector: ScaleGestureDetector?) {
override fun onScaleEnd(detector: ScaleGestureDetector) {
super.onScaleEnd(detector)
lastZoom += -currentZoom + 1

View File

@ -1,163 +1,68 @@
package com.dzeio.charts.series
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Rect
import android.graphics.RectF
import android.util.Log
import kotlin.math.max
import com.dzeio.charts.ChartView
class BarSerie : SerieAbstract() {
class BarSerie(
private val view: ChartView
) : BaseSerie(view) {
companion object {
const val TAG = "DzeioCharts/BarSerie"
private companion object {
const val TAG = "Charts/BarSerie"
}
var spacing: Float = 8f
var targetPercentList = arrayListOf<Float>()
var percentList = arrayListOf<Float>()
private var fgPaint: Paint = Paint().also {
it.isAntiAlias = true
val barPaint = Paint().apply {
isAntiAlias = true
color = Color.parseColor("#123456")
}
var previousRefresh = 0
override fun onDraw(canvas: Canvas, drawableSpace: RectF) {
val spacing = drawableSpace.width() / view.xAxis.displayCount / 10
val barWidth = drawableSpace.width() / view.xAxis.displayCount - spacing
val displayedEntries = getDisplayedEntries()
val max = view.yAxis.getYMax()
val min = view.yAxis.getYMin()
private val r = Rect()
// Log.d(TAG, "${space.left}, ${space.right}")
override fun onUpdate(): Boolean {
var needNewFrame = false
for (i in targetPercentList.indices) {
val value = view.animation.updateValue(
1f,
targetPercentList[i],
percentList[i],
0f,
0.00f
for (entry in displayedEntries) {
// calculated height in percent from 0 to 100
val height = (1 - entry.y / max) * drawableSpace.height()
// -1.945763981752553E-21
// 2.103653925902835E-21
val posX = view.xAxis.getPositionOnRect(entry, drawableSpace) - view.xAxis.getXOffset(drawableSpace) - canvas.width
// Log.d(TAG, "gpor = ${view.xAxis.getPositionOnRect(entry, space)}, gxo = ${view.xAxis.getXOffset(space)}")
// Log.d(TAG, "max = $max, y = ${entry.y}, height = $height")
// Log.d(TAG, "posX: ${posX / 60 / 60 / 1000}, offsetX = ${view.xAxis.x / (1000 * 60 * 60)}, x = ${entry.x / (1000 * 60 * 60)}, pouet: ${(view.xAxis.x + view.xAxis.displayCount * view.xAxis.increment) / (1000 * 60 * 60)}")
// Log.d(
// TAG, """
// ${posX.toFloat()},
// $height,
// ${(posX + barWidth).toFloat()},
// ${space.bottom}""".trimIndent()
// )
canvas.drawRect(
posX.toFloat(),
height,
(posX + barWidth).toFloat(),
drawableSpace.bottom,
barPaint
)
if (value != percentList[i]) {
needNewFrame = true
percentList[i] = value
}
}
return needNewFrame
}
override fun prepareData() {
val max: Float = if (view.yAxis.max != null) view.yAxis.max!! else {
getMax()
}
targetPercentList = arrayListOf()
// Log.d(TAG, "offset: ${view.getXOffset()}, displayed: ${view.getDisplayedEntries()}")
for (item in datas.subList(
view.getXOffset(),
view.getXOffset() + view.getDisplayedEntries()
)) {
// // // Process bottom texts
// val text = view.xAxis.onValueFormat(item.x)
// bottomTexts.add(text)
//
// // get Text boundaries
// view.xAxis.labels.build().getTextBounds(text, 0, text.length, r)
//
// // get height of text
// if (bottomTextHeight < r.height()) {
// bottomTextHeight = r.height()
// }
//
// // get text descent
// val descent = abs(r.bottom)
// if (bottomTextDescent < descent) {
// bottomTextDescent = descent
// }
// // process values
// Log.d(TAG, item.y.toString())
// add to animations the values
targetPercentList.add(1 - item.y / max)
}
// post list
val offset = view.getXOffset()
val movement = offset - previousRefresh
Log.d(TAG, "$offset - $previousRefresh = $movement")
if (movement != 0) {
previousRefresh = offset
}
// if (movement != 0) {
// Log.d(TAG, movement.toString())
// }
if (movement >= 1) {
percentList = percentList.subList(1, percentList.size).toCollection(ArrayList())
percentList.add(1f)
} else if (movement <= -1) {
percentList = percentList.subList(0, percentList.size - 1).toCollection(ArrayList())
percentList.add(0, 1f)
}
if (percentList.isEmpty() || percentList.size < targetPercentList.size) {
val temp = targetPercentList.size - percentList.size
for (i in 0 until temp) {
percentList.add(1f)
}
} else if (percentList.size > targetPercentList.size) {
val temp = percentList.size - targetPercentList.size
for (i in 0 until temp) {
percentList.removeAt(percentList.size - 1)
}
}
fgPaint.color = view.yAxis.color
}
override fun displayData(canvas: Canvas, rect: RectF) {
val barWidth = (rect.width() - view.padding * 2) / view.getDisplayedEntries() - spacing
if (percentList.isNotEmpty()) {
// draw each rectangles
for (i in 1..percentList.size) {
// Log.d(TAG, percentList[i - 1].toString())
val left = rect.left + spacing * i + barWidth * (i - 1).toFloat() + view.padding
// Log.d(TAG, "$spacing, $i, $barWidth = $left")
val right = rect.left + (spacing + barWidth) * i.toFloat()
val bottom = rect.top + rect.height() - view.padding
val top = (bottom - rect.top) * percentList[i - 1] + view.padding
// create rounded rect
canvas.drawRoundRect(left, top, right, bottom, 8f, 8f, fgPaint)
// remove the bottom corners DUH
canvas.drawRect(left, max(top, bottom - 8f), right, bottom, fgPaint)
val targetTop = (bottom - rect.top) * targetPercentList[i - 1]
val text = view.yAxis.onValueFormat(getMax() - getMax() * targetPercentList[i - 1], true)
view.xAxis.labels.build().getTextBounds(text, 0, text.length, r)
val doDisplayIn = r.width() + 10f < barWidth && bottom - targetTop > r.height() + 40f
if (view.debug || !doDisplayIn || (doDisplayIn && bottom - top > r.height() + 40f)) {
val y = if (doDisplayIn) top + r.height() + 20f else top - r.height()
canvas.drawText(
text,
left + (right - left) / 2,
y,
view.xAxis.labels.build()
entry.y.toString(),
(posX + barWidth / 2).toFloat(),
drawableSpace.bottom / 2,
view.yAxis.textLabel.apply {
textAlign = Paint.Align.CENTER
}
)
}
}
}
}
private fun getMax(): Float {
var calculatedMax = 0f
for (entry in datas.subList(
view.getXOffset(),
view.getDisplayedEntries() + view.getXOffset()
)) {
if (entry.y > calculatedMax) calculatedMax = entry.y
}
return if (calculatedMax < 0) 0f else calculatedMax
}
}

View File

@ -0,0 +1,160 @@
package com.dzeio.charts.series
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Rect
import android.graphics.RectF
import android.util.Log
import kotlin.math.max
class BarSerie : SerieAbstract() {
companion object {
const val TAG = "DzeioCharts/BarSerie"
}
var spacing: Float = 8f
/**
* Values displayed on the grapd
*/
var displayedDatas = arrayListOf<Float>()
/**
* Target values
*/
var targetDatas = arrayListOf<Float>()
var targetPercentList = arrayListOf<Float>()
var percentList = arrayListOf<Float>()
var previousRefresh = 0
private var fgPaint: Paint = Paint().apply {
isAntiAlias = true
}
private val r = Rect()
override fun onUpdate(): Boolean {
var needNewFrame = false
for (i in targetPercentList.indices) {
val value = view.animation.updateValue(
1f,
targetPercentList[i],
percentList[i],
0f,
0.00f
)
if (value != percentList[i]) {
needNewFrame = true
percentList[i] = value
}
}
return needNewFrame
}
override fun prepareData() {
val max: Float = if (view.yAxis.max != null) view.yAxis.max!! else {
getYMax(true)
}
targetPercentList = arrayListOf()
// Log.d(TAG, "offset: ${view.getXOffset()}, displayed: ${view.getDisplayedEntries()}")
for (item in getDisplayedEntries()) {
// // // Process bottom texts
// val text = view.xAxis.onValueFormat(item.x)
// bottomTexts.add(text)
//
// // get Text boundaries
// view.xAxis.labels.build().getTextBounds(text, 0, text.length, r)
//
// // get height of text
// if (bottomTextHeight < r.height()) {
// bottomTextHeight = r.height()
// }
//
// // get text descent
// val descent = abs(r.bottom)
// if (bottomTextDescent < descent) {
// bottomTextDescent = descent
// }
// process values
// Log.d(TAG, item.y.toString())
// add to animations the values
targetPercentList.add(1 - item.y / max)
}
// post list
val offset = view.getXOffset()
val movement = offset - previousRefresh
Log.d(TAG, "$offset - $previousRefresh = $movement")
if (movement != 0) {
previousRefresh = offset
}
// if (movement != 0) {
// Log.d(TAG, movement.toString())
// }
if (movement >= 1) {
percentList = percentList.subList(1, percentList.size).toCollection(ArrayList())
percentList.add(1f)
} else if (movement <= -1) {
percentList = percentList.subList(0, percentList.size - 1).toCollection(ArrayList())
percentList.add(0, 1f)
}
if (percentList.isEmpty() || percentList.size < targetPercentList.size) {
val temp = targetPercentList.size - percentList.size
for (i in 0 until temp) {
percentList.add(1f)
}
} else if (percentList.size > targetPercentList.size) {
val temp = percentList.size - targetPercentList.size
for (i in 0 until temp) {
percentList.removeAt(percentList.size - 1)
}
}
fgPaint.color = view.yAxis.color
}
override fun displayData(canvas: Canvas, rect: RectF) {
val barWidth = (rect.width() - view.padding * 2) / view.getDisplayedEntries() - spacing
if (percentList.isNotEmpty()) {
// draw each rectangles
for (i in 1..percentList.size) {
// Log.d(TAG, percentList[i - 1].toString())
val left = rect.left + spacing * i + barWidth * (i - 1).toFloat() + view.padding
// Log.d(TAG, "$spacing, $i, $barWidth = $left")
val right = rect.left + (spacing + barWidth) * i.toFloat()
val bottom = rect.top + rect.height() - view.padding
val top = (bottom - rect.top) * percentList[i - 1] + view.padding
// create rounded rect
canvas.drawRoundRect(left, top, right, bottom, 8f, 8f, fgPaint)
// remove the bottom corners DUH
canvas.drawRect(left, max(top, bottom - 8f), right, bottom, fgPaint)
val targetTop = (bottom - rect.top) * targetPercentList[i - 1]
val text = view.yAxis.onValueFormat(getYMax(true) - getYMax(true) * targetPercentList[i - 1], true)
view.xAxis.labels.build().getTextBounds(text, 0, text.length, r)
val doDisplayIn =
r.width() + 10f < barWidth && bottom - targetTop > r.height() + 40f
if (view.debug || !doDisplayIn || (doDisplayIn && bottom - top > r.height() + 40f)) {
val y = if (doDisplayIn) top + r.height() + 20f else top - r.height()
canvas.drawText(
text,
left + (right - left) / 2,
y,
view.xAxis.labels.build()
)
}
}
}
}
}

View File

@ -0,0 +1,33 @@
package com.dzeio.charts.series
import android.graphics.Canvas
import android.graphics.RectF
import com.dzeio.charts.ChartViewInterface
import com.dzeio.charts.Entry
import com.dzeio.charts.axis.YAxisPosition
sealed class BaseSerie(
private val view: ChartViewInterface
) : SerieInterface {
private companion object {
const val TAG = "Charts/BaseSerie"
}
override var yAxisPosition: YAxisPosition = YAxisPosition.RIGHT
override var entries: ArrayList<Entry> = arrayListOf()
override fun getDisplayedEntries(): ArrayList<Entry> {
// -+ view.xAxis.increment = one out of display
val minX = view.xAxis.x - view.xAxis.increment
val maxX =
view.xAxis.x + view.xAxis.displayCount * 2 * view.xAxis.increment + view.xAxis.increment
return entries.filter {
return@filter it.x in minX..maxX
} as ArrayList<Entry>
}
abstract override fun onDraw(canvas: Canvas, drawableSpace: RectF)
}

View File

@ -12,18 +12,31 @@ abstract class SerieAbstract {
lateinit var view: ChartView
/**
* get Serie Y max
*/
fun getYMax(displayedOnly: Boolean = false): Float {
var max = 0f
var localDatas = if (displayedOnly) datas.subList(
view.getXOffset(),
min(view.getXOffset() + view.getDisplayedEntries(), datas.size)
) else datas
var max = Float.MIN_VALUE
val localDatas = if (displayedOnly) getDisplayedEntries() else datas
for (data in localDatas) {
if (max < data.y) max = data.y
}
return max
}
/**
* get Serie Y min
*/
fun getYMin(displayedOnly: Boolean = false): Float {
var min = Float.MAX_VALUE
val localDatas = if (displayedOnly) getDisplayedEntries() else datas
for (data in localDatas) {
if (min > data.y) min = data.y
}
return min
}
/**
* Animation updates
*/
@ -41,4 +54,11 @@ abstract class SerieAbstract {
* @param rect the rectangle in which you have to draw data
*/
abstract fun displayData(canvas: Canvas, rect: RectF)
protected fun getDisplayedEntries(): MutableList<Entry> {
return datas.subList(
view.getXOffset(),
min(datas.size, view.getDisplayedEntries() + view.getXOffset())
)
}
}

View File

@ -0,0 +1,28 @@
package com.dzeio.charts.series
import android.graphics.Canvas
import android.graphics.RectF
import com.dzeio.charts.Entry
import com.dzeio.charts.axis.YAxisPosition
sealed interface SerieInterface {
/**
* location of the Y axis
*/
var yAxisPosition: YAxisPosition
/**
* filter out out of display entries
*
* @return the list of entries displayed
*/
fun getDisplayedEntries(): ArrayList<Entry>
/**
* set the entries for the list
*/
var entries: ArrayList<Entry>
fun onDraw(canvas: Canvas, drawableSpace: RectF)
}

View File

@ -1,324 +0,0 @@
package com.dzeio.charts.views
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Rect
import android.util.AttributeSet
import android.view.View
import com.dzeio.charts.Animation
import com.dzeio.charts.Entry
import com.dzeio.charts.axis.XAxis
import com.dzeio.charts.axis.YAxis
import com.dzeio.charts.utils.drawDottedLine
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
class BarChartView @JvmOverloads constructor(context: Context?, attrs: AttributeSet? = null) :
View(context, attrs) {
companion object {
const val TAG = "DzeioCharts/BarView"
}
var debug: Boolean = false
/**
* Number of entries displayed at the same time
*/
private var zoom = 100f
/**
* Number of labels displayed at the same time
*/
var numberOfLabels = 3
/**
* Spacing between entries
*/
var spacing = 22
val xAxis: XAxis<Double> = XAxis()
val yAxis: YAxis<Float> = YAxis()
val animation = Animation()
private val textTopMargin = 5
private var barWidth: Int = 0
private var percentList: ArrayList<Float> = ArrayList()
/**
* value goes from 1 to 0 (1 at bottom, 0 at top)
*/
private var targetPercentList: ArrayList<Float> = ArrayList()
private var fgPaint: Paint = Paint().also {
it.isAntiAlias = true
it.color = yAxis.color
}
private var linePaint = Paint().apply {
color = Color.parseColor("#123456")
strokeWidth = 6f
}
private val rect: Rect = Rect()
private var bottomTextDescent = 0
private var bottomTextHeight = 0
private var movementOffset: Int = 0
private val animator: Runnable = object : Runnable {
override fun run() {
var needNewFrame = false
// var txt = ""
// for (tpl in targetPercentList) {
// txt += "$tpl, "
// }
// Log.d(TAG, txt)
for (i in targetPercentList.indices) {
val value = animation.updateValue(
1f,
targetPercentList[i],
percentList[i],
0f,
0.01f
)
if (value != percentList[i]) {
// if (!needNewFrame) {
// Log.d(TAG, "$i, $value, ${percentList[i]}")
// }
needNewFrame = true
percentList[i] = value
}
}
if (needNewFrame) {
postDelayed(this, animation.getDelay().toLong())
}
invalidate()
}
}
var list: ArrayList<Entry> = arrayListOf()
private var fiftyPercent = 0f
private var bottomTexts: ArrayList<String> = arrayListOf()
var previousOffset = getXOffset()
fun refresh() {
val r = Rect()
// // prepare bottom texts
bottomTextDescent = 0
bottomTextHeight = 0
bottomTexts = arrayListOf()
// // prepare values
// set the bar Width (also handle div by 0)
barWidth = measuredWidth / max(min(list.size, getDisplayedEntries()), 1) - spacing
// calculate max depending on the maximum value displayed or set in the yAxis params
val max: Float = if (yAxis.max != null) yAxis.max!! else {
var calculatedMax = 0f
for (entry in list.subList(
this.getXOffset(),
getDisplayedEntries() + this.getXOffset()
)) {
if (entry.y > calculatedMax) calculatedMax = entry.y
}
if (calculatedMax < 0) 0f else calculatedMax
}
fiftyPercent = max / 2
// make sure the target list
// Log.d(TAG, list.size.toString())
targetPercentList = arrayListOf()
// Log.d(TAG, "List selected: ${getXOffset()} to ${getXOffset() + getDisplayedEntries() - 1}")
for (item in list.subList(getXOffset(), getXOffset() + getDisplayedEntries())) {
// // Process bottom texts
val text = xAxis.onValueFormat(item.x)
bottomTexts.add(text)
// get Text boundaries
xAxis.labels.build().getTextBounds(text, 0, text.length, r)
// get height of text
if (bottomTextHeight < r.height()) {
bottomTextHeight = r.height()
}
// get text descent
val descent = abs(r.bottom)
if (bottomTextDescent < descent) {
bottomTextDescent = descent
}
// // process values
// add to animations the values
targetPercentList.add(1 - item.y / max)
}
// post list
val movement = getXOffset() - previousOffset
previousOffset = getXOffset()
// Log.d(TAG, movement.toString())
if (movement >= 1) {
percentList = percentList.subList(min(movement, percentList.size), percentList.size).toCollection(
ArrayList()
)
for (i in 0 until movement) {
percentList.add(1f)
}
} else if (movement <= -1) {
percentList = percentList.subList(0, percentList.size + movement).toCollection(
ArrayList()
)
for (i in 0 until abs(movement)) {
percentList.add(0, 1f)
}
}
if (percentList.isEmpty() || percentList.size < targetPercentList.size) {
val temp = targetPercentList.size - percentList.size
for (i in 0 until temp) {
percentList.add(1f)
}
} else if (percentList.size > targetPercentList.size) {
val temp = percentList.size - targetPercentList.size
for (i in 0 until temp) {
percentList.removeAt(percentList.size - 1)
}
}
// Misc operations
fgPaint = Paint().apply {
isAntiAlias = true
color = yAxis.color
}
linePaint = Paint().apply {
color = yAxis.lineColor
strokeWidth = 4f
}
removeCallbacks(animator)
post(animator)
}
override fun onDraw(canvas: Canvas) {
val bottom = height - bottomTextHeight - textTopMargin.toFloat()
// draw sidebar and lines
val increment = bottom / yAxis.labelCount
val vIncrement = fiftyPercent * 2 / yAxis.labelCount
for (i in 0 until yAxis.labelCount) {
val text = (vIncrement * (yAxis.labelCount - i)).toString()
xAxis.labels.build().getTextBounds(text, 0, text.length, rect)
val posY = increment * i
canvas.drawDottedLine(0f, posY, measuredWidth.toFloat(), posY, 40f, linePaint)
canvas.drawText(text,
(measuredWidth - rect.width()).toFloat(), posY + rect.height() + 20f, xAxis.labels.build())
// canvas.drawDottedLine(0f, posY, measuredWidth.toFloat(), posY, 10f, linePaint)
}
if (percentList.isNotEmpty()) {
// draw each rectangles
for (i in 1..percentList.size) {
// Log.d(TAG, percentList[i - 1].toString())
val left = spacing * i + barWidth * (i - 1).toFloat()
// Log.d(TAG, "$spacing, $i, $barWidth = $left")
val right = (spacing + barWidth) * i.toFloat()
val top = bottom * percentList[i - 1]
// create rounded rect
canvas.drawRoundRect(left, top, right, bottom, 8f, 8f, fgPaint)
// remove the bottom corners DUH
canvas.drawRect(left, max(top, bottom - 8f), right, bottom, fgPaint)
if (debug) {
canvas.drawText(
bottomTexts[i - 1].toString(),
left + (right - left) / 2,
top + (bottom - top) / 2,
xAxis.labels.build()
)
}
}
}
if (bottomTexts.isNotEmpty() && numberOfLabels > 0) {
val size = bottomTexts.size
var i = 1
var items = size / max(2, (numberOfLabels - 2))
// handle cases where size is even and numberOfLabels is 3
if (size % 2 != 0) {
items += 1
}
// Log.i(TAG, "$size / max($numberOfLabels - 2, 2) = $items")
for (s in bottomTexts) {
if ((numberOfLabels <= 2 || i % items != 0) && i != 1 && i != size) {
i++
continue
}
// Log.i(TAG, "Drawing $i")
xAxis.labels.build().getTextBounds(s, 0, s.length, rect)
canvas.drawText(
s,
// handle last entry overflowing
min(
// handle first entry overflowing
max(
(spacing * i + barWidth * (i - 1) + barWidth / 2).toFloat(),
rect.width() / 2f
),
measuredWidth - rect.width() / 2f
),
(height - bottomTextDescent).toFloat(),
xAxis.labels.build()
)
i++
if (numberOfLabels == 1) {
break
}
}
}
}
// override fun onChartMoved(movementX: Float, movementY: Float) {
// movementOffset = (movementX / 100).toInt()
// refresh()
// }
// override fun onZoomChanged(scale: Float) {
// Log.d(TAG, "New Zoom: $scale")
// zoom = (scale * 1.2).toFloat()
// refresh()
// }
private fun getXOffset(): Int {
return min(max(0, xAxis.baseOffset + movementOffset), list.size - getDisplayedEntries())
}
private fun getDisplayedEntries(): Int {
// Log.d(TAG, "Number of entries displayed ${list.size}, ${xAxis.entriesDisplayed} + (($zoom - 100) * 10) = ${xAxis.entriesDisplayed + ((zoom - 100) * 10).toInt()}")
return max(1, min(list.size, xAxis.entriesDisplayed + ((zoom - 100) * 10).toInt()))
}
}

View File

@ -32,6 +32,9 @@ platform :android do
lane :beta do
gradle(task: "clean assembleRelease")
crashlytics
upload_to_play_store(
track: "beta"
)
# sh "your_script.sh"
# You can also use other beta testing services here

View File

@ -9,10 +9,10 @@ org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryErro
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
org.gradle.parallel=true
#Tue Jul 19 18:42:00 CEST 2022
android.nonTransitiveRClass=true
kotlin.code.style=official
android.useAndroidX=true
android.enableJetifier=true
org.gradle.unsafe.configuration-cache=true
android.enableJetifier=false
org.gradle.unsafe.configuration-cache=false

View File

@ -1,6 +1,6 @@
#Thu Jun 30 12:02:06 CEST 2022
#Sun Aug 07 01:05:57 CEST 2022
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME

View File

@ -12,6 +12,7 @@ dependencyResolutionManagement {
google()
mavenCentral()
maven { url 'https://jitpack.io' }
// mavenLocal()
}
}
rootProject.name = "OpenHealth"