From c39089de21e67e34cebd18f0ccdb5c242eb85b1c Mon Sep 17 00:00:00 2001 From: Nishant Mishra Date: Fri, 12 Dec 2025 12:48:46 +0530 Subject: [PATCH] feat(stats): fully implement last week screen --- .../main/java/org/nsh07/pomodoro/data/Stat.kt | 29 +++-- .../java/org/nsh07/pomodoro/data/StatDao.kt | 25 ++-- .../org/nsh07/pomodoro/data/StatRepository.kt | 26 ++-- .../pomodoro/ui/statsScreen/StatsScreen.kt | 14 ++- .../components/sharedBoundsReveal.kt | 10 +- ...bleWidth1DHeatmap.kt => visualizations.kt} | 58 +++++++++ .../ui/statsScreen/screens/LastWeekScreen.kt | 112 +++++++++++++++--- .../ui/statsScreen/screens/StatsMainScreen.kt | 4 +- .../statsScreen/viewModel/StatsViewModel.kt | 60 +++++++--- app/src/main/res/values/strings.xml | 1 + 10 files changed, 267 insertions(+), 72 deletions(-) rename app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/components/{VariableWidth1DHeatmap.kt => visualizations.kt} (68%) diff --git a/app/src/main/java/org/nsh07/pomodoro/data/Stat.kt b/app/src/main/java/org/nsh07/pomodoro/data/Stat.kt index 2110244..b97a564 100644 --- a/app/src/main/java/org/nsh07/pomodoro/data/Stat.kt +++ b/app/src/main/java/org/nsh07/pomodoro/data/Stat.kt @@ -1,8 +1,18 @@ /* * Copyright (c) 2025 Nishant Mishra * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . + * 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.data @@ -29,17 +39,10 @@ data class Stat( fun totalFocusTime() = focusTimeQ1 + focusTimeQ2 + focusTimeQ3 + focusTimeQ4 } -data class StatSummary( - val date: LocalDate, - val focusTime: Long, - val breakTime: Long -) - -data class StatFocusTime( +data class StatTime( val focusTimeQ1: Long, val focusTimeQ2: Long, val focusTimeQ3: Long, - val focusTimeQ4: Long -) { - fun total() = focusTimeQ1 + focusTimeQ2 + focusTimeQ3 + focusTimeQ4 -} \ No newline at end of file + val focusTimeQ4: Long, + val breakTime: Long, +) \ No newline at end of file diff --git a/app/src/main/java/org/nsh07/pomodoro/data/StatDao.kt b/app/src/main/java/org/nsh07/pomodoro/data/StatDao.kt index a58aff0..87877ea 100644 --- a/app/src/main/java/org/nsh07/pomodoro/data/StatDao.kt +++ b/app/src/main/java/org/nsh07/pomodoro/data/StatDao.kt @@ -1,8 +1,18 @@ /* * Copyright (c) 2025 Nishant Mishra * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . + * 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.data @@ -37,23 +47,24 @@ interface StatDao { @Query("SELECT * FROM stat WHERE date = :date") fun getStat(date: LocalDate): Flow - @Query("SELECT date, focusTimeQ1 + focusTimeQ2 + focusTimeQ3 + focusTimeQ4 as focusTime, breakTime FROM stat ORDER BY date DESC LIMIT :n") - fun getLastNDaysStatsSummary(n: Int): Flow> + @Query("SELECT date, focusTimeQ1, focusTimeQ2, focusTimeQ3, focusTimeQ4, breakTime FROM stat ORDER BY date DESC LIMIT :n") + fun getLastNDaysStats(n: Int): Flow> @Query( "SELECT " + "AVG(focusTimeQ1) AS focusTimeQ1, " + "AVG(focusTimeQ2) AS focusTimeQ2, " + "AVG(focusTimeQ3) AS focusTimeQ3, " + - "AVG(focusTimeQ4) AS focusTimeQ4 " + + "AVG(focusTimeQ4) AS focusTimeQ4, " + + "AVG(breakTime) AS breakTime " + "FROM (" + "SELECT * FROM (" + - "SELECT focusTimeQ1, focusTimeQ2, focusTimeQ3, focusTimeQ4 FROM stat ORDER BY date DESC LIMIT :n" + + "SELECT focusTimeQ1, focusTimeQ2, focusTimeQ3, focusTimeQ4, breakTime FROM stat ORDER BY date DESC LIMIT :n" + ") " + "WHERE focusTimeQ1 != 0 OR focusTimeQ2 != 0 OR focusTimeQ3 != 0 OR focusTimeQ4 != 0 " + ")" ) - fun getLastNDaysAvgFocusTimes(n: Int): Flow + fun getLastNDaysAvgStats(n: Int): Flow @Query("SELECT EXISTS (SELECT * FROM stat WHERE date = :date)") suspend fun statExists(date: LocalDate): Boolean diff --git a/app/src/main/java/org/nsh07/pomodoro/data/StatRepository.kt b/app/src/main/java/org/nsh07/pomodoro/data/StatRepository.kt index 7d2efbd..438fb48 100644 --- a/app/src/main/java/org/nsh07/pomodoro/data/StatRepository.kt +++ b/app/src/main/java/org/nsh07/pomodoro/data/StatRepository.kt @@ -1,8 +1,18 @@ /* * Copyright (c) 2025 Nishant Mishra * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . + * 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.data @@ -28,9 +38,9 @@ interface StatRepository { fun getTodayStat(): Flow - fun getLastNDaysStatsSummary(n: Int): Flow> + fun getLastNDaysStats(n: Int): Flow> - fun getLastNDaysAverageFocusTimes(n: Int): Flow + fun getLastNDaysAverageFocusTimes(n: Int): Flow suspend fun getLastDate(): LocalDate? } @@ -101,11 +111,11 @@ class AppStatRepository( return statDao.getStat(currentDate) } - override fun getLastNDaysStatsSummary(n: Int): Flow> = - statDao.getLastNDaysStatsSummary(n) + override fun getLastNDaysStats(n: Int): Flow> = + statDao.getLastNDaysStats(n) - override fun getLastNDaysAverageFocusTimes(n: Int): Flow = - statDao.getLastNDaysAvgFocusTimes(n) + override fun getLastNDaysAverageFocusTimes(n: Int): Flow = + statDao.getLastNDaysAvgStats(n) override suspend fun getLastDate(): LocalDate? = statDao.getLastDate() } \ No newline at end of file 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 ea3f277..7b7319f 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 @@ -62,6 +62,7 @@ fun StatsScreenRoot( val todayStat by viewModel.todayStat.collectAsStateWithLifecycle(null) val lastWeekSummaryChartData by viewModel.lastWeekSummaryChartData.collectAsStateWithLifecycle() + val lastWeekSummaryValues by viewModel.lastWeekStats.collectAsStateWithLifecycle() val lastWeekAnalysisValues by viewModel.lastWeekAverageFocusTimes.collectAsStateWithLifecycle() val lastMonthSummaryChartData by viewModel.lastMonthSummaryChartData.collectAsStateWithLifecycle() @@ -74,10 +75,11 @@ fun StatsScreenRoot( contentPadding = contentPadding, backStack = backStack, lastWeekSummaryChartData = lastWeekSummaryChartData, + lastWeekSummaryValues = lastWeekSummaryValues, lastMonthSummaryChartData = lastMonthSummaryChartData, lastYearSummaryChartData = lastYearSummaryChartData, todayStat = todayStat, - lastWeekAverageFocusTimes = lastWeekAnalysisValues, + lastWeekAnalysisValues = lastWeekAnalysisValues, lastMonthAverageFocusTimes = lastMonthAnalysisValues, lastYearAverageFocusTimes = lastYearAnalysisValues, generateSampleData = viewModel::generateSampleData, @@ -94,10 +96,11 @@ fun StatsScreen( contentPadding: PaddingValues, backStack: SnapshotStateList, lastWeekSummaryChartData: Pair>>, + lastWeekSummaryValues: List>>, lastMonthSummaryChartData: Pair>>, lastYearSummaryChartData: Pair>>, todayStat: Stat?, - lastWeekAverageFocusTimes: List, + lastWeekAnalysisValues: Pair, Long>, lastMonthAverageFocusTimes: List, lastYearAverageFocusTimes: List, generateSampleData: () -> Unit, @@ -134,7 +137,7 @@ fun StatsScreen( lastMonthSummaryChartData = lastMonthSummaryChartData, lastYearSummaryChartData = lastYearSummaryChartData, todayStat = todayStat, - lastWeekAverageFocusTimes = lastWeekAverageFocusTimes, + lastWeekAverageFocusTimes = lastWeekAnalysisValues.first, lastMonthAverageFocusTimes = lastMonthAverageFocusTimes, lastYearAverageFocusTimes = lastYearAverageFocusTimes, generateSampleData = generateSampleData, @@ -154,10 +157,11 @@ fun StatsScreen( entry { LastWeekScreen( contentPadding = contentPadding, - lastWeekAverageFocusTimes = lastWeekAverageFocusTimes, + lastWeekAnalysisValues = lastWeekAnalysisValues, + lastWeekSummaryValues = lastWeekSummaryValues, + lastWeekSummaryChartData = lastWeekSummaryChartData, onBack = backStack::removeLastOrNull, hoursMinutesFormat = hoursMinutesFormat, - lastWeekSummaryChartData = lastWeekSummaryChartData, hoursFormat = hoursFormat, minutesFormat = minutesFormat, axisTypeface = axisTypeface, diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/components/sharedBoundsReveal.kt b/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/components/sharedBoundsReveal.kt index d89847f..30ed8c1 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/components/sharedBoundsReveal.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/components/sharedBoundsReveal.kt @@ -23,6 +23,8 @@ import androidx.compose.animation.EnterTransition import androidx.compose.animation.ExitTransition import androidx.compose.animation.SharedTransitionDefaults import androidx.compose.animation.SharedTransitionScope +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable @@ -37,8 +39,8 @@ fun Modifier.sharedBoundsReveal( sharedTransitionScope: SharedTransitionScope, animatedVisibilityScope: AnimatedVisibilityScope = LocalNavAnimatedContentScope.current, boundsTransform: BoundsTransform = SharedTransitionDefaults.BoundsTransform, - enter: EnterTransition = EnterTransition.None, - exit: ExitTransition = ExitTransition.None, + enter: EnterTransition = fadeIn(), + exit: ExitTransition = fadeOut(), resizeMode: SharedTransitionScope.ResizeMode = SharedTransitionScope.ResizeMode.RemeasureToBounds, clipShape: Shape = MaterialTheme.shapes.largeIncreased, renderInOverlayDuringTransition: Boolean = true, @@ -55,7 +57,7 @@ fun Modifier.sharedBoundsReveal( clipInOverlayDuringTransition = OverlayClip(clipShape), renderInOverlayDuringTransition = renderInOverlayDuringTransition, ) - .skipToLookaheadSize() - .skipToLookaheadPosition() +// .skipToLookaheadSize() +// .skipToLookaheadPosition() } } diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/components/VariableWidth1DHeatmap.kt b/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/components/visualizations.kt similarity index 68% rename from app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/components/VariableWidth1DHeatmap.kt rename to app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/components/visualizations.kt index 573b22e..a27ded3 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/components/VariableWidth1DHeatmap.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/components/visualizations.kt @@ -25,9 +25,12 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme.colorScheme import androidx.compose.material3.MaterialTheme.shapes +import androidx.compose.material3.MaterialTheme.typography import androidx.compose.material3.Surface +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.tooling.preview.Preview @@ -35,6 +38,7 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.util.fastForEachIndexed import org.nsh07.pomodoro.ui.theme.TomatoTheme +import kotlin.math.roundToInt /** * A custom implementation of the 1-Dimensional heatmap plot that varies the width of the cells @@ -98,6 +102,60 @@ fun VariableWidth1DHeatmap( } } +@Composable +fun FocusBreakRatioVisualization( + focusDuration: Long, + breakDuration: Long, + modifier: Modifier = Modifier, + height: Dp = 40.dp, + gap: Dp = 2.dp +) { + val focusPercentage = ((focusDuration / (focusDuration.toFloat() + breakDuration)) * 100) + val breakPercentage = ((breakDuration / (focusDuration.toFloat() + breakDuration)) * 100) + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(gap), + modifier = modifier + ) { + Text( + text = focusPercentage.roundToInt().toString() + '%', + style = typography.bodyLarge, + color = colorScheme.primary, + modifier = Modifier.padding(end = 6.dp) + ) + Spacer( + Modifier + .weight(focusPercentage) + .height(height) + .background( + colorScheme.primary, + shapes.large.copy( + topEnd = shapes.extraSmall.topEnd, + bottomEnd = shapes.extraSmall.bottomEnd + ) + ) + ) + Spacer( + Modifier + .weight(breakPercentage) + .height(height) + .background( + colorScheme.tertiary, + shapes.large.copy( + topStart = shapes.extraSmall.topStart, + bottomStart = shapes.extraSmall.bottomStart + ) + ) + ) + Text( + text = breakPercentage.roundToInt().toString() + '%', + style = typography.bodyLarge, + color = colorScheme.tertiary, + modifier = Modifier.padding(start = 6.dp) + ) + } +} + @Preview @Composable fun VariableWidth1DHeatmapPreview() { 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 8921cfe..adb64a6 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 @@ -19,14 +19,15 @@ package org.nsh07.pomodoro.ui.statsScreen.screens import android.graphics.Typeface import androidx.compose.animation.SharedTransitionScope -import androidx.compose.foundation.background 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.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @@ -46,28 +47,34 @@ import androidx.compose.ui.Modifier 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.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.TimeColumnChart +import org.nsh07.pomodoro.ui.statsScreen.components.VariableWidth1DHeatmap import org.nsh07.pomodoro.ui.statsScreen.components.sharedBoundsReveal import org.nsh07.pomodoro.ui.theme.AppFonts.robotoFlexTopBar import org.nsh07.pomodoro.ui.theme.TomatoShapeDefaults.topListItemShape import org.nsh07.pomodoro.utils.millisecondsToHoursMinutes +import org.nsh07.pomodoro.utils.millisecondsToMinutes @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) @Composable fun SharedTransitionScope.LastWeekScreen( contentPadding: PaddingValues, - lastWeekAverageFocusTimes: List, + lastWeekAnalysisValues: Pair, Long>, + lastWeekSummaryValues: List>>, + lastWeekSummaryChartData: Pair>>, onBack: () -> Unit, modifier: Modifier = Modifier, hoursMinutesFormat: String, - lastWeekSummaryChartData: Pair>>, hoursFormat: String, minutesFormat: String, axisTypeface: Typeface, @@ -75,10 +82,10 @@ fun SharedTransitionScope.LastWeekScreen( ) { val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior() - val rankList = remember(lastWeekAverageFocusTimes) { + val rankList = remember(lastWeekAnalysisValues) { val sortedIndices = - lastWeekAverageFocusTimes.indices.sortedByDescending { lastWeekAverageFocusTimes[it] } - val ranks = MutableList(lastWeekAverageFocusTimes.size) { 0 } + lastWeekAnalysisValues.first.indices.sortedByDescending { lastWeekAnalysisValues.first[it] } + val ranks = MutableList(lastWeekAnalysisValues.first.size) { 0 } sortedIndices.forEachIndexed { rank, originalIndex -> ranks[originalIndex] = rank @@ -87,6 +94,10 @@ fun SharedTransitionScope.LastWeekScreen( ranks } + val focusDuration = remember(lastWeekAnalysisValues) { + lastWeekAnalysisValues.first.sum() + } + Scaffold( topBar = { TopAppBar( @@ -115,11 +126,7 @@ fun SharedTransitionScope.LastWeekScreen( ) } }, - scrollBehavior = scrollBehavior, - colors = TopAppBarDefaults.topAppBarColors( - containerColor = colorScheme.surfaceBright, - scrolledContainerColor = colorScheme.surfaceBright - ) + scrollBehavior = scrollBehavior ) }, modifier = modifier @@ -135,11 +142,10 @@ fun SharedTransitionScope.LastWeekScreen( ) { innerPadding -> val insets = mergePaddingValues(innerPadding, contentPadding) LazyColumn( - verticalArrangement = Arrangement.spacedBy(8.dp), + verticalArrangement = Arrangement.spacedBy(16.dp), contentPadding = insets, modifier = Modifier .fillMaxSize() - .background(colorScheme.surfaceBright) .padding(horizontal = 16.dp) ) { item { @@ -150,9 +156,7 @@ fun SharedTransitionScope.LastWeekScreen( ) { Text( millisecondsToHoursMinutes( - remember(lastWeekAverageFocusTimes) { - lastWeekAverageFocusTimes.sum().toLong() - }, + focusDuration, hoursMinutesFormat ), style = typography.displaySmall, @@ -175,7 +179,8 @@ fun SharedTransitionScope.LastWeekScreen( ) ) } - Spacer(Modifier.height(16.dp)) + } + item { TimeColumnChart( modelProducer = lastWeekSummaryChartData.first, hoursFormat = hoursFormat, @@ -194,6 +199,79 @@ fun SharedTransitionScope.LastWeekScreen( ) ) } + + item { Spacer(Modifier.height(8.dp)) } + + item { + Text( + "Focus overview", + style = typography.headlineSmall + ) + Text( + "Average focus durations at different times of the day", + style = typography.bodySmall, + color = colorScheme.onSurfaceVariant + ) + } + item { VariableWidth1DHeatmap(lastWeekAnalysisValues.first, rankList = rankList) } + item { + Row { + lastWeekAnalysisValues.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 { Spacer(Modifier.height(8.dp)) } + + item { + Text( + stringResource(R.string.focus_break_ratio), + style = typography.headlineSmall + ) + } + item { + FocusBreakRatioVisualization( + focusDuration = focusDuration, + breakDuration = lastWeekAnalysisValues.second + ) + } + + item { Spacer(Modifier.height(8.dp)) } + + item { + Text( + "Focus insights", + style = typography.headlineSmall + ) + Text( + "Focus overview of each day of the past week", + style = typography.bodySmall, + color = colorScheme.onSurfaceVariant + ) + } + item { + Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { + lastWeekSummaryValues.fastForEach { + Row(verticalAlignment = Alignment.CenterVertically) { + Text( + it.first, + style = typography.labelSmall, + modifier = Modifier.size(18.dp) + ) + VariableWidth1DHeatmap(it.second, rankList = rankList) + } + } + } + } } } } \ No newline at end of file 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 c428a67..b51d2d5 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 @@ -82,7 +82,7 @@ fun SharedTransitionScope.StatsMainScreen( lastMonthSummaryChartData: Pair>>, lastYearSummaryChartData: Pair>>, todayStat: Stat?, - lastWeekAverageFocusTimes: List, + lastWeekAverageFocusTimes: List, lastMonthAverageFocusTimes: List, lastYearAverageFocusTimes: List, generateSampleData: () -> Unit, @@ -254,7 +254,7 @@ fun SharedTransitionScope.StatsMainScreen( Text( millisecondsToHoursMinutes( remember(lastWeekAverageFocusTimes) { - lastWeekAverageFocusTimes.sum().toLong() + lastWeekAverageFocusTimes.sum() }, hoursMinutesFormat ), 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 f2b7362..13ed8f1 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 @@ -67,8 +67,10 @@ class StatsViewModel( private val yearDayFormatter = DateTimeFormatter.ofPattern("d MMM") + private val lastWeekStatsFlow = statRepository.getLastNDaysStats(7) + val lastWeekSummaryChartData: StateFlow>>> = - statRepository.getLastNDaysStatsSummary(7) + lastWeekStatsFlow .map { list -> // reversing is required because we need ascending order while the DB returns descending order val reversed = list.reversed() @@ -78,7 +80,7 @@ class StatsViewModel( Locale.getDefault() ) } - val values = reversed.map { it.focusTime } + val values = reversed.map { it.totalFocusTime() } lastWeekSummary.first.runTransaction { columnSeries { series(values) } extras { it[lastWeekSummary.second] = keys } @@ -92,29 +94,57 @@ class StatsViewModel( initialValue = lastWeekSummary ) - val lastWeekAverageFocusTimes: StateFlow> = + val lastWeekStats: StateFlow>>> = + lastWeekStatsFlow + .map { value -> + value.reversed().map { + Pair( + it.date.dayOfWeek.getDisplayName( + TextStyle.NARROW, + Locale.getDefault() + ), + listOf( + it.focusTimeQ1, + it.focusTimeQ2, + it.focusTimeQ3, + it.focusTimeQ4 + ) + ) + } + } + .flowOn(Dispatchers.IO) + .stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5000), + initialValue = emptyList() + ) + + val lastWeekAverageFocusTimes: StateFlow, Long>> = statRepository.getLastNDaysAverageFocusTimes(7) .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 lastMonthSummaryChartData: StateFlow>>> = - statRepository.getLastNDaysStatsSummary(30) + statRepository.getLastNDaysStats(30) .map { list -> val reversed = list.reversed() val keys = reversed.map { it.date.dayOfMonth.toString() } - val values = reversed.map { it.focusTime } + val values = reversed.map { it.totalFocusTime() } lastMonthSummary.first.runTransaction { columnSeries { series(values) } extras { it[lastMonthSummary.second] = keys } @@ -146,11 +176,11 @@ class StatsViewModel( ) val lastYearSummaryChartData: StateFlow>>> = - statRepository.getLastNDaysStatsSummary(365) + statRepository.getLastNDaysStats(365) .map { list -> val reversed = list.reversed() val keys = reversed.map { it.date.format(yearDayFormatter) } - val values = reversed.map { it.focusTime } + val values = reversed.map { it.totalFocusTime() } lastYearSummary.first.runTransaction { lineSeries { series(values) } extras { it[lastYearSummary.second] = keys } @@ -195,13 +225,11 @@ class StatsViewModel( (1 * 60 * 60 * 1000L..3 * 60 * 60 * 1000L).random(), (0..3 * 60 * 60 * 1000L).random(), (0..1 * 60 * 60 * 1000L).random(), - 0 + (0..100 * 60 * 1000L).random() ) ) it = it.plusDays(1) } - - statRepository.addBreakTime((0..30 * 60 * 1000L).random()) } } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3755e19..24df1ec 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -113,4 +113,5 @@ Automatically lock your device after a timeout, while keeping the AOD visible Timer reset Undo + Focus-break ratio \ No newline at end of file