perf: remove StaggeredItem animations from LazyColumn for smooth scrolling

StaggeredItem wrapped every list item in AnimatedVisibility with
slideInVertically + fadeIn + LaunchedEffect delays, causing jittery
scrolling due to excessive recompositions and layout passes.
This commit is contained in:
Paweł Orzech 2026-03-19 14:51:03 +01:00
parent c3fb3c7c98
commit f3ab562a6c
No known key found for this signature in database

View file

@ -129,8 +129,7 @@ fun FeedScreen(
var showRenameDialog by remember { mutableStateOf<GhostAccount?>(null) } var showRenameDialog by remember { mutableStateOf<GhostAccount?>(null) }
// Staggered entrance tracking // Staggered entrance tracking
val animatedKeys = remember { mutableStateMapOf<String, Boolean>() } // Stagger animations removed for smooth scrolling performance
var initialLoadComplete by remember { mutableStateOf(false) }
// FAB entrance animation // FAB entrance animation
var fabVisible by remember { mutableStateOf(false) } var fabVisible by remember { mutableStateOf(false) }
@ -535,12 +534,6 @@ fun FeedScreen(
key = { it.ghostId ?: "local_${it.localId}" }, key = { it.ghostId ?: "local_${it.localId}" },
contentType = { "search_post" } contentType = { "search_post" }
) { post -> ) { post ->
val itemKey = post.ghostId ?: "local_${post.localId}"
StaggeredItem(
key = itemKey,
animatedKeys = animatedKeys,
initialLoadComplete = initialLoadComplete
) {
PostCard( PostCard(
post = post, post = post,
onClick = { onPostClick(post) }, onClick = { onPostClick(post) },
@ -548,7 +541,6 @@ fun FeedScreen(
highlightQuery = searchQuery highlightQuery = searchQuery
) )
} }
}
} else { } else {
// Normal feed: pinned section + swipe actions // Normal feed: pinned section + swipe actions
// Pinned posts (pin icon on each post card is sufficient) // Pinned posts (pin icon on each post card is sufficient)
@ -558,12 +550,6 @@ fun FeedScreen(
key = { "pinned_${it.ghostId ?: "local_${it.localId}"}" }, key = { "pinned_${it.ghostId ?: "local_${it.localId}"}" },
contentType = { "pinned_post" } contentType = { "pinned_post" }
) { post -> ) { post ->
val itemKey = post.ghostId ?: "local_${post.localId}"
StaggeredItem(
key = itemKey,
animatedKeys = animatedKeys,
initialLoadComplete = initialLoadComplete
) {
SwipeablePostCard( SwipeablePostCard(
post = post, post = post,
onClick = { onPostClick(post) }, onClick = { onPostClick(post) },
@ -582,7 +568,6 @@ fun FeedScreen(
snackbarHostState = snackbarHostState snackbarHostState = snackbarHostState
) )
} }
}
// No extra separator — thick dividers built into each post // No extra separator — thick dividers built into each post
} }
@ -591,12 +576,6 @@ fun FeedScreen(
key = { it.ghostId ?: "local_${it.localId}" }, key = { it.ghostId ?: "local_${it.localId}" },
contentType = { "regular_post" } contentType = { "regular_post" }
) { post -> ) { post ->
val itemKey = post.ghostId ?: "local_${post.localId}"
StaggeredItem(
key = itemKey,
animatedKeys = animatedKeys,
initialLoadComplete = initialLoadComplete
) {
SwipeablePostCard( SwipeablePostCard(
post = post, post = post,
onClick = { onPostClick(post) }, onClick = { onPostClick(post) },
@ -616,7 +595,6 @@ fun FeedScreen(
) )
} }
} }
}
if (!isSearchActive && state.isLoadingMore) { if (!isSearchActive && state.isLoadingMore) {
item { item {
@ -630,14 +608,6 @@ fun FeedScreen(
} }
} }
LaunchedEffect(state.posts) {
if (state.posts.isNotEmpty() && !initialLoadComplete) {
delay(SwooshMotion.StaggerDelayMs * minOf(state.posts.size, 8) + 200)
initialLoadComplete = true
animatedKeys.clear() // Free memory — no longer needed
}
}
if (!isSearchActive) { if (!isSearchActive) {
PullRefreshIndicator( PullRefreshIndicator(
refreshing = state.isRefreshing, refreshing = state.isRefreshing,
@ -766,34 +736,6 @@ fun FeedScreen(
} }
} }
@Composable
private fun StaggeredItem(
key: String,
animatedKeys: MutableMap<String, Boolean>,
initialLoadComplete: Boolean,
content: @Composable () -> Unit
) {
val shouldAnimate = !initialLoadComplete && key !in animatedKeys
var visible by remember { mutableStateOf(!shouldAnimate) }
LaunchedEffect(key) {
if (shouldAnimate && animatedKeys.size < 8) {
delay(SwooshMotion.StaggerDelayMs * animatedKeys.size)
animatedKeys[key] = true
}
visible = true
}
AnimatedVisibility(
visible = visible,
enter = slideInVertically(
initialOffsetY = { it / 3 },
animationSpec = SwooshMotion.gentle()
) + fadeIn(animationSpec = SwooshMotion.quick())
) {
content()
}
}
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable