From 025a8a3209682cfffb0e8d294088e5eea110952e Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Sat, 23 Dec 2023 20:18:46 +0100 Subject: [PATCH] feat: Add EnableAppLockTile to SettingsScreen --- .../java/app/myzel394/alibi/db/AppSettings.kt | 17 +++- .../myzel394/alibi/helpers/AppLockHelper.kt | 20 ++-- .../SettingsScreen/Tiles/EnableAppLockTile.kt | 92 +++++++++++++++++++ .../alibi/ui/components/atoms/SettingsTile.kt | 2 + .../alibi/ui/screens/SettingsScreen.kt | 2 + app/src/main/res/values/strings.xml | 5 + 6 files changed, 124 insertions(+), 14 deletions(-) create mode 100644 app/src/main/java/app/myzel394/alibi/ui/components/SettingsScreen/Tiles/EnableAppLockTile.kt diff --git a/app/src/main/java/app/myzel394/alibi/db/AppSettings.kt b/app/src/main/java/app/myzel394/alibi/db/AppSettings.kt index 6657419..c57ecfe 100644 --- a/app/src/main/java/app/myzel394/alibi/db/AppSettings.kt +++ b/app/src/main/java/app/myzel394/alibi/db/AppSettings.kt @@ -22,6 +22,8 @@ data class AppSettings( val audioRecorderSettings: AudioRecorderSettings = AudioRecorderSettings.getDefaultInstance(), val videoRecorderSettings: VideoRecorderSettings = VideoRecorderSettings.getDefaultInstance(), + val appLockSettings: AppLockSettings? = null, + val hasSeenOnboarding: Boolean = false, val showAdvancedSettings: Boolean = false, val theme: Theme = Theme.SYSTEM, @@ -97,6 +99,14 @@ data class AppSettings( return copy(saveFolder = saveFolder) } + fun setAppLockSettings(appLockSettings: AppLockSettings?): AppSettings { + return copy(appLockSettings = appLockSettings) + } + + // If the object is present, biometric authentication is enabled. + // To disable biometric authentication, set the instance to null. + fun isAppLockEnabled() = appLockSettings != null + enum class Theme { SYSTEM, LIGHT, @@ -553,8 +563,7 @@ data class NotificationSettings( @Serializable class AppLockSettings { - // If the object is present, biometric authentication is enabled. - // To disable biometric authentication, set the instance to null. - val isEnabled - get() = true + companion object { + fun getDefaultInstance() = AppLockSettings() + } } diff --git a/app/src/main/java/app/myzel394/alibi/helpers/AppLockHelper.kt b/app/src/main/java/app/myzel394/alibi/helpers/AppLockHelper.kt index b963a14..182172e 100644 --- a/app/src/main/java/app/myzel394/alibi/helpers/AppLockHelper.kt +++ b/app/src/main/java/app/myzel394/alibi/helpers/AppLockHelper.kt @@ -15,13 +15,13 @@ class AppLockHelper { } companion object { - fun isSupported(context: Context): SupportType { + fun getSupportType(context: Context): SupportType { val biometricManager = BiometricManager.from(context) - when (biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG or BiometricManager.Authenticators.DEVICE_CREDENTIAL)) { - BiometricManager.BIOMETRIC_SUCCESS -> return SupportType.AVAILABLE - BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> return SupportType.NONE_ENROLLED + return when (biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG or BiometricManager.Authenticators.DEVICE_CREDENTIAL)) { + BiometricManager.BIOMETRIC_SUCCESS -> SupportType.AVAILABLE + BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> SupportType.NONE_ENROLLED - else -> return SupportType.UNAVAILABLE + else -> SupportType.UNAVAILABLE } } @@ -29,23 +29,23 @@ class AppLockHelper { context: Context, title: String, subtitle: String - ): CompletableDeferred { - val deferred = CompletableDeferred() + ): CompletableDeferred { + val deferred = CompletableDeferred() val mainExecutor = ContextCompat.getMainExecutor(context) val biometricPrompt = BiometricPrompt( context as FragmentActivity, object : BiometricPrompt.AuthenticationCallback() { override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { - deferred.completeExceptionally(Exception(errString.toString())) + deferred.complete(false) } override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) { - deferred.complete(Unit) + deferred.complete(true) } override fun onAuthenticationFailed() { - deferred.completeExceptionally(Exception("Authentication failed")) + deferred.complete(false) } } ) diff --git a/app/src/main/java/app/myzel394/alibi/ui/components/SettingsScreen/Tiles/EnableAppLockTile.kt b/app/src/main/java/app/myzel394/alibi/ui/components/SettingsScreen/Tiles/EnableAppLockTile.kt new file mode 100644 index 0000000..aeddb5e --- /dev/null +++ b/app/src/main/java/app/myzel394/alibi/ui/components/SettingsScreen/Tiles/EnableAppLockTile.kt @@ -0,0 +1,92 @@ +package app.myzel394.alibi.ui.components.SettingsScreen.Tiles + +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.magnifier +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Fingerprint +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Switch +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import app.myzel394.alibi.R +import app.myzel394.alibi.dataStore +import app.myzel394.alibi.db.AppLockSettings +import app.myzel394.alibi.db.AppSettings +import app.myzel394.alibi.helpers.AppLockHelper +import app.myzel394.alibi.ui.components.atoms.SettingsTile +import kotlinx.coroutines.launch + +@Composable +fun EnableAppLockTile( + settings: AppSettings, +) { + val scope = rememberCoroutineScope() + + val context = LocalContext.current + val dataStore = context.dataStore + + val appLockSupport = AppLockHelper.getSupportType(context) + + if (appLockSupport === AppLockHelper.SupportType.UNAVAILABLE) { + return + } + + SettingsTile( + title = stringResource(R.string.ui_settings_option_enableAppLock_title), + description = stringResource(R.string.ui_settings_option_enableAppLock_description), + tertiaryLine = { + if (appLockSupport === AppLockHelper.SupportType.NONE_ENROLLED) { + Text( + stringResource(R.string.ui_settings_option_enableAppLock_enrollmentRequired), + color = Color.Yellow, + style = MaterialTheme.typography.bodySmall, + modifier = Modifier.padding(top = 4.dp) + ) + } + }, + leading = { + Icon( + Icons.Default.Fingerprint, + contentDescription = null, + ) + }, + trailing = { + val title = stringResource(R.string.identityVerificationRequired_title) + val subtitle = stringResource(R.string.identityVerificationRequired_subtitle) + + Switch( + checked = settings.isAppLockEnabled(), + enabled = appLockSupport === AppLockHelper.SupportType.AVAILABLE, + onCheckedChange = { + scope.launch { + val authenticationSuccessful = AppLockHelper.authenticate( + context, + title = title, + subtitle = subtitle, + ).await() + + if (!authenticationSuccessful) { + return@launch + } + + dataStore.updateData { + it.setAppLockSettings( + if (it.appLockSettings == null) + AppLockSettings.getDefaultInstance() + else + null + ) + } + } + } + ) + } + ) +} \ No newline at end of file diff --git a/app/src/main/java/app/myzel394/alibi/ui/components/atoms/SettingsTile.kt b/app/src/main/java/app/myzel394/alibi/ui/components/atoms/SettingsTile.kt index 729995f..2f255fe 100644 --- a/app/src/main/java/app/myzel394/alibi/ui/components/atoms/SettingsTile.kt +++ b/app/src/main/java/app/myzel394/alibi/ui/components/atoms/SettingsTile.kt @@ -21,6 +21,7 @@ fun SettingsTile( firstModifier: Modifier = Modifier, title: String, description: String? = null, + tertiaryLine: (@Composable () -> Unit) = {}, leading: @Composable () -> Unit = {}, trailing: @Composable () -> Unit = {}, extra: (@Composable () -> Unit)? = null, @@ -49,6 +50,7 @@ fun SettingsTile( text = description, style = MaterialTheme.typography.bodySmall, ) + tertiaryLine() } Spacer(modifier = Modifier.width(16.dp)) trailing() diff --git a/app/src/main/java/app/myzel394/alibi/ui/screens/SettingsScreen.kt b/app/src/main/java/app/myzel394/alibi/ui/screens/SettingsScreen.kt index 471ee2f..217641d 100644 --- a/app/src/main/java/app/myzel394/alibi/ui/screens/SettingsScreen.kt +++ b/app/src/main/java/app/myzel394/alibi/ui/screens/SettingsScreen.kt @@ -52,6 +52,7 @@ import app.myzel394.alibi.ui.components.SettingsScreen.Tiles.AudioRecorderOutput import app.myzel394.alibi.ui.components.SettingsScreen.Tiles.AudioRecorderSamplingRateTile import app.myzel394.alibi.ui.components.SettingsScreen.Tiles.SaveFolderTile import app.myzel394.alibi.ui.components.SettingsScreen.Tiles.AudioRecorderShowAllMicrophonesTile +import app.myzel394.alibi.ui.components.SettingsScreen.Tiles.EnableAppLockTile import app.myzel394.alibi.ui.components.SettingsScreen.Tiles.VideoRecorderBitrateTile import app.myzel394.alibi.ui.components.SettingsScreen.atoms.ThemeSelector import app.myzel394.alibi.ui.components.atoms.GlobalSwitch @@ -138,6 +139,7 @@ fun SettingsScreen( InAppLanguagePicker() DeleteRecordingsImmediatelyTile(settings = settings) CustomNotificationTile(navController = navController, settings = settings) + EnableAppLockTile(settings = settings) GlobalSwitch( label = stringResource(R.string.ui_settings_advancedSettings_label), checked = settings.showAdvancedSettings, diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index eacf755..ced0bc1 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -162,4 +162,9 @@ Video Recorder is starting... Alibi is locked Unlock + Enable App Lock + Require your biometric info or your password to open Alibi + Please enroll a password or a biometric unlock method first + Verification required + You need to verify your identity to continue \ No newline at end of file