Updated on 2026-08-14

This commit is contained in:
Tangem 2026-05-04 14:22:49 +05:00
parent a99a1b370a
commit 14178e9027
6 changed files with 100 additions and 19 deletions

View file

@ -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
}

View file

@ -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()

View file

@ -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,

View file

@ -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()
}

View file

@ -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),

View file

@ -227,6 +227,9 @@ internal class SwapModel @Inject constructor(
private var isAmountChangedByUser: Boolean = false
private var lastPermissionNotificationTokens: Pair<String, String>? = null
private var preselectedFromCurrency: CryptoCurrency? = null
private var preselectedToCurrency: CryptoCurrency? = null
val approvalSlotNavigation = SlotNavigation<Unit>()
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()