1
0
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:
Florian Bouillon 2022-12-17 21:15:20 +01:00
parent 9d3585c42c
commit 1e4267731b
Signed by: Florian Bouillon
GPG Key ID: BEEAF3722D0EBF64
21 changed files with 336 additions and 64 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -91,5 +91,10 @@ class SettingsFragment : PreferenceFragmentCompat() {
return@setOnPreferenceChangeListener true return@setOnPreferenceChangeListener true
} }
val stepsGoalPreference = findPreference<EditTextPreference>(Settings.STEPS_GOAL)
stepsGoalPreference.apply {
}
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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