feat(stats): implement a basic last week screen

This commit is contained in:
Nishant Mishra
2025-12-11 22:28:45 +05:30
parent 38014f4d99
commit 6d94b992b0
4 changed files with 186 additions and 14 deletions

View File

@@ -51,6 +51,9 @@ sealed class Screen : NavKey {
sealed class Stats : Screen() {
@Serializable
object Main : Stats()
@Serializable
object LastWeek : Stats()
}
}

View File

@@ -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<Screen.Stats.Main> {
@@ -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<Screen.Stats.LastWeek> {
LastWeekScreen(
contentPadding = contentPadding,
lastWeekAverageFocusTimes = lastWeekAverageFocusTimes,
onBack = backStack::removeLastOrNull,
hoursMinutesFormat = hoursMinutesFormat,
lastWeekSummaryChartData = lastWeekSummaryChartData,
hoursFormat = hoursFormat,
minutesFormat = minutesFormat,
axisTypeface = axisTypeface,
markerTypeface = markerTypeface
)
}
}
)
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<Int>,
onBack: () -> Unit,
modifier: Modifier = Modifier,
hoursMinutesFormat: String,
lastWeekSummaryChartData: Pair<CartesianChartModelProducer, ExtraStore.Key<List<String>>>,
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()]
}
)
}
}
}
}

View File

@@ -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<Int>,
lastYearAverageFocusTimes: List<Int>,
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,