From 6d94b992b05b74589b04c955bac1447cf0ce1433 Mon Sep 17 00:00:00 2001 From: Nishant Mishra Date: Thu, 11 Dec 2025 22:28:45 +0530 Subject: [PATCH] feat(stats): implement a basic last week screen --- .../main/java/org/nsh07/pomodoro/ui/Screen.kt | 3 + .../pomodoro/ui/statsScreen/StatsScreen.kt | 40 +++-- .../ui/statsScreen/screens/LastWeekScreen.kt | 149 ++++++++++++++++++ .../ui/statsScreen/screens/StatsMainScreen.kt | 8 +- 4 files changed, 186 insertions(+), 14 deletions(-) create mode 100644 app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/screens/LastWeekScreen.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 c0a91a3..81ab022 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/Screen.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/Screen.kt @@ -51,6 +51,9 @@ sealed class Screen : NavKey { sealed class Stats : Screen() { @Serializable object Main : Stats() + + @Serializable + object LastWeek : 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 31e6bd5..f41f9bb 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 @@ -18,11 +18,10 @@ package org.nsh07.pomodoro.ui.statsScreen import android.graphics.Typeface -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.animation.slideInHorizontally -import androidx.compose.animation.slideOutHorizontally +import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.togetherWith +import androidx.compose.animation.unveilIn +import androidx.compose.animation.veilOut import androidx.compose.foundation.layout.PaddingValues import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @@ -42,6 +41,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.LastWeekScreen import org.nsh07.pomodoro.ui.statsScreen.screens.StatsMainScreen import org.nsh07.pomodoro.ui.statsScreen.viewModel.StatsViewModel import org.nsh07.pomodoro.ui.theme.AppFonts.googleFlex400 @@ -81,7 +81,10 @@ fun StatsScreenRoot( ) } -@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) +@OptIn( + ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class, + ExperimentalAnimationApi::class +) @Composable fun StatsScreen( contentPadding: PaddingValues, @@ -108,16 +111,13 @@ fun StatsScreen( backStack = backStack, onBack = backStack::removeLastOrNull, transitionSpec = { - (slideInHorizontally(initialOffsetX = { it })) - .togetherWith(slideOutHorizontally(targetOffsetX = { -it / 4 }) + fadeOut()) + unveilIn().togetherWith(veilOut()) }, popTransitionSpec = { - (slideInHorizontally(initialOffsetX = { -it / 4 }) + fadeIn()) - .togetherWith(slideOutHorizontally(targetOffsetX = { it })) + unveilIn().togetherWith(veilOut()) }, predictivePopTransitionSpec = { - (slideInHorizontally(initialOffsetX = { -it / 4 }) + fadeIn()) - .togetherWith(slideOutHorizontally(targetOffsetX = { it })) + unveilIn().togetherWith(veilOut()) }, entryProvider = entryProvider { entry { @@ -136,9 +136,27 @@ fun StatsScreen( minutesFormat = minutesFormat, axisTypeface = axisTypeface, markerTypeface = markerTypeface, + onNavigate = { + if (backStack.size < 2) backStack.add(it) + else backStack[backStack.lastIndex] = it + }, modifier = modifier ) } + + entry { + LastWeekScreen( + contentPadding = contentPadding, + lastWeekAverageFocusTimes = lastWeekAverageFocusTimes, + onBack = backStack::removeLastOrNull, + hoursMinutesFormat = hoursMinutesFormat, + lastWeekSummaryChartData = lastWeekSummaryChartData, + hoursFormat = hoursFormat, + minutesFormat = minutesFormat, + axisTypeface = axisTypeface, + markerTypeface = markerTypeface + ) + } } ) } 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 new file mode 100644 index 0000000..b7ad754 --- /dev/null +++ b/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/screens/LastWeekScreen.kt @@ -0,0 +1,149 @@ +/* + * 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.foundation.layout.Arrangement +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +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.LargeFlexibleTopAppBar +import androidx.compose.material3.MaterialTheme.typography +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +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.unit.dp +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.TimeColumnChart +import org.nsh07.pomodoro.ui.theme.AppFonts.robotoFlexTopBar +import org.nsh07.pomodoro.utils.millisecondsToHoursMinutes + +@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) +@Composable +fun LastWeekScreen( + contentPadding: PaddingValues, + lastWeekAverageFocusTimes: List, + onBack: () -> Unit, + modifier: Modifier = Modifier, + hoursMinutesFormat: String, + lastWeekSummaryChartData: Pair>>, + hoursFormat: String, + minutesFormat: String, + axisTypeface: Typeface, + markerTypeface: Typeface +) { + val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() + + val rankList = remember(lastWeekAverageFocusTimes) { + val sortedIndices = + lastWeekAverageFocusTimes.indices.sortedByDescending { lastWeekAverageFocusTimes[it] } + val ranks = MutableList(lastWeekAverageFocusTimes.size) { 0 } + + sortedIndices.forEachIndexed { rank, originalIndex -> + ranks[originalIndex] = rank + } + + ranks + } + + Scaffold( + topBar = { + LargeFlexibleTopAppBar( + title = { + Text(stringResource(R.string.last_week), fontFamily = robotoFlexTopBar) + }, + 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) + ) { innerPadding -> + val insets = mergePaddingValues(innerPadding, contentPadding) + LazyColumn( + verticalArrangement = Arrangement.spacedBy(8.dp), + contentPadding = insets, + modifier = Modifier.padding(horizontal = 16.dp) + ) { + item { + Row( + verticalAlignment = Alignment.Bottom, + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + Text( + millisecondsToHoursMinutes( + remember(lastWeekAverageFocusTimes) { + lastWeekAverageFocusTimes.sum().toLong() + }, + hoursMinutesFormat + ), + style = typography.displaySmall + ) + Text( + stringResource(R.string.focus_per_day_avg), + style = typography.titleSmall, + modifier = Modifier.padding(bottom = 5.2.dp) + ) + } + Spacer(Modifier.height(16.dp)) + TimeColumnChart( + modelProducer = lastWeekSummaryChartData.first, + hoursFormat = hoursFormat, + hoursMinutesFormat = hoursMinutesFormat, + minutesFormat = minutesFormat, + axisTypeface = axisTypeface, + markerTypeface = markerTypeface, + xValueFormatter = CartesianValueFormatter { context, x, _ -> + context.model.extraStore[lastWeekSummaryChartData.second][x.toInt()] + } + ) + } + } + } +} \ 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 54b2976..ba88b53 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 @@ -58,6 +58,7 @@ import com.patrykandpatrick.vico.core.common.data.ExtraStore import org.nsh07.pomodoro.BuildConfig import org.nsh07.pomodoro.R import org.nsh07.pomodoro.data.Stat +import org.nsh07.pomodoro.ui.Screen import org.nsh07.pomodoro.ui.mergePaddingValues import org.nsh07.pomodoro.ui.statsScreen.components.TimeColumnChart import org.nsh07.pomodoro.ui.statsScreen.components.TimeLineChart @@ -81,12 +82,13 @@ fun StatsMainScreen( lastMonthAverageFocusTimes: List, lastYearAverageFocusTimes: List, generateSampleData: () -> Unit, - modifier: Modifier = Modifier, hoursMinutesFormat: String, hoursFormat: String, minutesFormat: String, axisTypeface: Typeface, - markerTypeface: Typeface + markerTypeface: Typeface, + onNavigate: (Screen.Stats) -> Unit, + modifier: Modifier = Modifier, ) { val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior() @@ -212,7 +214,7 @@ fun StatsMainScreen( modifier = Modifier .clip(topListItemShape) .background(listItemColors.containerColor) - .clickable {} + .clickable { onNavigate(Screen.Stats.LastWeek) } .padding( start = 20.dp, top = 20.dp,