Remove biometric lock screen

This commit is contained in:
pavelb 2026-03-06 20:21:24 +01:00
parent 15e075b293
commit 61cb32a5f5
4 changed files with 1 additions and 141 deletions

View File

@ -19,10 +19,7 @@ data class DeterministicState(
val masterPassword: CharArray = CharArray(0),
val masterPasswordVisible: Boolean = false,
val generatedPassword: String = "",
val isLocked: Boolean = false,
val clipboardTimerSeconds: Int = 0,
val biometricEnabled: Boolean = false,
val lockTimeoutMinutes: Int = 5,
val showRotateWarning: Boolean = false,
val serviceHistory: List<String> = emptyList(),
) {
@ -34,10 +31,7 @@ data class DeterministicState(
masterPassword.contentEquals(other.masterPassword) &&
masterPasswordVisible == other.masterPasswordVisible &&
generatedPassword == other.generatedPassword &&
isLocked == other.isLocked &&
clipboardTimerSeconds == other.clipboardTimerSeconds &&
biometricEnabled == other.biometricEnabled &&
lockTimeoutMinutes == other.lockTimeoutMinutes &&
showRotateWarning == other.showRotateWarning &&
serviceHistory == other.serviceHistory
}
@ -47,10 +41,7 @@ data class DeterministicState(
result = 31 * result + masterPassword.contentHashCode()
result = 31 * result + masterPasswordVisible.hashCode()
result = 31 * result + generatedPassword.hashCode()
result = 31 * result + isLocked.hashCode()
result = 31 * result + clipboardTimerSeconds
result = 31 * result + biometricEnabled.hashCode()
result = 31 * result + lockTimeoutMinutes
result = 31 * result + showRotateWarning.hashCode()
result = 31 * result + serviceHistory.hashCode()
return result
@ -66,18 +57,10 @@ class DeterministicViewModel(app: Application) : AndroidViewModel(app) {
private val _state = MutableStateFlow(DeterministicState())
val state: StateFlow<DeterministicState> = _state.asStateFlow()
private val settingsPrefs = app.getSharedPreferences("det_settings", Context.MODE_PRIVATE)
private var clipboardClearJob: Job? = null
init {
_state.update {
it.copy(
biometricEnabled = settingsPrefs.getBoolean("biometric_enabled", false),
lockTimeoutMinutes = settingsPrefs.getInt("lock_timeout_minutes", 5),
serviceHistory = historyRepository.getNames(),
)
}
_state.update { it.copy(serviceHistory = historyRepository.getNames()) }
}
fun updateServiceName(name: String) = _state.update { it.copy(serviceName = name.lowercase()) }
@ -143,27 +126,6 @@ class DeterministicViewModel(app: Application) : AndroidViewModel(app) {
}
}
fun lock() = _state.update { it.copy(isLocked = true) }
fun unlock() = _state.update { it.copy(isLocked = false) }
fun onAppForeground(lastBackgroundTimeMs: Long) {
val timeoutMs = _state.value.lockTimeoutMinutes * 60 * 1000L
if (timeoutMs > 0 && System.currentTimeMillis() - lastBackgroundTimeMs >= timeoutMs) {
lock()
}
}
fun setBiometricEnabled(enabled: Boolean) {
settingsPrefs.edit().putBoolean("biometric_enabled", enabled).apply()
_state.update { it.copy(biometricEnabled = enabled) }
}
fun setLockTimeoutMinutes(minutes: Int) {
settingsPrefs.edit().putInt("lock_timeout_minutes", minutes).apply()
_state.update { it.copy(lockTimeoutMinutes = minutes) }
}
fun getDeviceSecret(): ByteArray = deviceSecretManager.exportSecret()
fun importDeviceSecret(secret: ByteArray) = deviceSecretManager.importSecret(secret)

View File

@ -180,7 +180,6 @@ fun AppNavigation() {
popExitTransition = { slideOutHorizontally(tween(300)) { it } },
) {
SettingsScreen(
viewModel = detViewModel,
onNavigateBack = { navController.popBackStack() },
onNavigateToExportImport = { navController.navigate(ROUTE_EXPORT_IMPORT) },
)

View File

@ -81,10 +81,6 @@ fun DeterministicScreen(
val state by viewModel.state.collectAsState()
val context = LocalContext.current
BackHandler { (context as? android.app.Activity)?.finish() }
if (state.isLocked) {
LockScreen(viewModel = viewModel)
return
}
var showRotateConfirmDialog by remember { mutableStateOf(false) }
val masterPasswordFocus = remember { FocusRequester() }

View File

@ -1,97 +0,0 @@
package cz.bugsy.passwordzebra.ui.screens
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricPrompt
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Lock
import androidx.compose.material3.Button
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat
import androidx.fragment.app.FragmentActivity
import cz.bugsy.passwordzebra.R
import cz.bugsy.passwordzebra.deterministic.DeterministicViewModel
@Composable
fun LockScreen(viewModel: DeterministicViewModel) {
val state by viewModel.state.collectAsState()
val context = LocalContext.current
fun launchBiometric() {
val activity = context as? FragmentActivity ?: return
val executor = ContextCompat.getMainExecutor(context)
val prompt = BiometricPrompt(
activity,
executor,
object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
viewModel.unlock()
}
},
)
val promptInfo = BiometricPrompt.PromptInfo.Builder()
.setTitle(context.getString(R.string.det_biometric_prompt_title))
.setSubtitle(context.getString(R.string.det_biometric_prompt_subtitle))
.setNegativeButtonText(context.getString(R.string.cancel))
.build()
prompt.authenticate(promptInfo)
}
LaunchedEffect(Unit) {
if (state.biometricEnabled) {
launchBiometric()
}
}
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(32.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
) {
Icon(
imageVector = Icons.Default.Lock,
contentDescription = null,
modifier = Modifier.size(64.dp),
tint = MaterialTheme.colorScheme.primary,
)
Spacer(modifier = Modifier.height(24.dp))
Text(
text = stringResource(R.string.det_locked_title),
style = MaterialTheme.typography.headlineMedium,
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = stringResource(R.string.det_locked_body),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
Spacer(modifier = Modifier.height(32.dp))
if (state.biometricEnabled) {
Button(onClick = { launchBiometric() }) {
Text(stringResource(R.string.det_unlock_biometric))
}
}
}
}
}