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.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<Long>,
modifier: Modifier = Modifier,
rankList: List<Int> = 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),

View File

@@ -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)
}
}
}