Implement password verification and enhance BackupScreen UI

- Added password verification logic in MainActivity to decrypt the private key and seed phrase.
- Updated BackupScreen to handle password verification asynchronously using coroutines.
- Improved UI layout for displaying seed phrases in two columns with colored words.
- Added a copy button for the seed phrase with clipboard functionality.
- Introduced a new composable function, WordItem, for better visual representation of seed phrase words.
This commit is contained in:
2026-01-21 16:44:35 +05:00
parent d443592435
commit 7d81dedfab
3 changed files with 176 additions and 33 deletions

File diff suppressed because one or more lines are too long

View File

@@ -542,10 +542,35 @@ fun MainScreen(
showSafetyScreen = true showSafetyScreen = true
}, },
onVerifyPassword = { password -> onVerifyPassword = { password ->
// TODO: Implement password verification // Verify password by trying to decrypt the private key
if (password == "test") { try {
"word1 word2 word3 word4 word5 word6 word7 word8 word9 word10 word11 word12" val publicKey = account?.publicKey ?: return@BackupScreen null
} else null val accountManager = AccountManager(context)
val encryptedAccount = accountManager.getAccount(publicKey)
if (encryptedAccount != null) {
// Try to decrypt private key with password
val decryptedPrivateKey = com.rosetta.messenger.crypto.CryptoManager.decryptWithPassword(
encryptedAccount.encryptedPrivateKey,
password
)
if (decryptedPrivateKey != null) {
// Password is correct, decrypt seed phrase
com.rosetta.messenger.crypto.CryptoManager.decryptWithPassword(
encryptedAccount.encryptedSeedPhrase,
password
)
} else {
null
}
} else {
null
}
} catch (e: Exception) {
Log.e("MainActivity", "Error verifying password", e)
null
}
} }
) )
} }

View File

@@ -9,9 +9,13 @@ import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.ContentCopy
import androidx.compose.material.icons.filled.Visibility import androidx.compose.material.icons.filled.Visibility
import androidx.compose.material.icons.filled.VisibilityOff import androidx.compose.material.icons.filled.VisibilityOff
import androidx.compose.material.icons.filled.Warning import androidx.compose.material.icons.filled.Warning
@@ -26,18 +30,21 @@ import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import kotlinx.coroutines.launch
import com.airbnb.lottie.compose.*
@Composable @Composable
fun BackupScreen( fun BackupScreen(
isDarkTheme: Boolean, isDarkTheme: Boolean,
onBack: () -> Unit, onBack: () -> Unit,
onVerifyPassword: (String) -> String? // Returns seed phrase if password is correct onVerifyPassword: suspend (String) -> String? // Returns seed phrase if password is correct
) { ) {
val backgroundColor = if (isDarkTheme) Color(0xFF1A1A1A) else Color(0xFFFFFFFF) val backgroundColor = if (isDarkTheme) Color(0xFF1A1A1A) else Color(0xFFFFFFFF)
val surfaceColor = if (isDarkTheme) Color(0xFF2C2C2E) else Color(0xFFF2F2F7) val surfaceColor = if (isDarkTheme) Color(0xFF2C2C2E) else Color(0xFFF2F2F7)
val textColor = if (isDarkTheme) Color.White else Color.Black val textColor = if (isDarkTheme) Color.White else Color.Black
val secondaryTextColor = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666) val secondaryTextColor = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666)
val scope = rememberCoroutineScope()
var password by remember { mutableStateOf("") } var password by remember { mutableStateOf("") }
var seedPhrase by remember { mutableStateOf<String?>(null) } var seedPhrase by remember { mutableStateOf<String?>(null) }
var passwordVisible by remember { mutableStateOf(false) } var passwordVisible by remember { mutableStateOf(false) }
@@ -91,11 +98,15 @@ fun BackupScreen(
modifier = Modifier.padding(16.dp), modifier = Modifier.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
Icon( val composition by rememberLottieComposition(LottieCompositionSpec.Asset("lottie/shush.json"))
imageVector = Icons.Filled.Warning, val progress by animateLottieCompositionAsState(
contentDescription = null, composition = composition,
tint = Color(0xFFFFAC00), iterations = 1
modifier = Modifier.size(48.dp) )
LottieAnimation(
composition = composition,
progress = { progress },
modifier = Modifier.size(80.dp)
) )
Spacer(modifier = Modifier.height(12.dp)) Spacer(modifier = Modifier.height(12.dp))
Text( Text(
@@ -128,9 +139,11 @@ fun BackupScreen(
onValueChange = { newValue -> onValueChange = { newValue ->
password = newValue password = newValue
// Try to verify password // Try to verify password
val result = onVerifyPassword(newValue) scope.launch {
if (result != null) { val result = onVerifyPassword(newValue)
seedPhrase = result if (result != null) {
seedPhrase = result
}
} }
}, },
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
@@ -138,6 +151,7 @@ fun BackupScreen(
color = textColor, color = textColor,
fontSize = 15.sp fontSize = 15.sp
), ),
cursorBrush = SolidColor(textColor),
visualTransformation = if (passwordVisible) visualTransformation = if (passwordVisible)
VisualTransformation.None VisualTransformation.None
else else
@@ -182,33 +196,80 @@ fun BackupScreen(
lineHeight = 20.sp lineHeight = 20.sp
) )
} else { } else {
// Seed Phrase Display // Seed Phrase Display with colored words
Surface( Spacer(modifier = Modifier.height(8.dp))
val words = seedPhrase!!.split(" ")
// Two column layout like in SeedPhraseScreen
Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
color = surfaceColor, horizontalArrangement = Arrangement.spacedBy(12.dp)
shape = RoundedCornerShape(10.dp)
) { ) {
Column(modifier = Modifier.padding(20.dp)) { // Left column (words 1-6)
val words = seedPhrase!!.split(" ") Column(
words.chunked(3).forEach { rowWords -> modifier = Modifier.weight(1f),
Row( verticalArrangement = Arrangement.spacedBy(12.dp)
modifier = Modifier.fillMaxWidth(), ) {
horizontalArrangement = Arrangement.SpaceBetween for (i in 0..5) {
) { if (i < words.size) {
rowWords.forEachIndexed { index, word -> WordItem(
Text( number = i + 1,
text = "${words.indexOf(word) + 1}. $word", word = words[i],
fontSize = 14.sp, isDarkTheme = isDarkTheme
fontWeight = FontWeight.Medium, )
color = textColor, }
modifier = Modifier.padding(vertical = 4.dp) }
) }
}
// Right column (words 7-12)
Column(
modifier = Modifier.weight(1f),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
for (i in 6..11) {
if (i < words.size) {
WordItem(
number = i + 1,
word = words[i],
isDarkTheme = isDarkTheme
)
} }
} }
} }
} }
Spacer(modifier = Modifier.height(16.dp))
// Copy button
val context = androidx.compose.ui.platform.LocalContext.current
Button(
onClick = {
val clipboard = context.getSystemService(android.content.Context.CLIPBOARD_SERVICE) as android.content.ClipboardManager
val clip = android.content.ClipData.newPlainText("Seed Phrase", seedPhrase)
clipboard.setPrimaryClip(clip)
},
modifier = Modifier.fillMaxWidth(),
colors = ButtonDefaults.buttonColors(
containerColor = Color(0xFF248AE6)
),
shape = RoundedCornerShape(10.dp)
) {
Icon(
imageVector = Icons.Filled.ContentCopy,
contentDescription = null,
modifier = Modifier.size(20.dp)
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = "Copy Seed Phrase",
fontSize = 15.sp,
fontWeight = FontWeight.Medium
)
}
Spacer(modifier = Modifier.height(16.dp))
Text( Text(
text = "Please don't share your seed phrase! The administration will never ask you for it.", text = "Please don't share your seed phrase! The administration will never ask you for it.",
fontSize = 14.sp, fontSize = 14.sp,
@@ -222,3 +283,59 @@ fun BackupScreen(
} }
} }
} }
@Composable
private fun WordItem(
number: Int,
word: String,
isDarkTheme: Boolean,
modifier: Modifier = Modifier
) {
val numberColor = if (isDarkTheme) Color(0xFF888888) else Color(0xFF999999)
// Beautiful solid colors that fit the theme
val wordColors = listOf(
Color(0xFF5E9FFF), // Soft blue
Color(0xFFFF7EB3), // Soft pink
Color(0xFF7B68EE), // Medium purple
Color(0xFF50C878), // Emerald green
Color(0xFFFF6B6B), // Coral red
Color(0xFF4ECDC4), // Teal
Color(0xFFFFB347), // Pastel orange
Color(0xFFBA55D3), // Medium orchid
Color(0xFF87CEEB), // Sky blue
Color(0xFFDDA0DD), // Plum
Color(0xFF98D8C8), // Mint
Color(0xFFF7DC6F) // Soft yellow
)
val wordColor = wordColors[(number - 1) % wordColors.size]
val bgColor = if (isDarkTheme) Color(0xFF2A2A2A) else Color(0xFFF5F5F5)
Box(
modifier = modifier
.fillMaxWidth()
.clip(RoundedCornerShape(12.dp))
.background(bgColor)
.padding(horizontal = 16.dp, vertical = 14.dp)
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth()
) {
Text(
text = "$number.",
fontSize = 15.sp,
color = numberColor,
modifier = Modifier.width(28.dp)
)
Text(
text = word,
fontSize = 17.sp,
fontWeight = FontWeight.SemiBold,
color = wordColor,
fontFamily = androidx.compose.ui.text.font.FontFamily.Monospace
)
}
}
}