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:
k1ngsterr1
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 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,6 +105,13 @@ fun ConfirmSeedPhraseScreen(
Spacer(modifier = Modifier.height(16.dp))
// Info Card
AnimatedVisibility(
visible = visible,
enter = fadeIn(tween(500)) + slideInVertically(
initialOffsetY = { -30 },
animationSpec = tween(500)
)
) {
Row(
modifier = Modifier
.fillMaxWidth()
@@ -122,6 +134,7 @@ fun ConfirmSeedPhraseScreen(
lineHeight = 18.sp
)
}
}
Spacer(modifier = Modifier.height(32.dp))
@@ -131,6 +144,13 @@ fun ConfirmSeedPhraseScreen(
wordsToConfirm[index].second.lowercase()
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(
wordNumber = wordIndex + 1,
value = userInputs[index],
@@ -143,6 +163,7 @@ fun ConfirmSeedPhraseScreen(
isCorrect = if (hasInput) isCorrect else null,
isDarkTheme = isDarkTheme
)
}
Spacer(modifier = Modifier.height(16.dp))
}
@@ -164,6 +185,13 @@ fun ConfirmSeedPhraseScreen(
Spacer(modifier = Modifier.weight(1f))
// Continue Button
AnimatedVisibility(
visible = visible,
enter = fadeIn(tween(500, delayMillis = 500)) + slideInVertically(
initialOffsetY = { 50 },
animationSpec = tween(500, delayMillis = 500)
)
) {
Button(
onClick = {
if (allCorrect) {
@@ -186,6 +214,7 @@ fun ConfirmSeedPhraseScreen(
fontWeight = FontWeight.SemiBold
)
}
}
Spacer(modifier = Modifier.height(32.dp))
}

View File

@@ -82,35 +82,55 @@ 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))
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))
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
AnimatedVisibility(
visible = visible,
enter = fadeIn(tween(500, delayMillis = 200)) + scaleIn(
initialScale = 0.9f,
animationSpec = tween(500, delayMillis = 200)
)
) {
Button(
onClick = { showPasteDialog = true },
modifier = Modifier
@@ -134,10 +154,15 @@ fun ImportSeedPhraseScreen(
fontWeight = FontWeight.SemiBold
)
}
}
Spacer(modifier = Modifier.height(24.dp))
// Clean grid
AnimatedVisibility(
visible = visible,
enter = fadeIn(tween(500, delayMillis = 300))
) {
Column(
modifier = Modifier
.fillMaxWidth()
@@ -169,10 +194,15 @@ fun ImportSeedPhraseScreen(
}
}
}
}
// Error
if (error != null) {
Spacer(modifier = Modifier.height(12.dp))
AnimatedVisibility(
visible = true,
enter = fadeIn(tween(300)) + scaleIn(initialScale = 0.9f)
) {
Text(
text = error ?: "",
fontSize = 14.sp,
@@ -180,10 +210,18 @@ fun ImportSeedPhraseScreen(
textAlign = TextAlign.Center
)
}
}
Spacer(modifier = Modifier.weight(1f))
// Import 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))

View File

@@ -66,10 +66,6 @@ fun SeedPhraseScreen(
}
}
AnimatedVisibility(
visible = visible,
enter = fadeIn(tween(300, easing = FastOutSlowInEasing))
) {
Column(
modifier = Modifier
.fillMaxSize()
@@ -78,15 +74,30 @@ fun SeedPhraseScreen(
) {
Spacer(modifier = Modifier.height(16.dp))
AnimatedVisibility(
visible = visible,
enter = fadeIn(tween(500)) + slideInVertically(
initialOffsetY = { -20 },
animationSpec = tween(500)
)
) {
Text(
text = "Your Recovery Phrase",
fontSize = 28.sp,
fontWeight = FontWeight.Bold,
color = textColor
)
}
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 = "Write down these 12 words in order.\nYou'll need them to restore your account.",
fontSize = 15.sp,
@@ -94,6 +105,7 @@ fun SeedPhraseScreen(
textAlign = TextAlign.Center,
lineHeight = 22.sp
)
}
Spacer(modifier = Modifier.height(32.dp))
@@ -112,6 +124,10 @@ fun SeedPhraseScreen(
)
}
} else {
AnimatedVisibility(
visible = visible,
enter = fadeIn(tween(500, delayMillis = 200))
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(12.dp)
@@ -122,10 +138,12 @@ fun SeedPhraseScreen(
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
for (i in 0..5) {
WordItem(
AnimatedWordItem(
number = i + 1,
word = seedPhrase[i],
isDarkTheme = isDarkTheme
isDarkTheme = isDarkTheme,
visible = visible,
delay = 300 + (i * 50)
)
}
}
@@ -136,20 +154,30 @@ fun SeedPhraseScreen(
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
for (i in 6..11) {
WordItem(
AnimatedWordItem(
number = i + 1,
word = seedPhrase[i],
isDarkTheme = isDarkTheme
isDarkTheme = isDarkTheme,
visible = visible,
delay = 300 + (i * 50)
)
}
}
}
}
}
Spacer(modifier = Modifier.height(20.dp))
// Copy button
if (!isGenerating) {
AnimatedVisibility(
visible = visible,
enter = fadeIn(tween(500, delayMillis = 600)) + scaleIn(
initialScale = 0.8f,
animationSpec = tween(500, delayMillis = 600)
)
) {
TextButton(
onClick = {
hasCopied = true
@@ -173,10 +201,18 @@ fun SeedPhraseScreen(
)
}
}
}
Spacer(modifier = Modifier.weight(1f))
// Continue button
AnimatedVisibility(
visible = visible,
enter = fadeIn(tween(500, delayMillis = 700)) + slideInVertically(
initialOffsetY = { 50 },
animationSpec = tween(500, delayMillis = 700)
)
) {
Button(
onClick = { onConfirm(seedPhrase) },
enabled = !isGenerating,
@@ -197,13 +233,13 @@ fun SeedPhraseScreen(
fontWeight = FontWeight.Medium
)
}
}
Spacer(modifier = Modifier.height(40.dp))
}
}
}
}
}
@Composable
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 isCreating by remember { mutableStateOf(false) }
var error by remember { mutableStateOf<String?>(null) }
var visible by remember { mutableStateOf(false) }
LaunchedEffect(Unit) {
visible = true
}
val passwordsMatch = password == confirmPassword && password.isNotEmpty()
val passwordStrong = password.length >= 6
@@ -104,6 +109,13 @@ fun SetPasswordScreen(
Spacer(modifier = Modifier.height(16.dp))
// Lock Icon
AnimatedVisibility(
visible = visible,
enter = fadeIn(tween(500)) + scaleIn(
initialScale = 0.5f,
animationSpec = tween(500, easing = FastOutSlowInEasing)
)
) {
Box(
modifier = Modifier
.size(80.dp)
@@ -118,18 +130,31 @@ fun SetPasswordScreen(
modifier = Modifier.size(40.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 = "Protect Your Account",
fontSize = 24.sp,
fontWeight = FontWeight.Bold,
color = textColor
)
}
Spacer(modifier = Modifier.height(8.dp))
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,
@@ -137,10 +162,18 @@ fun SetPasswordScreen(
textAlign = TextAlign.Center,
lineHeight = 20.sp
)
}
Spacer(modifier = Modifier.height(32.dp))
// Password Field
AnimatedVisibility(
visible = visible,
enter = fadeIn(tween(500, delayMillis = 300)) + slideInVertically(
initialOffsetY = { 50 },
animationSpec = tween(500, delayMillis = 300)
)
) {
OutlinedTextField(
value = password,
onValueChange = {
@@ -176,10 +209,18 @@ fun SetPasswordScreen(
imeAction = ImeAction.Next
)
)
}
// Password strength indicator
if (password.isNotEmpty()) {
Spacer(modifier = Modifier.height(8.dp))
AnimatedVisibility(
visible = visible,
enter = fadeIn(tween(400, delayMillis = 350)) + slideInHorizontally(
initialOffsetX = { -30 },
animationSpec = tween(400, delayMillis = 350)
)
) {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
@@ -208,10 +249,18 @@ fun SetPasswordScreen(
)
}
}
}
Spacer(modifier = Modifier.height(16.dp))
// Confirm Password Field
AnimatedVisibility(
visible = visible,
enter = fadeIn(tween(500, delayMillis = 400)) + slideInVertically(
initialOffsetY = { 50 },
animationSpec = tween(500, delayMillis = 400)
)
) {
OutlinedTextField(
value = confirmPassword,
onValueChange = {
@@ -248,10 +297,18 @@ fun SetPasswordScreen(
imeAction = ImeAction.Done
)
)
}
// Match indicator
if (confirmPassword.isNotEmpty()) {
Spacer(modifier = Modifier.height(8.dp))
AnimatedVisibility(
visible = visible,
enter = fadeIn(tween(400, delayMillis = 450)) + slideInHorizontally(
initialOffsetX = { -30 },
animationSpec = tween(400, delayMillis = 450)
)
) {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
@@ -274,10 +331,15 @@ fun SetPasswordScreen(
)
}
}
}
// Error message
error?.let { errorMsg ->
Spacer(modifier = Modifier.height(16.dp))
AnimatedVisibility(
visible = true,
enter = fadeIn(tween(300)) + scaleIn(initialScale = 0.9f)
) {
Text(
text = errorMsg,
fontSize = 14.sp,
@@ -285,10 +347,15 @@ fun SetPasswordScreen(
textAlign = TextAlign.Center
)
}
}
Spacer(modifier = Modifier.weight(1f))
// Info
AnimatedVisibility(
visible = visible,
enter = fadeIn(tween(500, delayMillis = 500))
) {
Row(
modifier = Modifier
.fillMaxWidth()
@@ -311,10 +378,18 @@ fun SetPasswordScreen(
lineHeight = 18.sp
)
}
}
Spacer(modifier = Modifier.height(16.dp))
// Create Account Button
AnimatedVisibility(
visible = visible,
enter = fadeIn(tween(500, delayMillis = 600)) + slideInVertically(
initialOffsetY = { 50 },
animationSpec = tween(500, delayMillis = 600)
)
) {
Button(
onClick = {
if (!passwordStrong) {
@@ -384,8 +459,7 @@ fun SetPasswordScreen(
fontWeight = FontWeight.SemiBold
)
}
}
} }
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.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)
)
}