From d311569df46428bb6462f083d09d136242716c82 Mon Sep 17 00:00:00 2001 From: greenart7c3 Date: Wed, 7 Jan 2026 06:57:34 -0300 Subject: [PATCH] Refactor: Move auth logic to BiometricAuthScreen --- .../greenart7c3/nostrsigner/MainActivity.kt | 126 +-------------- .../ui/components/BiometricAuthScreen.kt | 143 ++++++++++++++++++ 2 files changed, 150 insertions(+), 119 deletions(-) create mode 100644 app/src/main/java/com/greenart7c3/nostrsigner/ui/components/BiometricAuthScreen.kt diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/MainActivity.kt b/app/src/main/java/com/greenart7c3/nostrsigner/MainActivity.kt index 893237db..32a4cbdf 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/MainActivity.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/MainActivity.kt @@ -4,16 +4,11 @@ import android.content.Intent import android.net.ConnectivityManager import android.os.Bundle import android.util.Log -import android.widget.Toast -import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge -import androidx.activity.result.ActivityResult -import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface @@ -22,27 +17,21 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue 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.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.Density -import androidx.compose.ui.unit.dp -import androidx.compose.ui.window.Dialog -import androidx.compose.ui.window.DialogProperties import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.compose.rememberNavController import com.greenart7c3.nostrsigner.okhttp.HttpClientManager -import com.greenart7c3.nostrsigner.service.Biometrics import com.greenart7c3.nostrsigner.service.BunkerRequestUtils import com.greenart7c3.nostrsigner.ui.AccountScreen import com.greenart7c3.nostrsigner.ui.AccountStateViewModel -import com.greenart7c3.nostrsigner.ui.BiometricsTimeType import com.greenart7c3.nostrsigner.ui.CenterCircularProgressIndicator -import com.greenart7c3.nostrsigner.ui.components.RandomPinInput +import com.greenart7c3.nostrsigner.ui.components.BiometricAuthScreen import com.greenart7c3.nostrsigner.ui.navigation.Route import com.greenart7c3.nostrsigner.ui.theme.NostrSignerTheme import com.vitorpamplona.quartz.nip19Bech32.toNpub @@ -100,119 +89,18 @@ class MainActivity : AppCompatActivity() { ) { val navController = rememberNavController() mainViewModel.navController = navController - var isAuthenticated by remember { mutableStateOf(false) } - var showPinDialog by remember { mutableStateOf(false) } - val keyguardLauncher = - rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult -> - if (result.resultCode == RESULT_OK) { - isAuthenticated = true - } - } - val scope = rememberCoroutineScope() val context = LocalContext.current - - LaunchedEffect(Unit) { - launch(Dispatchers.IO) { - val settings = Amber.instance.settings - val lastAuthTime = settings.lastBiometricsTime - val whenToAsk = settings.biometricsTimeType - val isTimeToAsk = when (whenToAsk) { - BiometricsTimeType.EVERY_TIME -> true - BiometricsTimeType.ONE_MINUTE -> minutesBetween(lastAuthTime, System.currentTimeMillis()) >= 1 - BiometricsTimeType.FIVE_MINUTES -> minutesBetween(lastAuthTime, System.currentTimeMillis()) >= 5 - BiometricsTimeType.TEN_MINUTES -> minutesBetween(lastAuthTime, System.currentTimeMillis()) >= 10 - } - val shouldAuthenticate = (settings.useAuth || settings.usePin) && isTimeToAsk - if (!shouldAuthenticate) { - isAuthenticated = true - } else { - if (!isAuthenticated) { - launch(Dispatchers.Main) { - if (settings.usePin) { - showPinDialog = true - } else { - Biometrics.authenticate( - getString(R.string.authenticate), - this@MainActivity, - keyguardLauncher, - { - Amber.instance.settings = Amber.instance.settings.copy( - lastBiometricsTime = System.currentTimeMillis(), - ) - - LocalPreferences.saveSettingsToEncryptedStorage(Amber.instance.settings) - isAuthenticated = true - }, - { _, message -> - this@MainActivity.finishAndRemoveTask() - scope.launch { - Toast.makeText( - context, - message, - Toast.LENGTH_SHORT, - ).show() - } - }, - ) - } - } - } - } - } - } + var isAuthenticated by remember { mutableStateOf(false) } Surface( modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background, ) { - if (showPinDialog) { - Dialog( - properties = DialogProperties(usePlatformDefaultWidth = false), - onDismissRequest = { - showPinDialog = false - this@MainActivity.finishAndRemoveTask() - scope.launch { - Toast.makeText( - context, - getString(R.string.pin_does_not_match), - Toast.LENGTH_SHORT, - ).show() - } - }, - ) { - Surface( - modifier = Modifier - .fillMaxSize(), - color = MaterialTheme.colorScheme.background, - ) { - Box( - Modifier.padding(40.dp), - ) { - RandomPinInput( - text = getString(R.string.enter_pin), - onPinEntered = { - val pin = LocalPreferences.loadPinFromEncryptedStorage() - if (it == pin) { - Amber.instance.settings = Amber.instance.settings.copy( - lastBiometricsTime = System.currentTimeMillis(), - ) - - LocalPreferences.saveSettingsToEncryptedStorage(Amber.instance.settings) - isAuthenticated = true - showPinDialog = false - } else { - Toast.makeText( - context, - getString(R.string.pin_does_not_match), - Toast.LENGTH_SHORT, - ).show() - } - }, - ) - } - } - } - } + BiometricAuthScreen( + onAuth = { + isAuthenticated = it + }, + ) if (!isAuthenticated) { Box( diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/BiometricAuthScreen.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/BiometricAuthScreen.kt new file mode 100644 index 00000000..0d720032 --- /dev/null +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/BiometricAuthScreen.kt @@ -0,0 +1,143 @@ +package com.greenart7c3.nostrsigner.ui.components + +import android.app.Activity.RESULT_OK +import android.widget.Toast +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.ActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +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.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.DialogProperties +import com.greenart7c3.nostrsigner.Amber +import com.greenart7c3.nostrsigner.LocalPreferences +import com.greenart7c3.nostrsigner.R +import com.greenart7c3.nostrsigner.minutesBetween +import com.greenart7c3.nostrsigner.service.Biometrics +import com.greenart7c3.nostrsigner.ui.BiometricsTimeType +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +@Composable +fun BiometricAuthScreen( + onAuth: (Boolean) -> Unit, +) { + val context = LocalContext.current + var showPinDialog by remember { mutableStateOf(false) } + val keyguardLauncher = + rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult -> + if (result.resultCode == RESULT_OK) { + onAuth(true) + } + } + + LaunchedEffect(Unit) { + launch(Dispatchers.IO) { + val settings = Amber.instance.settings + val lastAuthTime = settings.lastBiometricsTime + val whenToAsk = settings.biometricsTimeType + val isTimeToAsk = when (whenToAsk) { + BiometricsTimeType.EVERY_TIME -> true + BiometricsTimeType.ONE_MINUTE -> minutesBetween(lastAuthTime, System.currentTimeMillis()) >= 1 + BiometricsTimeType.FIVE_MINUTES -> minutesBetween(lastAuthTime, System.currentTimeMillis()) >= 5 + BiometricsTimeType.TEN_MINUTES -> minutesBetween(lastAuthTime, System.currentTimeMillis()) >= 10 + } + val shouldAuthenticate = (settings.useAuth || settings.usePin) && isTimeToAsk + if (!shouldAuthenticate) { + onAuth(true) + } else { + launch(Dispatchers.Main) { + if (settings.usePin) { + showPinDialog = true + } else { + Biometrics.authenticate( + context.getString(R.string.authenticate), + context, + keyguardLauncher, + { + Amber.instance.settings = Amber.instance.settings.copy( + lastBiometricsTime = System.currentTimeMillis(), + ) + + LocalPreferences.saveSettingsToEncryptedStorage(Amber.instance.settings) + onAuth(true) + }, + { _, message -> + Amber.instance.getMainActivity()?.finishAndRemoveTask() + Amber.instance.applicationIOScope.launch(Dispatchers.Main) { + Toast.makeText( + context, + message, + Toast.LENGTH_SHORT, + ).show() + } + }, + ) + } + } + } + } + } + + if (showPinDialog) { + Dialog( + properties = DialogProperties(usePlatformDefaultWidth = false), + onDismissRequest = { + showPinDialog = false + Amber.instance.getMainActivity()?.finishAndRemoveTask() + Amber.instance.applicationIOScope.launch(Dispatchers.Main) { + Toast.makeText( + context, + Amber.instance.getString(R.string.pin_does_not_match), + Toast.LENGTH_SHORT, + ).show() + } + }, + ) { + Surface( + modifier = Modifier + .fillMaxSize(), + color = MaterialTheme.colorScheme.background, + ) { + Box( + Modifier.padding(40.dp), + ) { + RandomPinInput( + text = stringResource(R.string.enter_pin), + onPinEntered = { + val pin = LocalPreferences.loadPinFromEncryptedStorage() + if (it == pin) { + Amber.instance.settings = Amber.instance.settings.copy( + lastBiometricsTime = System.currentTimeMillis(), + ) + + LocalPreferences.saveSettingsToEncryptedStorage(Amber.instance.settings) + onAuth(true) + showPinDialog = false + } else { + Toast.makeText( + context, + context.getString(R.string.pin_does_not_match), + Toast.LENGTH_SHORT, + ).show() + } + }, + ) + } + } + } + } +}