Updated on 2026-08-14
This commit is contained in:
parent
b538cc76e1
commit
dbb8ab5d50
11 changed files with 133 additions and 53 deletions
|
|
@ -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<ResetCardError, Unit> = 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<ResetCardError, Unit> = 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
|
||||
}
|
||||
}
|
||||
|
|
@ -68,7 +68,7 @@ interface TangemSdkManager {
|
|||
allowsRequestAccessCodeFromRepository: Boolean,
|
||||
): CompletionResult<CardDTO>
|
||||
|
||||
suspend fun resetBackupCard(userWalletId: UserWalletId): CompletionResult<Unit>
|
||||
suspend fun resetBackupCard(cardNumber: Int, userWalletId: UserWalletId): CompletionResult<Unit>
|
||||
|
||||
suspend fun saveAccessCode(accessCode: String, cardsIds: Set<String>): CompletionResult<Unit>
|
||||
|
||||
|
|
|
|||
|
|
@ -170,10 +170,15 @@ class DefaultTangemSdkManager(
|
|||
.map { CardDTO(it) }
|
||||
}
|
||||
|
||||
override suspend fun resetBackupCard(userWalletId: UserWalletId): CompletionResult<Unit> {
|
||||
override suspend fun resetBackupCard(cardNumber: Int, userWalletId: UserWalletId): CompletionResult<Unit> {
|
||||
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(),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ class MockTangemSdkManager(
|
|||
return MockProvider.getCardDto()
|
||||
}
|
||||
|
||||
override suspend fun resetBackupCard(userWalletId: UserWalletId): CompletionResult<Unit> {
|
||||
override suspend fun resetBackupCard(cardNumber: Int, userWalletId: UserWalletId): CompletionResult<Unit> {
|
||||
return CompletionResult.Success(Unit)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -52,11 +52,11 @@
|
|||
<string name="card_settings_completed_reset_alert_title">Сброс завершён</string>
|
||||
<string name="card_settings_continue_reset_alert_message">Хотите сбросить следующую карту от этого кошелька?</string>
|
||||
<string name="card_settings_continue_reset_alert_title">Сброс карты</string>
|
||||
<string name="card_settings_interrupted_reset_alert_message">Рекомендуем завершить процесс сброса всех карт кошелька</string>
|
||||
<string name="card_settings_interrupted_reset_alert_title">Вы сбросили не все карты</string>
|
||||
<string name="card_settings_reset_card_to_factory">Заводские настройки</string>
|
||||
<string name="card_settings_security_mode">Тип безопасности</string>
|
||||
<string name="card_settings_title">Настройки карты</string>
|
||||
<string name="card_settings_interrupted_reset_alert_message">Рекомендуем завершить процесс сброса всех карт кошелька</string>
|
||||
<string name="card_settings_interrupted_reset_alert_title">Вы сбросили не все карты</string>
|
||||
<string name="cardano_coin_will_be_send_with_token_description">Помимо сетевой комиссий, сеть Cardano взимает %1$s ADA при транзакции с токеном %2$s</string>
|
||||
<string name="cardano_coin_will_be_send_with_token_title">Требования к транзакции Cardano</string>
|
||||
<string name="cardano_insufficient_balance_to_send_token_description">Чтобы совершить транзакцию %1$s, внесите некоторую сумму ADA для покрытия сетевой комиссии и минимального значения ADA (рекомендуется 5 ADA)</string>
|
||||
|
|
@ -257,6 +257,7 @@
|
|||
<string name="initial_message_change_access_code_body">Чтобы изменить код доступа, приложите карту как показано выше и не убирайте до окончания операции</string>
|
||||
<string name="initial_message_change_passcode_body">Чтобы изменить пароль, приложите карту как показано выше и не убирайте до окончания операции</string>
|
||||
<string name="initial_message_create_wallet_body">Чтобы создать кошелек, приложите карту как показано выше и не убирайте до окончания операции</string>
|
||||
<string name="initial_message_reset_backup_card_header">Приложите карту #%s для сброса</string>
|
||||
<string name="initial_message_scan_header">Приложите, чтобы отсканировать</string>
|
||||
<string name="initial_message_sign_header">Приложите, чтобы подписать</string>
|
||||
<string name="initial_message_tap_header">Приложите карту</string>
|
||||
|
|
|
|||
|
|
@ -51,11 +51,11 @@
|
|||
<string name="card_settings_completed_reset_alert_title">Reset complete</string>
|
||||
<string name="card_settings_continue_reset_alert_message">Do you want to reset the next card in this wallet?</string>
|
||||
<string name="card_settings_continue_reset_alert_title">Card reset</string>
|
||||
<string name="card_settings_interrupted_reset_alert_message">We recommend completing the reset process for all cards in this wallet</string>
|
||||
<string name="card_settings_interrupted_reset_alert_title">You haven\'t reset all your cards</string>
|
||||
<string name="card_settings_reset_card_to_factory">Reset to Factory Settings</string>
|
||||
<string name="card_settings_security_mode">Security Mode</string>
|
||||
<string name="card_settings_title">Card settings</string>
|
||||
<string name="card_settings_interrupted_reset_alert_message">We recommend completing the reset process for all cards in this wallet</string>
|
||||
<string name="card_settings_interrupted_reset_alert_title">You haven\'t reset all your cards</string>
|
||||
<string name="cardano_coin_will_be_send_with_token_description">In addition to network fee, the Cardano network charges %1$s ADA when transacting with the %2$s token</string>
|
||||
<string name="cardano_coin_will_be_send_with_token_title">Cardano transaction requirements</string>
|
||||
<string name="cardano_insufficient_balance_to_send_token_description">To make a %1$s transaction, you must deposit some ADA to cover the network fee and minimum ADA value (5 ADA recommended)</string>
|
||||
|
|
@ -256,6 +256,7 @@
|
|||
<string name="initial_message_change_access_code_body">To change the access code tap the card as shown above and do not remove until the end of the operation</string>
|
||||
<string name="initial_message_change_passcode_body">To change the passcode tap the card as shown above and do not remove until the end of the operation</string>
|
||||
<string name="initial_message_create_wallet_body">To create the wallet tap the card as shown above and do not remove until the end of the operation</string>
|
||||
<string name="initial_message_reset_backup_card_header">Tap the card #%s of the wallet</string>
|
||||
<string name="initial_message_scan_header">Tap to scan</string>
|
||||
<string name="initial_message_sign_header">Tap to sign</string>
|
||||
<string name="initial_message_tap_header">Tap the card</string>
|
||||
|
|
|
|||
|
|
@ -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<ResetCardError, Unit>
|
||||
|
||||
/** Reset backup card [cardNumber] with expected [UserWalletId] using [card] of reset card */
|
||||
suspend operator fun invoke(
|
||||
cardNumber: Int,
|
||||
card: CardDTO,
|
||||
userWalletId: UserWalletId,
|
||||
): Either<ResetCardError, Unit>
|
||||
}
|
||||
|
|
@ -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,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,8 @@ internal class WalletsUpdateActionResolver @Inject constructor(
|
|||
|
||||
fun resolve(wallets: List<UserWallet>, 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)) {
|
||||
|
|
|
|||
|
|
@ -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" }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue