diff --git a/app/src/main/java/com/rosetta/messenger/ui/auth/ConfirmSeedPhraseScreen.kt b/app/src/main/java/com/rosetta/messenger/ui/auth/ConfirmSeedPhraseScreen.kt index eb19b84..2a1ded2 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/auth/ConfirmSeedPhraseScreen.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/auth/ConfirmSeedPhraseScreen.kt @@ -50,6 +50,11 @@ fun ConfirmSeedPhraseScreen( var userInputs by remember { mutableStateOf(List(4) { "" }) } var showError by remember { mutableStateOf(false) } + var visible by remember { mutableStateOf(false) } + + LaunchedEffect(Unit) { + visible = true + } val allCorrect = wordsToConfirm.mapIndexed { i, (_, word) -> userInputs[i].trim().lowercase() == word.lowercase() @@ -100,7 +105,14 @@ fun ConfirmSeedPhraseScreen( Spacer(modifier = Modifier.height(16.dp)) // Info Card - Row( + AnimatedVisibility( + visible = visible, + enter = fadeIn(tween(500)) + slideInVertically( + initialOffsetY = { -30 }, + animationSpec = tween(500) + ) + ) { + Row( modifier = Modifier .fillMaxWidth() .clip(RoundedCornerShape(12.dp)) @@ -122,6 +134,7 @@ fun ConfirmSeedPhraseScreen( lineHeight = 18.sp ) } + } Spacer(modifier = Modifier.height(32.dp)) @@ -131,18 +144,26 @@ fun ConfirmSeedPhraseScreen( wordsToConfirm[index].second.lowercase() val hasInput = userInputs[index].isNotBlank() - WordInputField( - wordNumber = wordIndex + 1, - value = userInputs[index], - onValueChange = { - userInputs = userInputs.toMutableList().apply { - this[index] = it - } - showError = false - }, - isCorrect = if (hasInput) isCorrect else null, - isDarkTheme = isDarkTheme - ) + AnimatedVisibility( + visible = visible, + enter = fadeIn(tween(500, delayMillis = 100 + (index * 100))) + slideInVertically( + initialOffsetY = { 50 }, + animationSpec = tween(500, delayMillis = 100 + (index * 100)) + ) + ) { + WordInputField( + wordNumber = wordIndex + 1, + value = userInputs[index], + onValueChange = { + userInputs = userInputs.toMutableList().apply { + this[index] = it + } + showError = false + }, + isCorrect = if (hasInput) isCorrect else null, + isDarkTheme = isDarkTheme + ) + } Spacer(modifier = Modifier.height(16.dp)) } @@ -164,7 +185,14 @@ fun ConfirmSeedPhraseScreen( Spacer(modifier = Modifier.weight(1f)) // Continue Button - Button( + AnimatedVisibility( + visible = visible, + enter = fadeIn(tween(500, delayMillis = 500)) + slideInVertically( + initialOffsetY = { 50 }, + animationSpec = tween(500, delayMillis = 500) + ) + ) { + Button( onClick = { if (allCorrect) { onConfirmed() @@ -186,6 +214,7 @@ fun ConfirmSeedPhraseScreen( fontWeight = FontWeight.SemiBold ) } + } Spacer(modifier = Modifier.height(32.dp)) } diff --git a/app/src/main/java/com/rosetta/messenger/ui/auth/ImportSeedPhraseScreen.kt b/app/src/main/java/com/rosetta/messenger/ui/auth/ImportSeedPhraseScreen.kt index 881a1a4..aebc773 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/auth/ImportSeedPhraseScreen.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/auth/ImportSeedPhraseScreen.kt @@ -82,36 +82,56 @@ fun ImportSeedPhraseScreen( AnimatedVisibility( visible = visible, - enter = fadeIn(tween(600, easing = FastOutSlowInEasing)) + enter = fadeIn(tween(500, easing = FastOutSlowInEasing)) ) { Column( modifier = Modifier - .fillMaxSize() + .fillMaxWidth() .padding(horizontal = 24.dp), horizontalAlignment = Alignment.CenterHorizontally ) { Spacer(modifier = Modifier.height(16.dp)) - Text( - text = "Import Account", - fontSize = 28.sp, - fontWeight = FontWeight.Bold, - color = textColor - ) + AnimatedVisibility( + visible = visible, + enter = fadeIn(tween(500)) + slideInVertically( + initialOffsetY = { -20 }, + animationSpec = tween(500) + ) + ) { + Text( + text = "Import Account", + fontSize = 28.sp, + fontWeight = FontWeight.Bold, + color = textColor + ) + } Spacer(modifier = Modifier.height(12.dp)) - Text( - text = "Enter your 12-word recovery phrase", - fontSize = 15.sp, - color = secondaryTextColor, - textAlign = TextAlign.Center - ) + AnimatedVisibility( + visible = visible, + enter = fadeIn(tween(500, delayMillis = 100)) + ) { + Text( + text = "Enter your 12-word recovery phrase", + fontSize = 15.sp, + color = secondaryTextColor, + textAlign = TextAlign.Center + ) + } Spacer(modifier = Modifier.height(24.dp)) // Paste button - Button( + AnimatedVisibility( + visible = visible, + enter = fadeIn(tween(500, delayMillis = 200)) + scaleIn( + initialScale = 0.9f, + animationSpec = tween(500, delayMillis = 200) + ) + ) { + Button( onClick = { showPasteDialog = true }, modifier = Modifier .fillMaxWidth() @@ -134,11 +154,16 @@ fun ImportSeedPhraseScreen( fontWeight = FontWeight.SemiBold ) } + } Spacer(modifier = Modifier.height(24.dp)) // Clean grid - Column( + AnimatedVisibility( + visible = visible, + enter = fadeIn(tween(500, delayMillis = 300)) + ) { + Column( modifier = Modifier .fillMaxWidth() .clip(RoundedCornerShape(12.dp)) @@ -169,22 +194,35 @@ fun ImportSeedPhraseScreen( } } } + } // Error if (error != null) { Spacer(modifier = Modifier.height(12.dp)) - Text( - text = error ?: "", - fontSize = 14.sp, - color = Color(0xFFE53935), - textAlign = TextAlign.Center - ) + AnimatedVisibility( + visible = true, + enter = fadeIn(tween(300)) + scaleIn(initialScale = 0.9f) + ) { + Text( + text = error ?: "", + fontSize = 14.sp, + color = Color(0xFFE53935), + textAlign = TextAlign.Center + ) + } } Spacer(modifier = Modifier.weight(1f)) // Import button - Button( + AnimatedVisibility( + visible = visible, + enter = fadeIn(tween(500, delayMillis = 400)) + slideInVertically( + initialOffsetY = { 50 }, + animationSpec = tween(500, delayMillis = 400) + ) + ) { + Button( onClick = { val seedPhrase = words.map { it.trim() } @@ -213,6 +251,7 @@ fun ImportSeedPhraseScreen( shape = RoundedCornerShape(12.dp) ) { Text("Continue", fontSize = 17.sp, fontWeight = FontWeight.Medium) + } } Spacer(modifier = Modifier.height(40.dp)) diff --git a/app/src/main/java/com/rosetta/messenger/ui/auth/SeedPhraseScreen.kt b/app/src/main/java/com/rosetta/messenger/ui/auth/SeedPhraseScreen.kt index 67737eb..9f47b48 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/auth/SeedPhraseScreen.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/auth/SeedPhraseScreen.kt @@ -66,34 +66,46 @@ fun SeedPhraseScreen( } } - AnimatedVisibility( - visible = visible, - enter = fadeIn(tween(300, easing = FastOutSlowInEasing)) + Column( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 24.dp), + horizontalAlignment = Alignment.CenterHorizontally ) { - Column( - modifier = Modifier - .fillMaxSize() - .padding(horizontal = 24.dp), - horizontalAlignment = Alignment.CenterHorizontally + Spacer(modifier = Modifier.height(16.dp)) + + AnimatedVisibility( + visible = visible, + enter = fadeIn(tween(500)) + slideInVertically( + initialOffsetY = { -20 }, + animationSpec = tween(500) + ) ) { - Spacer(modifier = Modifier.height(16.dp)) - Text( text = "Your Recovery Phrase", fontSize = 28.sp, fontWeight = FontWeight.Bold, color = textColor ) + } Spacer(modifier = Modifier.height(12.dp)) - Text( - text = "Write down these 12 words in order.\nYou'll need them to restore your account.", - fontSize = 15.sp, - color = secondaryTextColor, - textAlign = TextAlign.Center, - lineHeight = 22.sp - ) + AnimatedVisibility( + visible = visible, + enter = fadeIn(tween(500, delayMillis = 100)) + slideInVertically( + initialOffsetY = { -20 }, + animationSpec = tween(500, delayMillis = 100) + ) + ) { + Text( + text = "Write down these 12 words in order.\nYou'll need them to restore your account.", + fontSize = 15.sp, + color = secondaryTextColor, + textAlign = TextAlign.Center, + lineHeight = 22.sp + ) + } Spacer(modifier = Modifier.height(32.dp)) @@ -112,35 +124,44 @@ fun SeedPhraseScreen( ) } } else { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(12.dp) + AnimatedVisibility( + visible = visible, + enter = fadeIn(tween(500, delayMillis = 200)) ) { - // Left column (words 1-6) - Column( - modifier = Modifier.weight(1f), - verticalArrangement = Arrangement.spacedBy(12.dp) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(12.dp) ) { - for (i in 0..5) { - WordItem( - number = i + 1, - word = seedPhrase[i], - isDarkTheme = isDarkTheme - ) + // Left column (words 1-6) + Column( + modifier = Modifier.weight(1f), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + for (i in 0..5) { + AnimatedWordItem( + number = i + 1, + word = seedPhrase[i], + isDarkTheme = isDarkTheme, + visible = visible, + delay = 300 + (i * 50) + ) + } } - } - - // Right column (words 7-12) - Column( - modifier = Modifier.weight(1f), - verticalArrangement = Arrangement.spacedBy(12.dp) - ) { - for (i in 6..11) { - WordItem( - number = i + 1, - word = seedPhrase[i], - isDarkTheme = isDarkTheme - ) + + // Right column (words 7-12) + Column( + modifier = Modifier.weight(1f), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + for (i in 6..11) { + AnimatedWordItem( + number = i + 1, + word = seedPhrase[i], + isDarkTheme = isDarkTheme, + visible = visible, + delay = 300 + (i * 50) + ) + } } } } @@ -150,56 +171,71 @@ fun SeedPhraseScreen( // Copy button if (!isGenerating) { - TextButton( - onClick = { - hasCopied = true - scope.launch { - delay(2000) - hasCopied = false - } - } + AnimatedVisibility( + visible = visible, + enter = fadeIn(tween(500, delayMillis = 600)) + scaleIn( + initialScale = 0.8f, + animationSpec = tween(500, delayMillis = 600) + ) ) { - Icon( - imageVector = if (hasCopied) Icons.Default.Check else Icons.Default.ContentCopy, - contentDescription = null, - tint = if (hasCopied) Color(0xFF4CAF50) else PrimaryBlue, - modifier = Modifier.size(20.dp) - ) - Spacer(modifier = Modifier.width(8.dp)) - Text( - text = if (hasCopied) "Copied" else "Copy to clipboard", - color = if (hasCopied) Color(0xFF4CAF50) else PrimaryBlue, - fontSize = 15.sp - ) + TextButton( + onClick = { + hasCopied = true + scope.launch { + delay(2000) + hasCopied = false + } + } + ) { + Icon( + imageVector = if (hasCopied) Icons.Default.Check else Icons.Default.ContentCopy, + contentDescription = null, + tint = if (hasCopied) Color(0xFF4CAF50) else PrimaryBlue, + modifier = Modifier.size(20.dp) + ) + Spacer(modifier = Modifier.width(8.dp)) + Text( + text = if (hasCopied) "Copied" else "Copy to clipboard", + color = if (hasCopied) Color(0xFF4CAF50) else PrimaryBlue, + fontSize = 15.sp + ) + } } } Spacer(modifier = Modifier.weight(1f)) // Continue button - Button( - onClick = { onConfirm(seedPhrase) }, - enabled = !isGenerating, - modifier = Modifier - .fillMaxWidth() - .height(50.dp), - colors = ButtonDefaults.buttonColors( - containerColor = PrimaryBlue, - contentColor = Color.White, - disabledContainerColor = if (isDarkTheme) Color(0xFF3A3A3A) else Color(0xFFE8E8E8), - disabledContentColor = if (isDarkTheme) Color(0xFF666666) else Color(0xFF999999) - ), - shape = RoundedCornerShape(12.dp) - ) { - Text( - text = "Continue", - fontSize = 17.sp, - fontWeight = FontWeight.Medium + AnimatedVisibility( + visible = visible, + enter = fadeIn(tween(500, delayMillis = 700)) + slideInVertically( + initialOffsetY = { 50 }, + animationSpec = tween(500, delayMillis = 700) ) + ) { + Button( + onClick = { onConfirm(seedPhrase) }, + enabled = !isGenerating, + modifier = Modifier + .fillMaxWidth() + .height(50.dp), + colors = ButtonDefaults.buttonColors( + containerColor = PrimaryBlue, + contentColor = Color.White, + disabledContainerColor = if (isDarkTheme) Color(0xFF3A3A3A) else Color(0xFFE8E8E8), + disabledContentColor = if (isDarkTheme) Color(0xFF666666) else Color(0xFF999999) + ), + shape = RoundedCornerShape(12.dp) + ) { + Text( + text = "Continue", + fontSize = 17.sp, + fontWeight = FontWeight.Medium + ) + } } - Spacer(modifier = Modifier.height(40.dp)) - } + Spacer(modifier = Modifier.height(40.dp)) } } } @@ -239,3 +275,28 @@ private fun WordItem( ) } } + +@Composable +private fun AnimatedWordItem( + number: Int, + word: String, + isDarkTheme: Boolean, + visible: Boolean, + delay: Int, + modifier: Modifier = Modifier +) { + AnimatedVisibility( + visible = visible, + enter = fadeIn(tween(400, delayMillis = delay)) + slideInHorizontally( + initialOffsetX = { if (number <= 6) -50 else 50 }, + animationSpec = tween(400, delayMillis = delay) + ) + ) { + WordItem( + number = number, + word = word, + isDarkTheme = isDarkTheme, + modifier = modifier + ) + } +} diff --git a/app/src/main/java/com/rosetta/messenger/ui/auth/SetPasswordScreen.kt b/app/src/main/java/com/rosetta/messenger/ui/auth/SetPasswordScreen.kt index 2b2056e..f89a8a4 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/auth/SetPasswordScreen.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/auth/SetPasswordScreen.kt @@ -54,6 +54,11 @@ fun SetPasswordScreen( var confirmPasswordVisible by remember { mutableStateOf(false) } var isCreating by remember { mutableStateOf(false) } var error by remember { mutableStateOf(null) } + var visible by remember { mutableStateOf(false) } + + LaunchedEffect(Unit) { + visible = true + } val passwordsMatch = password == confirmPassword && password.isNotEmpty() val passwordStrong = password.length >= 6 @@ -104,44 +109,72 @@ fun SetPasswordScreen( Spacer(modifier = Modifier.height(16.dp)) // Lock Icon - Box( - modifier = Modifier - .size(80.dp) - .clip(RoundedCornerShape(20.dp)) - .background(PrimaryBlue.copy(alpha = 0.1f)), - contentAlignment = Alignment.Center - ) { - Icon( - Icons.Default.Lock, - contentDescription = null, - tint = PrimaryBlue, - modifier = Modifier.size(40.dp) + AnimatedVisibility( + visible = visible, + enter = fadeIn(tween(500)) + scaleIn( + initialScale = 0.5f, + animationSpec = tween(500, easing = FastOutSlowInEasing) ) + ) { + Box( + modifier = Modifier + .size(80.dp) + .clip(RoundedCornerShape(20.dp)) + .background(PrimaryBlue.copy(alpha = 0.1f)), + contentAlignment = Alignment.Center + ) { + Icon( + Icons.Default.Lock, + contentDescription = null, + tint = PrimaryBlue, + modifier = Modifier.size(40.dp) + ) + } } Spacer(modifier = Modifier.height(24.dp)) - Text( - text = "Protect Your Account", - fontSize = 24.sp, - fontWeight = FontWeight.Bold, - color = textColor - ) + AnimatedVisibility( + visible = visible, + enter = fadeIn(tween(500, delayMillis = 100)) + slideInVertically( + initialOffsetY = { -20 }, + animationSpec = tween(500, delayMillis = 100) + ) + ) { + Text( + text = "Protect Your Account", + fontSize = 24.sp, + fontWeight = FontWeight.Bold, + color = textColor + ) + } Spacer(modifier = Modifier.height(8.dp)) - Text( - text = "This password encrypts your keys locally.\nYou'll need it to unlock Rosetta.", - fontSize = 14.sp, - color = secondaryTextColor, - textAlign = TextAlign.Center, - lineHeight = 20.sp - ) + AnimatedVisibility( + visible = visible, + enter = fadeIn(tween(500, delayMillis = 200)) + ) { + Text( + text = "This password encrypts your keys locally.\nYou'll need it to unlock Rosetta.", + fontSize = 14.sp, + color = secondaryTextColor, + textAlign = TextAlign.Center, + lineHeight = 20.sp + ) + } Spacer(modifier = Modifier.height(32.dp)) // Password Field - OutlinedTextField( + AnimatedVisibility( + visible = visible, + enter = fadeIn(tween(500, delayMillis = 300)) + slideInVertically( + initialOffsetY = { 50 }, + animationSpec = tween(500, delayMillis = 300) + ) + ) { + OutlinedTextField( value = password, onValueChange = { password = it @@ -176,43 +209,59 @@ fun SetPasswordScreen( imeAction = ImeAction.Next ) ) + } // Password strength indicator if (password.isNotEmpty()) { Spacer(modifier = Modifier.height(8.dp)) - Row( - modifier = Modifier.fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically + AnimatedVisibility( + visible = visible, + enter = fadeIn(tween(400, delayMillis = 350)) + slideInHorizontally( + initialOffsetX = { -30 }, + animationSpec = tween(400, delayMillis = 350) + ) ) { - val strength = when { - password.length < 6 -> "Weak" - password.length < 10 -> "Medium" - else -> "Strong" + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + val strength = when { + password.length < 6 -> "Weak" + password.length < 10 -> "Medium" + else -> "Strong" + } + val strengthColor = when { + password.length < 6 -> Color(0xFFE53935) + password.length < 10 -> Color(0xFFFFA726) + else -> Color(0xFF4CAF50) + } + Icon( + imageVector = Icons.Default.Shield, + contentDescription = null, + tint = strengthColor, + modifier = Modifier.size(16.dp) + ) + Spacer(modifier = Modifier.width(4.dp)) + Text( + text = "Password strength: $strength", + fontSize = 12.sp, + color = strengthColor + ) } - val strengthColor = when { - password.length < 6 -> Color(0xFFE53935) - password.length < 10 -> Color(0xFFFFA726) - else -> Color(0xFF4CAF50) - } - Icon( - imageVector = Icons.Default.Shield, - contentDescription = null, - tint = strengthColor, - modifier = Modifier.size(16.dp) - ) - Spacer(modifier = Modifier.width(4.dp)) - Text( - text = "Password strength: $strength", - fontSize = 12.sp, - color = strengthColor - ) } } Spacer(modifier = Modifier.height(16.dp)) // Confirm Password Field - OutlinedTextField( + AnimatedVisibility( + visible = visible, + enter = fadeIn(tween(500, delayMillis = 400)) + slideInVertically( + initialOffsetY = { 50 }, + animationSpec = tween(500, delayMillis = 400) + ) + ) { + OutlinedTextField( value = confirmPassword, onValueChange = { confirmPassword = it @@ -248,74 +297,100 @@ fun SetPasswordScreen( imeAction = ImeAction.Done ) ) + } // Match indicator if (confirmPassword.isNotEmpty()) { Spacer(modifier = Modifier.height(8.dp)) - Row( - modifier = Modifier.fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically - ) { - val matchIcon = if (passwordsMatch) Icons.Default.Check else Icons.Default.Close - val matchColor = if (passwordsMatch) Color(0xFF4CAF50) else Color(0xFFE53935) - val matchText = if (passwordsMatch) "Passwords match" else "Passwords don't match" - - Icon( - imageVector = matchIcon, - contentDescription = null, - tint = matchColor, - modifier = Modifier.size(16.dp) + AnimatedVisibility( + visible = visible, + enter = fadeIn(tween(400, delayMillis = 450)) + slideInHorizontally( + initialOffsetX = { -30 }, + animationSpec = tween(400, delayMillis = 450) ) - Spacer(modifier = Modifier.width(4.dp)) - Text( - text = matchText, + ) { + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + val matchIcon = if (passwordsMatch) Icons.Default.Check else Icons.Default.Close + val matchColor = if (passwordsMatch) Color(0xFF4CAF50) else Color(0xFFE53935) + val matchText = if (passwordsMatch) "Passwords match" else "Passwords don't match" + + Icon( + imageVector = matchIcon, + contentDescription = null, + tint = matchColor, + modifier = Modifier.size(16.dp) + ) + Spacer(modifier = Modifier.width(4.dp)) + Text( + text = matchText, fontSize = 12.sp, color = matchColor ) } + } } // Error message error?.let { errorMsg -> Spacer(modifier = Modifier.height(16.dp)) - Text( - text = errorMsg, - fontSize = 14.sp, - color = Color(0xFFE53935), - textAlign = TextAlign.Center - ) + AnimatedVisibility( + visible = true, + enter = fadeIn(tween(300)) + scaleIn(initialScale = 0.9f) + ) { + Text( + text = errorMsg, + fontSize = 14.sp, + color = Color(0xFFE53935), + textAlign = TextAlign.Center + ) + } } Spacer(modifier = Modifier.weight(1f)) // Info - Row( - modifier = Modifier - .fillMaxWidth() - .clip(RoundedCornerShape(12.dp)) - .background(cardColor) - .padding(16.dp), - verticalAlignment = Alignment.Top + AnimatedVisibility( + visible = visible, + enter = fadeIn(tween(500, delayMillis = 500)) ) { - Icon( - imageVector = Icons.Default.Info, - contentDescription = null, - tint = PrimaryBlue, - modifier = Modifier.size(20.dp) - ) - Spacer(modifier = Modifier.width(12.dp)) - Text( - text = "Your password is never stored or sent anywhere. It's only used to encrypt your keys locally.", - fontSize = 13.sp, - color = secondaryTextColor, - lineHeight = 18.sp - ) + Row( + modifier = Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(12.dp)) + .background(cardColor) + .padding(16.dp), + verticalAlignment = Alignment.Top + ) { + Icon( + imageVector = Icons.Default.Info, + contentDescription = null, + tint = PrimaryBlue, + modifier = Modifier.size(20.dp) + ) + Spacer(modifier = Modifier.width(12.dp)) + Text( + text = "Your password is never stored or sent anywhere. It's only used to encrypt your keys locally.", + fontSize = 13.sp, + color = secondaryTextColor, + lineHeight = 18.sp + ) + } } Spacer(modifier = Modifier.height(16.dp)) // Create Account Button - Button( + AnimatedVisibility( + visible = visible, + enter = fadeIn(tween(500, delayMillis = 600)) + slideInVertically( + initialOffsetY = { 50 }, + animationSpec = tween(500, delayMillis = 600) + ) + ) { + Button( onClick = { if (!passwordStrong) { error = "Password must be at least 6 characters" @@ -384,8 +459,7 @@ fun SetPasswordScreen( fontWeight = FontWeight.SemiBold ) } - } - + } } Spacer(modifier = Modifier.height(32.dp)) } } diff --git a/app/src/main/java/com/rosetta/messenger/ui/splash/SplashScreen.kt b/app/src/main/java/com/rosetta/messenger/ui/splash/SplashScreen.kt index 0bb83cf..347f189 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/splash/SplashScreen.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/splash/SplashScreen.kt @@ -11,6 +11,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.scale import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp import com.rosetta.messenger.R @@ -25,16 +26,23 @@ fun SplashScreen( // Animation states var startAnimation by remember { mutableStateOf(false) } + var alphaValue by remember { mutableStateOf(0f) } val scale by animateFloatAsState( - targetValue = if (startAnimation) 1f else 0f, + targetValue = if (startAnimation) 1f else 0.3f, animationSpec = spring( - dampingRatio = 0.5f, + dampingRatio = 0.6f, stiffness = Spring.StiffnessLow ), label = "scale" ) + val alpha by animateFloatAsState( + targetValue = alphaValue, + animationSpec = tween(600, easing = FastOutSlowInEasing), + label = "alpha" + ) + val pulseScale by rememberInfiniteTransition(label = "pulse").animateFloat( initialValue = 1f, targetValue = 1.1f, @@ -46,6 +54,8 @@ fun SplashScreen( ) LaunchedEffect(Unit) { + delay(100) + alphaValue = 1f startAnimation = true delay(2000) // Show splash for 2 seconds onSplashComplete() @@ -62,6 +72,7 @@ fun SplashScreen( modifier = Modifier .size(180.dp) .scale(scale * pulseScale) + .graphicsLayer { this.alpha = alpha } .background( color = Color(0xFF54A9EB).copy(alpha = 0.2f), shape = CircleShape @@ -75,6 +86,7 @@ fun SplashScreen( modifier = Modifier .size(150.dp) .scale(scale) + .graphicsLayer { this.alpha = alpha } .clip(CircleShape) ) }