diff --git a/android/src/main/java/io/parity/signer/domain/MainFlowViewModel.kt b/android/src/main/java/io/parity/signer/domain/MainFlowViewModel.kt index ab7e18eac3..552da9ee3e 100644 --- a/android/src/main/java/io/parity/signer/domain/MainFlowViewModel.kt +++ b/android/src/main/java/io/parity/signer/domain/MainFlowViewModel.kt @@ -7,7 +7,9 @@ import androidx.fragment.app.FragmentActivity import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import io.parity.signer.dependencygraph.ServiceLocator +import io.parity.signer.domain.backend.OperationResult import io.parity.signer.domain.usecases.ResetUseCase +import io.parity.signer.screens.error.ErrorStateDestinationState import io.parity.signer.uniffi.* import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch @@ -27,17 +29,16 @@ class MainFlowViewModel() : ViewModel() { val activity: FragmentActivity get() = ServiceLocator.activityScope!!.activity - fun onUnlockClicked() { - viewModelScope.launch { - when (authentication.authenticate(activity)) { + suspend fun onUnlockClicked(): OperationResult { + return when (authentication.authenticate(activity)) { AuthResult.AuthSuccess -> resetUseCase.totalRefresh() AuthResult.AuthError, AuthResult.AuthFailed, AuthResult.AuthUnavailable -> { Timber.e("Signer", "Auth failed, not unlocked") + OperationResult.Ok(Unit) } } - } } val authenticated: StateFlow = authentication.auth diff --git a/android/src/main/java/io/parity/signer/domain/NetworkExposedStateKeeper.kt b/android/src/main/java/io/parity/signer/domain/NetworkExposedStateKeeper.kt index 44a2d23982..0c675776ef 100644 --- a/android/src/main/java/io/parity/signer/domain/NetworkExposedStateKeeper.kt +++ b/android/src/main/java/io/parity/signer/domain/NetworkExposedStateKeeper.kt @@ -8,12 +8,12 @@ import android.content.Intent import android.content.IntentFilter import android.net.wifi.WifiManager import android.provider.Settings -import android.util.Log import io.parity.signer.domain.backend.UniffiInteractor import io.parity.signer.uniffi.historyAcknowledgeWarnings import io.parity.signer.uniffi.historyGetWarnings import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import timber.log.Timber class NetworkExposedStateKeeper( @@ -89,7 +89,6 @@ class NetworkExposedStateKeeper( val intentFilter = IntentFilter("android.hardware.usb.action.USB_STATE") val receiver: BroadcastReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { - Log.e("TAGG", "usb broadcast") reactOnUsb(intent) } } @@ -134,7 +133,7 @@ class NetworkExposedStateKeeper( private fun reactOnUsb(usbIntent: Intent) { if (FeatureFlags.isEnabled(FeatureOption.SKIP_USB_CHECK)) { - _usbDisconnected.value = false + _usbDisconnected.value = true updateGeneralAirgapState() return } @@ -149,7 +148,7 @@ class NetworkExposedStateKeeper( updateGeneralAirgapState() } null -> { - Log.d("USB", "usb action intent doesn't have connection state") + Timber.d("USB", "usb action intent doesn't have connection state") } } } diff --git a/android/src/main/java/io/parity/signer/domain/storage/SeedStorage.kt b/android/src/main/java/io/parity/signer/domain/storage/SeedStorage.kt index 7924c9c7c4..cf1fb945fb 100644 --- a/android/src/main/java/io/parity/signer/domain/storage/SeedStorage.kt +++ b/android/src/main/java/io/parity/signer/domain/storage/SeedStorage.kt @@ -8,13 +8,19 @@ import android.security.keystore.UserNotAuthenticatedException import timber.log.Timber import androidx.security.crypto.EncryptedSharedPreferences import androidx.security.crypto.MasterKey +import io.parity.signer.R import io.parity.signer.domain.FeatureFlags import io.parity.signer.domain.FeatureOption +import io.parity.signer.domain.backend.OperationResult +import io.parity.signer.screens.error.ErrorStateDestinationState +import io.parity.signer.uniffi.ErrorDisplayed import io.parity.signer.uniffi.historySeedWasShown import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update +import java.security.UnrecoverableKeyException +import javax.crypto.AEADBadTagException /** @@ -42,7 +48,7 @@ class SeedStorage { /** * @throws UserNotAuthenticatedException */ - fun init(appContext: Context) { + fun init(appContext: Context): OperationResult { hasStrongbox = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { appContext .packageManager @@ -76,21 +82,26 @@ class SeedStorage { Timber.e("ENCRY", "$appContext $KEYSTORE_NAME $masterKey") //we need to be authenticated for this - sharedPreferences = - if (FeatureFlags.isEnabled(FeatureOption.SKIP_UNLOCK_FOR_DEVELOPMENT)) { - appContext.getSharedPreferences( - "FeatureOption.SKIP_UNLOCK_FOR_DEVELOPMENT", - Context.MODE_PRIVATE - ) - } else { - EncryptedSharedPreferences( - appContext, - KEYSTORE_NAME, - masterKey, - EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, - EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM - ) - } + try { + sharedPreferences = + if (FeatureFlags.isEnabled(FeatureOption.SKIP_UNLOCK_FOR_DEVELOPMENT)) { + appContext.getSharedPreferences( + "FeatureOption.SKIP_UNLOCK_FOR_DEVELOPMENT", + Context.MODE_PRIVATE + ) + } else { + EncryptedSharedPreferences( + appContext, + KEYSTORE_NAME, + masterKey, + EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, + EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM + ) + } + } catch (e: Exception) { + return OperationResult.Err(consumeStorageAuthError(e, appContext)) + } + return OperationResult.Ok(Unit) } @@ -174,6 +185,38 @@ class SeedStorage { fun wipe() { sharedPreferences.edit().clear().commit() // No, not apply(), do it now! } +} - +private fun consumeStorageAuthError(e: Exception, context: Context): ErrorStateDestinationState { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + when (e) { + is AEADBadTagException, + is android.security.KeyStoreException, + is UnrecoverableKeyException -> { + return ErrorStateDestinationState( + argHeader = context.getString(R.string.error_secure_storage_title), + argDescription = context.getString(R.string.error_secure_storage_description), + argVerbose = e.stackTraceToString() + ) + } + else -> throw e + } + } else { + when (e) { + is AEADBadTagException, + is UnrecoverableKeyException -> { + return ErrorStateDestinationState( + argHeader = context.getString(R.string.error_secure_storage_title), + argDescription = context.getString(R.string.error_secure_storage_description), + argVerbose = e.stackTraceToString() + ) + } + else -> throw e + } + } } + + + + + diff --git a/android/src/main/java/io/parity/signer/domain/usecases/ResetUseCase.kt b/android/src/main/java/io/parity/signer/domain/usecases/ResetUseCase.kt index 2a3f1817af..fdb708c955 100644 --- a/android/src/main/java/io/parity/signer/domain/usecases/ResetUseCase.kt +++ b/android/src/main/java/io/parity/signer/domain/usecases/ResetUseCase.kt @@ -1,11 +1,16 @@ package io.parity.signer.domain.usecases +import android.widget.Toast import androidx.fragment.app.FragmentActivity +import io.parity.signer.R import io.parity.signer.dependencygraph.ServiceLocator +import io.parity.signer.domain.AuthResult import io.parity.signer.domain.Callback +import io.parity.signer.domain.backend.OperationResult import io.parity.signer.domain.getDbNameFromContext import io.parity.signer.domain.isDbCreatedAndOnboardingPassed import io.parity.signer.domain.storage.DatabaseAssetsInteractor +import io.parity.signer.screens.error.ErrorStateDestinationState import io.parity.signer.uniffi.historyInitHistoryNoCert import io.parity.signer.uniffi.historyInitHistoryWithCert import io.parity.signer.uniffi.initNavigation @@ -22,26 +27,52 @@ class ResetUseCase { private val activity: FragmentActivity get() = ServiceLocator.activityScope!!.activity - fun wipeToFactoryWithAuth(onAfterWide: Callback) { + suspend fun wipeToFactoryWithAuth(onAfterWipe: Callback): OperationResult { val authentication = ServiceLocator.authentication - authentication.authenticate(activity) { - databaseAssetsInteractor.wipe() - totalRefresh() - onAfterWide() + return when (authentication.authenticate(activity)) { + AuthResult.AuthError, + AuthResult.AuthFailed , + AuthResult.AuthUnavailable -> { + Toast.makeText( + activity.baseContext, + activity.baseContext.getString(R.string.auth_failed_message), + Toast.LENGTH_SHORT + ).show() + OperationResult.Ok(Unit) + } + AuthResult.AuthSuccess -> { + databaseAssetsInteractor.wipe() + val result = totalRefresh() + onAfterWipe() + return result + } } } /** * Auth user and wipe Vault to state without general verifier certificate */ - fun wipeNoGeneralCertWithAuth(onAfterWide: Callback) { + suspend fun wipeNoGeneralCertWithAuth(onAfterWide: Callback): OperationResult { val authentication = ServiceLocator.authentication - authentication.authenticate(activity) { - databaseAssetsInteractor.wipe() - databaseAssetsInteractor.copyAsset("") - totalRefresh() - historyInitHistoryNoCert() - onAfterWide() + return when (authentication.authenticate(activity)) { + AuthResult.AuthError, + AuthResult.AuthFailed, + AuthResult.AuthUnavailable -> { + Toast.makeText( + activity.baseContext, + activity.baseContext.getString(R.string.auth_failed_message), + Toast.LENGTH_SHORT + ).show() + OperationResult.Ok(Unit) + } + AuthResult.AuthSuccess -> { + databaseAssetsInteractor.wipe() + databaseAssetsInteractor.copyAsset("") + val result = totalRefresh() + historyInitHistoryNoCert() + onAfterWide() + return result + } } } @@ -67,14 +98,18 @@ class ResetUseCase { * This returns the app into starting state; * Do not forget to reset navigation UI state! */ - fun totalRefresh() { + fun totalRefresh(): OperationResult { if (!seedStorage.isInitialized()) { - seedStorage.init(appContext) + val result = seedStorage.init(appContext) + if (result is OperationResult.Err) { + return result + } } if (!appContext.isDbCreatedAndOnboardingPassed()) { initAssetsAndTotalRefresh() } else { totalRefreshDbExist() } + return OperationResult.Ok(Unit) } } diff --git a/android/src/main/java/io/parity/signer/screens/error/ErrorStateDestination.kt b/android/src/main/java/io/parity/signer/screens/error/ErrorStateDestination.kt index 823c4c3811..14ad0e125d 100644 --- a/android/src/main/java/io/parity/signer/screens/error/ErrorStateDestination.kt +++ b/android/src/main/java/io/parity/signer/screens/error/ErrorStateDestination.kt @@ -67,6 +67,11 @@ inline fun UniffiResult.handleErrorAppState(coreNavController: Na return this.toOperationResult().handleErrorAppState(coreNavController) } +data class ErrorStateDestinationState( + val argHeader: String, + val argDescription: String, + val argVerbose: String, +) inline fun OperationResult.handleErrorAppState( coreNavController: NavController @@ -75,6 +80,13 @@ inline fun OperationResult.handleErrorAppState( is OperationResult.Err -> { coreNavController.navigate( when (error) { + is ErrorStateDestinationState -> { + CoreUnlockedNavSubgraph.ErrorScreenGeneral.destination( + argHeader = error.argHeader, + argDescription = error.argDescription, + argVerbose = error.argVerbose, + ) + } is NavigationError -> { CoreUnlockedNavSubgraph.ErrorScreenGeneral.destination( argHeader = "Operation navigation error trying to get ${T::class.java}", @@ -88,6 +100,7 @@ inline fun OperationResult.handleErrorAppState( is ErrorDisplayed.DbSchemaMismatch -> { CoreUnlockedNavSubgraph.errorWrongDbVersionUpdate } + else -> { CoreUnlockedNavSubgraph.ErrorScreenGeneral.destination( argHeader = "Operation error to get ${T::class.java}", diff --git a/android/src/main/java/io/parity/signer/screens/settings/SettingsNavGraph.kt b/android/src/main/java/io/parity/signer/screens/settings/SettingsNavGraph.kt index 7a662c6674..7f09bbd00a 100644 --- a/android/src/main/java/io/parity/signer/screens/settings/SettingsNavGraph.kt +++ b/android/src/main/java/io/parity/signer/screens/settings/SettingsNavGraph.kt @@ -29,7 +29,7 @@ import io.parity.signer.ui.mainnavigation.CoreUnlockedNavSubgraph * all subsequent interactions should be in modals or drop-down menus */ fun NavGraphBuilder.settingsFullSubgraph( - navController: NavController, + coreNavController: NavController, ) { navigation( route = CoreUnlockedNavSubgraph.settings, @@ -40,32 +40,32 @@ fun NavGraphBuilder.settingsFullSubgraph( enterTransition = { fadeIn(animationSpec = tween(700)) }, exitTransition = { fadeOut(animationSpec = tween(700)) }, ) { - SettingsGeneralNavSubgraph(parentNavController = navController) + SettingsGeneralNavSubgraph(coreNavController = coreNavController) } composable(SettingsNavSubgraph.terms) { Box(modifier = Modifier.statusBarsPadding()) { TosScreen(onBack = { - navController.popBackStack(SettingsNavSubgraph.home, false) + coreNavController.popBackStack(SettingsNavSubgraph.home, false) }) } } composable(SettingsNavSubgraph.privacyPolicy) { Box(modifier = Modifier.statusBarsPadding()) { PpScreen(onBack = { - navController.popBackStack(SettingsNavSubgraph.home, false) + coreNavController.popBackStack(SettingsNavSubgraph.home, false) }) } } composable(SettingsNavSubgraph.backup) { - SeedBackupIntegratedScreen(navController) { - navController.popBackStack(SettingsNavSubgraph.home, false) + SeedBackupIntegratedScreen(coreNavController) { + coreNavController.popBackStack(SettingsNavSubgraph.home, false) } } logsNavigationSubgraph( - navController = navController, + navController = coreNavController, ) - networkListDestination(navController) - verifierSettingsDestination(navController) + networkListDestination(coreNavController) + verifierSettingsDestination(coreNavController) composable( route = SettingsNavSubgraph.NetworkDetails.route, arguments = listOf( @@ -78,10 +78,10 @@ fun NavGraphBuilder.settingsFullSubgraph( it.arguments?.getString(SettingsNavSubgraph.NetworkDetails.networkKey)!! NetworkDetailsSubgraph( networkKey, - navController, + coreNavController, ) } - signSpecsDestination(navController) + signSpecsDestination(coreNavController) } } diff --git a/android/src/main/java/io/parity/signer/screens/settings/general/SettingsGeneralNavSubgraph.kt b/android/src/main/java/io/parity/signer/screens/settings/general/SettingsGeneralNavSubgraph.kt index 89f2949276..b1e711ee4b 100644 --- a/android/src/main/java/io/parity/signer/screens/settings/general/SettingsGeneralNavSubgraph.kt +++ b/android/src/main/java/io/parity/signer/screens/settings/general/SettingsGeneralNavSubgraph.kt @@ -9,6 +9,7 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavController import androidx.navigation.compose.NavHost @@ -16,14 +17,16 @@ import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import io.parity.signer.components.exposesecurity.ExposedAlert import io.parity.signer.domain.Callback +import io.parity.signer.screens.error.handleErrorAppState import io.parity.signer.screens.settings.SettingsNavSubgraph import io.parity.signer.ui.BottomSheetWrapperRoot import io.parity.signer.ui.mainnavigation.CoreUnlockedNavSubgraph +import kotlinx.coroutines.launch @Composable internal fun SettingsGeneralNavSubgraph( - parentNavController: NavController, + coreNavController: NavController, ) { val context = LocalContext.current val vm: SettingsGeneralViewModel = viewModel() @@ -35,19 +38,19 @@ internal fun SettingsGeneralNavSubgraph( Box(modifier = Modifier.statusBarsPadding()) { SettingsScreenGeneralView( - navController = parentNavController, + navController = coreNavController, onWipeData = { menuNavController.navigate(SettingsGeneralMenu.wipe_factory) }, - onOpenLogs = { parentNavController.navigate(SettingsNavSubgraph.logs) }, - onShowTerms = { parentNavController.navigate(SettingsNavSubgraph.terms) }, + onOpenLogs = { coreNavController.navigate(SettingsNavSubgraph.logs) }, + onShowTerms = { coreNavController.navigate(SettingsNavSubgraph.terms) }, onShowPrivacyPolicy = { - parentNavController.navigate(SettingsNavSubgraph.privacyPolicy) + coreNavController.navigate(SettingsNavSubgraph.privacyPolicy) }, - onBackup = { parentNavController.navigate(SettingsNavSubgraph.backup) }, + onBackup = { coreNavController.navigate(SettingsNavSubgraph.backup) }, onManageNetworks = { - parentNavController.navigate(SettingsNavSubgraph.networkList) + coreNavController.navigate(SettingsNavSubgraph.networkList) }, onGeneralVerifier = { - parentNavController.navigate(SettingsNavSubgraph.generalVerifier) + coreNavController.navigate(SettingsNavSubgraph.generalVerifier) }, onExposedClicked = { menuNavController.navigate(SettingsGeneralMenu.exposed_shield_alert) }, isStrongBoxProtected = vm.isStrongBoxProtected, @@ -73,10 +76,12 @@ internal fun SettingsGeneralNavSubgraph( ConfirmFactorySettingsBottomSheet( onCancel = closeAction, onFactoryReset = { - vm.wipeToFactory { - parentNavController.navigate( - CoreUnlockedNavSubgraph.KeySet.destination(null) - ) + vm.viewModelScope.launch { + vm.wipeToFactory { + coreNavController.navigate( + CoreUnlockedNavSubgraph.KeySet.destination(null) + ) + }.handleErrorAppState(coreNavController) } } ) diff --git a/android/src/main/java/io/parity/signer/screens/settings/general/SettingsGeneralViewModel.kt b/android/src/main/java/io/parity/signer/screens/settings/general/SettingsGeneralViewModel.kt index e09baf2c54..ea861a8bb3 100644 --- a/android/src/main/java/io/parity/signer/screens/settings/general/SettingsGeneralViewModel.kt +++ b/android/src/main/java/io/parity/signer/screens/settings/general/SettingsGeneralViewModel.kt @@ -5,7 +5,9 @@ import androidx.lifecycle.ViewModel import io.parity.signer.dependencygraph.ServiceLocator import io.parity.signer.domain.Callback import io.parity.signer.domain.NetworkState +import io.parity.signer.domain.backend.OperationResult import io.parity.signer.domain.usecases.ResetUseCase +import io.parity.signer.screens.error.ErrorStateDestinationState import kotlinx.coroutines.flow.StateFlow @@ -29,7 +31,7 @@ class SettingsGeneralViewModel: ViewModel() { /** * Auth user and wipe the Vault to initial state */ - fun wipeToFactory(onAfterWipe: Callback) { - resetUseCase.wipeToFactoryWithAuth(onAfterWipe) + suspend fun wipeToFactory(onAfterWipe: Callback): OperationResult { + return resetUseCase.wipeToFactoryWithAuth(onAfterWipe) } } diff --git a/android/src/main/java/io/parity/signer/screens/settings/verifiercert/VerifierCertViewModel.kt b/android/src/main/java/io/parity/signer/screens/settings/verifiercert/VerifierCertViewModel.kt index fd973115d1..2c49fe8e57 100644 --- a/android/src/main/java/io/parity/signer/screens/settings/verifiercert/VerifierCertViewModel.kt +++ b/android/src/main/java/io/parity/signer/screens/settings/verifiercert/VerifierCertViewModel.kt @@ -4,8 +4,10 @@ import androidx.lifecycle.ViewModel import io.parity.signer.dependencygraph.ServiceLocator import io.parity.signer.domain.Callback import io.parity.signer.domain.VerifierDetailsModel +import io.parity.signer.domain.backend.OperationResult import io.parity.signer.domain.backend.UniffiResult import io.parity.signer.domain.usecases.ResetUseCase +import io.parity.signer.screens.error.ErrorStateDestinationState class VerifierCertViewModel: ViewModel() { @@ -16,7 +18,7 @@ class VerifierCertViewModel: ViewModel() { return uniffiInteractor.getVerifierDetails() } - fun wipeWithGeneralCertificate(onAfterAction: Callback) { - resetUseCase.wipeNoGeneralCertWithAuth(onAfterAction) + suspend fun wipeWithGeneralCertificate(onAfterAction: Callback): OperationResult { + return resetUseCase.wipeNoGeneralCertWithAuth(onAfterAction) } } diff --git a/android/src/main/java/io/parity/signer/screens/settings/verifiercert/VerifierSettingsScreenDestination.kt b/android/src/main/java/io/parity/signer/screens/settings/verifiercert/VerifierSettingsScreenDestination.kt index 9f5f447219..5b3b29c9a7 100644 --- a/android/src/main/java/io/parity/signer/screens/settings/verifiercert/VerifierSettingsScreenDestination.kt +++ b/android/src/main/java/io/parity/signer/screens/settings/verifiercert/VerifierSettingsScreenDestination.kt @@ -1,6 +1,7 @@ package io.parity.signer.screens.settings.verifiercert import androidx.compose.runtime.remember +import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder @@ -8,11 +9,12 @@ import androidx.navigation.compose.composable import io.parity.signer.screens.error.handleErrorAppState import io.parity.signer.screens.settings.SettingsNavSubgraph import io.parity.signer.ui.mainnavigation.CoreUnlockedNavSubgraph +import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking fun NavGraphBuilder.verifierSettingsDestination( - navController: NavController, + coreNavController: NavController, ) { composable(SettingsNavSubgraph.generalVerifier) { val vm: VerifierCertViewModel = viewModel() @@ -20,19 +22,21 @@ fun NavGraphBuilder.verifierSettingsDestination( val model = remember { runBlocking { vm.getVerifierCertModel() - }.handleErrorAppState(navController) + }.handleErrorAppState(coreNavController) } ?: return@composable VerifierScreenFull( verifierDetails = model, wipe = { - vm.wipeWithGeneralCertificate { - navController.navigate( - CoreUnlockedNavSubgraph.KeySet.destination(null) - ) + vm.viewModelScope.launch { + vm.wipeWithGeneralCertificate { + coreNavController.navigate( + CoreUnlockedNavSubgraph.KeySet.destination(null) + ) + }.handleErrorAppState(coreNavController) } }, - onBack = navController::popBackStack, + onBack = coreNavController::popBackStack, ) } } diff --git a/android/src/main/java/io/parity/signer/ui/mainnavigation/CoreUnlockedNavSubgraph.kt b/android/src/main/java/io/parity/signer/ui/mainnavigation/CoreUnlockedNavSubgraph.kt index 2b7f5030a8..22b729df4e 100644 --- a/android/src/main/java/io/parity/signer/ui/mainnavigation/CoreUnlockedNavSubgraph.kt +++ b/android/src/main/java/io/parity/signer/ui/mainnavigation/CoreUnlockedNavSubgraph.kt @@ -123,7 +123,7 @@ fun CoreUnlockedNavSubgraph(navController: NavHostController) { ) } settingsFullSubgraph( - navController = navController, + coreNavController = navController, ) networkHelpersCoreSubgraph( navController = navController, diff --git a/android/src/main/java/io/parity/signer/ui/rootnavigation/MainScreensAppFlow.kt b/android/src/main/java/io/parity/signer/ui/rootnavigation/MainScreensAppFlow.kt index 9d102c2eda..f72b3ae3cb 100644 --- a/android/src/main/java/io/parity/signer/ui/rootnavigation/MainScreensAppFlow.kt +++ b/android/src/main/java/io/parity/signer/ui/rootnavigation/MainScreensAppFlow.kt @@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.captionBarPadding import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.ui.Modifier import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavGraphBuilder import androidx.navigation.NavHostController @@ -15,8 +16,10 @@ import androidx.navigation.compose.rememberNavController import io.parity.signer.domain.MainFlowViewModel import io.parity.signer.domain.NetworkState import io.parity.signer.domain.addVaultLogger +import io.parity.signer.screens.error.handleErrorAppState import io.parity.signer.screens.initial.UnlockAppAuthScreen import io.parity.signer.ui.mainnavigation.CoreUnlockedNavSubgraph +import kotlinx.coroutines.launch fun NavGraphBuilder.mainSignerAppFlow(globalNavController: NavHostController) { @@ -26,9 +29,11 @@ fun NavGraphBuilder.mainSignerAppFlow(globalNavController: NavHostController) { val authenticated = mainFlowViewModel.authenticated.collectAsStateWithLifecycle() - val unlockedNavController = rememberNavController().apply { addVaultLogger() } + val unlockedNavController = + rememberNavController().apply { addVaultLogger() } - val networkState = mainFlowViewModel.networkState.collectAsStateWithLifecycle() + val networkState = + mainFlowViewModel.networkState.collectAsStateWithLifecycle() if (authenticated.value) { // Structure to contain all app @@ -44,14 +49,20 @@ fun NavGraphBuilder.mainSignerAppFlow(globalNavController: NavHostController) { when (networkState.value) { NetworkState.Active -> { if (unlockedNavController.currentDestination - ?.route != CoreUnlockedNavSubgraph.airgapBreached) { + ?.route != CoreUnlockedNavSubgraph.airgapBreached + ) { unlockedNavController.navigate(CoreUnlockedNavSubgraph.airgapBreached) } } else -> {} } } else { - UnlockAppAuthScreen(onUnlockClicked = mainFlowViewModel::onUnlockClicked) + UnlockAppAuthScreen(onUnlockClicked = { + mainFlowViewModel.viewModelScope.launch { + mainFlowViewModel.onUnlockClicked() + .handleErrorAppState(globalNavController) + } + }) } } } diff --git a/android/src/main/java/io/parity/signer/ui/rootnavigation/RootNavigationGraph.kt b/android/src/main/java/io/parity/signer/ui/rootnavigation/RootNavigationGraph.kt index fdcb1495b7..629dee9cda 100644 --- a/android/src/main/java/io/parity/signer/ui/rootnavigation/RootNavigationGraph.kt +++ b/android/src/main/java/io/parity/signer/ui/rootnavigation/RootNavigationGraph.kt @@ -16,6 +16,7 @@ import androidx.compose.ui.platform.LocalContext import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import io.parity.signer.domain.findActivity +import io.parity.signer.screens.error.errorStateDestination import io.parity.signer.screens.initial.eachstartchecks.enableEachStartAppFlow import io.parity.signer.screens.initial.firstTimeOnly.firstTimeOnlyOnboarding import io.parity.signer.screens.initial.splash.splashScreen @@ -73,6 +74,9 @@ fun RootNavigationGraph( ) enableEachStartAppFlow(navController) mainSignerAppFlow(navController) + errorStateDestination( //same destination as for core graph - so we can show error outside of unlock screen + navController = navController, + ) } } } diff --git a/android/src/main/res/values/strings.xml b/android/src/main/res/values/strings.xml index 3a9a9cf61e..ee4fa21e01 100644 --- a/android/src/main/res/values/strings.xml +++ b/android/src/main/res/values/strings.xml @@ -457,6 +457,8 @@ Please update your operation system for higher security Current Android version missing important security updated, exposed to at least: %1$s. It is recommended to install vault on up to date android version. Disable ADB on the device + Couldn\'t open secure storage + There was an error during opening secure storage. Possible reasons - device unlock method was changed. Error below