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

View File

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

View File

@@ -22,7 +22,7 @@ import de.lxtools.noteshop.R
enum class LockMethod { enum class LockMethod {
BIOMETRIC, BIOMETRIC,
PATTERN,
PASSWORD PASSWORD
} }
@@ -53,19 +53,7 @@ fun ChooseLockMethodDialog(
) )
Text(text = stringResource(R.string.biometric_lock), modifier = Modifier.padding(start = 8.dp)) 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( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()

View File

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

View File

@@ -61,7 +61,6 @@ fun JsonImportExportDialog(
biometricAuthenticator.promptBiometricAuth( biometricAuthenticator.promptBiometricAuth(
title = context.getString(R.string.authenticate_for_import_export), title = context.getString(R.string.authenticate_for_import_export),
subtitle = "", subtitle = "",
negativeButtonText = context.getString(R.string.cancel),
fragmentActivity = activity, fragmentActivity = activity,
crypto = crypto, crypto = crypto,
onSuccess = { result -> 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( biometricAuthenticator.promptBiometricAuth(
title = context.getString(R.string.confirm_to_proceed), title = context.getString(R.string.confirm_to_proceed),
subtitle = context.getString(R.string.authenticate_to_perform_action), subtitle = context.getString(R.string.authenticate_to_perform_action),
negativeButtonText = context.getString(R.string.cancel),
fragmentActivity = context as FragmentActivity, fragmentActivity = context as FragmentActivity,
onSuccess = { successAction() }, onSuccess = { successAction() },
onFailed = { onFailed = {

View File

@@ -79,12 +79,8 @@ fun AppDialogs(
onShowSetListPasswordDialogChange: (Boolean) -> Unit, onShowSetListPasswordDialogChange: (Boolean) -> Unit,
showUnlockPasswordDialog: Boolean, showUnlockPasswordDialog: Boolean,
onShowUnlockPasswordDialogChange: (Boolean) -> Unit, onShowUnlockPasswordDialogChange: (Boolean) -> Unit,
showUnlockPatternDialog: Boolean,
onShowUnlockPatternDialogChange: (Boolean) -> Unit,
onUnlock: (String) -> Unit, onUnlock: (String) -> Unit,
unlockErrorMessage: String?, unlockErrorMessage: String?,
showPasswordDialog: Boolean, showPasswordDialog: Boolean,
onShowPasswordDialogChange: (Boolean) -> Unit, onShowPasswordDialogChange: (Boolean) -> Unit,
onHasEncryptionPasswordChange: (Boolean) -> Unit, onHasEncryptionPasswordChange: (Boolean) -> Unit,
@@ -103,9 +99,6 @@ fun AppDialogs(
onConfirmLockMethod: (LockMethod) -> Unit, onConfirmLockMethod: (LockMethod) -> Unit,
canUseBiometrics: Boolean, canUseBiometrics: Boolean,
itemToLockType: de.lxtools.noteshop.ui.LockableItemType?, itemToLockType: de.lxtools.noteshop.ui.LockableItemType?,
showSetPatternDialog: Boolean,
onShowSetPatternDialogChange: (Boolean) -> Unit,
) { ) {
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val dynamicRecipeStrings = getDynamicRecipeStrings(recipesTitle) 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) { if (showPasswordDialog) {
EncryptionPasswordDialog( EncryptionPasswordDialog(
onDismiss = { onShowPasswordDialogChange(false) }, 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) { if (showChooseLockMethodDialog) {
ChooseLockMethodDialog( ChooseLockMethodDialog(
onDismiss = { onShowChooseLockMethodDialogChange(false) }, onDismiss = { onShowChooseLockMethodDialogChange(false) },
@@ -318,4 +280,4 @@ fun AppDialogs(
canUseBiometrics = canUseBiometrics canUseBiometrics = canUseBiometrics
) )
} }
} }

View File

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