From 75de5f3e0a034079f4df8876a09ed3ea38d3619c Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 13 Mar 2026 11:09:47 +0000 Subject: [PATCH] Replace runBlocking with suspend functions for PIN storage Make loadPinFromEncryptedStorage and savePinToEncryptedStorage suspend functions, and update all callers (SecurityScreen, ConfirmPinScreen, BiometricAuthScreen) to use coroutine scopes instead of blocking the calling thread. https://claude.ai/code/session_01TeTHfGJL42J7eHCDdhsDnP --- .../nostrsigner/LocalPreferences.kt | 10 ++--- .../nostrsigner/ui/SecurityScreen.kt | 16 ++++++-- .../nostrsigner/ui/SetupPinScreen.kt | 33 +++++++++-------- .../ui/components/BiometricAuthScreen.kt | 37 +++++++++++-------- 4 files changed, 56 insertions(+), 40 deletions(-) diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/LocalPreferences.kt b/app/src/main/java/com/greenart7c3/nostrsigner/LocalPreferences.kt index dc6cbc2f..e0d076ba 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/LocalPreferences.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/LocalPreferences.kt @@ -157,20 +157,20 @@ object LocalPreferences { } } - fun loadPinFromEncryptedStorage(): String? { + suspend fun loadPinFromEncryptedStorage(): String? { val context = Amber.instance // Migration: move PIN from plain SharedPreferences to encrypted DataStore val legacyPin = sharedPrefs(context).getString(SettingsKeys.PIN.key, null) if (legacyPin != null) { - runBlocking { DataStoreAccess.savePin(context, legacyPin) } + DataStoreAccess.savePin(context, legacyPin) sharedPrefs(context).edit { remove(SettingsKeys.PIN.key) } } - return runBlocking { DataStoreAccess.loadPin(context) } + return DataStoreAccess.loadPin(context) } - fun savePinToEncryptedStorage(pin: String?) { + suspend fun savePinToEncryptedStorage(pin: String?) { val context = Amber.instance - runBlocking { DataStoreAccess.savePin(context, pin) } + DataStoreAccess.savePin(context, pin) } suspend fun reloadApp() { diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/SecurityScreen.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/SecurityScreen.kt index e7c9478f..d1426fc7 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/SecurityScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/SecurityScreen.kt @@ -90,8 +90,12 @@ fun SecurityScreen( .padding(horizontal = 8.dp) .clickable { if (setupPin) { - val pin = LocalPreferences.loadPinFromEncryptedStorage() - navController.navigate("${Route.ConfirmPin.route.split("/")[0]}/$pin") + scope.launch(Dispatchers.IO) { + val pin = LocalPreferences.loadPinFromEncryptedStorage() + scope.launch(Dispatchers.Main) { + navController.navigate("${Route.ConfirmPin.route.split("/")[0]}/$pin") + } + } } else { navController.navigate(Route.SetupPin.route) } @@ -105,8 +109,12 @@ fun SecurityScreen( checked = setupPin, onCheckedChange = { if (setupPin) { - val pin = LocalPreferences.loadPinFromEncryptedStorage() - navController.navigate("${Route.ConfirmPin.route.split("/")[0]}/$pin") + scope.launch(Dispatchers.IO) { + val pin = LocalPreferences.loadPinFromEncryptedStorage() + scope.launch(Dispatchers.Main) { + navController.navigate("${Route.ConfirmPin.route.split("/")[0]}/$pin") + } + } } else { navController.navigate(Route.SetupPin.route) } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/SetupPinScreen.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/SetupPinScreen.kt index c1c91c0d..8e78617d 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/SetupPinScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/SetupPinScreen.kt @@ -2,6 +2,7 @@ package com.greenart7c3.nostrsigner.ui import androidx.compose.foundation.layout.Column import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.navigation.NavController @@ -10,6 +11,8 @@ import com.greenart7c3.nostrsigner.LocalPreferences import com.greenart7c3.nostrsigner.R import com.greenart7c3.nostrsigner.ui.components.RandomPinInput import com.greenart7c3.nostrsigner.ui.navigation.Route +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch @Composable fun SetupPinScreen( @@ -32,6 +35,7 @@ fun ConfirmPinScreen( navController: NavController, ) { val context = LocalContext.current + val scope = rememberCoroutineScope() Column( modifier, ) { @@ -41,22 +45,21 @@ fun ConfirmPinScreen( ToastManager.toast(context.getString(R.string.pin), context.getString(R.string.pin_does_not_match)) } else { val usePin = Amber.instance.settings.usePin - if (usePin) { - Amber.instance.settings = Amber.instance.settings.copy(usePin = false) - LocalPreferences.saveSettingsToEncryptedStorage(Amber.instance.settings) - LocalPreferences.savePinToEncryptedStorage(null) - navController.navigate(Route.Security.route) { - popUpTo(Route.Security.route) { - inclusive = true - } + scope.launch(Dispatchers.IO) { + if (usePin) { + Amber.instance.settings = Amber.instance.settings.copy(usePin = false) + LocalPreferences.saveSettingsToEncryptedStorage(Amber.instance.settings) + LocalPreferences.savePinToEncryptedStorage(null) + } else { + Amber.instance.settings = Amber.instance.settings.copy(usePin = true) + LocalPreferences.saveSettingsToEncryptedStorage(Amber.instance.settings) + LocalPreferences.savePinToEncryptedStorage(pin) } - } else { - Amber.instance.settings = Amber.instance.settings.copy(usePin = true) - LocalPreferences.saveSettingsToEncryptedStorage(Amber.instance.settings) - LocalPreferences.savePinToEncryptedStorage(pin) - navController.navigate(Route.Security.route) { - popUpTo(Route.Security.route) { - inclusive = true + scope.launch(Dispatchers.Main) { + navController.navigate(Route.Security.route) { + popUpTo(Route.Security.route) { + inclusive = true + } } } } 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 index 0d720032..ce999e51 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/BiometricAuthScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/BiometricAuthScreen.kt @@ -15,6 +15,7 @@ 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.Modifier import androidx.compose.ui.platform.LocalContext @@ -36,6 +37,7 @@ fun BiometricAuthScreen( onAuth: (Boolean) -> Unit, ) { val context = LocalContext.current + val scope = rememberCoroutineScope() var showPinDialog by remember { mutableStateOf(false) } val keyguardLauncher = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult -> @@ -117,22 +119,25 @@ fun BiometricAuthScreen( ) { 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() + onPinEntered = { enteredPin -> + scope.launch(Dispatchers.IO) { + val pin = LocalPreferences.loadPinFromEncryptedStorage() + scope.launch(Dispatchers.Main) { + if (enteredPin == 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() + } + } } }, )