Fix: Behebt den Absturz beim BiometricPrompt durch Entfernen des negativen Button-Textes.

This commit is contained in:
2025-11-04 10:55:09 +01:00
parent 6029da0ee5
commit fdfbe339d8
9 changed files with 36 additions and 218 deletions

View File

@@ -134,7 +134,6 @@ class BiometricAuthenticator(private val context: Context) {
fun promptBiometricAuth(
title: String,
subtitle: String,
negativeButtonText: String,
fragmentActivity: FragmentActivity,
onSuccess: (BiometricPrompt.AuthenticationResult) -> Unit,
onFailed: () -> Unit,
@@ -143,8 +142,7 @@ class BiometricAuthenticator(private val context: Context) {
promptInfo = BiometricPrompt.PromptInfo.Builder()
.setTitle(title)
.setSubtitle(subtitle)
.setNegativeButtonText(negativeButtonText)
.setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG)
.setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG or BiometricManager.Authenticators.DEVICE_CREDENTIAL)
.build()
val executor = ContextCompat.getMainExecutor(context)
@@ -171,7 +169,6 @@ class BiometricAuthenticator(private val context: Context) {
fun promptBiometricAuth(
title: String,
subtitle: String,
negativeButtonText: String,
fragmentActivity: FragmentActivity,
crypto: BiometricPrompt.CryptoObject,
onSuccess: (BiometricPrompt.AuthenticationResult) -> Unit,
@@ -181,8 +178,7 @@ class BiometricAuthenticator(private val context: Context) {
promptInfo = BiometricPrompt.PromptInfo.Builder()
.setTitle(title)
.setSubtitle(subtitle)
.setNegativeButtonText(negativeButtonText)
.setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG)
.setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG or BiometricManager.Authenticators.DEVICE_CREDENTIAL)
.build()
val executor = ContextCompat.getMainExecutor(context)
@@ -275,7 +271,6 @@ class MainActivity : FragmentActivity() {
biometricAuthenticator.promptBiometricAuth(
title = getString(R.string.unlock_app),
subtitle = getString(R.string.confirm_to_unlock),
negativeButtonText = getString(R.string.cancel),
fragmentActivity = this,
onSuccess = { _ -> isUnlocked = true },
onFailed = {

View File

@@ -155,7 +155,6 @@ fun AppShell(
biometricAuthenticator.promptBiometricAuth(
title = context.getString(R.string.remove_encryption),
subtitle = context.getString(R.string.confirm_to_disable_encryption),
negativeButtonText = context.getString(R.string.cancel),
fragmentActivity = activity,
crypto = crypto,
onSuccess = { result ->
@@ -217,7 +216,6 @@ fun AppShell(
biometricAuthenticator.promptBiometricAuth(
title = context.getString(R.string.remove_encryption),
subtitle = context.getString(R.string.confirm_to_remove_encryption),
negativeButtonText = context.getString(R.string.cancel),
fragmentActivity = activity,
crypto = crypto,
onSuccess = { result ->
@@ -531,7 +529,6 @@ fun AppShell(
biometricAuthenticator.promptBiometricAuth(
title = context.getString(R.string.unlock_app),
subtitle = context.getString(R.string.confirm_to_unlock),
negativeButtonText = context.getString(R.string.cancel),
fragmentActivity = activity,
crypto = crypto,
onSuccess = { result ->
@@ -642,12 +639,9 @@ fun AppShell(
var showSetPasswordDialog by rememberSaveable { mutableStateOf(false) }
var showSetRecipePasswordDialog by rememberSaveable { mutableStateOf(false) }
var showSetListPasswordDialog by rememberSaveable { mutableStateOf(false) }
var showSetPatternDialog by rememberSaveable { mutableStateOf(false) }
var showUnlockPatternDialog by rememberSaveable { mutableStateOf(false) }
var unlockPatternErrorMessage by remember { mutableStateOf<String?>(null) }
var showUnlockPasswordDialog by rememberSaveable { mutableStateOf(false) }
var showUnlockPinDialog by rememberSaveable { mutableStateOf(false) }
var unlockErrorMessage by rememberSaveable { mutableStateOf<String?>(null) }
var itemToUnlockId by rememberSaveable { mutableStateOf<Int?>(null) }
var itemToUnlockType by rememberSaveable { mutableStateOf<Screen?>(null) }
@@ -658,50 +652,34 @@ fun AppShell(
val onUnlockItem: (Int, Screen, Int) -> Unit = { id, type, protectionType ->
if (protectionType == 1) { // Password protected
itemToUnlockId = id
itemToUnlockType = type
showUnlockPasswordDialog = true
} else if (protectionType == 2) { // Biometric protected
if (canUseBiometrics) {
biometricAuthenticator.promptBiometricAuth(
title = context.getString(R.string.unlock_item),
subtitle = "",
negativeButtonText = context.getString(R.string.cancel),
fragmentActivity = context.findActivity() as FragmentActivity,
onSuccess = {
when (type) {
is Screen.ShoppingListDetail -> selectedListId = id
is Screen.NoteDetail -> selectedNoteId = id
is Screen.RecipeDetail -> selectedRecipeId = id
else -> {}
}
itemWasInitiallyLocked = true
currentScreen = type
},
onFailed = {
// Biometric failed, offer password as fallback if desired
itemToUnlockId = id
itemToUnlockType = type
showUnlockPasswordDialog = true
},
onError = { _, _ ->
// Biometric error, offer password as fallback if desired
itemToUnlockId = id
itemToUnlockType = type
showUnlockPasswordDialog = true
if (protectionType == 1 || protectionType == 2) { // Password or Biometric protected
biometricAuthenticator.promptBiometricAuth(
title = context.getString(R.string.unlock_item),
subtitle = "",
fragmentActivity = context.findActivity() as FragmentActivity,
onSuccess = {
when (type) {
is Screen.ShoppingListDetail -> selectedListId = id
is Screen.NoteDetail -> selectedNoteId = id
is Screen.RecipeDetail -> selectedRecipeId = id
else -> {}
}
)
} else {
// Biometrics not available, offer password as fallback
itemToUnlockId = id
itemToUnlockType = type
showUnlockPasswordDialog = true
}
} else if (protectionType == 3) { // Pattern protected
itemToUnlockId = id
itemToUnlockType = type
showUnlockPatternDialog = true
itemWasInitiallyLocked = true
currentScreen = type
},
onFailed = {
// Biometric failed, offer password as fallback if desired
itemToUnlockId = id
itemToUnlockType = type
showUnlockPasswordDialog = true
},
onError = { _, _ ->
// Biometric error, offer password as fallback if desired
itemToUnlockId = id
itemToUnlockType = type
showUnlockPasswordDialog = true
}
)
}
}
@@ -915,7 +893,6 @@ fun AppShell(
biometricAuthenticator.promptBiometricAuth(
title = context.getString(R.string.authenticate_to_perform_action),
subtitle = "",
negativeButtonText = context.getString(R.string.cancel),
fragmentActivity = activity,
onSuccess = { _ ->
if (isPassword) {
@@ -1185,15 +1162,6 @@ fun AppShell(
itemToUnlockType = null
}
},
showUnlockPatternDialog = showUnlockPatternDialog,
onShowUnlockPatternDialogChange = {
showUnlockPatternDialog = it
if (!it) {
unlockPatternErrorMessage = null
itemToUnlockId = null
itemToUnlockType = null
}
},
onUnlock = { password ->
val hashedPassword = PasswordHasher.hashPassword(password)
var isPasswordCorrect = false
@@ -1230,7 +1198,6 @@ fun AppShell(
itemToUnlockId = null
itemToUnlockType = null
showUnlockPasswordDialog = false
showUnlockPatternDialog = false
} else {
unlockErrorMessage = context.getString(R.string.incorrect_password)
}
@@ -1280,11 +1247,6 @@ fun AppShell(
LockMethod.BIOMETRIC -> {
shoppingListsViewModel.setProtectionBiometric(id)
}
LockMethod.PATTERN -> {
selectedListId = id
showSetPatternDialog = true
}
}
}
LockableItemType.NOTE -> {
@@ -1296,11 +1258,6 @@ fun AppShell(
LockMethod.BIOMETRIC -> {
notesViewModel.setProtectionBiometric(id)
}
LockMethod.PATTERN -> {
selectedNoteId = id
showSetPatternDialog = true
}
}
}
LockableItemType.RECIPE -> {
@@ -1312,11 +1269,6 @@ fun AppShell(
LockMethod.BIOMETRIC -> {
recipesViewModel.setProtectionBiometric(id)
}
LockMethod.PATTERN -> {
selectedRecipeId = id
showSetPatternDialog = true
}
}
}
}
@@ -1328,8 +1280,5 @@ fun AppShell(
},
canUseBiometrics = canUseBiometrics,
itemToLockType = itemToLockType,
showSetPatternDialog = showSetPatternDialog,
onShowSetPatternDialogChange = { showSetPatternDialog = it },
)
)
}

View File

@@ -22,7 +22,7 @@ import de.lxtools.noteshop.R
enum class LockMethod {
BIOMETRIC,
PATTERN,
PASSWORD
}
@@ -53,19 +53,7 @@ fun ChooseLockMethodDialog(
)
Text(text = stringResource(R.string.biometric_lock), modifier = Modifier.padding(start = 8.dp))
}
Row(
modifier = Modifier
.fillMaxWidth()
.clickable { selectedMethod = LockMethod.PATTERN }
.padding(vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically
) {
RadioButton(
selected = selectedMethod == LockMethod.PATTERN,
onClick = { selectedMethod = LockMethod.PATTERN }
)
Text(text = stringResource(R.string.pattern), modifier = Modifier.padding(start = 8.dp))
}
Row(
modifier = Modifier
.fillMaxWidth()

View File

@@ -115,7 +115,6 @@ fun EncryptionPasswordDialog(
biometricAuthenticator.promptBiometricAuth(
title = context.getString(R.string.set_encryption_password),
subtitle = "",
negativeButtonText = context.getString(R.string.cancel),
fragmentActivity = context as FragmentActivity,
crypto = cryptoObject,
onSuccess = { result ->

View File

@@ -61,7 +61,6 @@ fun JsonImportExportDialog(
biometricAuthenticator.promptBiometricAuth(
title = context.getString(R.string.authenticate_for_import_export),
subtitle = "",
negativeButtonText = context.getString(R.string.cancel),
fragmentActivity = activity,
crypto = crypto,
onSuccess = { result ->

View File

@@ -1,72 +0,0 @@
package de.lxtools.noteshop.ui
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.KeyboardType
import de.lxtools.noteshop.R
@Composable
fun SetPatternDialog(onDismiss: () -> Unit, onSetPattern: (String) -> Unit) {
var pattern by remember { mutableStateOf(emptyList<Int>()) }
AlertDialog(
onDismissRequest = onDismiss,
title = { Text(text = "Set Pattern") },
text = {
PatternInput(onPatternComplete = {
pattern = it
onSetPattern(it.joinToString(""))
})
},
confirmButton = {
TextButton(onClick = { onSetPattern(pattern.joinToString("")) }) {
Text(text = "OK")
}
},
dismissButton = {
TextButton(onClick = onDismiss) {
Text(text = "Cancel")
}
}
)
}
@Composable
fun UnlockPatternDialog(onDismiss: () -> Unit, onUnlock: (String) -> Unit, errorMessage: String?) {
var pattern by remember { mutableStateOf(emptyList<Int>()) }
AlertDialog(
onDismissRequest = onDismiss,
title = { Text(text = stringResource(R.string.unlock_item)) },
text = {
Column {
PatternInput(onPatternComplete = {
pattern = it
onUnlock(it.joinToString(""))
})
if (errorMessage != null) {
Text(text = errorMessage, color = MaterialTheme.colorScheme.error)
}
}
},
confirmButton = {
TextButton(onClick = { onUnlock(pattern.joinToString("")) }) {
Text(text = stringResource(R.string.unlock))
}
},
dismissButton = {
TextButton(onClick = onDismiss) {
Text(text = stringResource(R.string.cancel))
}
}
)
}

View File

@@ -96,7 +96,6 @@ fun SettingsScreen(
biometricAuthenticator.promptBiometricAuth(
title = context.getString(R.string.confirm_to_proceed),
subtitle = context.getString(R.string.authenticate_to_perform_action),
negativeButtonText = context.getString(R.string.cancel),
fragmentActivity = context as FragmentActivity,
onSuccess = { successAction() },
onFailed = {

View File

@@ -79,12 +79,8 @@ fun AppDialogs(
onShowSetListPasswordDialogChange: (Boolean) -> Unit,
showUnlockPasswordDialog: Boolean,
onShowUnlockPasswordDialogChange: (Boolean) -> Unit,
showUnlockPatternDialog: Boolean,
onShowUnlockPatternDialogChange: (Boolean) -> Unit,
onUnlock: (String) -> Unit,
unlockErrorMessage: String?,
showPasswordDialog: Boolean,
onShowPasswordDialogChange: (Boolean) -> Unit,
onHasEncryptionPasswordChange: (Boolean) -> Unit,
@@ -103,9 +99,6 @@ fun AppDialogs(
onConfirmLockMethod: (LockMethod) -> Unit,
canUseBiometrics: Boolean,
itemToLockType: de.lxtools.noteshop.ui.LockableItemType?,
showSetPatternDialog: Boolean,
onShowSetPatternDialogChange: (Boolean) -> Unit,
) {
val scope = rememberCoroutineScope()
val dynamicRecipeStrings = getDynamicRecipeStrings(recipesTitle)
@@ -212,27 +205,6 @@ fun AppDialogs(
)
}
if (showSetPatternDialog) {
de.lxtools.noteshop.ui.SetPatternDialog(
onDismiss = { onShowSetPatternDialogChange(false) },
onSetPattern = { pattern ->
when (itemToLockType) {
LockableItemType.NOTE -> notesViewModel.setProtectionPattern(pattern)
LockableItemType.RECIPE -> recipesViewModel.setProtectionPattern(pattern)
LockableItemType.SHOPPING_LIST -> shoppingListsViewModel.setProtectionPattern(pattern)
else -> {}
}
onShowSetPatternDialogChange(false)
}
)
}
if (showPasswordDialog) {
EncryptionPasswordDialog(
onDismiss = { onShowPasswordDialogChange(false) },
@@ -299,16 +271,6 @@ fun AppDialogs(
)
}
if (showUnlockPatternDialog) {
de.lxtools.noteshop.ui.UnlockPatternDialog(
onDismiss = {
onShowUnlockPatternDialogChange(false)
},
onUnlock = onUnlock,
errorMessage = unlockErrorMessage
)
}
if (showChooseLockMethodDialog) {
ChooseLockMethodDialog(
onDismiss = { onShowChooseLockMethodDialogChange(false) },
@@ -318,4 +280,4 @@ fun AppDialogs(
canUseBiometrics = canUseBiometrics
)
}
}
}

View File

@@ -238,8 +238,7 @@
<string name="unlock_item">Unlock Item</string>
<string name="enter_password_to_unlock">Enter password to unlock this item.</string>
<string name="incorrect_password">Incorrect password.</string>
<string name="incorrect_pattern">Incorrect pattern.</string>
<string name="data_encryption">Data Encryption</string>
<string name="set_encryption_password">Set Encryption Password</string>
<string name="change_encryption_password">Change Encryption Password</string>