From cf6eebad9a64e29441c7f32bfbe73825856e7305 Mon Sep 17 00:00:00 2001 From: Nishant Mishra Date: Sat, 13 Dec 2025 20:05:56 +0530 Subject: [PATCH] feat(stats): implement a basic last month screen --- .../main/java/org/nsh07/pomodoro/ui/Screen.kt | 3 + .../pomodoro/ui/statsScreen/StatsScreen.kt | 27 +- .../ui/statsScreen/screens/LastMonthScreen.kt | 311 ++++++++++++++++++ .../ui/statsScreen/screens/LastWeekScreen.kt | 32 +- .../ui/statsScreen/screens/StatsMainScreen.kt | 45 ++- .../statsScreen/viewModel/StatsViewModel.kt | 17 +- 6 files changed, 399 insertions(+), 36 deletions(-) create mode 100644 app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/screens/LastMonthScreen.kt diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/Screen.kt b/app/src/main/java/org/nsh07/pomodoro/ui/Screen.kt index 81ab022..0d7936c 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/Screen.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/Screen.kt @@ -54,6 +54,9 @@ sealed class Screen : NavKey { @Serializable object LastWeek : Stats() + + @Serializable + object LastMonth : Stats() } } diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/StatsScreen.kt b/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/StatsScreen.kt index 7b7319f..4a93b3f 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/StatsScreen.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/StatsScreen.kt @@ -45,6 +45,7 @@ import com.patrykandpatrick.vico.core.common.data.ExtraStore import org.nsh07.pomodoro.R import org.nsh07.pomodoro.data.Stat import org.nsh07.pomodoro.ui.Screen +import org.nsh07.pomodoro.ui.statsScreen.screens.LastMonthScreen import org.nsh07.pomodoro.ui.statsScreen.screens.LastWeekScreen import org.nsh07.pomodoro.ui.statsScreen.screens.StatsMainScreen import org.nsh07.pomodoro.ui.statsScreen.viewModel.StatsViewModel @@ -80,7 +81,7 @@ fun StatsScreenRoot( lastYearSummaryChartData = lastYearSummaryChartData, todayStat = todayStat, lastWeekAnalysisValues = lastWeekAnalysisValues, - lastMonthAverageFocusTimes = lastMonthAnalysisValues, + lastMonthAnalysisValues = lastMonthAnalysisValues, lastYearAverageFocusTimes = lastYearAnalysisValues, generateSampleData = viewModel::generateSampleData, modifier = modifier @@ -101,7 +102,7 @@ fun StatsScreen( lastYearSummaryChartData: Pair>>, todayStat: Stat?, lastWeekAnalysisValues: Pair, Long>, - lastMonthAverageFocusTimes: List, + lastMonthAnalysisValues: Pair, Long>, lastYearAverageFocusTimes: List, generateSampleData: () -> Unit, modifier: Modifier = Modifier @@ -138,7 +139,7 @@ fun StatsScreen( lastYearSummaryChartData = lastYearSummaryChartData, todayStat = todayStat, lastWeekAverageFocusTimes = lastWeekAnalysisValues.first, - lastMonthAverageFocusTimes = lastMonthAverageFocusTimes, + lastMonthAverageFocusTimes = lastMonthAnalysisValues.first, lastYearAverageFocusTimes = lastYearAverageFocusTimes, generateSampleData = generateSampleData, hoursFormat = hoursFormat, @@ -157,9 +158,23 @@ fun StatsScreen( entry { LastWeekScreen( contentPadding = contentPadding, - lastWeekAnalysisValues = lastWeekAnalysisValues, - lastWeekSummaryValues = lastWeekSummaryValues, - lastWeekSummaryChartData = lastWeekSummaryChartData, + focusBreakdownValues = lastWeekAnalysisValues, + focusHistoryValues = lastWeekSummaryValues, + mainChartData = lastWeekSummaryChartData, + onBack = backStack::removeLastOrNull, + hoursMinutesFormat = hoursMinutesFormat, + hoursFormat = hoursFormat, + minutesFormat = minutesFormat, + axisTypeface = axisTypeface, + markerTypeface = markerTypeface + ) + } + + entry { + LastMonthScreen( + contentPadding = contentPadding, + lastMonthAnalysisValues = lastMonthAnalysisValues, + lastMonthSummaryChartData = lastMonthSummaryChartData, onBack = backStack::removeLastOrNull, hoursMinutesFormat = hoursMinutesFormat, hoursFormat = hoursFormat, diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/screens/LastMonthScreen.kt b/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/screens/LastMonthScreen.kt new file mode 100644 index 0000000..3154968 --- /dev/null +++ b/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/screens/LastMonthScreen.kt @@ -0,0 +1,311 @@ +/* + * Copyright (c) 2025 Nishant Mishra + * + * This file is part of Tomato - a minimalist pomodoro timer for Android. + * + * Tomato is free software: you can redistribute it and/or modify it under the terms of the GNU + * General Public License as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * Tomato is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Tomato. + * If not, see . + */ + +package org.nsh07.pomodoro.ui.statsScreen.screens + +import android.graphics.Typeface +import androidx.compose.animation.SharedTransitionScope +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.material3.FilledTonalIconButton +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButtonDefaults +import androidx.compose.material3.MaterialTheme.colorScheme +import androidx.compose.material3.MaterialTheme.typography +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TonalToggleButton +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.rotate +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.util.fastForEach +import androidx.navigation3.ui.LocalNavAnimatedContentScope +import com.patrykandpatrick.vico.core.cartesian.data.CartesianChartModelProducer +import com.patrykandpatrick.vico.core.cartesian.data.CartesianValueFormatter +import com.patrykandpatrick.vico.core.cartesian.data.columnSeries +import com.patrykandpatrick.vico.core.common.data.ExtraStore +import org.nsh07.pomodoro.R +import org.nsh07.pomodoro.ui.mergePaddingValues +import org.nsh07.pomodoro.ui.statsScreen.components.FocusBreakRatioVisualization +import org.nsh07.pomodoro.ui.statsScreen.components.FocusBreakdownChart +import org.nsh07.pomodoro.ui.statsScreen.components.HorizontalStackedBar +import org.nsh07.pomodoro.ui.statsScreen.components.TimeColumnChart +import org.nsh07.pomodoro.ui.statsScreen.components.sharedBoundsReveal +import org.nsh07.pomodoro.ui.theme.AppFonts.robotoFlexTopBar +import org.nsh07.pomodoro.ui.theme.TomatoShapeDefaults.middleListItemShape +import org.nsh07.pomodoro.utils.millisecondsToHoursMinutes +import org.nsh07.pomodoro.utils.millisecondsToMinutes + +@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) +@Composable +fun SharedTransitionScope.LastMonthScreen( + contentPadding: PaddingValues, + lastMonthAnalysisValues: Pair, Long>, + lastMonthSummaryChartData: Pair>>, + onBack: () -> Unit, + modifier: Modifier = Modifier, + hoursMinutesFormat: String, + hoursFormat: String, + minutesFormat: String, + axisTypeface: Typeface, + markerTypeface: Typeface +) { + val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior() + + val lastMonthSummaryAnalysisModelProducer = remember { CartesianChartModelProducer() } + var breakdownChartExpanded by remember { mutableStateOf(false) } + + LaunchedEffect(lastMonthAnalysisValues.first) { + lastMonthSummaryAnalysisModelProducer.runTransaction { + columnSeries { + series(lastMonthAnalysisValues.first) + } + } + } + + val rankList = remember(lastMonthAnalysisValues) { + val sortedIndices = + lastMonthAnalysisValues.first.indices.sortedByDescending { lastMonthAnalysisValues.first[it] } + val ranks = MutableList(lastMonthAnalysisValues.first.size) { 0 } + + sortedIndices.forEachIndexed { rank, originalIndex -> + ranks[originalIndex] = rank + } + + ranks + } + + val focusDuration = remember(lastMonthAnalysisValues) { + lastMonthAnalysisValues.first.sum() + } + + Scaffold( + topBar = { + TopAppBar( + title = { + Text( + text = stringResource(R.string.last_month), + fontFamily = robotoFlexTopBar, + modifier = Modifier.sharedBounds( + sharedContentState = this@LastMonthScreen + .rememberSharedContentState("last month heading"), + animatedVisibilityScope = LocalNavAnimatedContentScope.current + ) + ) + }, + subtitle = { + Text(stringResource(R.string.stats)) + }, + navigationIcon = { + FilledTonalIconButton( + onClick = onBack, + shapes = IconButtonDefaults.shapes() + ) { + Icon( + painterResource(R.drawable.arrow_back), + null + ) + } + }, + scrollBehavior = scrollBehavior + ) + }, + modifier = modifier + .nestedScroll(scrollBehavior.nestedScrollConnection) + .sharedBoundsReveal( + sharedTransitionScope = this@LastMonthScreen, + sharedContentState = this@LastMonthScreen.rememberSharedContentState( + "last month card" + ), + animatedVisibilityScope = LocalNavAnimatedContentScope.current, + clipShape = middleListItemShape + ) + ) { innerPadding -> + val insets = mergePaddingValues(innerPadding, contentPadding) + LazyColumn( + verticalArrangement = Arrangement.spacedBy(16.dp), + contentPadding = insets, + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 16.dp) + ) { + item { + Spacer(Modifier.height(16.dp)) + Row( + verticalAlignment = Alignment.Bottom, + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + Text( + millisecondsToHoursMinutes( + focusDuration, + hoursMinutesFormat + ), + style = typography.displaySmall, + modifier = Modifier + .sharedElement( + sharedContentState = this@LastMonthScreen + .rememberSharedContentState("last month average focus timer"), + animatedVisibilityScope = LocalNavAnimatedContentScope.current + ) + ) + Text( + stringResource(R.string.focus_per_day_avg), + style = typography.titleSmall, + modifier = Modifier + .padding(bottom = 5.2.dp) + .sharedElement( + sharedContentState = this@LastMonthScreen + .rememberSharedContentState("focus per day average (month)"), + animatedVisibilityScope = LocalNavAnimatedContentScope.current + ) + ) + } + } + item { + TimeColumnChart( + modelProducer = lastMonthSummaryChartData.first, + hoursFormat = hoursFormat, + hoursMinutesFormat = hoursMinutesFormat, + minutesFormat = minutesFormat, + axisTypeface = axisTypeface, + markerTypeface = markerTypeface, + thickness = 8.dp, + xValueFormatter = CartesianValueFormatter { context, x, _ -> + context.model.extraStore[lastMonthSummaryChartData.second][x.toInt()] + }, + modifier = Modifier + .sharedElement( + sharedContentState = this@LastMonthScreen + .rememberSharedContentState("last month chart"), + animatedVisibilityScope = LocalNavAnimatedContentScope.current + ) + ) + } + + item { Spacer(Modifier.height(8.dp)) } + + item { + Text( + stringResource(R.string.focus_breakdown), + style = typography.headlineSmall + ) + Text( + stringResource(R.string.focus_breakdown_desc), + style = typography.bodySmall, + color = colorScheme.onSurfaceVariant + ) + } + + item { HorizontalStackedBar(lastMonthAnalysisValues.first, rankList = rankList) } + item { + Row { + lastMonthAnalysisValues.first.fastForEach { + Text( + if (it <= 60 * 60 * 1000) + millisecondsToMinutes(it, minutesFormat) + else millisecondsToHoursMinutes(it, hoursMinutesFormat), + style = typography.bodyLarge, + textAlign = TextAlign.Center, + color = colorScheme.onSurfaceVariant, + modifier = Modifier.weight(1f) + ) + } + } + } + + item { + val iconRotation by animateFloatAsState( + if (breakdownChartExpanded) 180f else 0f + ) + Column(modifier = Modifier.fillMaxWidth()) { + TonalToggleButton( + checked = breakdownChartExpanded, + onCheckedChange = { breakdownChartExpanded = it }, + modifier = Modifier.align(Alignment.End) + ) { + Icon( + painterResource(R.drawable.arrow_down), + stringResource(R.string.more_info), + modifier = Modifier.rotate(iconRotation) + ) + Spacer(Modifier.width(ButtonDefaults.IconSpacing)) + Text("Show chart") + } + + FocusBreakdownChart( + expanded = breakdownChartExpanded, + modelProducer = lastMonthSummaryAnalysisModelProducer, + modifier = Modifier.padding(top = 16.dp, bottom = 24.dp) + ) + } + } + + item { + Text( + stringResource(R.string.focus_break_ratio), + style = typography.headlineSmall + ) + } + item { + FocusBreakRatioVisualization( + focusDuration = focusDuration, + breakDuration = lastMonthAnalysisValues.second + ) + } + + item { Spacer(Modifier.height(8.dp)) } + + item { + Text( + "Focus history heatmap", + style = typography.headlineSmall + ) + Text( + "Focus history of the past year. Brighter colors represent a longer focus duration.", + style = typography.bodySmall, + color = colorScheme.onSurfaceVariant + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/screens/LastWeekScreen.kt b/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/screens/LastWeekScreen.kt index 890063b..264dd5b 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/screens/LastWeekScreen.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/screens/LastWeekScreen.kt @@ -81,9 +81,9 @@ import org.nsh07.pomodoro.utils.millisecondsToMinutes @Composable fun SharedTransitionScope.LastWeekScreen( contentPadding: PaddingValues, - lastWeekAnalysisValues: Pair, Long>, - lastWeekSummaryValues: List>>, - lastWeekSummaryChartData: Pair>>, + focusBreakdownValues: Pair, Long>, + focusHistoryValues: List>>, + mainChartData: Pair>>, onBack: () -> Unit, modifier: Modifier = Modifier, hoursMinutesFormat: String, @@ -97,18 +97,18 @@ fun SharedTransitionScope.LastWeekScreen( val lastWeekSummaryAnalysisModelProducer = remember { CartesianChartModelProducer() } var breakdownChartExpanded by remember { mutableStateOf(false) } - LaunchedEffect(lastWeekAnalysisValues.first) { + LaunchedEffect(focusBreakdownValues.first) { lastWeekSummaryAnalysisModelProducer.runTransaction { columnSeries { - series(lastWeekAnalysisValues.first) + series(focusBreakdownValues.first) } } } - val rankList = remember(lastWeekAnalysisValues) { + val rankList = remember(focusBreakdownValues) { val sortedIndices = - lastWeekAnalysisValues.first.indices.sortedByDescending { lastWeekAnalysisValues.first[it] } - val ranks = MutableList(lastWeekAnalysisValues.first.size) { 0 } + focusBreakdownValues.first.indices.sortedByDescending { focusBreakdownValues.first[it] } + val ranks = MutableList(focusBreakdownValues.first.size) { 0 } sortedIndices.forEachIndexed { rank, originalIndex -> ranks[originalIndex] = rank @@ -117,8 +117,8 @@ fun SharedTransitionScope.LastWeekScreen( ranks } - val focusDuration = remember(lastWeekAnalysisValues) { - lastWeekAnalysisValues.first.sum() + val focusDuration = remember(focusBreakdownValues) { + focusBreakdownValues.first.sum() } Scaffold( @@ -205,14 +205,14 @@ fun SharedTransitionScope.LastWeekScreen( } item { TimeColumnChart( - modelProducer = lastWeekSummaryChartData.first, + modelProducer = mainChartData.first, hoursFormat = hoursFormat, hoursMinutesFormat = hoursMinutesFormat, minutesFormat = minutesFormat, axisTypeface = axisTypeface, markerTypeface = markerTypeface, xValueFormatter = CartesianValueFormatter { context, x, _ -> - context.model.extraStore[lastWeekSummaryChartData.second][x.toInt()] + context.model.extraStore[mainChartData.second][x.toInt()] }, modifier = Modifier .sharedElement( @@ -237,10 +237,10 @@ fun SharedTransitionScope.LastWeekScreen( ) } - item { HorizontalStackedBar(lastWeekAnalysisValues.first, rankList = rankList) } + item { HorizontalStackedBar(focusBreakdownValues.first, rankList = rankList) } item { Row { - lastWeekAnalysisValues.first.fastForEach { + focusBreakdownValues.first.fastForEach { Text( if (it <= 60 * 60 * 1000) millisecondsToMinutes(it, minutesFormat) @@ -290,7 +290,7 @@ fun SharedTransitionScope.LastWeekScreen( item { FocusBreakRatioVisualization( focusDuration = focusDuration, - breakDuration = lastWeekAnalysisValues.second + breakDuration = focusBreakdownValues.second ) } @@ -320,7 +320,7 @@ fun SharedTransitionScope.LastWeekScreen( ) } } - lastWeekSummaryValues.fastForEach { + focusHistoryValues.fastForEach { Row(verticalAlignment = Alignment.CenterVertically) { Text( it.first, diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/screens/StatsMainScreen.kt b/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/screens/StatsMainScreen.kt index b51d2d5..5156dac 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/screens/StatsMainScreen.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/screens/StatsMainScreen.kt @@ -83,7 +83,7 @@ fun SharedTransitionScope.StatsMainScreen( lastYearSummaryChartData: Pair>>, todayStat: Stat?, lastWeekAverageFocusTimes: List, - lastMonthAverageFocusTimes: List, + lastMonthAverageFocusTimes: List, lastYearAverageFocusTimes: List, generateSampleData: () -> Unit, hoursMinutesFormat: String, @@ -303,9 +303,17 @@ fun SharedTransitionScope.StatsMainScreen( Column( verticalArrangement = Arrangement.spacedBy(16.dp), modifier = Modifier + .sharedBoundsReveal( + sharedTransitionScope = this@StatsMainScreen, + sharedContentState = this@StatsMainScreen.rememberSharedContentState( + "last month card" + ), + animatedVisibilityScope = LocalNavAnimatedContentScope.current, + clipShape = middleListItemShape + ) .clip(middleListItemShape) .background(listItemColors.containerColor) - .clickable {} + .clickable { onNavigate(Screen.Stats.LastMonth) } .padding( start = 20.dp, top = 20.dp, @@ -314,7 +322,12 @@ fun SharedTransitionScope.StatsMainScreen( ) { Text( stringResource(R.string.last_month), - style = typography.headlineSmall + style = typography.headlineSmall, + modifier = Modifier.sharedBounds( + sharedContentState = this@StatsMainScreen + .rememberSharedContentState("last month heading"), + animatedVisibilityScope = LocalNavAnimatedContentScope.current + ) ) Row( @@ -324,16 +337,28 @@ fun SharedTransitionScope.StatsMainScreen( Text( millisecondsToHoursMinutes( remember(lastMonthAverageFocusTimes) { - lastMonthAverageFocusTimes.sum().toLong() + lastMonthAverageFocusTimes.sum() }, hoursMinutesFormat ), - style = typography.displaySmall + style = typography.displaySmall, + modifier = Modifier + .sharedElement( + sharedContentState = this@StatsMainScreen + .rememberSharedContentState("last month average focus timer"), + animatedVisibilityScope = LocalNavAnimatedContentScope.current + ) ) Text( text = stringResource(R.string.focus_per_day_avg), style = typography.titleSmall, - modifier = Modifier.padding(bottom = 5.2.dp) + modifier = Modifier + .padding(bottom = 5.2.dp) + .sharedElement( + sharedContentState = this@StatsMainScreen + .rememberSharedContentState("focus per day average (month)"), + animatedVisibilityScope = LocalNavAnimatedContentScope.current + ) ) } @@ -347,7 +372,13 @@ fun SharedTransitionScope.StatsMainScreen( thickness = 8.dp, xValueFormatter = CartesianValueFormatter { context, x, _ -> context.model.extraStore[lastMonthSummaryChartData.second][x.toInt()] - } + }, + modifier = Modifier + .sharedElement( + sharedContentState = this@StatsMainScreen + .rememberSharedContentState("last month chart"), + animatedVisibilityScope = LocalNavAnimatedContentScope.current + ) ) } } diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/viewModel/StatsViewModel.kt b/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/viewModel/StatsViewModel.kt index 13ed8f1..9d34a7a 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/viewModel/StatsViewModel.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/viewModel/StatsViewModel.kt @@ -158,21 +158,24 @@ class StatsViewModel( initialValue = lastMonthSummary ) - val lastMonthAverageFocusTimes: StateFlow> = + val lastMonthAverageFocusTimes: StateFlow, Long>> = statRepository.getLastNDaysAverageFocusTimes(30) .map { - listOf( - it?.focusTimeQ1?.toInt() ?: 0, - it?.focusTimeQ2?.toInt() ?: 0, - it?.focusTimeQ3?.toInt() ?: 0, - it?.focusTimeQ4?.toInt() ?: 0 + Pair( + listOf( + it?.focusTimeQ1 ?: 0L, + it?.focusTimeQ2 ?: 0L, + it?.focusTimeQ3 ?: 0L, + it?.focusTimeQ4 ?: 0L + ), + it?.breakTime ?: 0L ) } .flowOn(Dispatchers.IO) .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5000), - initialValue = listOf(0, 0, 0, 0) + initialValue = Pair(listOf(0L, 0L, 0L, 0L), 0L) ) val lastYearSummaryChartData: StateFlow>>> =