diff --git a/.idea/.name b/.idea/.name
new file mode 100644
index 00000000..e510c497
--- /dev/null
+++ b/.idea/.name
@@ -0,0 +1 @@
+UnCrack
\ No newline at end of file
diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml
new file mode 100644
index 00000000..cf304975
--- /dev/null
+++ b/.idea/deploymentTargetSelector.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/other.xml b/.idea/other.xml
new file mode 100644
index 00000000..720dd0e3
--- /dev/null
+++ b/.idea/other.xml
@@ -0,0 +1,384 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 0342da91..31775f82 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -126,7 +126,7 @@ dependencies {
implementation("androidx.hilt:hilt-navigation-compose:1.2.0")
// Datastore
- implementation("androidx.datastore:datastore-preferences:1.0.0")
+ implementation("androidx.datastore:datastore-preferences:1.1.2")
// Lottie Animation
implementation("com.airbnb.android:lottie-compose:5.0.3")
@@ -163,4 +163,6 @@ dependencies {
// BCrypt
implementation("org.mindrot:jbcrypt:0.4")
+
+ implementation("androidx.biometric:biometric:1.2.0-alpha05")
}
\ No newline at end of file
diff --git a/app/release/app-release.aab b/app/release/app-release.aab
new file mode 100644
index 00000000..c15cfeac
Binary files /dev/null and b/app/release/app-release.aab differ
diff --git a/app/src/main/java/com/aritradas/uncrack/MainActivity.kt b/app/src/main/java/com/aritradas/uncrack/MainActivity.kt
index c32f7327..bd2eb114 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/components/SettingsItemGroup.kt b/app/src/main/java/com/aritradas/uncrack/components/SettingsItemGroup.kt
index 9cf6bcc1..2bd42d56 100644
--- a/app/src/main/java/com/aritradas/uncrack/components/SettingsItemGroup.kt
+++ b/app/src/main/java/com/aritradas/uncrack/components/SettingsItemGroup.kt
@@ -12,6 +12,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.dp
import com.aritradas.uncrack.ui.theme.BackgroundLight
+import com.aritradas.uncrack.ui.theme.SurfaceVariantLight
@Composable
fun SettingsItemGroup(
@@ -21,9 +22,9 @@ fun SettingsItemGroup(
Column(
modifier = modifier
.widthIn(max = 500.dp)
- .padding(start = 12.dp, end = 12.dp)
- .clip(RoundedCornerShape(8.dp))
- .background(BackgroundLight),
+ .padding(horizontal = 12.dp)
+ .clip(RoundedCornerShape(10.dp))
+ .background(SurfaceVariantLight),
horizontalAlignment = Alignment.CenterHorizontally
) {
columnScope()
diff --git a/app/src/main/java/com/aritradas/uncrack/data/datastore/DataStoreUtil.kt b/app/src/main/java/com/aritradas/uncrack/data/datastore/DataStoreUtil.kt
index ad42dc7c..691b085b 100644
--- a/app/src/main/java/com/aritradas/uncrack/data/datastore/DataStoreUtil.kt
+++ b/app/src/main/java/com/aritradas/uncrack/data/datastore/DataStoreUtil.kt
@@ -15,5 +15,6 @@ class DataStoreUtil @Inject constructor(context: Context) {
private val Context.dataStore: DataStore by preferencesDataStore("settings")
val IS_DARK_MODE_KEY = booleanPreferencesKey("dark_mode")
val IS_SS_BLOCK_KEY = booleanPreferencesKey("ss_block")
+ val IS_BIOMETRIC_AUTH_SET_KEY = booleanPreferencesKey("biometric_auth")
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/aritradas/uncrack/di/AppModule.kt b/app/src/main/java/com/aritradas/uncrack/di/AppModule.kt
index df01ca37..db62fbd8 100644
--- a/app/src/main/java/com/aritradas/uncrack/di/AppModule.kt
+++ b/app/src/main/java/com/aritradas/uncrack/di/AppModule.kt
@@ -27,6 +27,11 @@ import javax.inject.Singleton
@InstallIn(SingletonComponent::class)
object AppModule {
+ @Provides
+ fun provideContext(@ApplicationContext context: Context): Context {
+ return context
+ }
+
@Provides
fun provideDataStoreUtil(@ApplicationContext context: Context): DataStoreUtil =
DataStoreUtil(context)
diff --git a/app/src/main/java/com/aritradas/uncrack/di/BioMetricUtil.kt b/app/src/main/java/com/aritradas/uncrack/di/BioMetricUtil.kt
new file mode 100644
index 00000000..cf2f0f91
--- /dev/null
+++ b/app/src/main/java/com/aritradas/uncrack/di/BioMetricUtil.kt
@@ -0,0 +1,18 @@
+package com.aritradas.uncrack.di
+
+import android.content.Context
+import com.aritradas.uncrack.util.AppBioMetricManager
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.android.components.ViewModelComponent
+
+@Module
+@InstallIn(ViewModelComponent::class)
+class BioMetricUtil {
+
+ @Provides
+ fun provideAppBioMetricManager(context: Context): AppBioMetricManager {
+ return AppBioMetricManager(context)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/aritradas/uncrack/di/ViewModelModule.kt b/app/src/main/java/com/aritradas/uncrack/di/ViewModelModule.kt
new file mode 100644
index 00000000..c4491818
--- /dev/null
+++ b/app/src/main/java/com/aritradas/uncrack/di/ViewModelModule.kt
@@ -0,0 +1,22 @@
+package com.aritradas.uncrack.di
+
+import com.aritradas.uncrack.data.datastore.DataStoreUtil
+import com.aritradas.uncrack.sharedViewModel.SharedViewModel
+import com.aritradas.uncrack.util.AppBioMetricManager
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.android.components.ViewModelComponent
+
+@Module
+@InstallIn(ViewModelComponent::class)
+class ViewModelModule {
+
+ @Provides
+ fun provideMainViewModel(
+ bioMetricManager: AppBioMetricManager,
+ dataStoreUtil: DataStoreUtil,
+ ): SharedViewModel {
+ return SharedViewModel(bioMetricManager, dataStoreUtil)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/aritradas/uncrack/navigation/Navigation.kt b/app/src/main/java/com/aritradas/uncrack/navigation/Navigation.kt
index b061f3cd..8fbfe7b7 100644
--- a/app/src/main/java/com/aritradas/uncrack/navigation/Navigation.kt
+++ b/app/src/main/java/com/aritradas/uncrack/navigation/Navigation.kt
@@ -70,6 +70,7 @@ import com.aritradas.uncrack.ui.theme.FadeOut
import com.aritradas.uncrack.ui.theme.OnPrimaryContainerLight
import com.aritradas.uncrack.ui.theme.OnSurfaceVariantLight
import com.aritradas.uncrack.ui.theme.PrimaryDark
+import com.aritradas.uncrack.ui.theme.SurfaceVariantLight
import com.aritradas.uncrack.util.BackPressHandler
import com.aritradas.uncrack.util.ConnectivityObserver
import kotlinx.collections.immutable.ImmutableList
@@ -156,6 +157,7 @@ fun Navigation(
composable(Screen.SignUpScreen.name) {
SignupScreen(
+ navController,
authViewModel,
connectivityObserver,
onSignUp = {
@@ -271,7 +273,6 @@ fun Navigation(
composable(route = Screen.SettingsScreen.name) {
SettingsScreen(
- activity,
navController,
settingsViewModel
)
diff --git a/app/src/main/java/com/aritradas/uncrack/presentation/auth/login/LoginScreen.kt b/app/src/main/java/com/aritradas/uncrack/presentation/auth/login/LoginScreen.kt
index 711bcb91..fcf94a86 100644
--- a/app/src/main/java/com/aritradas/uncrack/presentation/auth/login/LoginScreen.kt
+++ b/app/src/main/java/com/aritradas/uncrack/presentation/auth/login/LoginScreen.kt
@@ -1,6 +1,7 @@
package com.aritradas.uncrack.presentation.auth.login
import android.widget.Toast
+import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
@@ -47,6 +48,7 @@ import com.aritradas.uncrack.components.UCButton
import com.aritradas.uncrack.components.UCTextField
import com.aritradas.uncrack.navigation.Screen
import com.aritradas.uncrack.presentation.auth.AuthViewModel
+import com.aritradas.uncrack.ui.theme.BackgroundLight
import com.aritradas.uncrack.ui.theme.DMSansFontFamily
import com.aritradas.uncrack.ui.theme.OnPrimaryContainerLight
import com.aritradas.uncrack.ui.theme.PrimaryLight
@@ -105,6 +107,7 @@ fun LoginScreen(
Column(
modifier = Modifier
.fillMaxSize()
+ .background(BackgroundLight)
.padding(paddingValues)
.padding(16.dp)
) {
@@ -202,10 +205,6 @@ fun LoginScreen(
}
}
}
-
- if (isLoading) {
- ProgressDialog {}
- }
}
else -> {
NoInternetScreen()
diff --git a/app/src/main/java/com/aritradas/uncrack/presentation/auth/signup/SignupScreen.kt b/app/src/main/java/com/aritradas/uncrack/presentation/auth/signup/SignupScreen.kt
index e0471321..a2d397de 100644
--- a/app/src/main/java/com/aritradas/uncrack/presentation/auth/signup/SignupScreen.kt
+++ b/app/src/main/java/com/aritradas/uncrack/presentation/auth/signup/SignupScreen.kt
@@ -1,6 +1,7 @@
package com.aritradas.uncrack.presentation.auth.signup
import android.widget.Toast
+import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
@@ -45,6 +46,7 @@ import com.aritradas.uncrack.components.UCButton
import com.aritradas.uncrack.components.UCTextField
import com.aritradas.uncrack.navigation.Screen
import com.aritradas.uncrack.presentation.auth.AuthViewModel
+import com.aritradas.uncrack.ui.theme.BackgroundLight
import com.aritradas.uncrack.ui.theme.DMSansFontFamily
import com.aritradas.uncrack.ui.theme.OnPrimaryContainerLight
import com.aritradas.uncrack.ui.theme.PrimaryLight
@@ -60,6 +62,7 @@ import kotlinx.coroutines.flow.collectLatest
@Composable
fun SignupScreen(
+ navController: NavController,
authViewModel: AuthViewModel,
connectivityObserver: ConnectivityObserver,
modifier: Modifier = Modifier,
@@ -106,9 +109,6 @@ fun SignupScreen(
}
}
- if (isLoading) {
- ProgressDialog {}
- }
when(networkStatus) {
ConnectivityObserver.Status.Available -> {
@@ -118,6 +118,7 @@ fun SignupScreen(
Column(
modifier = Modifier
.fillMaxSize()
+ .background(BackgroundLight)
.padding(paddingValues)
.padding(16.dp)
) {
@@ -229,7 +230,7 @@ fun SignupScreen(
Text(
modifier = Modifier.clickable {
-
+ navController.navigate(Screen.LoginScreen.name)
},
text = stringResource(id = R.string.login),
style = medium16.copy(color = PrimaryLight)
diff --git a/app/src/main/java/com/aritradas/uncrack/presentation/masterKey/confirmMasterKey/ConfirmMasterKeyScreen.kt b/app/src/main/java/com/aritradas/uncrack/presentation/masterKey/confirmMasterKey/ConfirmMasterKeyScreen.kt
index 80233b1b..e58e5397 100644
--- a/app/src/main/java/com/aritradas/uncrack/presentation/masterKey/confirmMasterKey/ConfirmMasterKeyScreen.kt
+++ b/app/src/main/java/com/aritradas/uncrack/presentation/masterKey/confirmMasterKey/ConfirmMasterKeyScreen.kt
@@ -36,8 +36,10 @@ import com.aritradas.uncrack.components.UCButton
import com.aritradas.uncrack.components.UCTextField
import com.aritradas.uncrack.navigation.Screen
import com.aritradas.uncrack.presentation.masterKey.KeyViewModel
+import com.aritradas.uncrack.ui.theme.BackgroundLight
import com.aritradas.uncrack.ui.theme.SurfaceVariantLight
import com.aritradas.uncrack.ui.theme.bold30
+import kotlinx.coroutines.delay
@Composable
fun ConfirmMasterKeyScreen(
@@ -45,16 +47,23 @@ fun ConfirmMasterKeyScreen(
modifier: Modifier = Modifier,
masterKeyViewModel: KeyViewModel = hiltViewModel()
) {
-
- val context = LocalContext.current
var confirmMasterKey by remember { mutableStateOf("") }
var passwordVisibility by remember { mutableStateOf(false) }
+ var isLoading by remember { mutableStateOf(false) }
val savedMasterKey = masterKeyViewModel.keyModel.password
LaunchedEffect(Unit) {
masterKeyViewModel.getMasterKey()
}
+ LaunchedEffect(isLoading) {
+ if (isLoading) {
+ delay(2000)
+ isLoading = false
+ navController.navigate(Screen.VaultScreen.name)
+ }
+ }
+
Scaffold(
modifier.fillMaxSize()
) { paddingValues ->
@@ -63,7 +72,7 @@ fun ConfirmMasterKeyScreen(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
- .background(SurfaceVariantLight)
+ .background(BackgroundLight)
.padding(16.dp)
) {
@@ -98,10 +107,7 @@ fun ConfirmMasterKeyScreen(
R.string.hide_password
)
-
- IconButton(onClick =
- { passwordVisibility = passwordVisibility.not() }
- ) {
+ IconButton(onClick = { passwordVisibility = passwordVisibility.not() }) {
Icon(
modifier = Modifier.size(24.dp),
painter = image,
@@ -117,8 +123,12 @@ fun ConfirmMasterKeyScreen(
modifier = Modifier
.fillMaxWidth(),
text = stringResource(R.string.unlock_uncrack),
+ isLoading = isLoading,
+ loadingText = "Unlocking...",
onClick = {
- navController.navigate(Screen.VaultScreen.name)
+ if (savedMasterKey == confirmMasterKey) {
+ isLoading = true
+ }
},
enabled = savedMasterKey == confirmMasterKey
)
diff --git a/app/src/main/java/com/aritradas/uncrack/presentation/masterKey/createMasterKey/CreateMasterKeyScreen.kt b/app/src/main/java/com/aritradas/uncrack/presentation/masterKey/createMasterKey/CreateMasterKeyScreen.kt
index 1d889a41..55b302f4 100644
--- a/app/src/main/java/com/aritradas/uncrack/presentation/masterKey/createMasterKey/CreateMasterKeyScreen.kt
+++ b/app/src/main/java/com/aritradas/uncrack/presentation/masterKey/createMasterKey/CreateMasterKeyScreen.kt
@@ -36,6 +36,7 @@ import com.aritradas.uncrack.components.UCTextField
import com.aritradas.uncrack.domain.model.Key
import com.aritradas.uncrack.navigation.Screen
import com.aritradas.uncrack.presentation.masterKey.KeyViewModel
+import com.aritradas.uncrack.ui.theme.BackgroundLight
import com.aritradas.uncrack.ui.theme.OnPrimaryContainerLight
import com.aritradas.uncrack.ui.theme.SurfaceTintLight
import com.aritradas.uncrack.ui.theme.SurfaceVariantLight
@@ -66,7 +67,7 @@ fun CreateMasterKeyScreen(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
- .background(SurfaceVariantLight)
+ .background(BackgroundLight)
.padding(16.dp)
) {
Text(
diff --git a/app/src/main/java/com/aritradas/uncrack/presentation/profile/ProfileScreen.kt b/app/src/main/java/com/aritradas/uncrack/presentation/profile/ProfileScreen.kt
index f4168574..9f0d891b 100644
--- a/app/src/main/java/com/aritradas/uncrack/presentation/profile/ProfileScreen.kt
+++ b/app/src/main/java/com/aritradas/uncrack/presentation/profile/ProfileScreen.kt
@@ -6,10 +6,13 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.systemBars
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@@ -30,8 +33,8 @@ import com.aritradas.uncrack.navigation.Screen
import com.aritradas.uncrack.sharedViewModel.UserViewModel
import com.aritradas.uncrack.ui.theme.BackgroundLight
import com.aritradas.uncrack.ui.theme.OnSurfaceLight
+import com.aritradas.uncrack.ui.theme.SurfaceLight
import com.aritradas.uncrack.ui.theme.SurfaceTintLight
-import com.aritradas.uncrack.ui.theme.SurfaceVariantLight
import com.aritradas.uncrack.ui.theme.medium22
import com.aritradas.uncrack.ui.theme.normal14
import com.aritradas.uncrack.util.Constants
@@ -45,19 +48,20 @@ fun ProfileScreen(
val context = LocalContext.current
val userData by userViewModel.state.collectAsState()
+ val paddingValues = WindowInsets.systemBars.asPaddingValues()
Column(
modifier = modifier
.fillMaxSize()
- .background(SurfaceVariantLight),
+ .padding(top = paddingValues.calculateTopPadding() + 10.dp)
+ .background(BackgroundLight),
horizontalAlignment = Alignment.CenterHorizontally
) {
Row(
modifier = Modifier
.fillMaxWidth()
- .background(BackgroundLight)
- .padding(16.dp),
+ .background(BackgroundLight),
verticalAlignment = Alignment.CenterVertically
) {
@@ -69,10 +73,9 @@ fun ProfileScreen(
) {
ProfileContainer(
userViewModel = userViewModel,
- modifier = Modifier.padding(top = 20.dp)
)
- Spacer(modifier = Modifier.height(22.dp))
+ Spacer(modifier = Modifier.height(20.dp))
Text(
text = userData.name,
@@ -94,12 +97,7 @@ fun ProfileScreen(
HorizontalDivider(
thickness = 2.dp,
- color = SurfaceVariantLight
- )
-
- HorizontalDivider(
- thickness = 2.dp,
- color = SurfaceVariantLight
+ color = SurfaceLight
)
UCSettingsCard(
@@ -126,7 +124,7 @@ fun ProfileScreen(
HorizontalDivider(
thickness = 2.dp,
- color = SurfaceVariantLight
+ color = SurfaceLight
)
UCSettingsCard(
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 00000000..cc69d822
--- /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 f43b93c1..7009ee9e 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
@@ -1,6 +1,5 @@
package com.aritradas.uncrack.presentation.settings
-import android.app.Activity
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
@@ -19,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
@@ -26,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
@@ -37,25 +39,27 @@ import com.aritradas.uncrack.components.UCSettingsCard
import com.aritradas.uncrack.components.UCSwitchCard
import com.aritradas.uncrack.components.UCTopAppBar
import com.aritradas.uncrack.navigation.Screen
+import com.aritradas.uncrack.ui.theme.BackgroundLight
import com.aritradas.uncrack.ui.theme.OnPrimaryContainerLight
import com.aritradas.uncrack.ui.theme.OnSurfaceVariantLight
-import com.aritradas.uncrack.ui.theme.SurfaceVariantLight
-import com.aritradas.uncrack.ui.theme.bold20
+import com.aritradas.uncrack.ui.theme.SurfaceLight
import com.aritradas.uncrack.ui.theme.medium14
import com.aritradas.uncrack.ui.theme.normal16
+import com.aritradas.uncrack.ui.theme.semiBold18
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SettingsScreen(
- activity: Activity,
navController: NavHostController,
settingsViewModel: SettingsViewModel,
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) }
@@ -76,7 +80,6 @@ fun SettingsScreen(
Icon(
painter = painterResource(id = R.drawable.logout),
contentDescription = null,
- tint = MaterialTheme.colorScheme.onPrimaryContainer
)
},
title = {
@@ -135,7 +138,6 @@ fun SettingsScreen(
Icon(
painter = painterResource(id = R.drawable.delete_icon),
contentDescription = null,
- tint = MaterialTheme.colorScheme.onPrimaryContainer
)
},
title = {
@@ -194,7 +196,7 @@ fun SettingsScreen(
UCTopAppBar(
modifier = Modifier.fillMaxWidth(),
title = "Settings",
- colors = TopAppBarDefaults.topAppBarColors(SurfaceVariantLight),
+ colors = TopAppBarDefaults.topAppBarColors(BackgroundLight),
onBackPress = { navController.popBackStack() }
)
}
@@ -204,7 +206,7 @@ fun SettingsScreen(
modifier = modifier
.fillMaxSize()
.padding(paddingValues)
- .background(SurfaceVariantLight),
+ .background(BackgroundLight)
) {
Text(
@@ -212,7 +214,7 @@ fun SettingsScreen(
.fillMaxWidth()
.padding(start = 16.dp, end = 18.dp, top = 18.dp),
text = stringResource(id = R.string.security),
- style = bold20.copy(color = OnPrimaryContainerLight)
+ style = semiBold18.copy(color = OnPrimaryContainerLight)
)
Spacer(modifier = Modifier.height(14.dp))
@@ -225,20 +227,22 @@ fun SettingsScreen(
}
)
-// HorizontalDivider(
-// thickness = 2.dp,
-// color = SurfaceVariantLight
-// )
-//
-// UCSwitchCard(
-// itemName = stringResource(R.string.unlock_with_biometric),
-// isChecked = false,
-// onChecked = {}
-// )
+ HorizontalDivider(
+ thickness = 2.dp,
+ color = SurfaceLight
+ )
+
+ UCSwitchCard(
+ itemName = stringResource(R.string.unlock_with_biometric),
+ isChecked = biometricAuthState,
+ onChecked = {
+ settingsViewModel.showBiometricPrompt(context as MainActivity)
+ }
+ )
HorizontalDivider(
thickness = 2.dp,
- color = SurfaceVariantLight
+ color = SurfaceLight
)
UCSwitchCard(
@@ -278,7 +282,7 @@ fun SettingsScreen(
.fillMaxWidth()
.padding(start = 16.dp, end = 18.dp, top = 18.dp),
text = stringResource(R.string.danger_zone),
- style = bold20.copy(color = OnPrimaryContainerLight)
+ style = semiBold18.copy(color = OnPrimaryContainerLight)
)
Spacer(modifier = Modifier.height(14.dp))
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 53e59082..57077882 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/presentation/tools/ToolsScreen.kt b/app/src/main/java/com/aritradas/uncrack/presentation/tools/ToolsScreen.kt
index a21b23b3..1a04c4eb 100644
--- a/app/src/main/java/com/aritradas/uncrack/presentation/tools/ToolsScreen.kt
+++ b/app/src/main/java/com/aritradas/uncrack/presentation/tools/ToolsScreen.kt
@@ -28,6 +28,7 @@ import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import com.aritradas.uncrack.R
import com.aritradas.uncrack.navigation.Screen
+import com.aritradas.uncrack.ui.theme.BackgroundLight
import com.aritradas.uncrack.ui.theme.OnPrimaryContainerLight
import com.aritradas.uncrack.ui.theme.SurfaceVariantLight
import com.aritradas.uncrack.ui.theme.medium18
@@ -48,17 +49,10 @@ fun ToolsScreen(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
- .background(SurfaceVariantLight)
+ .background(BackgroundLight)
.padding(16.dp),
) {
- Text(
- text = stringResource(R.string.tools),
- style = medium28.copy(OnPrimaryContainerLight)
- )
-
- Spacer(modifier = Modifier.height(20.dp))
-
Row(
modifier = modifier
.fillMaxWidth()
@@ -66,7 +60,7 @@ fun ToolsScreen(
.clickable {
navController.navigate(Screen.PasswordGeneratorScreen.name)
}
- .background(Color.White)
+ .background(SurfaceVariantLight)
.shadow(
elevation = 5.dp,
spotColor = Color(0x0D666666),
@@ -111,7 +105,7 @@ fun ToolsScreen(
.clickable {
navController.navigate(Screen.PasswordHealthScreen.name)
}
- .background(Color.White)
+ .background(SurfaceVariantLight)
.shadow(
elevation = 5.dp,
spotColor = Color(0x0D666666),
diff --git a/app/src/main/java/com/aritradas/uncrack/presentation/vault/VaultScreen.kt b/app/src/main/java/com/aritradas/uncrack/presentation/vault/VaultScreen.kt
index 0ae3568e..d505c9ce 100644
--- a/app/src/main/java/com/aritradas/uncrack/presentation/vault/VaultScreen.kt
+++ b/app/src/main/java/com/aritradas/uncrack/presentation/vault/VaultScreen.kt
@@ -39,11 +39,13 @@ import com.aritradas.uncrack.components.TypewriterText
import com.aritradas.uncrack.components.VaultCard
import com.aritradas.uncrack.sharedViewModel.UserViewModel
import com.aritradas.uncrack.presentation.vault.viewmodel.VaultViewModel
+import com.aritradas.uncrack.ui.theme.BackgroundLight
import com.aritradas.uncrack.ui.theme.OnSurfaceVariantLight
import com.aritradas.uncrack.ui.theme.PrimaryContainerLight
import com.aritradas.uncrack.ui.theme.SurfaceVariantLight
import com.aritradas.uncrack.ui.theme.medium24
import com.aritradas.uncrack.ui.theme.normal16
+import com.aritradas.uncrack.util.BackPressHandler
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@@ -58,6 +60,8 @@ fun VaultScreen(
var searchQuery by rememberSaveable { mutableStateOf("") }
val user by userViewModel.state.collectAsState()
+ BackPressHandler()
+
LaunchedEffect(Unit) {
vaultViewModel.getAccounts()
userViewModel.getCurrentUser()
@@ -77,19 +81,25 @@ fun VaultScreen(
}
) { paddingValues ->
Column(
- modifier = modifier
+ modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
- .background(SurfaceVariantLight)
- .padding(16.dp)
+ .background(BackgroundLight)
+ .then(modifier),
+ verticalArrangement = Arrangement.Top
) {
Text(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 16.dp, vertical = 8.dp),
text = "Hello, ${user.name}",
style = medium24.copy(Color.Black)
)
SearchBar(
- modifier = Modifier.fillMaxWidth(),
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 16.dp, vertical = 8.dp),
query = searchQuery,
onQueryChange = {
searchQuery = it
@@ -111,7 +121,6 @@ fun VaultScreen(
"Linkedin"
))
}
-
},
colors = SearchBarDefaults.colors(
containerColor = PrimaryContainerLight
@@ -135,7 +144,8 @@ fun VaultScreen(
LazyColumn(
modifier = Modifier
- .fillMaxSize(),
+ .fillMaxSize()
+ .padding(horizontal = 16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
if (accounts.isNotEmpty()) {
@@ -150,7 +160,6 @@ fun VaultScreen(
} else {
item {
EmptyState(
- modifier = Modifier.padding(top = 100.dp),
stateTitle = "Hey ${user.name}, \n currently there are no passwords saved",
image = R.drawable.vault_empty_state
)
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 00000000..e9c8c788
--- /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
diff --git a/app/src/main/java/com/aritradas/uncrack/util/AppBioMetricManager.kt b/app/src/main/java/com/aritradas/uncrack/util/AppBioMetricManager.kt
new file mode 100644
index 00000000..ec1d9c9b
--- /dev/null
+++ b/app/src/main/java/com/aritradas/uncrack/util/AppBioMetricManager.kt
@@ -0,0 +1,64 @@
+package com.aritradas.uncrack.util
+
+import android.content.Context
+import androidx.biometric.BiometricManager
+import androidx.biometric.BiometricPrompt
+import com.aritradas.uncrack.MainActivity
+import com.aritradas.uncrack.presentation.settings.BiometricAuthListener
+import javax.inject.Inject
+
+class AppBioMetricManager @Inject constructor(appContext: Context) {
+
+ private var biometricPrompt: BiometricPrompt? = null
+ private val biometricManager = BiometricManager.from(appContext)
+
+ fun canAuthenticate(): Boolean {
+ return when (biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG)) {
+ BiometricManager.BIOMETRIC_SUCCESS -> {
+ true
+ }
+
+ BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> {
+ false
+ }
+
+ else -> {
+ false
+ }
+ }
+ }
+
+ fun initBiometricPrompt(activity: MainActivity, listener: BiometricAuthListener) {
+ biometricPrompt = BiometricPrompt(
+ activity,
+ object : BiometricPrompt.AuthenticationCallback() {
+ override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
+ super.onAuthenticationError(errorCode, errString)
+ val cancelled = errorCode in arrayListOf(
+ BiometricPrompt.ERROR_CANCELED,
+ BiometricPrompt.ERROR_USER_CANCELED,
+ BiometricPrompt.ERROR_NEGATIVE_BUTTON,
+ )
+ if (cancelled) {
+ listener.onUserCancelled()
+ } else {
+ listener.onErrorOccurred()
+ }
+ }
+
+ override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
+ super.onAuthenticationSucceeded(result)
+ listener.onBiometricAuthSuccess()
+ }
+ },
+ )
+
+ val promptInfo = BiometricPrompt.PromptInfo.Builder()
+ .setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG)
+ .setTitle("Unlock UnCrack")
+ .setSubtitle("Confirm biometric to get logged in")
+ .setNegativeButtonText("Cancel")
+ .build()
+ biometricPrompt?.authenticate(promptInfo)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/profile_image_1.xml b/app/src/main/res/drawable/profile_image_1.xml
new file mode 100644
index 00000000..a638bf4b
--- /dev/null
+++ b/app/src/main/res/drawable/profile_image_1.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/profile_image_2.xml b/app/src/main/res/drawable/profile_image_2.xml
new file mode 100644
index 00000000..68175242
--- /dev/null
+++ b/app/src/main/res/drawable/profile_image_2.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/profile_image_3.xml b/app/src/main/res/drawable/profile_image_3.xml
new file mode 100644
index 00000000..d1e9b6db
--- /dev/null
+++ b/app/src/main/res/drawable/profile_image_3.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/profile_image_4.xml b/app/src/main/res/drawable/profile_image_4.xml
new file mode 100644
index 00000000..22ac685c
--- /dev/null
+++ b/app/src/main/res/drawable/profile_image_4.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
diff --git a/uncrack_release.jks b/uncrack_release.jks
index 377c2584..967bd6b1 100644
Binary files a/uncrack_release.jks and b/uncrack_release.jks differ