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:
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user