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
AnimatedVisibility(
visible = hasReply,
enter = fadeIn(tween(150)) + expandVertically(),
exit = fadeOut(tween(100)) + shrinkVertically()
enter = fadeIn(tween(100)) + expandVertically(animationSpec = tween(100)),
exit = fadeOut(tween(0)) + shrinkVertically(animationSpec = tween(0))
) {
Row(
modifier = Modifier

View File

@@ -246,19 +246,17 @@ fun OnboardingScreen(
state = pagerState,
modifier = Modifier
.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
beyondBoundsPageCount = 2,
flingBehavior = PagerDefaults.flingBehavior(
state = pagerState,
lowVelocityAnimationSpec = tween(
durationMillis = 250,
easing = FastOutSlowInEasing
),
snapAnimationSpec = spring(
dampingRatio = Spring.DampingRatioNoBouncy,
stiffness = Spring.StiffnessMedium
)
lowVelocityAnimationSpec = snap(), // Instant!
snapAnimationSpec = snap() // No animation!
)
) { page ->
OnboardingPageContent(
@@ -374,142 +372,160 @@ fun AnimatedRosettaLogo(
bookComposition: Any?,
modifier: Modifier = Modifier
) {
// Use derivedStateOf for optimized reads
// Use derivedStateOf for optimized reads - prevents unnecessary recompositions
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(
modifier = modifier,
contentAlignment = Alignment.Center
) {
// Rosetta icon (page 0) with pulse animation like splash screen
androidx.compose.animation.AnimatedVisibility(
visible = currentPage == 0,
enter = fadeIn(animationSpec = tween(200)),
exit = fadeOut(animationSpec = tween(200))
// === 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
) {
Box(contentAlignment = Alignment.Center) {
val pulseScale by rememberInfiniteTransition(label = "pulse").animateFloat(
initialValue = 1f,
targetValue = 1.1f,
animationSpec = infiniteRepeatable(
animation = tween(800, easing = FastOutSlowInEasing),
repeatMode = RepeatMode.Reverse
),
label = "pulseScale"
)
// Glow effect behind logo
Box(
modifier = Modifier
.size(180.dp)
.scale(pulseScale)
.background(
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)
)
}
// Glow effect
Box(
modifier = Modifier
.size(180.dp)
.scale(if (currentPage == 0) pulseScale else 1f)
.background(
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)
androidx.compose.animation.AnimatedVisibility(
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(
composition = lottieComp,
progress = { progress },
modifier = Modifier.fillMaxSize(),
maintainOriginalImageBounds = true
)
}
// 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
)
}
// Money animation (page 2)
androidx.compose.animation.AnimatedVisibility(
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(
composition = lottieComp,
progress = { progress },
modifier = Modifier.fillMaxSize(),
maintainOriginalImageBounds = true
)
}
// Page 2: Money animation
if (moneyLottieComp != null) {
LottieAnimation(
composition = moneyLottieComp,
progress = { moneyProgress },
modifier = Modifier
.fillMaxSize()
.graphicsLayer {
alpha = if (currentPage == 2) 1f else 0f
compositingStrategy = androidx.compose.ui.graphics.CompositingStrategy.Offscreen
clip = false
},
maintainOriginalImageBounds = true,
enableMergePaths = false
)
}
// Lock animation (page 3)
androidx.compose.animation.AnimatedVisibility(
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(
composition = lottieComp,
progress = { progress },
modifier = Modifier.fillMaxSize(),
maintainOriginalImageBounds = true
)
}
// Page 3: Lock animation
if (lockLottieComp != null) {
LottieAnimation(
composition = lockLottieComp,
progress = { lockProgress },
modifier = Modifier
.fillMaxSize()
.graphicsLayer {
alpha = if (currentPage == 3) 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
)
}
// Page 4: Book animation
if (bookLottieComp != null) {
LottieAnimation(
composition = bookLottieComp,
progress = { bookProgress },
modifier = Modifier
.fillMaxSize()
.graphicsLayer {
alpha = if (currentPage == 4) 1f else 0f
compositingStrategy = androidx.compose.ui.graphics.CompositingStrategy.Offscreen
clip = false
},
maintainOriginalImageBounds = true,
enableMergePaths = false
)
}
}
}