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,142 +372,160 @@ 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 } }
// Pre-calculate all animation progress states ONCE (they stay in memory)
val ideaLottieComp = ideaComposition as? com.airbnb.lottie.LottieComposition
val moneyLottieComp = moneyComposition as? com.airbnb.lottie.LottieComposition
val lockLottieComp = lockComposition as? com.airbnb.lottie.LottieComposition
val bookLottieComp = bookComposition as? com.airbnb.lottie.LottieComposition
// All animations are always "playing" but only visible one shows
val ideaProgress by animateLottieCompositionAsState(
composition = ideaLottieComp,
iterations = 1,
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(
initialValue = 1f,
targetValue = 1.1f,
animationSpec = infiniteRepeatable(
animation = tween(800, easing = FastOutSlowInEasing),
repeatMode = RepeatMode.Reverse
),
label = "pulseScale"
)
Box( Box(
modifier = modifier, modifier = modifier,
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
// Rosetta icon (page 0) with pulse animation like splash screen // === PRE-RENDERED LAYERS - All always exist, just alpha changes ===
androidx.compose.animation.AnimatedVisibility(
visible = currentPage == 0, // Page 0: Rosetta Logo
enter = fadeIn(animationSpec = tween(200)), Box(
exit = fadeOut(animationSpec = tween(200)) modifier = Modifier
.fillMaxSize()
.graphicsLayer {
alpha = if (currentPage == 0) 1f else 0f
},
contentAlignment = Alignment.Center
) { ) {
Box(contentAlignment = Alignment.Center) { // Glow effect
val pulseScale by rememberInfiniteTransition(label = "pulse").animateFloat( Box(
initialValue = 1f, modifier = Modifier
targetValue = 1.1f, .size(180.dp)
animationSpec = infiniteRepeatable( .scale(if (currentPage == 0) pulseScale else 1f)
animation = tween(800, easing = FastOutSlowInEasing), .background(
repeatMode = RepeatMode.Reverse color = Color(0xFF54A9EB).copy(alpha = 0.2f),
), shape = CircleShape
label = "pulseScale" )
) )
// Main logo
// Glow effect behind logo Image(
Box( painter = painterResource(id = R.drawable.rosetta_icon),
modifier = Modifier contentDescription = "Rosetta Logo",
.size(180.dp) modifier = Modifier
.scale(pulseScale) .size(150.dp)
.background( .clip(CircleShape)
color = Color(0xFF54A9EB).copy(alpha = 0.2f), )
shape = CircleShape
)
)
// Main logo
Image(
painter = painterResource(id = R.drawable.rosetta_icon),
contentDescription = "Rosetta Logo",
modifier = Modifier
.size(150.dp)
.clip(CircleShape)
)
}
} }
// Idea animation (page 1) // Page 1: Idea animation (always in memory!)
androidx.compose.animation.AnimatedVisibility( if (ideaLottieComp != null) {
visible = currentPage == 1, LottieAnimation(
enter = fadeIn(animationSpec = tween(200)), composition = ideaLottieComp,
exit = fadeOut(animationSpec = tween(200)) progress = { ideaProgress },
) { modifier = Modifier
ideaComposition?.let { comp -> .fillMaxSize()
val lottieComp = comp as? com.airbnb.lottie.LottieComposition .graphicsLayer {
val progress by animateLottieCompositionAsState( alpha = if (currentPage == 1) 1f else 0f
composition = lottieComp, // Hardware layer optimization
iterations = 1, compositingStrategy = androidx.compose.ui.graphics.CompositingStrategy.Offscreen
isPlaying = true, // Disable clipping for performance
speed = 1.5f clip = false
) },
LottieAnimation( maintainOriginalImageBounds = true,
composition = lottieComp, // Disable dynamic properties for max performance
progress = { progress }, enableMergePaths = false
modifier = Modifier.fillMaxSize(), )
maintainOriginalImageBounds = true
)
}
} }
// Money animation (page 2) // Page 2: Money animation
androidx.compose.animation.AnimatedVisibility( if (moneyLottieComp != null) {
visible = currentPage == 2, LottieAnimation(
enter = fadeIn(animationSpec = tween(200)), composition = moneyLottieComp,
exit = fadeOut(animationSpec = tween(200)) progress = { moneyProgress },
) { modifier = Modifier
moneyComposition?.let { comp -> .fillMaxSize()
val lottieComp = comp as? com.airbnb.lottie.LottieComposition .graphicsLayer {
val progress by animateLottieCompositionAsState( alpha = if (currentPage == 2) 1f else 0f
composition = lottieComp, compositingStrategy = androidx.compose.ui.graphics.CompositingStrategy.Offscreen
iterations = 1, clip = false
isPlaying = true, },
speed = 1.5f maintainOriginalImageBounds = true,
) enableMergePaths = false
LottieAnimation( )
composition = lottieComp,
progress = { progress },
modifier = Modifier.fillMaxSize(),
maintainOriginalImageBounds = true
)
}
} }
// Lock animation (page 3) // Page 3: Lock animation
androidx.compose.animation.AnimatedVisibility( if (lockLottieComp != null) {
visible = currentPage == 3, LottieAnimation(
enter = fadeIn(animationSpec = tween(200)), composition = lockLottieComp,
exit = fadeOut(animationSpec = tween(200)) progress = { lockProgress },
) { modifier = Modifier
lockComposition?.let { comp -> .fillMaxSize()
val lottieComp = comp as? com.airbnb.lottie.LottieComposition .graphicsLayer {
val progress by animateLottieCompositionAsState( alpha = if (currentPage == 3) 1f else 0f
composition = lottieComp, compositingStrategy = androidx.compose.ui.graphics.CompositingStrategy.Offscreen
iterations = 1, clip = false
isPlaying = true, },
speed = 1.5f maintainOriginalImageBounds = true,
) enableMergePaths = false
LottieAnimation( )
composition = lottieComp,
progress = { progress },
modifier = Modifier.fillMaxSize(),
maintainOriginalImageBounds = true
)
}
} }
// Book animation (page 4) // Page 4: Book animation
androidx.compose.animation.AnimatedVisibility( if (bookLottieComp != null) {
visible = currentPage == 4, LottieAnimation(
enter = fadeIn(animationSpec = tween(200)), composition = bookLottieComp,
exit = fadeOut(animationSpec = tween(200)) progress = { bookProgress },
) { modifier = Modifier
bookComposition?.let { comp -> .fillMaxSize()
val lottieComp = comp as? com.airbnb.lottie.LottieComposition .graphicsLayer {
val progress by animateLottieCompositionAsState( alpha = if (currentPage == 4) 1f else 0f
composition = lottieComp, compositingStrategy = androidx.compose.ui.graphics.CompositingStrategy.Offscreen
iterations = 1, clip = false
isPlaying = true, },
speed = 1.5f maintainOriginalImageBounds = true,
) enableMergePaths = false
LottieAnimation( )
composition = lottieComp,
progress = { progress },
modifier = Modifier.fillMaxSize(),
maintainOriginalImageBounds = true
)
}
} }
} }
} }