From a26b0eac7403d9945e5ca615c4346322a7a17a91 Mon Sep 17 00:00:00 2001 From: Nishant Mishra Date: Wed, 15 Oct 2025 23:25:29 +0530 Subject: [PATCH] refactor(stats): refactor flow collection to improve performance --- .../pomodoro/ui/statsScreen/StatsScreen.kt | 74 ++++++--- .../statsScreen/viewModel/StatsViewModel.kt | 147 +++++++++--------- 2 files changed, 128 insertions(+), 93 deletions(-) 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 e40a0b2..3af64e8 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 @@ -35,6 +35,7 @@ import androidx.compose.material3.Text 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 @@ -56,10 +57,8 @@ 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 kotlinx.coroutines.runBlocking import org.nsh07.pomodoro.R import org.nsh07.pomodoro.data.Stat -import org.nsh07.pomodoro.data.StatFocusTime import org.nsh07.pomodoro.ui.statsScreen.viewModel.StatsViewModel import org.nsh07.pomodoro.ui.theme.AppFonts.openRundeClock import org.nsh07.pomodoro.ui.theme.AppFonts.robotoFlexTopBar @@ -72,20 +71,41 @@ fun StatsScreenRoot( viewModel: StatsViewModel = viewModel(factory = StatsViewModel.Factory) ) { val todayStat by viewModel.todayStat.collectAsStateWithLifecycle(null) - val lastWeekAverageFocusTimes by viewModel - .lastWeekAverageFocusTimes.collectAsStateWithLifecycle(null) - val lastMonthAverageFocusTimes by viewModel - .lastMonthAverageFocusTimes.collectAsStateWithLifecycle(null) + + val lastWeekSummaryChartData by viewModel.lastWeekSummaryChartData.collectAsStateWithLifecycle() + val lastWeekAnalysisValues by viewModel.lastWeekAverageFocusTimes.collectAsStateWithLifecycle() + + 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) + } + } + } StatsScreen( contentPadding = contentPadding, - lastWeekSummaryChartData = remember { viewModel.lastWeekSummaryChartData }, - lastWeekSummaryAnalysisModelProducer = remember { viewModel.lastWeekSummaryAnalysisModelProducer }, - lastMonthSummaryChartData = remember { viewModel.lastMonthSummaryChartData }, - lastMonthSummaryAnalysisModelProducer = remember { viewModel.lastMonthSummaryAnalysisModelProducer }, + lastWeekSummaryChartData = lastWeekSummaryChartData, + lastWeekSummaryAnalysisModelProducer = lastWeekSummaryAnalysisModelProducer, + lastMonthSummaryChartData = lastMonthSummaryChartData, + lastMonthSummaryAnalysisModelProducer = lastMonthSummaryAnalysisModelProducer, todayStat = todayStat, - lastWeekAverageFocusTimes = lastWeekAverageFocusTimes, - lastMonthAverageFocusTimes = lastMonthAverageFocusTimes, + lastWeekAverageFocusTimes = lastWeekAnalysisValues, + lastMonthAverageFocusTimes = lastMonthAnalysisValues, modifier = modifier ) } @@ -99,8 +119,8 @@ fun StatsScreen( lastMonthSummaryChartData: Pair>>, lastMonthSummaryAnalysisModelProducer: CartesianChartModelProducer, todayStat: Stat?, - lastWeekAverageFocusTimes: StatFocusTime?, - lastMonthAverageFocusTimes: StatFocusTime?, + lastWeekAverageFocusTimes: List, + lastMonthAverageFocusTimes: List, modifier: Modifier = Modifier ) { val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() @@ -218,7 +238,11 @@ fun StatsScreen( .padding(horizontal = 16.dp) ) { Text( - millisecondsToHoursMinutes(lastWeekAverageFocusTimes?.total() ?: 0), + millisecondsToHoursMinutes( + remember(lastWeekAverageFocusTimes) { + lastWeekAverageFocusTimes.sum().toLong() + } + ), style = typography.displaySmall, fontFamily = openRundeClock ) @@ -290,7 +314,11 @@ fun StatsScreen( .padding(horizontal = 16.dp) ) { Text( - millisecondsToHoursMinutes(lastMonthAverageFocusTimes?.total() ?: 0), + millisecondsToHoursMinutes( + remember(lastMonthAverageFocusTimes) { + lastMonthAverageFocusTimes.sum().toLong() + } + ), style = typography.displaySmall, fontFamily = openRundeClock ) @@ -356,23 +384,25 @@ fun StatsScreen( @Composable fun StatsScreenPreview() { val modelProducer = remember { CartesianChartModelProducer() } + val keys = remember { ExtraStore.Key>() } - runBlocking { + LaunchedEffect(Unit) { modelProducer.runTransaction { columnSeries { series(5, 6, 5, 2, 11, 8, 5, 2, 15, 11, 8, 13, 12, 10, 2, 7) } + extras { it[keys] = listOf("M", "T", "W", "T", "F", "S", "S") } } } StatsScreen( PaddingValues(), - Pair(modelProducer, ExtraStore.Key()), + Pair(modelProducer, keys), modelProducer, - Pair(modelProducer, ExtraStore.Key()), + Pair(modelProducer, keys), modelProducer, null, - null, - null + listOf(0, 0, 0, 0), + listOf(0, 0, 0, 0) ) -} +} \ No newline at end of file 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 73f4f8e..9f0e553 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 @@ -16,9 +16,11 @@ 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.common.data.ExtraStore -import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.launch +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.TextStyle @@ -29,83 +31,86 @@ class StatsViewModel( ) : ViewModel() { val todayStat = statRepository.getTodayStat().distinctUntilChanged() - private val lastWeekStatsSummary = statRepository.getLastNDaysStatsSummary(7) - val lastWeekAverageFocusTimes = - statRepository.getLastNDaysAverageFocusTimes(7).distinctUntilChanged() - private val lastMonthStatsSummary = statRepository.getLastNDaysStatsSummary(30) - val lastMonthAverageFocusTimes = - statRepository.getLastNDaysAverageFocusTimes(30).distinctUntilChanged() - val lastWeekSummaryChartData = + private val lastWeekSummary = Pair(CartesianChartModelProducer(), ExtraStore.Key>()) - val lastWeekSummaryAnalysisModelProducer = CartesianChartModelProducer() - val lastMonthSummaryChartData = + private val lastMonthSummary = Pair(CartesianChartModelProducer(), ExtraStore.Key>()) - val lastMonthSummaryAnalysisModelProducer = CartesianChartModelProducer() - init { - viewModelScope.launch(Dispatchers.IO) { - lastWeekStatsSummary - .collect { list -> - // reversing is required because we need ascending order while the DB returns descending order - val reversed = list.reversed() - val keys = reversed.map { - it.date.dayOfWeek.getDisplayName( - TextStyle.NARROW, - Locale.getDefault() - ) - } - val values = reversed.map { it.focusTime } - lastWeekSummaryChartData.first.runTransaction { - columnSeries { series(values) } - extras { it[lastWeekSummaryChartData.second] = keys } - } + val lastWeekSummaryChartData: StateFlow>>> = + statRepository.getLastNDaysStatsSummary(7) + .map { list -> + // reversing is required because we need ascending order while the DB returns descending order + val reversed = list.reversed() + val keys = reversed.map { + it.date.dayOfWeek.getDisplayName( + TextStyle.NARROW, + Locale.getDefault() + ) } - } - viewModelScope.launch(Dispatchers.IO) { - lastWeekAverageFocusTimes - .collect { - lastWeekSummaryAnalysisModelProducer.runTransaction { - columnSeries { - series( - it?.focusTimeQ1 ?: 0, - it?.focusTimeQ2 ?: 0, - it?.focusTimeQ3 ?: 0, - it?.focusTimeQ4 ?: 0 - ) - } - } + val values = reversed.map { it.focusTime } + lastWeekSummary.first.runTransaction { + columnSeries { series(values) } + extras { it[lastWeekSummary.second] = keys } } - } - viewModelScope.launch(Dispatchers.IO) { - lastMonthStatsSummary - .collect { list -> - val reversed = list.reversed() - val keys = reversed.map { it.date.dayOfMonth.toString() } - val values = reversed.map { it.focusTime } - lastMonthSummaryChartData.first.runTransaction { - columnSeries { series(values) } - extras { it[lastMonthSummaryChartData.second] = keys } - } + lastWeekSummary + } + .stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5000), + initialValue = lastWeekSummary + ) + + val lastWeekAverageFocusTimes: StateFlow> = + statRepository.getLastNDaysAverageFocusTimes(7) + .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) + ) + + val lastMonthSummaryChartData: StateFlow>>> = + statRepository.getLastNDaysStatsSummary(30) + .map { list -> + val reversed = list.reversed() + val keys = reversed.map { it.date.dayOfMonth.toString() } + val values = reversed.map { it.focusTime } + lastMonthSummary.first.runTransaction { + columnSeries { series(values) } + extras { it[lastMonthSummary.second] = keys } } - } - viewModelScope.launch(Dispatchers.IO) { - lastMonthAverageFocusTimes - .collect { - lastMonthSummaryAnalysisModelProducer.runTransaction { - columnSeries { - series( - it?.focusTimeQ1 ?: 0, - it?.focusTimeQ2 ?: 0, - it?.focusTimeQ3 ?: 0, - it?.focusTimeQ4 ?: 0 - ) - } - } - } - } - } + lastMonthSummary + } + .stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5000), + initialValue = lastMonthSummary + ) + + val lastMonthAverageFocusTimes: StateFlow> = + statRepository.getLastNDaysAverageFocusTimes(30) + .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 {