From 4f2812d039eca7fb11e048c5b79c4335ea6ac8ce Mon Sep 17 00:00:00 2001 From: Nishant Mishra Date: Fri, 12 Dec 2025 21:22:38 +0530 Subject: [PATCH] fix(stats): fix crash when opening last week screen with empty stats table --- .../statsScreen/components/visualizations.kt | 108 ++++++++++-------- .../ui/statsScreen/screens/LastWeekScreen.kt | 17 ++- 2 files changed, 77 insertions(+), 48 deletions(-) diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/components/visualizations.kt b/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/components/visualizations.kt index e552996..e0a6fa1 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/components/visualizations.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/components/visualizations.kt @@ -21,6 +21,7 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement 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.material3.MaterialTheme.colorScheme @@ -33,6 +34,7 @@ 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.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp @@ -41,13 +43,19 @@ 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 - * instead of the colors. The colors are varied according to the `maxIndex` value passed but they do - * NOT correspond to the actual values represented by the cells, and exist for aesthetic reasons - * only. + * A "Horizontal stacked bar" component, which can be considered as a horizontal stacked bar chart + * with a single bar. This component can be stacked in a column to create a "100% stacked bar chart" + * where each bar is the same length to easily visualize proportions of each type of value + * represented + * + * @param values Values to be represented by the bar + * @param rankList A list of the rank of each element if the list was sorted in a non-increasing + * order + * @param height Height of the bar + * @param gap Gap between each part of the bar */ @Composable -fun VariableWidth1DHeatmap( +fun HorizontalStackedBar( values: List, modifier: Modifier = Modifier, rankList: List = remember(values) { @@ -112,60 +120,70 @@ fun FocusBreakRatioVisualization( 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 + if (focusDuration + breakDuration > 0) { + val focusPercentage = ((focusDuration / (focusDuration.toFloat() + breakDuration)) * 100) + val breakPercentage = 100 - focusPercentage + 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) + ) + if (focusDuration > 0) Spacer( + Modifier + .weight(focusPercentage) + .height(height) + .background( + colorScheme.primary, + if (breakDuration > 0) shapes.large.copy( + topEnd = shapes.extraSmall.topEnd, + bottomEnd = shapes.extraSmall.bottomEnd + ) else shapes.large ) - ) - ) - Spacer( - Modifier - .weight(breakPercentage) - .height(height) - .background( - colorScheme.tertiary, - shapes.large.copy( - topStart = shapes.extraSmall.topStart, - bottomStart = shapes.extraSmall.bottomStart + ) + if (breakDuration > 0) Spacer( + Modifier + .weight(breakPercentage) + .height(height) + .background( + colorScheme.tertiary, + if (focusDuration > 0) shapes.large.copy( + topStart = shapes.extraSmall.topStart, + bottomStart = shapes.extraSmall.bottomStart + ) else shapes.large ) - ) - ) + ) + Text( + text = breakPercentage.roundToInt().toString() + '%', + style = typography.bodyLarge, + color = colorScheme.tertiary, + modifier = Modifier.padding(start = 6.dp) + ) + } + } else { Text( - text = breakPercentage.roundToInt().toString() + '%', + text = "Not enough data", style = typography.bodyLarge, - color = colorScheme.tertiary, - modifier = Modifier.padding(start = 6.dp) + color = colorScheme.outline, + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxSize() ) } } @Preview @Composable -fun VariableWidth1DHeatmapPreview() { +fun HorizontalStackedBarPreview() { val values = listOf(38L, 190L, 114L, 14L) val rankList = listOf(2, 0, 1, 3) TomatoTheme(dynamicColor = false) { Surface { - VariableWidth1DHeatmap( + HorizontalStackedBar( values = values, rankList = rankList, modifier = Modifier.padding(16.dp), 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 8f09b7f..323ccb2 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 @@ -28,6 +28,7 @@ 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.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @@ -57,8 +58,8 @@ 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.HorizontalStackedBar 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 @@ -213,7 +214,7 @@ fun SharedTransitionScope.LastWeekScreen( color = colorScheme.onSurfaceVariant ) } - item { VariableWidth1DHeatmap(lastWeekAnalysisValues.first, rankList = rankList) } + item { HorizontalStackedBar(lastWeekAnalysisValues.first, rankList = rankList) } item { Row { lastWeekAnalysisValues.first.fastForEach { @@ -260,6 +261,16 @@ fun SharedTransitionScope.LastWeekScreen( } item { Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { + Row { + Spacer(Modifier.width(18.dp)) + (0..8 step 2).forEach { + Text( + (it * 10).toString() + '%', + style = typography.labelSmall, + modifier = Modifier.weight(1f) + ) + } + } lastWeekSummaryValues.fastForEach { Row(verticalAlignment = Alignment.CenterVertically) { Text( @@ -267,7 +278,7 @@ fun SharedTransitionScope.LastWeekScreen( style = typography.labelSmall, modifier = Modifier.size(18.dp) ) - VariableWidth1DHeatmap(it.second, rankList = rankList) + HorizontalStackedBar(it.second, rankList = rankList) } } }