From 3f73286a6d656189dfa0941a8ed6aa8b9c247acd Mon Sep 17 00:00:00 2001 From: Dmitry Borodin Date: Wed, 17 Jan 2024 18:58:33 +0100 Subject: [PATCH 01/10] work in progress - catching errors when storage created to pass it to UI fixes #2272 --- .../signer/domain/storage/SeedStorage.kt | 50 +++++++++++++------ .../signer/domain/usecases/ResetUseCase.kt | 4 +- 2 files changed, 37 insertions(+), 17 deletions(-) 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..2d2519cff2 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 @@ -10,11 +10,15 @@ import androidx.security.crypto.EncryptedSharedPreferences import androidx.security.crypto.MasterKey import io.parity.signer.domain.FeatureFlags import io.parity.signer.domain.FeatureOption +import io.parity.signer.domain.backend.OperationResult +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 +46,8 @@ class SeedStorage { /** * @throws UserNotAuthenticatedException */ - fun init(appContext: Context) { + fun init(appContext: Context): OperationResult {//todo dmitry see errors + //todo dmitry see errors in io/parity/signer/screens/error/ErrorStateDestination.kt:71 hasStrongbox = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { appContext .packageManager @@ -76,21 +81,36 @@ 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) { + when (e) { + is AEADBadTagException, +// is android.security.KeyStoreException,//todo dmitry minapi requirement + is UnrecoverableKeyException -> { + //todo dmitry + return OperationResult.Err(ErrorDisplayed.Str("todo dmitry")) + } + + else -> throw e } + } + + return OperationResult.Ok(Unit) } 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..d1b0f2ff23 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 @@ -22,12 +22,12 @@ class ResetUseCase { private val activity: FragmentActivity get() = ServiceLocator.activityScope!!.activity - fun wipeToFactoryWithAuth(onAfterWide: Callback) { + fun wipeToFactoryWithAuth(onAfterWipe: Callback) { val authentication = ServiceLocator.authentication authentication.authenticate(activity) { databaseAssetsInteractor.wipe() totalRefresh() - onAfterWide() + onAfterWipe() } } From 930462a2da387f7d119dc28224a3c6b1267c6804 Mon Sep 17 00:00:00 2001 From: Dmitry Borodin Date: Thu, 18 Jan 2024 11:27:18 +0100 Subject: [PATCH 02/10] seed storage error handling implemented in seed storage --- .../signer/domain/storage/SeedStorage.kt | 51 ++++++++++++++----- .../screens/error/ErrorStateDestination.kt | 13 +++++ android/src/main/res/values/strings.xml | 2 + 3 files changed, 52 insertions(+), 14 deletions(-) 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 2d2519cff2..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,9 +8,11 @@ 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 @@ -46,8 +48,7 @@ class SeedStorage { /** * @throws UserNotAuthenticatedException */ - fun init(appContext: Context): OperationResult {//todo dmitry see errors - //todo dmitry see errors in io/parity/signer/screens/error/ErrorStateDestination.kt:71 + fun init(appContext: Context): OperationResult { hasStrongbox = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { appContext .packageManager @@ -98,18 +99,8 @@ class SeedStorage { ) } } catch (e: Exception) { - when (e) { - is AEADBadTagException, -// is android.security.KeyStoreException,//todo dmitry minapi requirement - is UnrecoverableKeyException -> { - //todo dmitry - return OperationResult.Err(ErrorDisplayed.Str("todo dmitry")) - } - - else -> throw e - } + return OperationResult.Err(consumeStorageAuthError(e, appContext)) } - return OperationResult.Ok(Unit) } @@ -194,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/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/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 From 24778b5e8def8861f9ccb7d6725eb50adca79352 Mon Sep 17 00:00:00 2001 From: Dmitry Borodin Date: Thu, 18 Jan 2024 13:32:03 +0100 Subject: [PATCH 03/10] passing seed storage errors to the UI and show error --- .../parity/signer/domain/MainFlowViewModel.kt | 9 +-- .../signer/domain/usecases/ResetUseCase.kt | 63 ++++++++++++++----- .../screens/settings/SettingsNavGraph.kt | 22 +++---- .../general/SettingsGeneralNavSubgraph.kt | 29 +++++---- .../general/SettingsGeneralViewModel.kt | 6 +- .../verifiercert/VerifierCertViewModel.kt | 6 +- .../VerifierSettingsScreenDestination.kt | 18 +++--- .../mainnavigation/CoreUnlockedNavSubgraph.kt | 2 +- .../ui/rootnavigation/MainScreensAppFlow.kt | 21 +++++-- 9 files changed, 119 insertions(+), 57 deletions(-) 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/usecases/ResetUseCase.kt b/android/src/main/java/io/parity/signer/domain/usecases/ResetUseCase.kt index d1b0f2ff23..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(onAfterWipe: Callback) { + suspend fun wipeToFactoryWithAuth(onAfterWipe: Callback): OperationResult { val authentication = ServiceLocator.authentication - authentication.authenticate(activity) { - databaseAssetsInteractor.wipe() - totalRefresh() - onAfterWipe() + 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/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..c282ea57f2 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,22 @@ 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( + unlockedNavController + ) + } + }) } } } From 72b84aa8915f4cbeff05afea1a69d4e501f0b8ae Mon Sep 17 00:00:00 2001 From: Dmitry Borodin Date: Thu, 18 Jan 2024 13:50:58 +0100 Subject: [PATCH 04/10] fixed feature toggle, migrated logs to timber --- .../io/parity/signer/domain/NetworkExposedStateKeeper.kt | 8 ++++---- .../parity/signer/ui/rootnavigation/MainScreensAppFlow.kt | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) 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..688920b753 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,7 @@ 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") + Timber.e("TODO DMITRY", "usb broadcast") reactOnUsb(intent) } } @@ -134,7 +134,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 +149,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/ui/rootnavigation/MainScreensAppFlow.kt b/android/src/main/java/io/parity/signer/ui/rootnavigation/MainScreensAppFlow.kt index c282ea57f2..96ee3e4e10 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 @@ -61,6 +61,7 @@ fun NavGraphBuilder.mainSignerAppFlow(globalNavController: NavHostController) { UnlockAppAuthScreen(onUnlockClicked = { mainFlowViewModel.viewModelScope.launch { mainFlowViewModel.onUnlockClicked().handleErrorAppState( + //todo dmitry show error in current graph, unlocked is not shown here! unlockedNavController ) } From 49d9b7053e9db7183d2361794aaf96b2f59a530c Mon Sep 17 00:00:00 2001 From: Dmitry Borodin Date: Sat, 20 Jan 2024 18:39:36 +0100 Subject: [PATCH 05/10] added error state for global nav state --- .../parity/signer/ui/rootnavigation/MainScreensAppFlow.kt | 7 ++----- .../parity/signer/ui/rootnavigation/RootNavigationGraph.kt | 4 ++++ 2 files changed, 6 insertions(+), 5 deletions(-) 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 96ee3e4e10..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 @@ -54,16 +54,13 @@ fun NavGraphBuilder.mainSignerAppFlow(globalNavController: NavHostController) { unlockedNavController.navigate(CoreUnlockedNavSubgraph.airgapBreached) } } - else -> {} } } else { UnlockAppAuthScreen(onUnlockClicked = { mainFlowViewModel.viewModelScope.launch { - mainFlowViewModel.onUnlockClicked().handleErrorAppState( - //todo dmitry show error in current graph, unlocked is not shown here! - unlockedNavController - ) + 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, + ) } } } From 6418c931026cbb79e1e21720a6018125eb1dd2fd Mon Sep 17 00:00:00 2001 From: Dmitry Borodin Date: Sat, 20 Jan 2024 18:42:00 +0100 Subject: [PATCH 06/10] removed outdated log --- .../java/io/parity/signer/domain/NetworkExposedStateKeeper.kt | 1 - 1 file changed, 1 deletion(-) 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 688920b753..0c675776ef 100644 --- a/android/src/main/java/io/parity/signer/domain/NetworkExposedStateKeeper.kt +++ b/android/src/main/java/io/parity/signer/domain/NetworkExposedStateKeeper.kt @@ -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) { - Timber.e("TODO DMITRY", "usb broadcast") reactOnUsb(intent) } } From 18575168d2792e02a3e412a79467dbd3c151c394 Mon Sep 17 00:00:00 2001 From: Dmitry Borodin Date: Sat, 20 Jan 2024 18:57:52 +0100 Subject: [PATCH 07/10] passing errors for add seed as well - work in progress --- .../signer/domain/backend/UniffiInteractor.kt | 8 ++++++++ .../signer/domain/storage/SeedRepository.kt | 17 ++++++++--------- .../domain/usecases/CreateKeySetUseCase.kt | 3 ++- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/android/src/main/java/io/parity/signer/domain/backend/UniffiInteractor.kt b/android/src/main/java/io/parity/signer/domain/backend/UniffiInteractor.kt index 4f96c32685..bf5582ee00 100644 --- a/android/src/main/java/io/parity/signer/domain/backend/UniffiInteractor.kt +++ b/android/src/main/java/io/parity/signer/domain/backend/UniffiInteractor.kt @@ -1,6 +1,7 @@ package io.parity.signer.domain.backend import android.content.Context +import io.parity.signer.domain.AuthResult import io.parity.signer.domain.KeySetDetailsModel import io.parity.signer.domain.KeySetsListModel import io.parity.signer.domain.NetworkModel @@ -354,6 +355,13 @@ sealed class OperationResult { data class Err(val error: E) : OperationResult() } +sealed interface AuthOperationResult{ + data object Success: AuthOperationResult + data class Error(val exception: Exception): AuthOperationResult + data class AuthFailed(val result: AuthResult) : AuthOperationResult + +} + sealed class CompletableResult { data class Ok(val result: T) : CompletableResult() data class Err(val error: E) : CompletableResult() diff --git a/android/src/main/java/io/parity/signer/domain/storage/SeedRepository.kt b/android/src/main/java/io/parity/signer/domain/storage/SeedRepository.kt index bb2a049d16..04e889bf2d 100644 --- a/android/src/main/java/io/parity/signer/domain/storage/SeedRepository.kt +++ b/android/src/main/java/io/parity/signer/domain/storage/SeedRepository.kt @@ -6,6 +6,7 @@ import android.widget.Toast import androidx.fragment.app.FragmentActivity import io.parity.signer.domain.AuthResult import io.parity.signer.domain.Authentication +import io.parity.signer.domain.backend.AuthOperationResult import io.parity.signer.domain.backend.OperationResult import io.parity.signer.domain.backend.UniffiInteractor import io.parity.signer.domain.backend.UniffiResult @@ -121,33 +122,31 @@ class SeedRepository( seedName: String, seedPhrase: String, networksKeys: List - ): Boolean { - //todo return operation result here and show error in UI - // Check if seed name already exists + ): AuthOperationResult { if (isSeedPhraseCollision(seedPhrase)) { - return false + return AuthOperationResult.Error(Exception("Seed Phrase Collision - can't proceed")) } try { addSeedDangerous(seedName, seedPhrase, networksKeys) - return true + return AuthOperationResult.Success } catch (e: UserNotAuthenticatedException) { return when (val authResult = authentication.authenticate(activity)) { AuthResult.AuthSuccess -> { addSeedDangerous(seedName, seedPhrase, networksKeys) - true + AuthOperationResult.Success } AuthResult.AuthError, AuthResult.AuthFailed, AuthResult.AuthUnavailable -> { - Timber.e(TAG, "auth error - $authResult") - false + Timber.w(TAG, "auth error - $authResult") + AuthOperationResult.AuthFailed(authResult) } } } catch (e: java.lang.Exception) { Timber.e(TAG, e.toString()) - return false + return AuthOperationResult.Error(e) } } diff --git a/android/src/main/java/io/parity/signer/domain/usecases/CreateKeySetUseCase.kt b/android/src/main/java/io/parity/signer/domain/usecases/CreateKeySetUseCase.kt index e4733fe649..8a7565f4ef 100644 --- a/android/src/main/java/io/parity/signer/domain/usecases/CreateKeySetUseCase.kt +++ b/android/src/main/java/io/parity/signer/domain/usecases/CreateKeySetUseCase.kt @@ -1,6 +1,7 @@ package io.parity.signer.domain.usecases import io.parity.signer.dependencygraph.ServiceLocator +import io.parity.signer.domain.backend.AuthOperationResult /** * Creates key set @@ -11,7 +12,7 @@ class CreateKeySetUseCase() { seedName: String, seedPhrase: String, networksKeys: List, - ): Boolean { + ): AuthOperationResult { val repository = ServiceLocator.activityScope!!.seedRepository return repository.addSeed( seedName = seedName, From baa5ec036943c77cf4dc651a0cade2c084c3c6f1 Mon Sep 17 00:00:00 2001 From: Dmitry Borodin Date: Sat, 20 Jan 2024 19:13:19 +0100 Subject: [PATCH 08/10] passing errors while adding seed work in progress --- .../screens/error/ErrorStateScreenGeneral.kt | 2 +- .../NewKeySetNetworksViewModel.kt | 3 +- .../NewKeysetSelectNetworkScreen.kt | 7 ++-- ...eysetSelectNetworkRestoreFlowFullScreen.kt | 34 +++++++++++++------ .../scan/bananasplit/BananaSplitViewModel.kt | 7 ++-- 5 files changed, 37 insertions(+), 16 deletions(-) diff --git a/android/src/main/java/io/parity/signer/screens/error/ErrorStateScreenGeneral.kt b/android/src/main/java/io/parity/signer/screens/error/ErrorStateScreenGeneral.kt index 6074a315c8..951b1ea890 100644 --- a/android/src/main/java/io/parity/signer/screens/error/ErrorStateScreenGeneral.kt +++ b/android/src/main/java/io/parity/signer/screens/error/ErrorStateScreenGeneral.kt @@ -74,7 +74,7 @@ internal fun ErrorStateScreen( ) } } -// PrimaryButtonWide( +// PrimaryButtonWide( todo dmitry remove? // label = stringResource(R.string.button_next), // modifier = Modifier.padding(24.dp), // onClicked = {}, diff --git a/android/src/main/java/io/parity/signer/screens/keysets/create/backupstepscreens/NewKeySetNetworksViewModel.kt b/android/src/main/java/io/parity/signer/screens/keysets/create/backupstepscreens/NewKeySetNetworksViewModel.kt index 763d482b0c..216c067647 100644 --- a/android/src/main/java/io/parity/signer/screens/keysets/create/backupstepscreens/NewKeySetNetworksViewModel.kt +++ b/android/src/main/java/io/parity/signer/screens/keysets/create/backupstepscreens/NewKeySetNetworksViewModel.kt @@ -5,6 +5,7 @@ import androidx.lifecycle.viewModelScope import io.parity.signer.dependencygraph.ServiceLocator import io.parity.signer.domain.Callback import io.parity.signer.domain.NetworkModel +import io.parity.signer.domain.backend.AuthOperationResult import io.parity.signer.domain.usecases.AllNetworksUseCase import io.parity.signer.domain.usecases.CreateKeySetUseCase import kotlinx.coroutines.launch @@ -23,7 +24,7 @@ class NewKeySetNetworksViewModel : ViewModel() { fun createKeySetWithNetworks( seedName: String, seedPhrase: String, networkForKeys: Set, - onAfterCreate: (Boolean) -> Unit = {}, + onAfterCreate: (AuthOperationResult) -> Unit = {}, ): Unit { viewModelScope.launch { val success = createKeySetUseCase.createKeySetWithNetworks( diff --git a/android/src/main/java/io/parity/signer/screens/keysets/create/backupstepscreens/NewKeysetSelectNetworkScreen.kt b/android/src/main/java/io/parity/signer/screens/keysets/create/backupstepscreens/NewKeysetSelectNetworkScreen.kt index f65139401c..7b580be88f 100644 --- a/android/src/main/java/io/parity/signer/screens/keysets/create/backupstepscreens/NewKeysetSelectNetworkScreen.kt +++ b/android/src/main/java/io/parity/signer/screens/keysets/create/backupstepscreens/NewKeysetSelectNetworkScreen.kt @@ -41,6 +41,7 @@ import io.parity.signer.components.base.SignerDivider import io.parity.signer.components.items.NetworkItemMultiselect import io.parity.signer.domain.Callback import io.parity.signer.domain.NetworkModel +import io.parity.signer.domain.backend.AuthOperationResult import io.parity.signer.ui.BottomSheetWrapperContent import io.parity.signer.ui.theme.SignerNewTheme import io.parity.signer.ui.theme.SignerTypeface @@ -82,14 +83,16 @@ fun NewKeySetSelectNetworkScreen( seedName = seedName, seedPhrase = seedPhrase, networkForKeys = selected.value.mapNotNull { selected -> networks.find { it.key == selected } } .toSet(), - onAfterCreate = { isSuccess -> - if (isSuccess) { + onAfterCreate = { success -> + if (success == AuthOperationResult.Success) { Toast.makeText( context, context.getString(R.string.key_set_has_been_created_toast, seedName), Toast.LENGTH_LONG ).show() onSuccess() + } else { + //todo dmitry handle error } } ) diff --git a/android/src/main/java/io/parity/signer/screens/keysets/restore/restorephrase/RecoverKeysetSelectNetworkRestoreFlowFullScreen.kt b/android/src/main/java/io/parity/signer/screens/keysets/restore/restorephrase/RecoverKeysetSelectNetworkRestoreFlowFullScreen.kt index 9ae811bb81..578f7f5626 100644 --- a/android/src/main/java/io/parity/signer/screens/keysets/restore/restorephrase/RecoverKeysetSelectNetworkRestoreFlowFullScreen.kt +++ b/android/src/main/java/io/parity/signer/screens/keysets/restore/restorephrase/RecoverKeysetSelectNetworkRestoreFlowFullScreen.kt @@ -9,6 +9,8 @@ import androidx.compose.ui.platform.LocalContext import androidx.lifecycle.viewmodel.compose.viewModel import io.parity.signer.R import io.parity.signer.domain.Callback +import io.parity.signer.domain.backend.AuthOperationResult +import io.parity.signer.screens.error.ErrorStateDestinationState import io.parity.signer.screens.keysets.create.backupstepscreens.NewKeySetNetworksViewModel import io.parity.signer.screens.keysets.restore.recoverkeysetnetworks.RecoverKeysetSelectNetworkScreenBase @@ -17,6 +19,7 @@ import io.parity.signer.screens.keysets.restore.recoverkeysetnetworks.RecoverKey fun RecoverKeysetSelectNetworkRestoreFlowFullScreen( seedName: String, seedPhrase: String, + showError: (ErrorStateDestinationState) -> Unit, //todo dmitry check we can go back here onBack: Callback, navigateOnSuccess: Callback, ) { @@ -37,17 +40,28 @@ fun RecoverKeysetSelectNetworkRestoreFlowFullScreen( val onProceedAction = { networksViewModel.createKeySetWithNetworks( seedName = seedName, seedPhrase = seedPhrase, - networkForKeys = selected.value.mapNotNull { selected -> networks.find { it.key == selected } } - .toSet(), - onAfterCreate = { isSuccess -> - if (isSuccess) { - Toast.makeText( - context, - context.getString(R.string.key_set_has_been_recovered_toast, seedName), - Toast.LENGTH_LONG - ).show() + networkForKeys = selected.value.mapNotNull { selected -> + networks.find { it.key == selected } }.toSet(), + onAfterCreate = { success -> + when (success) { + is AuthOperationResult.AuthFailed, + is AuthOperationResult.Error -> { + //todo dmitry make ErrorStateDestinationState from AuthOperationResult + showError() + } + AuthOperationResult.Success -> { + Toast.makeText( + context, + context.getString( + R.string.key_set_has_been_recovered_toast, + seedName + ), + Toast.LENGTH_LONG + ).show() + navigateOnSuccess() + } } - navigateOnSuccess() + } ) } diff --git a/android/src/main/java/io/parity/signer/screens/scan/bananasplit/BananaSplitViewModel.kt b/android/src/main/java/io/parity/signer/screens/scan/bananasplit/BananaSplitViewModel.kt index 9bcccb9a77..505b522366 100644 --- a/android/src/main/java/io/parity/signer/screens/scan/bananasplit/BananaSplitViewModel.kt +++ b/android/src/main/java/io/parity/signer/screens/scan/bananasplit/BananaSplitViewModel.kt @@ -5,6 +5,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import io.parity.signer.R import io.parity.signer.dependencygraph.ServiceLocator +import io.parity.signer.domain.backend.AuthOperationResult import io.parity.signer.domain.mapState import io.parity.signer.domain.storage.SeedRepository import io.parity.signer.domain.submitErrorState @@ -89,14 +90,16 @@ class BananaSplitViewModel() : ViewModel() { ) { val seedName = seedName.value val seedPhrase = _seedPhrase.value!! - val isSaved = createKeySetUseCase.createKeySetWithNetworks( + val success = createKeySetUseCase.createKeySetWithNetworks( seedName, seedPhrase, networksKeys.toList(), ) - if (!isSaved) { + if (success == AuthOperationResult.Success) { _isCustomErrorTerminal.value = context.getString(R.string.banana_split_password_error_cannot_save_seed) return + } else { + //todo dmitry show error } _isSuccessTerminal.value = seedName } From 77c145b2c195ad4addf388b2b3deefa68211cc9a Mon Sep 17 00:00:00 2001 From: Dmitry Borodin Date: Sat, 20 Jan 2024 21:59:24 +0100 Subject: [PATCH 09/10] error state handling process 2 --- .../screens/error/ErrorStateDestination.kt | 64 -------------- .../error/ErrorStateMessageCreation.kt | 83 +++++++++++++++++++ .../screens/error/ErrorStateScreenGeneral.kt | 5 -- .../create/NewKeysetBackupStepSubgraph.kt | 4 + .../NewKeysetSelectNetworkScreen.kt | 27 ++++-- .../keysets/restore/KeysetRecoverSubgraph.kt | 5 ++ ...eysetSelectNetworkRestoreFlowFullScreen.kt | 6 +- 7 files changed, 112 insertions(+), 82 deletions(-) create mode 100644 android/src/main/java/io/parity/signer/screens/error/ErrorStateMessageCreation.kt 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 14ad0e125d..639d2aa120 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 @@ -8,15 +8,11 @@ import androidx.navigation.NavGraphBuilder import androidx.navigation.NavType import androidx.navigation.compose.composable import androidx.navigation.navArgument -import io.parity.signer.domain.NavigationError -import io.parity.signer.domain.backend.OperationResult import io.parity.signer.domain.backend.UniffiResult import io.parity.signer.domain.backend.toOperationResult -import io.parity.signer.domain.getDebugDetailedDescriptionString import io.parity.signer.screens.error.wrongversion.errorWrongVersionSubgraph import io.parity.signer.screens.initial.eachstartchecks.airgap.AirgapScreen import io.parity.signer.ui.mainnavigation.CoreUnlockedNavSubgraph -import io.parity.signer.uniffi.ErrorDisplayed fun NavGraphBuilder.errorStateDestination( @@ -67,63 +63,3 @@ 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 -): T? { - return when (this) { - 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}", - argDescription = error.message, - argVerbose = "", - ) - } - - is ErrorDisplayed -> - when (error) { - is ErrorDisplayed.DbSchemaMismatch -> { - CoreUnlockedNavSubgraph.errorWrongDbVersionUpdate - } - - else -> { - CoreUnlockedNavSubgraph.ErrorScreenGeneral.destination( - argHeader = "Operation error to get ${T::class.java}", - argDescription = error.toString(), - argVerbose = error.getDebugDetailedDescriptionString(), - ) - } - } - - else -> { - CoreUnlockedNavSubgraph.ErrorScreenGeneral.destination( - argHeader = "Operation unknown error trying to get ${T::class.java}", - argDescription = "", - argVerbose = error.toString(), - ) - } - } - ) - null - } - - is OperationResult.Ok -> { - result - } - } -} diff --git a/android/src/main/java/io/parity/signer/screens/error/ErrorStateMessageCreation.kt b/android/src/main/java/io/parity/signer/screens/error/ErrorStateMessageCreation.kt new file mode 100644 index 0000000000..2debdbca4c --- /dev/null +++ b/android/src/main/java/io/parity/signer/screens/error/ErrorStateMessageCreation.kt @@ -0,0 +1,83 @@ +package io.parity.signer.screens.error + +import androidx.navigation.NavController +import io.parity.signer.domain.NavigationError +import io.parity.signer.domain.backend.AuthOperationResult +import io.parity.signer.domain.backend.OperationResult +import io.parity.signer.domain.getDebugDetailedDescriptionString +import io.parity.signer.ui.mainnavigation.CoreUnlockedNavSubgraph +import io.parity.signer.uniffi.ErrorDisplayed + + +data class ErrorStateDestinationState( + val argHeader: String, + val argDescription: String, + val argVerbose: String, +) + +inline fun OperationResult.handleErrorAppState( + coreNavController: NavController +): T? { + return when (this) { + 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}", + argDescription = error.message, + argVerbose = "", + ) + } + + is ErrorDisplayed -> + when (error) { + is ErrorDisplayed.DbSchemaMismatch -> { + CoreUnlockedNavSubgraph.errorWrongDbVersionUpdate + } + + else -> { + CoreUnlockedNavSubgraph.ErrorScreenGeneral.destination( + argHeader = "Operation error to get ${T::class.java}", + argDescription = error.toString(), + argVerbose = error.getDebugDetailedDescriptionString(), + ) + } + } + + else -> { + CoreUnlockedNavSubgraph.ErrorScreenGeneral.destination( + argHeader = "Operation unknown error trying to get ${T::class.java}", + argDescription = "", + argVerbose = error.toString(), + ) + } + } + ) + null + } + + is OperationResult.Ok -> { + result + } + } +} + + +//todo dmitry nope, need to create text at some level +inline fun AuthOperationResult.handleErrorAppState( + coreNavController: NavController +): Unit? { + return when (this) { + is AuthOperationResult.AuthFailed -> TODO() + is AuthOperationResult.Error -> TODO() + AuthOperationResult.Success -> TODO() + } +} diff --git a/android/src/main/java/io/parity/signer/screens/error/ErrorStateScreenGeneral.kt b/android/src/main/java/io/parity/signer/screens/error/ErrorStateScreenGeneral.kt index 951b1ea890..9e1c1e8fd1 100644 --- a/android/src/main/java/io/parity/signer/screens/error/ErrorStateScreenGeneral.kt +++ b/android/src/main/java/io/parity/signer/screens/error/ErrorStateScreenGeneral.kt @@ -74,11 +74,6 @@ internal fun ErrorStateScreen( ) } } -// PrimaryButtonWide( todo dmitry remove? -// label = stringResource(R.string.button_next), -// modifier = Modifier.padding(24.dp), -// onClicked = {}, -// ) } } diff --git a/android/src/main/java/io/parity/signer/screens/keysets/create/NewKeysetBackupStepSubgraph.kt b/android/src/main/java/io/parity/signer/screens/keysets/create/NewKeysetBackupStepSubgraph.kt index 1fa0fa9452..aab74ac8cd 100644 --- a/android/src/main/java/io/parity/signer/screens/keysets/create/NewKeysetBackupStepSubgraph.kt +++ b/android/src/main/java/io/parity/signer/screens/keysets/create/NewKeysetBackupStepSubgraph.kt @@ -17,6 +17,7 @@ import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController +import io.parity.signer.domain.backend.AuthOperationResult import io.parity.signer.domain.backend.toOperationResult import io.parity.signer.domain.popUpToTop import io.parity.signer.screens.error.handleErrorAppState @@ -108,6 +109,9 @@ fun NewKeysetSubgraph( popUpToTop(coreNavController) } }, + showError = { error: AuthOperationResult -> + error.handleErrorAppState(coreNavController) + }, onBack = subgraphNavController::popBackStack, ) } diff --git a/android/src/main/java/io/parity/signer/screens/keysets/create/backupstepscreens/NewKeysetSelectNetworkScreen.kt b/android/src/main/java/io/parity/signer/screens/keysets/create/backupstepscreens/NewKeysetSelectNetworkScreen.kt index 7b580be88f..5789179a66 100644 --- a/android/src/main/java/io/parity/signer/screens/keysets/create/backupstepscreens/NewKeysetSelectNetworkScreen.kt +++ b/android/src/main/java/io/parity/signer/screens/keysets/create/backupstepscreens/NewKeysetSelectNetworkScreen.kt @@ -55,6 +55,7 @@ fun NewKeySetSelectNetworkScreen( seedName: String, seedPhrase: String, onBack: Callback, + showError: (AuthOperationResult) -> Unit, onSuccess: Callback, ) { val networksViewModel: NewKeySetNetworksViewModel = viewModel() @@ -84,15 +85,23 @@ fun NewKeySetSelectNetworkScreen( networkForKeys = selected.value.mapNotNull { selected -> networks.find { it.key == selected } } .toSet(), onAfterCreate = { success -> - if (success == AuthOperationResult.Success) { - Toast.makeText( - context, - context.getString(R.string.key_set_has_been_created_toast, seedName), - Toast.LENGTH_LONG - ).show() - onSuccess() - } else { - //todo dmitry handle error + when (success) { + is AuthOperationResult.AuthFailed, + is AuthOperationResult.Error -> { + showError(success) + } + + AuthOperationResult.Success -> { + Toast.makeText( + context, + context.getString( + R.string.key_set_has_been_created_toast, + seedName + ), + Toast.LENGTH_LONG + ).show() + onSuccess() + } } } ) diff --git a/android/src/main/java/io/parity/signer/screens/keysets/restore/KeysetRecoverSubgraph.kt b/android/src/main/java/io/parity/signer/screens/keysets/restore/KeysetRecoverSubgraph.kt index d4d78a01e0..8620bdcc0e 100644 --- a/android/src/main/java/io/parity/signer/screens/keysets/restore/KeysetRecoverSubgraph.kt +++ b/android/src/main/java/io/parity/signer/screens/keysets/restore/KeysetRecoverSubgraph.kt @@ -20,8 +20,10 @@ import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import androidx.navigation.navArgument +import io.parity.signer.domain.backend.AuthOperationResult import io.parity.signer.domain.popUpToTop import io.parity.signer.domain.submitErrorState +import io.parity.signer.screens.error.handleErrorAppState import io.parity.signer.screens.keysets.restore.keysetname.KeysetRecoverNameScreen import io.parity.signer.screens.keysets.restore.restorephrase.KeysetRecoverPhraseScreen import io.parity.signer.screens.keysets.restore.restorephrase.RecoverKeysetSelectNetworkRestoreFlowFullScreen @@ -100,6 +102,9 @@ fun KeysetRecoverSubgraph( seedName = keysetName, seedPhrase = seedPhrase, onBack = localNavController::popBackStack, + showError = { error: AuthOperationResult -> + error.handleErrorAppState(coreNavController) + }, navigateOnSuccess = { coreNavController.navigate( CoreUnlockedNavSubgraph.KeySet.destination( diff --git a/android/src/main/java/io/parity/signer/screens/keysets/restore/restorephrase/RecoverKeysetSelectNetworkRestoreFlowFullScreen.kt b/android/src/main/java/io/parity/signer/screens/keysets/restore/restorephrase/RecoverKeysetSelectNetworkRestoreFlowFullScreen.kt index 578f7f5626..b0f5f49cce 100644 --- a/android/src/main/java/io/parity/signer/screens/keysets/restore/restorephrase/RecoverKeysetSelectNetworkRestoreFlowFullScreen.kt +++ b/android/src/main/java/io/parity/signer/screens/keysets/restore/restorephrase/RecoverKeysetSelectNetworkRestoreFlowFullScreen.kt @@ -19,7 +19,7 @@ import io.parity.signer.screens.keysets.restore.recoverkeysetnetworks.RecoverKey fun RecoverKeysetSelectNetworkRestoreFlowFullScreen( seedName: String, seedPhrase: String, - showError: (ErrorStateDestinationState) -> Unit, //todo dmitry check we can go back here + showError: (AuthOperationResult) -> Unit, onBack: Callback, navigateOnSuccess: Callback, ) { @@ -46,8 +46,7 @@ fun RecoverKeysetSelectNetworkRestoreFlowFullScreen( when (success) { is AuthOperationResult.AuthFailed, is AuthOperationResult.Error -> { - //todo dmitry make ErrorStateDestinationState from AuthOperationResult - showError() + showError(success) } AuthOperationResult.Success -> { Toast.makeText( @@ -61,7 +60,6 @@ fun RecoverKeysetSelectNetworkRestoreFlowFullScreen( navigateOnSuccess() } } - } ) } From f0394606109b0509a9176cbcf60153882a8b5c8d Mon Sep 17 00:00:00 2001 From: Dmitry Borodin Date: Wed, 24 Jan 2024 14:56:10 +0100 Subject: [PATCH 10/10] implemented showing error as end of add seed operation --- .../screens/error/ErrorStateDestination.kt | 5 --- .../error/ErrorStateMessageCreation.kt | 33 +++++++++++++++---- .../KeySetDetailsScreenSubgraph.kt | 5 ++- .../create/NewKeysetBackupStepSubgraph.kt | 4 ++- .../keysets/restore/KeysetRecoverSubgraph.kt | 4 ++- .../scan/bananasplit/BananaSplitViewModel.kt | 4 +-- 6 files changed, 36 insertions(+), 19 deletions(-) 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 0add96ea7c..639d2aa120 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 @@ -63,8 +63,3 @@ inline fun UniffiResult.handleErrorAppState(coreNavController: Na return this.toOperationResult().handleErrorAppState(coreNavController) } -data class ErrorStateDestinationState( - val argHeader: String, - val argDescription: String, - val argVerbose: String, -) diff --git a/android/src/main/java/io/parity/signer/screens/error/ErrorStateMessageCreation.kt b/android/src/main/java/io/parity/signer/screens/error/ErrorStateMessageCreation.kt index 2debdbca4c..07a49a3589 100644 --- a/android/src/main/java/io/parity/signer/screens/error/ErrorStateMessageCreation.kt +++ b/android/src/main/java/io/parity/signer/screens/error/ErrorStateMessageCreation.kt @@ -1,6 +1,9 @@ package io.parity.signer.screens.error +import android.content.Context +import android.widget.Toast import androidx.navigation.NavController +import io.parity.signer.R import io.parity.signer.domain.NavigationError import io.parity.signer.domain.backend.AuthOperationResult import io.parity.signer.domain.backend.OperationResult @@ -29,6 +32,7 @@ inline fun OperationResult.handleErrorAppState( argVerbose = error.argVerbose, ) } + is NavigationError -> { CoreUnlockedNavSubgraph.ErrorScreenGeneral.destination( argHeader = "Operation navigation error trying to get ${T::class.java}", @@ -71,13 +75,30 @@ inline fun OperationResult.handleErrorAppState( } -//todo dmitry nope, need to create text at some level -inline fun AuthOperationResult.handleErrorAppState( - coreNavController: NavController +fun AuthOperationResult.handleErrorAppState( + coreNavController: NavController, + context: Context, ): Unit? { return when (this) { - is AuthOperationResult.AuthFailed -> TODO() - is AuthOperationResult.Error -> TODO() - AuthOperationResult.Success -> TODO() + is AuthOperationResult.AuthFailed -> { + Toast.makeText(context, R.string.auth_failed_message, Toast.LENGTH_SHORT) + .show() + null + } + + is AuthOperationResult.Error -> { + coreNavController.navigate( + CoreUnlockedNavSubgraph.ErrorScreenGeneral.destination( + argHeader = "Operation error", + argDescription = exception.toString(), + argVerbose = exception.stackTraceToString(), + ) + ) + null + } + + AuthOperationResult.Success -> { + Unit + } } } diff --git a/android/src/main/java/io/parity/signer/screens/keysetdetails/KeySetDetailsScreenSubgraph.kt b/android/src/main/java/io/parity/signer/screens/keysetdetails/KeySetDetailsScreenSubgraph.kt index 5729bce070..6f380f1789 100644 --- a/android/src/main/java/io/parity/signer/screens/keysetdetails/KeySetDetailsScreenSubgraph.kt +++ b/android/src/main/java/io/parity/signer/screens/keysetdetails/KeySetDetailsScreenSubgraph.kt @@ -6,12 +6,11 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import androidx.compose.runtime.getValue -import androidx.compose.runtime.setValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel diff --git a/android/src/main/java/io/parity/signer/screens/keysets/create/NewKeysetBackupStepSubgraph.kt b/android/src/main/java/io/parity/signer/screens/keysets/create/NewKeysetBackupStepSubgraph.kt index aab74ac8cd..b4f6271bad 100644 --- a/android/src/main/java/io/parity/signer/screens/keysets/create/NewKeysetBackupStepSubgraph.kt +++ b/android/src/main/java/io/parity/signer/screens/keysets/create/NewKeysetBackupStepSubgraph.kt @@ -12,6 +12,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost @@ -97,6 +98,7 @@ fun NewKeysetSubgraph( } } composable(NewKeySetBackupStepSubgraph.NewKeySetSelectNetworks) { + val context = LocalContext.current NewKeySetSelectNetworkScreen( seedName = seedName, seedPhrase = seedPhrase, @@ -110,7 +112,7 @@ fun NewKeysetSubgraph( } }, showError = { error: AuthOperationResult -> - error.handleErrorAppState(coreNavController) + error.handleErrorAppState(coreNavController, context) }, onBack = subgraphNavController::popBackStack, ) diff --git a/android/src/main/java/io/parity/signer/screens/keysets/restore/KeysetRecoverSubgraph.kt b/android/src/main/java/io/parity/signer/screens/keysets/restore/KeysetRecoverSubgraph.kt index 8620bdcc0e..78822f3355 100644 --- a/android/src/main/java/io/parity/signer/screens/keysets/restore/KeysetRecoverSubgraph.kt +++ b/android/src/main/java/io/parity/signer/screens/keysets/restore/KeysetRecoverSubgraph.kt @@ -12,6 +12,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavController @@ -97,13 +98,14 @@ fun KeysetRecoverSubgraph( ) { val seedPhrase = it.arguments?.getString(KeysetRecoverSubgraph.NetworksSelection.seedPhrase)!! + val context = LocalContext.current RecoverKeysetSelectNetworkRestoreFlowFullScreen( seedName = keysetName, seedPhrase = seedPhrase, onBack = localNavController::popBackStack, showError = { error: AuthOperationResult -> - error.handleErrorAppState(coreNavController) + error.handleErrorAppState(coreNavController, context) }, navigateOnSuccess = { coreNavController.navigate( diff --git a/android/src/main/java/io/parity/signer/screens/scan/bananasplit/BananaSplitViewModel.kt b/android/src/main/java/io/parity/signer/screens/scan/bananasplit/BananaSplitViewModel.kt index 505b522366..61dfc8ea40 100644 --- a/android/src/main/java/io/parity/signer/screens/scan/bananasplit/BananaSplitViewModel.kt +++ b/android/src/main/java/io/parity/signer/screens/scan/bananasplit/BananaSplitViewModel.kt @@ -94,12 +94,10 @@ class BananaSplitViewModel() : ViewModel() { seedName, seedPhrase, networksKeys.toList(), ) - if (success == AuthOperationResult.Success) { + if (success != AuthOperationResult.Success) { _isCustomErrorTerminal.value = context.getString(R.string.banana_split_password_error_cannot_save_seed) return - } else { - //todo dmitry show error } _isSuccessTerminal.value = seedName }