From 4afa432a29cfed05f1f96d741ac4521776376d4d Mon Sep 17 00:00:00 2001 From: Avior Date: Tue, 28 Feb 2023 15:49:09 +0100 Subject: [PATCH] feat: Add average graph and estimation on goal completion (#153) --- .../ui/weight/ListWeightFragment.kt | 116 ++++++++++++------ app/src/main/res/values-fr/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 3 files changed, 82 insertions(+), 36 deletions(-) diff --git a/app/src/main/java/com/dzeio/openhealth/ui/weight/ListWeightFragment.kt b/app/src/main/java/com/dzeio/openhealth/ui/weight/ListWeightFragment.kt index b031ee2..d8864c7 100644 --- a/app/src/main/java/com/dzeio/openhealth/ui/weight/ListWeightFragment.kt +++ b/app/src/main/java/com/dzeio/openhealth/ui/weight/ListWeightFragment.kt @@ -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) { + private fun updateGraph(list: List, goal: Float? = null) { val chart = binding.chart val serie = chart.series[0] as LineSerie + val avSerie = chart.series[1] as LineSerie val entries: ArrayList = arrayListOf() + val previous = 5 + val next = 5 + val averageList = arrayListOf() + 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( diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 80f7d21..35f6696 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -64,6 +64,7 @@ Import réussi, redémarrage de l\'application Export Réussi! Importer/Exporter + Jours avant d\'atteindre son but: ~%1$d jours 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 Cloué au lit diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 09e486b..ca4efda 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -77,6 +77,7 @@ Import successful, Restarting the application Export successful! Import/Export + Days until goal is achieved: ~%1$d days 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 Bedridden