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,6 +105,13 @@ fun ConfirmSeedPhraseScreen(
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
// Info Card // Info Card
AnimatedVisibility(
visible = visible,
enter = fadeIn(tween(500)) + slideInVertically(
initialOffsetY = { -30 },
animationSpec = tween(500)
)
) {
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -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,6 +144,13 @@ fun ConfirmSeedPhraseScreen(
wordsToConfirm[index].second.lowercase() wordsToConfirm[index].second.lowercase()
val hasInput = userInputs[index].isNotBlank() val hasInput = userInputs[index].isNotBlank()
AnimatedVisibility(
visible = visible,
enter = fadeIn(tween(500, delayMillis = 100 + (index * 100))) + slideInVertically(
initialOffsetY = { 50 },
animationSpec = tween(500, delayMillis = 100 + (index * 100))
)
) {
WordInputField( WordInputField(
wordNumber = wordIndex + 1, wordNumber = wordIndex + 1,
value = userInputs[index], value = userInputs[index],
@@ -143,6 +163,7 @@ fun ConfirmSeedPhraseScreen(
isCorrect = if (hasInput) isCorrect else null, isCorrect = if (hasInput) isCorrect else null,
isDarkTheme = isDarkTheme isDarkTheme = isDarkTheme
) )
}
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
} }
@@ -164,6 +185,13 @@ fun ConfirmSeedPhraseScreen(
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
// Continue Button // Continue Button
AnimatedVisibility(
visible = visible,
enter = fadeIn(tween(500, delayMillis = 500)) + slideInVertically(
initialOffsetY = { 50 },
animationSpec = tween(500, delayMillis = 500)
)
) {
Button( Button(
onClick = { onClick = {
if (allCorrect) { if (allCorrect) {
@@ -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,35 +82,55 @@ 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))
AnimatedVisibility(
visible = visible,
enter = fadeIn(tween(500)) + slideInVertically(
initialOffsetY = { -20 },
animationSpec = tween(500)
)
) {
Text( Text(
text = "Import Account", text = "Import Account",
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))
AnimatedVisibility(
visible = visible,
enter = fadeIn(tween(500, delayMillis = 100))
) {
Text( Text(
text = "Enter your 12-word recovery phrase", text = "Enter your 12-word recovery phrase",
fontSize = 15.sp, fontSize = 15.sp,
color = secondaryTextColor, color = secondaryTextColor,
textAlign = TextAlign.Center textAlign = TextAlign.Center
) )
}
Spacer(modifier = Modifier.height(24.dp)) Spacer(modifier = Modifier.height(24.dp))
// Paste button // Paste button
AnimatedVisibility(
visible = visible,
enter = fadeIn(tween(500, delayMillis = 200)) + scaleIn(
initialScale = 0.9f,
animationSpec = tween(500, delayMillis = 200)
)
) {
Button( Button(
onClick = { showPasteDialog = true }, onClick = { showPasteDialog = true },
modifier = Modifier modifier = Modifier
@@ -134,10 +154,15 @@ fun ImportSeedPhraseScreen(
fontWeight = FontWeight.SemiBold fontWeight = FontWeight.SemiBold
) )
} }
}
Spacer(modifier = Modifier.height(24.dp)) Spacer(modifier = Modifier.height(24.dp))
// Clean grid // Clean grid
AnimatedVisibility(
visible = visible,
enter = fadeIn(tween(500, delayMillis = 300))
) {
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -169,10 +194,15 @@ fun ImportSeedPhraseScreen(
} }
} }
} }
}
// Error // Error
if (error != null) { if (error != null) {
Spacer(modifier = Modifier.height(12.dp)) Spacer(modifier = Modifier.height(12.dp))
AnimatedVisibility(
visible = true,
enter = fadeIn(tween(300)) + scaleIn(initialScale = 0.9f)
) {
Text( Text(
text = error ?: "", text = error ?: "",
fontSize = 14.sp, fontSize = 14.sp,
@@ -180,10 +210,18 @@ fun ImportSeedPhraseScreen(
textAlign = TextAlign.Center textAlign = TextAlign.Center
) )
} }
}
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
// Import button // Import button
AnimatedVisibility(
visible = visible,
enter = fadeIn(tween(500, delayMillis = 400)) + slideInVertically(
initialOffsetY = { 50 },
animationSpec = tween(500, delayMillis = 400)
)
) {
Button( 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,10 +66,6 @@ fun SeedPhraseScreen(
} }
} }
AnimatedVisibility(
visible = visible,
enter = fadeIn(tween(300, easing = FastOutSlowInEasing))
) {
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
@@ -78,15 +74,30 @@ fun SeedPhraseScreen(
) { ) {
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
AnimatedVisibility(
visible = visible,
enter = fadeIn(tween(500)) + slideInVertically(
initialOffsetY = { -20 },
animationSpec = tween(500)
)
) {
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))
AnimatedVisibility(
visible = visible,
enter = fadeIn(tween(500, delayMillis = 100)) + slideInVertically(
initialOffsetY = { -20 },
animationSpec = tween(500, delayMillis = 100)
)
) {
Text( Text(
text = "Write down these 12 words in order.\nYou'll need them to restore your account.", text = "Write down these 12 words in order.\nYou'll need them to restore your account.",
fontSize = 15.sp, fontSize = 15.sp,
@@ -94,6 +105,7 @@ fun SeedPhraseScreen(
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
lineHeight = 22.sp lineHeight = 22.sp
) )
}
Spacer(modifier = Modifier.height(32.dp)) Spacer(modifier = Modifier.height(32.dp))
@@ -112,6 +124,10 @@ fun SeedPhraseScreen(
) )
} }
} else { } else {
AnimatedVisibility(
visible = visible,
enter = fadeIn(tween(500, delayMillis = 200))
) {
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(12.dp) horizontalArrangement = Arrangement.spacedBy(12.dp)
@@ -122,10 +138,12 @@ fun SeedPhraseScreen(
verticalArrangement = Arrangement.spacedBy(12.dp) verticalArrangement = Arrangement.spacedBy(12.dp)
) { ) {
for (i in 0..5) { for (i in 0..5) {
WordItem( AnimatedWordItem(
number = i + 1, number = i + 1,
word = seedPhrase[i], word = seedPhrase[i],
isDarkTheme = isDarkTheme isDarkTheme = isDarkTheme,
visible = visible,
delay = 300 + (i * 50)
) )
} }
} }
@@ -136,20 +154,30 @@ fun SeedPhraseScreen(
verticalArrangement = Arrangement.spacedBy(12.dp) verticalArrangement = Arrangement.spacedBy(12.dp)
) { ) {
for (i in 6..11) { for (i in 6..11) {
WordItem( AnimatedWordItem(
number = i + 1, number = i + 1,
word = seedPhrase[i], word = seedPhrase[i],
isDarkTheme = isDarkTheme isDarkTheme = isDarkTheme,
visible = visible,
delay = 300 + (i * 50)
) )
} }
} }
} }
} }
}
Spacer(modifier = Modifier.height(20.dp)) Spacer(modifier = Modifier.height(20.dp))
// Copy button // Copy button
if (!isGenerating) { if (!isGenerating) {
AnimatedVisibility(
visible = visible,
enter = fadeIn(tween(500, delayMillis = 600)) + scaleIn(
initialScale = 0.8f,
animationSpec = tween(500, delayMillis = 600)
)
) {
TextButton( TextButton(
onClick = { onClick = {
hasCopied = true hasCopied = true
@@ -173,10 +201,18 @@ fun SeedPhraseScreen(
) )
} }
} }
}
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
// Continue button // Continue button
AnimatedVisibility(
visible = visible,
enter = fadeIn(tween(500, delayMillis = 700)) + slideInVertically(
initialOffsetY = { 50 },
animationSpec = tween(500, delayMillis = 700)
)
) {
Button( Button(
onClick = { onConfirm(seedPhrase) }, onClick = { onConfirm(seedPhrase) },
enabled = !isGenerating, enabled = !isGenerating,
@@ -197,13 +233,13 @@ fun SeedPhraseScreen(
fontWeight = FontWeight.Medium fontWeight = FontWeight.Medium
) )
} }
}
Spacer(modifier = Modifier.height(40.dp)) Spacer(modifier = Modifier.height(40.dp))
} }
} }
} }
} }
}
@Composable @Composable
private fun WordItem( private fun WordItem(
@@ -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,6 +109,13 @@ fun SetPasswordScreen(
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
// Lock Icon // Lock Icon
AnimatedVisibility(
visible = visible,
enter = fadeIn(tween(500)) + scaleIn(
initialScale = 0.5f,
animationSpec = tween(500, easing = FastOutSlowInEasing)
)
) {
Box( Box(
modifier = Modifier modifier = Modifier
.size(80.dp) .size(80.dp)
@@ -118,18 +130,31 @@ fun SetPasswordScreen(
modifier = Modifier.size(40.dp) modifier = Modifier.size(40.dp)
) )
} }
}
Spacer(modifier = Modifier.height(24.dp)) Spacer(modifier = Modifier.height(24.dp))
AnimatedVisibility(
visible = visible,
enter = fadeIn(tween(500, delayMillis = 100)) + slideInVertically(
initialOffsetY = { -20 },
animationSpec = tween(500, delayMillis = 100)
)
) {
Text( Text(
text = "Protect Your Account", text = "Protect Your Account",
fontSize = 24.sp, fontSize = 24.sp,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
color = textColor color = textColor
) )
}
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
AnimatedVisibility(
visible = visible,
enter = fadeIn(tween(500, delayMillis = 200))
) {
Text( Text(
text = "This password encrypts your keys locally.\nYou'll need it to unlock Rosetta.", text = "This password encrypts your keys locally.\nYou'll need it to unlock Rosetta.",
fontSize = 14.sp, fontSize = 14.sp,
@@ -137,10 +162,18 @@ fun SetPasswordScreen(
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
lineHeight = 20.sp lineHeight = 20.sp
) )
}
Spacer(modifier = Modifier.height(32.dp)) Spacer(modifier = Modifier.height(32.dp))
// Password Field // Password Field
AnimatedVisibility(
visible = visible,
enter = fadeIn(tween(500, delayMillis = 300)) + slideInVertically(
initialOffsetY = { 50 },
animationSpec = tween(500, delayMillis = 300)
)
) {
OutlinedTextField( OutlinedTextField(
value = password, value = password,
onValueChange = { onValueChange = {
@@ -176,10 +209,18 @@ 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))
AnimatedVisibility(
visible = visible,
enter = fadeIn(tween(400, delayMillis = 350)) + slideInHorizontally(
initialOffsetX = { -30 },
animationSpec = tween(400, delayMillis = 350)
)
) {
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
@@ -208,10 +249,18 @@ fun SetPasswordScreen(
) )
} }
} }
}
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
// Confirm Password Field // Confirm Password Field
AnimatedVisibility(
visible = visible,
enter = fadeIn(tween(500, delayMillis = 400)) + slideInVertically(
initialOffsetY = { 50 },
animationSpec = tween(500, delayMillis = 400)
)
) {
OutlinedTextField( OutlinedTextField(
value = confirmPassword, value = confirmPassword,
onValueChange = { onValueChange = {
@@ -248,10 +297,18 @@ 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))
AnimatedVisibility(
visible = visible,
enter = fadeIn(tween(400, delayMillis = 450)) + slideInHorizontally(
initialOffsetX = { -30 },
animationSpec = tween(400, delayMillis = 450)
)
) {
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
@@ -274,10 +331,15 @@ fun SetPasswordScreen(
) )
} }
} }
}
// Error message // Error message
error?.let { errorMsg -> error?.let { errorMsg ->
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
AnimatedVisibility(
visible = true,
enter = fadeIn(tween(300)) + scaleIn(initialScale = 0.9f)
) {
Text( Text(
text = errorMsg, text = errorMsg,
fontSize = 14.sp, fontSize = 14.sp,
@@ -285,10 +347,15 @@ fun SetPasswordScreen(
textAlign = TextAlign.Center textAlign = TextAlign.Center
) )
} }
}
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
// Info // Info
AnimatedVisibility(
visible = visible,
enter = fadeIn(tween(500, delayMillis = 500))
) {
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -311,10 +378,18 @@ fun SetPasswordScreen(
lineHeight = 18.sp lineHeight = 18.sp
) )
} }
}
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
// Create Account Button // Create Account Button
AnimatedVisibility(
visible = visible,
enter = fadeIn(tween(500, delayMillis = 600)) + slideInVertically(
initialOffsetY = { 50 },
animationSpec = tween(500, delayMillis = 600)
)
) {
Button( Button(
onClick = { onClick = {
if (!passwordStrong) { if (!passwordStrong) {
@@ -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)
) )
} }