diff --git a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt index f33cf4b1af..6d8ea8f746 100644 --- a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt +++ b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt @@ -194,6 +194,12 @@ internal class ChildFactory @Inject constructor( token = route.token, appCurrency = route.appCurrency, showPortfolio = route.showPortfolio, + analyticsParams = route.analyticsParams?.let { + MarketsTokenDetailsComponent.AnalyticsParams( + blockchain = it.blockchain, + source = it.source, + ) + }, ), componentFactory = marketsTokenDetailsComponentFactory, ) diff --git a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt index 2362e15686..0d8cee2df5 100644 --- a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt +++ b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt @@ -272,5 +272,13 @@ sealed class AppRoute(val path: String) : Route { val token: TokenMarketParams, val appCurrency: AppCurrency, val showPortfolio: Boolean, - ) : AppRoute(path = "/markets_token_details/${token.id}/$showPortfolio") + val analyticsParams: AnalyticsParams? = null, + ) : AppRoute(path = "/markets_token_details/${token.id}/$showPortfolio") { + + @Serializable + data class AnalyticsParams( + val blockchain: String?, + val source: String, + ) + } } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/markets/models/response/TokenMarketChartResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/markets/models/response/TokenMarketChartResponse.kt index 777bbc1228..fbe921d20b 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/markets/models/response/TokenMarketChartResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/markets/models/response/TokenMarketChartResponse.kt @@ -1,8 +1,10 @@ package com.tangem.datasource.api.markets.models.response import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass import java.math.BigDecimal +@JsonClass(generateAdapter = true) data class TokenMarketChartResponse( @Json(name = "prices") val prices: Map, diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/markets/models/response/TokenMarketInfoResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/markets/models/response/TokenMarketInfoResponse.kt index 13e4a315e1..68c54776fa 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/markets/models/response/TokenMarketInfoResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/markets/models/response/TokenMarketInfoResponse.kt @@ -1,8 +1,10 @@ package com.tangem.datasource.api.markets.models.response import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass import java.math.BigDecimal +@JsonClass(generateAdapter = true) data class TokenMarketInfoResponse( @Json(name = "id") val id: String, @@ -30,6 +32,7 @@ data class TokenMarketInfoResponse( val pricePerformance: PricePerformance?, ) { + @JsonClass(generateAdapter = true) data class PriceChangePercentage( @Json(name = "24h") val day: BigDecimal?, @@ -47,6 +50,7 @@ data class TokenMarketInfoResponse( val allTime: BigDecimal?, ) + @JsonClass(generateAdapter = true) data class Network( @Json(name = "network_id") val networkId: String, @@ -58,6 +62,7 @@ data class TokenMarketInfoResponse( val decimalCount: Int?, ) + @JsonClass(generateAdapter = true) data class Insights( @Json(name = "holders_change") val holdersChange: Change?, @@ -70,6 +75,7 @@ data class TokenMarketInfoResponse( @Json(name = "networks") val sourceNetworks: List?, ) { + @JsonClass(generateAdapter = true) data class SourceNetwork( @Json(name = "network_id") val id: String, @@ -78,6 +84,7 @@ data class TokenMarketInfoResponse( ) } + @JsonClass(generateAdapter = true) data class Change( @Json(name = "24h") val day: BigDecimal?, @@ -87,6 +94,7 @@ data class TokenMarketInfoResponse( val month: BigDecimal?, ) + @JsonClass(generateAdapter = true) data class Metrics( @Json(name = "market_rating") val marketRating: Int?, @@ -102,6 +110,7 @@ data class TokenMarketInfoResponse( val fullyDilutedValuation: BigDecimal?, ) + @JsonClass(generateAdapter = true) data class Links( @Json(name = "official_links") val officialLinks: List? = null, @@ -113,6 +122,7 @@ data class TokenMarketInfoResponse( val blockchainSite: List? = null, ) + @JsonClass(generateAdapter = true) data class Link( @Json(name = "title") val title: String, @@ -122,6 +132,7 @@ data class TokenMarketInfoResponse( val link: String, ) + @JsonClass(generateAdapter = true) data class PricePerformance( @Json(name = "24h") val day: Range?, @@ -131,6 +142,7 @@ data class TokenMarketInfoResponse( val allTime: Range?, ) + @JsonClass(generateAdapter = true) data class Range( @Json(name = "low_price") val low: BigDecimal?, diff --git a/data/markets/build.gradle.kts b/data/markets/build.gradle.kts index 7e23136300..5378b4b964 100644 --- a/data/markets/build.gradle.kts +++ b/data/markets/build.gradle.kts @@ -14,6 +14,8 @@ dependencies { implementation(projects.core.datasource) implementation(projects.core.utils) implementation(projects.core.pagination) + implementation(projects.core.analytics) + implementation(projects.core.analytics.models) implementation(projects.domain.legacy) implementation(projects.domain.markets) diff --git a/data/markets/src/main/java/com/tangem/data/markets/DefaultMarketsTokenRepository.kt b/data/markets/src/main/java/com/tangem/data/markets/DefaultMarketsTokenRepository.kt index cd5b9ca9fc..39637200e5 100644 --- a/data/markets/src/main/java/com/tangem/data/markets/DefaultMarketsTokenRepository.kt +++ b/data/markets/src/main/java/com/tangem/data/markets/DefaultMarketsTokenRepository.kt @@ -4,13 +4,16 @@ import com.tangem.blockchain.common.Blockchain import com.tangem.blockchainsdk.compatibility.applyL2Compatibility import com.tangem.blockchainsdk.compatibility.getTokenIdIfL2Network import com.tangem.blockchainsdk.utils.fromNetworkId +import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.data.common.currency.CryptoCurrencyFactory import com.tangem.data.common.currency.getNetwork import com.tangem.data.common.utils.retryOnError +import com.tangem.data.markets.analytics.MarketsDataAnalyticsEvent import com.tangem.data.markets.converters.TokenChartConverter import com.tangem.data.markets.converters.TokenMarketInfoConverter import com.tangem.data.markets.converters.TokenMarketListConverter import com.tangem.data.markets.converters.toRequestParam +import com.tangem.datasource.api.common.response.ApiResponseError import com.tangem.datasource.api.common.response.getOrThrow import com.tangem.datasource.api.markets.TangemTechMarketsApi import com.tangem.datasource.api.tangemTech.TangemTechApi @@ -31,6 +34,7 @@ internal class DefaultMarketsTokenRepository( private val tangemTechApi: TangemTechApi, private val userWalletsStore: UserWalletsStore, private val dispatcherProvider: CoroutineDispatcherProvider, + private val analyticsEventHandler: AnalyticsEventHandler, ) : MarketsTokenRepository { private fun createTokenMarketsFetcher(firstBatchSize: Int, nextBatchSize: Int) = LimitOffsetBatchFetcher( @@ -62,10 +66,14 @@ internal class DefaultMarketsTokenRepository( // we shouldn't infinitely retry on the first batch request val res = if (isFirstBatchFetching) { - requestCall() + catchApiErrorAndSendEvent(errorEvent = MarketsDataAnalyticsEvent.List.Error) { + requestCall() + } } else { retryOnError(priority = true) { - requestCall() + catchApiErrorAndSendEvent(errorEvent = MarketsDataAnalyticsEvent.List.Error) { + requestCall() + } } } @@ -92,6 +100,9 @@ internal class DefaultMarketsTokenRepository( val tokenMarketsUpdateFetcher = MarketsBatchUpdateFetcher( tangemTechApi = tangemTechApi, marketsApi = marketsApi, + onApiError = { + analyticsEventHandler.send(MarketsDataAnalyticsEvent.List.Error.toEvent()) + }, ) val atomicInteger = AtomicInteger(0) @@ -109,6 +120,7 @@ internal class DefaultMarketsTokenRepository( fiatCurrencyCode: String, interval: PriceChangeInterval, tokenId: String, + tokenSymbol: String, ): TokenChart { val mappedTokenId = getTokenIdIfL2Network(tokenId) val response = marketsApi.getCoinChart( @@ -117,13 +129,23 @@ internal class DefaultMarketsTokenRepository( interval = interval.toRequestParam(), ) - return TokenChartConverter.convert(interval, response.getOrThrow()) + val result = catchApiErrorAndSendEvent( + errorEvent = MarketsDataAnalyticsEvent.Details.Error( + request = MarketsDataAnalyticsEvent.Details.Error.Request.Chart, + tokenSymbol = tokenSymbol, + ), + ) { + response.getOrThrow() + } + + return TokenChartConverter.convert(interval, result) } override suspend fun getChartPreview( fiatCurrencyCode: String, interval: PriceChangeInterval, tokenId: String, + tokenSymbol: String, ): TokenChart { val mappedTokenId = getTokenIdIfL2Network(tokenId) val response = marketsApi.getCoinsListCharts( @@ -132,7 +154,11 @@ internal class DefaultMarketsTokenRepository( interval = interval.toRequestParam(), ) - val chart = response.getOrThrow()[mappedTokenId] ?: error("No chart preview data for token $mappedTokenId") + val chart = catchApiErrorAndSendEvent(errorEvent = MarketsDataAnalyticsEvent.List.Error) { + response.getOrThrow()[mappedTokenId] ?: error( + "No chart preview data for the token $mappedTokenId", + ) + } return TokenChartConverter.convert(interval, chart) } @@ -140,6 +166,7 @@ internal class DefaultMarketsTokenRepository( override suspend fun getTokenInfo( fiatCurrencyCode: String, tokenId: String, + tokenSymbol: String, languageCode: String, ): TokenMarketInfo { val response = marketsApi.getCoinMarketData( @@ -148,12 +175,22 @@ internal class DefaultMarketsTokenRepository( language = languageCode, ) - val resultResponse = response.getOrThrow().applyL2Compatibility(tokenId) + val result = catchApiErrorAndSendEvent( + errorEvent = MarketsDataAnalyticsEvent.Details.Error( + request = MarketsDataAnalyticsEvent.Details.Error.Request.Info, + tokenSymbol = tokenSymbol, + ), + ) { + response.getOrThrow() + } + + val resultResponse = result.applyL2Compatibility(tokenId) return TokenMarketInfoConverter.convert(resultResponse) } override suspend fun getTokenQuotes(fiatCurrencyCode: String, tokenId: String): TokenQuotes { // TODO change method when backend is ready + // add error analytics event val response = marketsApi.getCoinMarketData( currency = fiatCurrencyCode, coinId = tokenId, @@ -194,4 +231,16 @@ internal class DefaultMarketsTokenRepository( ) } } + + private inline fun catchApiErrorAndSendEvent(errorEvent: MarketsDataAnalyticsEvent, block: () -> T): T { + return try { + block() + } catch (e: ApiResponseError.HttpException) { + analyticsEventHandler.send(errorEvent.toEvent()) + throw e + } catch (e: ApiResponseError.TimeoutException) { + analyticsEventHandler.send(errorEvent.toEvent()) + throw e + } + } } \ No newline at end of file diff --git a/data/markets/src/main/java/com/tangem/data/markets/MarketsBatchUpdateFetcher.kt b/data/markets/src/main/java/com/tangem/data/markets/MarketsBatchUpdateFetcher.kt index 3a377ab901..95fb2cca25 100644 --- a/data/markets/src/main/java/com/tangem/data/markets/MarketsBatchUpdateFetcher.kt +++ b/data/markets/src/main/java/com/tangem/data/markets/MarketsBatchUpdateFetcher.kt @@ -4,6 +4,7 @@ import com.tangem.data.common.utils.retryOnError import com.tangem.data.markets.converters.TokenMarketChartsConverter import com.tangem.data.markets.converters.TokenQuotesShortConverter import com.tangem.data.markets.converters.toRequestParam +import com.tangem.datasource.api.common.response.ApiResponseError import com.tangem.datasource.api.common.response.getOrThrow import com.tangem.datasource.api.markets.TangemTechMarketsApi import com.tangem.datasource.api.markets.models.response.TokenMarketChartListResponse @@ -20,6 +21,7 @@ import kotlinx.coroutines.launch internal class MarketsBatchUpdateFetcher( private val marketsApi: TangemTechMarketsApi, private val tangemTechApi: TangemTechApi, + private val onApiError: () -> Unit, ) : BatchUpdateFetcher, TokenMarketUpdateRequest> { override suspend fun BatchUpdateFetcher.UpdateContext>.fetchUpdateAsync( @@ -35,11 +37,13 @@ internal class MarketsBatchUpdateFetcher( val updateTasks = idsToUpdate.map { batchIds -> async { retryOnError { - marketsApi.getCoinsListCharts( - coinIds = batchIds.second.joinToString(separator = ","), - interval = updateRequest.interval.toRequestParam(), - currency = updateRequest.currency, - ).getOrThrow() + catchApiError(onApiError) { + marketsApi.getCoinsListCharts( + coinIds = batchIds.second.joinToString(separator = ","), + interval = updateRequest.interval.toRequestParam(), + currency = updateRequest.currency, + ).getOrThrow() + } } } } @@ -62,11 +66,13 @@ internal class MarketsBatchUpdateFetcher( } is TokenMarketUpdateRequest.UpdateQuotes -> { val quotesRes = retryOnError { - tangemTechApi.getQuotes( - currencyId = updateRequest.currencyId, - coinIds = idsToUpdate.map { it.second }.flatten().joinToString(separator = ","), - fields = quoteFields.joinToString(separator = ","), - ).getOrThrow() + catchApiError(onApiError) { + tangemTechApi.getQuotes( + currencyId = updateRequest.currencyId, + coinIds = idsToUpdate.map { it.second }.flatten().joinToString(separator = ","), + fields = quoteFields.joinToString(separator = ","), + ).getOrThrow() + } } update { @@ -106,6 +112,15 @@ internal class MarketsBatchUpdateFetcher( ) } + private inline fun catchApiError(onError: () -> Unit, block: () -> T): T { + return try { + block() + } catch (e: ApiResponseError) { + onError() + throw e + } + } + companion object { private val quoteFields = listOf( "price", diff --git a/data/markets/src/main/java/com/tangem/data/markets/analytics/MarketsDataAnalyticsEvent.kt b/data/markets/src/main/java/com/tangem/data/markets/analytics/MarketsDataAnalyticsEvent.kt new file mode 100644 index 0000000000..b58f04cb33 --- /dev/null +++ b/data/markets/src/main/java/com/tangem/data/markets/analytics/MarketsDataAnalyticsEvent.kt @@ -0,0 +1,42 @@ +package com.tangem.data.markets.analytics + +import com.tangem.core.analytics.models.AnalyticsEvent + +sealed interface MarketsDataAnalyticsEvent { + + sealed class List( + event: String, + params: Map = mapOf(), + ) : AnalyticsEvent(category = "Markets", event = event, params = params), MarketsDataAnalyticsEvent { + + data object Error : List(event = "Data Error") + } + + sealed class Details( + event: String, + params: Map = mapOf(), + ) : AnalyticsEvent(category = "Markets / Chart", event = event, params = params), MarketsDataAnalyticsEvent { + + data class Error( + val request: Request, + val tokenSymbol: String, + ) : Details( + event = "Data Error", + params = mapOf( + "Source" to request.source, + "Token" to tokenSymbol, + ), + ) { + + enum class Request(val source: String) { + Chart("Chart"), + Info("Blocks"), + } + } + } + + fun toEvent(): AnalyticsEvent = when (this) { + is List -> this + is Details -> this + } +} \ No newline at end of file diff --git a/data/markets/src/main/java/com/tangem/data/markets/di/MarketsDataModule.kt b/data/markets/src/main/java/com/tangem/data/markets/di/MarketsDataModule.kt index 03cb704bd6..4ef2f1e094 100644 --- a/data/markets/src/main/java/com/tangem/data/markets/di/MarketsDataModule.kt +++ b/data/markets/src/main/java/com/tangem/data/markets/di/MarketsDataModule.kt @@ -1,5 +1,6 @@ package com.tangem.data.markets.di +import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.data.markets.DefaultMarketsTokenRepository import com.tangem.datasource.api.markets.TangemTechMarketsApi import com.tangem.datasource.api.tangemTech.TangemTechApi @@ -24,12 +25,14 @@ internal object MarketsDataModule { @DevTangemApi tangemTechApi: TangemTechApi, userWalletsStore: UserWalletsStore, dispatchers: CoroutineDispatcherProvider, + analyticsEventHandler: AnalyticsEventHandler, ): MarketsTokenRepository { return DefaultMarketsTokenRepository( marketsApi = marketsApi, tangemTechApi = tangemTechApi, dispatcherProvider = dispatchers, userWalletsStore = userWalletsStore, + analyticsEventHandler = analyticsEventHandler, ) } } \ No newline at end of file diff --git a/domain/markets/src/main/java/com/tangem/domain/markets/GetTokenMarketInfoUseCase.kt b/domain/markets/src/main/java/com/tangem/domain/markets/GetTokenMarketInfoUseCase.kt index c7935cb4e0..2038181eef 100644 --- a/domain/markets/src/main/java/com/tangem/domain/markets/GetTokenMarketInfoUseCase.kt +++ b/domain/markets/src/main/java/com/tangem/domain/markets/GetTokenMarketInfoUseCase.kt @@ -9,11 +9,16 @@ class GetTokenMarketInfoUseCase( private val marketsTokenRepository: MarketsTokenRepository, ) { - suspend operator fun invoke(appCurrency: AppCurrency, tokenId: String): Either { + suspend operator fun invoke( + appCurrency: AppCurrency, + tokenId: String, + tokenSymbol: String, + ): Either { return Either.catch { marketsTokenRepository.getTokenInfo( fiatCurrencyCode = appCurrency.code, tokenId = tokenId, + tokenSymbol = tokenSymbol, languageCode = SupportedLanguages.getCurrentSupportedLanguageCode(), ) }.mapLeft {} diff --git a/domain/markets/src/main/java/com/tangem/domain/markets/GetTokenPriceChartUseCase.kt b/domain/markets/src/main/java/com/tangem/domain/markets/GetTokenPriceChartUseCase.kt index feeeadab56..c6d677f34b 100644 --- a/domain/markets/src/main/java/com/tangem/domain/markets/GetTokenPriceChartUseCase.kt +++ b/domain/markets/src/main/java/com/tangem/domain/markets/GetTokenPriceChartUseCase.kt @@ -12,6 +12,7 @@ class GetTokenPriceChartUseCase( appCurrency: AppCurrency, interval: PriceChangeInterval, tokenId: String, + tokenSymbol: String, preview: Boolean, ): Either { return Either.catch { @@ -20,12 +21,14 @@ class GetTokenPriceChartUseCase( fiatCurrencyCode = appCurrency.code, interval = interval, tokenId = tokenId, + tokenSymbol = tokenSymbol, ) } else { marketsTokenRepository.getChart( fiatCurrencyCode = appCurrency.code, interval = interval, tokenId = tokenId, + tokenSymbol = tokenSymbol, ) } }.mapLeft {} diff --git a/domain/markets/src/main/java/com/tangem/domain/markets/repositories/MarketsTokenRepository.kt b/domain/markets/src/main/java/com/tangem/domain/markets/repositories/MarketsTokenRepository.kt index 1e40c55a13..c9ff5dae64 100644 --- a/domain/markets/src/main/java/com/tangem/domain/markets/repositories/MarketsTokenRepository.kt +++ b/domain/markets/src/main/java/com/tangem/domain/markets/repositories/MarketsTokenRepository.kt @@ -12,11 +12,26 @@ interface MarketsTokenRepository { nextBatchSize: Int, ): TokenListBatchFlow - suspend fun getChart(fiatCurrencyCode: String, interval: PriceChangeInterval, tokenId: String): TokenChart + suspend fun getChart( + fiatCurrencyCode: String, + interval: PriceChangeInterval, + tokenId: String, + tokenSymbol: String, + ): TokenChart - suspend fun getChartPreview(fiatCurrencyCode: String, interval: PriceChangeInterval, tokenId: String): TokenChart + suspend fun getChartPreview( + fiatCurrencyCode: String, + interval: PriceChangeInterval, + tokenId: String, + tokenSymbol: String, + ): TokenChart - suspend fun getTokenInfo(fiatCurrencyCode: String, tokenId: String, languageCode: String): TokenMarketInfo + suspend fun getTokenInfo( + fiatCurrencyCode: String, + tokenId: String, + tokenSymbol: String, + languageCode: String, + ): TokenMarketInfo suspend fun getTokenQuotes(fiatCurrencyCode: String, tokenId: String): TokenQuotes diff --git a/features/markets/api/src/main/kotlin/com/tangem/features/markets/details/MarketsTokenDetailsComponent.kt b/features/markets/api/src/main/kotlin/com/tangem/features/markets/details/MarketsTokenDetailsComponent.kt index 3a4b5e4b57..81ecc42dcc 100644 --- a/features/markets/api/src/main/kotlin/com/tangem/features/markets/details/MarketsTokenDetailsComponent.kt +++ b/features/markets/api/src/main/kotlin/com/tangem/features/markets/details/MarketsTokenDetailsComponent.kt @@ -20,6 +20,13 @@ interface MarketsTokenDetailsComponent : ComposableContentComponent { val token: TokenMarketParams, val appCurrency: AppCurrency, val showPortfolio: Boolean, + val analyticsParams: AnalyticsParams?, + ) + + @Serializable + data class AnalyticsParams( + val blockchain: String?, + val source: String, ) @Composable diff --git a/features/markets/api/src/main/kotlin/com/tangem/features/markets/token/block/TokenMarketBlockComponent.kt b/features/markets/api/src/main/kotlin/com/tangem/features/markets/token/block/TokenMarketBlockComponent.kt index 9cc202bdb4..270528b0c7 100644 --- a/features/markets/api/src/main/kotlin/com/tangem/features/markets/token/block/TokenMarketBlockComponent.kt +++ b/features/markets/api/src/main/kotlin/com/tangem/features/markets/token/block/TokenMarketBlockComponent.kt @@ -11,11 +11,7 @@ interface TokenMarketBlockComponent : ComposableContentComponent { @Serializable data class Params( - val cryptoCurrencyID: CryptoCurrency.ID, - val tokenId: String, - val tokenName: String, - val tokenSymbol: String, - val tokenImageUrl: String?, + val cryptoCurrency: CryptoCurrency, ) interface Factory { diff --git a/features/markets/impl/build.gradle.kts b/features/markets/impl/build.gradle.kts index a6e62fdf7c..bd7451e801 100644 --- a/features/markets/impl/build.gradle.kts +++ b/features/markets/impl/build.gradle.kts @@ -63,6 +63,8 @@ dependencies { implementation(projects.core.decompose) implementation(projects.core.ui) implementation(projects.core.featuretoggles) + implementation(projects.core.analytics) + implementation(projects.core.analytics.models) /* Common */ implementation(projects.common.ui) diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/DefaultMarketsTokenDetailsComponent.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/DefaultMarketsTokenDetailsComponent.kt index d78eb7c19f..681d36de1b 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/DefaultMarketsTokenDetailsComponent.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/DefaultMarketsTokenDetailsComponent.kt @@ -7,6 +7,7 @@ import androidx.compose.ui.unit.Dp import androidx.lifecycle.compose.LifecycleStartEffect import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.tangem.blockchainsdk.compatibility.getTokenIdIfL2Network +import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.context.child import com.tangem.core.decompose.model.getOrCreateModel @@ -14,6 +15,7 @@ import com.tangem.core.ui.res.LocalMainBottomSheetColor import com.tangem.core.ui.res.TangemTheme import com.tangem.features.markets.details.MarketsTokenDetailsComponent import com.tangem.features.markets.details.MarketsTokenDetailsComponent.Params +import com.tangem.features.markets.details.impl.analytics.MarketDetailsAnalyticsEvent import com.tangem.features.markets.details.impl.model.MarketsTokenDetailsModel import com.tangem.features.markets.details.impl.model.state.TokenNetworksState import com.tangem.features.markets.details.impl.ui.MarketsTokenDetailsContent @@ -29,6 +31,7 @@ import kotlinx.coroutines.launch internal class DefaultMarketsTokenDetailsComponent @AssistedInject constructor( @Assisted appComponentContext: AppComponentContext, @Assisted params: Params, + analyticsEventHandler: AnalyticsEventHandler, portfolioComponentFactory: MarketsPortfolioComponent.Factory, ) : AppComponentContext by appComponentContext, MarketsTokenDetailsComponent { @@ -38,12 +41,17 @@ internal class DefaultMarketsTokenDetailsComponent @AssistedInject constructor( id = getTokenIdIfL2Network(params.token.id), ), ) + private val analyticsParams = params.analyticsParams + private val model: MarketsTokenDetailsModel = getOrCreateModel(updatedParams) private val portfolioComponent: MarketsPortfolioComponent? = if (updatedParams.showPortfolio) { portfolioComponentFactory.create( context = child("my_portfolio"), - params = MarketsPortfolioComponent.Params(updatedParams.token), + params = MarketsPortfolioComponent.Params( + updatedParams.token, + analyticsParams = analyticsParams?.source?.let { MarketsPortfolioComponent.AnalyticsParams(it) }, + ), ) } else { null @@ -59,6 +67,18 @@ internal class DefaultMarketsTokenDetailsComponent @AssistedInject constructor( } } } + + // === Analytics === + if (analyticsParams != null) { + analyticsEventHandler.send( + MarketDetailsAnalyticsEvent.EventBuilder( + token = params.token, + ).screenOpened( + blockchain = analyticsParams.blockchain, + source = analyticsParams.source, + ), + ) + } } @Composable diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/analytics/MarketDetailsAnalyticsEvent.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/analytics/MarketDetailsAnalyticsEvent.kt new file mode 100644 index 0000000000..545104f625 --- /dev/null +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/analytics/MarketDetailsAnalyticsEvent.kt @@ -0,0 +1,64 @@ +package com.tangem.features.markets.details.impl.analytics + +import com.tangem.core.analytics.models.AnalyticsEvent +import com.tangem.domain.markets.PriceChangeInterval +import com.tangem.domain.markets.TokenMarketParams + +internal class MarketDetailsAnalyticsEvent( + event: String, + params: Map = mapOf(), +) : AnalyticsEvent(category = "Markets / Chart", event = event, params = params) { + + data class EventBuilder( + val token: TokenMarketParams, + ) { + fun screenOpened(blockchain: String?, source: String) = MarketDetailsAnalyticsEvent( + event = "Token Chart Screen Opened", + params = buildMap { + put("Token", token.symbol) + blockchain?.let { put("blockchain", it) } + put("Source", source) + }, + ) + + fun intervalChanged(intervalType: IntervalType, interval: PriceChangeInterval) = MarketDetailsAnalyticsEvent( + event = "Button - Period", + params = mapOf( + "Token" to token.symbol, + "Period" to interval.toAnalyticsString(), + "Source" to intervalType.source, + ), + ) + + fun readMoreClicked() = MarketDetailsAnalyticsEvent( + event = "Button - Read More", + params = mapOf( + "Token" to token.symbol, + ), + ) + + fun linkClicked(linkTitle: String) = MarketDetailsAnalyticsEvent( + event = "Button - Links", + params = mapOf( + "Token" to token.symbol, + "Link" to linkTitle, + ), + ) + } + + enum class IntervalType(val source: String) { + Chart("Chart"), + PricePerformance("Price"), + Insights("Insights"), + } +} + +private fun PriceChangeInterval.toAnalyticsString() = when (this) { + PriceChangeInterval.H24 -> "24h" + PriceChangeInterval.WEEK -> "7d" + PriceChangeInterval.MONTH -> "1m" + PriceChangeInterval.MONTH3 -> "3m" + PriceChangeInterval.MONTH6 -> "6m" + PriceChangeInterval.YEAR -> "1y" + PriceChangeInterval.ALL_TIME -> "All" +} \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/MarketsTokenDetailsModel.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/MarketsTokenDetailsModel.kt index c1f7cd2393..5f12f70b59 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/MarketsTokenDetailsModel.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/MarketsTokenDetailsModel.kt @@ -5,6 +5,7 @@ import arrow.core.getOrElse import com.tangem.common.ui.charts.state.MarketChartData import com.tangem.common.ui.charts.state.MarketChartDataProducer import com.tangem.common.ui.charts.state.sorted +import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.decompose.di.ComponentScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer @@ -20,6 +21,7 @@ import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.markets.* import com.tangem.features.markets.details.MarketsTokenDetailsComponent +import com.tangem.features.markets.details.impl.analytics.MarketDetailsAnalyticsEvent import com.tangem.features.markets.details.impl.model.converters.DescriptionConverter import com.tangem.features.markets.details.impl.model.converters.TokenMarketInfoConverter import com.tangem.features.markets.details.impl.model.formatter.* @@ -56,10 +58,12 @@ internal class MarketsTokenDetailsModel @Inject constructor( private val getTokenMarketInfoUseCase: GetTokenMarketInfoUseCase, private val getTokenFullQuotesUseCase: GetTokenFullQuotesUseCase, private val urlOpener: UrlOpener, + private val analyticsEventHandler: AnalyticsEventHandler, ) : Model() { private var quotesJob = JobHolder() private val params = paramsContainer.require() + private val analyticsEventBuilder = MarketDetailsAnalyticsEvent.EventBuilder(token = params.token) private val currentAppCurrency = getSelectedAppCurrencyUseCase() .map { maybeAppCurrency -> @@ -75,14 +79,36 @@ internal class MarketsTokenDetailsModel @Inject constructor( onInfoClick = { showInfoBottomSheet(it) }, - onLinkClick = { - urlOpener.openUrl(it.url) + onLinkClick = { link -> + urlOpener.openUrl(link.url) + // === Analytics === + analyticsEventHandler.send(analyticsEventBuilder.linkClicked(linkTitle = link.title)) }, + // === Analytics === + onPricePerformanceIntervalChanged = { + analyticsEventHandler.send( + analyticsEventBuilder.intervalChanged( + intervalType = MarketDetailsAnalyticsEvent.IntervalType.PricePerformance, + interval = it, + ), + ) + }, + onInsightsIntervalChanged = { + analyticsEventHandler.send( + analyticsEventBuilder.intervalChanged( + intervalType = MarketDetailsAnalyticsEvent.IntervalType.Insights, + interval = it, + ), + ) + }, + // ================== ) private val descriptionConverter = DescriptionConverter( onReadModeClicked = { showInfoBottomSheet(it) + // === Analytics === + analyticsEventHandler.send(analyticsEventBuilder.readMoreClicked()) }, ) @@ -167,6 +193,14 @@ internal class MarketsTokenDetailsModel @Inject constructor( currentQuotes = currentQuotes, lastUpdatedTimestamp = lastUpdatedTimestamp, currentTokenInfo = currentTokenInfo, + onPricePerformanceIntervalChanged = { + analyticsEventHandler.send( + analyticsEventBuilder.intervalChanged( + intervalType = MarketDetailsAnalyticsEvent.IntervalType.PricePerformance, + interval = it, + ), + ) + }, ) private val loadChartJobHolder = JobHolder() @@ -221,6 +255,7 @@ internal class MarketsTokenDetailsModel @Inject constructor( appCurrency = currentAppCurrency.value, interval = interval, tokenId = params.token.id, + tokenSymbol = params.token.symbol, preview = false, ) @@ -290,6 +325,7 @@ internal class MarketsTokenDetailsModel @Inject constructor( val tokenMarketInfo = getTokenMarketInfoUseCase( appCurrency = currentAppCurrency.value, tokenId = params.token.id, + tokenSymbol = params.token.symbol, ) tokenMarketInfo.fold( @@ -368,6 +404,15 @@ internal class MarketsTokenDetailsModel @Inject constructor( private fun onSelectedIntervalChange(interval: PriceChangeInterval) { if (state.value.selectedInterval == interval) return + // === Analytics === + analyticsEventHandler.send( + analyticsEventBuilder.intervalChanged( + intervalType = MarketDetailsAnalyticsEvent.IntervalType.Chart, + interval = interval, + ), + ) + // ================== + val quotes = currentQuotes.value val priceChangePercent = quotes.getFormattedPercentByInterval(interval) diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/DescriptionConverter.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/DescriptionConverter.kt index 40d76daa99..7b16182d58 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/DescriptionConverter.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/DescriptionConverter.kt @@ -32,6 +32,7 @@ internal class DescriptionConverter( ), ), body = stringReference(value.fullDescription ?: ""), + showGeneratedAINotification = true, ), ) }, diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/InsightsConverter.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/InsightsConverter.kt index 376bf704a0..adb9f89f72 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/InsightsConverter.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/InsightsConverter.kt @@ -5,6 +5,7 @@ import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.wrappedList import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.markets.PriceChangeInterval import com.tangem.domain.markets.TokenMarketInfo import com.tangem.features.markets.details.impl.ui.state.InfoBottomSheetContent import com.tangem.features.markets.details.impl.ui.state.InfoPointUM @@ -21,6 +22,7 @@ import java.math.BigDecimal internal class InsightsConverter( private val appCurrency: Provider, private val onInfoClick: (InfoBottomSheetContent) -> Unit, + private val onIntervalChanged: (PriceChangeInterval) -> Unit, ) : Converter { override fun convert(value: TokenMarketInfo.Insights): InsightsUM { @@ -55,6 +57,7 @@ internal class InsightsConverter( ), ) }, + onIntervalChanged = onIntervalChanged, ) } } diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/LinksConverter.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/LinksConverter.kt index 1b571e168f..ca676ee2c5 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/LinksConverter.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/LinksConverter.kt @@ -1,9 +1,9 @@ package com.tangem.features.markets.details.impl.model.converters import androidx.compose.runtime.Stable -import com.tangem.core.ui.extensions.stringReference import com.tangem.domain.markets.TokenMarketInfo import com.tangem.features.markets.details.impl.ui.state.LinksUM +import com.tangem.features.markets.details.impl.ui.state.LinksUM.Link import com.tangem.features.markets.impl.R import com.tangem.utils.converter.Converter import kotlinx.collections.immutable.toImmutableList @@ -25,7 +25,7 @@ internal class LinksConverter( private fun TokenMarketInfo.Link.convert(): LinksUM.Link { return LinksUM.Link( - title = stringReference(title), + title = title, iconRes = getIconById(id), url = link, ) diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/PricePerformanceConverter.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/PricePerformanceConverter.kt index 29b04a9541..d1800c88c5 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/PricePerformanceConverter.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/PricePerformanceConverter.kt @@ -3,6 +3,7 @@ package com.tangem.features.markets.details.impl.model.converters import androidx.compose.runtime.Stable import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.markets.PriceChangeInterval import com.tangem.domain.markets.TokenMarketInfo import com.tangem.features.markets.details.impl.ui.state.PricePerformanceUM import com.tangem.utils.Provider @@ -13,6 +14,7 @@ import java.math.RoundingMode @Stable internal class PricePerformanceConverter( private val appCurrency: Provider, + private val onIntervalChanged: (PriceChangeInterval) -> Unit, ) { fun convert(value: TokenMarketInfo.PricePerformance, currentPrice: BigDecimal): PricePerformanceUM { @@ -20,6 +22,7 @@ internal class PricePerformanceConverter( h24 = value.day.convert(currentPrice), month = value.month.convert(currentPrice), all = value.allTime.convert(currentPrice), + onIntervalChanged = onIntervalChanged, ) } diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/TokenMarketInfoConverter.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/TokenMarketInfoConverter.kt index e90ad2b0d3..4f3145408b 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/TokenMarketInfoConverter.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/TokenMarketInfoConverter.kt @@ -2,6 +2,7 @@ package com.tangem.features.markets.details.impl.model.converters import androidx.compose.runtime.Stable import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.markets.PriceChangeInterval import com.tangem.domain.markets.TokenMarketInfo import com.tangem.features.markets.details.impl.ui.state.InfoBottomSheetContent import com.tangem.features.markets.details.impl.ui.state.LinksUM @@ -14,15 +15,24 @@ internal class TokenMarketInfoConverter( appCurrency: Provider, onInfoClick: (InfoBottomSheetContent) -> Unit, onLinkClick: (LinksUM.Link) -> Unit, + onPricePerformanceIntervalChanged: (PriceChangeInterval) -> Unit, + onInsightsIntervalChanged: (PriceChangeInterval) -> Unit, ) : Converter { - private val insightsConverter = InsightsConverter(appCurrency = appCurrency, onInfoClick = onInfoClick) + private val insightsConverter = InsightsConverter( + appCurrency = appCurrency, + onInfoClick = onInfoClick, + onIntervalChanged = onInsightsIntervalChanged, + ) @Suppress("UnusedPrivateMember") // TODO second markets iteration private val securityScoreConverter = SecurityScoreConverter(onInfoClick = onInfoClick) private val metricsConverter = MetricsConverter(appCurrency = appCurrency, onInfoClick = onInfoClick) - private val pricePerformanceConverter = PricePerformanceConverter(appCurrency = appCurrency) + private val pricePerformanceConverter = PricePerformanceConverter( + appCurrency = appCurrency, + onIntervalChanged = onPricePerformanceIntervalChanged, + ) private val linksConverter = LinksConverter(onLinkClick = onLinkClick) override fun convert(value: TokenMarketInfo): MarketsTokenDetailsUM.InformationBlocks { diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/state/QuotesStateUpdater.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/state/QuotesStateUpdater.kt index dd664b03ca..4bf8b58e27 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/state/QuotesStateUpdater.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/state/QuotesStateUpdater.kt @@ -4,6 +4,7 @@ import com.tangem.core.ui.components.marketprice.PriceChangeType import com.tangem.core.ui.event.consumedEvent import com.tangem.core.ui.event.triggeredEvent import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.markets.PriceChangeInterval import com.tangem.domain.markets.TokenMarketInfo import com.tangem.domain.markets.TokenQuotes import com.tangem.features.markets.details.impl.model.converters.PricePerformanceConverter @@ -22,8 +23,12 @@ internal class QuotesStateUpdater( private val currentQuotes: MutableStateFlow, private val lastUpdatedTimestamp: MutableStateFlow, private val currentTokenInfo: MutableStateFlow, + private val onPricePerformanceIntervalChanged: (PriceChangeInterval) -> Unit, ) { - private val pricePerformanceConverter = PricePerformanceConverter(currentAppCurrency) + private val pricePerformanceConverter = PricePerformanceConverter( + currentAppCurrency, + onIntervalChanged = onPricePerformanceIntervalChanged, + ) suspend fun updateQuotes(newQuotes: TokenQuotes) { val triggerPriceChangeType = getFormattedPriceChange( diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/InfoBottomSheet.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/InfoBottomSheet.kt index 180c7eefa9..f0d31afa7b 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/InfoBottomSheet.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/InfoBottomSheet.kt @@ -44,11 +44,13 @@ internal fun InfoBottomSheet(config: TangemBottomSheetConfig) { ), ) - AdditionalInfoNotification( - modifier = Modifier - .padding(top = TangemTheme.dimens.spacing12, bottom = TangemTheme.dimens.spacing16) - .fillMaxWidth(), - ) + if (it.showGeneratedAINotification) { + AdditionalInfoNotification( + modifier = Modifier + .padding(top = TangemTheme.dimens.spacing12, bottom = TangemTheme.dimens.spacing16) + .fillMaxWidth(), + ) + } SpacerH(bottomBarHeight) } diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/InsightsBlock.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/InsightsBlock.kt index df5dab7cce..e13502279d 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/InsightsBlock.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/InsightsBlock.kt @@ -47,7 +47,10 @@ internal fun InsightsBlock(state: InsightsUM, modifier: Modifier = Modifier) { PriceChangeInterval.MONTH, ), initialSelectedItem = PriceChangeInterval.H24, - onClick = { currentInterval = it }, + onClick = { + currentInterval = it + state.onIntervalChanged(it) + }, ) { Box( Modifier @@ -177,6 +180,7 @@ private fun ContentPreview() { ), ), onInfoClick = {}, + onIntervalChanged = {}, ), ) } diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/LinksBlock.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/LinksBlock.kt index e08e4a3052..de51bd5a8f 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/LinksBlock.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/LinksBlock.kt @@ -97,7 +97,7 @@ private fun SubBlock( ) { links.fastForEach { Chip( - text = it.title, + text = stringReference(it.title), iconResId = it.iconRes, onClick = { onLinkClick(it) }, ) @@ -163,36 +163,36 @@ private fun ContentPreview() { state = LinksUM( officialLinks = persistentListOf( LinksUM.Link( - title = stringReference("Website"), + title = "Website", iconRes = R.drawable.ic_plus_24, url = "https://tangem.com", ), LinksUM.Link( - title = stringReference("Website"), + title = "Website", iconRes = R.drawable.ic_plus_24, url = "https://tangem.com", ), LinksUM.Link( - title = stringReference("Website"), + title = "Website", iconRes = R.drawable.ic_plus_24, url = "https://tangem.com", ), ), social = persistentListOf( LinksUM.Link( - title = stringReference("Twitter"), + title = "Twitter", iconRes = R.drawable.ic_plus_24, url = "https://tangem.com", ), LinksUM.Link( - title = stringReference("Facebook"), + title = "Facebook", iconRes = R.drawable.ic_plus_24, url = "https://tangem.com", ), ), repository = persistentListOf( LinksUM.Link( - title = stringReference("Github"), + title = "Github", iconRes = R.drawable.ic_plus_24, url = "https://tangem.com", ), diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/PricePerformanceBlock.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/PricePerformanceBlock.kt index e1b5444e2e..c111de7630 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/PricePerformanceBlock.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/PricePerformanceBlock.kt @@ -54,7 +54,10 @@ internal fun PricePerformanceBlock(state: PricePerformanceUM, modifier: Modifier PriceChangeInterval.ALL_TIME, ), initialSelectedItem = PriceChangeInterval.H24, - onClick = { currentInterval = it }, + onClick = { + currentInterval = it + state.onIntervalChanged(it) + }, ) { Box( Modifier @@ -227,6 +230,7 @@ private fun ContentPreview() { high = "\$580,5M", indicatorFraction = 0.2f, ), + onIntervalChanged = {}, ), ) } diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/state/InfoBottomSheetContent.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/state/InfoBottomSheetContent.kt index 858fc017c5..c68995460f 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/state/InfoBottomSheetContent.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/state/InfoBottomSheetContent.kt @@ -6,4 +6,5 @@ import com.tangem.core.ui.extensions.TextReference internal data class InfoBottomSheetContent( val title: TextReference, val body: TextReference, + val showGeneratedAINotification: Boolean = false, ) : TangemBottomSheetConfigContent \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/state/InsightsUM.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/state/InsightsUM.kt index 9156f9bf1b..e2d0b3270b 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/state/InsightsUM.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/state/InsightsUM.kt @@ -1,5 +1,6 @@ package com.tangem.features.markets.details.impl.ui.state +import com.tangem.domain.markets.PriceChangeInterval import kotlinx.collections.immutable.ImmutableList internal data class InsightsUM( @@ -7,4 +8,5 @@ internal data class InsightsUM( val weekInfo: ImmutableList, val monthInfo: ImmutableList, val onInfoClick: () -> Unit, + val onIntervalChanged: (PriceChangeInterval) -> Unit, ) \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/state/LinksUM.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/state/LinksUM.kt index d1a6d13860..5b4ea87e18 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/state/LinksUM.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/state/LinksUM.kt @@ -1,7 +1,6 @@ package com.tangem.features.markets.details.impl.ui.state import androidx.annotation.DrawableRes -import com.tangem.core.ui.extensions.TextReference import kotlinx.collections.immutable.ImmutableList internal data class LinksUM( @@ -13,7 +12,7 @@ internal data class LinksUM( ) { data class Link( @DrawableRes val iconRes: Int, - val title: TextReference, + val title: String, val url: String, ) } \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/state/PricePerformanceUM.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/state/PricePerformanceUM.kt index e56e10312e..9448472a0d 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/state/PricePerformanceUM.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/state/PricePerformanceUM.kt @@ -1,11 +1,13 @@ package com.tangem.features.markets.details.impl.ui.state import androidx.annotation.FloatRange +import com.tangem.domain.markets.PriceChangeInterval internal data class PricePerformanceUM( val h24: Value, val month: Value, val all: Value, + val onIntervalChanged: (PriceChangeInterval) -> Unit, ) { data class Value( val low: String, diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/entry/impl/DefaultMarketsEntryComponent.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/entry/impl/DefaultMarketsEntryComponent.kt index ad932e8965..79865f0661 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/entry/impl/DefaultMarketsEntryComponent.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/entry/impl/DefaultMarketsEntryComponent.kt @@ -78,6 +78,10 @@ internal class DefaultMarketsEntryComponent @AssistedInject constructor( token = token.toSerializableParam(), appCurrency = appCurrency, showPortfolio = true, + analyticsParams = MarketsTokenDetailsComponent.AnalyticsParams( + blockchain = null, + source = "Market", + ), ), ), ) diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/api/MarketsPortfolioComponent.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/api/MarketsPortfolioComponent.kt index c736781c03..62babbdc62 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/api/MarketsPortfolioComponent.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/api/MarketsPortfolioComponent.kt @@ -11,7 +11,15 @@ import kotlinx.serialization.Serializable interface MarketsPortfolioComponent : ComposableContentComponent { @Serializable - data class Params(val token: TokenMarketParams) + data class Params( + val token: TokenMarketParams, + val analyticsParams: AnalyticsParams?, + ) + + @Serializable + data class AnalyticsParams( + val source: String, + ) fun setTokenNetworks(networks: List) diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/analytics/PortfolioAnalyticsEvent.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/analytics/PortfolioAnalyticsEvent.kt new file mode 100644 index 0000000000..5f7ff68ab5 --- /dev/null +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/analytics/PortfolioAnalyticsEvent.kt @@ -0,0 +1,50 @@ +package com.tangem.features.markets.portfolio.impl.analytics + +import com.tangem.core.analytics.models.AnalyticsEvent +import com.tangem.domain.markets.TokenMarketParams +import com.tangem.features.markets.portfolio.impl.ui.state.TokenActionsBSContentUM + +internal class PortfolioAnalyticsEvent( + event: String, + params: Map = mapOf(), +) : AnalyticsEvent(category = "Markets / Chart", event = event, params = params) { + + data class EventBuilder( + val token: TokenMarketParams, + val source: String?, + ) { + + fun addToPortfolioClicked() = PortfolioAnalyticsEvent( + event = "Button - Add To Portfolio", + params = mapOf( + "Token" to token.symbol, + ), + ) + + fun addToPortfolioWalletChanged() = PortfolioAnalyticsEvent(event = "Wallet Selected") + + fun addToPortfolioContinue(blockchainNames: List) = PortfolioAnalyticsEvent( + event = "Token Network Selected", + params = mapOf( + "Count" to blockchainNames.size.toString(), + "Token" to token.symbol, + "blockchain" to blockchainNames.joinToString(separator = ", "), + ), + ) + + fun quickActionClick(actionUM: TokenActionsBSContentUM.Action, blockchainName: String) = + PortfolioAnalyticsEvent( + event = when (actionUM) { + TokenActionsBSContentUM.Action.Buy -> "Button - Buy" + TokenActionsBSContentUM.Action.Receive -> "Button - Receive" + TokenActionsBSContentUM.Action.Exchange -> "Button - Swap" + else -> "error" + }, + params = buildMap { + put("Token", token.symbol) + source?.let { put("Source", source) } + put("blockchain", blockchainName) + }, + ) + } +} \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/MarketsPortfolioModel.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/MarketsPortfolioModel.kt index fa57001e94..9c6c4bcba1 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/MarketsPortfolioModel.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/MarketsPortfolioModel.kt @@ -2,6 +2,7 @@ package com.tangem.features.markets.portfolio.impl.model import androidx.compose.runtime.Stable import arrow.core.getOrElse +import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.decompose.di.ComponentScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer @@ -24,9 +25,11 @@ import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase import com.tangem.features.markets.impl.R import com.tangem.features.markets.portfolio.api.MarketsPortfolioComponent +import com.tangem.features.markets.portfolio.impl.analytics.PortfolioAnalyticsEvent import com.tangem.features.markets.portfolio.impl.loader.PortfolioDataLoader import com.tangem.features.markets.portfolio.impl.ui.WarningDialog import com.tangem.features.markets.portfolio.impl.ui.state.MyPortfolioUM +import com.tangem.lib.crypto.BlockchainUtils import com.tangem.utils.Provider import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.flow.* @@ -49,12 +52,17 @@ internal class MarketsPortfolioModel @Inject constructor( private val hasMissedDerivationsUseCase: HasMissedDerivationsUseCase, private val saveMarketTokensUseCase: SaveMarketTokensUseCase, private val addToPortfolioManager: AddToPortfolioManager, + private val analyticsEventHandler: AnalyticsEventHandler, ) : Model() { val state: StateFlow get() = _state private val _state: MutableStateFlow = MutableStateFlow(value = MyPortfolioUM.Loading) private val params = paramsContainer.require() + private val analyticsEventBuilder = PortfolioAnalyticsEvent.EventBuilder( + token = params.token, + source = params.analyticsParams?.source, + ) /** Multi-wallet [UserWalletId] that user uses to add new tokens in AddToPortfolio bottom sheet */ private val selectedMultiWalletIdFlow = MutableStateFlow(value = null) @@ -72,14 +80,37 @@ internal class MarketsPortfolioModel @Inject constructor( ) private val factory = MyPortfolioUMFactory( - onAddClick = { onAddToPortfolioBSVisibilityChange(isShow = true) }, + onAddClick = { + onAddToPortfolioBSVisibilityChange(isShow = true) + // === Analytics === + analyticsEventHandler.send( + analyticsEventBuilder.addToPortfolioClicked(), + ) + }, addToPortfolioBSContentUMFactory = AddToPortfolioBSContentUMFactory( token = params.token, onAddToPortfolioVisibilityChange = ::onAddToPortfolioBSVisibilityChange, onWalletSelectorVisibilityChange = ::onWalletSelectorVisibilityChange, onNetworkSwitchClick = ::onNetworkSwitchClick, - onWalletSelect = ::onWalletSelect, - onContinueClick = ::onContinueClick, + onWalletSelect = { + onWalletSelect(it) + // === Analytics === + analyticsEventHandler.send( + analyticsEventBuilder.addToPortfolioWalletChanged(), + ) + }, + onContinueClick = { selectedWalletId, addedNetworks -> + onContinueClick(selectedWalletId, addedNetworks) + + // === Analytics === + analyticsEventHandler.send( + analyticsEventBuilder.addToPortfolioContinue( + blockchainNames = addedNetworks.mapNotNull { + BlockchainUtils.getNetworkInfo(it.networkId)?.name + }, + ), + ) + }, ), currentState = Provider { _state.value }, tokenActionsHandler = tokenActionsIntentsFactory.create( @@ -87,6 +118,14 @@ internal class MarketsPortfolioModel @Inject constructor( updateTokenReceiveBSConfig = { updateBlock -> updateTokensState { it.copy(tokenReceiveBSConfig = updateBlock(it.tokenReceiveBSConfig)) } }, + onHandleQuickAction = { handledAction -> + analyticsEventHandler.send( + analyticsEventBuilder.quickActionClick( + actionUM = handledAction.action, + blockchainName = handledAction.cryptoCurrencyData.status.currency.network.name, + ), + ) + }, ), updateTokens = { updateBlock -> updateTokensState { state -> diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/TokenActionsHandler.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/TokenActionsHandler.kt index b43dc3b16f..fc2464a7f9 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/TokenActionsHandler.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/TokenActionsHandler.kt @@ -34,10 +34,9 @@ internal class TokenActionsHandler @AssistedInject constructor( private val clipboardManager: ClipboardManager, private val uiMessageSender: UiMessageSender, private val reduxStateHolder: ReduxStateHolder, - @Assisted - private val currentAppCurrency: Provider, - @Assisted("updateTokenReceiveBSConfig") - private val updateTokenReceiveBSConfig: ((TangemBottomSheetConfig) -> TangemBottomSheetConfig) -> Unit, + @Assisted private val currentAppCurrency: Provider, + @Assisted private val updateTokenReceiveBSConfig: ((TangemBottomSheetConfig) -> TangemBottomSheetConfig) -> Unit, + @Assisted private val onHandleQuickAction: (HandledQuickAction) -> Unit, private val isDemoCardUseCase: IsDemoCardUseCase, private val messageSender: UiMessageSender, ) { @@ -48,6 +47,12 @@ internal class TokenActionsHandler @AssistedInject constructor( ) fun handle(action: TokenActionsBSContentUM.Action, cryptoCurrencyData: PortfolioData.CryptoCurrencyData) { + onHandleQuickAction( + HandledQuickAction( + action = action, + cryptoCurrencyData = cryptoCurrencyData, + ), + ) if (handleDemoMode(action, cryptoCurrencyData.userWallet)) return when (action) { @@ -177,8 +182,13 @@ internal class TokenActionsHandler @AssistedInject constructor( interface Factory { fun create( currentAppCurrency: Provider, - @Assisted("updateTokenReceiveBSConfig") updateTokenReceiveBSConfig: ((TangemBottomSheetConfig) -> TangemBottomSheetConfig) -> Unit, + onHandleQuickAction: (HandledQuickAction) -> Unit, ): TokenActionsHandler } + + data class HandledQuickAction( + val action: TokenActionsBSContentUM.Action, + val cryptoCurrencyData: PortfolioData.CryptoCurrencyData, + ) } \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/token/block/impl/model/TokenMarketBlockModel.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/token/block/impl/model/TokenMarketBlockModel.kt index 49adf98ff9..d0ca34298a 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/token/block/impl/model/TokenMarketBlockModel.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/token/block/impl/model/TokenMarketBlockModel.kt @@ -40,6 +40,7 @@ internal class TokenMarketBlockModel @Inject constructor( ) : Model() { private val params = paramsContainer.require() + private val priceAndTimePointValuesConverter = PriceAndTimePointValuesConverter(needToFormatAxis = false) private val currentAppCurrency = getSelectedAppCurrencyUseCase() @@ -56,7 +57,7 @@ internal class TokenMarketBlockModel @Inject constructor( val state = MutableStateFlow( TokenMarketBlockUM( - currencySymbol = params.tokenSymbol, + currencySymbol = params.cryptoCurrency.symbol, currentPrice = null, h24Percent = null, priceChangeType = PriceChangeType.NEUTRAL, @@ -72,7 +73,7 @@ internal class TokenMarketBlockModel @Inject constructor( private fun startFetching() { modelScope.launch { getTokenQuotesUseCase( - currencyID = params.cryptoCurrencyID, + currencyID = params.cryptoCurrency.id, interval = PriceChangeInterval.H24, refresh = true, ).collect { @@ -100,9 +101,12 @@ internal class TokenMarketBlockModel @Inject constructor( } }.saveIn(quotesUpdateJobHolder) + val tokenId = params.cryptoCurrency.id.rawCurrencyId ?: return + modelScope.launch(dispatchers.main) { val result = getTokenPriceChartUseCase( - tokenId = params.tokenId, + tokenId = tokenId, + tokenSymbol = params.cryptoCurrency.symbol, interval = PriceChangeInterval.H24, appCurrency = currentAppCurrency.value, // TODO get currency from quotes use case [REDACTED_TASK_KEY] preview = true, @@ -128,12 +132,13 @@ internal class TokenMarketBlockModel @Inject constructor( private fun navigateToMarketDetails() { val quotes = quotesState ?: return + val tokenId = params.cryptoCurrency.id.rawCurrencyId ?: return val tokenParam = TokenMarketParams( - id = params.tokenId, - name = params.tokenSymbol, - imageUrl = params.tokenImageUrl, - symbol = params.tokenSymbol, + id = tokenId, + name = params.cryptoCurrency.name, + imageUrl = params.cryptoCurrency.iconUrl, + symbol = params.cryptoCurrency.symbol, tokenQuotes = TokenMarketParams.Quotes( currentPrice = quotes.currentPrice, h24Percent = quotes.h24Percent, @@ -147,6 +152,10 @@ internal class TokenMarketBlockModel @Inject constructor( token = tokenParam, appCurrency = currentAppCurrency.value, showPortfolio = false, + analyticsParams = AppRoute.MarketsTokenDetails.AnalyticsParams( + blockchain = params.cryptoCurrency.network.name, + source = "Token", + ), ), ) } diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/analytics/MarketsListAnalyticsEvent.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/analytics/MarketsListAnalyticsEvent.kt new file mode 100644 index 0000000000..99f0faa3c3 --- /dev/null +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/analytics/MarketsListAnalyticsEvent.kt @@ -0,0 +1,34 @@ +package com.tangem.features.markets.tokenlist.impl.analytics + +import com.tangem.core.analytics.models.AnalyticsEvent +import com.tangem.features.markets.tokenlist.impl.ui.state.MarketsListUM +import com.tangem.features.markets.tokenlist.impl.ui.state.SortByTypeUM + +internal sealed class MarketsListAnalyticsEvent( + event: String, + params: Map = mapOf(), +) : AnalyticsEvent(category = "Markets", event = event, params = params) { + + data object BottomSheetOpened : MarketsListAnalyticsEvent(event = "Markets Screen Opened") + + data class SortBy( + val sortByTypeUM: SortByTypeUM, + val interval: MarketsListUM.TrendInterval, + ) : MarketsListAnalyticsEvent( + event = "Sort By", + params = mapOf( + "Type" to when (sortByTypeUM) { + SortByTypeUM.Rating -> "Rating" + SortByTypeUM.Trending -> "Trending" + SortByTypeUM.ExperiencedBuyers -> "Buyers" + SortByTypeUM.TopGainers -> "Gainers" + SortByTypeUM.TopLosers -> "Losers" + }, + "Period" to when (interval) { + MarketsListUM.TrendInterval.H24 -> "24h" + MarketsListUM.TrendInterval.D7 -> "7d" + MarketsListUM.TrendInterval.M1 -> "1m" + }, + ), + ) +} \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/MarketsListModel.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/MarketsListModel.kt index e140b438b6..f3dd2d3c1a 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/MarketsListModel.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/MarketsListModel.kt @@ -2,6 +2,7 @@ package com.tangem.features.markets.tokenlist.impl.model import androidx.compose.runtime.Stable import arrow.core.getOrElse +import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.decompose.di.ComponentScoped import com.tangem.core.decompose.model.Model import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase @@ -9,8 +10,9 @@ import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.markets.GetMarketsTokenListFlowUseCase import com.tangem.domain.markets.TokenMarket import com.tangem.features.markets.entry.BottomSheetState -import com.tangem.features.markets.tokenlist.impl.model.statemanager.MarketsListUMStateManager +import com.tangem.features.markets.tokenlist.impl.analytics.MarketsListAnalyticsEvent import com.tangem.features.markets.tokenlist.impl.model.statemanager.MarketsListBatchFlowManager +import com.tangem.features.markets.tokenlist.impl.model.statemanager.MarketsListUMStateManager import com.tangem.features.markets.tokenlist.impl.ui.state.ListUM import com.tangem.features.markets.tokenlist.impl.ui.state.MarketsListItemUM import com.tangem.features.markets.tokenlist.impl.ui.state.SortByTypeUM @@ -32,6 +34,7 @@ internal class MarketsListModel @Inject constructor( override val dispatchers: CoroutineDispatcherProvider, getMarketsTokenListFlowUseCase: GetMarketsTokenListFlowUseCase, getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, + private val analyticsEventHandler: AnalyticsEventHandler, ) : Model() { private var updateQuotesJob = JobHolder() @@ -221,10 +224,30 @@ internal class MarketsListModel @Inject constructor( } } + // analytics + initAnalytics() + // initial loading mainMarketsListManager.reload() } + private fun initAnalytics() { + containerBottomSheetState + .onEach { + if (it == BottomSheetState.EXPANDED) { + analyticsEventHandler.send(MarketsListAnalyticsEvent.BottomSheetOpened) + } + }.launchIn(modelScope) + + state + .filter { it.isInSearchMode.not() } + .map { MarketsListAnalyticsEvent.SortBy(it.selectedSortBy, it.selectedInterval) } + .distinctUntilChanged() + .onEach { + analyticsEventHandler.send(it) + }.launchIn(modelScope) + } + private fun onTokenUIClicked(token: MarketsListItemUM) { modelScope.launch { activeListManager.getTokenById(token.id)?.let { found -> diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/TokenDetailsFragment.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/TokenDetailsFragment.kt index c942539d2f..cb4222f44b 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/TokenDetailsFragment.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/TokenDetailsFragment.kt @@ -85,15 +85,9 @@ internal class TokenDetailsFragment : ComposeFragment() { } private fun CryptoCurrency.toParam(): TokenMarketBlockComponent.Params? { - val tokenId = id.rawCurrencyId ?: return null // token price is not available + id.rawCurrencyId ?: return null // token price is not available - return TokenMarketBlockComponent.Params( - cryptoCurrencyID = id, - tokenId = tokenId, - tokenName = name, - tokenSymbol = symbol, - tokenImageUrl = iconUrl, - ) + return TokenMarketBlockComponent.Params(cryptoCurrency = this) } @Composable diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt index d22db8edce..3b57403e42 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt @@ -403,6 +403,10 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor( token = tokenMarketParams, appCurrency = getSelectedAppCurrencyUseCase.unwrap(), showPortfolio = false, + analyticsParams = AppRoute.MarketsTokenDetails.AnalyticsParams( + blockchain = cryptoCurrencyStatus.currency.network.name, + source = "Main", + ), ), ) }