feat(stats): implement showing last year stats in a line chart

This commit is contained in:
Nishant Mishra
2025-10-16 00:04:33 +05:30
parent a26b0eac74
commit 09c7ca4559
4 changed files with 113 additions and 27 deletions

View File

@@ -78,34 +78,18 @@ fun StatsScreenRoot(
val lastMonthSummaryChartData by viewModel.lastMonthSummaryChartData.collectAsStateWithLifecycle()
val lastMonthAnalysisValues by viewModel.lastMonthAverageFocusTimes.collectAsStateWithLifecycle()
val lastWeekSummaryAnalysisModelProducer = remember { CartesianChartModelProducer() }
val lastMonthSummaryAnalysisModelProducer = remember { CartesianChartModelProducer() }
LaunchedEffect(lastWeekAnalysisValues) {
lastWeekSummaryAnalysisModelProducer.runTransaction {
columnSeries {
series(lastWeekAnalysisValues)
}
}
}
LaunchedEffect(lastMonthAnalysisValues) {
lastMonthSummaryAnalysisModelProducer.runTransaction {
columnSeries {
series(lastMonthAnalysisValues)
}
}
}
val lastYearSummaryChartData by viewModel.lastYearSummaryChartData.collectAsStateWithLifecycle()
val lastYearAnalysisValues by viewModel.lastYearAverageFocusTimes.collectAsStateWithLifecycle()
StatsScreen(
contentPadding = contentPadding,
lastWeekSummaryChartData = lastWeekSummaryChartData,
lastWeekSummaryAnalysisModelProducer = lastWeekSummaryAnalysisModelProducer,
lastMonthSummaryChartData = lastMonthSummaryChartData,
lastMonthSummaryAnalysisModelProducer = lastMonthSummaryAnalysisModelProducer,
lastYearSummaryChartData = lastYearSummaryChartData,
todayStat = todayStat,
lastWeekAverageFocusTimes = lastWeekAnalysisValues,
lastMonthAverageFocusTimes = lastMonthAnalysisValues,
lastYearAverageFocusTimes = lastYearAnalysisValues,
modifier = modifier
)
}
@@ -115,12 +99,12 @@ fun StatsScreenRoot(
fun StatsScreen(
contentPadding: PaddingValues,
lastWeekSummaryChartData: Pair<CartesianChartModelProducer, ExtraStore.Key<List<String>>>,
lastWeekSummaryAnalysisModelProducer: CartesianChartModelProducer,
lastMonthSummaryChartData: Pair<CartesianChartModelProducer, ExtraStore.Key<List<String>>>,
lastMonthSummaryAnalysisModelProducer: CartesianChartModelProducer,
lastYearSummaryChartData: Pair<CartesianChartModelProducer, ExtraStore.Key<List<String>>>,
todayStat: Stat?,
lastWeekAverageFocusTimes: List<Int>,
lastMonthAverageFocusTimes: List<Int>,
lastYearAverageFocusTimes: List<Int>,
modifier: Modifier = Modifier
) {
val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
@@ -128,6 +112,25 @@ fun StatsScreen(
var lastWeekStatExpanded by rememberSaveable { mutableStateOf(false) }
var lastMonthStatExpanded by rememberSaveable { mutableStateOf(false) }
val lastWeekSummaryAnalysisModelProducer = remember { CartesianChartModelProducer() }
val lastMonthSummaryAnalysisModelProducer = remember { CartesianChartModelProducer() }
LaunchedEffect(lastWeekAverageFocusTimes) {
lastWeekSummaryAnalysisModelProducer.runTransaction {
columnSeries {
series(lastWeekAverageFocusTimes)
}
}
}
LaunchedEffect(lastMonthAverageFocusTimes) {
lastMonthSummaryAnalysisModelProducer.runTransaction {
columnSeries {
series(lastMonthAverageFocusTimes)
}
}
}
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection)
@@ -371,8 +374,51 @@ fun StatsScreen(
modifier = Modifier.padding(horizontal = 32.dp)
)
}
Spacer(Modifier.height(16.dp))
}
item { Spacer(Modifier) }
item {
Text(
stringResource(R.string.last_year),
style = typography.headlineSmall,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
)
}
item {
Row(
verticalAlignment = Alignment.Bottom,
horizontalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
) {
Text(
millisecondsToHoursMinutes(
remember(lastYearAverageFocusTimes) {
lastYearAverageFocusTimes.sum().toLong()
}
),
style = typography.displaySmall,
fontFamily = openRundeClock
)
Text(
text = stringResource(R.string.focus_per_day_avg),
style = typography.titleSmall,
modifier = Modifier.padding(bottom = 6.3.dp)
)
}
}
item {
TimeLineChart(
lastYearSummaryChartData.first,
modifier = Modifier.padding(start = 16.dp),
xValueFormatter = CartesianValueFormatter { context, x, _ ->
context.model.extraStore[lastYearSummaryChartData.second][x.toInt()]
}
)
}
item { Spacer(Modifier.height(16.dp)) }
}
}
}
@@ -398,11 +444,11 @@ fun StatsScreenPreview() {
StatsScreen(
PaddingValues(),
Pair(modelProducer, keys),
modelProducer,
Pair(modelProducer, keys),
modelProducer,
Pair(modelProducer, keys),
null,
listOf(0, 0, 0, 0),
listOf(0, 0, 0, 0),
listOf(0, 0, 0, 0)
)
}

View File

@@ -49,7 +49,7 @@ import org.nsh07.pomodoro.utils.millisecondsToMinutes
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
internal fun TimeLineChart(
fun TimeLineChart(
modelProducer: CartesianChartModelProducer,
modifier: Modifier = Modifier,
thickness: Float = 2f,

View File

@@ -15,6 +15,7 @@ import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import com.patrykandpatrick.vico.core.cartesian.data.CartesianChartModelProducer
import com.patrykandpatrick.vico.core.cartesian.data.columnSeries
import com.patrykandpatrick.vico.core.cartesian.data.lineSeries
import com.patrykandpatrick.vico.core.common.data.ExtraStore
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
@@ -23,6 +24,7 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import org.nsh07.pomodoro.TomatoApplication
import org.nsh07.pomodoro.data.StatRepository
import java.time.format.DateTimeFormatter
import java.time.format.TextStyle
import java.util.Locale
@@ -34,9 +36,12 @@ class StatsViewModel(
private val lastWeekSummary =
Pair(CartesianChartModelProducer(), ExtraStore.Key<List<String>>())
private val lastMonthSummary =
Pair(CartesianChartModelProducer(), ExtraStore.Key<List<String>>())
private val lastYearSummary =
Pair(CartesianChartModelProducer(), ExtraStore.Key<List<String>>())
private val yearDayFormatter = DateTimeFormatter.ofPattern("d MMM")
val lastWeekSummaryChartData: StateFlow<Pair<CartesianChartModelProducer, ExtraStore.Key<List<String>>>> =
statRepository.getLastNDaysStatsSummary(7)
@@ -112,6 +117,40 @@ class StatsViewModel(
initialValue = listOf(0, 0, 0, 0)
)
val lastYearSummaryChartData: StateFlow<Pair<CartesianChartModelProducer, ExtraStore.Key<List<String>>>> =
statRepository.getLastNDaysStatsSummary(365)
.map { list ->
val reversed = list.reversed()
val keys = reversed.map { it.date.format(yearDayFormatter) }
val values = reversed.map { it.focusTime }
lastYearSummary.first.runTransaction {
lineSeries { series(values) }
extras { it[lastYearSummary.second] = keys }
}
lastYearSummary
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = lastYearSummary
)
val lastYearAverageFocusTimes: StateFlow<List<Int>> =
statRepository.getLastNDaysAverageFocusTimes(365)
.map {
listOf(
it?.focusTimeQ1?.toInt() ?: 0,
it?.focusTimeQ2?.toInt() ?: 0,
it?.focusTimeQ3?.toInt() ?: 0,
it?.focusTimeQ4?.toInt() ?: 0
)
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = listOf(0, 0, 0, 0)
)
companion object {
val Factory: ViewModelProvider.Factory = viewModelFactory {
initializer {

View File

@@ -56,4 +56,5 @@
<string name="up_next">Up next</string>
<string name="timer">Timer</string>
<string name="timer_progress">Timer progress</string>
<string name="last_year">Last year</string>
</resources>