Updated on 2026-08-14
This commit is contained in:
parent
e1262077cf
commit
75763fb2e0
7 changed files with 103 additions and 47 deletions
|
|
@ -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")
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue