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:
parent
cd537470ba
commit
4afa432a29
@ -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(
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user