feat: Enhance UnlockScreen with account selection dropdown and search functionality
This commit is contained in:
@@ -130,9 +130,9 @@ fun AuthFlow(
|
||||
selectedAccountId = selectedAccountId,
|
||||
onUnlocked = { account -> onAuthComplete(account) },
|
||||
onSwitchAccount = {
|
||||
if (accounts.size > 1) {
|
||||
currentScreen = AuthScreen.SELECT_ACCOUNT
|
||||
}
|
||||
// Navigate to create new account screen
|
||||
currentScreen = AuthScreen.WELCOME
|
||||
selectedAccountId = null
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -58,6 +58,24 @@ fun SetPasswordScreen(
|
||||
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
|
||||
}
|
||||
view.viewTreeObserver.addOnGlobalLayoutListener(listener)
|
||||
onDispose {
|
||||
view.viewTreeObserver.removeOnGlobalLayoutListener(listener)
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
visible = true
|
||||
}
|
||||
@@ -109,9 +127,18 @@ fun SetPasswordScreen(
|
||||
.verticalScroll(rememberScrollState()),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
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)
|
||||
)
|
||||
|
||||
// Lock Icon
|
||||
AnimatedVisibility(
|
||||
visible = visible,
|
||||
enter = fadeIn(tween(500)) + scaleIn(
|
||||
@@ -121,8 +148,8 @@ fun SetPasswordScreen(
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(80.dp)
|
||||
.clip(RoundedCornerShape(20.dp))
|
||||
.size(iconSize)
|
||||
.clip(RoundedCornerShape(if (isKeyboardVisible) 12.dp else 20.dp))
|
||||
.background(PrimaryBlue.copy(alpha = 0.1f)),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
@@ -130,12 +157,12 @@ fun SetPasswordScreen(
|
||||
Icons.Default.Lock,
|
||||
contentDescription = null,
|
||||
tint = PrimaryBlue,
|
||||
modifier = Modifier.size(40.dp)
|
||||
modifier = Modifier.size(iconInnerSize)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
Spacer(modifier = Modifier.height(if (isKeyboardVisible) 12.dp else 24.dp))
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = visible,
|
||||
@@ -146,13 +173,13 @@ fun SetPasswordScreen(
|
||||
) {
|
||||
Text(
|
||||
text = "Protect Your Account",
|
||||
fontSize = 24.sp,
|
||||
fontSize = if (isKeyboardVisible) 20.sp else 24.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = textColor
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Spacer(modifier = Modifier.height(if (isKeyboardVisible) 6.dp else 8.dp))
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = visible,
|
||||
@@ -160,14 +187,14 @@ fun SetPasswordScreen(
|
||||
) {
|
||||
Text(
|
||||
text = "This password encrypts your keys locally.\nYou'll need it to unlock Rosetta.",
|
||||
fontSize = 14.sp,
|
||||
fontSize = if (isKeyboardVisible) 12.sp else 14.sp,
|
||||
color = secondaryTextColor,
|
||||
textAlign = TextAlign.Center,
|
||||
lineHeight = 20.sp
|
||||
lineHeight = if (isKeyboardVisible) 16.sp else 20.sp
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
Spacer(modifier = Modifier.height(if (isKeyboardVisible) 16.dp else 32.dp))
|
||||
|
||||
// Password Field
|
||||
AnimatedVisibility(
|
||||
|
||||
@@ -5,6 +5,8 @@ import androidx.compose.animation.*
|
||||
import androidx.compose.animation.core.*
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
@@ -15,7 +17,11 @@ import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.scale
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
@@ -24,18 +30,28 @@ import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||
import androidx.compose.ui.text.input.VisualTransformation
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.rosetta.messenger.R
|
||||
import com.rosetta.messenger.crypto.CryptoManager
|
||||
import com.rosetta.messenger.data.AccountManager
|
||||
import com.rosetta.messenger.data.DecryptedAccount
|
||||
import com.rosetta.messenger.data.EncryptedAccount
|
||||
import com.rosetta.messenger.network.ProtocolManager
|
||||
import com.rosetta.messenger.network.ProtocolState
|
||||
import com.rosetta.messenger.ui.onboarding.PrimaryBlue
|
||||
import com.rosetta.messenger.ui.chats.getAvatarColor
|
||||
import com.rosetta.messenger.ui.chats.getAvatarText
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
// Account model for dropdown
|
||||
data class AccountItem(
|
||||
val publicKey: String,
|
||||
val name: String,
|
||||
val encryptedAccount: EncryptedAccount
|
||||
)
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun UnlockScreen(
|
||||
@@ -48,6 +64,7 @@ fun UnlockScreen(
|
||||
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 cardBackground by animateColorAsState(if (isDarkTheme) Color(0xFF2A2A2A) else Color(0xFFF5F5F5), animationSpec = themeAnimSpec)
|
||||
|
||||
val context = LocalContext.current
|
||||
val accountManager = remember { AccountManager(context) }
|
||||
@@ -57,17 +74,63 @@ fun UnlockScreen(
|
||||
var passwordVisible by remember { mutableStateOf(false) }
|
||||
var isUnlocking by remember { mutableStateOf(false) }
|
||||
var error by remember { mutableStateOf<String?>(null) }
|
||||
var currentPublicKey by remember { mutableStateOf<String?>(null) }
|
||||
|
||||
// Load current account
|
||||
// Account selection state
|
||||
var accounts by remember { mutableStateOf<List<AccountItem>>(emptyList()) }
|
||||
var selectedAccount by remember { mutableStateOf<AccountItem?>(null) }
|
||||
var isDropdownExpanded by remember { mutableStateOf(false) }
|
||||
var searchQuery by remember { mutableStateOf("") }
|
||||
val searchFocusRequester = remember { FocusRequester() }
|
||||
|
||||
// Load accounts
|
||||
LaunchedEffect(Unit) {
|
||||
currentPublicKey = selectedAccountId ?: accountManager.currentPublicKey.first()
|
||||
val allAccounts = accountManager.getAllAccounts()
|
||||
accounts = allAccounts.map { acc ->
|
||||
AccountItem(
|
||||
publicKey = acc.publicKey,
|
||||
name = acc.name,
|
||||
encryptedAccount = acc
|
||||
)
|
||||
}
|
||||
|
||||
// Select account
|
||||
val targetPublicKey = selectedAccountId ?: accountManager.currentPublicKey.first()
|
||||
selectedAccount = accounts.find { it.publicKey == targetPublicKey } ?: accounts.firstOrNull()
|
||||
}
|
||||
|
||||
// Filter accounts by search
|
||||
val filteredAccounts = remember(searchQuery, accounts) {
|
||||
if (searchQuery.isEmpty()) accounts
|
||||
else accounts.filter {
|
||||
it.name.contains(searchQuery, ignoreCase = true) ||
|
||||
it.publicKey.contains(searchQuery, ignoreCase = true)
|
||||
}
|
||||
}
|
||||
|
||||
// Entry animation
|
||||
var visible by remember { mutableStateOf(false) }
|
||||
LaunchedEffect(Unit) { visible = true }
|
||||
|
||||
// Dropdown animation
|
||||
val dropdownProgress by animateFloatAsState(
|
||||
targetValue = if (isDropdownExpanded) 1f else 0f,
|
||||
animationSpec = spring(
|
||||
dampingRatio = Spring.DampingRatioMediumBouncy,
|
||||
stiffness = Spring.StiffnessLow
|
||||
),
|
||||
label = "dropdownProgress"
|
||||
)
|
||||
|
||||
// Auto-focus search when dropdown opens
|
||||
LaunchedEffect(isDropdownExpanded) {
|
||||
if (isDropdownExpanded) {
|
||||
kotlinx.coroutines.delay(200)
|
||||
searchFocusRequester.requestFocus()
|
||||
} else {
|
||||
searchQuery = ""
|
||||
}
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
@@ -76,12 +139,13 @@ fun UnlockScreen(
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.imePadding()
|
||||
.padding(horizontal = 24.dp)
|
||||
.statusBarsPadding(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Spacer(modifier = Modifier.weight(0.2f))
|
||||
Spacer(modifier = Modifier.height(40.dp))
|
||||
|
||||
// Rosetta Logo
|
||||
AnimatedVisibility(
|
||||
@@ -92,12 +156,12 @@ fun UnlockScreen(
|
||||
painter = painterResource(id = R.drawable.rosetta_icon),
|
||||
contentDescription = "Rosetta",
|
||||
modifier = Modifier
|
||||
.size(120.dp)
|
||||
.size(100.dp)
|
||||
.clip(CircleShape)
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
// Title
|
||||
AnimatedVisibility(
|
||||
@@ -117,24 +181,263 @@ fun UnlockScreen(
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
// Account info
|
||||
AnimatedVisibility(
|
||||
visible = visible && currentPublicKey != null,
|
||||
visible = visible,
|
||||
enter = fadeIn(tween(600, delayMillis = 300))
|
||||
) {
|
||||
Text(
|
||||
text = "Enter your password to unlock",
|
||||
text = "Select your account and enter password",
|
||||
fontSize = 16.sp,
|
||||
color = secondaryTextColor,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(48.dp))
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
|
||||
// Account Selector Card
|
||||
AnimatedVisibility(
|
||||
visible = visible,
|
||||
enter = fadeIn(tween(600, delayMillis = 350)) + slideInVertically(
|
||||
initialOffsetY = { 50 },
|
||||
animationSpec = tween(600, delayMillis = 350)
|
||||
)
|
||||
) {
|
||||
Column {
|
||||
// Account selector dropdown
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable { isDropdownExpanded = !isDropdownExpanded },
|
||||
colors = CardDefaults.cardColors(containerColor = cardBackground),
|
||||
shape = RoundedCornerShape(16.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
// Avatar
|
||||
if (selectedAccount != null) {
|
||||
val avatarColors = getAvatarColor(selectedAccount!!.name, isDarkTheme)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(48.dp)
|
||||
.clip(CircleShape)
|
||||
.background(avatarColors.backgroundColor),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
text = getAvatarText(selectedAccount!!.publicKey),
|
||||
fontSize = 18.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = avatarColors.textColor
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.width(12.dp))
|
||||
|
||||
// Account info
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
text = selectedAccount?.name ?: "Select Account",
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = textColor,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
if (selectedAccount != null) {
|
||||
Text(
|
||||
text = selectedAccount!!.publicKey.take(20) + "...",
|
||||
fontSize = 13.sp,
|
||||
color = secondaryTextColor,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Dropdown arrow with rotation
|
||||
Icon(
|
||||
imageVector = Icons.Default.KeyboardArrowDown,
|
||||
contentDescription = null,
|
||||
tint = secondaryTextColor,
|
||||
modifier = Modifier
|
||||
.size(24.dp)
|
||||
.graphicsLayer {
|
||||
rotationZ = 180f * dropdownProgress
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Dropdown list with animation
|
||||
AnimatedVisibility(
|
||||
visible = isDropdownExpanded,
|
||||
enter = fadeIn(tween(200)) + expandVertically(
|
||||
expandFrom = Alignment.Top,
|
||||
animationSpec = spring(
|
||||
dampingRatio = Spring.DampingRatioMediumBouncy,
|
||||
stiffness = Spring.StiffnessMediumLow
|
||||
)
|
||||
),
|
||||
exit = fadeOut(tween(150)) + shrinkVertically(
|
||||
shrinkTowards = Alignment.Top,
|
||||
animationSpec = tween(200)
|
||||
)
|
||||
) {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 8.dp)
|
||||
.heightIn(max = 300.dp),
|
||||
colors = CardDefaults.cardColors(containerColor = cardBackground),
|
||||
shape = RoundedCornerShape(16.dp)
|
||||
) {
|
||||
Column {
|
||||
// Search field
|
||||
OutlinedTextField(
|
||||
value = searchQuery,
|
||||
onValueChange = { searchQuery = it },
|
||||
placeholder = {
|
||||
Text(
|
||||
"Search accounts...",
|
||||
color = secondaryTextColor.copy(alpha = 0.6f)
|
||||
)
|
||||
},
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
Icons.Default.Search,
|
||||
contentDescription = null,
|
||||
tint = secondaryTextColor
|
||||
)
|
||||
},
|
||||
colors = OutlinedTextFieldDefaults.colors(
|
||||
focusedBorderColor = PrimaryBlue,
|
||||
unfocusedBorderColor = Color.Transparent,
|
||||
focusedContainerColor = Color.Transparent,
|
||||
unfocusedContainerColor = Color.Transparent,
|
||||
focusedTextColor = textColor,
|
||||
unfocusedTextColor = textColor,
|
||||
cursorColor = PrimaryBlue
|
||||
),
|
||||
singleLine = true,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 12.dp, vertical = 8.dp)
|
||||
.focusRequester(searchFocusRequester),
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
)
|
||||
|
||||
Divider(
|
||||
color = if (isDarkTheme) Color(0xFF3A3A3A) else Color(0xFFE0E0E0),
|
||||
thickness = 0.5.dp
|
||||
)
|
||||
|
||||
// Account list
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
items(filteredAccounts, key = { it.publicKey }) { account ->
|
||||
val isSelected = account.publicKey == selectedAccount?.publicKey
|
||||
val itemScale by animateFloatAsState(
|
||||
targetValue = if (isSelected) 1f else 0.98f,
|
||||
label = "itemScale"
|
||||
)
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.scale(itemScale)
|
||||
.clickable {
|
||||
selectedAccount = account
|
||||
isDropdownExpanded = false
|
||||
password = ""
|
||||
error = null
|
||||
}
|
||||
.background(
|
||||
if (isSelected) PrimaryBlue.copy(alpha = 0.1f)
|
||||
else Color.Transparent
|
||||
)
|
||||
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
// Avatar
|
||||
val avatarColors = getAvatarColor(account.name, isDarkTheme)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(40.dp)
|
||||
.clip(CircleShape)
|
||||
.background(avatarColors.backgroundColor),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
text = getAvatarText(account.publicKey),
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = avatarColors.textColor
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.width(12.dp))
|
||||
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
text = account.name,
|
||||
fontSize = 15.sp,
|
||||
fontWeight = if (isSelected) FontWeight.SemiBold else FontWeight.Normal,
|
||||
color = textColor,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
Text(
|
||||
text = account.publicKey.take(16) + "...",
|
||||
fontSize = 12.sp,
|
||||
color = secondaryTextColor,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
|
||||
if (isSelected) {
|
||||
Icon(
|
||||
Icons.Default.Check,
|
||||
contentDescription = null,
|
||||
tint = PrimaryBlue,
|
||||
modifier = Modifier.size(20.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (filteredAccounts.isEmpty()) {
|
||||
item {
|
||||
Text(
|
||||
text = "No accounts found",
|
||||
color = secondaryTextColor,
|
||||
fontSize = 14.sp,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(24.dp),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
|
||||
// Password Field
|
||||
AnimatedVisibility(
|
||||
visible = visible,
|
||||
visible = visible && !isDropdownExpanded,
|
||||
enter = fadeIn(tween(600, delayMillis = 400)) + slideInVertically(
|
||||
initialOffsetY = { 50 },
|
||||
animationSpec = tween(600, delayMillis = 400)
|
||||
@@ -197,7 +500,7 @@ fun UnlockScreen(
|
||||
|
||||
// Unlock Button
|
||||
AnimatedVisibility(
|
||||
visible = visible,
|
||||
visible = visible && !isDropdownExpanded,
|
||||
enter = fadeIn(tween(600, delayMillis = 500)) + slideInVertically(
|
||||
initialOffsetY = { 50 },
|
||||
animationSpec = tween(600, delayMillis = 500)
|
||||
@@ -205,6 +508,10 @@ fun UnlockScreen(
|
||||
) {
|
||||
Button(
|
||||
onClick = {
|
||||
if (selectedAccount == null) {
|
||||
error = "Please select an account"
|
||||
return@Button
|
||||
}
|
||||
if (password.isEmpty()) {
|
||||
error = "Please enter your password"
|
||||
return@Button
|
||||
@@ -213,20 +520,7 @@ fun UnlockScreen(
|
||||
isUnlocking = true
|
||||
scope.launch {
|
||||
try {
|
||||
val publicKey = currentPublicKey ?: run {
|
||||
error = "No account found"
|
||||
isUnlocking = false
|
||||
return@launch
|
||||
}
|
||||
|
||||
val accounts = accountManager.getAllAccounts()
|
||||
val account = accounts.find { it.publicKey == publicKey }
|
||||
|
||||
if (account == null) {
|
||||
error = "Account not found"
|
||||
isUnlocking = false
|
||||
return@launch
|
||||
}
|
||||
val account = selectedAccount!!.encryptedAccount
|
||||
|
||||
// Try to decrypt
|
||||
val decryptedPrivateKey = CryptoManager.decryptWithPassword(
|
||||
@@ -253,20 +547,20 @@ fun UnlockScreen(
|
||||
name = account.name
|
||||
)
|
||||
|
||||
// 🔌 Connect to server and authenticate
|
||||
Log.d("UnlockScreen", "🔌 Connecting to server...")
|
||||
// Connect to server and authenticate
|
||||
Log.d("UnlockScreen", "Connecting to server...")
|
||||
ProtocolManager.authenticate(account.publicKey, privateKeyHash)
|
||||
|
||||
accountManager.setCurrentAccount(publicKey)
|
||||
accountManager.setCurrentAccount(account.publicKey)
|
||||
onUnlocked(decryptedAccount)
|
||||
|
||||
} catch (e: Exception) {
|
||||
error = "Failed to unlock: ${e.message}"
|
||||
error = "Failed to unlock: \${e.message}"
|
||||
isUnlocking = false
|
||||
}
|
||||
}
|
||||
},
|
||||
enabled = password.isNotEmpty() && !isUnlocking,
|
||||
enabled = selectedAccount != null && password.isNotEmpty() && !isUnlocking,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(56.dp),
|
||||
@@ -302,9 +596,9 @@ fun UnlockScreen(
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
// Switch Account button
|
||||
// Create New Account button
|
||||
AnimatedVisibility(
|
||||
visible = visible,
|
||||
visible = visible && !isDropdownExpanded,
|
||||
enter = fadeIn(tween(600, delayMillis = 600)) + slideInVertically(
|
||||
initialOffsetY = { 50 },
|
||||
animationSpec = tween(600, delayMillis = 600)
|
||||
@@ -314,21 +608,21 @@ fun UnlockScreen(
|
||||
onClick = onSwitchAccount
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.SwapHoriz,
|
||||
imageVector = Icons.Default.PersonAdd,
|
||||
contentDescription = null,
|
||||
tint = PrimaryBlue,
|
||||
modifier = Modifier.size(20.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text(
|
||||
text = "Switch Account",
|
||||
text = "Create New Account",
|
||||
color = PrimaryBlue,
|
||||
fontSize = 15.sp
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.weight(0.3f))
|
||||
Spacer(modifier = Modifier.height(60.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user