Updated on 2026-08-14

This commit is contained in:
Tangem 2024-12-27 13:35:00 +03:00
parent 9897c63c17
commit 7d9c12f1bc
10 changed files with 123 additions and 38 deletions

View file

@ -13,6 +13,7 @@ sealed class Onboarding(
class Started : Onboarding("Onboarding", "Onboarding Started")
class Finished : Onboarding("Onboarding", "Onboarding Finished")
data object OfflineAttestationFailed : Onboarding(category = "Onboarding", event = "Offline Attestation Failed")
sealed class CreateWallet(
event: String,

View file

@ -6,6 +6,7 @@ import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase
import com.tangem.tap.domain.scanCard.CardScanningFeatureToggles
import com.tangem.tap.domain.scanCard.DefaultScanCardProcessor
import com.tangem.tap.domain.scanCard.LegacyScanProcessor
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@ -24,7 +25,9 @@ internal object CardLegacyDomainModule {
@Provides
@Singleton
fun provideScanCardProcessor(): ScanCardProcessor = DefaultScanCardProcessor()
fun provideScanCardProcessor(legacyScanProcessor: LegacyScanProcessor): ScanCardProcessor {
return DefaultScanCardProcessor(legacyScanProcessor = legacyScanProcessor)
}
@Provides
@Singleton

View file

@ -10,7 +10,9 @@ import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.store
// TODO: Remove this object after feature toggle was removed and use ScanCardUseCase instead
internal class DefaultScanCardProcessor : ScanCardProcessor {
internal class DefaultScanCardProcessor(
private val legacyScanProcessor: LegacyScanProcessor,
) : ScanCardProcessor {
private val isNewCardScanningEnabled: Boolean
get() = store.inject(DaggerGraphState::cardScanningFeatureToggles).isNewCardScanningEnabled
@ -21,7 +23,10 @@ internal class DefaultScanCardProcessor : ScanCardProcessor {
return if (isNewCardScanningEnabled) {
UseCaseScanProcessor.scan(cardId, allowsRequestAccessCodeFromRepository)
} else {
LegacyScanProcessor.scan(cardId, allowsRequestAccessCodeFromRepository)
legacyScanProcessor.scan(
cardId = cardId,
allowsRequestAccessCodeFromRepository = allowsRequestAccessCodeFromRepository,
)
}
}
@ -38,24 +43,24 @@ internal class DefaultScanCardProcessor : ScanCardProcessor {
) {
if (isNewCardScanningEnabled) {
UseCaseScanProcessor.scan(
analyticsSource,
cardId,
onProgressStateChange,
onWalletNotCreated,
disclaimerWillShow,
onFailure,
onSuccess,
analyticsSource = analyticsSource,
cardId = cardId,
onProgressStateChange = onProgressStateChange,
onWalletNotCreated = onWalletNotCreated,
disclaimerWillShow = disclaimerWillShow,
onFailure = onFailure,
onSuccess = onSuccess,
)
} else {
LegacyScanProcessor.scan(
analyticsSource,
cardId,
onProgressStateChange,
onWalletNotCreated,
disclaimerWillShow,
onCancel,
onFailure,
onSuccess,
legacyScanProcessor.scan(
analyticsSource = analyticsSource,
cardId = cardId,
onProgressStateChange = onProgressStateChange,
onWalletNotCreated = onWalletNotCreated,
disclaimerWillShow = disclaimerWillShow,
onCancel = onCancel,
onFailure = onFailure,
onSuccess = onSuccess,
)
}
}

View file

@ -2,13 +2,22 @@ package com.tangem.tap.domain.scanCard
import com.tangem.common.CompletionResult
import com.tangem.common.core.TangemError
import com.tangem.common.core.TangemSdkError
import com.tangem.common.doOnFailure
import com.tangem.common.doOnSuccess
import com.tangem.common.routing.AppRoute
import com.tangem.core.analytics.Analytics
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.Basic
import com.tangem.core.decompose.di.GlobalUiMessageSender
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.ui.R
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.toWrappedList
import com.tangem.core.ui.message.DialogMessage
import com.tangem.core.ui.message.EventMessageAction
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.datasource.local.preferences.PreferencesKeys
import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull
@ -18,6 +27,8 @@ import com.tangem.domain.common.util.twinsIsTwinned
import com.tangem.domain.feedback.models.FeedbackEmailType
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
import com.tangem.sdk.extensions.localizedDescriptionRes
import com.tangem.tap.common.analytics.events.Onboarding
import com.tangem.tap.common.analytics.paramsInterceptor.CardContextInterceptor
import com.tangem.tap.common.extensions.*
import com.tangem.tap.common.redux.AppDialog
@ -33,12 +44,15 @@ import com.tangem.tap.scope
import com.tangem.tap.store
import com.tangem.tap.tangemSdkManager
import com.tangem.utils.extensions.DELAY_SDK_DIALOG_CLOSE
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.coroutines.*
import javax.inject.Inject
import javax.inject.Singleton
internal object LegacyScanProcessor {
@Singleton
internal class LegacyScanProcessor @Inject constructor(
@GlobalUiMessageSender private val uiMessageSender: UiMessageSender,
private val analyticsEventHandler: AnalyticsEventHandler,
) {
suspend fun scan(
cardId: String? = null,
@ -72,7 +86,16 @@ internal object LegacyScanProcessor {
result
.doOnFailure { error ->
onFailure(error)
onScanFailure(
error = error,
onFailure = onFailure,
onCancel = {
mainScope.launch {
onProgressStateChange.invoke(false)
onCancel()
}
},
)
}
.doOnSuccess { scanResponse ->
tangemSdkManager.changeDisplayedCardIdNumbersCount(scanResponse)
@ -82,7 +105,6 @@ internal object LegacyScanProcessor {
showDisclaimerIfNeed(
scanResponse = scanResponse,
disclaimerWillShow = disclaimerWillShow,
onFailure = onFailure,
nextHandler = { scanResponse2 ->
onScanSuccess(
scanResponse = scanResponse2,
@ -114,7 +136,6 @@ internal object LegacyScanProcessor {
scanResponse: ScanResponse,
crossinline disclaimerWillShow: () -> Unit = {},
crossinline nextHandler: suspend (ScanResponse) -> Unit,
crossinline onFailure: suspend (error: TangemError) -> Unit,
) {
val disclaimer = scanResponse.card.createDisclaimer()
@ -135,6 +156,49 @@ internal object LegacyScanProcessor {
}
}
private suspend inline fun onScanFailure(
error: TangemError,
crossinline onFailure: suspend (TangemError) -> Unit,
crossinline onCancel: () -> Job,
) {
if (error is TangemSdkError.CardVerificationFailed) {
analyticsEventHandler.send(event = Onboarding.OfflineAttestationFailed)
val resource = error.localizedDescriptionRes()
val resId = resource.resId ?: R.string.common_unknown_error
val resArgs = resource.args.map { it.value }
uiMessageSender.send(
message = DialogMessage(
message = resourceReference(id = resId, resArgs.toWrappedList()),
title = resourceReference(id = R.string.security_alert_title),
isDismissable = false,
firstActionBuilder = {
EventMessageAction(
title = resourceReference(id = R.string.alert_button_request_support),
onClick = {
mainScope.launch {
onCancel()
store.inject(DaggerGraphState::sendFeedbackEmailUseCase).invoke(
type = FeedbackEmailType.CardAttestationFailed,
)
}
},
)
},
secondActionBuilder = {
cancelAction {
mainScope.launch { onCancel() }
}
},
),
)
} else {
onFailure(error)
}
}
@Suppress("LongMethod", "MagicNumber")
private suspend inline fun onScanSuccess(
scanResponse: ScanResponse,

View file

@ -60,6 +60,7 @@ class SendFeedbackEmailUseCase(
is FeedbackEmailType.ScanningProblem,
is FeedbackEmailType.CurrencyDescriptionError,
is FeedbackEmailType.PreActivatedWallet,
is FeedbackEmailType.CardAttestationFailed,
-> this
is FeedbackEmailType.DirectUserRequest,
is FeedbackEmailType.RateCanBeBetter,

View file

@ -48,4 +48,8 @@ sealed interface FeedbackEmailType {
}
data class PreActivatedWallet(override val cardInfo: CardInfo) : FeedbackEmailType
data object CardAttestationFailed : FeedbackEmailType {
override val cardInfo: CardInfo? = null
}
}

View file

@ -21,12 +21,14 @@ internal class EmailMessageBodyResolver(
when (type) {
is FeedbackEmailType.DirectUserRequest -> addUserRequestBody(type.cardInfo)
is FeedbackEmailType.RateCanBeBetter -> addCardAndPhoneInfo(type.cardInfo)
is FeedbackEmailType.ScanningProblem -> addScanningProblemBody()
is FeedbackEmailType.TransactionSendingProblem -> addTransactionSendingProblemBody(type.cardInfo)
is FeedbackEmailType.StakingProblem -> addStakingProblemBody(type)
is FeedbackEmailType.SwapProblem -> addSwapProblemBody(type)
is FeedbackEmailType.CurrencyDescriptionError -> addTokenInfo(type)
is FeedbackEmailType.PreActivatedWallet -> addUserRequestBody(type.cardInfo)
is FeedbackEmailType.ScanningProblem,
is FeedbackEmailType.CardAttestationFailed,
-> addPhoneInfoBody()
}
return build()
@ -50,7 +52,7 @@ internal class EmailMessageBodyResolver(
addPhoneInfo(phoneInfo = feedbackRepository.getPhoneInfo())
}
private fun FeedbackDataBuilder.addScanningProblemBody() {
private fun FeedbackDataBuilder.addPhoneInfoBody() {
addPhoneInfo(phoneInfo = feedbackRepository.getPhoneInfo())
}

View file

@ -19,6 +19,7 @@ internal class EmailMessageTitleResolver(private val resources: Resources) {
return when (type) {
is FeedbackEmailType.DirectUserRequest,
is FeedbackEmailType.CurrencyDescriptionError,
is FeedbackEmailType.CardAttestationFailed,
-> R.string.feedback_preface_support
is FeedbackEmailType.RateCanBeBetter -> R.string.feedback_preface_rate_negative
is FeedbackEmailType.ScanningProblem -> R.string.feedback_preface_scan_failed

View file

@ -19,20 +19,24 @@ internal class EmailSubjectResolver(private val resources: Resources) {
return when (type) {
is FeedbackEmailType.DirectUserRequest -> {
if (type.cardInfo.isStart2Coin) {
R.string.feedback_subject_support
resources.getStringSafe(R.string.feedback_subject_support)
} else {
R.string.feedback_subject_support_tangem
resources.getStringSafe(R.string.feedback_subject_support_tangem)
}
}
is FeedbackEmailType.RateCanBeBetter -> R.string.feedback_subject_rate_negative
is FeedbackEmailType.ScanningProblem -> R.string.feedback_subject_scan_failed
is FeedbackEmailType.RateCanBeBetter -> resources.getStringSafe(R.string.feedback_subject_rate_negative)
is FeedbackEmailType.ScanningProblem -> resources.getStringSafe(R.string.feedback_subject_scan_failed)
is FeedbackEmailType.TransactionSendingProblem,
is FeedbackEmailType.StakingProblem,
is FeedbackEmailType.SwapProblem,
-> R.string.feedback_subject_tx_failed
is FeedbackEmailType.PreActivatedWallet -> R.string.feedback_subject_pre_activated_wallet
is FeedbackEmailType.CurrencyDescriptionError -> R.string.feedback_token_description_error
-> resources.getStringSafe(R.string.feedback_subject_tx_failed)
is FeedbackEmailType.PreActivatedWallet -> {
resources.getStringSafe(R.string.feedback_subject_pre_activated_wallet)
}
is FeedbackEmailType.CurrencyDescriptionError -> {
resources.getStringSafe(R.string.feedback_token_description_error)
}
FeedbackEmailType.CardAttestationFailed -> "Card attestation failed"
}
.let(resources::getStringSafe)
}
}

View file

@ -90,7 +90,7 @@ markdownComposeView = "0.5.4"
# region Tangem
tangemBlockchainSdk = "develop-900"
#tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds
tangemCardSdk = "develop-420"
tangemCardSdk = "develop-421"
#tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^
tangemVico = "2.0.0-alpha.25-tangem17"
#tangemVico = "0.0.1" # Keep it! - used for local builds ^