diff --git a/.idea/other.xml b/.idea/other.xml
index 9542437..720dd0e 100644
--- a/.idea/other.xml
+++ b/.idea/other.xml
@@ -267,6 +267,17 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/java/com/aritradas/uncrack/MainActivity.kt b/app/src/main/java/com/aritradas/uncrack/MainActivity.kt
index c32f732..bd2eb11 100644
--- a/app/src/main/java/com/aritradas/uncrack/MainActivity.kt
+++ b/app/src/main/java/com/aritradas/uncrack/MainActivity.kt
@@ -14,9 +14,15 @@ import androidx.annotation.RequiresApi
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.core.view.WindowCompat
+import androidx.fragment.app.FragmentActivity
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
import com.aritradas.uncrack.navigation.Navigation
import com.aritradas.uncrack.presentation.settings.SettingsViewModel
+import com.aritradas.uncrack.sharedViewModel.SharedViewModel
import com.aritradas.uncrack.ui.theme.UnCrackTheme
+import com.aritradas.uncrack.util.AppBioMetricManager
import com.aritradas.uncrack.util.NetworkConnectivityObserver
import com.google.android.gms.tasks.Task
import com.google.android.play.core.appupdate.AppUpdateManagerFactory
@@ -25,11 +31,18 @@ import com.google.android.play.core.appupdate.AppUpdateOptions
import com.google.android.play.core.install.model.AppUpdateType
import com.google.android.play.core.install.model.UpdateAvailability
import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+import javax.inject.Inject
@AndroidEntryPoint
-class MainActivity : ComponentActivity() {
+class MainActivity : FragmentActivity() {
private val settingsViewModel: SettingsViewModel by viewModels()
+ private val viewModel: SharedViewModel by viewModels()
+
+ @Inject
+ lateinit var appBioMetricManager: AppBioMetricManager
private val activityResultLauncher = registerForActivityResult(
ActivityResultContracts.StartIntentSenderForResult()
@@ -75,6 +88,28 @@ class MainActivity : ComponentActivity() {
Navigation(this, connectivityObserver)
}
}
+
+ setObserver()
+ }
+
+ private fun setObserver() {
+ lifecycleScope.launch {
+ lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
+ viewModel.initAuth.collect { value ->
+ if (value && viewModel.loading.value) {
+ viewModel.showBiometricPrompt(this@MainActivity)
+ }
+ }
+ }
+ }
+
+ lifecycleScope.launch {
+ lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
+ viewModel.finishActivity.collect { value ->
+ if (value) finish()
+ }
+ }
+ }
}
private fun checkForAppUpdate() {
diff --git a/app/src/main/java/com/aritradas/uncrack/presentation/settings/BiometricAuthListener.kt b/app/src/main/java/com/aritradas/uncrack/presentation/settings/BiometricAuthListener.kt
new file mode 100644
index 0000000..cc69d82
--- /dev/null
+++ b/app/src/main/java/com/aritradas/uncrack/presentation/settings/BiometricAuthListener.kt
@@ -0,0 +1,7 @@
+package com.aritradas.uncrack.presentation.settings
+
+interface BiometricAuthListener {
+ fun onBiometricAuthSuccess()
+ fun onUserCancelled()
+ fun onErrorOccurred()
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/aritradas/uncrack/presentation/settings/SettingsScreen.kt b/app/src/main/java/com/aritradas/uncrack/presentation/settings/SettingsScreen.kt
index f73b598..9e04a3d 100644
--- a/app/src/main/java/com/aritradas/uncrack/presentation/settings/SettingsScreen.kt
+++ b/app/src/main/java/com/aritradas/uncrack/presentation/settings/SettingsScreen.kt
@@ -18,6 +18,7 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf
@@ -25,10 +26,12 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController
+import com.aritradas.uncrack.MainActivity
import com.aritradas.uncrack.R
import com.aritradas.uncrack.components.SettingsItemGroup
import com.aritradas.uncrack.components.ThemeDialog
@@ -51,9 +54,11 @@ fun SettingsScreen(
modifier: Modifier = Modifier
) {
+ val context = LocalContext.current
val isScreenshotEnabled by settingsViewModel.isScreenshotEnabled.observeAsState(false)
val onLogOutComplete by settingsViewModel.onLogOutComplete.observeAsState(false)
val onDeleteAccountComplete by settingsViewModel.onDeleteAccountComplete.observeAsState(false)
+ val biometricAuthState by settingsViewModel.biometricAuthState.collectAsState()
var openThemeDialog by remember { mutableStateOf(false) }
var openLogoutDialog by remember { mutableStateOf(false) }
var openDeleteAccountDialog by remember { mutableStateOf(false) }
@@ -228,8 +233,10 @@ fun SettingsScreen(
UCSwitchCard(
itemName = stringResource(R.string.unlock_with_biometric),
- isChecked = false,
- onChecked = {}
+ isChecked = biometricAuthState,
+ onChecked = {
+ settingsViewModel.showBiometricPrompt(context as MainActivity)
+ }
)
HorizontalDivider(
diff --git a/app/src/main/java/com/aritradas/uncrack/presentation/settings/SettingsViewModel.kt b/app/src/main/java/com/aritradas/uncrack/presentation/settings/SettingsViewModel.kt
index 53e5908..5707788 100644
--- a/app/src/main/java/com/aritradas/uncrack/presentation/settings/SettingsViewModel.kt
+++ b/app/src/main/java/com/aritradas/uncrack/presentation/settings/SettingsViewModel.kt
@@ -1,22 +1,34 @@
package com.aritradas.uncrack.presentation.settings
+import androidx.datastore.preferences.core.edit
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.aritradas.uncrack.MainActivity
+import com.aritradas.uncrack.data.datastore.DataStoreUtil
import com.aritradas.uncrack.domain.repository.AccountRepository
import com.aritradas.uncrack.domain.repository.KeyRepository
+import com.aritradas.uncrack.util.AppBioMetricManager
import com.aritradas.uncrack.util.runIO
import com.google.firebase.Firebase
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.auth
import com.google.firebase.firestore.FirebaseFirestore
import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class SettingsViewModel @Inject constructor(
private val keyRepository: KeyRepository,
- private val accountRepository: AccountRepository
+ private val accountRepository: AccountRepository,
+ private val appBioMetricManager: AppBioMetricManager,
+ dataStoreUtil: DataStoreUtil
): ViewModel() {
private val auth = Firebase.auth
@@ -26,6 +38,41 @@ class SettingsViewModel @Inject constructor(
val isScreenshotEnabled: LiveData get() = _isScreenshotEnabled
private val user = auth.currentUser
private val userDB = FirebaseFirestore.getInstance()
+ private val dataStore = dataStoreUtil.dataStore
+ private val _biometricAuthState = MutableStateFlow(false)
+ val biometricAuthState: StateFlow = _biometricAuthState
+
+ init {
+ viewModelScope.launch(Dispatchers.IO) {
+ dataStore.data.map { preferences ->
+ preferences[DataStoreUtil.IS_BIOMETRIC_AUTH_SET_KEY] ?: false
+ }.collect {
+ _biometricAuthState.value = it
+ }
+ }
+ }
+
+ fun showBiometricPrompt(activity: MainActivity) {
+ appBioMetricManager.initBiometricPrompt(
+ activity = activity,
+ listener = object : BiometricAuthListener {
+ override fun onBiometricAuthSuccess() {
+ viewModelScope.launch {
+ dataStore.edit { preferences ->
+ preferences[DataStoreUtil.IS_BIOMETRIC_AUTH_SET_KEY] =
+ !_biometricAuthState.value
+ }
+ }
+ }
+
+ override fun onUserCancelled() {
+ }
+
+ override fun onErrorOccurred() {
+ }
+ }
+ )
+ }
fun setScreenshotEnabled(enabled: Boolean) {
_isScreenshotEnabled.value = enabled
diff --git a/app/src/main/java/com/aritradas/uncrack/sharedViewModel/SharedViewModel.kt b/app/src/main/java/com/aritradas/uncrack/sharedViewModel/SharedViewModel.kt
new file mode 100644
index 0000000..e9c8c78
--- /dev/null
+++ b/app/src/main/java/com/aritradas/uncrack/sharedViewModel/SharedViewModel.kt
@@ -0,0 +1,77 @@
+package com.aritradas.uncrack.sharedViewModel
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.aritradas.uncrack.MainActivity
+import com.aritradas.uncrack.data.datastore.DataStoreUtil
+import com.aritradas.uncrack.presentation.settings.BiometricAuthListener
+import com.aritradas.uncrack.util.AppBioMetricManager
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class SharedViewModel @Inject constructor(
+ private val appBioMetricManager: AppBioMetricManager,
+ dataStoreUtil: DataStoreUtil,
+) : ViewModel() {
+
+ private val dataStore = dataStoreUtil.dataStore
+
+ private val _loading = MutableStateFlow(true)
+ val loading: StateFlow = _loading.asStateFlow()
+
+ private val _initAuth = MutableStateFlow(false)
+ val initAuth: StateFlow = _initAuth.asStateFlow()
+
+ private val _finishActivity = MutableStateFlow(false)
+ val finishActivity: StateFlow = _finishActivity.asStateFlow()
+
+ init {
+ viewModelScope.launch(Dispatchers.IO) {
+ dataStore.data.map { preferences ->
+ preferences[DataStoreUtil.IS_BIOMETRIC_AUTH_SET_KEY] ?: false
+ }.collect { biometricAuthState ->
+ if (biometricAuthState && appBioMetricManager.canAuthenticate()) {
+ _initAuth.emit(true)
+ } else {
+ delay(1_000L)
+ _loading.emit(false)
+ }
+ }
+ }
+ }
+
+ fun showBiometricPrompt(mainActivity: MainActivity) {
+ appBioMetricManager.initBiometricPrompt(
+ activity = mainActivity,
+ listener = object : BiometricAuthListener {
+ override fun onBiometricAuthSuccess() {
+ viewModelScope.launch {
+ _loading.emit(false)
+ }
+ }
+
+ override fun onUserCancelled() {
+ finishActivity()
+ }
+
+ override fun onErrorOccurred() {
+ finishActivity()
+ }
+ }
+ )
+ }
+
+ private fun finishActivity() {
+ viewModelScope.launch {
+ _finishActivity.emit(true)
+ }
+ }
+}
\ No newline at end of file