Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Allow for an "All Time" view in statistics page #712

Merged
merged 9 commits into from
Sep 28, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ enum class Page(
Usage(R.string.usage, { ImageVector.vectorResource(ic_cannabis) }, { Usage() }),
HitTimer(R.string.hit_timer, { Default.LockClock }, { ComposeHitTimer() }),
Symptoms(R.string.symptoms, { Default.MedicalServices }, { Symptoms() }),
Stats(R.string.stats, { Default.GraphicEq }, { StatisticsPage(koinInject()) })
Stats(R.string.stats, { Default.GraphicEq }, { StatisticsPage(koinInject(), koinInject()) })
}

@Composable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,33 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import br.com.colman.petals.settings.SettingsRepository
import br.com.colman.petals.statistics.card.AverageUseCard
import br.com.colman.petals.statistics.component.MultiPeriodSelect
import br.com.colman.petals.statistics.component.Period
import br.com.colman.petals.statistics.component.Period.Zero
import br.com.colman.petals.statistics.graph.AllTimeGraph
import br.com.colman.petals.statistics.graph.UsePerDayOfWeekGraph
import br.com.colman.petals.statistics.graph.UsePerHourGraph
import br.com.colman.petals.use.repository.Use
import br.com.colman.petals.use.repository.UseRepository

@Composable
fun StatisticsPage(useRepository: UseRepository) {
fun StatisticsPage(useRepository: UseRepository, settingsRepository: SettingsRepository) {
var selectedPeriods by remember { mutableStateOf(listOf<Period>(Zero)) }
val uses by useRepository.all().collectAsState(emptyList())
val usesInPeriod = selectedPeriods.associateWith(uses)
val dateFormat = settingsRepository.dateFormat.collectAsState(settingsRepository.dateFormatList.first())

Column(Modifier.verticalScroll(rememberScrollState()).testTag("StatisticsMainColumn")) {
MultiPeriodSelect(selectedPeriods) { selectedPeriods = it }
if (selectedPeriods.isEmpty()) return

UsePerHourGraph(usesInPeriod)
UsePerDayOfWeekGraph(usesInPeriod)
if (uses.isNotEmpty()) {
AllTimeGraph(uses, dateFormat.value)
}
usesInPeriod.forEach { (period, uses) ->
AverageUseCard(uses, period.toDateRange())
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package br.com.colman.petals.statistics.graph

import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import br.com.colman.petals.R.string
import br.com.colman.petals.statistics.graph.component.LineChart
import br.com.colman.petals.statistics.graph.data.createAllTimeDistribution
import br.com.colman.petals.statistics.graph.formatter.DaysSinceFirstUseFormatter
import br.com.colman.petals.use.repository.Use
import com.github.mikephil.charting.components.LimitLine
import com.github.mikephil.charting.data.LineDataSet
import java.time.LocalDate
import java.time.YearMonth

@Composable
fun AllTimeGraph(uses: List<Use>, dateFormat: String) {
val description = stringResource(string.grams_distribution_over_days_since_first_use)
val gramsData = createAllTimeDistribution(uses)
val gramsDataList = listOf(gramsData)

LineChart(gramsDataList, description, 5f) {
axisMinimum = 1f
labelCount = 5
granularity = 1f
valueFormatter = DaysSinceFirstUseFormatter(uses, dateFormat).formatDate
addLimitLine(LimitLine(YearMonth.now().monthValue.toFloat()).apply { lineWidth = 2f })
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import com.github.mikephil.charting.data.LineDataSet
fun LineChart(
datasets: List<LineDataSet>,
description: String,
visibleXRange: Float? = null,
configureXAxis: XAxis.() -> Unit
) {
val colors = MaterialTheme.colors
Expand All @@ -34,6 +35,13 @@ fun LineChart(
chart.notifyDataSetChanged()
chart.invalidate()

visibleXRange?.let {
chart.setVisibleXRange(it, it)
chart.setVisibleXRangeMaximum(datasets[0].xMax)
chart.moveViewToX(datasets[0].xMax)
chart.notifyDataSetChanged()
}

chart.axisRight.isEnabled = false
chart.axisLeft.apply {
axisMinimum = 0f
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package br.com.colman.petals.statistics.graph.data

import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color.Companion.Green
import androidx.compose.ui.graphics.Color.Companion.White
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.res.stringResource
import br.com.colman.petals.R
import br.com.colman.petals.statistics.graph.formatter.GramsValueFormatter
import br.com.colman.petals.use.repository.Use
import br.com.colman.petals.use.repository.totalGrams
import com.github.mikephil.charting.data.Entry
import com.github.mikephil.charting.data.LineDataSet
import java.time.LocalDate.now

@Composable
fun createAllTimeDistribution(uses: List<Use>): LineDataSet {
val label = stringResource(R.string.grams_distribution_over_days_since_first_use)
return createAllTimeLineDataSet(calculateAllTimeGramsDistribution(uses), label)
}

fun createAllTimeLineDataSet(entryList: List<Entry>, label: String): LineDataSet {
return LineDataSet(entryList, label).apply {
valueFormatter = GramsValueFormatter
lineWidth = 6f
setDrawCircles(true)
setDrawFilled(false)
setDrawValues(true)
fillColor = Green.toArgb()
color = Green.toArgb()
valueTextColor = White.toArgb()
valueTextSize = 14f
}
}

private fun calculateAllTimeGramsDistribution(uses: List<Use>): List<Entry> {
val firstUseDate = uses.minBy { it.date }.localDate.toEpochDay()
val daysSinceFirstUse = (firstUseDate..now().toEpochDay()).toList()
return daysSinceFirstUse.associateWith { uses.filter { u -> u.localDate.toEpochDay() == it } }.toSortedMap()
.map { (k, v) ->
Entry((k - dayBeforeFirstUseDate).toFloat(), v.totalGrams.toFloat())
LeoColman marked this conversation as resolved.
Show resolved Hide resolved
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package br.com.colman.petals.statistics.graph.formatter

import br.com.colman.petals.use.repository.Use
import com.github.mikephil.charting.components.AxisBase
import com.github.mikephil.charting.formatter.IAxisValueFormatter
import java.time.LocalDate
import java.time.format.DateTimeFormatter

class DaysSinceFirstUseFormatter(uses: List<Use>, dateFormat: String) {

val formatDate = object : IAxisValueFormatter {
override fun getFormattedValue(value: Float, axis: AxisBase?): String {
val formatter = DateTimeFormatter.ofPattern(dateFormat)
val dayBeforeFirstUseDateToEpochDay: Long = uses.minBy { it.date }.localDate.toEpochDay() - 1

val epochDay = (value + dayBeforeFirstUseDateToEpochDay).toLong()
val localDate = LocalDate.ofEpochDay(epochDay)

return formatter.format(localDate)
}
}
}
1 change: 1 addition & 0 deletions app/src/main/res/values-pt/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -179,4 +179,5 @@
<string name="hours_24">24 horas</string>
<string name="remove_ads_title">Remover Anúncios</string>
<string name="remove_ads">Ativar uma experiência sem anúncios</string>
<string name="grams_distribution_over_days_since_first_use">Distribuição de gramas por dias desde o primeiro uso</string>
</resources>
1 change: 1 addition & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@
<string name="no_break_period">No break period</string>
<string name="hours_12">12 hours</string>
<string name="hours_24">24 hours</string>
<string name="grams_distribution_over_days_since_first_use">Grams distribution over days since first use</string>
<plurals name="last_x_days">
<item quantity="one">Last %s day</item>
<item quantity="other">Last %s days</item>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package br.com.colman.petals.statistics.graph.formatter

import br.com.colman.petals.use.repository.Use
import io.kotest.core.spec.style.FunSpec
import io.kotest.data.Row2
import io.kotest.data.forAll
import io.kotest.matchers.shouldBe
import java.time.LocalDateTime
import java.time.Month

class DaysSinceFirstUseFormatterTest : FunSpec({

val uses = listOf(
Use(LocalDateTime.of(2021, Month.FEBRUARY, 7, 15, 30), 0.6.toBigDecimal(), 18.toBigDecimal()),
Use(LocalDateTime.of(2021, Month.FEBRUARY, 8, 21, 57), 1.2.toBigDecimal(), 18.toBigDecimal()),
Use(LocalDateTime.of(2021, Month.FEBRUARY, 9, 9, 30), 0.6.toBigDecimal(), 18.toBigDecimal())
)

test("getFormattedValue returns correctly formatted days since first use (yyyy-MM-dd)") {
forAll(
Row2(0f, "2021-02-06"),
Row2(1f, "2021-02-07"),
Row2(2f, "2021-02-08"),
Row2(3f, "2021-02-09")
) { value, expectedDay ->
val actual = DaysSinceFirstUseFormatter(uses, "yyyy-MM-dd").formatDate.getFormattedValue(value, null)
actual shouldBe expectedDay
}
}
})
Loading