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

feat: Add average graph and estimation on goal completion (#153)

This commit is contained in:
Florian Bouillon 2023-02-28 15:49:09 +01:00 committed by GitHub
parent cd537470ba
commit 4afa432a29
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 82 additions and 36 deletions

View File

@ -26,12 +26,14 @@ import com.dzeio.openhealth.core.BaseFragment
import com.dzeio.openhealth.data.weight.Weight
import com.dzeio.openhealth.databinding.FragmentListWeightBinding
import com.dzeio.openhealth.units.Units
import com.dzeio.openhealth.utils.ChartUtils
import com.dzeio.openhealth.utils.PermissionsUtils
import com.google.android.material.color.MaterialColors
import dagger.hilt.android.AndroidEntryPoint
import java.text.DateFormat
import java.util.Date
import java.util.Locale
import kotlin.math.abs
@AndroidEntryPoint
class ListWeightFragment :
@ -131,16 +133,11 @@ class ListWeightFragment :
}
viewModel.goalWeight.observe(viewLifecycleOwner) {
binding.chart.yAxis.apply {
clearLines()
if (it != null) {
addLine(
it,
Line(true, Paint(linePaint).apply { strokeWidth = 4f })
)
}
binding.chart.refresh()
val list = viewModel.weights.value
if (list == null) {
return@observe
}
updateGraph(list, it)
}
viewModel.weights.observe(viewLifecycleOwner) { list ->
@ -154,42 +151,40 @@ class ListWeightFragment :
unit.unit,
unit.formatToString(it.weight)
),
it.formatTimestamp(),
getString(
R.string.weight_item,
it.formatTimestamp(),
it.bmi ?: 0f,
it.totalBodyWater ?: 0f,
it.muscles ?: 0f,
it.leanBodyMass ?: 0f,
it.bodyFat ?: 0f,
it.boneMass ?: 0f,
it.visceralFat ?: 0f,
it.basalMetabolicRate ?: 0,
it.totalDailyEnergyExpendure ?: 0
),
icon = R.drawable.ic_outline_edit_24
)
}.reversed()
)
updateGraph(list)
updateGraph(list, viewModel.goalWeight.value)
}
}
val chart = binding.chart
val serie = LineSerie(chart).apply {
linePaint.color = MaterialColors.getColor(
requireView(),
com.google.android.material.R.attr.colorPrimary
)
textPaint.color = MaterialColors.getColor(
requireView(),
com.google.android.material.R.attr.colorOnPrimary
)
}
chart.apply {
series = arrayListOf(serie)
series = arrayListOf(LineSerie(chart), LineSerie(chart))
ChartUtils.materielTheme(this, requireView())
(series[1] as LineSerie).linePaint.color = MaterialColors.getColor(
requireView(),
com.google.android.material.R.attr.colorSecondary
)
animator.enabled = false
yAxis.apply {
setYMin(null)
textLabel.color = MaterialColors.getColor(
requireView(),
com.google.android.material.R.attr.colorOnPrimaryContainer
)
linePaint.color = MaterialColors.getColor(
requireView(),
com.google.android.material.R.attr.colorOnPrimaryContainer
)
onValueFormat = { value -> "${value.toInt()}" }
}
@ -198,10 +193,6 @@ class ListWeightFragment :
// 7 day history
dataWidth = 6.048e+8 * 4
scrollEnabled = true
textPaint.color = MaterialColors.getColor(
requireView(),
com.google.android.material.R.attr.colorOnPrimaryContainer
)
textPaint.textSize = 32f
onValueFormat = onValueFormat@{
val formatter = DateFormat.getDateTimeInstance(
@ -232,12 +223,65 @@ class ListWeightFragment :
requireActivity().removeMenuProvider(menuProvider)
}
private fun updateGraph(list: List<Weight>) {
private fun updateGraph(list: List<Weight>, goal: Float? = null) {
val chart = binding.chart
val serie = chart.series[0] as LineSerie
val avSerie = chart.series[1] as LineSerie
val entries: ArrayList<Entry> = arrayListOf()
val previous = 5
val next = 5
val averageList = arrayListOf<Entry>()
for (i in list.indices) {
val sub = list.subList((i - previous).coerceAtLeast(0), (i + next).coerceAtMost(list.size - 1))
val average = (sub.map { it.weight }.reduceOrNull { acc, it -> it + acc } ?: 0f) / sub.size
val it = list[i]
averageList.add(
Entry(
it.timestamp.toDouble(),
average
)
)
}
val direction = averageList.last().y - averageList[averageList.size - 2].y
chart.yAxis.clearLines()
if (goal != null) {
chart.yAxis.apply {
addLine(
goal,
Line(true, Paint(linePaint).apply { strokeWidth = 4f })
)
}
val dt = averageList.last().x - averageList[list.size - previous].x
val timeUntilGoal = ((list.last().weight - goal) / abs(direction)).toLong() * dt
binding.goalText.text = getString(
R.string.days_until_goal_is_achieved,
(timeUntilGoal / 1000 / 60 / 60 / 24).toInt()
)
averageList.add(
Entry(
(list.last().timestamp + timeUntilGoal),
goal
)
)
binding.goalText.visibility = View.VISIBLE
} else {
averageList.add(
Entry(
averageList.last().x + chart.xAxis.dataWidth!!,
averageList.last().y + direction * 7 * 4
)
)
binding.goalText.visibility = View.GONE
}
avSerie.entries = averageList
list.forEach {
entries.add(
Entry(

View File

@ -64,6 +64,7 @@
<string name="import_complete">Import réussi, redémarrage de l\'application</string>
<string name="export_complete">Export Réussi!</string>
<string name="import_export">Importer/Exporter</string>
<string name="days_until_goal_is_achieved">Jours avant d\'atteindre son but: ~%1$d jours</string>
<string name="weight_item">Date: %1$s\nBMI: %2$.2f\nEau corporelle: %3$.2f\nMuscles: %4$.2f\nMasse maigre: %5$.2f\nMasse grasse: %6$.2f\nMasse osseuse: %7$.2f\nGraisse viscérale: %8$.2f\nMétabolisme basal: %9$d\nDépense énergétique quotidienne totale: %10$d</string>
<string-array name="activity_levels">
<item>Cloué au lit</item>

View File

@ -77,6 +77,7 @@
<string name="import_complete">Import successful, Restarting the application</string>
<string name="export_complete">Export successful!</string>
<string name="import_export">Import/Export</string>
<string name="days_until_goal_is_achieved">Days until goal is achieved: ~%1$d days</string>
<string name="weight_item">Date: %1$s\nBMI: %2$.2f\nBody water: %3$.2f\nMuscles: %4$.2f\nLean body mass: %5$.2f\nBody fat: %6$.2f\nBone mass: %7$.2f\nVisceral fat: %8$.2f\nBasal metabolic rate: %9$d\nTotal daily energy expendure: %10$d\n</string>
<string-array name="activity_levels">
<item>Bedridden</item>