feat: Add biometric unlock functionality with saved password retrieval in UnlockScreen
This commit is contained in:
@@ -189,6 +189,8 @@ fun UnlockScreen(
|
|||||||
// Биометрия
|
// Биометрия
|
||||||
var biometricAvailable by remember { mutableStateOf<BiometricAvailability>(BiometricAvailability.NotAvailable("Проверка...")) }
|
var biometricAvailable by remember { mutableStateOf<BiometricAvailability>(BiometricAvailability.NotAvailable("Проверка...")) }
|
||||||
var isBiometricEnabled by remember { mutableStateOf(false) }
|
var isBiometricEnabled by remember { mutableStateOf(false) }
|
||||||
|
var savedPasswordsMap by remember { mutableStateOf<Map<String, String>>(emptyMap()) }
|
||||||
|
var dataLoaded by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
// Account selection state
|
// Account selection state
|
||||||
var accounts by remember { mutableStateOf<List<AccountItem>>(emptyList()) }
|
var accounts by remember { mutableStateOf<List<AccountItem>>(emptyList()) }
|
||||||
@@ -228,17 +230,46 @@ fun UnlockScreen(
|
|||||||
biometricAvailable = biometricManager.isBiometricAvailable()
|
biometricAvailable = biometricManager.isBiometricAvailable()
|
||||||
isBiometricEnabled = biometricPrefs.isBiometricEnabled.first()
|
isBiometricEnabled = biometricPrefs.isBiometricEnabled.first()
|
||||||
|
|
||||||
|
// Загружаем сохранённые пароли для всех аккаунтов
|
||||||
|
val passwords = mutableMapOf<String, String>()
|
||||||
|
accounts.forEach { account ->
|
||||||
|
val encryptedPwd = biometricPrefs.getEncryptedPassword(account.publicKey)
|
||||||
|
if (encryptedPwd != null) {
|
||||||
|
passwords[account.publicKey] = encryptedPwd
|
||||||
|
}
|
||||||
|
}
|
||||||
|
savedPasswordsMap = passwords
|
||||||
|
|
||||||
android.util.Log.d("UnlockScreen", "🔐 Biometric available: $biometricAvailable")
|
android.util.Log.d("UnlockScreen", "🔐 Biometric available: $biometricAvailable")
|
||||||
android.util.Log.d("UnlockScreen", "🔐 Biometric enabled: $isBiometricEnabled")
|
android.util.Log.d("UnlockScreen", "🔐 Biometric enabled: $isBiometricEnabled")
|
||||||
android.util.Log.d("UnlockScreen", "🔐 Activity: $activity")
|
android.util.Log.d("UnlockScreen", "🔐 Activity: $activity")
|
||||||
|
android.util.Log.d("UnlockScreen", "🔐 Selected account: ${selectedAccount?.publicKey}")
|
||||||
|
android.util.Log.d("UnlockScreen", "🔐 Saved passwords count: ${passwords.size}")
|
||||||
|
android.util.Log.d("UnlockScreen", "🔐 Has password for selected: ${selectedAccount?.let { passwords.containsKey(it.publicKey) }}")
|
||||||
|
|
||||||
|
dataLoaded = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Автоматически пытаемся разблокировать через биометрию при выборе аккаунта
|
// Автоматически пытаемся разблокировать через биометрию при выборе аккаунта
|
||||||
LaunchedEffect(selectedAccount, isBiometricEnabled) {
|
LaunchedEffect(selectedAccount, isBiometricEnabled, savedPasswordsMap, dataLoaded) {
|
||||||
|
android.util.Log.d("UnlockScreen", "🚀 LaunchedEffect triggered")
|
||||||
|
android.util.Log.d("UnlockScreen", "🚀 dataLoaded: $dataLoaded")
|
||||||
|
android.util.Log.d("UnlockScreen", "🚀 selectedAccount: ${selectedAccount?.publicKey}")
|
||||||
|
android.util.Log.d("UnlockScreen", "🚀 isBiometricEnabled: $isBiometricEnabled")
|
||||||
|
android.util.Log.d("UnlockScreen", "🚀 biometricAvailable: $biometricAvailable")
|
||||||
|
android.util.Log.d("UnlockScreen", "🚀 activity: $activity")
|
||||||
|
|
||||||
|
if (!dataLoaded) {
|
||||||
|
android.util.Log.d("UnlockScreen", "⏸️ Data not loaded yet, waiting...")
|
||||||
|
return@LaunchedEffect
|
||||||
|
}
|
||||||
|
|
||||||
if (selectedAccount != null && isBiometricEnabled && activity != null &&
|
if (selectedAccount != null && isBiometricEnabled && activity != null &&
|
||||||
biometricAvailable is BiometricAvailability.Available) {
|
biometricAvailable is BiometricAvailability.Available) {
|
||||||
|
|
||||||
val encryptedPassword = biometricPrefs.getEncryptedPassword(selectedAccount!!.publicKey)
|
val encryptedPassword = savedPasswordsMap[selectedAccount!!.publicKey]
|
||||||
|
android.util.Log.d("UnlockScreen", "🔑 Encrypted password found: ${encryptedPassword != null}")
|
||||||
|
|
||||||
if (encryptedPassword != null) {
|
if (encryptedPassword != null) {
|
||||||
// Небольшая задержка для анимации UI
|
// Небольшая задержка для анимации UI
|
||||||
delay(500)
|
delay(500)
|
||||||
@@ -705,79 +736,133 @@ fun UnlockScreen(
|
|||||||
enter = fadeIn(tween(400, delayMillis = 500))
|
enter = fadeIn(tween(400, delayMillis = 500))
|
||||||
) {
|
) {
|
||||||
Column {
|
Column {
|
||||||
Button(
|
// Check if biometric unlock is available for selected account
|
||||||
onClick = {
|
val canUseBiometric = remember(selectedAccount, isBiometricEnabled, biometricAvailable) {
|
||||||
if (selectedAccount == null) {
|
selectedAccount != null &&
|
||||||
error = "Please select an account"
|
isBiometricEnabled &&
|
||||||
return@Button
|
biometricAvailable is BiometricAvailability.Available &&
|
||||||
}
|
activity != null
|
||||||
if (password.isEmpty()) {
|
}
|
||||||
error = "Please enter your password"
|
|
||||||
return@Button
|
val hasSavedPassword = remember(selectedAccount, savedPasswordsMap) {
|
||||||
}
|
selectedAccount?.let { savedPasswordsMap.containsKey(it.publicKey) } ?: false
|
||||||
|
}
|
||||||
|
|
||||||
|
val showBiometricButton = canUseBiometric && hasSavedPassword
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
|
) {
|
||||||
|
// Password Unlock Button
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
if (selectedAccount == null) {
|
||||||
|
error = "Please select an account"
|
||||||
|
return@Button
|
||||||
|
}
|
||||||
|
if (password.isEmpty()) {
|
||||||
|
error = "Please enter your password"
|
||||||
|
return@Button
|
||||||
|
}
|
||||||
|
|
||||||
scope.launch {
|
scope.launch {
|
||||||
performUnlock(
|
performUnlock(
|
||||||
selectedAccount = selectedAccount,
|
selectedAccount = selectedAccount,
|
||||||
password = password,
|
password = password,
|
||||||
accountManager = accountManager,
|
accountManager = accountManager,
|
||||||
onUnlocking = { isUnlocking = it },
|
onUnlocking = { isUnlocking = it },
|
||||||
onError = { error = it },
|
onError = { error = it },
|
||||||
onSuccess = { decryptedAccount ->
|
onSuccess = { decryptedAccount ->
|
||||||
// If biometric is enabled and password not saved yet, save password
|
onUnlocked(decryptedAccount)
|
||||||
if (biometricAvailable is BiometricAvailability.Available &&
|
}
|
||||||
isBiometricEnabled && activity != null) {
|
)
|
||||||
scope.launch {
|
}
|
||||||
// Check if password is already saved
|
},
|
||||||
val hasPassword = biometricPrefs.hasEncryptedPassword(decryptedAccount.publicKey)
|
enabled = selectedAccount != null && password.isNotEmpty() && !isUnlocking,
|
||||||
if (!hasPassword) {
|
modifier = Modifier
|
||||||
biometricManager.encryptPassword(
|
.weight(1f)
|
||||||
activity = activity,
|
.height(56.dp),
|
||||||
password = password,
|
colors =
|
||||||
onSuccess = { encryptedPassword ->
|
ButtonDefaults.buttonColors(
|
||||||
scope.launch {
|
containerColor = PrimaryBlue,
|
||||||
biometricPrefs.saveEncryptedPassword(
|
contentColor = Color.White,
|
||||||
decryptedAccount.publicKey,
|
disabledContainerColor = PrimaryBlue.copy(alpha = 0.5f),
|
||||||
encryptedPassword
|
disabledContentColor = Color.White.copy(alpha = 0.5f)
|
||||||
)
|
),
|
||||||
}
|
shape = RoundedCornerShape(12.dp)
|
||||||
},
|
) {
|
||||||
onError = { /* Ignore save errors */ },
|
if (isUnlocking) {
|
||||||
onCancel = { /* User cancelled */ }
|
CircularProgressIndicator(
|
||||||
|
modifier = Modifier.size(24.dp),
|
||||||
|
color = Color.White,
|
||||||
|
strokeWidth = 2.dp
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Icon(
|
||||||
|
imageVector = TablerIcons.LockOpen,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(20.dp)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text(text = "Unlock", fontSize = 16.sp, fontWeight = FontWeight.SemiBold)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Biometric Unlock Button
|
||||||
|
if (showBiometricButton) {
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
if (selectedAccount == null || activity == null) return@Button
|
||||||
|
|
||||||
|
val encryptedPassword = savedPasswordsMap[selectedAccount!!.publicKey]
|
||||||
|
if (encryptedPassword != null) {
|
||||||
|
biometricManager.decryptPassword(
|
||||||
|
activity = activity,
|
||||||
|
encryptedData = encryptedPassword,
|
||||||
|
onSuccess = { decryptedPassword ->
|
||||||
|
scope.launch {
|
||||||
|
performUnlock(
|
||||||
|
selectedAccount = selectedAccount,
|
||||||
|
password = decryptedPassword,
|
||||||
|
accountManager = accountManager,
|
||||||
|
onUnlocking = { isUnlocking = it },
|
||||||
|
onError = { error = it },
|
||||||
|
onSuccess = { decryptedAccount ->
|
||||||
|
onUnlocked(decryptedAccount)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
onError = { errorMessage ->
|
||||||
|
error = errorMessage
|
||||||
|
},
|
||||||
|
onCancel = {
|
||||||
|
// User cancelled
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
onUnlocked(decryptedAccount)
|
|
||||||
}
|
}
|
||||||
)
|
},
|
||||||
}
|
enabled = !isUnlocking,
|
||||||
},
|
modifier = Modifier
|
||||||
enabled = selectedAccount != null && password.isNotEmpty() && !isUnlocking,
|
.width(56.dp)
|
||||||
modifier = Modifier.fillMaxWidth().height(56.dp),
|
.height(56.dp),
|
||||||
colors =
|
colors =
|
||||||
ButtonDefaults.buttonColors(
|
ButtonDefaults.buttonColors(
|
||||||
containerColor = PrimaryBlue,
|
containerColor = PrimaryBlue,
|
||||||
contentColor = Color.White,
|
contentColor = Color.White,
|
||||||
disabledContainerColor = PrimaryBlue.copy(alpha = 0.5f),
|
disabledContainerColor = PrimaryBlue.copy(alpha = 0.5f),
|
||||||
disabledContentColor = Color.White.copy(alpha = 0.5f)
|
disabledContentColor = Color.White.copy(alpha = 0.5f)
|
||||||
),
|
),
|
||||||
shape = RoundedCornerShape(12.dp)
|
shape = RoundedCornerShape(12.dp),
|
||||||
) {
|
contentPadding = PaddingValues(0.dp)
|
||||||
if (isUnlocking) {
|
) {
|
||||||
CircularProgressIndicator(
|
Icon(
|
||||||
modifier = Modifier.size(24.dp),
|
imageVector = TablerIcons.Fingerprint,
|
||||||
color = Color.White,
|
contentDescription = "Unlock with biometric",
|
||||||
strokeWidth = 2.dp
|
modifier = Modifier.size(28.dp)
|
||||||
)
|
)
|
||||||
} else {
|
}
|
||||||
Icon(
|
|
||||||
imageVector = TablerIcons.LockOpen,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.size(20.dp)
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
|
||||||
Text(text = "Unlock", fontSize = 16.sp, fontWeight = FontWeight.SemiBold)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user