From f3ab562a6cc5503b0f9ec341aec00e5f91cf51ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Orzech?= Date: Thu, 19 Mar 2026 14:51:03 +0100 Subject: [PATCH] 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. --- .../swoosh/microblog/ui/feed/FeedScreen.kt | 122 +++++------------- 1 file changed, 32 insertions(+), 90 deletions(-) diff --git a/app/src/main/java/com/swoosh/microblog/ui/feed/FeedScreen.kt b/app/src/main/java/com/swoosh/microblog/ui/feed/FeedScreen.kt index a210cb2..be35099 100644 --- a/app/src/main/java/com/swoosh/microblog/ui/feed/FeedScreen.kt +++ b/app/src/main/java/com/swoosh/microblog/ui/feed/FeedScreen.kt @@ -129,8 +129,7 @@ fun FeedScreen( var showRenameDialog by remember { mutableStateOf(null) } // Staggered entrance tracking - val animatedKeys = remember { mutableStateMapOf() } - var initialLoadComplete by remember { mutableStateOf(false) } + // Stagger animations removed for smooth scrolling performance // FAB entrance animation var fabVisible by remember { mutableStateOf(false) } @@ -535,19 +534,12 @@ fun FeedScreen( key = { it.ghostId ?: "local_${it.localId}" }, contentType = { "search_post" } ) { post -> - val itemKey = post.ghostId ?: "local_${post.localId}" - StaggeredItem( - key = itemKey, - animatedKeys = animatedKeys, - initialLoadComplete = initialLoadComplete - ) { - PostCard( - post = post, - onClick = { onPostClick(post) }, - onCancelQueue = { viewModel.cancelQueuedPost(post) }, - highlightQuery = searchQuery - ) - } + PostCard( + post = post, + onClick = { onPostClick(post) }, + onCancelQueue = { viewModel.cancelQueuedPost(post) }, + highlightQuery = searchQuery + ) } } else { // Normal feed: pinned section + swipe actions @@ -558,45 +550,6 @@ fun FeedScreen( key = { "pinned_${it.ghostId ?: "local_${it.localId}"}" }, contentType = { "pinned_post" } ) { post -> - val itemKey = post.ghostId ?: "local_${post.localId}" - StaggeredItem( - key = itemKey, - animatedKeys = animatedKeys, - initialLoadComplete = initialLoadComplete - ) { - SwipeablePostCard( - post = post, - onClick = { onPostClick(post) }, - onCancelQueue = { viewModel.cancelQueuedPost(post) }, - onShare = { - val postUrl = ShareUtils.resolvePostUrl(post, baseUrl) - if (postUrl != null) { - val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager - clipboard.setPrimaryClip(ClipData.newPlainText("Post URL", postUrl)) - } - }, - onEdit = { onEditPost(post) }, - onDelete = { postPendingDelete = post }, - onTogglePin = { viewModel.toggleFeatured(post) }, - onTagClick = { tag -> viewModel.filterByTag(tag) }, - snackbarHostState = snackbarHostState - ) - } - } - // No extra separator — thick dividers built into each post - } - - items( - regularPosts, - key = { it.ghostId ?: "local_${it.localId}" }, - contentType = { "regular_post" } - ) { post -> - val itemKey = post.ghostId ?: "local_${post.localId}" - StaggeredItem( - key = itemKey, - animatedKeys = animatedKeys, - initialLoadComplete = initialLoadComplete - ) { SwipeablePostCard( post = post, onClick = { onPostClick(post) }, @@ -615,6 +568,31 @@ fun FeedScreen( snackbarHostState = snackbarHostState ) } + // No extra separator — thick dividers built into each post + } + + items( + regularPosts, + key = { it.ghostId ?: "local_${it.localId}" }, + contentType = { "regular_post" } + ) { post -> + SwipeablePostCard( + post = post, + onClick = { onPostClick(post) }, + onCancelQueue = { viewModel.cancelQueuedPost(post) }, + onShare = { + val postUrl = ShareUtils.resolvePostUrl(post, baseUrl) + if (postUrl != null) { + val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + clipboard.setPrimaryClip(ClipData.newPlainText("Post URL", postUrl)) + } + }, + onEdit = { onEditPost(post) }, + onDelete = { postPendingDelete = post }, + onTogglePin = { viewModel.toggleFeatured(post) }, + onTagClick = { tag -> viewModel.filterByTag(tag) }, + snackbarHostState = snackbarHostState + ) } } @@ -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) { PullRefreshIndicator( refreshing = state.isRefreshing, @@ -766,34 +736,6 @@ fun FeedScreen( } } -@Composable -private fun StaggeredItem( - key: String, - animatedKeys: MutableMap, - 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) @Composable