Enhance UI animations in SetPasswordScreen and SplashScreen

- Added animated visibility for elements in SetPasswordScreen to improve user experience during password setup.
- Introduced fade and scale animations for icons and text in SetPasswordScreen.
- Updated SplashScreen to include alpha animations for smoother transitions.
- Adjusted animation parameters for better performance and visual appeal.
- Addressed deprecation warnings related to Gradle configurations in the project.
This commit is contained in:
2026-01-08 20:04:43 +05:00
parent 42ddfe5b18
commit fc54cc89df
5 changed files with 433 additions and 218 deletions

View File

@@ -50,6 +50,11 @@ fun ConfirmSeedPhraseScreen(
var userInputs by remember { mutableStateOf(List(4) { "" }) } var userInputs by remember { mutableStateOf(List(4) { "" }) }
var showError by remember { mutableStateOf(false) } var showError by remember { mutableStateOf(false) }
var visible by remember { mutableStateOf(false) }
LaunchedEffect(Unit) {
visible = true
}
val allCorrect = wordsToConfirm.mapIndexed { i, (_, word) -> val allCorrect = wordsToConfirm.mapIndexed { i, (_, word) ->
userInputs[i].trim().lowercase() == word.lowercase() userInputs[i].trim().lowercase() == word.lowercase()
@@ -100,7 +105,14 @@ fun ConfirmSeedPhraseScreen(
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
// Info Card // Info Card
Row( AnimatedVisibility(
visible = visible,
enter = fadeIn(tween(500)) + slideInVertically(
initialOffsetY = { -30 },
animationSpec = tween(500)
)
) {
Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.clip(RoundedCornerShape(12.dp)) .clip(RoundedCornerShape(12.dp))
@@ -122,6 +134,7 @@ fun ConfirmSeedPhraseScreen(
lineHeight = 18.sp lineHeight = 18.sp
) )
} }
}
Spacer(modifier = Modifier.height(32.dp)) Spacer(modifier = Modifier.height(32.dp))
@@ -131,18 +144,26 @@ fun ConfirmSeedPhraseScreen(
wordsToConfirm[index].second.lowercase() wordsToConfirm[index].second.lowercase()
val hasInput = userInputs[index].isNotBlank() val hasInput = userInputs[index].isNotBlank()
WordInputField( AnimatedVisibility(
wordNumber = wordIndex + 1, visible = visible,
value = userInputs[index], enter = fadeIn(tween(500, delayMillis = 100 + (index * 100))) + slideInVertically(
onValueChange = { initialOffsetY = { 50 },
userInputs = userInputs.toMutableList().apply { animationSpec = tween(500, delayMillis = 100 + (index * 100))
this[index] = it )
} ) {
showError = false WordInputField(
}, wordNumber = wordIndex + 1,
isCorrect = if (hasInput) isCorrect else null, value = userInputs[index],
isDarkTheme = isDarkTheme onValueChange = {
) userInputs = userInputs.toMutableList().apply {
this[index] = it
}
showError = false
},
isCorrect = if (hasInput) isCorrect else null,
isDarkTheme = isDarkTheme
)
}
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
} }
@@ -164,7 +185,14 @@ fun ConfirmSeedPhraseScreen(
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
// Continue Button // Continue Button
Button( AnimatedVisibility(
visible = visible,
enter = fadeIn(tween(500, delayMillis = 500)) + slideInVertically(
initialOffsetY = { 50 },
animationSpec = tween(500, delayMillis = 500)
)
) {
Button(
onClick = { onClick = {
if (allCorrect) { if (allCorrect) {
onConfirmed() onConfirmed()
@@ -186,6 +214,7 @@ fun ConfirmSeedPhraseScreen(
fontWeight = FontWeight.SemiBold fontWeight = FontWeight.SemiBold
) )
} }
}
Spacer(modifier = Modifier.height(32.dp)) Spacer(modifier = Modifier.height(32.dp))
} }

View File

@@ -82,36 +82,56 @@ fun ImportSeedPhraseScreen(
AnimatedVisibility( AnimatedVisibility(
visible = visible, visible = visible,
enter = fadeIn(tween(600, easing = FastOutSlowInEasing)) enter = fadeIn(tween(500, easing = FastOutSlowInEasing))
) { ) {
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxWidth()
.padding(horizontal = 24.dp), .padding(horizontal = 24.dp),
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
Text( AnimatedVisibility(
text = "Import Account", visible = visible,
fontSize = 28.sp, enter = fadeIn(tween(500)) + slideInVertically(
fontWeight = FontWeight.Bold, initialOffsetY = { -20 },
color = textColor animationSpec = tween(500)
) )
) {
Text(
text = "Import Account",
fontSize = 28.sp,
fontWeight = FontWeight.Bold,
color = textColor
)
}
Spacer(modifier = Modifier.height(12.dp)) Spacer(modifier = Modifier.height(12.dp))
Text( AnimatedVisibility(
text = "Enter your 12-word recovery phrase", visible = visible,
fontSize = 15.sp, enter = fadeIn(tween(500, delayMillis = 100))
color = secondaryTextColor, ) {
textAlign = TextAlign.Center Text(
) text = "Enter your 12-word recovery phrase",
fontSize = 15.sp,
color = secondaryTextColor,
textAlign = TextAlign.Center
)
}
Spacer(modifier = Modifier.height(24.dp)) Spacer(modifier = Modifier.height(24.dp))
// Paste button // 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 }, onClick = { showPasteDialog = true },
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -134,11 +154,16 @@ fun ImportSeedPhraseScreen(
fontWeight = FontWeight.SemiBold fontWeight = FontWeight.SemiBold
) )
} }
}
Spacer(modifier = Modifier.height(24.dp)) Spacer(modifier = Modifier.height(24.dp))
// Clean grid // Clean grid
Column( AnimatedVisibility(
visible = visible,
enter = fadeIn(tween(500, delayMillis = 300))
) {
Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.clip(RoundedCornerShape(12.dp)) .clip(RoundedCornerShape(12.dp))
@@ -169,22 +194,35 @@ fun ImportSeedPhraseScreen(
} }
} }
} }
}
// Error // Error
if (error != null) { if (error != null) {
Spacer(modifier = Modifier.height(12.dp)) Spacer(modifier = Modifier.height(12.dp))
Text( AnimatedVisibility(
text = error ?: "", visible = true,
fontSize = 14.sp, enter = fadeIn(tween(300)) + scaleIn(initialScale = 0.9f)
color = Color(0xFFE53935), ) {
textAlign = TextAlign.Center Text(
) text = error ?: "",
fontSize = 14.sp,
color = Color(0xFFE53935),
textAlign = TextAlign.Center
)
}
} }
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
// Import button // Import button
Button( AnimatedVisibility(
visible = visible,
enter = fadeIn(tween(500, delayMillis = 400)) + slideInVertically(
initialOffsetY = { 50 },
animationSpec = tween(500, delayMillis = 400)
)
) {
Button(
onClick = { onClick = {
val seedPhrase = words.map { it.trim() } val seedPhrase = words.map { it.trim() }
@@ -213,6 +251,7 @@ fun ImportSeedPhraseScreen(
shape = RoundedCornerShape(12.dp) shape = RoundedCornerShape(12.dp)
) { ) {
Text("Continue", fontSize = 17.sp, fontWeight = FontWeight.Medium) Text("Continue", fontSize = 17.sp, fontWeight = FontWeight.Medium)
}
} }
Spacer(modifier = Modifier.height(40.dp)) Spacer(modifier = Modifier.height(40.dp))

View File

@@ -66,34 +66,46 @@ fun SeedPhraseScreen(
} }
} }
AnimatedVisibility( Column(
visible = visible, modifier = Modifier
enter = fadeIn(tween(300, easing = FastOutSlowInEasing)) .fillMaxSize()
.padding(horizontal = 24.dp),
horizontalAlignment = Alignment.CenterHorizontally
) { ) {
Column( Spacer(modifier = Modifier.height(16.dp))
modifier = Modifier
.fillMaxSize() AnimatedVisibility(
.padding(horizontal = 24.dp), visible = visible,
horizontalAlignment = Alignment.CenterHorizontally enter = fadeIn(tween(500)) + slideInVertically(
initialOffsetY = { -20 },
animationSpec = tween(500)
)
) { ) {
Spacer(modifier = Modifier.height(16.dp))
Text( Text(
text = "Your Recovery Phrase", text = "Your Recovery Phrase",
fontSize = 28.sp, fontSize = 28.sp,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
color = textColor color = textColor
) )
}
Spacer(modifier = Modifier.height(12.dp)) Spacer(modifier = Modifier.height(12.dp))
Text( AnimatedVisibility(
text = "Write down these 12 words in order.\nYou'll need them to restore your account.", visible = visible,
fontSize = 15.sp, enter = fadeIn(tween(500, delayMillis = 100)) + slideInVertically(
color = secondaryTextColor, initialOffsetY = { -20 },
textAlign = TextAlign.Center, animationSpec = tween(500, delayMillis = 100)
lineHeight = 22.sp )
) ) {
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)) Spacer(modifier = Modifier.height(32.dp))
@@ -112,35 +124,44 @@ fun SeedPhraseScreen(
) )
} }
} else { } else {
Row( AnimatedVisibility(
modifier = Modifier.fillMaxWidth(), visible = visible,
horizontalArrangement = Arrangement.spacedBy(12.dp) enter = fadeIn(tween(500, delayMillis = 200))
) { ) {
// Left column (words 1-6) Row(
Column( modifier = Modifier.fillMaxWidth(),
modifier = Modifier.weight(1f), horizontalArrangement = Arrangement.spacedBy(12.dp)
verticalArrangement = Arrangement.spacedBy(12.dp)
) { ) {
for (i in 0..5) { // Left column (words 1-6)
WordItem( Column(
number = i + 1, modifier = Modifier.weight(1f),
word = seedPhrase[i], verticalArrangement = Arrangement.spacedBy(12.dp)
isDarkTheme = isDarkTheme ) {
) 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)
// Right column (words 7-12) Column(
Column( modifier = Modifier.weight(1f),
modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(12.dp)
verticalArrangement = Arrangement.spacedBy(12.dp) ) {
) { for (i in 6..11) {
for (i in 6..11) { AnimatedWordItem(
WordItem( number = i + 1,
number = i + 1, word = seedPhrase[i],
word = seedPhrase[i], isDarkTheme = isDarkTheme,
isDarkTheme = isDarkTheme visible = visible,
) delay = 300 + (i * 50)
)
}
} }
} }
} }
@@ -150,56 +171,71 @@ fun SeedPhraseScreen(
// Copy button // Copy button
if (!isGenerating) { if (!isGenerating) {
TextButton( AnimatedVisibility(
onClick = { visible = visible,
hasCopied = true enter = fadeIn(tween(500, delayMillis = 600)) + scaleIn(
scope.launch { initialScale = 0.8f,
delay(2000) animationSpec = tween(500, delayMillis = 600)
hasCopied = false )
}
}
) { ) {
Icon( TextButton(
imageVector = if (hasCopied) Icons.Default.Check else Icons.Default.ContentCopy, onClick = {
contentDescription = null, hasCopied = true
tint = if (hasCopied) Color(0xFF4CAF50) else PrimaryBlue, scope.launch {
modifier = Modifier.size(20.dp) delay(2000)
) hasCopied = false
Spacer(modifier = Modifier.width(8.dp)) }
Text( }
text = if (hasCopied) "Copied" else "Copy to clipboard", ) {
color = if (hasCopied) Color(0xFF4CAF50) else PrimaryBlue, Icon(
fontSize = 15.sp 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)) Spacer(modifier = Modifier.weight(1f))
// Continue button // Continue button
Button( AnimatedVisibility(
onClick = { onConfirm(seedPhrase) }, visible = visible,
enabled = !isGenerating, enter = fadeIn(tween(500, delayMillis = 700)) + slideInVertically(
modifier = Modifier initialOffsetY = { 50 },
.fillMaxWidth() animationSpec = tween(500, delayMillis = 700)
.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
) )
) {
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
)
}
}

View File

@@ -54,6 +54,11 @@ fun SetPasswordScreen(
var confirmPasswordVisible by remember { mutableStateOf(false) } var confirmPasswordVisible by remember { mutableStateOf(false) }
var isCreating by remember { mutableStateOf(false) } var isCreating by remember { mutableStateOf(false) }
var error by remember { mutableStateOf<String?>(null) } var error by remember { mutableStateOf<String?>(null) }
var visible by remember { mutableStateOf(false) }
LaunchedEffect(Unit) {
visible = true
}
val passwordsMatch = password == confirmPassword && password.isNotEmpty() val passwordsMatch = password == confirmPassword && password.isNotEmpty()
val passwordStrong = password.length >= 6 val passwordStrong = password.length >= 6
@@ -104,44 +109,72 @@ fun SetPasswordScreen(
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
// Lock Icon // Lock Icon
Box( AnimatedVisibility(
modifier = Modifier visible = visible,
.size(80.dp) enter = fadeIn(tween(500)) + scaleIn(
.clip(RoundedCornerShape(20.dp)) initialScale = 0.5f,
.background(PrimaryBlue.copy(alpha = 0.1f)), animationSpec = tween(500, easing = FastOutSlowInEasing)
contentAlignment = Alignment.Center
) {
Icon(
Icons.Default.Lock,
contentDescription = null,
tint = PrimaryBlue,
modifier = Modifier.size(40.dp)
) )
) {
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)) Spacer(modifier = Modifier.height(24.dp))
Text( AnimatedVisibility(
text = "Protect Your Account", visible = visible,
fontSize = 24.sp, enter = fadeIn(tween(500, delayMillis = 100)) + slideInVertically(
fontWeight = FontWeight.Bold, initialOffsetY = { -20 },
color = textColor animationSpec = tween(500, delayMillis = 100)
) )
) {
Text(
text = "Protect Your Account",
fontSize = 24.sp,
fontWeight = FontWeight.Bold,
color = textColor
)
}
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
Text( AnimatedVisibility(
text = "This password encrypts your keys locally.\nYou'll need it to unlock Rosetta.", visible = visible,
fontSize = 14.sp, enter = fadeIn(tween(500, delayMillis = 200))
color = secondaryTextColor, ) {
textAlign = TextAlign.Center, Text(
lineHeight = 20.sp 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)) Spacer(modifier = Modifier.height(32.dp))
// Password Field // Password Field
OutlinedTextField( AnimatedVisibility(
visible = visible,
enter = fadeIn(tween(500, delayMillis = 300)) + slideInVertically(
initialOffsetY = { 50 },
animationSpec = tween(500, delayMillis = 300)
)
) {
OutlinedTextField(
value = password, value = password,
onValueChange = { onValueChange = {
password = it password = it
@@ -176,43 +209,59 @@ fun SetPasswordScreen(
imeAction = ImeAction.Next imeAction = ImeAction.Next
) )
) )
}
// Password strength indicator // Password strength indicator
if (password.isNotEmpty()) { if (password.isNotEmpty()) {
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
Row( AnimatedVisibility(
modifier = Modifier.fillMaxWidth(), visible = visible,
verticalAlignment = Alignment.CenterVertically enter = fadeIn(tween(400, delayMillis = 350)) + slideInHorizontally(
initialOffsetX = { -30 },
animationSpec = tween(400, delayMillis = 350)
)
) { ) {
val strength = when { Row(
password.length < 6 -> "Weak" modifier = Modifier.fillMaxWidth(),
password.length < 10 -> "Medium" verticalAlignment = Alignment.CenterVertically
else -> "Strong" ) {
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)) Spacer(modifier = Modifier.height(16.dp))
// Confirm Password Field // Confirm Password Field
OutlinedTextField( AnimatedVisibility(
visible = visible,
enter = fadeIn(tween(500, delayMillis = 400)) + slideInVertically(
initialOffsetY = { 50 },
animationSpec = tween(500, delayMillis = 400)
)
) {
OutlinedTextField(
value = confirmPassword, value = confirmPassword,
onValueChange = { onValueChange = {
confirmPassword = it confirmPassword = it
@@ -248,74 +297,100 @@ fun SetPasswordScreen(
imeAction = ImeAction.Done imeAction = ImeAction.Done
) )
) )
}
// Match indicator // Match indicator
if (confirmPassword.isNotEmpty()) { if (confirmPassword.isNotEmpty()) {
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
Row( AnimatedVisibility(
modifier = Modifier.fillMaxWidth(), visible = visible,
verticalAlignment = Alignment.CenterVertically enter = fadeIn(tween(400, delayMillis = 450)) + slideInHorizontally(
) { initialOffsetX = { -30 },
val matchIcon = if (passwordsMatch) Icons.Default.Check else Icons.Default.Close animationSpec = tween(400, delayMillis = 450)
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( Row(
text = matchText, 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, fontSize = 12.sp,
color = matchColor color = matchColor
) )
} }
}
} }
// Error message // Error message
error?.let { errorMsg -> error?.let { errorMsg ->
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
Text( AnimatedVisibility(
text = errorMsg, visible = true,
fontSize = 14.sp, enter = fadeIn(tween(300)) + scaleIn(initialScale = 0.9f)
color = Color(0xFFE53935), ) {
textAlign = TextAlign.Center Text(
) text = errorMsg,
fontSize = 14.sp,
color = Color(0xFFE53935),
textAlign = TextAlign.Center
)
}
} }
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
// Info // Info
Row( AnimatedVisibility(
modifier = Modifier visible = visible,
.fillMaxWidth() enter = fadeIn(tween(500, delayMillis = 500))
.clip(RoundedCornerShape(12.dp))
.background(cardColor)
.padding(16.dp),
verticalAlignment = Alignment.Top
) { ) {
Icon( Row(
imageVector = Icons.Default.Info, modifier = Modifier
contentDescription = null, .fillMaxWidth()
tint = PrimaryBlue, .clip(RoundedCornerShape(12.dp))
modifier = Modifier.size(20.dp) .background(cardColor)
) .padding(16.dp),
Spacer(modifier = Modifier.width(12.dp)) verticalAlignment = Alignment.Top
Text( ) {
text = "Your password is never stored or sent anywhere. It's only used to encrypt your keys locally.", Icon(
fontSize = 13.sp, imageVector = Icons.Default.Info,
color = secondaryTextColor, contentDescription = null,
lineHeight = 18.sp 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)) Spacer(modifier = Modifier.height(16.dp))
// Create Account Button // Create Account Button
Button( AnimatedVisibility(
visible = visible,
enter = fadeIn(tween(500, delayMillis = 600)) + slideInVertically(
initialOffsetY = { 50 },
animationSpec = tween(500, delayMillis = 600)
)
) {
Button(
onClick = { onClick = {
if (!passwordStrong) { if (!passwordStrong) {
error = "Password must be at least 6 characters" error = "Password must be at least 6 characters"
@@ -384,8 +459,7 @@ fun SetPasswordScreen(
fontWeight = FontWeight.SemiBold fontWeight = FontWeight.SemiBold
) )
} }
} } }
Spacer(modifier = Modifier.height(32.dp)) Spacer(modifier = Modifier.height(32.dp))
} }
} }

View File

@@ -11,6 +11,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.scale import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.rosetta.messenger.R import com.rosetta.messenger.R
@@ -25,16 +26,23 @@ fun SplashScreen(
// Animation states // Animation states
var startAnimation by remember { mutableStateOf(false) } var startAnimation by remember { mutableStateOf(false) }
var alphaValue by remember { mutableStateOf(0f) }
val scale by animateFloatAsState( val scale by animateFloatAsState(
targetValue = if (startAnimation) 1f else 0f, targetValue = if (startAnimation) 1f else 0.3f,
animationSpec = spring( animationSpec = spring(
dampingRatio = 0.5f, dampingRatio = 0.6f,
stiffness = Spring.StiffnessLow stiffness = Spring.StiffnessLow
), ),
label = "scale" label = "scale"
) )
val alpha by animateFloatAsState(
targetValue = alphaValue,
animationSpec = tween(600, easing = FastOutSlowInEasing),
label = "alpha"
)
val pulseScale by rememberInfiniteTransition(label = "pulse").animateFloat( val pulseScale by rememberInfiniteTransition(label = "pulse").animateFloat(
initialValue = 1f, initialValue = 1f,
targetValue = 1.1f, targetValue = 1.1f,
@@ -46,6 +54,8 @@ fun SplashScreen(
) )
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
delay(100)
alphaValue = 1f
startAnimation = true startAnimation = true
delay(2000) // Show splash for 2 seconds delay(2000) // Show splash for 2 seconds
onSplashComplete() onSplashComplete()
@@ -62,6 +72,7 @@ fun SplashScreen(
modifier = Modifier modifier = Modifier
.size(180.dp) .size(180.dp)
.scale(scale * pulseScale) .scale(scale * pulseScale)
.graphicsLayer { this.alpha = alpha }
.background( .background(
color = Color(0xFF54A9EB).copy(alpha = 0.2f), color = Color(0xFF54A9EB).copy(alpha = 0.2f),
shape = CircleShape shape = CircleShape
@@ -75,6 +86,7 @@ fun SplashScreen(
modifier = Modifier modifier = Modifier
.size(150.dp) .size(150.dp)
.scale(scale) .scale(scale)
.graphicsLayer { this.alpha = alpha }
.clip(CircleShape) .clip(CircleShape)
) )
} }