fix: stats screen shows layout instantly, only numbers animate (no stagger entrance)

This commit is contained in:
Paweł Orzech 2026-03-19 14:59:28 +01:00
parent 3da3e97e77
commit 4a2a18282c
No known key found for this signature in database

View file

@ -1,11 +1,7 @@
package com.swoosh.microblog.ui.stats
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateIntAsState
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.scaleIn
import androidx.compose.animation.slideInVertically
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
@ -22,8 +18,6 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import com.swoosh.microblog.ui.animation.SwooshMotion
import kotlinx.coroutines.delay
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@ -32,38 +26,24 @@ fun StatsScreen(
) {
val state by viewModel.uiState.collectAsStateWithLifecycle()
// Staggered entrance for stats cards
val cardVisible = remember { List(4) { mutableStateOf(false) } }
var writingStatsVisible by remember { mutableStateOf(false) }
LaunchedEffect(state.isLoading) {
if (!state.isLoading) {
cardVisible.forEachIndexed { index, vis ->
delay(SwooshMotion.StaggerDelayMs * index)
vis.value = true
}
delay(SwooshMotion.StaggerDelayMs * 4)
writingStatsVisible = true
}
}
// Animated counters (ST3) — snappier 400ms count-up
// Animated counters — numbers count up from 0 when data loads
val animatedTotal by animateIntAsState(
targetValue = if (!state.isLoading) state.stats.totalPosts else 0,
targetValue = state.stats.totalPosts,
animationSpec = tween(400),
label = "totalPosts"
)
val animatedPublished by animateIntAsState(
targetValue = if (!state.isLoading) state.stats.publishedCount else 0,
targetValue = state.stats.publishedCount,
animationSpec = tween(400),
label = "published"
)
val animatedDrafts by animateIntAsState(
targetValue = if (!state.isLoading) state.stats.draftCount else 0,
targetValue = state.stats.draftCount,
animationSpec = tween(400),
label = "drafts"
)
val animatedScheduled by animateIntAsState(
targetValue = if (!state.isLoading) state.stats.scheduledCount else 0,
targetValue = state.stats.scheduledCount,
animationSpec = tween(400),
label = "scheduled"
)
@ -75,14 +55,6 @@ fun StatsScreen(
)
}
) { padding ->
if (state.isLoading) {
Box(
modifier = Modifier.fillMaxSize().padding(padding),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator()
}
} else {
Column(
modifier = Modifier
.fillMaxSize()
@ -101,58 +73,38 @@ fun StatsScreen(
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
AnimatedVisibility(
visible = cardVisible[0].value,
modifier = Modifier.weight(1f),
enter = scaleIn(animationSpec = SwooshMotion.bouncy()) + fadeIn(SwooshMotion.quick())
) {
StatsCard(
modifier = Modifier.weight(1f),
value = "$animatedTotal",
label = "Total Posts",
icon = Icons.AutoMirrored.Filled.Article
)
}
AnimatedVisibility(
visible = cardVisible[1].value,
modifier = Modifier.weight(1f),
enter = scaleIn(animationSpec = SwooshMotion.bouncy()) + fadeIn(SwooshMotion.quick())
) {
StatsCard(
modifier = Modifier.weight(1f),
value = "$animatedPublished",
label = "Published",
icon = Icons.Default.Create
)
}
}
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
AnimatedVisibility(
visible = cardVisible[2].value,
modifier = Modifier.weight(1f),
enter = scaleIn(animationSpec = SwooshMotion.bouncy()) + fadeIn(SwooshMotion.quick())
) {
StatsCard(
modifier = Modifier.weight(1f),
value = "$animatedDrafts",
label = "Drafts",
icon = Icons.Default.TextFields
)
}
AnimatedVisibility(
visible = cardVisible[3].value,
modifier = Modifier.weight(1f),
enter = scaleIn(animationSpec = SwooshMotion.bouncy()) + fadeIn(SwooshMotion.quick())
) {
StatsCard(
modifier = Modifier.weight(1f),
value = "$animatedScheduled",
label = "Scheduled",
icon = Icons.Default.Schedule
)
}
}
Spacer(modifier = Modifier.height(8.dp))
@ -163,10 +115,6 @@ fun StatsScreen(
fontWeight = FontWeight.SemiBold
)
AnimatedVisibility(
visible = writingStatsVisible,
enter = slideInVertically(initialOffsetY = { it / 3 }, animationSpec = SwooshMotion.gentle()) + fadeIn(SwooshMotion.quick())
) {
OutlinedCard(modifier = Modifier.fillMaxWidth()) {
Column(
modifier = Modifier.padding(16.dp),
@ -185,7 +133,6 @@ fun StatsScreen(
WritingStatRow("Shortest post", "${state.stats.shortestPostWords} words")
}
}
}
if (state.error != null) {
Spacer(modifier = Modifier.height(8.dp))
@ -198,7 +145,6 @@ fun StatsScreen(
}
}
}
}
@Composable
private fun StatsCard(