Refactor UI components in ChatsListScreen, ForwardChatPickerBottomSheet, and SearchScreen for improved readability and maintainability; adjust text color alpha values, streamline imports, and enhance keyboard handling functionality.
This commit is contained in:
@@ -5,7 +5,6 @@ import androidx.compose.animation.core.*
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.*
|
||||
@@ -35,21 +34,38 @@ import kotlinx.coroutines.launch
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun SetPasswordScreen(
|
||||
seedPhrase: List<String>,
|
||||
isDarkTheme: Boolean,
|
||||
onBack: () -> Unit,
|
||||
onAccountCreated: (DecryptedAccount) -> Unit
|
||||
seedPhrase: List<String>,
|
||||
isDarkTheme: Boolean,
|
||||
onBack: () -> Unit,
|
||||
onAccountCreated: (DecryptedAccount) -> Unit
|
||||
) {
|
||||
val themeAnimSpec = tween<Color>(durationMillis = 500, easing = CubicBezierEasing(0.4f, 0f, 0.2f, 1f))
|
||||
val backgroundColor by animateColorAsState(if (isDarkTheme) AuthBackground else AuthBackgroundLight, animationSpec = themeAnimSpec)
|
||||
val textColor by animateColorAsState(if (isDarkTheme) Color.White else Color.Black, animationSpec = themeAnimSpec)
|
||||
val secondaryTextColor by animateColorAsState(if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666), animationSpec = themeAnimSpec)
|
||||
val cardColor by animateColorAsState(if (isDarkTheme) AuthSurface else AuthSurfaceLight, animationSpec = themeAnimSpec)
|
||||
|
||||
val themeAnimSpec =
|
||||
tween<Color>(durationMillis = 500, easing = CubicBezierEasing(0.4f, 0f, 0.2f, 1f))
|
||||
val backgroundColor by
|
||||
animateColorAsState(
|
||||
if (isDarkTheme) AuthBackground else AuthBackgroundLight,
|
||||
animationSpec = themeAnimSpec
|
||||
)
|
||||
val textColor by
|
||||
animateColorAsState(
|
||||
if (isDarkTheme) Color.White else Color.Black,
|
||||
animationSpec = themeAnimSpec
|
||||
)
|
||||
val secondaryTextColor by
|
||||
animateColorAsState(
|
||||
if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666),
|
||||
animationSpec = themeAnimSpec
|
||||
)
|
||||
val cardColor by
|
||||
animateColorAsState(
|
||||
if (isDarkTheme) AuthSurface else AuthSurfaceLight,
|
||||
animationSpec = themeAnimSpec
|
||||
)
|
||||
|
||||
val context = LocalContext.current
|
||||
val accountManager = remember { AccountManager(context) }
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
|
||||
var password by remember { mutableStateOf("") }
|
||||
var confirmPassword by remember { mutableStateOf("") }
|
||||
var passwordVisible by remember { mutableStateOf(false) }
|
||||
@@ -57,492 +73,540 @@ fun SetPasswordScreen(
|
||||
var isCreating by remember { mutableStateOf(false) }
|
||||
var error by remember { mutableStateOf<String?>(null) }
|
||||
var visible by remember { mutableStateOf(false) }
|
||||
|
||||
|
||||
// Track keyboard visibility
|
||||
val view = androidx.compose.ui.platform.LocalView.current
|
||||
var isKeyboardVisible by remember { mutableStateOf(false) }
|
||||
|
||||
|
||||
DisposableEffect(view) {
|
||||
val listener = android.view.ViewTreeObserver.OnGlobalLayoutListener {
|
||||
val rect = android.graphics.Rect()
|
||||
view.getWindowVisibleDisplayFrame(rect)
|
||||
val screenHeight = view.rootView.height
|
||||
val keypadHeight = screenHeight - rect.bottom
|
||||
isKeyboardVisible = keypadHeight > screenHeight * 0.15
|
||||
}
|
||||
val listener =
|
||||
android.view.ViewTreeObserver.OnGlobalLayoutListener {
|
||||
val rect = android.graphics.Rect()
|
||||
view.getWindowVisibleDisplayFrame(rect)
|
||||
val screenHeight = view.rootView.height
|
||||
val keypadHeight = screenHeight - rect.bottom
|
||||
isKeyboardVisible = keypadHeight > screenHeight * 0.15
|
||||
}
|
||||
view.viewTreeObserver.addOnGlobalLayoutListener(listener)
|
||||
onDispose {
|
||||
view.viewTreeObserver.removeOnGlobalLayoutListener(listener)
|
||||
}
|
||||
onDispose { view.viewTreeObserver.removeOnGlobalLayoutListener(listener) }
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
visible = true
|
||||
}
|
||||
|
||||
|
||||
LaunchedEffect(Unit) { visible = true }
|
||||
|
||||
val passwordsMatch = password == confirmPassword && password.isNotEmpty()
|
||||
val isPasswordWeak = password.isNotEmpty() && password.length < 6
|
||||
val canContinue = passwordsMatch && !isCreating
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(backgroundColor)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.statusBarsPadding()
|
||||
) {
|
||||
|
||||
Box(modifier = Modifier.fillMaxSize().background(backgroundColor)) {
|
||||
Column(modifier = Modifier.fillMaxSize().statusBarsPadding()) {
|
||||
// Top Bar
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 8.dp, vertical = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp, vertical = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
IconButton(onClick = onBack, enabled = !isCreating) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.ArrowBack,
|
||||
contentDescription = "Back",
|
||||
tint = textColor.copy(alpha = 0.6f)
|
||||
imageVector = Icons.Default.ArrowBack,
|
||||
contentDescription = "Back",
|
||||
tint = textColor.copy(alpha = 0.6f)
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
Text(
|
||||
text = "Set Password",
|
||||
fontSize = 18.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = textColor
|
||||
text = "Set Password",
|
||||
fontSize = 18.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = textColor
|
||||
)
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
Spacer(modifier = Modifier.width(48.dp))
|
||||
}
|
||||
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.imePadding()
|
||||
.padding(horizontal = 24.dp)
|
||||
.verticalScroll(rememberScrollState()),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
modifier =
|
||||
Modifier.fillMaxSize()
|
||||
.imePadding()
|
||||
.padding(horizontal = 24.dp)
|
||||
.verticalScroll(rememberScrollState()),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Spacer(modifier = Modifier.height(if (isKeyboardVisible) 8.dp else 16.dp))
|
||||
|
||||
|
||||
// Lock Icon - smaller when keyboard is visible
|
||||
val iconSize by animateDpAsState(
|
||||
targetValue = if (isKeyboardVisible) 48.dp else 80.dp,
|
||||
animationSpec = tween(300, easing = FastOutSlowInEasing)
|
||||
)
|
||||
val iconInnerSize by animateDpAsState(
|
||||
targetValue = if (isKeyboardVisible) 24.dp else 40.dp,
|
||||
animationSpec = tween(300, easing = FastOutSlowInEasing)
|
||||
)
|
||||
|
||||
val iconSize by
|
||||
animateDpAsState(
|
||||
targetValue = if (isKeyboardVisible) 48.dp else 80.dp,
|
||||
animationSpec = tween(300, easing = FastOutSlowInEasing)
|
||||
)
|
||||
val iconInnerSize by
|
||||
animateDpAsState(
|
||||
targetValue = if (isKeyboardVisible) 24.dp else 40.dp,
|
||||
animationSpec = tween(300, easing = FastOutSlowInEasing)
|
||||
)
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = visible,
|
||||
enter = fadeIn(tween(500)) + scaleIn(
|
||||
initialScale = 0.5f,
|
||||
animationSpec = tween(500, easing = FastOutSlowInEasing)
|
||||
)
|
||||
visible = visible,
|
||||
enter =
|
||||
fadeIn(tween(500)) +
|
||||
scaleIn(
|
||||
initialScale = 0.5f,
|
||||
animationSpec =
|
||||
tween(500, easing = FastOutSlowInEasing)
|
||||
)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(iconSize)
|
||||
.clip(RoundedCornerShape(if (isKeyboardVisible) 12.dp else 20.dp))
|
||||
.background(PrimaryBlue.copy(alpha = 0.1f)),
|
||||
contentAlignment = Alignment.Center
|
||||
modifier =
|
||||
Modifier.size(iconSize)
|
||||
.clip(
|
||||
RoundedCornerShape(
|
||||
if (isKeyboardVisible) 12.dp else 20.dp
|
||||
)
|
||||
)
|
||||
.background(PrimaryBlue.copy(alpha = 0.1f)),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.Lock,
|
||||
contentDescription = null,
|
||||
tint = PrimaryBlue,
|
||||
modifier = Modifier.size(iconInnerSize)
|
||||
Icons.Default.Lock,
|
||||
contentDescription = null,
|
||||
tint = PrimaryBlue,
|
||||
modifier = Modifier.size(iconInnerSize)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Spacer(modifier = Modifier.height(if (isKeyboardVisible) 12.dp else 24.dp))
|
||||
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = visible,
|
||||
enter = fadeIn(tween(500, delayMillis = 100)) + slideInVertically(
|
||||
initialOffsetY = { -20 },
|
||||
animationSpec = tween(500, delayMillis = 100)
|
||||
)
|
||||
visible = visible,
|
||||
enter =
|
||||
fadeIn(tween(500, delayMillis = 100)) +
|
||||
slideInVertically(
|
||||
initialOffsetY = { -20 },
|
||||
animationSpec = tween(500, delayMillis = 100)
|
||||
)
|
||||
) {
|
||||
Text(
|
||||
text = "Protect Your Account",
|
||||
fontSize = if (isKeyboardVisible) 20.sp else 24.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = textColor
|
||||
text = "Protect Your Account",
|
||||
fontSize = if (isKeyboardVisible) 20.sp else 24.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = textColor
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Spacer(modifier = Modifier.height(if (isKeyboardVisible) 6.dp else 8.dp))
|
||||
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = visible,
|
||||
enter = fadeIn(tween(500, delayMillis = 200))
|
||||
visible = visible,
|
||||
enter = fadeIn(tween(500, delayMillis = 200))
|
||||
) {
|
||||
Text(
|
||||
text = "This password encrypts your keys locally.\nYou'll need it to unlock Rosetta.",
|
||||
fontSize = if (isKeyboardVisible) 12.sp else 14.sp,
|
||||
color = secondaryTextColor,
|
||||
textAlign = TextAlign.Center,
|
||||
lineHeight = if (isKeyboardVisible) 16.sp else 20.sp
|
||||
text =
|
||||
"This password encrypts your keys locally.\nYou'll need it to unlock Rosetta.",
|
||||
fontSize = if (isKeyboardVisible) 12.sp else 14.sp,
|
||||
color = secondaryTextColor,
|
||||
textAlign = TextAlign.Center,
|
||||
lineHeight = if (isKeyboardVisible) 16.sp else 20.sp
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Spacer(modifier = Modifier.height(if (isKeyboardVisible) 16.dp else 32.dp))
|
||||
|
||||
|
||||
// Password Field
|
||||
AnimatedVisibility(
|
||||
visible = visible,
|
||||
enter = fadeIn(tween(500, delayMillis = 300)) + slideInVertically(
|
||||
initialOffsetY = { 50 },
|
||||
animationSpec = tween(500, delayMillis = 300)
|
||||
)
|
||||
visible = visible,
|
||||
enter =
|
||||
fadeIn(tween(500, delayMillis = 300)) +
|
||||
slideInVertically(
|
||||
initialOffsetY = { 50 },
|
||||
animationSpec = tween(500, delayMillis = 300)
|
||||
)
|
||||
) {
|
||||
OutlinedTextField(
|
||||
value = password,
|
||||
onValueChange = {
|
||||
password = it
|
||||
error = null
|
||||
},
|
||||
label = { Text("Password") },
|
||||
placeholder = { Text("Enter password") },
|
||||
singleLine = true,
|
||||
visualTransformation = if (passwordVisible)
|
||||
VisualTransformation.None else PasswordVisualTransformation(),
|
||||
trailingIcon = {
|
||||
IconButton(onClick = { passwordVisible = !passwordVisible }) {
|
||||
Icon(
|
||||
imageVector = if (passwordVisible)
|
||||
Icons.Default.VisibilityOff else Icons.Default.Visibility,
|
||||
contentDescription = if (passwordVisible) "Hide" else "Show"
|
||||
)
|
||||
}
|
||||
},
|
||||
colors = OutlinedTextFieldDefaults.colors(
|
||||
focusedBorderColor = PrimaryBlue,
|
||||
unfocusedBorderColor = if (isDarkTheme) Color(0xFF4A4A4A) else Color(0xFFD0D0D0),
|
||||
focusedLabelColor = PrimaryBlue,
|
||||
cursorColor = PrimaryBlue,
|
||||
focusedTextColor = textColor,
|
||||
unfocusedTextColor = textColor
|
||||
),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
keyboardOptions = KeyboardOptions(
|
||||
keyboardType = KeyboardType.Password,
|
||||
imeAction = ImeAction.Next
|
||||
value = password,
|
||||
onValueChange = {
|
||||
password = it
|
||||
error = null
|
||||
},
|
||||
label = { Text("Password") },
|
||||
placeholder = { Text("Enter password") },
|
||||
singleLine = true,
|
||||
visualTransformation =
|
||||
if (passwordVisible) VisualTransformation.None
|
||||
else PasswordVisualTransformation(),
|
||||
trailingIcon = {
|
||||
IconButton(onClick = { passwordVisible = !passwordVisible }) {
|
||||
Icon(
|
||||
imageVector =
|
||||
if (passwordVisible) Icons.Default.VisibilityOff
|
||||
else Icons.Default.Visibility,
|
||||
contentDescription =
|
||||
if (passwordVisible) "Hide" else "Show"
|
||||
)
|
||||
}
|
||||
},
|
||||
colors =
|
||||
OutlinedTextFieldDefaults.colors(
|
||||
focusedBorderColor = PrimaryBlue,
|
||||
unfocusedBorderColor =
|
||||
if (isDarkTheme) Color(0xFF4A4A4A)
|
||||
else Color(0xFFD0D0D0),
|
||||
focusedLabelColor = PrimaryBlue,
|
||||
cursorColor = PrimaryBlue,
|
||||
focusedTextColor = textColor,
|
||||
unfocusedTextColor = textColor
|
||||
),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
keyboardOptions =
|
||||
KeyboardOptions(
|
||||
keyboardType = KeyboardType.Password,
|
||||
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)
|
||||
)
|
||||
visible = visible,
|
||||
enter =
|
||||
fadeIn(tween(400, delayMillis = 350)) +
|
||||
slideInHorizontally(
|
||||
initialOffsetX = { -30 },
|
||||
animationSpec = tween(400, delayMillis = 350)
|
||||
)
|
||||
) {
|
||||
Column(modifier = Modifier.fillMaxWidth()) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
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)
|
||||
}
|
||||
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)
|
||||
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
|
||||
text = "Password strength: $strength",
|
||||
fontSize = 12.sp,
|
||||
color = strengthColor
|
||||
)
|
||||
}
|
||||
// Warning for weak passwords
|
||||
if (isPasswordWeak) {
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.background(Color(0xFFE53935).copy(alpha = 0.1f))
|
||||
.padding(8.dp),
|
||||
verticalAlignment = Alignment.Top
|
||||
modifier =
|
||||
Modifier.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.background(
|
||||
Color(0xFFE53935).copy(alpha = 0.1f)
|
||||
)
|
||||
.padding(8.dp),
|
||||
verticalAlignment = Alignment.Top
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Warning,
|
||||
contentDescription = null,
|
||||
tint = Color(0xFFE53935),
|
||||
modifier = Modifier.size(16.dp)
|
||||
imageVector = Icons.Default.Warning,
|
||||
contentDescription = null,
|
||||
tint = Color(0xFFE53935),
|
||||
modifier = Modifier.size(16.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text(
|
||||
text = "Your password is too weak. Consider using at least 6 characters for better security.",
|
||||
fontSize = 11.sp,
|
||||
color = Color(0xFFE53935),
|
||||
lineHeight = 14.sp
|
||||
text =
|
||||
"Your password is too weak. Consider using at least 6 characters for better security.",
|
||||
fontSize = 11.sp,
|
||||
color = Color(0xFFE53935),
|
||||
lineHeight = 14.sp
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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)
|
||||
)
|
||||
visible = visible,
|
||||
enter =
|
||||
fadeIn(tween(500, delayMillis = 400)) +
|
||||
slideInVertically(
|
||||
initialOffsetY = { 50 },
|
||||
animationSpec = tween(500, delayMillis = 400)
|
||||
)
|
||||
) {
|
||||
OutlinedTextField(
|
||||
value = confirmPassword,
|
||||
onValueChange = {
|
||||
confirmPassword = it
|
||||
error = null
|
||||
},
|
||||
label = { Text("Confirm Password") },
|
||||
placeholder = { Text("Re-enter password") },
|
||||
singleLine = true,
|
||||
visualTransformation = if (confirmPasswordVisible)
|
||||
VisualTransformation.None else PasswordVisualTransformation(),
|
||||
trailingIcon = {
|
||||
IconButton(onClick = { confirmPasswordVisible = !confirmPasswordVisible }) {
|
||||
Icon(
|
||||
imageVector = if (confirmPasswordVisible)
|
||||
Icons.Default.VisibilityOff else Icons.Default.Visibility,
|
||||
contentDescription = if (confirmPasswordVisible) "Hide" else "Show"
|
||||
)
|
||||
}
|
||||
},
|
||||
isError = confirmPassword.isNotEmpty() && !passwordsMatch,
|
||||
colors = OutlinedTextFieldDefaults.colors(
|
||||
focusedBorderColor = PrimaryBlue,
|
||||
unfocusedBorderColor = if (isDarkTheme) Color(0xFF4A4A4A) else Color(0xFFD0D0D0),
|
||||
focusedLabelColor = PrimaryBlue,
|
||||
cursorColor = PrimaryBlue,
|
||||
focusedTextColor = textColor,
|
||||
unfocusedTextColor = textColor
|
||||
),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
keyboardOptions = KeyboardOptions(
|
||||
keyboardType = KeyboardType.Password,
|
||||
imeAction = ImeAction.Done
|
||||
value = confirmPassword,
|
||||
onValueChange = {
|
||||
confirmPassword = it
|
||||
error = null
|
||||
},
|
||||
label = { Text("Confirm Password") },
|
||||
placeholder = { Text("Re-enter password") },
|
||||
singleLine = true,
|
||||
visualTransformation =
|
||||
if (confirmPasswordVisible) VisualTransformation.None
|
||||
else PasswordVisualTransformation(),
|
||||
trailingIcon = {
|
||||
IconButton(
|
||||
onClick = {
|
||||
confirmPasswordVisible = !confirmPasswordVisible
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
imageVector =
|
||||
if (confirmPasswordVisible)
|
||||
Icons.Default.VisibilityOff
|
||||
else Icons.Default.Visibility,
|
||||
contentDescription =
|
||||
if (confirmPasswordVisible) "Hide" else "Show"
|
||||
)
|
||||
}
|
||||
},
|
||||
isError = confirmPassword.isNotEmpty() && !passwordsMatch,
|
||||
colors =
|
||||
OutlinedTextFieldDefaults.colors(
|
||||
focusedBorderColor = PrimaryBlue,
|
||||
unfocusedBorderColor =
|
||||
if (isDarkTheme) Color(0xFF4A4A4A)
|
||||
else Color(0xFFD0D0D0),
|
||||
focusedLabelColor = PrimaryBlue,
|
||||
cursorColor = PrimaryBlue,
|
||||
focusedTextColor = textColor,
|
||||
unfocusedTextColor = textColor
|
||||
),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
keyboardOptions =
|
||||
KeyboardOptions(
|
||||
keyboardType = KeyboardType.Password,
|
||||
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)
|
||||
)
|
||||
visible = visible,
|
||||
enter =
|
||||
fadeIn(tween(400, delayMillis = 450)) +
|
||||
slideInHorizontally(
|
||||
initialOffsetX = { -30 },
|
||||
animationSpec = tween(400, delayMillis = 450)
|
||||
)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
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"
|
||||
|
||||
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)
|
||||
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
|
||||
)
|
||||
}
|
||||
Text(text = matchText, fontSize = 12.sp, color = matchColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Error message
|
||||
error?.let { errorMsg ->
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
AnimatedVisibility(
|
||||
visible = true,
|
||||
enter = fadeIn(tween(300)) + scaleIn(initialScale = 0.9f)
|
||||
visible = true,
|
||||
enter = fadeIn(tween(300)) + scaleIn(initialScale = 0.9f)
|
||||
) {
|
||||
Text(
|
||||
text = errorMsg,
|
||||
fontSize = 14.sp,
|
||||
color = Color(0xFFE53935),
|
||||
textAlign = TextAlign.Center
|
||||
text = errorMsg,
|
||||
fontSize = 14.sp,
|
||||
color = Color(0xFFE53935),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
|
||||
|
||||
// Info - hide when keyboard is visible
|
||||
AnimatedVisibility(
|
||||
visible = visible && !isKeyboardVisible,
|
||||
enter = fadeIn(tween(400)) + slideInVertically(
|
||||
initialOffsetY = { 30 },
|
||||
animationSpec = tween(400)
|
||||
) + scaleIn(
|
||||
initialScale = 0.9f,
|
||||
animationSpec = tween(400)
|
||||
),
|
||||
exit = fadeOut(tween(300)) + slideOutVertically(
|
||||
targetOffsetY = { 30 },
|
||||
animationSpec = tween(300)
|
||||
) + scaleOut(
|
||||
targetScale = 0.9f,
|
||||
animationSpec = tween(300)
|
||||
)
|
||||
visible = visible && !isKeyboardVisible,
|
||||
enter =
|
||||
fadeIn(tween(400)) +
|
||||
slideInVertically(
|
||||
initialOffsetY = { 30 },
|
||||
animationSpec = tween(400)
|
||||
) +
|
||||
scaleIn(initialScale = 0.9f, animationSpec = tween(400)),
|
||||
exit =
|
||||
fadeOut(tween(300)) +
|
||||
slideOutVertically(
|
||||
targetOffsetY = { 30 },
|
||||
animationSpec = tween(300)
|
||||
) +
|
||||
scaleOut(targetScale = 0.9f, animationSpec = tween(300))
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
.background(cardColor)
|
||||
.padding(16.dp),
|
||||
verticalAlignment = Alignment.Top
|
||||
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)
|
||||
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
|
||||
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
|
||||
AnimatedVisibility(
|
||||
visible = visible,
|
||||
enter = fadeIn(tween(500, delayMillis = 600)) + slideInVertically(
|
||||
initialOffsetY = { 50 },
|
||||
animationSpec = tween(500, delayMillis = 600)
|
||||
)
|
||||
visible = visible,
|
||||
enter =
|
||||
fadeIn(tween(500, delayMillis = 600)) +
|
||||
slideInVertically(
|
||||
initialOffsetY = { 50 },
|
||||
animationSpec = tween(500, delayMillis = 600)
|
||||
)
|
||||
) {
|
||||
Button(
|
||||
onClick = {
|
||||
if (!passwordsMatch) {
|
||||
error = "Passwords don't match"
|
||||
return@Button
|
||||
onClick = {
|
||||
if (!passwordsMatch) {
|
||||
error = "Passwords don't match"
|
||||
return@Button
|
||||
}
|
||||
|
||||
isCreating = true
|
||||
scope.launch {
|
||||
try {
|
||||
// Generate keys from seed phrase
|
||||
val keyPair =
|
||||
CryptoManager.generateKeyPairFromSeed(seedPhrase)
|
||||
|
||||
// Encrypt private key and seed phrase
|
||||
val encryptedPrivateKey =
|
||||
CryptoManager.encryptWithPassword(
|
||||
keyPair.privateKey,
|
||||
password
|
||||
)
|
||||
val encryptedSeedPhrase =
|
||||
CryptoManager.encryptWithPassword(
|
||||
seedPhrase.joinToString(" "),
|
||||
password
|
||||
)
|
||||
|
||||
// Save account with truncated public key as name
|
||||
val truncatedKey =
|
||||
"${keyPair.publicKey.take(6)}...${keyPair.publicKey.takeLast(4)}"
|
||||
val account =
|
||||
EncryptedAccount(
|
||||
publicKey = keyPair.publicKey,
|
||||
encryptedPrivateKey = encryptedPrivateKey,
|
||||
encryptedSeedPhrase = encryptedSeedPhrase,
|
||||
name = truncatedKey
|
||||
)
|
||||
|
||||
accountManager.saveAccount(account)
|
||||
accountManager.setCurrentAccount(keyPair.publicKey)
|
||||
|
||||
// 🔌 Connect to server and authenticate
|
||||
val privateKeyHash =
|
||||
CryptoManager.generatePrivateKeyHash(
|
||||
keyPair.privateKey
|
||||
)
|
||||
ProtocolManager.connect()
|
||||
// Give WebSocket time to connect before authenticating
|
||||
kotlinx.coroutines.delay(500)
|
||||
ProtocolManager.authenticate(
|
||||
keyPair.publicKey,
|
||||
privateKeyHash
|
||||
)
|
||||
|
||||
// Create DecryptedAccount to pass to callback
|
||||
val decryptedAccount =
|
||||
DecryptedAccount(
|
||||
publicKey = keyPair.publicKey,
|
||||
privateKey = keyPair.privateKey,
|
||||
seedPhrase = seedPhrase,
|
||||
privateKeyHash = privateKeyHash,
|
||||
name = truncatedKey
|
||||
)
|
||||
|
||||
onAccountCreated(decryptedAccount)
|
||||
} catch (e: Exception) {
|
||||
error = "Failed to create account: ${e.message}"
|
||||
isCreating = false
|
||||
}
|
||||
}
|
||||
},
|
||||
enabled = canContinue,
|
||||
modifier = Modifier.fillMaxWidth().height(56.dp),
|
||||
colors =
|
||||
ButtonDefaults.buttonColors(
|
||||
containerColor = PrimaryBlue,
|
||||
contentColor = Color.White,
|
||||
disabledContainerColor = PrimaryBlue.copy(alpha = 0.5f),
|
||||
disabledContentColor = Color.White.copy(alpha = 0.5f)
|
||||
),
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
) {
|
||||
if (isCreating) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.size(24.dp),
|
||||
color = Color.White,
|
||||
strokeWidth = 2.dp
|
||||
)
|
||||
} else {
|
||||
Text(
|
||||
text = "Create Account",
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
}
|
||||
|
||||
isCreating = true
|
||||
scope.launch {
|
||||
try {
|
||||
// Generate keys from seed phrase
|
||||
val keyPair = CryptoManager.generateKeyPairFromSeed(seedPhrase)
|
||||
|
||||
// Encrypt private key and seed phrase
|
||||
val encryptedPrivateKey = CryptoManager.encryptWithPassword(
|
||||
keyPair.privateKey, password
|
||||
)
|
||||
val encryptedSeedPhrase = CryptoManager.encryptWithPassword(
|
||||
seedPhrase.joinToString(" "), password
|
||||
)
|
||||
|
||||
// Save account with truncated public key as name
|
||||
val truncatedKey = "${keyPair.publicKey.take(6)}...${keyPair.publicKey.takeLast(4)}"
|
||||
val account = EncryptedAccount(
|
||||
publicKey = keyPair.publicKey,
|
||||
encryptedPrivateKey = encryptedPrivateKey,
|
||||
encryptedSeedPhrase = encryptedSeedPhrase,
|
||||
name = truncatedKey
|
||||
)
|
||||
|
||||
accountManager.saveAccount(account)
|
||||
accountManager.setCurrentAccount(keyPair.publicKey)
|
||||
|
||||
// 🔌 Connect to server and authenticate
|
||||
val privateKeyHash = CryptoManager.generatePrivateKeyHash(keyPair.privateKey)
|
||||
ProtocolManager.connect()
|
||||
// Give WebSocket time to connect before authenticating
|
||||
kotlinx.coroutines.delay(500)
|
||||
ProtocolManager.authenticate(keyPair.publicKey, privateKeyHash)
|
||||
|
||||
// Create DecryptedAccount to pass to callback
|
||||
val decryptedAccount = DecryptedAccount(
|
||||
publicKey = keyPair.publicKey,
|
||||
privateKey = keyPair.privateKey,
|
||||
seedPhrase = seedPhrase,
|
||||
privateKeyHash = privateKeyHash,
|
||||
name = truncatedKey
|
||||
)
|
||||
|
||||
onAccountCreated(decryptedAccount)
|
||||
} catch (e: Exception) {
|
||||
error = "Failed to create account: ${e.message}"
|
||||
isCreating = false
|
||||
}
|
||||
}
|
||||
},
|
||||
enabled = canContinue,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(56.dp),
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = PrimaryBlue,
|
||||
contentColor = Color.White,
|
||||
disabledContainerColor = PrimaryBlue.copy(alpha = 0.5f),
|
||||
disabledContentColor = Color.White.copy(alpha = 0.5f)
|
||||
),
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
) {
|
||||
if (isCreating) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.size(24.dp),
|
||||
color = Color.White,
|
||||
strokeWidth = 2.dp
|
||||
)
|
||||
} else {
|
||||
Text(
|
||||
text = "Create Account",
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
}
|
||||
} }
|
||||
}
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,6 @@ package com.rosetta.messenger.ui.auth
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.animation.core.*
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
@@ -14,7 +13,6 @@ import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
@@ -24,7 +22,6 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.airbnb.lottie.compose.*
|
||||
import com.rosetta.messenger.ui.onboarding.PrimaryBlue
|
||||
import com.rosetta.messenger.ui.onboarding.PrimaryBlue
|
||||
|
||||
// Auth colors
|
||||
val AuthBackground = Color(0xFF1B1B1B)
|
||||
@@ -34,229 +31,232 @@ val AuthSurfaceLight = Color(0xFFF5F5F5)
|
||||
|
||||
@Composable
|
||||
fun WelcomeScreen(
|
||||
isDarkTheme: Boolean,
|
||||
hasExistingAccount: Boolean = false,
|
||||
onBack: () -> Unit = {},
|
||||
onCreateSeed: () -> Unit,
|
||||
onImportSeed: () -> Unit
|
||||
isDarkTheme: Boolean,
|
||||
hasExistingAccount: Boolean = false,
|
||||
onBack: () -> Unit = {},
|
||||
onCreateSeed: () -> Unit,
|
||||
onImportSeed: () -> Unit
|
||||
) {
|
||||
val themeAnimSpec = tween<Color>(durationMillis = 500, easing = CubicBezierEasing(0.4f, 0f, 0.2f, 1f))
|
||||
val backgroundColor by animateColorAsState(if (isDarkTheme) AuthBackground else AuthBackgroundLight, animationSpec = themeAnimSpec)
|
||||
val textColor by animateColorAsState(if (isDarkTheme) Color.White else Color.Black, animationSpec = themeAnimSpec)
|
||||
val secondaryTextColor by animateColorAsState(if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666), animationSpec = themeAnimSpec)
|
||||
|
||||
val themeAnimSpec =
|
||||
tween<Color>(durationMillis = 500, easing = CubicBezierEasing(0.4f, 0f, 0.2f, 1f))
|
||||
val backgroundColor by
|
||||
animateColorAsState(
|
||||
if (isDarkTheme) AuthBackground else AuthBackgroundLight,
|
||||
animationSpec = themeAnimSpec
|
||||
)
|
||||
val textColor by
|
||||
animateColorAsState(
|
||||
if (isDarkTheme) Color.White else Color.Black,
|
||||
animationSpec = themeAnimSpec
|
||||
)
|
||||
val secondaryTextColor by
|
||||
animateColorAsState(
|
||||
if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666),
|
||||
animationSpec = themeAnimSpec
|
||||
)
|
||||
|
||||
// Sync navigation bar color with background
|
||||
val view = LocalView.current
|
||||
SideEffect {
|
||||
val window = (view.context as? android.app.Activity)?.window
|
||||
window?.navigationBarColor = backgroundColor.toArgb()
|
||||
}
|
||||
|
||||
|
||||
// Animation for Lottie
|
||||
val lockComposition by rememberLottieComposition(LottieCompositionSpec.Asset("lottie/lock.json"))
|
||||
val lockProgress by animateLottieCompositionAsState(
|
||||
composition = lockComposition,
|
||||
iterations = 1, // Play once
|
||||
speed = 1f
|
||||
)
|
||||
|
||||
val lockComposition by
|
||||
rememberLottieComposition(LottieCompositionSpec.Asset("lottie/lock.json"))
|
||||
val lockProgress by
|
||||
animateLottieCompositionAsState(
|
||||
composition = lockComposition,
|
||||
iterations = 1, // Play once
|
||||
speed = 1f
|
||||
)
|
||||
|
||||
// Entry animation
|
||||
var visible by remember { mutableStateOf(false) }
|
||||
LaunchedEffect(Unit) { visible = true }
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(backgroundColor)
|
||||
) {
|
||||
|
||||
Box(modifier = Modifier.fillMaxSize().background(backgroundColor)) {
|
||||
// Back button when coming from UnlockScreen
|
||||
if (hasExistingAccount) {
|
||||
IconButton(
|
||||
onClick = onBack,
|
||||
modifier = Modifier
|
||||
.statusBarsPadding()
|
||||
.padding(4.dp)
|
||||
) {
|
||||
IconButton(onClick = onBack, modifier = Modifier.statusBarsPadding().padding(4.dp)) {
|
||||
Icon(
|
||||
Icons.Default.ArrowBack,
|
||||
contentDescription = "Back",
|
||||
tint = textColor.copy(alpha = 0.6f)
|
||||
Icons.Default.ArrowBack,
|
||||
contentDescription = "Back",
|
||||
tint = textColor.copy(alpha = 0.6f)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = 24.dp)
|
||||
.statusBarsPadding(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
modifier = Modifier.fillMaxSize().padding(horizontal = 24.dp).statusBarsPadding(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Spacer(modifier = Modifier.weight(0.15f))
|
||||
|
||||
|
||||
// Animated Lock Icon
|
||||
AnimatedVisibility(
|
||||
visible = visible,
|
||||
enter = fadeIn(tween(600)) + scaleIn(tween(600, easing = FastOutSlowInEasing))
|
||||
visible = visible,
|
||||
enter = fadeIn(tween(600)) + scaleIn(tween(600, easing = FastOutSlowInEasing))
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier.size(180.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Box(modifier = Modifier.size(180.dp), contentAlignment = Alignment.Center) {
|
||||
lockComposition?.let { comp ->
|
||||
LottieAnimation(
|
||||
composition = comp,
|
||||
progress = { lockProgress },
|
||||
modifier = Modifier.fillMaxSize()
|
||||
composition = comp,
|
||||
progress = { lockProgress },
|
||||
modifier = Modifier.fillMaxSize()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
|
||||
|
||||
// Title
|
||||
AnimatedVisibility(
|
||||
visible = visible,
|
||||
enter = fadeIn(tween(600, delayMillis = 200)) + slideInVertically(
|
||||
initialOffsetY = { 50 },
|
||||
animationSpec = tween(600, delayMillis = 200)
|
||||
)
|
||||
visible = visible,
|
||||
enter =
|
||||
fadeIn(tween(600, delayMillis = 200)) +
|
||||
slideInVertically(
|
||||
initialOffsetY = { 50 },
|
||||
animationSpec = tween(600, delayMillis = 200)
|
||||
)
|
||||
) {
|
||||
Text(
|
||||
text = "Your Keys,\nYour Messages",
|
||||
fontSize = 32.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = textColor,
|
||||
textAlign = TextAlign.Center,
|
||||
lineHeight = 40.sp
|
||||
text = "Your Keys,\nYour Messages",
|
||||
fontSize = 32.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = textColor,
|
||||
textAlign = TextAlign.Center,
|
||||
lineHeight = 40.sp
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
|
||||
// Subtitle
|
||||
AnimatedVisibility(
|
||||
visible = visible,
|
||||
enter = fadeIn(tween(600, delayMillis = 300)) + slideInVertically(
|
||||
initialOffsetY = { 50 },
|
||||
animationSpec = tween(600, delayMillis = 300)
|
||||
)
|
||||
visible = visible,
|
||||
enter =
|
||||
fadeIn(tween(600, delayMillis = 300)) +
|
||||
slideInVertically(
|
||||
initialOffsetY = { 50 },
|
||||
animationSpec = tween(600, delayMillis = 300)
|
||||
)
|
||||
) {
|
||||
Text(
|
||||
text = "Secure messaging with\ncryptographic keys",
|
||||
fontSize = 16.sp,
|
||||
color = secondaryTextColor,
|
||||
textAlign = TextAlign.Center,
|
||||
lineHeight = 24.sp,
|
||||
modifier = Modifier.padding(horizontal = 8.dp)
|
||||
text = "Secure messaging with\ncryptographic keys",
|
||||
fontSize = 16.sp,
|
||||
color = secondaryTextColor,
|
||||
textAlign = TextAlign.Center,
|
||||
lineHeight = 24.sp,
|
||||
modifier = Modifier.padding(horizontal = 8.dp)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
|
||||
// Features list with icons - placed above buttons
|
||||
AnimatedVisibility(
|
||||
visible = visible,
|
||||
enter = fadeIn(tween(600, delayMillis = 400))
|
||||
) {
|
||||
AnimatedVisibility(visible = visible, enter = fadeIn(tween(600, delayMillis = 400))) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceEvenly
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceEvenly
|
||||
) {
|
||||
CompactFeatureItem(
|
||||
icon = Icons.Default.Security,
|
||||
text = "Encrypted",
|
||||
isDarkTheme = isDarkTheme,
|
||||
textColor = textColor
|
||||
icon = Icons.Default.Security,
|
||||
text = "Encrypted",
|
||||
isDarkTheme = isDarkTheme,
|
||||
textColor = textColor
|
||||
)
|
||||
CompactFeatureItem(
|
||||
icon = Icons.Default.NoAccounts,
|
||||
text = "No Phone",
|
||||
isDarkTheme = isDarkTheme,
|
||||
textColor = textColor
|
||||
icon = Icons.Default.NoAccounts,
|
||||
text = "No Phone",
|
||||
isDarkTheme = isDarkTheme,
|
||||
textColor = textColor
|
||||
)
|
||||
CompactFeatureItem(
|
||||
icon = Icons.Default.Key,
|
||||
text = "Your Keys",
|
||||
isDarkTheme = isDarkTheme,
|
||||
textColor = textColor
|
||||
icon = Icons.Default.Key,
|
||||
text = "Your Keys",
|
||||
isDarkTheme = isDarkTheme,
|
||||
textColor = textColor
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
|
||||
|
||||
// Create Seed Button
|
||||
AnimatedVisibility(
|
||||
visible = visible,
|
||||
enter = fadeIn(tween(600, delayMillis = 500)) + slideInVertically(
|
||||
initialOffsetY = { 100 },
|
||||
animationSpec = tween(600, delayMillis = 500)
|
||||
)
|
||||
visible = visible,
|
||||
enter =
|
||||
fadeIn(tween(600, delayMillis = 500)) +
|
||||
slideInVertically(
|
||||
initialOffsetY = { 100 },
|
||||
animationSpec = tween(600, delayMillis = 500)
|
||||
)
|
||||
) {
|
||||
Button(
|
||||
onClick = onCreateSeed,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(56.dp),
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = PrimaryBlue,
|
||||
contentColor = Color.White
|
||||
),
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
elevation = ButtonDefaults.buttonElevation(
|
||||
defaultElevation = 0.dp,
|
||||
pressedElevation = 0.dp
|
||||
)
|
||||
onClick = onCreateSeed,
|
||||
modifier = Modifier.fillMaxWidth().height(56.dp),
|
||||
colors =
|
||||
ButtonDefaults.buttonColors(
|
||||
containerColor = PrimaryBlue,
|
||||
contentColor = Color.White
|
||||
),
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
elevation =
|
||||
ButtonDefaults.buttonElevation(
|
||||
defaultElevation = 0.dp,
|
||||
pressedElevation = 0.dp
|
||||
)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Key,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(22.dp)
|
||||
imageVector = Icons.Default.Key,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(22.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(12.dp))
|
||||
Text(
|
||||
text = "Generate New Seed Phrase",
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
text = "Generate New Seed Phrase",
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
|
||||
|
||||
// Import Seed Button
|
||||
AnimatedVisibility(
|
||||
visible = visible,
|
||||
enter = fadeIn(tween(600, delayMillis = 600)) + slideInVertically(
|
||||
initialOffsetY = { 100 },
|
||||
animationSpec = tween(600, delayMillis = 600)
|
||||
)
|
||||
visible = visible,
|
||||
enter =
|
||||
fadeIn(tween(600, delayMillis = 600)) +
|
||||
slideInVertically(
|
||||
initialOffsetY = { 100 },
|
||||
animationSpec = tween(600, delayMillis = 600)
|
||||
)
|
||||
) {
|
||||
TextButton(
|
||||
onClick = onImportSeed,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(56.dp),
|
||||
shape = RoundedCornerShape(16.dp)
|
||||
onClick = onImportSeed,
|
||||
modifier = Modifier.fillMaxWidth().height(56.dp),
|
||||
shape = RoundedCornerShape(16.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Download,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(20.dp),
|
||||
tint = PrimaryBlue
|
||||
imageVector = Icons.Default.Download,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(20.dp),
|
||||
tint = PrimaryBlue
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text(
|
||||
text = "I Already Have a Seed Phrase",
|
||||
fontSize = 15.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = PrimaryBlue
|
||||
text = "I Already Have a Seed Phrase",
|
||||
fontSize = 15.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = PrimaryBlue
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Spacer(modifier = Modifier.weight(0.15f))
|
||||
}
|
||||
}
|
||||
@@ -264,70 +264,62 @@ fun WelcomeScreen(
|
||||
|
||||
@Composable
|
||||
private fun CompactFeatureItem(
|
||||
icon: androidx.compose.ui.graphics.vector.ImageVector,
|
||||
text: String,
|
||||
isDarkTheme: Boolean,
|
||||
textColor: Color
|
||||
icon: androidx.compose.ui.graphics.vector.ImageVector,
|
||||
text: String,
|
||||
isDarkTheme: Boolean,
|
||||
textColor: Color
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(48.dp)
|
||||
.clip(CircleShape)
|
||||
.background(PrimaryBlue.copy(alpha = 0.12f)),
|
||||
contentAlignment = Alignment.Center
|
||||
modifier =
|
||||
Modifier.size(48.dp)
|
||||
.clip(CircleShape)
|
||||
.background(PrimaryBlue.copy(alpha = 0.12f)),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = null,
|
||||
tint = PrimaryBlue,
|
||||
modifier = Modifier.size(24.dp)
|
||||
imageVector = icon,
|
||||
contentDescription = null,
|
||||
tint = PrimaryBlue,
|
||||
modifier = Modifier.size(24.dp)
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = text,
|
||||
fontSize = 13.sp,
|
||||
color = textColor.copy(alpha = 0.8f),
|
||||
fontWeight = FontWeight.Medium,
|
||||
textAlign = TextAlign.Center
|
||||
text = text,
|
||||
fontSize = 13.sp,
|
||||
color = textColor.copy(alpha = 0.8f),
|
||||
fontWeight = FontWeight.Medium,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun FeatureItem(
|
||||
icon: androidx.compose.ui.graphics.vector.ImageVector,
|
||||
text: String,
|
||||
isDarkTheme: Boolean,
|
||||
textColor: Color
|
||||
icon: androidx.compose.ui.graphics.vector.ImageVector,
|
||||
text: String,
|
||||
isDarkTheme: Boolean,
|
||||
textColor: Color
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(40.dp)
|
||||
.clip(CircleShape)
|
||||
.background(PrimaryBlue.copy(alpha = 0.15f)),
|
||||
contentAlignment = Alignment.Center
|
||||
modifier =
|
||||
Modifier.size(40.dp)
|
||||
.clip(CircleShape)
|
||||
.background(PrimaryBlue.copy(alpha = 0.15f)),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = null,
|
||||
tint = PrimaryBlue,
|
||||
modifier = Modifier.size(20.dp)
|
||||
imageVector = icon,
|
||||
contentDescription = null,
|
||||
tint = PrimaryBlue,
|
||||
modifier = Modifier.size(20.dp)
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
Text(
|
||||
text = text,
|
||||
fontSize = 15.sp,
|
||||
color = textColor,
|
||||
fontWeight = FontWeight.Medium
|
||||
)
|
||||
Text(text = text, fontSize = 15.sp, color = textColor, fontWeight = FontWeight.Medium)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user