mirror of
https://github.com/dzeiocom/OpenHealth.git
synced 2025-04-23 03:12:14 +00:00
feat: Add daily Steps goal
This commit is contained in:
parent
9d3585c42c
commit
1e4267731b
@ -58,7 +58,7 @@ android {
|
|||||||
targetSdk = sdkTarget
|
targetSdk = sdkTarget
|
||||||
|
|
||||||
// Semantic Versioning
|
// Semantic Versioning
|
||||||
versionName = "1.0.0"
|
versionName = "0.1.0"
|
||||||
versionCode = 1
|
versionCode = 1
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
@ -82,6 +82,7 @@ android {
|
|||||||
getByName("release") {
|
getByName("release") {
|
||||||
// Slimmer version
|
// Slimmer version
|
||||||
isMinifyEnabled = true
|
isMinifyEnabled = true
|
||||||
|
isDebuggable = true
|
||||||
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
|
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
|
||||||
signingConfig = signingConfigs.getByName("release")
|
signingConfig = signingConfigs.getByName("release")
|
||||||
}
|
}
|
||||||
@ -129,7 +130,7 @@ dependencies {
|
|||||||
implementation("androidx.core:core-ktx:1.9.0")
|
implementation("androidx.core:core-ktx:1.9.0")
|
||||||
implementation("androidx.appcompat:appcompat:1.7.0-alpha01")
|
implementation("androidx.appcompat:appcompat:1.7.0-alpha01")
|
||||||
implementation("javax.inject:javax.inject:1")
|
implementation("javax.inject:javax.inject:1")
|
||||||
implementation("com.google.android.material:material:1.8.0-alpha03")
|
implementation("com.google.android.material:material:1.8.0-beta01")
|
||||||
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
|
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
|
||||||
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.5.1")
|
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.5.1")
|
||||||
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1")
|
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1")
|
||||||
@ -173,13 +174,13 @@ dependencies {
|
|||||||
kapt("com.google.dagger:hilt-compiler:2.43.2")
|
kapt("com.google.dagger:hilt-compiler:2.43.2")
|
||||||
|
|
||||||
// Google Fit
|
// Google Fit
|
||||||
implementation("com.google.android.gms:play-services-fitness:21.1.0")
|
// implementation("com.google.android.gms:play-services-fitness:21.1.0")
|
||||||
implementation("com.google.android.gms:play-services-auth:20.4.0")
|
// implementation("com.google.android.gms:play-services-auth:20.4.0")
|
||||||
implementation("androidx.health.connect:connect-client:1.0.0-alpha07")
|
// implementation("androidx.health.connect:connect-client:1.0.0-alpha08")
|
||||||
|
|
||||||
// Samsung Health
|
// Samsung Health
|
||||||
implementation(files("libs/samsung-health-data-1.5.0.aar"))
|
// implementation(files("libs/samsung-health-data-1.5.0.aar"))
|
||||||
implementation("com.google.code.gson:gson:2.9.1")
|
// implementation("com.google.code.gson:gson:2.9.1")
|
||||||
|
|
||||||
// ROOM
|
// ROOM
|
||||||
implementation("androidx.room:room-runtime:2.4.3")
|
implementation("androidx.room:room-runtime:2.4.3")
|
||||||
@ -195,6 +196,7 @@ dependencies {
|
|||||||
// OSS Licenses
|
// OSS Licenses
|
||||||
implementation("com.google.android.gms:play-services-oss-licenses:17.0.0")
|
implementation("com.google.android.gms:play-services-oss-licenses:17.0.0")
|
||||||
|
|
||||||
|
// Open Food Fact
|
||||||
implementation("com.squareup.retrofit2:retrofit:2.9.0")
|
implementation("com.squareup.retrofit2:retrofit:2.9.0")
|
||||||
implementation("com.squareup.retrofit2:converter-gson:2.9.0")
|
implementation("com.squareup.retrofit2:converter-gson:2.9.0")
|
||||||
implementation("com.squareup.okhttp3:logging-interceptor:4.9.1")
|
implementation("com.squareup.okhttp3:logging-interceptor:4.9.1")
|
||||||
|
@ -26,4 +26,9 @@ object Settings {
|
|||||||
* format in which the weight the user want it to be displayed as
|
* format in which the weight the user want it to be displayed as
|
||||||
*/
|
*/
|
||||||
const val MASS_UNIT = "com.dzeio.open-health.unit.mass"
|
const val MASS_UNIT = "com.dzeio.open-health.unit.mass"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Goal number of steps each days
|
||||||
|
*/
|
||||||
|
const val STEPS_GOAL = "com.dzeio.open-health.steps.goal-daily"
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ data class OFFProduct(
|
|||||||
@SerializedName("product_name")
|
@SerializedName("product_name")
|
||||||
var name: String,
|
var name: String,
|
||||||
|
|
||||||
@SerializedName("serving_quantity")
|
@SerializedName("serving_size")
|
||||||
var serving: String,
|
var serving: String,
|
||||||
|
|
||||||
@SerializedName("nutriments")
|
@SerializedName("nutriments")
|
||||||
|
@ -11,13 +11,16 @@ interface StepDao : BaseDao<Step> {
|
|||||||
@Query("SELECT * FROM Step ORDER BY timestamp DESC")
|
@Query("SELECT * FROM Step ORDER BY timestamp DESC")
|
||||||
fun getAll(): Flow<List<Step>>
|
fun getAll(): Flow<List<Step>>
|
||||||
|
|
||||||
@Query("SELECT * FROM Step where id = :weightId")
|
@Query("SELECT * FROM Step WHERE timestamp >= :time")
|
||||||
|
fun getAfter(time: Long): Flow<List<Step>>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM Step WHERE id = :weightId")
|
||||||
fun getOne(weightId: Long): Flow<Step?>
|
fun getOne(weightId: Long): Flow<Step?>
|
||||||
|
|
||||||
@Query("Select count(*) from Step")
|
@Query("SELECT count(*) FROM Step")
|
||||||
fun getCount(): Flow<Int>
|
fun getCount(): Flow<Int>
|
||||||
|
|
||||||
@Query("Select * FROM Step ORDER BY timestamp DESC LIMIT 1")
|
@Query("SELECT * FROM Step ORDER BY timestamp DESC LIMIT 1")
|
||||||
fun last(): Flow<Step?>
|
fun last(): Flow<Step?>
|
||||||
|
|
||||||
@Query("DELETE FROM Step where source = :source")
|
@Query("DELETE FROM Step where source = :source")
|
||||||
|
@ -2,7 +2,8 @@ package com.dzeio.openhealth.data.step
|
|||||||
|
|
||||||
import kotlinx.coroutines.flow.filter
|
import kotlinx.coroutines.flow.filter
|
||||||
import kotlinx.coroutines.flow.firstOrNull
|
import kotlinx.coroutines.flow.firstOrNull
|
||||||
import kotlinx.coroutines.withTimeoutOrNull
|
import java.util.Calendar
|
||||||
|
import java.util.TimeZone
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@ -23,7 +24,13 @@ class StepRepository @Inject constructor(
|
|||||||
suspend fun deleteFromSource(value: String) = stepDao.deleteFromSource(value)
|
suspend fun deleteFromSource(value: String) = stepDao.deleteFromSource(value)
|
||||||
|
|
||||||
suspend fun todaySteps(): Int {
|
suspend fun todaySteps(): Int {
|
||||||
val steps = getSteps().firstOrNull()
|
val cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
|
||||||
|
|
||||||
|
cal.set(Calendar.HOUR, 0)
|
||||||
|
cal.set(Calendar.MINUTE, 0)
|
||||||
|
cal.set(Calendar.SECOND, 0)
|
||||||
|
cal.set(Calendar.MILLISECOND, 0)
|
||||||
|
val steps = stepDao.getAfter(cal.timeInMillis).firstOrNull()
|
||||||
if (steps == null) {
|
if (steps == null) {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ class AboutFragment : BaseStaticFragment<FragmentAboutBinding>() {
|
|||||||
resources.getString(R.string.version_number, BuildConfig.VERSION_NAME)
|
resources.getString(R.string.version_number, BuildConfig.VERSION_NAME)
|
||||||
|
|
||||||
binding.contactUs.setOnClickListener {
|
binding.contactUs.setOnClickListener {
|
||||||
openLink("mailto:context.openhealth@dze.io")
|
openLink("mailto:contact.openhealth@dze.io")
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.github.setOnClickListener {
|
binding.github.setOnClickListener {
|
||||||
|
@ -92,18 +92,29 @@ class BrowseFragment :
|
|||||||
}
|
}
|
||||||
|
|
||||||
viewModel.steps.observe(viewLifecycleOwner) {
|
viewModel.steps.observe(viewLifecycleOwner) {
|
||||||
binding.stepsText.setText("$it of xxx steps")
|
updateStepsText(it, viewModel.stepsGoal.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.stepsGoal.observe(viewLifecycleOwner) {
|
||||||
|
updateStepsText(viewModel.steps.value, it)
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModel.weight.observe(viewLifecycleOwner) {
|
viewModel.weight.observe(viewLifecycleOwner) {
|
||||||
binding.weightText.setText(
|
binding.weightText.setText(
|
||||||
String.format(
|
String.format(
|
||||||
resources.getString(R.string.weight_current),
|
resources.getString(R.string.weight_current),
|
||||||
it,
|
String.format(resources.getString(R.string.unit_mass_kilogram_unit), it)
|
||||||
resources.getString(R.string.unit_mass_kilogram_unit)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun updateStepsText(numberOfSteps: Int?, goal: Int?) {
|
||||||
|
var text = "${numberOfSteps ?: 0} steps"
|
||||||
|
if (goal != null) {
|
||||||
|
text = "${numberOfSteps ?: 0} of $goal steps"
|
||||||
|
}
|
||||||
|
binding.stepsText.setText(text)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,11 @@ package com.dzeio.openhealth.ui.browse
|
|||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.dzeio.openhealth.Settings
|
||||||
import com.dzeio.openhealth.core.BaseViewModel
|
import com.dzeio.openhealth.core.BaseViewModel
|
||||||
import com.dzeio.openhealth.data.step.StepRepository
|
import com.dzeio.openhealth.data.step.StepRepository
|
||||||
import com.dzeio.openhealth.data.weight.WeightRepository
|
import com.dzeio.openhealth.data.weight.WeightRepository
|
||||||
|
import com.dzeio.openhealth.utils.Configuration
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@ -14,12 +16,16 @@ import javax.inject.Inject
|
|||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class BrowseViewModel @Inject internal constructor(
|
class BrowseViewModel @Inject internal constructor(
|
||||||
stepRepository: StepRepository,
|
stepRepository: StepRepository,
|
||||||
weightRepository: WeightRepository
|
weightRepository: WeightRepository,
|
||||||
|
private val config: Configuration
|
||||||
) : BaseViewModel() {
|
) : BaseViewModel() {
|
||||||
|
|
||||||
private val _steps = MutableLiveData(0)
|
private val _steps = MutableLiveData(0)
|
||||||
val steps: LiveData<Int> = _steps
|
val steps: LiveData<Int> = _steps
|
||||||
|
|
||||||
|
private val _stepsGoal: MutableLiveData<Int?> = MutableLiveData()
|
||||||
|
val stepsGoal: LiveData<Int?> = _stepsGoal
|
||||||
|
|
||||||
private val _weight = MutableLiveData(0f)
|
private val _weight = MutableLiveData(0f)
|
||||||
val weight: LiveData<Float> = _weight
|
val weight: LiveData<Float> = _weight
|
||||||
|
|
||||||
@ -36,5 +42,9 @@ class BrowseViewModel @Inject internal constructor(
|
|||||||
_weight.postValue(it.weight)
|
_weight.postValue(it.weight)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._stepsGoal.postValue(
|
||||||
|
config.getInt(Settings.STEPS_GOAL).value
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -91,5 +91,10 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
|||||||
|
|
||||||
return@setOnPreferenceChangeListener true
|
return@setOnPreferenceChangeListener true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val stepsGoalPreference = findPreference<EditTextPreference>(Settings.STEPS_GOAL)
|
||||||
|
stepsGoalPreference.apply {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,12 +64,16 @@ class StepsHomeFragment :
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val errorColor = MaterialColors.getColor(
|
||||||
|
requireView(),
|
||||||
|
com.google.android.material.R.attr.colorError
|
||||||
|
)
|
||||||
|
|
||||||
chart.apply {
|
chart.apply {
|
||||||
series = arrayListOf(serie)
|
series = arrayListOf(serie)
|
||||||
// debug = true
|
// debug = true
|
||||||
|
|
||||||
yAxis.apply {
|
yAxis.apply {
|
||||||
setYMax(500f)
|
|
||||||
textLabel.color = MaterialColors.getColor(
|
textLabel.color = MaterialColors.getColor(
|
||||||
requireView(),
|
requireView(),
|
||||||
com.google.android.material.R.attr.colorOnPrimaryContainer
|
com.google.android.material.R.attr.colorOnPrimaryContainer
|
||||||
@ -78,14 +82,15 @@ class StepsHomeFragment :
|
|||||||
requireView(),
|
requireView(),
|
||||||
com.google.android.material.R.attr.colorOnPrimaryContainer
|
com.google.android.material.R.attr.colorOnPrimaryContainer
|
||||||
)
|
)
|
||||||
|
goalLinePaint.color = errorColor
|
||||||
//
|
//
|
||||||
onValueFormat = { value -> "${value.toInt()}" }
|
onValueFormat = { value -> "${value.toInt()}" }
|
||||||
}
|
}
|
||||||
|
|
||||||
xAxis.apply {
|
xAxis.apply {
|
||||||
increment = 3600000.0
|
increment = 86400000.0
|
||||||
// displayCount = 168
|
// displayCount = 168
|
||||||
displayCount = 10
|
// displayCount = 10
|
||||||
textPaint.color = MaterialColors.getColor(
|
textPaint.color = MaterialColors.getColor(
|
||||||
requireView(),
|
requireView(),
|
||||||
com.google.android.material.R.attr.colorOnPrimaryContainer
|
com.google.android.material.R.attr.colorOnPrimaryContainer
|
||||||
@ -103,6 +108,11 @@ class StepsHomeFragment :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
viewModel.goal.observe(viewLifecycleOwner) {
|
||||||
|
chart.yAxis.setGoalLine(it?.toFloat())
|
||||||
|
chart.refresh()
|
||||||
|
}
|
||||||
|
|
||||||
viewModel.items.observe(viewLifecycleOwner) { list ->
|
viewModel.items.observe(viewLifecycleOwner) { list ->
|
||||||
adapter.set(list)
|
adapter.set(list)
|
||||||
|
|
||||||
@ -110,12 +120,7 @@ class StepsHomeFragment :
|
|||||||
return@observe
|
return@observe
|
||||||
}
|
}
|
||||||
|
|
||||||
val cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
|
|
||||||
|
|
||||||
cal.set(Calendar.HOUR, 0)
|
|
||||||
cal.set(Calendar.MINUTE, 0)
|
|
||||||
cal.set(Calendar.SECOND, 0)
|
|
||||||
cal.set(Calendar.MILLISECOND, 0)
|
|
||||||
|
|
||||||
// chart.animation.enabled = false
|
// chart.animation.enabled = false
|
||||||
// chart.animation.refreshRate = 60
|
// chart.animation.refreshRate = 60
|
||||||
@ -125,11 +130,33 @@ class StepsHomeFragment :
|
|||||||
|
|
||||||
// chart.xAxis.labels.size = 32f
|
// chart.xAxis.labels.size = 32f
|
||||||
|
|
||||||
serie.entries = list.reversed().map {
|
val entries: HashMap<Long, Entry> = HashMap()
|
||||||
return@map Entry(it.timestamp.toDouble(), it.value.toFloat())
|
|
||||||
} as ArrayList<Entry>
|
|
||||||
|
|
||||||
chart.xAxis.x = serie.entries.first().x
|
list.forEach {
|
||||||
|
val cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
|
||||||
|
cal.timeInMillis = it.timestamp
|
||||||
|
|
||||||
|
cal.set(Calendar.HOUR, 0)
|
||||||
|
cal.set(Calendar.AM_PM, Calendar.AM)
|
||||||
|
val ts = cal.timeInMillis
|
||||||
|
if (!entries.containsKey(ts)) {
|
||||||
|
entries[ts] = Entry((ts).toDouble(), 0F, errorColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
entries[ts]!!.y += it.value.toFloat()
|
||||||
|
|
||||||
|
if (viewModel.goal.value != null) {
|
||||||
|
if (entries[ts]!!.y > viewModel.goal.value!!) {
|
||||||
|
entries[ts]!!.color = null
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
entries[ts]!!.color = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
serie.entries = ArrayList(entries.values)
|
||||||
|
|
||||||
|
chart.xAxis.x = chart.xAxis.getXMax()
|
||||||
|
|
||||||
|
|
||||||
chart.refresh()
|
chart.refresh()
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
package com.dzeio.openhealth.ui.steps
|
package com.dzeio.openhealth.ui.steps
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.dzeio.openhealth.Settings
|
||||||
import com.dzeio.openhealth.core.BaseViewModel
|
import com.dzeio.openhealth.core.BaseViewModel
|
||||||
import com.dzeio.openhealth.data.step.Step
|
import com.dzeio.openhealth.data.step.Step
|
||||||
import com.dzeio.openhealth.data.step.StepRepository
|
import com.dzeio.openhealth.data.step.StepRepository
|
||||||
|
import com.dzeio.openhealth.utils.Configuration
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@ -12,15 +15,23 @@ import javax.inject.Inject
|
|||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class StepsHomeViewModel@Inject internal constructor(
|
class StepsHomeViewModel@Inject internal constructor(
|
||||||
private val stepRepository: StepRepository
|
private val stepRepository: StepRepository,
|
||||||
|
private val config: Configuration
|
||||||
) : BaseViewModel() {
|
) : BaseViewModel() {
|
||||||
val items: MutableLiveData<List<Step>> = MutableLiveData()
|
val items: MutableLiveData<List<Step>> = MutableLiveData()
|
||||||
|
|
||||||
|
private val _goal: MutableLiveData<Int?> = MutableLiveData()
|
||||||
|
val goal: LiveData<Int?> = _goal
|
||||||
|
|
||||||
fun init() {
|
fun init() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
stepRepository.getSteps().collectLatest {
|
stepRepository.getSteps().collectLatest {
|
||||||
items.postValue(it)
|
items.postValue(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._goal.postValue(
|
||||||
|
config.getInt(Settings.STEPS_GOAL).value
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,87 @@
|
|||||||
|
package com.dzeio.openhealth.utils.fields
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.text.InputType
|
||||||
|
import android.text.TextUtils
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.util.Log
|
||||||
|
import android.widget.EditText
|
||||||
|
import androidx.preference.EditTextPreference
|
||||||
|
|
||||||
|
|
||||||
|
class IntEditTextPreference : EditTextPreference, EditTextPreference.OnBindEditTextListener {
|
||||||
|
|
||||||
|
private var txt: String? = null
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
context: Context,
|
||||||
|
attrs: AttributeSet?,
|
||||||
|
defStyleAttr: Int,
|
||||||
|
defStyleRes: Int
|
||||||
|
) : super(context, attrs, defStyleAttr, defStyleRes) {
|
||||||
|
setOnBindEditTextListener(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
|
||||||
|
context,
|
||||||
|
attrs,
|
||||||
|
defStyleAttr
|
||||||
|
) {
|
||||||
|
setOnBindEditTextListener(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
|
||||||
|
setOnBindEditTextListener(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(context: Context) : super(context) {
|
||||||
|
setOnBindEditTextListener(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves the text to the current data storage.
|
||||||
|
*
|
||||||
|
* @param text The text to save
|
||||||
|
*/
|
||||||
|
override fun setText(text: String?) {
|
||||||
|
|
||||||
|
val wasBlocking = shouldDisableDependents()
|
||||||
|
val pouet = Integer.parseInt(text.toString())
|
||||||
|
this.txt = text
|
||||||
|
pouet::class.simpleName?.let { Log.d("pouet2", it) }
|
||||||
|
persistInt(pouet)
|
||||||
|
val isBlocking = shouldDisableDependents()
|
||||||
|
if (isBlocking != wasBlocking) {
|
||||||
|
notifyDependencyChange(isBlocking)
|
||||||
|
}
|
||||||
|
notifyChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getText(): String? {
|
||||||
|
return this.txt
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSetInitialValue(defaultValue: Any?) {
|
||||||
|
var value: Int
|
||||||
|
if (defaultValue != null) {
|
||||||
|
val strDefaultValue = defaultValue as String
|
||||||
|
val defaultIntValue = strDefaultValue.toInt()
|
||||||
|
value = getPersistedInt(defaultIntValue)
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
value = getPersistedInt(0)
|
||||||
|
} catch (e: ClassCastException) {
|
||||||
|
value = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
text = value.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun shouldDisableDependents(): Boolean {
|
||||||
|
return TextUtils.isEmpty(text) || super.shouldDisableDependents()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindEditText(editText: EditText) {
|
||||||
|
editText.inputType = InputType.TYPE_CLASS_NUMBER
|
||||||
|
}
|
||||||
|
}
|
@ -13,11 +13,11 @@
|
|||||||
<!-- Units -->
|
<!-- Units -->
|
||||||
<string name="unit_mass_kilogram_name_singular">Kilogram</string>
|
<string name="unit_mass_kilogram_name_singular">Kilogram</string>
|
||||||
<string name="unit_mass_kilogram_name_plural">Kilograms</string>
|
<string name="unit_mass_kilogram_name_plural">Kilograms</string>
|
||||||
<string name="unit_mass_kilogram_unit" translatable="false">kg</string>
|
<string name="unit_mass_kilogram_unit" translatable="false">%1$skg</string>
|
||||||
|
|
||||||
<string name="unit_mass_pound_name_singular">Pound</string>
|
<string name="unit_mass_pound_name_singular">Pound</string>
|
||||||
<string name="unit_mass_pound_name_plural">Pounds</string>
|
<string name="unit_mass_pound_name_plural">Pounds</string>
|
||||||
<string name="unit_mass_pound_unit" translatable="false">lbs</string>
|
<string name="unit_mass_pound_unit" translatable="false">%1$slbs</string>
|
||||||
|
|
||||||
<string name="unit_volume_milliliter_name_singular">Milliliter</string>
|
<string name="unit_volume_milliliter_name_singular">Milliliter</string>
|
||||||
<string name="unit_volume_milliliter_name_plural">Milliliters</string>
|
<string name="unit_volume_milliliter_name_plural">Milliliters</string>
|
||||||
@ -52,7 +52,7 @@
|
|||||||
<string name="permission_declined">You declined a permission, you can\'t use this extension unless you enable it manually</string>
|
<string name="permission_declined">You declined a permission, you can\'t use this extension unless you enable it manually</string>
|
||||||
<string name="edit_daily_goal">Modifiy daily goal</string>
|
<string name="edit_daily_goal">Modifiy daily goal</string>
|
||||||
<string name="menu_steps">Steps</string>
|
<string name="menu_steps">Steps</string>
|
||||||
<string name="weight_current">Current weight: %1$s%2$s</string>
|
<string name="weight_current">Current weight: %1$s</string>
|
||||||
|
|
||||||
|
|
||||||
<!-- Error Activity Translations -->
|
<!-- Error Activity Translations -->
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
android:key="com.dzeio.open-health.app.language"
|
android:key="com.dzeio.open-health.app.language"
|
||||||
android:title="@string/languages" />
|
android:title="@string/languages" />
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
<PreferenceCategory android:title="Weight Settings">
|
<PreferenceCategory android:title="Weight Settings">
|
||||||
|
|
||||||
<EditTextPreference
|
<EditTextPreference
|
||||||
@ -32,9 +33,8 @@
|
|||||||
android:entryValues="@array/mass_units"
|
android:entryValues="@array/mass_units"
|
||||||
android:key="com.dzeio.open-health.unit.mass"
|
android:key="com.dzeio.open-health.unit.mass"
|
||||||
android:title="Mass Unit" />
|
android:title="Mass Unit" />
|
||||||
|
|
||||||
|
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
<PreferenceCategory android:title="Water Settings">
|
<PreferenceCategory android:title="Water Settings">
|
||||||
|
|
||||||
<SwitchPreference
|
<SwitchPreference
|
||||||
@ -55,4 +55,14 @@
|
|||||||
android:inputType="number"
|
android:inputType="number"
|
||||||
android:title="Daily Water intake" />
|
android:title="Daily Water intake" />
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
|
<PreferenceCategory android:title="Steps settings">
|
||||||
|
<com.dzeio.openhealth.utils.fields.IntEditTextPreference
|
||||||
|
android:key="com.dzeio.open-health.steps.goal-daily"
|
||||||
|
android:title="Number of steps each steps"
|
||||||
|
android:selectAllOnFocus="true"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:inputType="number"
|
||||||
|
/>
|
||||||
|
</PreferenceCategory>
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
||||||
|
@ -4,6 +4,7 @@ package com.dzeio.charts
|
|||||||
* A Base entry for any charts
|
* A Base entry for any charts
|
||||||
*/
|
*/
|
||||||
data class Entry(
|
data class Entry(
|
||||||
val x: Double,
|
var x: Double,
|
||||||
val y: Float
|
var y: Float,
|
||||||
|
var color: Int? = null
|
||||||
)
|
)
|
||||||
|
@ -14,12 +14,12 @@ class XAxis(
|
|||||||
override var x: Double = 0.0
|
override var x: Double = 0.0
|
||||||
set(value) {
|
set(value) {
|
||||||
val max = getXMax() - increment * displayCount
|
val max = getXMax() - increment * displayCount
|
||||||
if (value > max) {
|
val min = getXMin()
|
||||||
|
if (value > max && min <= max) {
|
||||||
field = max
|
field = max
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val min = getXMin()
|
|
||||||
if (value < min) {
|
if (value < min) {
|
||||||
field = min
|
field = min
|
||||||
return
|
return
|
||||||
|
@ -6,6 +6,7 @@ import android.graphics.Paint
|
|||||||
import android.graphics.Rect
|
import android.graphics.Rect
|
||||||
import android.graphics.RectF
|
import android.graphics.RectF
|
||||||
import com.dzeio.charts.ChartViewInterface
|
import com.dzeio.charts.ChartViewInterface
|
||||||
|
import com.dzeio.charts.utils.drawDottedLine
|
||||||
|
|
||||||
class YAxis(
|
class YAxis(
|
||||||
private val view: ChartViewInterface
|
private val view: ChartViewInterface
|
||||||
@ -25,6 +26,12 @@ class YAxis(
|
|||||||
color = Color.BLUE
|
color = Color.BLUE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override val goalLinePaint = Paint().apply {
|
||||||
|
isAntiAlias = true
|
||||||
|
color = Color.RED
|
||||||
|
strokeWidth = 4f
|
||||||
|
}
|
||||||
|
|
||||||
var onValueFormat: (value: Float) -> String = { it -> it.toString() }
|
var onValueFormat: (value: Float) -> String = { it -> it.toString() }
|
||||||
|
|
||||||
override var labelCount = 5
|
override var labelCount = 5
|
||||||
@ -49,15 +56,19 @@ class YAxis(
|
|||||||
return max!!
|
return max!!
|
||||||
}
|
}
|
||||||
if (view.series.isEmpty()) {
|
if (view.series.isEmpty()) {
|
||||||
return 100f
|
return (this.goalLine ?: 90f) + 10f
|
||||||
}
|
}
|
||||||
return view.series
|
val seriesMax = view.series
|
||||||
.maxOf { serie ->
|
.maxOf { serie ->
|
||||||
if (serie.getDisplayedEntries().isEmpty()) {
|
if (serie.getDisplayedEntries().isEmpty()) {
|
||||||
return@maxOf 0f
|
return@maxOf 0f
|
||||||
}
|
}
|
||||||
return@maxOf serie.getDisplayedEntries().maxOf { entry -> entry.y }
|
return@maxOf serie.getDisplayedEntries().maxOf { entry -> entry.y }
|
||||||
}
|
}
|
||||||
|
if (this.goalLine != null) {
|
||||||
|
return if (seriesMax > this.goalLine!!) seriesMax else this.goalLine!! + 1000f
|
||||||
|
}
|
||||||
|
return seriesMax
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getYMin(): Float {
|
override fun getYMin(): Float {
|
||||||
@ -65,7 +76,7 @@ class YAxis(
|
|||||||
return min!!
|
return min!!
|
||||||
}
|
}
|
||||||
if (view.series.isEmpty()) {
|
if (view.series.isEmpty()) {
|
||||||
return 0f
|
return this.goalLine ?: 0f
|
||||||
}
|
}
|
||||||
return view.series
|
return view.series
|
||||||
.minOf { serie ->
|
.minOf { serie ->
|
||||||
@ -80,6 +91,7 @@ class YAxis(
|
|||||||
if (!enabled) {
|
if (!enabled) {
|
||||||
return 0f
|
return 0f
|
||||||
}
|
}
|
||||||
|
|
||||||
val min = getYMin()
|
val min = getYMin()
|
||||||
val max = getYMax() - min
|
val max = getYMax() - min
|
||||||
val top = space.top
|
val top = space.top
|
||||||
@ -89,7 +101,7 @@ class YAxis(
|
|||||||
val increment = (bottom - top) / labelCount
|
val increment = (bottom - top) / labelCount
|
||||||
val valueIncrement = (max - min) / labelCount
|
val valueIncrement = (max - min) / labelCount
|
||||||
for (index in 0 until labelCount) {
|
for (index in 0 until labelCount) {
|
||||||
val text = onValueFormat((valueIncrement * (index + 1))).toString()
|
val text = onValueFormat((valueIncrement * (index + 1)))
|
||||||
textLabel.getTextBounds(text, 0, text.length, rect)
|
textLabel.getTextBounds(text, 0, text.length, rect)
|
||||||
maxWidth = maxWidth.coerceAtLeast(rect.width().toFloat())
|
maxWidth = maxWidth.coerceAtLeast(rect.width().toFloat())
|
||||||
|
|
||||||
@ -105,10 +117,29 @@ class YAxis(
|
|||||||
canvas.drawLine(space.left, posY, space.right - maxWidth - 32f, posY, linePaint)
|
canvas.drawLine(space.left, posY, space.right - maxWidth - 32f, posY, linePaint)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.goalLine != null) {
|
||||||
|
val pos = (1 - this.goalLine!! / max) * space.height() + space.top
|
||||||
|
canvas.drawDottedLine(
|
||||||
|
0f,
|
||||||
|
pos,
|
||||||
|
space.right - maxWidth - 32f,
|
||||||
|
pos,
|
||||||
|
space.right / 20,
|
||||||
|
goalLinePaint
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return maxWidth + 32f
|
return maxWidth + 32f
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun refresh() {
|
override fun refresh() {
|
||||||
// TODO("Not yet implemented")
|
// TODO("Not yet implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var goalLine: Float? = null
|
||||||
|
|
||||||
|
override fun setGoalLine(height: Float?) {
|
||||||
|
goalLine = height
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,11 @@ sealed interface YAxisInterface {
|
|||||||
*/
|
*/
|
||||||
val linePaint: Paint
|
val linePaint: Paint
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Goal line paint
|
||||||
|
*/
|
||||||
|
val goalLinePaint: Paint
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* run when manually refreshing the system
|
* run when manually refreshing the system
|
||||||
*
|
*
|
||||||
@ -72,4 +77,9 @@ sealed interface YAxisInterface {
|
|||||||
* @return the width of the sidebar
|
* @return the width of the sidebar
|
||||||
*/
|
*/
|
||||||
fun onDraw(canvas: Canvas, space: RectF): Float
|
fun onDraw(canvas: Canvas, space: RectF): Float
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a Goal line
|
||||||
|
*/
|
||||||
|
fun setGoalLine(height: Float?)
|
||||||
}
|
}
|
@ -5,7 +5,6 @@ import android.graphics.Color
|
|||||||
import android.graphics.Paint
|
import android.graphics.Paint
|
||||||
import android.graphics.Rect
|
import android.graphics.Rect
|
||||||
import android.graphics.RectF
|
import android.graphics.RectF
|
||||||
import android.util.Log
|
|
||||||
import com.dzeio.charts.ChartView
|
import com.dzeio.charts.ChartView
|
||||||
import com.dzeio.charts.utils.drawRoundRect
|
import com.dzeio.charts.utils.drawRoundRect
|
||||||
|
|
||||||
@ -47,18 +46,21 @@ class BarSerie(
|
|||||||
for (entry in displayedEntries) {
|
for (entry in displayedEntries) {
|
||||||
// calculated height in percent from 0 to 100
|
// calculated height in percent from 0 to 100
|
||||||
val top = (1 - entry.y / max) * drawableSpace.height() + drawableSpace.top
|
val top = (1 - entry.y / max) * drawableSpace.height() + drawableSpace.top
|
||||||
var posX = drawableSpace.left + (view.xAxis.getPositionOnRect(entry, drawableSpace) - view.xAxis.getXOffset(drawableSpace)).toFloat()
|
var posX = drawableSpace.left + (view.xAxis.getPositionOnRect(
|
||||||
|
entry,
|
||||||
|
drawableSpace
|
||||||
|
) - view.xAxis.getXOffset(drawableSpace)).toFloat()
|
||||||
// Log.d(TAG, "gpor = ${view.xAxis.getPositionOnRect(entry, space)}, gxo = ${view.xAxis.getXOffset(space)}")
|
// 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, "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: ${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(
|
// Log.d(
|
||||||
TAG, """
|
// TAG, """
|
||||||
${posX},
|
// ${posX},
|
||||||
$top,
|
// $top,
|
||||||
${(posX + barWidth)},
|
// ${(posX + barWidth)},
|
||||||
${drawableSpace.bottom}""".trimIndent()
|
// ${drawableSpace.bottom}""".trimIndent()
|
||||||
)
|
// )
|
||||||
|
|
||||||
val right = (posX + barWidth).coerceAtMost(drawableSpace.right)
|
val right = (posX + barWidth).coerceAtMost(drawableSpace.right)
|
||||||
|
|
||||||
@ -72,6 +74,13 @@ class BarSerie(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handle color recoloration
|
||||||
|
val paint = Paint(barPaint)
|
||||||
|
|
||||||
|
if (entry.color != null) {
|
||||||
|
paint.color = entry.color!!
|
||||||
|
}
|
||||||
|
|
||||||
canvas.drawRoundRect(
|
canvas.drawRoundRect(
|
||||||
posX,
|
posX,
|
||||||
top,
|
top,
|
||||||
@ -82,7 +91,7 @@ class BarSerie(
|
|||||||
32f,
|
32f,
|
||||||
0f,
|
0f,
|
||||||
0f,
|
0f,
|
||||||
barPaint
|
paint
|
||||||
)
|
)
|
||||||
|
|
||||||
// handle text display
|
// handle text display
|
||||||
|
@ -55,7 +55,17 @@ fun Canvas.drawDottedLine(
|
|||||||
/**
|
/**
|
||||||
* A more customizable drawRoundRect function
|
* A more customizable drawRoundRect function
|
||||||
*/
|
*/
|
||||||
fun Canvas.drawRoundRect(left: Float, top: Float, right: Float, bottom: Float, topLeft: Float, topRight: Float, bottomLeft: Float, bottomRight: Float, paint: Paint) {
|
fun Canvas.drawRoundRect(
|
||||||
|
left: Float,
|
||||||
|
top: Float,
|
||||||
|
right: Float,
|
||||||
|
bottom: Float,
|
||||||
|
topLeft: Float,
|
||||||
|
topRight: Float,
|
||||||
|
bottomLeft: Float,
|
||||||
|
bottomRight: Float,
|
||||||
|
paint: Paint
|
||||||
|
) {
|
||||||
val maxRound = arrayOf(topLeft, topRight, bottomLeft, bottomRight).maxOf { it }
|
val maxRound = arrayOf(topLeft, topRight, bottomLeft, bottomRight).maxOf { it }
|
||||||
val width = right - left
|
val width = right - left
|
||||||
val height = bottom - top
|
val height = bottom - top
|
||||||
@ -81,14 +91,30 @@ fun Canvas.drawRoundRect(left: Float, top: Float, right: Float, bottom: Float, t
|
|||||||
if (bottomLeft == 0f) {
|
if (bottomLeft == 0f) {
|
||||||
drawRect(left, bottom - height / 2, left + width / 2, bottom, paint)
|
drawRect(left, bottom - height / 2, left + width / 2, bottom, paint)
|
||||||
} else {
|
} else {
|
||||||
drawRoundRect(left, bottom - height / 2, left + width / 2, bottom, bottomLeft, bottomLeft, paint)
|
drawRoundRect(
|
||||||
|
left,
|
||||||
|
bottom - height / 2,
|
||||||
|
left + width / 2,
|
||||||
|
bottom,
|
||||||
|
bottomLeft,
|
||||||
|
bottomLeft,
|
||||||
|
paint
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// bottom right border
|
// bottom right border
|
||||||
if (bottomRight == 0f) {
|
if (bottomRight == 0f) {
|
||||||
drawRect(right - width / 2, bottom - height / 2, right, bottom, paint)
|
drawRect(right - width / 2, bottom - height / 2, right, bottom, paint)
|
||||||
} else {
|
} else {
|
||||||
drawRoundRect(right - width / 2, bottom - height / 2, right, bottom, bottomRight, bottomRight, paint)
|
drawRoundRect(
|
||||||
|
right - width / 2,
|
||||||
|
bottom - height / 2,
|
||||||
|
right,
|
||||||
|
bottom,
|
||||||
|
bottomRight,
|
||||||
|
bottomRight,
|
||||||
|
paint
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -96,6 +122,23 @@ fun Canvas.drawRoundRect(left: Float, top: Float, right: Float, bottom: Float, t
|
|||||||
/**
|
/**
|
||||||
* A more customizable drawRoundRect function
|
* A more customizable drawRoundRect function
|
||||||
*/
|
*/
|
||||||
fun Canvas.drawRoundRect(rect: RectF, topLeft: Float, topRight: Float, bottomLeft: Float, bottomRight: Float, paint: Paint) {
|
fun Canvas.drawRoundRect(
|
||||||
drawRoundRect(rect.left, rect.top, rect.right, rect.bottom, topLeft, topRight, bottomLeft, bottomRight, paint)
|
rect: RectF,
|
||||||
|
topLeft: Float,
|
||||||
|
topRight: Float,
|
||||||
|
bottomLeft: Float,
|
||||||
|
bottomRight: Float,
|
||||||
|
paint: Paint
|
||||||
|
) {
|
||||||
|
drawRoundRect(
|
||||||
|
rect.left,
|
||||||
|
rect.top,
|
||||||
|
rect.right,
|
||||||
|
rect.bottom,
|
||||||
|
topLeft,
|
||||||
|
topRight,
|
||||||
|
bottomLeft,
|
||||||
|
bottomRight,
|
||||||
|
paint
|
||||||
|
)
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user