Add biometric lock and rate limit handling
Biometric lock: opt-in Face ID/Touch ID/fingerprint app lock with toggle in ProfileScreen. Locks on background, requires auth on foreground return. Platform implementations: BiometricPrompt (Android), LAContext (iOS). Rate limit: 429 responses parsed with Retry-After header, user-friendly error messages in all 10 locales, retry plugin respects 429. ErrorMessageParser updated for both iOS Swift and KMM.
This commit is contained in:
@@ -60,6 +60,12 @@ import com.tt.honeyDue.network.APILayer
|
||||
import com.tt.honeyDue.platform.ContractorImportHandler
|
||||
import com.tt.honeyDue.platform.PlatformUpgradeScreen
|
||||
import com.tt.honeyDue.platform.ResidenceImportHandler
|
||||
import com.tt.honeyDue.storage.BiometricPreference
|
||||
import com.tt.honeyDue.ui.screens.BiometricLockScreen
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleEventObserver
|
||||
import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||
|
||||
import honeydue.composeapp.generated.resources.Res
|
||||
import honeydue.composeapp.generated.resources.compose_multiplatform
|
||||
@@ -82,6 +88,28 @@ fun App(
|
||||
var hasCompletedOnboarding by remember { mutableStateOf(DataManager.hasCompletedOnboarding.value) }
|
||||
val navController = rememberNavController()
|
||||
|
||||
// Biometric lock state - starts locked if biometric is enabled and user is logged in
|
||||
var isLocked by rememberSaveable {
|
||||
mutableStateOf(BiometricPreference.isBiometricEnabled() && DataManager.authToken.value != null)
|
||||
}
|
||||
|
||||
// Lock the app when returning from background
|
||||
val lifecycleOwner = LocalLifecycleOwner.current
|
||||
DisposableEffect(lifecycleOwner) {
|
||||
val observer = LifecycleEventObserver { _, event ->
|
||||
if (event == Lifecycle.Event.ON_STOP) {
|
||||
// App going to background - lock if biometric is enabled
|
||||
if (BiometricPreference.isBiometricEnabled() && DataManager.authToken.value != null) {
|
||||
isLocked = true
|
||||
}
|
||||
}
|
||||
}
|
||||
lifecycleOwner.lifecycle.addObserver(observer)
|
||||
onDispose {
|
||||
lifecycleOwner.lifecycle.removeObserver(observer)
|
||||
}
|
||||
}
|
||||
|
||||
// Handle navigation from notification tap
|
||||
// Note: The actual navigation to the task column happens in MainScreen -> AllTasksScreen
|
||||
// We just need to ensure the user is on MainRoute when a task navigation is requested
|
||||
@@ -156,6 +184,7 @@ fun App(
|
||||
else -> MainRoute
|
||||
}
|
||||
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
color = MaterialTheme.colorScheme.background
|
||||
@@ -692,6 +721,14 @@ fun App(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Biometric lock overlay - shown when app is locked
|
||||
if (isLocked && isLoggedIn && isVerified) {
|
||||
BiometricLockScreen(
|
||||
onUnlocked = { isLocked = false }
|
||||
)
|
||||
}
|
||||
} // close Box
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user