fix(stats): fix crash when opening last week screen with empty stats table

This commit is contained in:
Nishant Mishra
2025-12-12 21:22:38 +05:30
parent a02eee3795
commit 4f2812d039
2 changed files with 77 additions and 48 deletions

View File

@@ -21,6 +21,7 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme.colorScheme import androidx.compose.material3.MaterialTheme.colorScheme
@@ -33,6 +34,7 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
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 import kotlin.math.roundToInt
/** /**
* A custom implementation of the 1-Dimensional heatmap plot that varies the width of the cells * A "Horizontal stacked bar" component, which can be considered as a horizontal stacked bar chart
* instead of the colors. The colors are varied according to the `maxIndex` value passed but they do * with a single bar. This component can be stacked in a column to create a "100% stacked bar chart"
* NOT correspond to the actual values represented by the cells, and exist for aesthetic reasons * where each bar is the same length to easily visualize proportions of each type of value
* only. * 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 @Composable
fun VariableWidth1DHeatmap( fun HorizontalStackedBar(
values: List<Long>, values: List<Long>,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
rankList: List<Int> = remember(values) { rankList: List<Int> = remember(values) {
@@ -112,8 +120,9 @@ fun FocusBreakRatioVisualization(
height: Dp = 40.dp, height: Dp = 40.dp,
gap: Dp = 2.dp gap: Dp = 2.dp
) { ) {
if (focusDuration + breakDuration > 0) {
val focusPercentage = ((focusDuration / (focusDuration.toFloat() + breakDuration)) * 100) val focusPercentage = ((focusDuration / (focusDuration.toFloat() + breakDuration)) * 100)
val breakPercentage = ((breakDuration / (focusDuration.toFloat() + breakDuration)) * 100) val breakPercentage = 100 - focusPercentage
Row( Row(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(gap), horizontalArrangement = Arrangement.spacedBy(gap),
@@ -125,28 +134,28 @@ fun FocusBreakRatioVisualization(
color = colorScheme.primary, color = colorScheme.primary,
modifier = Modifier.padding(end = 6.dp) modifier = Modifier.padding(end = 6.dp)
) )
Spacer( if (focusDuration > 0) Spacer(
Modifier Modifier
.weight(focusPercentage) .weight(focusPercentage)
.height(height) .height(height)
.background( .background(
colorScheme.primary, colorScheme.primary,
shapes.large.copy( if (breakDuration > 0) shapes.large.copy(
topEnd = shapes.extraSmall.topEnd, topEnd = shapes.extraSmall.topEnd,
bottomEnd = shapes.extraSmall.bottomEnd bottomEnd = shapes.extraSmall.bottomEnd
) else shapes.large
) )
) )
) if (breakDuration > 0) Spacer(
Spacer(
Modifier Modifier
.weight(breakPercentage) .weight(breakPercentage)
.height(height) .height(height)
.background( .background(
colorScheme.tertiary, colorScheme.tertiary,
shapes.large.copy( if (focusDuration > 0) shapes.large.copy(
topStart = shapes.extraSmall.topStart, topStart = shapes.extraSmall.topStart,
bottomStart = shapes.extraSmall.bottomStart bottomStart = shapes.extraSmall.bottomStart
) ) else shapes.large
) )
) )
Text( Text(
@@ -156,16 +165,25 @@ fun FocusBreakRatioVisualization(
modifier = Modifier.padding(start = 6.dp) modifier = Modifier.padding(start = 6.dp)
) )
} }
} else {
Text(
text = "Not enough data",
style = typography.bodyLarge,
color = colorScheme.outline,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxSize()
)
}
} }
@Preview @Preview
@Composable @Composable
fun VariableWidth1DHeatmapPreview() { fun HorizontalStackedBarPreview() {
val values = listOf(38L, 190L, 114L, 14L) val values = listOf(38L, 190L, 114L, 14L)
val rankList = listOf(2, 0, 1, 3) val rankList = listOf(2, 0, 1, 3)
TomatoTheme(dynamicColor = false) { TomatoTheme(dynamicColor = false) {
Surface { Surface {
VariableWidth1DHeatmap( HorizontalStackedBar(
values = values, values = values,
rankList = rankList, rankList = rankList,
modifier = Modifier.padding(16.dp), modifier = Modifier.padding(16.dp),

View File

@@ -28,6 +28,7 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi 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.R
import org.nsh07.pomodoro.ui.mergePaddingValues import org.nsh07.pomodoro.ui.mergePaddingValues
import org.nsh07.pomodoro.ui.statsScreen.components.FocusBreakRatioVisualization 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.TimeColumnChart
import org.nsh07.pomodoro.ui.statsScreen.components.VariableWidth1DHeatmap
import org.nsh07.pomodoro.ui.statsScreen.components.sharedBoundsReveal import org.nsh07.pomodoro.ui.statsScreen.components.sharedBoundsReveal
import org.nsh07.pomodoro.ui.theme.AppFonts.robotoFlexTopBar import org.nsh07.pomodoro.ui.theme.AppFonts.robotoFlexTopBar
import org.nsh07.pomodoro.ui.theme.TomatoShapeDefaults.topListItemShape import org.nsh07.pomodoro.ui.theme.TomatoShapeDefaults.topListItemShape
@@ -213,7 +214,7 @@ fun SharedTransitionScope.LastWeekScreen(
color = colorScheme.onSurfaceVariant color = colorScheme.onSurfaceVariant
) )
} }
item { VariableWidth1DHeatmap(lastWeekAnalysisValues.first, rankList = rankList) } item { HorizontalStackedBar(lastWeekAnalysisValues.first, rankList = rankList) }
item { item {
Row { Row {
lastWeekAnalysisValues.first.fastForEach { lastWeekAnalysisValues.first.fastForEach {
@@ -260,6 +261,16 @@ fun SharedTransitionScope.LastWeekScreen(
} }
item { item {
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { 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 { lastWeekSummaryValues.fastForEach {
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
Text( Text(
@@ -267,7 +278,7 @@ fun SharedTransitionScope.LastWeekScreen(
style = typography.labelSmall, style = typography.labelSmall,
modifier = Modifier.size(18.dp) modifier = Modifier.size(18.dp)
) )
VariableWidth1DHeatmap(it.second, rankList = rankList) HorizontalStackedBar(it.second, rankList = rankList)
} }
} }
} }