From 14178e902786f1ec2e747cccf01c69f84847986c Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 4 May 2026 14:22:49 +0500 Subject: [PATCH] Updated on 2026-08-14 --- .../api/choosetoken/ChooseTokenBridge.kt | 6 +- .../choosetoken/model/ChooseTokenModel.kt | 3 +- .../model/PortfolioListBlockDelegate.kt | 1 + .../feature/swap/domain/SwapInteractorImpl.kt | 4 +- .../feature/swap/analytics/SwapEvents.kt | 38 +++++++++-- .../tangem/feature/swap/model/SwapModel.kt | 67 ++++++++++++++++--- 6 files changed, 100 insertions(+), 19 deletions(-) diff --git a/features/common-features/api/src/main/java/com/tangem/features/commonfeatures/api/choosetoken/ChooseTokenBridge.kt b/features/common-features/api/src/main/java/com/tangem/features/commonfeatures/api/choosetoken/ChooseTokenBridge.kt index 596f8564aa..b581a64908 100644 --- a/features/common-features/api/src/main/java/com/tangem/features/commonfeatures/api/choosetoken/ChooseTokenBridge.kt +++ b/features/common-features/api/src/main/java/com/tangem/features/commonfeatures/api/choosetoken/ChooseTokenBridge.kt @@ -6,8 +6,8 @@ import com.tangem.domain.models.account.AccountStatus import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId -import com.tangem.features.commonfeatures.api.choosetoken.model.ChooseTokenPortfolioFullBlockUM import com.tangem.features.commonfeatures.api.R +import com.tangem.features.commonfeatures.api.choosetoken.model.ChooseTokenPortfolioFullBlockUM import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.MutableStateFlow @@ -95,4 +95,8 @@ sealed interface ChooseTokenAnalyticsPayload { @JvmInline value class ScreensSources(val value: String) : ChooseTokenAnalyticsPayload + + @Suppress("BooleanPropertyNaming") + @JvmInline + value class IsMarketTokenSelected(val value: Boolean) : ChooseTokenAnalyticsPayload } \ No newline at end of file diff --git a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/model/ChooseTokenModel.kt b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/model/ChooseTokenModel.kt index 4687184b49..97810d1fda 100644 --- a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/model/ChooseTokenModel.kt +++ b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/model/ChooseTokenModel.kt @@ -80,11 +80,12 @@ internal class ChooseTokenModel @Inject constructor( addToPortfolioManager.onSuccessAdded.receiveAsFlow() .onEach { addedResult -> val isSearched = ChooseTokenAnalyticsPayload.IsSearched(isSearchingState) + val isMarketToken = ChooseTokenAnalyticsPayload.IsMarketTokenSelected(true) val chooseTokenResult = ChooseTokenResult( currency = addedResult.addedCurrency, account = addedResult.account, wallet = addedResult.wallet, - analyticsPayload = setOf(isSearched), + analyticsPayload = setOf(isSearched, isMarketToken), ) bridge.onCurrencyChosen(chooseTokenResult) marketBlockDelegate.addToPortfolioSlot.dismiss() diff --git a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/model/PortfolioListBlockDelegate.kt b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/model/PortfolioListBlockDelegate.kt index f2acbf62c8..8a8420567e 100644 --- a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/model/PortfolioListBlockDelegate.kt +++ b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/model/PortfolioListBlockDelegate.kt @@ -112,6 +112,7 @@ internal class PortfolioListBlockDelegate @AssistedInject constructor( private fun onTokenItemClick(wallet: UserWallet, account: AccountStatus, currencyStatus: CryptoCurrencyStatus) { val analyticsPayload = setOf( ChooseTokenAnalyticsPayload.IsSearched(searchQueryState.isSearchingState), + ChooseTokenAnalyticsPayload.IsMarketTokenSelected(false), ) val result = ChooseTokenResult( account = account, diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt index 35e943bda2..9fc28e116c 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt @@ -159,8 +159,8 @@ internal class SwapInteractorImpl @Inject constructor( return pairs.firstOrNull { pair -> pair.from.network == fromSwapCurrencyStatus.currency.network.rawId && pair.from.contractAddress == fromSwapCurrencyStatus.currency.getContractAddress() && - pair.to.network == toSwapCurrencyStatus.currency.network.rawId - pair.to.contractAddress == toSwapCurrencyStatus.currency.getContractAddress() + pair.to.network == toSwapCurrencyStatus.currency.network.rawId && + pair.to.contractAddress == toSwapCurrencyStatus.currency.getContractAddress() }?.providers.orEmpty() } diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/analytics/SwapEvents.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/analytics/SwapEvents.kt index 12d6bb07b8..04428126e9 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/analytics/SwapEvents.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/analytics/SwapEvents.kt @@ -3,16 +3,15 @@ package com.tangem.feature.swap.analytics import com.tangem.core.analytics.models.AnalyticsEvent import com.tangem.core.analytics.models.AnalyticsParam.Key.ACCOUNT_DERIVATION_FROM import com.tangem.core.analytics.models.AnalyticsParam.Key.ACCOUNT_DERIVATION_TO -import com.tangem.core.analytics.models.AnalyticsParam.Key.BLOCKCHAIN import com.tangem.core.analytics.models.AnalyticsParam.Key.ERROR_CODE import com.tangem.core.analytics.models.AnalyticsParam.Key.ERROR_MESSAGE import com.tangem.core.analytics.models.AnalyticsParam.Key.FEE_TOKEN import com.tangem.core.analytics.models.AnalyticsParam.Key.PROVIDER import com.tangem.core.analytics.models.AnalyticsParam.Key.RECEIVE_TOKEN import com.tangem.core.analytics.models.AnalyticsParam.Key.SEND_TOKEN -import com.tangem.core.analytics.models.AnalyticsParam.Key.TOKEN_PARAM import com.tangem.core.analytics.models.AppsFlyerIncludedEvent import com.tangem.core.analytics.models.getReferralParams +import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.feature.swap.domain.models.domain.SwapProvider import com.tangem.feature.swap.domain.models.ui.FeeType @@ -25,13 +24,15 @@ sealed class SwapEvents( ) : AnalyticsEvent(SWAP_CATEGORY, event, params) { class SwapScreenOpened( - val token: String, - val blockchain: String, + val fromCurrency: CryptoCurrency?, + val toCurrency: CryptoCurrency?, ) : SwapEvents( event = "Swap Screen Opened", params = mapOf( - TOKEN_PARAM to token, - BLOCKCHAIN to blockchain, + SEND_TOKEN to fromCurrency?.symbol.orEmpty(), + "Send Blockchain" to fromCurrency?.network?.name.orEmpty(), + RECEIVE_TOKEN to toCurrency?.symbol.orEmpty(), + "Receive Blockchain" to toCurrency?.network?.name.orEmpty(), ), ), AppsFlyerIncludedEvent @@ -48,6 +49,31 @@ sealed class SwapEvents( }, ) + class ChoosePopularToken( + val direction: String, + val currency: CryptoCurrency, + ) : SwapEvents( + event = "Choose popular token", + params = mapOf( + "Direction" to direction, + "Token" to currency.symbol, + "Blockchain" to currency.network.name, + ), + ) + + class PreselectedTokenChanged( + val direction: String, + val preSelectedToken: CryptoCurrency, + val selectedToken: CryptoCurrency, + ) : SwapEvents( + event = "Pre-selected token changed", + params = mapOf( + "Direction" to direction, + "Pre-selected Token" to preSelectedToken.symbol, + "Selected Token" to selectedToken.symbol, + ), + ) + class ButtonSwapClicked(val sendToken: String, val receiveToken: String) : SwapEvents( event = "Button - Swap", params = mapOf("Send Token" to sendToken, "Receive Token" to receiveToken), diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt index 7778741e7a..6755e4209a 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt @@ -227,6 +227,9 @@ internal class SwapModel @Inject constructor( private var isAmountChangedByUser: Boolean = false private var lastPermissionNotificationTokens: Pair? = null + private var preselectedFromCurrency: CryptoCurrency? = null + private var preselectedToCurrency: CryptoCurrency? = null + val approvalSlotNavigation = SlotNavigation() val approvalCallback = object : GiveApprovalComponent.Callback { @@ -275,14 +278,6 @@ internal class SwapModel @Inject constructor( initTokens() - // TODO swap analytics - analyticsEventHandler.send( - SwapEvents.SwapScreenOpened( - token = initialCryptoCurrency?.symbol.orEmpty(), - blockchain = initialCryptoCurrency?.network?.name.orEmpty(), - ), - ) - getBalanceHidingSettingsUseCase().onEach { settings -> isBalanceHidden = settings.isBalanceHidden uiState = stateBuilder.updateBalanceHiddenState(uiState, isBalanceHidden) @@ -304,9 +299,28 @@ internal class SwapModel @Inject constructor( } private fun subscribeToTokenSelection() { + fun sendAnalytics(result: ChooseTokenResult, direction: String) { + result.analyticsPayload.forEach { analyticPayload -> + when (analyticPayload) { + is ChooseTokenAnalyticsPayload.IsMarketTokenSelected -> { + if (analyticPayload.value) { + analyticsEventHandler.send( + SwapEvents.ChoosePopularToken( + direction = direction, + currency = result.currency.currency, + ), + ) + } + } + else -> Unit + } + } + } + chooseFromTokenBridge.onCurrencyChosen.receiveAsFlow() .onEach { result -> - onTokenSelect(result, isFromDirection = true) + onTokenSelect(result = result, isFromDirection = true) + sendAnalytics(result = result, direction = "From") } .launchIn(modelScope) @@ -320,6 +334,7 @@ internal class SwapModel @Inject constructor( chooseToTokenBridge.onCurrencyChosen.receiveAsFlow() .onEach { result -> onTokenSelect(result, isFromDirection = false) + sendAnalytics(result = result, direction = "To") } .launchIn(modelScope) @@ -342,6 +357,16 @@ internal class SwapModel @Inject constructor( isPaymentAccount = params.tangemPayInput != null, ) + preselectedFromCurrency = fromSwapCurrencyStatus?.currency + preselectedToCurrency = toSwapCurrencyStatus?.currency + + analyticsEventHandler.send( + SwapEvents.SwapScreenOpened( + fromCurrency = fromSwapCurrencyStatus?.currency, + toCurrency = toSwapCurrencyStatus?.currency, + ), + ) + dataState = dataState.copy( fromSwapCurrencyStatus = fromSwapCurrencyStatus, toSwapCurrencyStatus = toSwapCurrencyStatus, @@ -416,6 +441,11 @@ internal class SwapModel @Inject constructor( return } + sendPreselectedTokenChangedIfNeeded( + isFromDirection = isFromDirection, + selectedCurrency = selectedCurrencyStatus.currency, + ) + dataState = if (isFromDirection) { // Reset amount if from token is changed lastAmount.value = INITIAL_AMOUNT @@ -465,6 +495,25 @@ internal class SwapModel @Inject constructor( } } + private fun sendPreselectedTokenChangedIfNeeded(isFromDirection: Boolean, selectedCurrency: CryptoCurrency) { + val preselected = if (isFromDirection) preselectedFromCurrency else preselectedToCurrency + if (preselected == null || preselected == selectedCurrency) return + + analyticsEventHandler.send( + SwapEvents.PreselectedTokenChanged( + direction = if (isFromDirection) "From" else "To", + preSelectedToken = preselected, + selectedToken = selectedCurrency, + ), + ) + + if (isFromDirection) { + preselectedFromCurrency = null + } else { + preselectedToCurrency = null + } + } + private fun onChangeCardsClicked() { modelScope.launch { singleTaskScheduler.cancelTask()