fix(stats): fix crash when opening last week screen with empty stats table
This commit is contained in:
@@ -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),
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user