From 77cb43fd8dd48a9e40349ba12e233d9fc9a45a88 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 20 Jan 2025 12:11:48 +0500 Subject: [PATCH 1/2] Updated on 2026-08-14 --- .../com/tangem/common/ui/notifications/NotificationsFactory.kt | 2 +- gradle/dependencies.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationsFactory.kt b/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationsFactory.kt index 2f18a10314..d25fea1cbc 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationsFactory.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationsFactory.kt @@ -173,7 +173,7 @@ object NotificationsFactory { val spendingAmount = if (cryptoCurrency is CryptoCurrency.Token) { feeAmount } else { - sendingAmount + sendingAmount + feeAmount } val diff = balance.minus(spendingAmount) if (existentialDeposit != null && diff >= BigDecimal.ZERO && existentialDeposit > diff) { diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 3ecbbb9cf6..6f68640684 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -88,7 +88,7 @@ markdownComposeView = "0.5.4" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "release-app_5.20-908" +tangemBlockchainSdk = "release-app_5.20-909" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "release-app_5.20-422" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From 5b841dbac5ece6759a5d90d22eb419ada4a0a7db Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 21 Jan 2025 13:32:44 +0300 Subject: [PATCH 2/2] Updated on 2026-08-14 --- .../com/tangem/tap/ApplicationEntryPoint.kt | 5 +++ .../java/com/tangem/tap/TangemApplication.kt | 6 +++ .../domain/scanCard/LegacyScanProcessor.kt | 36 +++++---------- .../domain/twins/CreateFirstTwinWalletTask.kt | 35 +++++++++++++-- .../twins/CreateSecondTwinWalletTask.kt | 27 +++++++++--- .../tap/domain/twins/TwinCardsManager.kt | 8 +--- .../twins/redux/TwinCardsMiddleware.kt | 44 +++++++++++++++---- .../redux/OnboardingWalletMiddleware.kt | 25 +++++++++++ .../tap/proxy/redux/DaggerGraphState.kt | 2 + .../models/event/OnboardingAnalyticsEvent.kt | 18 ++++++++ .../tangem/core/ui/message/dialog/Dialogs.kt | 39 ++++++++++++++++ .../backup/model/MultiWalletBackupModel.kt | 42 +++++++++++++++--- gradle/dependencies.toml | 2 +- 13 files changed, 235 insertions(+), 54 deletions(-) create mode 100644 core/analytics/models/src/main/java/com/tangem/core/analytics/models/event/OnboardingAnalyticsEvent.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/message/dialog/Dialogs.kt diff --git a/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt b/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt index 328a4fe0f8..d28fb50cbc 100644 --- a/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt +++ b/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt @@ -8,6 +8,8 @@ import com.tangem.common.routing.AppRouter import com.tangem.core.analytics.filter.OneTimeEventFilter import com.tangem.core.configtoggle.blockchain.ExcludedBlockchainsManager import com.tangem.core.configtoggle.feature.FeatureTogglesManager +import com.tangem.core.decompose.di.GlobalUiMessageSender +import com.tangem.core.decompose.ui.UiMessageSender import com.tangem.core.navigation.settings.SettingsManager import com.tangem.core.navigation.share.ShareManager import com.tangem.core.navigation.url.UrlOpener @@ -134,4 +136,7 @@ interface ApplicationEntryPoint { fun getSettingsManager(): SettingsManager fun getBlockchainExceptionHandler(): BlockchainExceptionHandler + + @GlobalUiMessageSender + fun getUiMessageSender(): UiMessageSender } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/TangemApplication.kt b/app/src/main/java/com/tangem/tap/TangemApplication.kt index 3819915526..d25917729c 100644 --- a/app/src/main/java/com/tangem/tap/TangemApplication.kt +++ b/app/src/main/java/com/tangem/tap/TangemApplication.kt @@ -21,6 +21,7 @@ import com.tangem.core.analytics.models.Basic import com.tangem.core.analytics.models.Basic.TransactionSent.WalletForm import com.tangem.core.configtoggle.blockchain.ExcludedBlockchainsManager import com.tangem.core.configtoggle.feature.FeatureTogglesManager +import com.tangem.core.decompose.ui.UiMessageSender import com.tangem.core.navigation.settings.SettingsManager import com.tangem.core.ui.clipboard.ClipboardManager import com.tangem.datasource.api.common.MoshiConverter @@ -210,6 +211,10 @@ abstract class TangemApplication : Application(), ImageLoaderFactory { private val blockchainExceptionHandler: BlockchainExceptionHandler get() = entryPoint.getBlockchainExceptionHandler() + + private val uiMessageSender: UiMessageSender + get() = entryPoint.getUiMessageSender() + // endregion override fun onCreate() { @@ -308,6 +313,7 @@ abstract class TangemApplication : Application(), ImageLoaderFactory { appPreferencesStore = appPreferencesStore, clipboardManager = clipboardManager, settingsManager = settingsManager, + uiMessageSender = uiMessageSender, ), ), ) diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt index 96137b75ea..0d0d13797e 100644 --- a/app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt @@ -11,13 +11,13 @@ 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.analytics.models.event.OnboardingAnalyticsEvent 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.core.ui.message.dialog.Dialogs import com.tangem.datasource.api.tangemTech.models.UserTokensResponse import com.tangem.datasource.local.preferences.PreferencesKeys import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull @@ -28,7 +28,6 @@ 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 @@ -168,36 +167,25 @@ internal class LegacyScanProcessor @Inject constructor( crossinline onCancel: () -> Unit, ) { if (error is TangemSdkError.CardVerificationFailed) { - analyticsEventHandler.send(event = Onboarding.OfflineAttestationFailed) + analyticsEventHandler.send(event = OnboardingAnalyticsEvent.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() + message = Dialogs.cardVerificationFailed( + errorDescription = resourceReference(id = resId, resArgs.toWrappedList()), + onRequestSupport = { + mainScope.launch { + onCancel() - store.inject(DaggerGraphState::sendFeedbackEmailUseCase).invoke( - type = FeedbackEmailType.CardAttestationFailed, - ) - } - }, - ) - }, - secondActionBuilder = { - cancelAction { - mainScope.launch { onCancel() } + store.inject(DaggerGraphState::sendFeedbackEmailUseCase).invoke( + type = FeedbackEmailType.CardAttestationFailed, + ) } }, + onCancelClick = { onCancel() }, ), ) } else { diff --git a/app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt b/app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt index 35d535aa53..b7b896682c 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt @@ -4,7 +4,9 @@ import com.tangem.common.CompletionResult import com.tangem.common.card.EllipticCurve import com.tangem.common.core.CardSession import com.tangem.common.core.CardSessionRunnable +import com.tangem.common.core.CompletionCallback import com.tangem.domain.common.TwinsHelper +import com.tangem.operations.attestation.AttestationTask import com.tangem.operations.wallet.CreateWalletResponse import com.tangem.operations.wallet.CreateWalletTask import com.tangem.operations.wallet.PurgeWalletCommand @@ -13,7 +15,7 @@ class CreateFirstTwinWalletTask(private val firstCardId: String) : CardSessionRu override val allowsRequestAccessCodeFromRepository: Boolean = false - override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { + override fun run(session: CardSession, callback: CompletionCallback) { val card = session.environment.card val publicKey = card?.wallets?.firstOrNull()?.publicKey if (publicKey != null) { @@ -29,13 +31,40 @@ class CreateFirstTwinWalletTask(private val firstCardId: String) : CardSessionRu when (response) { is CompletionResult.Success -> { session.environment.card = session.environment.card?.setWallets(emptyList()) - CreateWalletTask(EllipticCurve.Secp256k1).run(session) { callback(it) } + createWallet(session, callback) } is CompletionResult.Failure -> callback(CompletionResult.Failure(response.error)) } } } else { - CreateWalletTask(EllipticCurve.Secp256k1).run(session) { callback(it) } + createWallet(session, callback) + } + } + + private fun createWallet(session: CardSession, callback: CompletionCallback) { + CreateWalletTask(EllipticCurve.Secp256k1).run(session) { createWalletResponse -> + when (createWalletResponse) { + is CompletionResult.Success -> { + runAttestation(session) { attestationResponse -> + when (attestationResponse) { + is CompletionResult.Success -> callback(createWalletResponse) + is CompletionResult.Failure -> callback(CompletionResult.Failure(attestationResponse.error)) + } + } + } + is CompletionResult.Failure -> callback(CompletionResult.Failure(createWalletResponse.error)) + } + } + } + + companion object { + + internal fun runAttestation(session: CardSession, callback: CompletionCallback<*>) { + val mode = session.environment.config.attestationMode + val secureStorage = session.environment.secureStorage + val attestationTask = AttestationTask(mode, secureStorage) + + attestationTask.run(session, callback) } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt b/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt index d91c57c086..d55d7d7d32 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt @@ -6,11 +6,13 @@ import com.tangem.common.KeyPair import com.tangem.common.card.EllipticCurve import com.tangem.common.core.CardSession import com.tangem.common.core.CardSessionRunnable +import com.tangem.common.core.CompletionCallback import com.tangem.common.extensions.hexToBytes import com.tangem.domain.common.TwinsHelper import com.tangem.operations.wallet.CreateWalletResponse import com.tangem.operations.wallet.CreateWalletTask import com.tangem.operations.wallet.PurgeWalletCommand +import com.tangem.tap.domain.twins.CreateFirstTwinWalletTask.Companion.runAttestation class CreateSecondTwinWalletTask( private val firstPublicKey: String, @@ -59,12 +61,10 @@ class CreateSecondTwinWalletTask( CreateWalletTask(EllipticCurve.Secp256k1).run(session) { result -> when (result) { is CompletionResult.Success -> { - session.environment.card = session.environment.card?.updateWallet(result.data.wallet) - - WriteProtectedIssuerDataTask(firstPublicKey.hexToBytes(), issuerKeys).run(session) { writeResult -> - when (writeResult) { - is CompletionResult.Success -> callback(result) - is CompletionResult.Failure -> callback(CompletionResult.Failure(writeResult.error)) + runAttestation(session) { attestationResponse -> + when (attestationResponse) { + is CompletionResult.Success -> writeProtectedIssuerData(session, result, callback) + is CompletionResult.Failure -> callback(CompletionResult.Failure(attestationResponse.error)) } } } @@ -72,4 +72,19 @@ class CreateSecondTwinWalletTask( } } } + + private fun writeProtectedIssuerData( + session: CardSession, + result: CompletionResult.Success, + callback: CompletionCallback, + ) { + session.environment.card = session.environment.card?.updateWallet(result.data.wallet) + + WriteProtectedIssuerDataTask(firstPublicKey.hexToBytes(), issuerKeys).run(session) { writeResult -> + when (writeResult) { + is CompletionResult.Success -> callback(result) + is CompletionResult.Failure -> callback(CompletionResult.Failure(writeResult.error)) + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt index beaf919a98..2a9dcc2d23 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt @@ -1,7 +1,6 @@ package com.tangem.tap.domain.twins import com.tangem.Message -import com.tangem.blockchain.extensions.Result import com.tangem.common.CompletionResult import com.tangem.common.KeyPair import com.tangem.common.extensions.hexToBytes @@ -57,7 +56,7 @@ class TwinCardsManager(card: CardDTO) { return response } - suspend fun complete(message: Message): Result { + suspend fun complete(message: Message): CompletionResult { val response = tangemSdkManager.finalizeTwin( secondCardPublicKey = secondCardPublicKey!!.hexToBytes(), issuerKeyPair = getIssuerKeys(), @@ -65,10 +64,7 @@ class TwinCardsManager(card: CardDTO) { initialMessage = message, ) - return when (response) { - is CompletionResult.Success -> Result.Success(response.data) - is CompletionResult.Failure -> Result.fromTangemSdkError(response.error) - } + return response } private suspend fun getIssuerKeys(): KeyPair { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt index 37ecb6d41f..78a0248326 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt @@ -1,17 +1,25 @@ package com.tangem.tap.features.onboarding.products.twins.redux -import com.tangem.blockchain.extensions.Result import com.tangem.common.CompletionResult +import com.tangem.common.core.TangemError +import com.tangem.common.core.TangemSdkError import com.tangem.common.extensions.guard import com.tangem.common.routing.AppRoute import com.tangem.common.routing.utils.popTo import com.tangem.core.analytics.Analytics +import com.tangem.core.analytics.models.event.OnboardingAnalyticsEvent +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.dialog.Dialogs import com.tangem.domain.common.extensions.makePrimaryWalletManager import com.tangem.domain.common.extensions.withMainContext 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.domain.wallets.legacy.asLockable +import com.tangem.sdk.extensions.localizedDescriptionRes import com.tangem.tap.common.analytics.events.Onboarding import com.tangem.tap.common.entities.ProgressState import com.tangem.tap.common.extensions.* @@ -185,8 +193,7 @@ private fun handle(action: Action, dispatch: DispatchFunction) { store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.CreateSecondWallet)) } } - is CompletionResult.Failure -> { - } + is CompletionResult.Failure -> showCardVerificationFailedDialog(result.error) } } } @@ -208,8 +215,7 @@ private fun handle(action: Action, dispatch: DispatchFunction) { store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.CreateThirdWallet)) } } - is CompletionResult.Failure -> { - } + is CompletionResult.Failure -> showCardVerificationFailedDialog(result.error) } } } @@ -218,7 +224,7 @@ private fun handle(action: Action, dispatch: DispatchFunction) { scope.launch { when (val result = manager.complete(action.message)) { - is Result.Success -> { + is CompletionResult.Success -> { Analytics.send(Onboarding.Twins.SetupFinished()) updateScanResponse(result.data) @@ -234,8 +240,7 @@ private fun handle(action: Action, dispatch: DispatchFunction) { } } } - is Result.Failure -> { - } + is CompletionResult.Failure -> showCardVerificationFailedDialog(result.error) } } } @@ -349,6 +354,29 @@ private fun handle(action: Action, dispatch: DispatchFunction) { } } +private fun showCardVerificationFailedDialog(error: TangemError) { + if (error is TangemSdkError.CardVerificationFailed) { + Analytics.send(event = OnboardingAnalyticsEvent.Onboarding.OfflineAttestationFailed) + + val resource = error.localizedDescriptionRes() + val resId = resource.resId ?: R.string.common_unknown_error + val resArgs = resource.args.map { it.value } + + store.inject(DaggerGraphState::uiMessageSender).send( + message = Dialogs.cardVerificationFailed( + errorDescription = resourceReference(id = resId, resArgs.toWrappedList()), + onRequestSupport = { + mainScope.launch { + store.inject(DaggerGraphState::sendFeedbackEmailUseCase).invoke( + type = FeedbackEmailType.CardAttestationFailed, + ) + } + }, + ), + ) + } +} + private fun getPopBackScreen(): KClass { val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt index d99a38a119..b87183595a 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt @@ -12,9 +12,14 @@ import com.tangem.common.routing.AppRoute import com.tangem.common.routing.AppRouter import com.tangem.common.services.Result import com.tangem.core.analytics.Analytics +import com.tangem.core.analytics.models.event.OnboardingAnalyticsEvent +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.toWrappedList +import com.tangem.core.ui.message.dialog.Dialogs import com.tangem.domain.common.extensions.withIOContext import com.tangem.domain.common.extensions.withMainContext import com.tangem.domain.common.util.cardTypesResolver +import com.tangem.domain.feedback.models.FeedbackEmailType import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.CardDTO.Companion.RING_BATCH_IDS import com.tangem.domain.models.scan.CardDTO.Companion.RING_BATCH_PREFIX @@ -30,6 +35,7 @@ import com.tangem.feature.wallet.presentation.wallet.domain.BackupValidator import com.tangem.operations.attestation.OnlineCardVerifier import com.tangem.operations.backup.BackupService import com.tangem.sdk.api.CreateProductWalletTaskResponse +import com.tangem.sdk.extensions.localizedDescriptionRes import com.tangem.tap.* import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Onboarding @@ -469,6 +475,25 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) val crashlytics = FirebaseCrashlytics.getInstance() when (val error = result.error) { + is TangemSdkError.CardVerificationFailed -> { + Analytics.send(event = OnboardingAnalyticsEvent.Onboarding.OfflineAttestationFailed) + + val resource = error.localizedDescriptionRes() + val resId = resource.resId ?: com.tangem.core.ui.R.string.common_unknown_error + val resArgs = resource.args.map { it.value } + + store.inject(DaggerGraphState::uiMessageSender).send( + message = Dialogs.cardVerificationFailed( + errorDescription = resourceReference(id = resId, resArgs.toWrappedList()), + onRequestSupport = { + mainScope.launch { + store.inject(DaggerGraphState::sendFeedbackEmailUseCase) + .invoke(type = FeedbackEmailType.CardAttestationFailed) + } + }, + ), + ) + } is TangemSdkError.BackupFailedNotEmptyWallets -> { store.dispatchOnMain( GlobalAction.ShowDialog( diff --git a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt index 6de664b3ce..bd75d53d4e 100644 --- a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt +++ b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt @@ -4,6 +4,7 @@ import com.tangem.blockchainsdk.BlockchainSDKFactory import com.tangem.blockchainsdk.signer.TransactionSignerFactory import com.tangem.blockchainsdk.utils.ExcludedBlockchains import com.tangem.common.routing.AppRouter +import com.tangem.core.decompose.ui.UiMessageSender import com.tangem.core.navigation.settings.SettingsManager import com.tangem.core.navigation.share.ShareManager import com.tangem.core.navigation.url.UrlOpener @@ -79,4 +80,5 @@ data class DaggerGraphState( val appPreferencesStore: AppPreferencesStore? = null, val clipboardManager: ClipboardManager? = null, val settingsManager: SettingsManager? = null, + val uiMessageSender: UiMessageSender? = null, ) : StateType \ No newline at end of file diff --git a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/event/OnboardingAnalyticsEvent.kt b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/event/OnboardingAnalyticsEvent.kt new file mode 100644 index 0000000000..454bdc1ab7 --- /dev/null +++ b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/event/OnboardingAnalyticsEvent.kt @@ -0,0 +1,18 @@ +package com.tangem.core.analytics.models.event + +import com.tangem.core.analytics.models.AnalyticsEvent + +sealed class OnboardingAnalyticsEvent( + category: String, + event: String, + params: Map = mapOf(), +) : AnalyticsEvent(category, event, params) { + + sealed class Onboarding( + event: String, + params: Map = mapOf(), + ) : OnboardingAnalyticsEvent(category = "Onboarding", event = event, params = params) { + + data object OfflineAttestationFailed : Onboarding(event = "Offline Attestation Failed") + } +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/message/dialog/Dialogs.kt b/core/ui/src/main/java/com/tangem/core/ui/message/dialog/Dialogs.kt new file mode 100644 index 0000000000..86aa88795f --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/message/dialog/Dialogs.kt @@ -0,0 +1,39 @@ +package com.tangem.core.ui.message.dialog + +import com.tangem.core.ui.R +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.message.DialogMessage +import com.tangem.core.ui.message.EventMessageAction + +/** +[REDACTED_AUTHOR] + */ +object Dialogs { + + /** + * Card verification failed dialog + * + * @param errorDescription sdk error description + * @param onRequestSupport lambda be invoked when request support action is clicked + * @param onCancelClick lambda be invoked when cancel action is clicked + */ + fun cardVerificationFailed( + errorDescription: TextReference, + onRequestSupport: () -> Unit, + onCancelClick: () -> Unit = {}, + ): DialogMessage { + return DialogMessage( + message = errorDescription, + title = resourceReference(id = R.string.security_alert_title), + isDismissable = false, + firstActionBuilder = { + EventMessageAction( + title = resourceReference(id = R.string.alert_button_request_support), + onClick = onRequestSupport, + ) + }, + secondActionBuilder = { cancelAction(onClick = onCancelClick) }, + ) + } +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/model/MultiWalletBackupModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/model/MultiWalletBackupModel.kt index 9d8fb21173..24b85a782c 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/model/MultiWalletBackupModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/model/MultiWalletBackupModel.kt @@ -5,11 +5,17 @@ import com.google.firebase.crashlytics.FirebaseCrashlytics import com.tangem.common.CompletionResult import com.tangem.common.core.TangemSdkError import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.core.analytics.models.event.OnboardingAnalyticsEvent import com.tangem.core.decompose.di.ComponentScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.core.decompose.ui.UiMessageSender import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.toWrappedList +import com.tangem.core.ui.message.dialog.Dialogs import com.tangem.domain.card.repository.CardSdkConfigRepository +import com.tangem.domain.feedback.SendFeedbackEmailUseCase +import com.tangem.domain.feedback.models.FeedbackEmailType import com.tangem.domain.models.scan.ProductType import com.tangem.features.onboarding.v2.impl.R import com.tangem.features.onboarding.v2.multiwallet.impl.analytics.OnboardingEvent @@ -21,6 +27,7 @@ import com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.ui.resetB import com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.ui.state.MultiWalletBackupUM import com.tangem.sdk.api.BackupServiceHolder import com.tangem.sdk.api.TangemSdkManager +import com.tangem.sdk.extensions.localizedDescriptionRes import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow @@ -31,13 +38,16 @@ import javax.inject.Inject @Stable @ComponentScoped +@Suppress("LongParameterList") class MultiWalletBackupModel @Inject constructor( paramsContainer: ParamsContainer, override val dispatchers: CoroutineDispatcherProvider, private val backupServiceHolder: BackupServiceHolder, private val cardSdkConfigRepository: CardSdkConfigRepository, private val tangemSdkManager: TangemSdkManager, - private val analyticsHandler: AnalyticsEventHandler, + private val analyticsEventHandler: AnalyticsEventHandler, + private val uiMessageSender: UiMessageSender, + private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase, ) : Model() { @Suppress("UnusedPrivateMember") @@ -65,10 +75,10 @@ class MultiWalletBackupModel @Inject constructor( init { // for wallet 1 this event is sent in Wallet1ChooseOptionModel if (scanResponse.productType == ProductType.Wallet2 || scanResponse.productType == ProductType.Ring) { - analyticsHandler.send(OnboardingEvent.Backup.ScreenOpened) + analyticsEventHandler.send(OnboardingEvent.Backup.ScreenOpened) } - analyticsHandler.send(OnboardingEvent.Backup.Started) + analyticsEventHandler.send(OnboardingEvent.Backup.Started) } private fun getInitState(): MultiWalletBackupUM { @@ -153,7 +163,7 @@ class MultiWalletBackupModel @Inject constructor( modelScope.launch { eventFlow.emit(MultiWalletBackupComponent.Event.Done) } - analyticsHandler.send(OnboardingEvent.Backup.Finished(cardsCount = state.value.numberOfBackupCards + 1)) + analyticsEventHandler.send(OnboardingEvent.Backup.Finished(cardsCount = state.value.numberOfBackupCards + 1)) } private fun showOnlyOneBackupWarningDialog() { @@ -203,6 +213,7 @@ class MultiWalletBackupModel @Inject constructor( } is CompletionResult.Failure -> { when (val error = result.error) { + is TangemSdkError.CardVerificationFailed -> showCardVerificationFailedDialog(error) is TangemSdkError.BackupFailedNotEmptyWallets -> { _uiState.update { st -> st.copy( @@ -212,7 +223,7 @@ class MultiWalletBackupModel @Inject constructor( _uiState.update { it.copy(dialog = null) } }, onDismissClick = { - analyticsHandler.send(OnboardingEvent.Backup.ResetCancelEvent) + analyticsEventHandler.send(OnboardingEvent.Backup.ResetCancelEvent) }, ), ) @@ -234,8 +245,27 @@ class MultiWalletBackupModel @Inject constructor( } } + private fun showCardVerificationFailedDialog(error: TangemSdkError.CardVerificationFailed) { + analyticsEventHandler.send(event = OnboardingAnalyticsEvent.Onboarding.OfflineAttestationFailed) + + val resource = error.localizedDescriptionRes() + val resId = resource.resId ?: com.tangem.core.ui.R.string.common_unknown_error + val resArgs = resource.args.map { it.value } + + uiMessageSender.send( + message = Dialogs.cardVerificationFailed( + errorDescription = resourceReference(id = resId, resArgs.toWrappedList()), + onRequestSupport = { + modelScope.launch { + sendFeedbackEmailUseCase(type = FeedbackEmailType.CardAttestationFailed) + } + }, + ), + ) + } + private fun resetBackupCard(cardId: String) { - analyticsHandler.send(OnboardingEvent.Backup.ResetPerformEvent) + analyticsEventHandler.send(OnboardingEvent.Backup.ResetPerformEvent) modelScope.launch { tangemSdkManager.resetToFactorySettings( diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 3ecbbb9cf6..a8bac2aead 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -90,7 +90,7 @@ markdownComposeView = "0.5.4" # region Tangem tangemBlockchainSdk = "release-app_5.20-908" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds -tangemCardSdk = "release-app_5.20-422" +tangemCardSdk = "release-app_5.20-423" #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 ^