mirror of
https://github.com/pawelorzech/Swoosh.git
synced 2026-03-31 20:15:41 +00:00
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:
parent
c3fb3c7c98
commit
f3ab562a6c
1 changed files with 32 additions and 90 deletions
|
|
@ -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,19 +534,12 @@ 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}"
|
PostCard(
|
||||||
StaggeredItem(
|
post = post,
|
||||||
key = itemKey,
|
onClick = { onPostClick(post) },
|
||||||
animatedKeys = animatedKeys,
|
onCancelQueue = { viewModel.cancelQueuedPost(post) },
|
||||||
initialLoadComplete = initialLoadComplete
|
highlightQuery = searchQuery
|
||||||
) {
|
)
|
||||||
PostCard(
|
|
||||||
post = post,
|
|
||||||
onClick = { onPostClick(post) },
|
|
||||||
onCancelQueue = { viewModel.cancelQueuedPost(post) },
|
|
||||||
highlightQuery = searchQuery
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Normal feed: pinned section + swipe actions
|
// Normal feed: pinned section + swipe actions
|
||||||
|
|
@ -558,45 +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(
|
|
||||||
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(
|
SwipeablePostCard(
|
||||||
post = post,
|
post = post,
|
||||||
onClick = { onPostClick(post) },
|
onClick = { onPostClick(post) },
|
||||||
|
|
@ -615,6 +568,31 @@ fun FeedScreen(
|
||||||
snackbarHostState = snackbarHostState
|
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) {
|
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
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue