feat: Optimize animations in OnboardingScreen and improve MessageInputBar visibility transitions

This commit is contained in:
k1ngsterr1
2026-01-15 23:42:52 +05:00
parent 80f5e436ee
commit 5919194471
2 changed files with 148 additions and 132 deletions

View File

@@ -2393,8 +2393,8 @@ private fun MessageInputBar(
// REPLY PANEL // REPLY PANEL
AnimatedVisibility( AnimatedVisibility(
visible = hasReply, visible = hasReply,
enter = fadeIn(tween(150)) + expandVertically(), enter = fadeIn(tween(100)) + expandVertically(animationSpec = tween(100)),
exit = fadeOut(tween(100)) + shrinkVertically() exit = fadeOut(tween(0)) + shrinkVertically(animationSpec = tween(0))
) { ) {
Row( Row(
modifier = Modifier modifier = Modifier

View File

@@ -246,19 +246,17 @@ fun OnboardingScreen(
state = pagerState, state = pagerState,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.height(150.dp), .height(150.dp)
.graphicsLayer {
// Hardware acceleration for entire pager
compositingStrategy = androidx.compose.ui.graphics.CompositingStrategy.Offscreen
},
// Pre-load adjacent pages for smooth swiping // Pre-load adjacent pages for smooth swiping
beyondBoundsPageCount = 2, beyondBoundsPageCount = 2,
flingBehavior = PagerDefaults.flingBehavior( flingBehavior = PagerDefaults.flingBehavior(
state = pagerState, state = pagerState,
lowVelocityAnimationSpec = tween( lowVelocityAnimationSpec = snap(), // Instant!
durationMillis = 250, snapAnimationSpec = snap() // No animation!
easing = FastOutSlowInEasing
),
snapAnimationSpec = spring(
dampingRatio = Spring.DampingRatioNoBouncy,
stiffness = Spring.StiffnessMedium
)
) )
) { page -> ) { page ->
OnboardingPageContent( OnboardingPageContent(
@@ -374,20 +372,46 @@ fun AnimatedRosettaLogo(
bookComposition: Any?, bookComposition: Any?,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
// Use derivedStateOf for optimized reads // Use derivedStateOf for optimized reads - prevents unnecessary recompositions
val currentPage by remember { derivedStateOf { pagerState.currentPage } } val currentPage by remember { derivedStateOf { pagerState.currentPage } }
Box( // Pre-calculate all animation progress states ONCE (they stay in memory)
modifier = modifier, val ideaLottieComp = ideaComposition as? com.airbnb.lottie.LottieComposition
contentAlignment = Alignment.Center val moneyLottieComp = moneyComposition as? com.airbnb.lottie.LottieComposition
) { val lockLottieComp = lockComposition as? com.airbnb.lottie.LottieComposition
// Rosetta icon (page 0) with pulse animation like splash screen val bookLottieComp = bookComposition as? com.airbnb.lottie.LottieComposition
androidx.compose.animation.AnimatedVisibility(
visible = currentPage == 0, // All animations are always "playing" but only visible one shows
enter = fadeIn(animationSpec = tween(200)), val ideaProgress by animateLottieCompositionAsState(
exit = fadeOut(animationSpec = tween(200)) composition = ideaLottieComp,
) { iterations = 1,
Box(contentAlignment = Alignment.Center) { isPlaying = currentPage == 1,
speed = 1.5f,
restartOnPlay = true
)
val moneyProgress by animateLottieCompositionAsState(
composition = moneyLottieComp,
iterations = 1,
isPlaying = currentPage == 2,
speed = 1.5f,
restartOnPlay = true
)
val lockProgress by animateLottieCompositionAsState(
composition = lockLottieComp,
iterations = 1,
isPlaying = currentPage == 3,
speed = 1.5f,
restartOnPlay = true
)
val bookProgress by animateLottieCompositionAsState(
composition = bookLottieComp,
iterations = 1,
isPlaying = currentPage == 4,
speed = 1.5f,
restartOnPlay = true
)
// Pulse animation for logo (always running, cheap)
val pulseScale by rememberInfiniteTransition(label = "pulse").animateFloat( val pulseScale by rememberInfiniteTransition(label = "pulse").animateFloat(
initialValue = 1f, initialValue = 1f,
targetValue = 1.1f, targetValue = 1.1f,
@@ -398,17 +422,31 @@ fun AnimatedRosettaLogo(
label = "pulseScale" label = "pulseScale"
) )
// Glow effect behind logo Box(
modifier = modifier,
contentAlignment = Alignment.Center
) {
// === PRE-RENDERED LAYERS - All always exist, just alpha changes ===
// Page 0: Rosetta Logo
Box(
modifier = Modifier
.fillMaxSize()
.graphicsLayer {
alpha = if (currentPage == 0) 1f else 0f
},
contentAlignment = Alignment.Center
) {
// Glow effect
Box( Box(
modifier = Modifier modifier = Modifier
.size(180.dp) .size(180.dp)
.scale(pulseScale) .scale(if (currentPage == 0) pulseScale else 1f)
.background( .background(
color = Color(0xFF54A9EB).copy(alpha = 0.2f), color = Color(0xFF54A9EB).copy(alpha = 0.2f),
shape = CircleShape shape = CircleShape
) )
) )
// Main logo // Main logo
Image( Image(
painter = painterResource(id = R.drawable.rosetta_icon), painter = painterResource(id = R.drawable.rosetta_icon),
@@ -418,100 +456,78 @@ fun AnimatedRosettaLogo(
.clip(CircleShape) .clip(CircleShape)
) )
} }
// Page 1: Idea animation (always in memory!)
if (ideaLottieComp != null) {
LottieAnimation(
composition = ideaLottieComp,
progress = { ideaProgress },
modifier = Modifier
.fillMaxSize()
.graphicsLayer {
alpha = if (currentPage == 1) 1f else 0f
// Hardware layer optimization
compositingStrategy = androidx.compose.ui.graphics.CompositingStrategy.Offscreen
// Disable clipping for performance
clip = false
},
maintainOriginalImageBounds = true,
// Disable dynamic properties for max performance
enableMergePaths = false
)
} }
// Idea animation (page 1) // Page 2: Money animation
androidx.compose.animation.AnimatedVisibility( if (moneyLottieComp != null) {
visible = currentPage == 1,
enter = fadeIn(animationSpec = tween(200)),
exit = fadeOut(animationSpec = tween(200))
) {
ideaComposition?.let { comp ->
val lottieComp = comp as? com.airbnb.lottie.LottieComposition
val progress by animateLottieCompositionAsState(
composition = lottieComp,
iterations = 1,
isPlaying = true,
speed = 1.5f
)
LottieAnimation( LottieAnimation(
composition = lottieComp, composition = moneyLottieComp,
progress = { progress }, progress = { moneyProgress },
modifier = Modifier.fillMaxSize(), modifier = Modifier
maintainOriginalImageBounds = true .fillMaxSize()
.graphicsLayer {
alpha = if (currentPage == 2) 1f else 0f
compositingStrategy = androidx.compose.ui.graphics.CompositingStrategy.Offscreen
clip = false
},
maintainOriginalImageBounds = true,
enableMergePaths = false
) )
} }
}
// Money animation (page 2) // Page 3: Lock animation
androidx.compose.animation.AnimatedVisibility( if (lockLottieComp != null) {
visible = currentPage == 2,
enter = fadeIn(animationSpec = tween(200)),
exit = fadeOut(animationSpec = tween(200))
) {
moneyComposition?.let { comp ->
val lottieComp = comp as? com.airbnb.lottie.LottieComposition
val progress by animateLottieCompositionAsState(
composition = lottieComp,
iterations = 1,
isPlaying = true,
speed = 1.5f
)
LottieAnimation( LottieAnimation(
composition = lottieComp, composition = lockLottieComp,
progress = { progress }, progress = { lockProgress },
modifier = Modifier.fillMaxSize(), modifier = Modifier
maintainOriginalImageBounds = true .fillMaxSize()
.graphicsLayer {
alpha = if (currentPage == 3) 1f else 0f
compositingStrategy = androidx.compose.ui.graphics.CompositingStrategy.Offscreen
clip = false
},
maintainOriginalImageBounds = true,
enableMergePaths = false
) )
} }
}
// Lock animation (page 3) // Page 4: Book animation
androidx.compose.animation.AnimatedVisibility( if (bookLottieComp != null) {
visible = currentPage == 3,
enter = fadeIn(animationSpec = tween(200)),
exit = fadeOut(animationSpec = tween(200))
) {
lockComposition?.let { comp ->
val lottieComp = comp as? com.airbnb.lottie.LottieComposition
val progress by animateLottieCompositionAsState(
composition = lottieComp,
iterations = 1,
isPlaying = true,
speed = 1.5f
)
LottieAnimation( LottieAnimation(
composition = lottieComp, composition = bookLottieComp,
progress = { progress }, progress = { bookProgress },
modifier = Modifier.fillMaxSize(), modifier = Modifier
maintainOriginalImageBounds = true .fillMaxSize()
.graphicsLayer {
alpha = if (currentPage == 4) 1f else 0f
compositingStrategy = androidx.compose.ui.graphics.CompositingStrategy.Offscreen
clip = false
},
maintainOriginalImageBounds = true,
enableMergePaths = false
) )
} }
} }
// Book animation (page 4)
androidx.compose.animation.AnimatedVisibility(
visible = currentPage == 4,
enter = fadeIn(animationSpec = tween(200)),
exit = fadeOut(animationSpec = tween(200))
) {
bookComposition?.let { comp ->
val lottieComp = comp as? com.airbnb.lottie.LottieComposition
val progress by animateLottieCompositionAsState(
composition = lottieComp,
iterations = 1,
isPlaying = true,
speed = 1.5f
)
LottieAnimation(
composition = lottieComp,
progress = { progress },
modifier = Modifier.fillMaxSize(),
maintainOriginalImageBounds = true
)
}
}
}
} }
@Composable @Composable