Remove biometric lock screen
This commit is contained in:
parent
5ae7ea0fc7
commit
19e6e3d1c0
@ -19,10 +19,7 @@ data class DeterministicState(
|
|||||||
val masterPassword: CharArray = CharArray(0),
|
val masterPassword: CharArray = CharArray(0),
|
||||||
val masterPasswordVisible: Boolean = false,
|
val masterPasswordVisible: Boolean = false,
|
||||||
val generatedPassword: String = "",
|
val generatedPassword: String = "",
|
||||||
val isLocked: Boolean = false,
|
|
||||||
val clipboardTimerSeconds: Int = 0,
|
val clipboardTimerSeconds: Int = 0,
|
||||||
val biometricEnabled: Boolean = false,
|
|
||||||
val lockTimeoutMinutes: Int = 5,
|
|
||||||
val showRotateWarning: Boolean = false,
|
val showRotateWarning: Boolean = false,
|
||||||
val serviceHistory: List<String> = emptyList(),
|
val serviceHistory: List<String> = emptyList(),
|
||||||
) {
|
) {
|
||||||
@ -34,10 +31,7 @@ data class DeterministicState(
|
|||||||
masterPassword.contentEquals(other.masterPassword) &&
|
masterPassword.contentEquals(other.masterPassword) &&
|
||||||
masterPasswordVisible == other.masterPasswordVisible &&
|
masterPasswordVisible == other.masterPasswordVisible &&
|
||||||
generatedPassword == other.generatedPassword &&
|
generatedPassword == other.generatedPassword &&
|
||||||
isLocked == other.isLocked &&
|
|
||||||
clipboardTimerSeconds == other.clipboardTimerSeconds &&
|
clipboardTimerSeconds == other.clipboardTimerSeconds &&
|
||||||
biometricEnabled == other.biometricEnabled &&
|
|
||||||
lockTimeoutMinutes == other.lockTimeoutMinutes &&
|
|
||||||
showRotateWarning == other.showRotateWarning &&
|
showRotateWarning == other.showRotateWarning &&
|
||||||
serviceHistory == other.serviceHistory
|
serviceHistory == other.serviceHistory
|
||||||
}
|
}
|
||||||
@ -47,10 +41,7 @@ data class DeterministicState(
|
|||||||
result = 31 * result + masterPassword.contentHashCode()
|
result = 31 * result + masterPassword.contentHashCode()
|
||||||
result = 31 * result + masterPasswordVisible.hashCode()
|
result = 31 * result + masterPasswordVisible.hashCode()
|
||||||
result = 31 * result + generatedPassword.hashCode()
|
result = 31 * result + generatedPassword.hashCode()
|
||||||
result = 31 * result + isLocked.hashCode()
|
|
||||||
result = 31 * result + clipboardTimerSeconds
|
result = 31 * result + clipboardTimerSeconds
|
||||||
result = 31 * result + biometricEnabled.hashCode()
|
|
||||||
result = 31 * result + lockTimeoutMinutes
|
|
||||||
result = 31 * result + showRotateWarning.hashCode()
|
result = 31 * result + showRotateWarning.hashCode()
|
||||||
result = 31 * result + serviceHistory.hashCode()
|
result = 31 * result + serviceHistory.hashCode()
|
||||||
return result
|
return result
|
||||||
@ -66,18 +57,10 @@ class DeterministicViewModel(app: Application) : AndroidViewModel(app) {
|
|||||||
private val _state = MutableStateFlow(DeterministicState())
|
private val _state = MutableStateFlow(DeterministicState())
|
||||||
val state: StateFlow<DeterministicState> = _state.asStateFlow()
|
val state: StateFlow<DeterministicState> = _state.asStateFlow()
|
||||||
|
|
||||||
private val settingsPrefs = app.getSharedPreferences("det_settings", Context.MODE_PRIVATE)
|
|
||||||
|
|
||||||
private var clipboardClearJob: Job? = null
|
private var clipboardClearJob: Job? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
_state.update {
|
_state.update { it.copy(serviceHistory = historyRepository.getNames()) }
|
||||||
it.copy(
|
|
||||||
biometricEnabled = settingsPrefs.getBoolean("biometric_enabled", false),
|
|
||||||
lockTimeoutMinutes = settingsPrefs.getInt("lock_timeout_minutes", 5),
|
|
||||||
serviceHistory = historyRepository.getNames(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateServiceName(name: String) = _state.update { it.copy(serviceName = name.lowercase()) }
|
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 getDeviceSecret(): ByteArray = deviceSecretManager.exportSecret()
|
||||||
|
|
||||||
fun importDeviceSecret(secret: ByteArray) = deviceSecretManager.importSecret(secret)
|
fun importDeviceSecret(secret: ByteArray) = deviceSecretManager.importSecret(secret)
|
||||||
|
|||||||
@ -180,7 +180,6 @@ fun AppNavigation() {
|
|||||||
popExitTransition = { slideOutHorizontally(tween(300)) { it } },
|
popExitTransition = { slideOutHorizontally(tween(300)) { it } },
|
||||||
) {
|
) {
|
||||||
SettingsScreen(
|
SettingsScreen(
|
||||||
viewModel = detViewModel,
|
|
||||||
onNavigateBack = { navController.popBackStack() },
|
onNavigateBack = { navController.popBackStack() },
|
||||||
onNavigateToExportImport = { navController.navigate(ROUTE_EXPORT_IMPORT) },
|
onNavigateToExportImport = { navController.navigate(ROUTE_EXPORT_IMPORT) },
|
||||||
)
|
)
|
||||||
|
|||||||
@ -81,10 +81,6 @@ fun DeterministicScreen(
|
|||||||
val state by viewModel.state.collectAsState()
|
val state by viewModel.state.collectAsState()
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
BackHandler { (context as? android.app.Activity)?.finish() }
|
BackHandler { (context as? android.app.Activity)?.finish() }
|
||||||
if (state.isLocked) {
|
|
||||||
LockScreen(viewModel = viewModel)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var showRotateConfirmDialog by remember { mutableStateOf(false) }
|
var showRotateConfirmDialog by remember { mutableStateOf(false) }
|
||||||
val masterPasswordFocus = remember { FocusRequester() }
|
val masterPasswordFocus = remember { FocusRequester() }
|
||||||
|
|||||||
@ -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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user