From 6668e5741b15503eec70860c8212db22e5e96a32 Mon Sep 17 00:00:00 2001 From: starry-shivam Date: Sat, 13 Apr 2024 12:46:00 +0530 Subject: [PATCH] Add UI for app lock Signed-off-by: starry-shivam --- app/build.gradle | 4 +- .../com/starry/greenstash/MainActivity.kt | 54 ++++-- .../com/starry/greenstash/MainViewModel.kt | 10 +- .../backups/composables/BackupScreen.kt | 2 +- .../ui/screens/other/AppLockedScreen.kt | 157 ++++++++++++++++++ .../settings/composables/GoalCardStyle.kt | 3 + .../settings/composables/SettingsScreen.kt | 2 +- app/src/main/res/drawable/app_lock_icon.png | Bin 0 -> 14792 bytes .../{backup_logo.png => backup_icon.png} | Bin app/src/main/res/values-es/strings.xml | 7 + app/src/main/res/values-tr/strings.xml | 7 + app/src/main/res/values-zh-rCN/strings.xml | 7 + app/src/main/res/values/strings.xml | 7 + 13 files changed, 241 insertions(+), 19 deletions(-) create mode 100644 app/src/main/java/com/starry/greenstash/ui/screens/other/AppLockedScreen.kt create mode 100644 app/src/main/res/drawable/app_lock_icon.png rename app/src/main/res/drawable/{backup_logo.png => backup_icon.png} (100%) diff --git a/app/build.gradle b/app/build.gradle index 9332463b..5e8e4124 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -121,8 +121,8 @@ dependencies { implementation 'com.maxkeppeler.sheets-compose-dialogs:core:1.3.0' implementation 'com.maxkeppeler.sheets-compose-dialogs:calendar:1.3.0' implementation 'com.maxkeppeler.sheets-compose-dialogs:date-time:1.3.0' - // Taptarget compose. - implementation "com.pierfrancescosoffritti.taptargetcompose:core:1.1.0" + // Tap-target compose. + implementation "com.pierfrancescosoffritti.taptargetcompose:core:1.1.1" // Lottie animations. implementation "com.airbnb.android:lottie-compose:4.1.0" // Bio-metric authentication. diff --git a/app/src/main/java/com/starry/greenstash/MainActivity.kt b/app/src/main/java/com/starry/greenstash/MainActivity.kt index e0f05025..a3586b81 100644 --- a/app/src/main/java/com/starry/greenstash/MainActivity.kt +++ b/app/src/main/java/com/starry/greenstash/MainActivity.kt @@ -30,10 +30,14 @@ import androidx.activity.compose.setContent import androidx.appcompat.app.AppCompatActivity import androidx.biometric.BiometricManager import androidx.biometric.BiometricPrompt +import androidx.compose.animation.Crossfade +import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface +import androidx.compose.runtime.State import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.Modifier import androidx.core.content.ContextCompat import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen @@ -41,10 +45,12 @@ import androidx.lifecycle.ViewModelProvider import androidx.navigation.compose.rememberNavController import com.google.accompanist.systemuicontroller.rememberSystemUiController import com.starry.greenstash.ui.navigation.NavGraph +import com.starry.greenstash.ui.screens.other.AppLockedScreen import com.starry.greenstash.ui.screens.settings.SettingsViewModel import com.starry.greenstash.ui.screens.settings.ThemeMode import com.starry.greenstash.ui.theme.GreenStashTheme import com.starry.greenstash.utils.Utils +import com.starry.greenstash.utils.toToast import dagger.hilt.android.AndroidEntryPoint import java.util.concurrent.Executor @@ -73,8 +79,10 @@ class MainActivity : AppCompatActivity() { mainViewModel.refreshReminders() val appLockStatus = settingsViewModel.getAppLockValue() + val showAppContents = mutableStateOf(false) - if (appLockStatus && !mainViewModel.appUnlocked) { + // check if app lock is enabled and user has not unlocked the app. + if (appLockStatus && !mainViewModel.isAppUnlocked()) { executor = ContextCompat.getMainExecutor(this) biometricPrompt = BiometricPrompt(this, executor, object : BiometricPrompt.AuthenticationCallback() { @@ -84,8 +92,8 @@ class MainActivity : AppCompatActivity() { ) { super.onAuthenticationSucceeded(result) // make app contents visible after successful authentication. - setAppContents() - mainViewModel.appUnlocked = true + showAppContents.value = true + mainViewModel.setAppUnlocked(true) } override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { @@ -98,11 +106,16 @@ class MainActivity : AppCompatActivity() { */ val biometricManager = BiometricManager.from(this@MainActivity) if (biometricManager.canAuthenticate(Utils.getAuthenticators()) != BiometricManager.BIOMETRIC_SUCCESS) { - setAppContents() - mainViewModel.appUnlocked = true + // make app contents visible. + showAppContents.value = true + // disable app lock. + mainViewModel.setAppUnlocked(true) + // disable app lock in settings. settingsViewModel.setAppLock(false) + // show error message. + getString(R.string.app_lock_unable_to_authenticate).toToast(this@MainActivity) } else { - finish() // close the app. + showAppContents.value = false } } }) @@ -113,14 +126,15 @@ class MainActivity : AppCompatActivity() { .setAllowedAuthenticators(Utils.getAuthenticators()) .build() - biometricPrompt.authenticate(promptInfo) - } else { - setAppContents() + showAppContents.value = true } + + // set app contents based on the value of showAppContents. + setAppContents(showAppContents) } - fun setAppContents() { + private fun setAppContents(showAppContents: State) { setContent { GreenStashTheme(settingsViewModel = settingsViewModel) { val systemUiController = rememberSystemUiController() @@ -138,9 +152,23 @@ class MainActivity : AppCompatActivity() { modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background ) { - val navController = rememberNavController() - val screen by mainViewModel.startDestination - NavGraph(navController = navController, screen) + Crossfade( + targetState = showAppContents, + label = "AppLockCrossFade", + animationSpec = tween(500) + ) { showAppContents -> + // show app contents only if user has authenticated. + if (showAppContents.value) { + val navController = rememberNavController() + val screen by mainViewModel.startDestination + NavGraph(navController = navController, screen) + } else { + // show app locked screen if user has not authenticated. + AppLockedScreen(onAuthRequest = { + biometricPrompt.authenticate(promptInfo) + }) + } + } } } } diff --git a/app/src/main/java/com/starry/greenstash/MainViewModel.kt b/app/src/main/java/com/starry/greenstash/MainViewModel.kt index 648e6d18..b951fcb6 100644 --- a/app/src/main/java/com/starry/greenstash/MainViewModel.kt +++ b/app/src/main/java/com/starry/greenstash/MainViewModel.kt @@ -49,11 +49,11 @@ class MainViewModel @Inject constructor( private val reminderManager: ReminderManager ) : ViewModel() { /** - * Storing app lock status to avoid asking for authentication + * Store app lock status to avoid asking for authentication * when activity restarts like when changing app or device * theme or when changing device orientation. */ - var appUnlocked = false + private var _appUnlocked = false private val _isLoading: MutableState = mutableStateOf(true) val isLoading: State = _isLoading @@ -82,4 +82,10 @@ class MainViewModel @Inject constructor( reminderManager.checkAndScheduleReminders(goalDao.getAllGoals()) } } + + fun isAppUnlocked(): Boolean = _appUnlocked + + fun setAppUnlocked(value: Boolean) { + _appUnlocked = value + } } \ No newline at end of file diff --git a/app/src/main/java/com/starry/greenstash/ui/screens/backups/composables/BackupScreen.kt b/app/src/main/java/com/starry/greenstash/ui/screens/backups/composables/BackupScreen.kt index 7108ed5e..c408112c 100644 --- a/app/src/main/java/com/starry/greenstash/ui/screens/backups/composables/BackupScreen.kt +++ b/app/src/main/java/com/starry/greenstash/ui/screens/backups/composables/BackupScreen.kt @@ -182,7 +182,7 @@ private fun BackupScreenContent( contentAlignment = Alignment.Center ) { AsyncImage( - model = R.drawable.backup_logo, + model = R.drawable.backup_icon, contentDescription = null, modifier = Modifier.size(200.dp) ) diff --git a/app/src/main/java/com/starry/greenstash/ui/screens/other/AppLockedScreen.kt b/app/src/main/java/com/starry/greenstash/ui/screens/other/AppLockedScreen.kt new file mode 100644 index 00000000..f29bb17c --- /dev/null +++ b/app/src/main/java/com/starry/greenstash/ui/screens/other/AppLockedScreen.kt @@ -0,0 +1,157 @@ +/** + * MIT License + * + * Copyright (c) [2022 - Present] Stɑrry Shivɑm + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + + +package com.starry.greenstash.ui.screens.other + +import androidx.compose.animation.animateContentSize +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.tween +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.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Fingerprint +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.FilledTonalButton +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.surfaceColorAtElevation +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import coil.compose.AsyncImage +import com.starry.greenstash.R +import com.starry.greenstash.ui.theme.greenstashFont +import kotlinx.coroutines.delay + + +@Composable +fun AppLockedScreen(onAuthRequest: () -> Unit) { + Column( + Modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + LockIconCard() // Animated lock icon + + Text( + text = stringResource(id = R.string.app_lock_screen_title), + style = MaterialTheme.typography.headlineSmall, + fontFamily = greenstashFont, + modifier = Modifier.padding(top = 16.dp, bottom = 4.dp) + ) + + Text( + text = stringResource(id = R.string.app_lock_screen_subtitle), + style = MaterialTheme.typography.bodySmall, + fontFamily = greenstashFont, + modifier = Modifier.padding(horizontal = 42.dp) + ) + + FilledTonalButton( + onClick = onAuthRequest, + modifier = Modifier.padding(top = 18.dp) + ) { + Icon( + imageVector = Icons.Filled.Fingerprint, + contentDescription = stringResource(id = R.string.app_lock_button_icon_desc), + modifier = Modifier.size(ButtonDefaults.IconSize) + ) + Spacer(modifier = Modifier.width(ButtonDefaults.IconSpacing)) + Text( + text = stringResource(id = R.string.app_lock_button_text), + fontFamily = greenstashFont, + ) + } + } +} + +@Composable +private fun LockIconCard() { + val isAnimated = remember { mutableStateOf(false) } + val animationSize by animateFloatAsState( + targetValue = if (isAnimated.value) 1f else 0.8f, + animationSpec = tween(durationMillis = 500), label = "animationSize" + ) + + LaunchedEffect(key1 = true) { + delay(300) + isAnimated.value = true + } + + Card( + modifier = Modifier + .size(350.dp * animationSize) + .clip(CircleShape) + .animateContentSize(), + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp) + ) + ) { + Box( + modifier = Modifier + .fillMaxSize() + .padding(24.dp), + contentAlignment = Alignment.Center + ) { + AsyncImage( + model = R.drawable.app_lock_icon, + contentDescription = null, + modifier = Modifier.size(200.dp) + ) + } + } + +} + + +@Preview(showBackground = true) +@Composable +private fun AppLockedScreenPV() { + AppLockedScreen(onAuthRequest = { + // do nothing + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/starry/greenstash/ui/screens/settings/composables/GoalCardStyle.kt b/app/src/main/java/com/starry/greenstash/ui/screens/settings/composables/GoalCardStyle.kt index c5ef9803..d6c71282 100644 --- a/app/src/main/java/com/starry/greenstash/ui/screens/settings/composables/GoalCardStyle.kt +++ b/app/src/main/java/com/starry/greenstash/ui/screens/settings/composables/GoalCardStyle.kt @@ -43,7 +43,9 @@ 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.rememberScrollState import androidx.compose.foundation.selection.selectable +import androidx.compose.foundation.verticalScroll import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack @@ -115,6 +117,7 @@ fun GoalCardStyle(navController: NavController) { modifier = Modifier .fillMaxSize() .padding(paddingValues) + .verticalScroll(rememberScrollState()), ) { OutlinedCard( modifier = Modifier diff --git a/app/src/main/java/com/starry/greenstash/ui/screens/settings/composables/SettingsScreen.kt b/app/src/main/java/com/starry/greenstash/ui/screens/settings/composables/SettingsScreen.kt index 89fe69df..9bdf8803 100644 --- a/app/src/main/java/com/starry/greenstash/ui/screens/settings/composables/SettingsScreen.kt +++ b/app/src/main/java/com/starry/greenstash/ui/screens/settings/composables/SettingsScreen.kt @@ -472,7 +472,7 @@ private fun SecuritySettings(viewModel: SettingsViewModel) { super.onAuthenticationSucceeded(result) context.getString(R.string.auth_successful) .toToast(context) - mainActivity.mainViewModel.appUnlocked = true + mainActivity.mainViewModel.setAppUnlocked(true) viewModel.setAppLock(true) } diff --git a/app/src/main/res/drawable/app_lock_icon.png b/app/src/main/res/drawable/app_lock_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..64ca89a4bac1c1ae4ca29a48a3248c27d4e022ab GIT binary patch literal 14792 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 U¡Hola! Por favor selecciona tú moneda preferida para iniciar. Comencemos + + GreenStash está bloqueado + Haz clic en el botón de abajo para autenticar. + Autorizar + Desbloquear Aplicación + El bloqueo de la aplicación está desactivado porque el dispositivo no puede autenticarse + Metas de Ahorro Open side menu diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 0961b3fc..eafb0885 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -24,6 +24,13 @@ Merhaba! Lütfen başlamak için tercih edilen para birimini seçin. Başla + + GreenStash kilitli + Kimlik doğrulamak için aşağıdaki düğmeye tıklayın. + Yetkilendir + Uygulamayı Aç + Cihaz kimlik doğrulayamadığı için uygulama kilitli değil + Birikim Hedefleri Yan menüyü aç diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 3f59c4ac..2da5b2dd 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -24,6 +24,13 @@ 您好!请选择您的货币。 开始 + + GreenStash已锁定 + 点击下面的按钮进行身份验证。 + 授权 + 解锁应用 + 由于设备无法进行身份验证,应用程序锁定已禁用 + 省钱目标 打开侧边菜单 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a429d93a..15f81e95 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -24,6 +24,13 @@ Hello! Please select your preferred currency to get started. Let\'s Get Started! + + GreenStash is locked + Click the button below to authenticate. + Authorize + Unlock App + App-lock is disabled because the device is unable to authenticate + Saving Goals Open side menu