From d470f278fc30e6dce30460766d396f4c7599f6fd Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 24 Oct 2023 13:04:14 +0300 Subject: [PATCH] Updated on 2026-08-14 --- .../BiometricUserWalletsListManager.kt | 55 +++++++++---------- .../wallets/legacy/UserWalletsListError.kt | 20 ++++--- .../wallets/legacy/UserWalletsListManager.kt | 12 ++-- .../wallets/models/UnlockWalletError.kt | 6 -- .../wallets/models/UnlockWalletsError.kt | 12 ++++ .../wallets/usecase/UnlockWalletsUseCase.kt | 38 ++++++++----- .../wallet/state/WalletAlertState.kt | 6 ++ .../wallet/viewmodels/WalletViewModel.kt | 32 +++++++---- 8 files changed, 108 insertions(+), 73 deletions(-) delete mode 100644 domain/wallets/src/main/java/com/tangem/domain/wallets/models/UnlockWalletError.kt create mode 100644 domain/wallets/src/main/java/com/tangem/domain/wallets/models/UnlockWalletsError.kt diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt index 823acaaa5d..9bc719665c 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt @@ -1,7 +1,6 @@ package com.tangem.tap.domain.userWalletList.implementation import com.tangem.common.* -import com.tangem.common.extensions.guard import com.tangem.domain.wallets.legacy.UserWalletsListError import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.models.UserWallet @@ -57,12 +56,29 @@ internal class BiometricUserWalletsListManager( override val walletsCount: Int get() = state.value.userWallets.size - override suspend fun unlock(): CompletionResult { - return unlockWithBiometryInternal().mapUnlockResult() - } + override suspend fun unlock(throwIfNotAllWalletsUnlocked: Boolean): CompletionResult { + return unlockWithBiometryInternal() + .mapFailure { error -> + if (error is UserWalletsListError) { + error + } else { + UserWalletsListError.UnableToUnlockUserWallets(error) + } + } + .map { + if (throwIfNotAllWalletsUnlocked && state.value.userWallets.any(UserWallet::isLocked)) { + Timber.e("Not all user wallets have been unlocked") + throw UserWalletsListError.NotAllUserWalletsUnlocked + } - override suspend fun unlockAndSelect(selectedWalletId: UserWalletId): CompletionResult { - return unlockWithBiometryInternal(selectedWalletId = selectedWalletId).mapUnlockResult() + val selectedUserWallet = selectedUserWalletSync + if (selectedUserWallet == null || selectedUserWallet.isLocked) { + Timber.e("Unable to find selected user wallet") + throw UserWalletsListError.NoUserWalletSelected + } else { + selectedUserWallet + } + } } override fun lock() { @@ -189,7 +205,7 @@ internal class BiometricUserWalletsListManager( } } - private suspend fun unlockWithBiometryInternal(selectedWalletId: UserWalletId? = null): CompletionResult { + private suspend fun unlockWithBiometryInternal(): CompletionResult { return keysRepository.getAll() .map { keys -> state.update { prevState -> @@ -198,7 +214,7 @@ internal class BiometricUserWalletsListManager( ) } } - .flatMap { loadModels(selectedWalletId = selectedWalletId) } + .flatMap { loadModels() } .map { state.update { prevState -> val hasLockedUserWallets = prevState.userWallets.any { it.isLocked } @@ -207,23 +223,6 @@ internal class BiometricUserWalletsListManager( } } - private fun CompletionResult.mapUnlockResult(): CompletionResult { - return this - .mapFailure { error -> - if (error is UserWalletsListError) { - error - } else { - UserWalletsListError.UnableToUnlockUserWallets(cause = error) - } - } - .map { - selectedUserWalletSync.guard { - Timber.e("Unable to find selected user wallet") - throw UserWalletsListError.NoUserWalletSelected - } - } - } - private suspend fun saveEncryptionKeyIfNotNull(userWallet: UserWallet): CompletionResult { val encryptionKey = userWallet.scanResponse.card.encryptionKey ?.let { UserWalletEncryptionKey(userWallet.walletId, it) } @@ -245,7 +244,7 @@ internal class BiometricUserWalletsListManager( } } - private suspend fun loadModels(selectedWalletId: UserWalletId? = null): CompletionResult { + private suspend fun loadModels(): CompletionResult { return getSavedUserWallets() .map { userWallets -> if (userWallets.isNotEmpty()) { @@ -255,7 +254,7 @@ internal class BiometricUserWalletsListManager( prevState.copy( userWallets = wallets, selectedUserWalletId = findOrSetSelectedUserWalletId( - prevSelectedWalletId = selectedWalletId ?: prevState.selectedUserWalletId, + prevSelectedWalletId = prevState.selectedUserWalletId, userWallets = wallets, ), ) @@ -316,7 +315,7 @@ internal class BiometricUserWalletsListManager( private fun findSelectedUserWallet(userWallets: List = state.value.userWallets): UserWallet? { return userWallets.firstOrNull { - it.walletId == state.value.selectedUserWalletId && !it.isLocked + it.walletId == state.value.selectedUserWalletId } } diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListError.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListError.kt index 33f066c9ba..f287da375c 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListError.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListError.kt @@ -19,14 +19,6 @@ sealed class UserWalletsListError(code: Int) : TangemError(code) { override var customMessage: String = "Encryption key invalidated" } - object BiometricsAuthenticationDisabled : UserWalletsListError(code = 60005) { - override var customMessage: String = "Biometrics authentication disabled" - } - - object NoUserWalletSelected : UserWalletsListError(code = 60006) { - override var customMessage: String = "No user wallet selected" - } - data class BiometricsAuthenticationLockout(val isPermanent: Boolean) : UserWalletsListError(code = 60003) { override var customMessage: String = "Biometric authentication lockout, permanent: $isPermanent" } @@ -35,4 +27,16 @@ sealed class UserWalletsListError(code: Int) : TangemError(code) { override var customMessage: String = "An error has occurred, please scan your card to log in" override val messageResId: Int = R.string.user_wallet_list_error_unable_to_unlock } + + object BiometricsAuthenticationDisabled : UserWalletsListError(code = 60005) { + override var customMessage: String = "Biometrics authentication disabled" + } + + object NoUserWalletSelected : UserWalletsListError(code = 60006) { + override var customMessage: String = "No user wallet selected" + } + + object NotAllUserWalletsUnlocked : UserWalletsListError(code = 60007) { + override var customMessage: String = "Not all user wallets was unlocked" + } } \ No newline at end of file diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManager.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManager.kt index db6a353f7b..7211f73a66 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManager.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManager.kt @@ -105,17 +105,13 @@ interface UserWalletsListManager { /** * Receive saved [UserWallet]s, populate [userWallets] flow with it and set [isLocked] as false. * + * @param throwIfNotAllWalletsUnlocked Indicates that the function must throw + * [UserWalletsListError.NotAllUserWalletsUnlocked] if not all user wallets are unlocked. + * * @return [CompletionResult] of operation, with selected [UserWallet] * or null if there is no selected [UserWallet] */ - suspend fun unlock(): CompletionResult - - /** - * Unlock all [UserWallet]s and select passed [UserWalletId] - * - * @param selectedWalletId [UserWalletId] of [UserWallet] which must be selected - */ - suspend fun unlockAndSelect(selectedWalletId: UserWalletId): CompletionResult + suspend fun unlock(throwIfNotAllWalletsUnlocked: Boolean = false): CompletionResult /** Remove [UserWallet]s from [userWallets] and set [isLocked] as true */ fun lock() diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/models/UnlockWalletError.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/models/UnlockWalletError.kt deleted file mode 100644 index 311cdc5d0f..0000000000 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/models/UnlockWalletError.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.tangem.domain.wallets.models - -sealed interface UnlockWalletError { - - object CommonError : UnlockWalletError -} \ No newline at end of file diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/models/UnlockWalletsError.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/models/UnlockWalletsError.kt new file mode 100644 index 0000000000..a6558fb0dd --- /dev/null +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/models/UnlockWalletsError.kt @@ -0,0 +1,12 @@ +package com.tangem.domain.wallets.models + +sealed class UnlockWalletsError { + + object UnableToUnlockWallets : UnlockWalletsError() + + object NoUserWalletSelected : UnlockWalletsError() + + object NotAllUserWalletsUnlocked : UnlockWalletsError() + + data class DataError(val cause: Throwable) : UnlockWalletsError() +} \ No newline at end of file diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/UnlockWalletsUseCase.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/UnlockWalletsUseCase.kt index 2a713a0ac8..b73f7d1e43 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/UnlockWalletsUseCase.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/UnlockWalletsUseCase.kt @@ -1,14 +1,13 @@ package com.tangem.domain.wallets.usecase import arrow.core.Either -import arrow.core.left -import arrow.core.right +import arrow.core.raise.either +import arrow.core.raise.ensureNotNull import com.tangem.common.doOnFailure -import com.tangem.common.doOnSuccess +import com.tangem.domain.wallets.legacy.UserWalletsListError import com.tangem.domain.wallets.legacy.WalletsStateHolder import com.tangem.domain.wallets.legacy.asLockable -import com.tangem.domain.wallets.models.UnlockWalletError -import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.domain.wallets.models.UnlockWalletsError /** * Unlock wallets use case @@ -19,14 +18,27 @@ import com.tangem.domain.wallets.models.UserWalletId */ class UnlockWalletsUseCase(private val walletsStateHolder: WalletsStateHolder) { - suspend operator fun invoke(selectedWalletId: UserWalletId): Either { - val userWalletsListManager = walletsStateHolder.userWalletsListManager?.asLockable() - ?: return UnlockWalletError.CommonError.left() + suspend operator fun invoke(throwIfNotAllWalletsUnlocked: Boolean = false): Either = + either { + val userWalletsListManager = ensureNotNull( + value = walletsStateHolder.userWalletsListManager?.asLockable(), + raise = { + UnlockWalletsError.DataError( + cause = IllegalStateException("The lockable user wallets list manager could not be found") + ) + }, + ) - userWalletsListManager.unlockAndSelect(selectedWalletId = selectedWalletId) - .doOnSuccess { return Unit.right() } - .doOnFailure { return UnlockWalletError.CommonError.left() } + userWalletsListManager.unlock(throwIfNotAllWalletsUnlocked) + .doOnFailure { error -> + val e = when (error) { + is UserWalletsListError.NoUserWalletSelected -> UnlockWalletsError.NoUserWalletSelected + is UserWalletsListError.NotAllUserWalletsUnlocked -> + UnlockWalletsError.NotAllUserWalletsUnlocked + else -> UnlockWalletsError.UnableToUnlockWallets + } - return Unit.right() - } + raise(e) + } + } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletAlertState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletAlertState.kt index 57bfcb20c6..5b2a1cb935 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletAlertState.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletAlertState.kt @@ -32,4 +32,10 @@ internal sealed class WalletAlertState { override val message: TextReference = resourceReference(R.string.error_wrong_wallet_tapped) override val onConfirmClick: (() -> Unit)? = null } + + object RescanWallets : WalletAlertState() { + override val title: TextReference = resourceReference(R.string.common_attention) + override val message: TextReference = resourceReference(R.string.key_invalidated_warning_description) + override val onConfirmClick: (() -> Unit)? = null + } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt index 52ba8578cd..85b3ccd83e 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt @@ -57,6 +57,7 @@ import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsUseCase import com.tangem.domain.userwallets.UserWalletBuilder import com.tangem.domain.walletconnect.WalletConnectActions import com.tangem.domain.walletmanager.WalletManagersFacade +import com.tangem.domain.wallets.models.UnlockWalletsError import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.usecase.* @@ -78,7 +79,6 @@ import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.delay import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import java.math.BigDecimal import javax.inject.Inject import kotlin.properties.Delegates @@ -511,10 +511,8 @@ internal class WalletViewModel @Inject constructor( viewModelScope.launch(dispatchers.main) { val userWallet = state.walletsListConfig.wallets[index] - withContext(dispatchers.io) { - if (userWallet !is WalletCardState.LockedContent) { - selectWalletUseCase(userWalletId = userWallet.id) - } + launch(dispatchers.io) { + selectWalletUseCase(userWallet.id) } val cacheState = WalletStateCache.getState(userWalletId = userWallet.id) @@ -842,17 +840,31 @@ internal class WalletViewModel @Inject constructor( } override fun onUnlockWalletClick() { - val state = uiState as? WalletState.ContentState ?: return - analyticsEventsHandler.send(WalletScreenAnalyticsEvent.MainScreen.NoticeWalletLocked) viewModelScope.launch(dispatchers.main) { - unlockWalletsUseCase( - selectedWalletId = state.walletsListConfig.wallets[state.walletsListConfig.selectedWalletIndex].id, - ) + unlockWalletsUseCase(throwIfNotAllWalletsUnlocked = true) + .onLeft(::handleUnlockWalletsError) } } + private fun handleUnlockWalletsError(error: UnlockWalletsError) { + val event = when (error) { + is UnlockWalletsError.DataError, + is UnlockWalletsError.UnableToUnlockWallets, + -> WalletEvent.ShowToast(resourceReference(R.string.user_wallet_list_error_unable_to_unlock)) + is UnlockWalletsError.NoUserWalletSelected, + is UnlockWalletsError.NotAllUserWalletsUnlocked, + -> WalletEvent.ShowAlert(WalletAlertState.RescanWallets) + } + + uiState = stateFactory.getStateAndTriggerEvent( + state = uiState, + event = event, + setUiState = { uiState = it }, + ) + } + override fun onUnlockWalletNotificationClick() { val state = requireNotNull(uiState as? WalletLockedState) { "Impossible to unlock wallet if state isn't WalletLockedState"