Updated on 2026-08-14

This commit is contained in:
Tangem 2024-07-23 17:53:30 +03:00
parent 324ae269fe
commit 54833d8fc6
14 changed files with 166 additions and 111 deletions

View file

@ -4,7 +4,6 @@ import android.os.Bundle
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import arrow.core.getOrElse
import com.tangem.common.CompletionResult
import com.tangem.common.doOnSuccess
import com.tangem.common.routing.AppRoute
@ -14,10 +13,9 @@ import com.tangem.domain.card.ScanCardProcessor
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.domain.wallets.builder.UserWalletIdBuilder
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.events.Settings
import com.tangem.tap.common.extensions.dispatchDialogShow
@ -38,7 +36,6 @@ import javax.inject.Inject
@HiltViewModel
internal class CardSettingsViewModel @Inject constructor(
private val scanCardProcessor: ScanCardProcessor,
private val getUserWalletUseCase: GetUserWalletUseCase,
private val tangemSdkManager: TangemSdkManager,
savedStateHandle: SavedStateHandle,
) : ViewModel() {
@ -47,6 +44,8 @@ internal class CardSettingsViewModel @Inject constructor(
?.unbundle(UserWalletId.serializer())
?: error("User wallet ID is required for CardSettingsViewModel")
private val scannedScanResponse = MutableStateFlow<ScanResponse?>(value = null)
val screenState: MutableStateFlow<CardSettingsScreenState> = MutableStateFlow(getInitialState())
private fun getInitialState() = CardSettingsScreenState(
@ -58,13 +57,11 @@ internal class CardSettingsViewModel @Inject constructor(
private fun scanCard() = viewModelScope.launch {
scanCardProcessor.scan(allowsRequestAccessCodeFromRepository = true)
.doOnSuccess { scanResponse ->
scannedScanResponse.value = scanResponse
val scannedUserWalletId = UserWalletIdBuilder.scanResponse(scanResponse).build()
val isCorrectUserWalletScanned = scannedUserWalletId == userWalletId
if (isCorrectUserWalletScanned) {
val userWallet = getUserWallet()
updateCardDetails(userWallet)
if (userWalletId == scannedUserWalletId || scannedUserWalletId == null) {
updateCardDetails(scanResponse)
} else {
store.dispatchDialogShow(
AppDialog.SimpleOkDialogRes(
@ -76,9 +73,9 @@ internal class CardSettingsViewModel @Inject constructor(
}
}
private fun updateCardDetails(userWallet: UserWallet) {
val card = userWallet.scanResponse.card
val cardTypesResolver = userWallet.scanResponse.cardTypesResolver
private fun updateCardDetails(scanResponse: ScanResponse) {
val card = scanResponse.card
val cardTypesResolver = scanResponse.cardTypesResolver
val cardId = cardTypesResolver.getCardId()
val currentSecurityOption = getCurrentSecurityOption(card)
@ -112,7 +109,7 @@ internal class CardSettingsViewModel @Inject constructor(
if (isResetCardAllowed) {
CardInfo.ResetToFactorySettings(
description = getResetToFactoryDescription(card, cardTypesResolver),
description = getResetToFactoryDescription(card.backupStatus, cardTypesResolver),
).let(::add)
}
}
@ -129,9 +126,21 @@ internal class CardSettingsViewModel @Inject constructor(
changeAccessCode()
}
is CardInfo.ResetToFactorySettings -> {
val card = requireNotNull(scannedScanResponse.value) {
"Impossible to reset card if ScanResponse is null"
}.card
Analytics.send(Settings.CardSettings.ButtonFactoryReset())
store.dispatchNavigationAction {
push(route = AppRoute.ResetToFactory(userWalletId))
push(
route = AppRoute.ResetToFactory(
userWalletId = userWalletId,
cardSpecificInfo = AppRoute.ResetToFactory.CardSpecificInfo(
cardId = card.cardId,
backupStatus = card.backupStatus,
),
),
)
}
}
is CardInfo.SecurityMode -> {
@ -150,9 +159,9 @@ internal class CardSettingsViewModel @Inject constructor(
}
private fun changeAccessCode() = viewModelScope.launch {
val card = getUserWallet().scanResponse.card
val scanResponse = requireNotNull(scannedScanResponse.value) { "Scan response is null" }
when (val result = tangemSdkManager.setAccessCode(card.cardId)) {
when (val result = tangemSdkManager.setAccessCode(scanResponse.card.cardId)) {
is CompletionResult.Success -> Analytics.send(Settings.CardSettings.UserCodeChanged())
is CompletionResult.Failure -> {
Timber.e("Failed to change access code: ${result.error}")
@ -160,12 +169,6 @@ internal class CardSettingsViewModel @Inject constructor(
}
}
private fun getUserWallet(): UserWallet {
return getUserWalletUseCase(userWalletId).getOrElse {
error("Failed to get user wallet $userWalletId: $it")
}
}
private fun isResetToFactoryAllowedByCard(card: CardDTO, cardTypesResolver: CardTypesResolver): Boolean {
val hasPermanentWallet = card.wallets.any { it.settings.isPermanent }
val isNotAllowed = hasPermanentWallet || cardTypesResolver.isStart2Coin()

View file

@ -5,8 +5,11 @@ import com.tangem.domain.models.scan.CardDTO
import com.tangem.tap.features.details.ui.cardsettings.TextReference
import com.tangem.wallet.R
internal fun getResetToFactoryDescription(card: CardDTO, typesResolver: CardTypesResolver): TextReference {
return if (card.backupStatus?.isActive != true || typesResolver.isTangemTwins()) {
internal fun getResetToFactoryDescription(
backupStatus: CardDTO.BackupStatus?,
typesResolver: CardTypesResolver,
): TextReference {
return if (backupStatus?.isActive != true || typesResolver.isTangemTwins()) {
TextReference.Res(R.string.reset_card_without_backup_to_factory_message)
} else {
TextReference.Res(R.string.reset_card_with_backup_to_factory_message)

View file

@ -11,12 +11,11 @@ import com.tangem.common.routing.utils.popTo
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.domain.card.DeleteSavedAccessCodesUseCase
import com.tangem.domain.card.ResetCardUseCase
import com.tangem.domain.card.ResetCardUserCodeParams
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.legacy.UserWalletsListManager
import com.tangem.domain.wallets.legacy.asLockable
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.usecase.DeleteWalletUseCase
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
@ -39,7 +38,7 @@ import javax.inject.Inject
@Suppress("LongParameterList")
@HiltViewModel
internal class ResetCardViewModel @Inject constructor(
private val getUserWalletUseCase: GetUserWalletUseCase,
getUserWalletUseCase: GetUserWalletUseCase,
private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
private val resetCardUseCase: ResetCardUseCase,
private val deleteSavedAccessCodesUseCase: DeleteSavedAccessCodesUseCase,
@ -49,9 +48,26 @@ internal class ResetCardViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
) : ViewModel() {
private val userWalletId = savedStateHandle.get<Bundle>(AppRoute.ResetToFactory.USER_WALLET_ID)
// region Card-set specific data. All cards from single set have the same userWalletId and cardTypesResolver
private val currentUserWalletId = savedStateHandle.get<Bundle>(AppRoute.ResetToFactory.USER_WALLET_ID)
?.unbundle(UserWalletId.serializer())
?: error("User wallet ID must be provided for ResetCardViewModel")
?: error("UserWalletId must be provided for ResetCardViewModel")
// Use only for card-specific data
private val userWallet = getUserWalletUseCase(userWalletId = currentUserWalletId)
.getOrElse { error("Failed to get user wallet: $it") }
private val currentCardTypesResolver = userWallet.cardTypesResolver
private val currentUserCodeParams = ResetCardUserCodeParams(
isAccessCodeSet = userWallet.scanResponse.card.isAccessCodeSet,
isPasscodeSet = userWallet.scanResponse.card.isPasscodeSet,
)
// endregion
// region Data of card that was scanned on CardSettings
private val primaryCardId: String
private val primaryBackupStatus: CardDTO.BackupStatus?
// endregion
// TODO: move logic to separate domain entity
private var resetBackupCardCount = 0
@ -60,29 +76,33 @@ internal class ResetCardViewModel @Inject constructor(
value = getInitialState(),
)
private fun getInitialState(): ResetCardScreenState {
val userWallet = getUserWalletUseCase(userWalletId).getOrElse {
error("Failed to get user wallet $userWalletId: $it")
}
val card = userWallet.scanResponse.card
val cardTypesResolver = userWallet.scanResponse.cardTypesResolver
val descriptionText = getResetToFactoryDescription(card, cardTypesResolver)
val isTangemWallet = cardTypesResolver.isTangemWallet() || cardTypesResolver.isWallet2()
val showResetPasswordButton = isTangemWallet && card.backupStatus is CardDTO.BackupStatus.Active
init {
val cardSpecificInfo = savedStateHandle.get<Bundle>(AppRoute.ResetToFactory.CARD_SPECIFIC_DATA)
?.unbundle(AppRoute.ResetToFactory.CardSpecificInfo.serializer())
?: error("CardSpecificData must be provided for ResetCardViewModel")
primaryCardId = cardSpecificInfo.cardId
primaryBackupStatus = cardSpecificInfo.backupStatus
}
private fun getInitialState(): ResetCardScreenState {
val shouldShowResetPasswordButton = shouldShowResetPasswordButton()
val warningsToShow = buildList {
add(ResetCardScreenState.WarningsToReset.LOST_WALLET_ACCESS)
if (showResetPasswordButton) {
if (shouldShowResetPasswordButton) {
add(ResetCardScreenState.WarningsToReset.LOST_PASSWORD_RESTORE)
}
}
return ResetCardScreenState(
resetButtonEnabled = false,
descriptionText = descriptionText,
descriptionText = getResetToFactoryDescription(
backupStatus = primaryBackupStatus,
typesResolver = currentCardTypesResolver,
),
warningsToShow = warningsToShow,
showResetPasswordButton = showResetPasswordButton,
showResetPasswordButton = shouldShowResetPasswordButton,
acceptCondition1Checked = false,
acceptCondition2Checked = false,
onAcceptCondition1ToggleClick = ::toggleFirstCondition,
@ -92,6 +112,12 @@ internal class ResetCardViewModel @Inject constructor(
)
}
private fun shouldShowResetPasswordButton(): Boolean {
val isTangemWallet = currentCardTypesResolver.isTangemWallet() || currentCardTypesResolver.isWallet2()
return isTangemWallet && primaryBackupStatus is CardDTO.BackupStatus.Active
}
private fun toggleFirstCondition(isAccepted: Boolean) {
screenState.update { prevState ->
val resetButtonEnabled = if (prevState.showResetPasswordButton) {
@ -154,12 +180,9 @@ internal class ResetCardViewModel @Inject constructor(
private fun makeFullReset() {
viewModelScope.launch {
val userWallet = getUserWallet()
val scanResponse = userWallet.scanResponse
resetCardUseCase(card = scanResponse.card).onRight {
deleteSavedAccessCodesUseCase(scanResponse.card.cardId)
val hasUserWallets = deleteWalletUseCase(userWalletId).getOrElse {
resetCardUseCase(cardId = primaryCardId, params = currentUserCodeParams).onRight {
deleteSavedAccessCodesUseCase(cardId = primaryCardId)
val hasUserWallets = deleteWalletUseCase(userWalletId = currentUserWalletId).getOrElse {
Timber.e("Unable to delete user wallet: $it")
return@launch
}
@ -183,14 +206,15 @@ internal class ResetCardViewModel @Inject constructor(
dismissDialog()
viewModelScope.launch {
val userWallet = getUserWallet()
resetCardUseCase(
cardNumber = resetBackupCardCount + 1,
card = userWallet.scanResponse.card,
userWalletId = userWalletId,
params = currentUserCodeParams,
userWalletId = currentUserWalletId,
)
.onRight {
resetBackupCardCount++
.onRight { isResetCompleted ->
if (isResetCompleted) {
resetBackupCardCount++
}
delay(DELAY_SDK_DIALOG_CLOSE)
@ -213,7 +237,7 @@ internal class ResetCardViewModel @Inject constructor(
}
private fun checkRemainingBackupCards() {
val backupCardsCount = getUserWallet().scanResponse.getBackupCardsCount()
val backupCardsCount = getBackupCardsCount()
when {
backupCardsCount > resetBackupCardCount -> showDialog(ResetCardDialog.ContinueResetDialog)
@ -227,12 +251,6 @@ internal class ResetCardViewModel @Inject constructor(
}
}
private fun getUserWallet(): UserWallet {
return getUserWalletUseCase(userWalletId).getOrElse {
error("Failed to get user wallet $userWalletId: $it")
}
}
private fun dismissAndFinishFullReset() {
dismissDialog()
@ -249,7 +267,7 @@ internal class ResetCardViewModel @Inject constructor(
if (isLocked && userWalletsListManager.hasUserWallets) {
store.dispatchNavigationAction { popTo<AppRoute.Welcome>() }
} else {
store.dispatchNavigationAction { popTo<AppRoute.Home>() }
store.dispatchNavigationAction { replaceAll(AppRoute.Home) }
}
}
}
@ -262,10 +280,10 @@ internal class ResetCardViewModel @Inject constructor(
screenState.update { it.copy(dialog = null) }
}
private fun ScanResponse.getBackupCardsCount(): Int {
if (!cardTypesResolver.isMultiwalletAllowed()) return 0
private fun getBackupCardsCount(): Int {
if (!currentCardTypesResolver.isMultiwalletAllowed()) return 0
return when (val status = card.backupStatus) {
return when (val status = primaryBackupStatus) {
is CardDTO.BackupStatus.Active -> status.cardCount
is CardDTO.BackupStatus.CardLinked,
is CardDTO.BackupStatus.NoBackup,