From 75763fb2e0c694322068e7cea5e2e4b67e192c4e Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 28 Jun 2024 16:33:29 +0800 Subject: [PATCH] Updated on 2026-08-14 --- .../tap/common/analytics/events/Settings.kt | 10 ++- .../domain/card/DefaultResetCardUseCase.kt | 26 +++--- .../features/details/redux/DetailsAction.kt | 5 +- .../details/redux/DetailsMiddleware.kt | 8 +- .../features/details/redux/DetailsReducer.kt | 20 +++-- .../features/details/redux/DetailsState.kt | 1 + .../ui/resetcard/ResetCardViewModel.kt | 80 +++++++++++++++---- 7 files changed, 103 insertions(+), 47 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/Settings.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/Settings.kt index b9a831cb3d..8ba8e26539 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/Settings.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/Settings.kt @@ -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") 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 f63f480485..244dd02b7b 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 @@ -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 = 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 = 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 { + 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 } diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt index cc3efe720c..fa88770744 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt @@ -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() { diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt index 372889fe91..c82a82d087 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt @@ -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( diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt index d8f6637f7f..5a915b21d5 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt @@ -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) } diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt index 2c77d5806e..8e7e583ecf 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt @@ -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, 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 f0628f0cee..fd32427821 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,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") + } } \ No newline at end of file