feat: Optimize animations in OnboardingScreen and improve MessageInputBar visibility transitions
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user