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,60 +120,70 @@ fun FocusBreakRatioVisualization(
height: Dp = 40.dp, height: Dp = 40.dp,
gap: Dp = 2.dp gap: Dp = 2.dp
) { ) {
val focusPercentage = ((focusDuration / (focusDuration.toFloat() + breakDuration)) * 100) if (focusDuration + breakDuration > 0) {
val breakPercentage = ((breakDuration / (focusDuration.toFloat() + breakDuration)) * 100) val focusPercentage = ((focusDuration / (focusDuration.toFloat() + breakDuration)) * 100)
Row( val breakPercentage = 100 - focusPercentage
verticalAlignment = Alignment.CenterVertically, Row(
horizontalArrangement = Arrangement.spacedBy(gap), verticalAlignment = Alignment.CenterVertically,
modifier = modifier horizontalArrangement = Arrangement.spacedBy(gap),
) { modifier = modifier
Text( ) {
text = focusPercentage.roundToInt().toString() + '%', Text(
style = typography.bodyLarge, text = focusPercentage.roundToInt().toString() + '%',
color = colorScheme.primary, style = typography.bodyLarge,
modifier = Modifier.padding(end = 6.dp) color = colorScheme.primary,
) modifier = Modifier.padding(end = 6.dp)
Spacer( )
Modifier if (focusDuration > 0) Spacer(
.weight(focusPercentage) Modifier
.height(height) .weight(focusPercentage)
.background( .height(height)
colorScheme.primary, .background(
shapes.large.copy( colorScheme.primary,
topEnd = shapes.extraSmall.topEnd, if (breakDuration > 0) shapes.large.copy(
bottomEnd = shapes.extraSmall.bottomEnd topEnd = shapes.extraSmall.topEnd,
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, if (focusDuration > 0) shapes.large.copy(
shapes.large.copy( topStart = shapes.extraSmall.topStart,
topStart = shapes.extraSmall.topStart, bottomStart = shapes.extraSmall.bottomStart
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(
text = breakPercentage.roundToInt().toString() + '%', text = "Not enough data",
style = typography.bodyLarge, style = typography.bodyLarge,
color = colorScheme.tertiary, color = colorScheme.outline,
modifier = Modifier.padding(start = 6.dp) 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)
} }
} }
} }