Updated on 2026-08-14

This commit is contained in:
Tangem 2024-06-28 16:33:29 +08:00
parent e1262077cf
commit 75763fb2e0
7 changed files with 103 additions and 47 deletions

View file

@ -36,11 +36,19 @@ sealed class Settings(
) : Settings("Settings / Card Settings", event, params, error) {
class ButtonFactoryReset : CardSettings("Button - Factory Reset")
class FactoryResetFinished(error: Throwable? = null) : CardSettings(
class FactoryResetFinished(cardsCount: Int? = null, error: Throwable? = null) : CardSettings(
event = "Factory Reset Finished",
params = buildMap {
cardsCount?.let { put("Cards Count", "$it") }
},
error = error,
)
class FactoryResetCanceled(cardsCount: Int) : CardSettings(
event = "Factory Reset Canceled",
params = mapOf("Cards Count" to "$cardsCount"),
)
class UserCodeChanged : CardSettings("User Code Changed")
class ButtonChangeSecurityMode : CardSettings("Button - Change Security Mode")

View file

@ -1,12 +1,15 @@
package com.tangem.tap.domain.card
import arrow.core.Either
import arrow.core.left
import arrow.core.raise.either
import arrow.core.right
import com.tangem.common.*
import com.tangem.common.CompletionResult
import com.tangem.common.UserCodeType
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
@ -19,13 +22,12 @@ internal class DefaultResetCardUseCase(
override suspend fun invoke(card: CardDTO): Either<ResetCardError, Unit> = either {
enterRequiredAccessCode(card) {
tangemSdkManager.resetToFactorySettings(
resetToFactorySettings(
cardId = card.cardId,
allowsRequestAccessCodeFromRepository = true,
)
.doOnSuccess { Unit.right() }
.doOnFailure { raise(it.mapToDomainError()) }
}
.mapToEither()
}
override suspend fun invoke(
@ -34,20 +36,19 @@ internal class DefaultResetCardUseCase(
userWalletId: UserWalletId,
): Either<ResetCardError, Unit> = either {
enterRequiredAccessCode(card) {
tangemSdkManager.resetBackupCard(cardNumber, userWalletId)
.doOnSuccess { Unit.right() }
.doOnFailure { raise(it.mapToDomainError()) }
resetBackupCard(cardNumber = cardNumber, userWalletId = userWalletId)
}
.mapToEither()
}
private suspend fun enterRequiredAccessCode(
card: CardDTO,
task: suspend TangemSdkManager.() -> CompletionResult<*>,
) {
): CompletionResult<*> {
val policyBeforeReset = tangemSdkManager.userCodeRequestPolicy
requestMandatoryAccessCodeEntry(card)
tangemSdkManager.task()
return tangemSdkManager.task()
.doOnResult { tangemSdkManager.setUserCodeRequestPolicy(policyBeforeReset) }
}
@ -65,6 +66,13 @@ internal class DefaultResetCardUseCase(
}
}
private fun CompletionResult<*>.mapToEither(): Either<ResetCardError, Unit> {
return when (this) {
is CompletionResult.Failure -> error.mapToDomainError().left()
is CompletionResult.Success -> Unit.right()
}
}
private fun TangemError.mapToDomainError(): ResetCardError {
return if (this is TangemSdkError.UserCancelled) ResetCardError.UserCanceled else ResetCardError.AnotherSdkError
}

View file

@ -4,8 +4,6 @@ import androidx.lifecycle.LifecycleCoroutineScope
import com.tangem.core.ui.extensions.TextReference
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.apptheme.model.AppThemeMode
import com.tangem.domain.common.CardTypesResolver
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ScanResponse
import org.rekotlin.Action
@ -33,7 +31,8 @@ sealed class DetailsAction : Action {
data object ScanCard : DetailsAction()
data class PrepareCardSettingsData(val card: CardDTO, val cardTypesResolver: CardTypesResolver) : DetailsAction()
data class PrepareCardSettingsData(val scanResponse: ScanResponse) : DetailsAction()
data object ResetCardSettingsData : DetailsAction()
data object ScanAndSaveUserWallet : DetailsAction() {

View file

@ -13,7 +13,6 @@ import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.domain.apptheme.model.AppThemeMode
import com.tangem.domain.common.TapWorkarounds.isTangemTwins
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.wallets.builder.UserWalletBuilder
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
@ -147,7 +146,7 @@ class DetailsMiddleware {
}
.doOnFailure { error ->
if (error is TangemSdkError && error !is TangemSdkError.UserCancelled) {
Analytics.send(Settings.CardSettings.FactoryResetFinished(error))
Analytics.send(Settings.CardSettings.FactoryResetFinished(error = error))
}
}
.doOnResult {
@ -472,10 +471,7 @@ class DetailsMiddleware {
if (isSameWallet) {
store.dispatchOnMain(
DetailsAction.PrepareCardSettingsData(
scanResponse.card,
scanResponse.cardTypesResolver,
),
DetailsAction.PrepareCardSettingsData(scanResponse = scanResponse),
)
} else {
store.dispatchDialogShow(

View file

@ -5,6 +5,7 @@ import com.tangem.domain.apptheme.model.AppThemeMode
import com.tangem.domain.common.CardTypesResolver
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.tap.common.extensions.inject
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.domain.extensions.signedHashesCount
@ -29,11 +30,7 @@ private fun internalReduce(action: Action, state: AppState): DetailsState {
handlePrepareScreen(action, state)
}
is DetailsAction.PrepareCardSettingsData -> {
handlePrepareCardSettingsScreen(
card = action.card,
cardTypesResolver = action.cardTypesResolver,
state = detailsState,
)
handlePrepareCardSettingsScreen(scanResponse = action.scanResponse, state = detailsState)
}
is DetailsAction.ResetCardSettingsData -> detailsState.copy(cardSettingsState = null)
is DetailsAction.ResetToFactory -> {
@ -97,17 +94,17 @@ private fun handlePrepareScreen(action: DetailsAction.PrepareScreen, state: AppS
)
}
private fun handlePrepareCardSettingsScreen(
card: CardDTO,
cardTypesResolver: CardTypesResolver,
state: DetailsState,
): DetailsState {
private fun handlePrepareCardSettingsScreen(scanResponse: ScanResponse, state: DetailsState): DetailsState {
val cardTypesResolver = scanResponse.cardTypesResolver
val card = scanResponse.card
val isTangemWallet = cardTypesResolver.isTangemWallet() || cardTypesResolver.isWallet2()
val isShowPasswordResetRadioButton = isTangemWallet && card.backupStatus is CardDTO.BackupStatus.Active
val cardSettingsState = CardSettingsState(
cardInfo = card.toCardInfo(cardTypesResolver),
scanResponse = scanResponse,
manageSecurityState = prepareSecurityOptions(card, cardTypesResolver),
card = card,
card = scanResponse.card,
resetCardAllowed = isResetToFactoryAllowedByCard(card, cardTypesResolver),
resetButtonEnabled = false,
condition1Checked = false,
@ -124,6 +121,7 @@ private fun handlePrepareCardSettingsScreen(
isShowPasswordResetRadioButton = isShowPasswordResetRadioButton,
dialog = null,
)
return state.copy(cardSettingsState = cardSettingsState)
}

View file

@ -39,6 +39,7 @@ data class AccessCodeRecoveryState(
data class CardSettingsState(
val cardInfo: CardInfo,
val card: CardDTO,
val scanResponse: ScanResponse,
val manageSecurityState: ManageSecurityState?,
val resetCardAllowed: Boolean,
val resetButtonEnabled: Boolean,

View file

@ -2,13 +2,21 @@ package com.tangem.tap.features.details.ui.resetcard
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.tangem.core.analytics.api.AnalyticsEventHandler
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.common.util.cardTypesResolver
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.legacy.asLockable
import com.tangem.domain.wallets.models.UserWalletId
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.analytics.events.Settings
import com.tangem.tap.common.extensions.onUserWalletSelected
import com.tangem.tap.features.details.redux.CardSettingsState
import com.tangem.tap.features.details.redux.DetailsAction.ResetToFactory
@ -23,6 +31,7 @@ import kotlinx.coroutines.launch
import javax.inject.Inject
import com.tangem.tap.features.details.redux.CardSettingsState.Dialog as CardSettingsDialog
@Suppress("LongParameterList")
@HiltViewModel
internal class ResetCardViewModel @Inject constructor(
private val resetCardFeatureToggles: ResetCardFeatureToggles,
@ -30,14 +39,17 @@ internal class ResetCardViewModel @Inject constructor(
private val resetCardUseCase: ResetCardUseCase,
private val deleteSavedAccessCodesUseCase: DeleteSavedAccessCodesUseCase,
private val deleteWalletUseCase: DeleteWalletUseCase,
private val userWalletsListManager: UserWalletsListManager,
private val analyticsEventHandler: AnalyticsEventHandler,
) : ViewModel() {
private val currentUserWallet = getSelectedWalletSyncUseCase().getOrNull()
?: error("Selected user wallet can't be null")
private val firstCardScanResponse = store.state.detailsState.cardSettingsState?.scanResponse
?: error("ScanResponse can't be null")
private val currentUserWalletId = createUserWalletId(firstCardScanResponse)
// TODO: move logic to separate domain entity
private val backupCardsCount = (currentUserWallet.getCardsCount() ?: 0) - 1
private var resetCardsCount = 0
private var resetBackupCardCount = 0
fun updateState(state: CardSettingsState?): ResetCardScreenState.ResetCardScreenContent {
val descriptionText = state?.cardInfo
@ -82,7 +94,7 @@ internal class ResetCardViewModel @Inject constructor(
CardSettingsDialog.InterruptedResetDialog -> {
ResetCardScreenState.ResetCardScreenContent.Dialog.InterruptedReset(
onConfirmClick = ::onContinueResetClick,
onDismiss = ::dismissAndFinishFullReset,
onDismiss = ::onInterruptedResetDialogDismiss,
)
}
CardSettingsDialog.CompletedResetDialog -> {
@ -105,9 +117,9 @@ internal class ResetCardViewModel @Inject constructor(
private fun makeFullReset() {
viewModelScope.launch {
resetCardUseCase(card = currentUserWallet.scanResponse.card).onRight {
deleteSavedAccessCodesUseCase(currentUserWallet.cardId)
deleteWalletUseCase(currentUserWallet.walletId)
resetCardUseCase(card = firstCardScanResponse.card).onRight {
deleteSavedAccessCodesUseCase(firstCardScanResponse.card.cardId)
deleteWalletUseCase(currentUserWalletId)
val newSelectedWallet = getSelectedWalletSyncUseCase().getOrNull()
if (newSelectedWallet != null) {
@ -126,12 +138,12 @@ internal class ResetCardViewModel @Inject constructor(
viewModelScope.launch {
resetCardUseCase(
cardNumber = resetCardsCount + 1,
card = currentUserWallet.scanResponse.card,
userWalletId = currentUserWallet.walletId,
cardNumber = resetBackupCardCount + 1,
card = firstCardScanResponse.card,
userWalletId = currentUserWalletId,
)
.onRight {
resetCardsCount++
resetBackupCardCount++
delay(DELAY_SDK_DIALOG_CLOSE)
@ -147,10 +159,23 @@ internal class ResetCardViewModel @Inject constructor(
showDialog(CardSettingsDialog.InterruptedResetDialog)
}
private fun onInterruptedResetDialogDismiss() {
analyticsEventHandler.send(Settings.CardSettings.FactoryResetCanceled(cardsCount = resetBackupCardCount + 1))
dismissAndFinishFullReset()
}
private fun checkRemainingBackupCards() {
val backupCardsCount = firstCardScanResponse.getBackupCardsCount()
when {
backupCardsCount > resetCardsCount -> showDialog(CardSettingsDialog.ContinueResetDialog)
backupCardsCount == resetCardsCount -> showDialog(CardSettingsDialog.CompletedResetDialog)
backupCardsCount > resetBackupCardCount -> showDialog(CardSettingsDialog.ContinueResetDialog)
backupCardsCount == resetBackupCardCount -> {
analyticsEventHandler.send(
event = Settings.CardSettings.FactoryResetFinished(cardsCount = resetBackupCardCount + 1),
)
showDialog(CardSettingsDialog.CompletedResetDialog)
}
else -> finishFullReset()
}
}
@ -162,12 +187,17 @@ internal class ResetCardViewModel @Inject constructor(
}
private fun finishFullReset() {
val newSelectedWallet = getSelectedWalletSyncUseCase().getOrNull()
val newSelectedWallet = userWalletsListManager.selectedUserWalletSync
if (newSelectedWallet != null) {
store.dispatch(NavigationAction.PopBackTo(AppScreen.Wallet))
} else {
store.dispatch(NavigationAction.PopBackTo(AppScreen.Home))
val isLocked = runCatching { userWalletsListManager.asLockable()?.isLockedSync }.isSuccess
if (isLocked && userWalletsListManager.hasUserWallets) {
store.dispatch(NavigationAction.PopBackTo(AppScreen.Welcome))
} else {
store.dispatch(NavigationAction.PopBackTo(AppScreen.Home))
}
}
}
@ -178,4 +208,20 @@ internal class ResetCardViewModel @Inject constructor(
private fun dismissDialog() {
store.dispatch(ResetToFactory.DismissDialog)
}
private fun ScanResponse.getBackupCardsCount(): Int {
if (!cardTypesResolver.isMultiwalletAllowed()) return 0
return when (val status = card.backupStatus) {
is CardDTO.BackupStatus.Active -> status.cardCount
is CardDTO.BackupStatus.CardLinked -> status.cardCount
is CardDTO.BackupStatus.NoBackup -> 0
null -> 0 // Multi-currency wallet without backup function. Example, 4.12
}
}
private fun createUserWalletId(scanResponse: ScanResponse): UserWalletId {
return UserWalletIdBuilder.scanResponse(scanResponse).build()
?: error("UserWalletId can't be null")
}
}