feat: Add AsLockedApp wrapper to require id verification if enabled

This commit is contained in:
Myzel394 2023-12-23 20:41:36 +01:00
parent 025a8a3209
commit 6661d457ea
No known key found for this signature in database
GPG Key ID: 50098FCA22080F0F
4 changed files with 90 additions and 11 deletions

View File

@ -14,7 +14,7 @@ import androidx.core.view.WindowCompat
import androidx.datastore.dataStore import androidx.datastore.dataStore
import app.myzel394.alibi.db.AppSettings import app.myzel394.alibi.db.AppSettings
import app.myzel394.alibi.db.AppSettingsSerializer import app.myzel394.alibi.db.AppSettingsSerializer
import app.myzel394.alibi.ui.LockedApp import app.myzel394.alibi.ui.AsLockedApp
import app.myzel394.alibi.ui.Navigation import app.myzel394.alibi.ui.Navigation
import app.myzel394.alibi.ui.SUPPORTS_DARK_MODE_NATIVELY import app.myzel394.alibi.ui.SUPPORTS_DARK_MODE_NATIVELY
import app.myzel394.alibi.ui.theme.AlibiTheme import app.myzel394.alibi.ui.theme.AlibiTheme
@ -33,8 +33,10 @@ class MainActivity : AppCompatActivity() {
setContent { setContent {
AlibiTheme { AlibiTheme {
AsLockedApp {
Navigation() Navigation()
} }
} }
} }
} }
}

View File

@ -1,11 +1,13 @@
package app.myzel394.alibi.helpers package app.myzel394.alibi.helpers
import android.app.Activity
import android.content.Context import android.content.Context
import androidx.biometric.BiometricManager import androidx.biometric.BiometricManager
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.biometric.BiometricPrompt import androidx.biometric.BiometricPrompt
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CompletableDeferred
import kotlin.system.exitProcess
class AppLockHelper { class AppLockHelper {
enum class SupportType { enum class SupportType {
@ -60,5 +62,15 @@ class AppLockHelper {
return deferred return deferred
} }
fun closeApp(context: Context) {
(context as? Activity)?.let {
it.finishAndRemoveTask()
it.finishAffinity()
it.finish()
}
exitProcess(0)
}
} }
} }

View File

@ -20,17 +20,88 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import app.myzel394.alibi.R import app.myzel394.alibi.R
import app.myzel394.alibi.dataStore
import app.myzel394.alibi.helpers.AppLockHelper
import kotlinx.coroutines.launch
// After this amount, close the app // After this amount, close the app
const val MAX_TRIES = 5 const val MAX_TRIES = 10
@Composable @Composable
fun LockedApp() { fun AsLockedApp(
content: (@Composable () -> Unit),
) {
val scope = rememberCoroutineScope()
val context = LocalContext.current
val settings = context
.dataStore
.data
.collectAsState(initial = null)
.value ?: return
// -1 = Unlocked, any other value = locked
var tries by remember { mutableIntStateOf(0) }
LaunchedEffect(settings.isAppLockEnabled()) {
if (!settings.isAppLockEnabled()) {
tries = -1
}
}
if (tries == -1) {
return content()
}
val title = stringResource(R.string.identityVerificationRequired_title)
val subtitle = stringResource(R.string.identityVerificationRequired_subtitle)
fun openAuthentication() {
if (tries >= MAX_TRIES) {
AppLockHelper.closeApp(context)
return
}
scope.launch {
val successful = AppLockHelper.authenticate(
context,
title,
subtitle,
).await()
if (successful) {
tries = -1
return@launch
}
tries++
if (tries >= MAX_TRIES) {
AppLockHelper.closeApp(context)
}
}
}
LaunchedEffect(settings.isAppLockEnabled()) {
if (settings.isAppLockEnabled()) {
openAuthentication()
}
}
Scaffold { paddingValues -> Scaffold { paddingValues ->
Column( Column(
modifier = Modifier modifier = Modifier
@ -60,7 +131,7 @@ fun LockedApp() {
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.height(BIG_PRIMARY_BUTTON_SIZE), .height(BIG_PRIMARY_BUTTON_SIZE),
onClick = {}, onClick = ::openAuthentication,
colors = ButtonDefaults.filledTonalButtonColors(), colors = ButtonDefaults.filledTonalButtonColors(),
) { ) {
Icon( Icon(

View File

@ -72,12 +72,6 @@ fun Navigation(
} }
} }
LaunchedEffect(Unit) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
AppLockHelper.authenticate(context, "Title", "Subtitle")
}
}
LaunchedEffect(settings.theme) { LaunchedEffect(settings.theme) {
if (!SUPPORTS_DARK_MODE_NATIVELY) { if (!SUPPORTS_DARK_MODE_NATIVELY) {
val currentValue = AppCompatDelegate.getDefaultNightMode() val currentValue = AppCompatDelegate.getDefaultNightMode()