1
0
mirror of https://github.com/dzeiocom/OpenHealth.git synced 2025-04-23 19:32:11 +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.data.weight.Weight
import com.dzeio.openhealth.databinding.FragmentListWeightBinding import com.dzeio.openhealth.databinding.FragmentListWeightBinding
import com.dzeio.openhealth.units.Units import com.dzeio.openhealth.units.Units
import com.dzeio.openhealth.utils.ChartUtils
import com.dzeio.openhealth.utils.PermissionsUtils import com.dzeio.openhealth.utils.PermissionsUtils
import com.google.android.material.color.MaterialColors import com.google.android.material.color.MaterialColors
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import java.text.DateFormat import java.text.DateFormat
import java.util.Date import java.util.Date
import java.util.Locale import java.util.Locale
import kotlin.math.abs
@AndroidEntryPoint @AndroidEntryPoint
class ListWeightFragment : class ListWeightFragment :
@ -131,16 +133,11 @@ class ListWeightFragment :
} }
viewModel.goalWeight.observe(viewLifecycleOwner) { viewModel.goalWeight.observe(viewLifecycleOwner) {
binding.chart.yAxis.apply { val list = viewModel.weights.value
clearLines() if (list == null) {
if (it != null) { return@observe
addLine(
it,
Line(true, Paint(linePaint).apply { strokeWidth = 4f })
)
}
binding.chart.refresh()
} }
updateGraph(list, it)
} }
viewModel.weights.observe(viewLifecycleOwner) { list -> viewModel.weights.observe(viewLifecycleOwner) { list ->
@ -154,42 +151,40 @@ class ListWeightFragment :
unit.unit, unit.unit,
unit.formatToString(it.weight) unit.formatToString(it.weight)
), ),
getString(
R.string.weight_item,
it.formatTimestamp(), 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 icon = R.drawable.ic_outline_edit_24
) )
}.reversed() }.reversed()
) )
updateGraph(list) updateGraph(list, viewModel.goalWeight.value)
} }
} }
val chart = binding.chart 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 { 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 animator.enabled = false
yAxis.apply { yAxis.apply {
setYMin(null) 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()}" } onValueFormat = { value -> "${value.toInt()}" }
} }
@ -198,10 +193,6 @@ class ListWeightFragment :
// 7 day history // 7 day history
dataWidth = 6.048e+8 * 4 dataWidth = 6.048e+8 * 4
scrollEnabled = true scrollEnabled = true
textPaint.color = MaterialColors.getColor(
requireView(),
com.google.android.material.R.attr.colorOnPrimaryContainer
)
textPaint.textSize = 32f textPaint.textSize = 32f
onValueFormat = onValueFormat@{ onValueFormat = onValueFormat@{
val formatter = DateFormat.getDateTimeInstance( val formatter = DateFormat.getDateTimeInstance(
@ -232,12 +223,65 @@ class ListWeightFragment :
requireActivity().removeMenuProvider(menuProvider) requireActivity().removeMenuProvider(menuProvider)
} }
private fun updateGraph(list: List<Weight>) { private fun updateGraph(list: List<Weight>, goal: Float? = null) {
val chart = binding.chart val chart = binding.chart
val serie = chart.series[0] as LineSerie val serie = chart.series[0] as LineSerie
val avSerie = chart.series[1] as LineSerie
val entries: ArrayList<Entry> = arrayListOf() 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 { list.forEach {
entries.add( entries.add(
Entry( Entry(

View File

@ -64,6 +64,7 @@
<string name="import_complete">Import réussi, redémarrage de l\'application</string> <string name="import_complete">Import réussi, redémarrage de l\'application</string>
<string name="export_complete">Export Réussi!</string> <string name="export_complete">Export Réussi!</string>
<string name="import_export">Importer/Exporter</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 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"> <string-array name="activity_levels">
<item>Cloué au lit</item> <item>Cloué au lit</item>

View File

@ -77,6 +77,7 @@
<string name="import_complete">Import successful, Restarting the application</string> <string name="import_complete">Import successful, Restarting the application</string>
<string name="export_complete">Export successful!</string> <string name="export_complete">Export successful!</string>
<string name="import_export">Import/Export</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 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"> <string-array name="activity_levels">
<item>Bedridden</item> <item>Bedridden</item>