Updated on 2026-08-14

This commit is contained in:
Tangem 2025-08-21 17:21:54 +02:00
parent b8f3d5315f
commit 54e094abac
8 changed files with 145 additions and 4 deletions

View file

@ -187,6 +187,10 @@ sealed class AnalyticsParam {
Pending(value = "Pending"),
}
enum class EnsStatus(val value: String) {
EMPTY("Empty"), FULL("Full")
}
companion object Key {
const val BLOCKCHAIN = "Blockchain"
const val TOKEN_PARAM = "Token"
@ -235,5 +239,7 @@ sealed class AnalyticsParam {
const val SEND_BLOCKCHAIN = "Send Blockchain"
const val RECEIVE_BLOCKCHAIN = "Receive Blockchain"
const val CHOSEN_TOKEN = "Token Chosen"
const val ENS = "ENS"
const val ENS_ADDRESS = "ENS Address"
}
}

View file

@ -0,0 +1,59 @@
package com.tangem.domain.tokens.model.analytics
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.AnalyticsParam.Key.BLOCKCHAIN
import com.tangem.core.analytics.models.AnalyticsParam.Key.ENS
import com.tangem.core.analytics.models.AnalyticsParam.Key.TOKEN_PARAM
sealed class TokenReceiveNewAnalyticsEvent(
event: String,
params: Map<String, String> = mapOf(),
) : AnalyticsEvent("Token / Receive", event, params) {
class ReceiveScreenOpened(
token: String,
blockchainName: String,
ensStatus: AnalyticsParam.EnsStatus,
) : TokenReceiveNewAnalyticsEvent(
event = "Receive Screen Opened",
params = mapOf(
TOKEN_PARAM to token,
BLOCKCHAIN to blockchainName,
ENS to ensStatus.value,
),
)
class ButtonCopyAddress(
token: String,
blockchainName: String,
) : TokenReceiveNewAnalyticsEvent(
event = "Button - Copy Address",
params = mapOf(
TOKEN_PARAM to token,
BLOCKCHAIN to blockchainName,
),
)
class ButtonCopyEns(
token: String,
blockchainName: String,
) : TokenReceiveNewAnalyticsEvent(
event = "Button - ENS",
params = mapOf(
TOKEN_PARAM to token,
BLOCKCHAIN to blockchainName,
),
)
class QrScreenOpened(
token: String,
blockchainName: String,
) : TokenReceiveNewAnalyticsEvent(
event = "QR Screen Opened",
params = mapOf(
TOKEN_PARAM to token,
BLOCKCHAIN to blockchainName,
),
)
}

View file

@ -3,6 +3,7 @@ package com.tangem.features.send.v2.send.analytics
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.AnalyticsParam.Key.BLOCKCHAIN
import com.tangem.core.analytics.models.AnalyticsParam.Key.ENS_ADDRESS
import com.tangem.core.analytics.models.AnalyticsParam.Key.FEE_TYPE
import com.tangem.core.analytics.models.AnalyticsParam.Key.NONCE
import com.tangem.core.analytics.models.AnalyticsParam.Key.TOKEN_PARAM
@ -23,6 +24,7 @@ internal sealed class SendAnalyticEvents(
val feeType: AnalyticsParam.FeeType,
val blockchain: String,
val nonceNotEmpty: Boolean,
private val ensStatus: AnalyticsParam.EnsStatus,
) : SendAnalyticEvents(
event = "Transaction Sent Screen Opened",
params = mapOf(
@ -30,6 +32,10 @@ internal sealed class SendAnalyticEvents(
FEE_TYPE to feeType.value,
BLOCKCHAIN to blockchain,
NONCE to nonceNotEmpty.toString().capitalize(),
ENS_ADDRESS to when (ensStatus) {
AnalyticsParam.EnsStatus.EMPTY -> false.toString()
AnalyticsParam.EnsStatus.FULL -> true.toString()
},
),
)

View file

@ -28,6 +28,7 @@ internal class SendAnalyticHelper @Inject constructor(
feeType = feeType,
blockchain = cryptoCurrency.network.name,
nonceNotEmpty = feeSelectorUM.nonce != null,
ensStatus = getEnsStatus(sendUM),
),
)
analyticsEventHandler.send(
@ -52,4 +53,14 @@ internal class SendAnalyticHelper @Inject constructor(
else -> Basic.TransactionSent.MemoType.Null
}
}
private fun getEnsStatus(sendUM: SendUM): AnalyticsParam.EnsStatus {
val blockchainAddressForEns =
(sendUM.destinationUM as? DestinationUM.Content)?.addressTextField?.blockchainAddress
return if (blockchainAddressForEns != null) {
AnalyticsParam.EnsStatus.FULL
} else {
AnalyticsParam.EnsStatus.EMPTY
}
}
}

View file

@ -93,6 +93,7 @@ internal class DefaultTokenReceiveComponent @AssistedInject constructor(
notificationConfigs = model.state.value.notificationConfigs,
showMemoDisclaimer = model.params.config.showMemoDisclaimer,
fullName = model.params.config.cryptoCurrency.network.name,
tokenName = model.getTokenName(),
),
)
TokenReceiveRoutes.Warning -> TokenReceiveWarningComponent(

View file

@ -39,5 +39,6 @@ internal class TokenReceiveAssetsComponent(
val onDismiss: () -> Unit,
val showMemoDisclaimer: Boolean,
val fullName: String,
val tokenName: String,
)
}

View file

@ -1,10 +1,14 @@
package com.tangem.features.tokenreceive.model
import androidx.compose.runtime.Stable
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.domain.tokens.model.analytics.TokenReceiveNewAnalyticsEvent
import com.tangem.features.tokenreceive.component.TokenReceiveAssetsComponent
import com.tangem.features.tokenreceive.entity.ReceiveAddress
import com.tangem.features.tokenreceive.ui.state.ReceiveAssetsUM
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.MutableStateFlow
@ -16,10 +20,21 @@ import javax.inject.Inject
internal class TokenReceiveAssetsModel @Inject constructor(
paramsContainer: ParamsContainer,
override val dispatchers: CoroutineDispatcherProvider,
analyticsEventHandler: AnalyticsEventHandler,
) : Model() {
private val params = paramsContainer.require<TokenReceiveAssetsComponent.TokenReceiveAssetsParams>()
init {
analyticsEventHandler.send(
TokenReceiveNewAnalyticsEvent.ReceiveScreenOpened(
token = params.tokenName,
blockchainName = params.fullName,
ensStatus = configureEnsStatus(),
),
)
}
internal val state: StateFlow<ReceiveAssetsUM>
field = MutableStateFlow<ReceiveAssetsUM>(
ReceiveAssetsUM(
@ -32,4 +47,13 @@ internal class TokenReceiveAssetsModel @Inject constructor(
fullName = params.fullName,
),
)
private fun configureEnsStatus(): AnalyticsParam.EnsStatus {
val hasEnsAddress = params.addresses.values.any { it.type == ReceiveAddress.Type.Ens }
return if (hasEnsAddress) {
AnalyticsParam.EnsStatus.FULL
} else {
AnalyticsParam.EnsStatus.EMPTY
}
}
}

View file

@ -3,6 +3,7 @@ package com.tangem.features.tokenreceive.model
import com.arkivanov.decompose.router.stack.StackNavigation
import com.arkivanov.decompose.router.stack.push
import com.tangem.common.ui.notifications.NotificationUM
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
@ -19,6 +20,7 @@ import com.tangem.domain.models.ReceiveAddressModel
import com.tangem.domain.models.ens.EnsAddress
import com.tangem.domain.models.network.Network
import com.tangem.domain.tokens.SaveViewedTokenReceiveWarningUseCase
import com.tangem.domain.tokens.model.analytics.TokenReceiveNewAnalyticsEvent
import com.tangem.domain.transaction.usecase.GetReverseResolvedEnsAddressUseCase
import com.tangem.features.tokenreceive.TokenReceiveComponent
import com.tangem.features.tokenreceive.component.TokenReceiveModelCallback
@ -35,6 +37,7 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import javax.inject.Inject
@Suppress("LongParameterList")
@ModelScoped
internal class TokenReceiveModel @Inject constructor(
private val clipboardManager: ClipboardManager,
@ -43,6 +46,7 @@ internal class TokenReceiveModel @Inject constructor(
private val saveViewedTokenReceiveWarningUseCase: SaveViewedTokenReceiveWarningUseCase,
paramsContainer: ParamsContainer,
override val dispatchers: CoroutineDispatcherProvider,
private val analyticsEventHandler: AnalyticsEventHandler,
) : Model(), TokenReceiveModelCallback {
private val iconStateConverter by lazy(::CryptoCurrencyToIconStateConverter)
@ -61,11 +65,18 @@ internal class TokenReceiveModel @Inject constructor(
}
override fun onQrCodeClick(id: Int) {
analyticsEventHandler.send(
TokenReceiveNewAnalyticsEvent.QrScreenOpened(
token = getTokenName(),
blockchainName = params.config.cryptoCurrency.network.name,
),
)
stackNavigation.push(configuration = TokenReceiveRoutes.QrCode(addressId = id))
}
override fun onCopyClick(id: Int) {
val addressToCopy = state.value.addresses[id] ?: return
sendCopyActionAnalytic(addressToCopy)
clipboardManager.setText(text = addressToCopy.value, isSensitive = true)
}
@ -85,6 +96,13 @@ internal class TokenReceiveModel @Inject constructor(
}
}
internal fun getTokenName(): String {
return when (val asset = params.config.asset) {
Asset.Currency -> params.config.cryptoCurrency.symbol
Asset.NFT -> asset.name
}
}
private fun mapAddresses(addresses: List<ReceiveAddressModel>): ImmutableMap<Int, ReceiveAddress> {
return buildMap {
addresses.mapIndexed { index, model ->
@ -151,10 +169,7 @@ internal class TokenReceiveModel @Inject constructor(
title = resourceReference(
R.string.receive_bottom_sheet_warning_title,
wrappedList(
when (val asset = params.config.asset) {
Asset.Currency -> params.config.cryptoCurrency.symbol
Asset.NFT -> asset.name
},
getTokenName(),
params.config.cryptoCurrency.network.name,
),
),
@ -183,4 +198,22 @@ internal class TokenReceiveModel @Inject constructor(
notificationConfigs = getNotifications().toImmutableList(),
)
}
private fun sendCopyActionAnalytic(receiveAddress: ReceiveAddress) {
val event = when (receiveAddress.type) {
is ReceiveAddress.Type.Default -> {
TokenReceiveNewAnalyticsEvent.ButtonCopyAddress(
token = getTokenName(),
blockchainName = params.config.cryptoCurrency.network.name,
)
}
ReceiveAddress.Type.Ens -> {
TokenReceiveNewAnalyticsEvent.ButtonCopyEns(
token = getTokenName(),
blockchainName = params.config.cryptoCurrency.network.name,
)
}
}
analyticsEventHandler.send(event)
}
}