diff --git a/app/src/main/java/com/tangem/tap/domain/card/DefaultResetCardUseCase.kt b/app/src/main/java/com/tangem/tap/domain/card/DefaultResetCardUseCase.kt index 6cfd662efd..f63f480485 100644 --- a/app/src/main/java/com/tangem/tap/domain/card/DefaultResetCardUseCase.kt +++ b/app/src/main/java/com/tangem/tap/domain/card/DefaultResetCardUseCase.kt @@ -3,14 +3,14 @@ package com.tangem.tap.domain.card import arrow.core.Either import arrow.core.raise.either import arrow.core.right -import com.tangem.common.CompletionResult -import com.tangem.common.UserCodeType +import com.tangem.common.* +import com.tangem.common.core.TangemError import com.tangem.common.core.TangemSdkError import com.tangem.common.core.UserCodeRequestPolicy -import com.tangem.common.doOnResult import com.tangem.domain.card.ResetCardUseCase import com.tangem.domain.card.models.ResetCardError import com.tangem.domain.models.scan.CardDTO +import com.tangem.domain.wallets.models.UserWalletId import com.tangem.tap.domain.sdk.TangemSdkManager internal class DefaultResetCardUseCase( @@ -18,25 +18,37 @@ internal class DefaultResetCardUseCase( ) : ResetCardUseCase { override suspend fun invoke(card: CardDTO): Either = either { + enterRequiredAccessCode(card) { + tangemSdkManager.resetToFactorySettings( + cardId = card.cardId, + allowsRequestAccessCodeFromRepository = true, + ) + .doOnSuccess { Unit.right() } + .doOnFailure { raise(it.mapToDomainError()) } + } + } + + override suspend fun invoke( + cardNumber: Int, + card: CardDTO, + userWalletId: UserWalletId, + ): Either = either { + enterRequiredAccessCode(card) { + tangemSdkManager.resetBackupCard(cardNumber, userWalletId) + .doOnSuccess { Unit.right() } + .doOnFailure { raise(it.mapToDomainError()) } + } + } + + private suspend fun enterRequiredAccessCode( + card: CardDTO, + task: suspend TangemSdkManager.() -> CompletionResult<*>, + ) { val policyBeforeReset = tangemSdkManager.userCodeRequestPolicy requestMandatoryAccessCodeEntry(card) - val result = tangemSdkManager.resetToFactorySettings( - cardId = card.cardId, - allowsRequestAccessCodeFromRepository = true, - ) + tangemSdkManager.task() .doOnResult { tangemSdkManager.setUserCodeRequestPolicy(policyBeforeReset) } - - when (result) { - is CompletionResult.Success -> Unit.right() - is CompletionResult.Failure -> { - if (result.error is TangemSdkError.UserCancelled) { - raise(ResetCardError.UserCanceled) - } else { - raise(ResetCardError.AnotherSdkError) - } - } - } } private fun requestMandatoryAccessCodeEntry(card: CardDTO) { @@ -52,4 +64,8 @@ internal class DefaultResetCardUseCase( tangemSdkManager.setUserCodeRequestPolicy(policy = UserCodeRequestPolicy.Always(type)) } } + + private fun TangemError.mapToDomainError(): ResetCardError { + return if (this is TangemSdkError.UserCancelled) ResetCardError.UserCanceled else ResetCardError.AnotherSdkError + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/TangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/sdk/TangemSdkManager.kt index 396b363be7..e3de1dc4a7 100644 --- a/app/src/main/java/com/tangem/tap/domain/sdk/TangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/sdk/TangemSdkManager.kt @@ -68,7 +68,7 @@ interface TangemSdkManager { allowsRequestAccessCodeFromRepository: Boolean, ): CompletionResult - suspend fun resetBackupCard(userWalletId: UserWalletId): CompletionResult + suspend fun resetBackupCard(cardNumber: Int, userWalletId: UserWalletId): CompletionResult suspend fun saveAccessCode(accessCode: String, cardsIds: Set): CompletionResult diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/impl/DefaultTangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/sdk/impl/DefaultTangemSdkManager.kt index d97f63e0c6..72b578cea9 100644 --- a/app/src/main/java/com/tangem/tap/domain/sdk/impl/DefaultTangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/sdk/impl/DefaultTangemSdkManager.kt @@ -170,10 +170,15 @@ class DefaultTangemSdkManager( .map { CardDTO(it) } } - override suspend fun resetBackupCard(userWalletId: UserWalletId): CompletionResult { + override suspend fun resetBackupCard(cardNumber: Int, userWalletId: UserWalletId): CompletionResult { return runTaskAsyncReturnOnMain( runnable = ResetBackupCardTask(userWalletId), - initialMessage = Message(resources.getString(R.string.card_settings_reset_card_to_factory)), + initialMessage = Message( + resources.getString( + R.string.initial_message_reset_backup_card_header, + cardNumber.toString(), + ), + ), ) } diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/impl/MockTangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/sdk/impl/MockTangemSdkManager.kt index 54fdfd0fa8..7c5585c68b 100644 --- a/app/src/main/java/com/tangem/tap/domain/sdk/impl/MockTangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/sdk/impl/MockTangemSdkManager.kt @@ -87,7 +87,7 @@ class MockTangemSdkManager( return MockProvider.getCardDto() } - override suspend fun resetBackupCard(userWalletId: UserWalletId): CompletionResult { + override suspend fun resetBackupCard(cardNumber: Int, userWalletId: UserWalletId): CompletionResult { return CompletionResult.Success(Unit) } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardViewModel.kt index 47dd7a0286..f0628f0cee 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardViewModel.kt @@ -2,12 +2,13 @@ package com.tangem.tap.features.details.ui.resetcard import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import arrow.core.raise.either +import com.tangem.core.navigation.AppScreen +import com.tangem.core.navigation.NavigationAction import com.tangem.domain.card.DeleteSavedAccessCodesUseCase import com.tangem.domain.card.ResetCardUseCase -import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.usecase.DeleteWalletUseCase import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase +import com.tangem.feature.wallet.presentation.wallet.domain.getCardsCount import com.tangem.tap.common.extensions.onUserWalletSelected import com.tangem.tap.features.details.redux.CardSettingsState import com.tangem.tap.features.details.redux.DetailsAction.ResetToFactory @@ -15,7 +16,9 @@ import com.tangem.tap.features.details.ui.cardsettings.TextReference import com.tangem.tap.features.details.ui.resetcard.featuretoggles.ResetCardFeatureToggles import com.tangem.tap.features.details.ui.utils.toResetCardDescriptionText import com.tangem.tap.store +import com.tangem.utils.extensions.DELAY_SDK_DIALOG_CLOSE import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import javax.inject.Inject import com.tangem.tap.features.details.redux.CardSettingsState.Dialog as CardSettingsDialog @@ -29,6 +32,13 @@ internal class ResetCardViewModel @Inject constructor( private val deleteWalletUseCase: DeleteWalletUseCase, ) : ViewModel() { + private val currentUserWallet = getSelectedWalletSyncUseCase().getOrNull() + ?: error("Selected user wallet can't be null") + + // TODO: move logic to separate domain entity + private val backupCardsCount = (currentUserWallet.getCardsCount() ?: 0) - 1 + private var resetCardsCount = 0 + fun updateState(state: CardSettingsState?): ResetCardScreenState.ResetCardScreenContent { val descriptionText = state?.cardInfo ?.toResetCardDescriptionText() @@ -94,14 +104,19 @@ internal class ResetCardViewModel @Inject constructor( } private fun makeFullReset() { - val currentUserWallet = getSelectedWalletSyncUseCase().getOrNull() ?: return - viewModelScope.launch { - resetCurrentCard(userWallet = currentUserWallet).onRight { + resetCardUseCase(card = currentUserWallet.scanResponse.card).onRight { + deleteSavedAccessCodesUseCase(currentUserWallet.cardId) + deleteWalletUseCase(currentUserWallet.walletId) + val newSelectedWallet = getSelectedWalletSyncUseCase().getOrNull() if (newSelectedWallet != null) { store.onUserWalletSelected(newSelectedWallet) } + + delay(DELAY_SDK_DIALOG_CLOSE) + + checkRemainingBackupCards() } } } @@ -109,7 +124,21 @@ internal class ResetCardViewModel @Inject constructor( private fun onContinueResetClick() { dismissDialog() - // TODO: [REDACTED_TASK_KEY] + viewModelScope.launch { + resetCardUseCase( + cardNumber = resetCardsCount + 1, + card = currentUserWallet.scanResponse.card, + userWalletId = currentUserWallet.walletId, + ) + .onRight { + resetCardsCount++ + + delay(DELAY_SDK_DIALOG_CLOSE) + + checkRemainingBackupCards() + } + .onLeft { showDialog(CardSettingsDialog.InterruptedResetDialog) } + } } private fun onContinueResetDialogDismiss() { @@ -118,16 +147,28 @@ internal class ResetCardViewModel @Inject constructor( showDialog(CardSettingsDialog.InterruptedResetDialog) } - private suspend fun resetCurrentCard(userWallet: UserWallet) = either { - resetCardUseCase(userWallet.scanResponse.card).bind() - deleteSavedAccessCodesUseCase(userWallet.cardId).bind() - deleteWalletUseCase(userWallet.walletId).bind() + private fun checkRemainingBackupCards() { + when { + backupCardsCount > resetCardsCount -> showDialog(CardSettingsDialog.ContinueResetDialog) + backupCardsCount == resetCardsCount -> showDialog(CardSettingsDialog.CompletedResetDialog) + else -> finishFullReset() + } } private fun dismissAndFinishFullReset() { dismissDialog() - // TODO: [REDACTED_TASK_KEY] + finishFullReset() + } + + private fun finishFullReset() { + val newSelectedWallet = getSelectedWalletSyncUseCase().getOrNull() + + if (newSelectedWallet != null) { + store.dispatch(NavigationAction.PopBackTo(AppScreen.Wallet)) + } else { + store.dispatch(NavigationAction.PopBackTo(AppScreen.Home)) + } } private fun showDialog(dialog: CardSettingsDialog) { diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 1d192d5741..1135d892dc 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -52,11 +52,11 @@ Сброс завершён Хотите сбросить следующую карту от этого кошелька? Сброс карты + Рекомендуем завершить процесс сброса всех карт кошелька + Вы сбросили не все карты Заводские настройки Тип безопасности Настройки карты - Рекомендуем завершить процесс сброса всех карт кошелька - Вы сбросили не все карты Помимо сетевой комиссий, сеть Cardano взимает %1$s ADA при транзакции с токеном %2$s Требования к транзакции Cardano Чтобы совершить транзакцию %1$s, внесите некоторую сумму ADA для покрытия сетевой комиссии и минимального значения ADA (рекомендуется 5 ADA) @@ -257,6 +257,7 @@ Чтобы изменить код доступа, приложите карту как показано выше и не убирайте до окончания операции Чтобы изменить пароль, приложите карту как показано выше и не убирайте до окончания операции Чтобы создать кошелек, приложите карту как показано выше и не убирайте до окончания операции + Приложите карту #%s для сброса Приложите, чтобы отсканировать Приложите, чтобы подписать Приложите карту diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index fcfa15c334..8bbaa40db7 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -51,11 +51,11 @@ Reset complete Do you want to reset the next card in this wallet? Card reset + We recommend completing the reset process for all cards in this wallet + You haven\'t reset all your cards Reset to Factory Settings Security Mode Card settings - We recommend completing the reset process for all cards in this wallet - You haven\'t reset all your cards In addition to network fee, the Cardano network charges %1$s ADA when transacting with the %2$s token Cardano transaction requirements To make a %1$s transaction, you must deposit some ADA to cover the network fee and minimum ADA value (5 ADA recommended) @@ -256,6 +256,7 @@ To change the access code tap the card as shown above and do not remove until the end of the operation To change the passcode tap the card as shown above and do not remove until the end of the operation To create the wallet tap the card as shown above and do not remove until the end of the operation + Tap the card #%s of the wallet Tap to scan Tap to sign Tap the card diff --git a/domain/card/src/main/kotlin/com/tangem/domain/card/ResetCardUseCase.kt b/domain/card/src/main/kotlin/com/tangem/domain/card/ResetCardUseCase.kt index 85cf78be8a..7bdd4f2293 100644 --- a/domain/card/src/main/kotlin/com/tangem/domain/card/ResetCardUseCase.kt +++ b/domain/card/src/main/kotlin/com/tangem/domain/card/ResetCardUseCase.kt @@ -3,6 +3,7 @@ package com.tangem.domain.card import arrow.core.Either import com.tangem.domain.card.models.ResetCardError import com.tangem.domain.models.scan.CardDTO +import com.tangem.domain.wallets.models.UserWalletId /** * Use case for resetting card to factory settings @@ -11,5 +12,13 @@ import com.tangem.domain.models.scan.CardDTO */ interface ResetCardUseCase { + /** Reset card [card] to factory settings */ suspend operator fun invoke(card: CardDTO): Either + + /** Reset backup card [cardNumber] with expected [UserWalletId] using [card] of reset card */ + suspend operator fun invoke( + cardNumber: Int, + card: CardDTO, + userWalletId: UserWalletId, + ): Either } \ 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 44d391e849..8507f48bbc 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 @@ -302,21 +302,27 @@ internal class WalletViewModel @Inject constructor( ) /* - * If card is reset to factory settings, then Compose need some time to draw the WalletScreen. - * Otherwise, scroll isn't happened + * Should not show scroll animation if WalletScreen isn't in the background. + * Example, reset card */ - withContext(dispatchers.io) { delay(timeMillis = 1000) } + if (screenLifecycleProvider.isBackgroundState.value) { + updateStateByDeleteWalletTransformer(action) + } else { + withContext(dispatchers.io) { delay(timeMillis = 1000) } - scrollToWallet( - index = action.selectedWalletIndex, - onConsume = { - stateHolder.update( - DeleteWalletTransformer( - selectedWalletIndex = action.selectedWalletIndex, - deletedWalletId = action.deletedWalletId, - ), - ) - }, + scrollToWallet( + index = action.selectedWalletIndex, + onConsume = { updateStateByDeleteWalletTransformer(action) }, + ) + } + } + + private fun updateStateByDeleteWalletTransformer(action: WalletsUpdateActionResolver.Action.DeleteWallet) { + stateHolder.update( + DeleteWalletTransformer( + selectedWalletIndex = action.selectedWalletIndex, + deletedWalletId = action.deletedWalletId, + ), ) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletsUpdateActionResolver.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletsUpdateActionResolver.kt index 737fb1a107..aeb7ea5c03 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletsUpdateActionResolver.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletsUpdateActionResolver.kt @@ -25,7 +25,8 @@ internal class WalletsUpdateActionResolver @Inject constructor( fun resolve(wallets: List, currentState: WalletScreenState): Action { val selectedWallet = getSelectedWalletSyncUseCase().getOrElse { - error("Unable to find selected wallet: $it") + /* Selected user wallet can be null after reset if remaining user wallets is locked */ + return Action.Unknown } val action = if (isFirstInitialization(currentState)) { diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index b39d711a8d..5063455aba 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -89,7 +89,7 @@ markdown = "0.7.2" # region Tangem tangemBlockchainSdk = "develop-654" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds -tangemCardSdk = "develop-359" +tangemCardSdk = "develop-360" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ # endregion Tangem @@ -197,7 +197,7 @@ test-junit-android = { module = "androidx.test.ext:junit", version.ref = "junitA test-truth = { module = "com.google.truth:truth", version.ref = "truth" } test-mockk = { module = "io.mockk:mockk", version.ref = "mockk" } test-kaspresso = { module = "com.kaspersky.android-components:kaspresso", version.ref = "kaspresso" } -test-kaspresso-compose = { module = "com.kaspersky.android-components:kaspresso-compose-support", version.ref = "kaspresso-compose"} +test-kaspresso-compose = { module = "com.kaspersky.android-components:kaspresso-compose-support", version.ref = "kaspresso-compose" } test-compose-junit = { module = "androidx.compose.ui:ui-test-junit4", version.ref = "compose-junit" } test-hamcrest = { module = "org.hamcrest:hamcrest", version.ref = "hamcrest" } test-hilt = { module = "com.google.dagger:hilt-android-testing", version.ref = "hilt" }