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