cff4233de6
Supports WebDAV, SFTP, SFTPGo, Nextcloud, ownCloud, Google Drive, Dropbox, and OneDrive. Credentials encrypted with Android Keystore. Biometric app-lock, conflict resolution, and auto-sync via WorkManager. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
113 lines
4.0 KiB
Kotlin
113 lines
4.0 KiB
Kotlin
package com.syncflow
|
|
|
|
import android.os.Build
|
|
import android.os.Bundle
|
|
import androidx.activity.compose.setContent
|
|
import androidx.activity.enableEdgeToEdge
|
|
import androidx.appcompat.app.AppCompatActivity
|
|
import androidx.biometric.BiometricManager
|
|
import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG
|
|
import androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL
|
|
import androidx.biometric.BiometricPrompt
|
|
import androidx.compose.foundation.background
|
|
import androidx.compose.foundation.layout.Box
|
|
import androidx.compose.foundation.layout.fillMaxSize
|
|
import androidx.compose.material3.CircularProgressIndicator
|
|
import androidx.compose.material3.MaterialTheme
|
|
import androidx.compose.material3.Surface
|
|
import androidx.compose.runtime.*
|
|
import androidx.compose.ui.Alignment
|
|
import androidx.compose.ui.Modifier
|
|
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)
|
|
|
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
installSplashScreen()
|
|
super.onCreate(savedInstanceState)
|
|
enableEdgeToEdge()
|
|
setContent {
|
|
SyncFlowTheme {
|
|
Surface(modifier = Modifier.fillMaxSize()) {
|
|
SyncFlowNavGraph(rememberNavController())
|
|
}
|
|
if (isLocked) {
|
|
LockOverlay()
|
|
LaunchedEffect(Unit) {
|
|
showBiometricPrompt(onSuccess = { isLocked = false })
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
override fun onStop() {
|
|
super.onStop()
|
|
if (isChangingConfigurations) return
|
|
lifecycleScope.launch {
|
|
if (appPreferences.biometricLockEnabled.first() && canAuthenticate()) {
|
|
isLocked = true
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun canAuthenticate(): Boolean {
|
|
val authenticators = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)
|
|
BIOMETRIC_STRONG or DEVICE_CREDENTIAL
|
|
else
|
|
BIOMETRIC_STRONG
|
|
return BiometricManager.from(this).canAuthenticate(authenticators) ==
|
|
BiometricManager.BIOMETRIC_SUCCESS
|
|
}
|
|
|
|
private fun showBiometricPrompt(onSuccess: () -> Unit) {
|
|
val executor = ContextCompat.getMainExecutor(this)
|
|
val prompt = BiometricPrompt(this, executor, object : BiometricPrompt.AuthenticationCallback() {
|
|
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
|
|
onSuccess()
|
|
}
|
|
})
|
|
val promptInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
BiometricPrompt.PromptInfo.Builder()
|
|
.setTitle("Unlock SyncFlow")
|
|
.setSubtitle("Confirm your identity to continue")
|
|
.setAllowedAuthenticators(BIOMETRIC_STRONG or DEVICE_CREDENTIAL)
|
|
.build()
|
|
} else {
|
|
BiometricPrompt.PromptInfo.Builder()
|
|
.setTitle("Unlock SyncFlow")
|
|
.setSubtitle("Confirm your identity to continue")
|
|
.setNegativeButtonText("Cancel")
|
|
.build()
|
|
}
|
|
prompt.authenticate(promptInfo)
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
private fun LockOverlay() {
|
|
Box(
|
|
modifier = Modifier
|
|
.fillMaxSize()
|
|
.background(MaterialTheme.colorScheme.background),
|
|
contentAlignment = Alignment.Center,
|
|
) {
|
|
CircularProgressIndicator()
|
|
}
|
|
}
|