package com.syncflow import android.Manifest import android.content.pm.PackageManager import android.os.Build import android.os.Bundle import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity import androidx.biometric.BiometricManager import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_WEAK import androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL import androidx.biometric.BiometricPrompt import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box 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.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.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.core.content.ContextCompat import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.lifecycle.lifecycleScope import androidx.navigation.compose.rememberNavController import com.syncflow.data.preferences.AppPreferences import com.syncflow.ui.navigation.SyncFlowNavGraph import com.syncflow.ui.theme.SyncFlowTheme import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import javax.inject.Inject @AndroidEntryPoint class MainActivity : AppCompatActivity() { @Inject lateinit var appPreferences: AppPreferences private var isLocked by mutableStateOf(false) private var showRetry by mutableStateOf(false) private val requestNotificationPermission = registerForActivityResult(ActivityResultContracts.RequestPermission()) { /* proceed regardless */ } override fun onCreate(savedInstanceState: Bundle?) { installSplashScreen() super.onCreate(savedInstanceState) enableEdgeToEdge() requestNotificationPermissionIfNeeded() setContent { SyncFlowTheme { Surface(modifier = Modifier.fillMaxSize()) { SyncFlowNavGraph(rememberNavController()) } if (isLocked) { LockOverlay( showRetry = showRetry, onRetry = { triggerBiometric() }, ) } } } } override fun onResume() { super.onResume() if (isLocked) triggerBiometric() } override fun onStop() { super.onStop() if (isChangingConfigurations) return lifecycleScope.launch { if (appPreferences.biometricLockEnabled.first() && canAuthenticate()) { isLocked = true showRetry = false } } } private fun triggerBiometric() { showRetry = false val authenticators = bestAuthenticators() val executor = ContextCompat.getMainExecutor(this) val prompt = BiometricPrompt(this, executor, object : BiometricPrompt.AuthenticationCallback() { override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) { isLocked = false showRetry = false } override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { // Show the Unlock button so the user can tap to retry manually showRetry = true } override fun onAuthenticationFailed() { // Wrong biometric — BiometricPrompt retries automatically } }) val promptInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { BiometricPrompt.PromptInfo.Builder() .setTitle("Unlock SyncFlow") .setSubtitle("Use fingerprint or PIN") .setAllowedAuthenticators(authenticators) .build() } else { BiometricPrompt.PromptInfo.Builder() .setTitle("Unlock SyncFlow") .setSubtitle("Use fingerprint") .setNegativeButtonText("Cancel") .build() } prompt.authenticate(promptInfo) } private fun bestAuthenticators(): Int { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) return BIOMETRIC_STRONG val bm = BiometricManager.from(this) // Prefer strong+credential; fall back to weak+credential so side-sensor phones work return if (bm.canAuthenticate(BIOMETRIC_STRONG or DEVICE_CREDENTIAL) == BiometricManager.BIOMETRIC_SUCCESS) BIOMETRIC_STRONG or DEVICE_CREDENTIAL else BIOMETRIC_WEAK or DEVICE_CREDENTIAL } private fun requestNotificationPermissionIfNeeded() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED ) { requestNotificationPermission.launch(Manifest.permission.POST_NOTIFICATIONS) } } } private fun canAuthenticate(): Boolean { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) return BiometricManager.from(this).canAuthenticate(BIOMETRIC_STRONG) == BiometricManager.BIOMETRIC_SUCCESS val bm = BiometricManager.from(this) return bm.canAuthenticate(BIOMETRIC_STRONG or DEVICE_CREDENTIAL) == BiometricManager.BIOMETRIC_SUCCESS || bm.canAuthenticate(BIOMETRIC_WEAK or DEVICE_CREDENTIAL) == BiometricManager.BIOMETRIC_SUCCESS } } @Composable private fun LockOverlay(showRetry: Boolean, onRetry: () -> Unit) { Box( modifier = Modifier .fillMaxSize() .background(MaterialTheme.colorScheme.background), contentAlignment = Alignment.Center, ) { Column( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(12.dp), ) { Icon(Icons.Default.Lock, null, modifier = Modifier.size(56.dp), tint = MaterialTheme.colorScheme.primary) Text("SyncFlow is locked", style = MaterialTheme.typography.titleMedium) if (showRetry) { Button(onClick = onRetry) { Text("Unlock") } } else { Text( "Use fingerprint or PIN to unlock", style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant, ) } } } }