Updated on 2026-08-14
This commit is contained in:
parent
b32cc4a928
commit
2ba0f3d4a1
42 changed files with 581 additions and 74 deletions
|
|
@ -194,6 +194,12 @@ internal class ChildFactory @Inject constructor(
|
||||||
token = route.token,
|
token = route.token,
|
||||||
appCurrency = route.appCurrency,
|
appCurrency = route.appCurrency,
|
||||||
showPortfolio = route.showPortfolio,
|
showPortfolio = route.showPortfolio,
|
||||||
|
analyticsParams = route.analyticsParams?.let {
|
||||||
|
MarketsTokenDetailsComponent.AnalyticsParams(
|
||||||
|
blockchain = it.blockchain,
|
||||||
|
source = it.source,
|
||||||
|
)
|
||||||
|
},
|
||||||
),
|
),
|
||||||
componentFactory = marketsTokenDetailsComponentFactory,
|
componentFactory = marketsTokenDetailsComponentFactory,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -272,5 +272,13 @@ sealed class AppRoute(val path: String) : Route {
|
||||||
val token: TokenMarketParams,
|
val token: TokenMarketParams,
|
||||||
val appCurrency: AppCurrency,
|
val appCurrency: AppCurrency,
|
||||||
val showPortfolio: Boolean,
|
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,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
package com.tangem.datasource.api.markets.models.response
|
package com.tangem.datasource.api.markets.models.response
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class TokenMarketChartResponse(
|
data class TokenMarketChartResponse(
|
||||||
@Json(name = "prices")
|
@Json(name = "prices")
|
||||||
val prices: Map<Long, BigDecimal>,
|
val prices: Map<Long, BigDecimal>,
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
package com.tangem.datasource.api.markets.models.response
|
package com.tangem.datasource.api.markets.models.response
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class TokenMarketInfoResponse(
|
data class TokenMarketInfoResponse(
|
||||||
@Json(name = "id")
|
@Json(name = "id")
|
||||||
val id: String,
|
val id: String,
|
||||||
|
|
@ -30,6 +32,7 @@ data class TokenMarketInfoResponse(
|
||||||
val pricePerformance: PricePerformance?,
|
val pricePerformance: PricePerformance?,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class PriceChangePercentage(
|
data class PriceChangePercentage(
|
||||||
@Json(name = "24h")
|
@Json(name = "24h")
|
||||||
val day: BigDecimal?,
|
val day: BigDecimal?,
|
||||||
|
|
@ -47,6 +50,7 @@ data class TokenMarketInfoResponse(
|
||||||
val allTime: BigDecimal?,
|
val allTime: BigDecimal?,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class Network(
|
data class Network(
|
||||||
@Json(name = "network_id")
|
@Json(name = "network_id")
|
||||||
val networkId: String,
|
val networkId: String,
|
||||||
|
|
@ -58,6 +62,7 @@ data class TokenMarketInfoResponse(
|
||||||
val decimalCount: Int?,
|
val decimalCount: Int?,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class Insights(
|
data class Insights(
|
||||||
@Json(name = "holders_change")
|
@Json(name = "holders_change")
|
||||||
val holdersChange: Change?,
|
val holdersChange: Change?,
|
||||||
|
|
@ -70,6 +75,7 @@ data class TokenMarketInfoResponse(
|
||||||
@Json(name = "networks")
|
@Json(name = "networks")
|
||||||
val sourceNetworks: List<SourceNetwork>?,
|
val sourceNetworks: List<SourceNetwork>?,
|
||||||
) {
|
) {
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class SourceNetwork(
|
data class SourceNetwork(
|
||||||
@Json(name = "network_id")
|
@Json(name = "network_id")
|
||||||
val id: String,
|
val id: String,
|
||||||
|
|
@ -78,6 +84,7 @@ data class TokenMarketInfoResponse(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class Change(
|
data class Change(
|
||||||
@Json(name = "24h")
|
@Json(name = "24h")
|
||||||
val day: BigDecimal?,
|
val day: BigDecimal?,
|
||||||
|
|
@ -87,6 +94,7 @@ data class TokenMarketInfoResponse(
|
||||||
val month: BigDecimal?,
|
val month: BigDecimal?,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class Metrics(
|
data class Metrics(
|
||||||
@Json(name = "market_rating")
|
@Json(name = "market_rating")
|
||||||
val marketRating: Int?,
|
val marketRating: Int?,
|
||||||
|
|
@ -102,6 +110,7 @@ data class TokenMarketInfoResponse(
|
||||||
val fullyDilutedValuation: BigDecimal?,
|
val fullyDilutedValuation: BigDecimal?,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class Links(
|
data class Links(
|
||||||
@Json(name = "official_links")
|
@Json(name = "official_links")
|
||||||
val officialLinks: List<Link>? = null,
|
val officialLinks: List<Link>? = null,
|
||||||
|
|
@ -113,6 +122,7 @@ data class TokenMarketInfoResponse(
|
||||||
val blockchainSite: List<Link>? = null,
|
val blockchainSite: List<Link>? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class Link(
|
data class Link(
|
||||||
@Json(name = "title")
|
@Json(name = "title")
|
||||||
val title: String,
|
val title: String,
|
||||||
|
|
@ -122,6 +132,7 @@ data class TokenMarketInfoResponse(
|
||||||
val link: String,
|
val link: String,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class PricePerformance(
|
data class PricePerformance(
|
||||||
@Json(name = "24h")
|
@Json(name = "24h")
|
||||||
val day: Range?,
|
val day: Range?,
|
||||||
|
|
@ -131,6 +142,7 @@ data class TokenMarketInfoResponse(
|
||||||
val allTime: Range?,
|
val allTime: Range?,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class Range(
|
data class Range(
|
||||||
@Json(name = "low_price")
|
@Json(name = "low_price")
|
||||||
val low: BigDecimal?,
|
val low: BigDecimal?,
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ dependencies {
|
||||||
implementation(projects.core.datasource)
|
implementation(projects.core.datasource)
|
||||||
implementation(projects.core.utils)
|
implementation(projects.core.utils)
|
||||||
implementation(projects.core.pagination)
|
implementation(projects.core.pagination)
|
||||||
|
implementation(projects.core.analytics)
|
||||||
|
implementation(projects.core.analytics.models)
|
||||||
|
|
||||||
implementation(projects.domain.legacy)
|
implementation(projects.domain.legacy)
|
||||||
implementation(projects.domain.markets)
|
implementation(projects.domain.markets)
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,16 @@ import com.tangem.blockchain.common.Blockchain
|
||||||
import com.tangem.blockchainsdk.compatibility.applyL2Compatibility
|
import com.tangem.blockchainsdk.compatibility.applyL2Compatibility
|
||||||
import com.tangem.blockchainsdk.compatibility.getTokenIdIfL2Network
|
import com.tangem.blockchainsdk.compatibility.getTokenIdIfL2Network
|
||||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
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.CryptoCurrencyFactory
|
||||||
import com.tangem.data.common.currency.getNetwork
|
import com.tangem.data.common.currency.getNetwork
|
||||||
import com.tangem.data.common.utils.retryOnError
|
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.TokenChartConverter
|
||||||
import com.tangem.data.markets.converters.TokenMarketInfoConverter
|
import com.tangem.data.markets.converters.TokenMarketInfoConverter
|
||||||
import com.tangem.data.markets.converters.TokenMarketListConverter
|
import com.tangem.data.markets.converters.TokenMarketListConverter
|
||||||
import com.tangem.data.markets.converters.toRequestParam
|
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.common.response.getOrThrow
|
||||||
import com.tangem.datasource.api.markets.TangemTechMarketsApi
|
import com.tangem.datasource.api.markets.TangemTechMarketsApi
|
||||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||||
|
|
@ -31,6 +34,7 @@ internal class DefaultMarketsTokenRepository(
|
||||||
private val tangemTechApi: TangemTechApi,
|
private val tangemTechApi: TangemTechApi,
|
||||||
private val userWalletsStore: UserWalletsStore,
|
private val userWalletsStore: UserWalletsStore,
|
||||||
private val dispatcherProvider: CoroutineDispatcherProvider,
|
private val dispatcherProvider: CoroutineDispatcherProvider,
|
||||||
|
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||||
) : MarketsTokenRepository {
|
) : MarketsTokenRepository {
|
||||||
|
|
||||||
private fun createTokenMarketsFetcher(firstBatchSize: Int, nextBatchSize: Int) = LimitOffsetBatchFetcher(
|
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
|
// we shouldn't infinitely retry on the first batch request
|
||||||
val res = if (isFirstBatchFetching) {
|
val res = if (isFirstBatchFetching) {
|
||||||
requestCall()
|
catchApiErrorAndSendEvent(errorEvent = MarketsDataAnalyticsEvent.List.Error) {
|
||||||
|
requestCall()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
retryOnError(priority = true) {
|
retryOnError(priority = true) {
|
||||||
requestCall()
|
catchApiErrorAndSendEvent(errorEvent = MarketsDataAnalyticsEvent.List.Error) {
|
||||||
|
requestCall()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -92,6 +100,9 @@ internal class DefaultMarketsTokenRepository(
|
||||||
val tokenMarketsUpdateFetcher = MarketsBatchUpdateFetcher(
|
val tokenMarketsUpdateFetcher = MarketsBatchUpdateFetcher(
|
||||||
tangemTechApi = tangemTechApi,
|
tangemTechApi = tangemTechApi,
|
||||||
marketsApi = marketsApi,
|
marketsApi = marketsApi,
|
||||||
|
onApiError = {
|
||||||
|
analyticsEventHandler.send(MarketsDataAnalyticsEvent.List.Error.toEvent())
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
val atomicInteger = AtomicInteger(0)
|
val atomicInteger = AtomicInteger(0)
|
||||||
|
|
@ -109,6 +120,7 @@ internal class DefaultMarketsTokenRepository(
|
||||||
fiatCurrencyCode: String,
|
fiatCurrencyCode: String,
|
||||||
interval: PriceChangeInterval,
|
interval: PriceChangeInterval,
|
||||||
tokenId: String,
|
tokenId: String,
|
||||||
|
tokenSymbol: String,
|
||||||
): TokenChart {
|
): TokenChart {
|
||||||
val mappedTokenId = getTokenIdIfL2Network(tokenId)
|
val mappedTokenId = getTokenIdIfL2Network(tokenId)
|
||||||
val response = marketsApi.getCoinChart(
|
val response = marketsApi.getCoinChart(
|
||||||
|
|
@ -117,13 +129,23 @@ internal class DefaultMarketsTokenRepository(
|
||||||
interval = interval.toRequestParam(),
|
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(
|
override suspend fun getChartPreview(
|
||||||
fiatCurrencyCode: String,
|
fiatCurrencyCode: String,
|
||||||
interval: PriceChangeInterval,
|
interval: PriceChangeInterval,
|
||||||
tokenId: String,
|
tokenId: String,
|
||||||
|
tokenSymbol: String,
|
||||||
): TokenChart {
|
): TokenChart {
|
||||||
val mappedTokenId = getTokenIdIfL2Network(tokenId)
|
val mappedTokenId = getTokenIdIfL2Network(tokenId)
|
||||||
val response = marketsApi.getCoinsListCharts(
|
val response = marketsApi.getCoinsListCharts(
|
||||||
|
|
@ -132,7 +154,11 @@ internal class DefaultMarketsTokenRepository(
|
||||||
interval = interval.toRequestParam(),
|
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)
|
return TokenChartConverter.convert(interval, chart)
|
||||||
}
|
}
|
||||||
|
|
@ -140,6 +166,7 @@ internal class DefaultMarketsTokenRepository(
|
||||||
override suspend fun getTokenInfo(
|
override suspend fun getTokenInfo(
|
||||||
fiatCurrencyCode: String,
|
fiatCurrencyCode: String,
|
||||||
tokenId: String,
|
tokenId: String,
|
||||||
|
tokenSymbol: String,
|
||||||
languageCode: String,
|
languageCode: String,
|
||||||
): TokenMarketInfo {
|
): TokenMarketInfo {
|
||||||
val response = marketsApi.getCoinMarketData(
|
val response = marketsApi.getCoinMarketData(
|
||||||
|
|
@ -148,12 +175,22 @@ internal class DefaultMarketsTokenRepository(
|
||||||
language = languageCode,
|
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)
|
return TokenMarketInfoConverter.convert(resultResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getTokenQuotes(fiatCurrencyCode: String, tokenId: String): TokenQuotes {
|
override suspend fun getTokenQuotes(fiatCurrencyCode: String, tokenId: String): TokenQuotes {
|
||||||
// TODO change method when backend is ready
|
// TODO change method when backend is ready
|
||||||
|
// add error analytics event
|
||||||
val response = marketsApi.getCoinMarketData(
|
val response = marketsApi.getCoinMarketData(
|
||||||
currency = fiatCurrencyCode,
|
currency = fiatCurrencyCode,
|
||||||
coinId = tokenId,
|
coinId = tokenId,
|
||||||
|
|
@ -194,4 +231,16 @@ internal class DefaultMarketsTokenRepository(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private inline fun <T> 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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4,6 +4,7 @@ import com.tangem.data.common.utils.retryOnError
|
||||||
import com.tangem.data.markets.converters.TokenMarketChartsConverter
|
import com.tangem.data.markets.converters.TokenMarketChartsConverter
|
||||||
import com.tangem.data.markets.converters.TokenQuotesShortConverter
|
import com.tangem.data.markets.converters.TokenQuotesShortConverter
|
||||||
import com.tangem.data.markets.converters.toRequestParam
|
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.common.response.getOrThrow
|
||||||
import com.tangem.datasource.api.markets.TangemTechMarketsApi
|
import com.tangem.datasource.api.markets.TangemTechMarketsApi
|
||||||
import com.tangem.datasource.api.markets.models.response.TokenMarketChartListResponse
|
import com.tangem.datasource.api.markets.models.response.TokenMarketChartListResponse
|
||||||
|
|
@ -20,6 +21,7 @@ import kotlinx.coroutines.launch
|
||||||
internal class MarketsBatchUpdateFetcher(
|
internal class MarketsBatchUpdateFetcher(
|
||||||
private val marketsApi: TangemTechMarketsApi,
|
private val marketsApi: TangemTechMarketsApi,
|
||||||
private val tangemTechApi: TangemTechApi,
|
private val tangemTechApi: TangemTechApi,
|
||||||
|
private val onApiError: () -> Unit,
|
||||||
) : BatchUpdateFetcher<Int, List<TokenMarket>, TokenMarketUpdateRequest> {
|
) : BatchUpdateFetcher<Int, List<TokenMarket>, TokenMarketUpdateRequest> {
|
||||||
|
|
||||||
override suspend fun BatchUpdateFetcher.UpdateContext<Int, List<TokenMarket>>.fetchUpdateAsync(
|
override suspend fun BatchUpdateFetcher.UpdateContext<Int, List<TokenMarket>>.fetchUpdateAsync(
|
||||||
|
|
@ -35,11 +37,13 @@ internal class MarketsBatchUpdateFetcher(
|
||||||
val updateTasks = idsToUpdate.map { batchIds ->
|
val updateTasks = idsToUpdate.map { batchIds ->
|
||||||
async {
|
async {
|
||||||
retryOnError {
|
retryOnError {
|
||||||
marketsApi.getCoinsListCharts(
|
catchApiError(onApiError) {
|
||||||
coinIds = batchIds.second.joinToString(separator = ","),
|
marketsApi.getCoinsListCharts(
|
||||||
interval = updateRequest.interval.toRequestParam(),
|
coinIds = batchIds.second.joinToString(separator = ","),
|
||||||
currency = updateRequest.currency,
|
interval = updateRequest.interval.toRequestParam(),
|
||||||
).getOrThrow()
|
currency = updateRequest.currency,
|
||||||
|
).getOrThrow()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -62,11 +66,13 @@ internal class MarketsBatchUpdateFetcher(
|
||||||
}
|
}
|
||||||
is TokenMarketUpdateRequest.UpdateQuotes -> {
|
is TokenMarketUpdateRequest.UpdateQuotes -> {
|
||||||
val quotesRes = retryOnError {
|
val quotesRes = retryOnError {
|
||||||
tangemTechApi.getQuotes(
|
catchApiError(onApiError) {
|
||||||
currencyId = updateRequest.currencyId,
|
tangemTechApi.getQuotes(
|
||||||
coinIds = idsToUpdate.map { it.second }.flatten().joinToString(separator = ","),
|
currencyId = updateRequest.currencyId,
|
||||||
fields = quoteFields.joinToString(separator = ","),
|
coinIds = idsToUpdate.map { it.second }.flatten().joinToString(separator = ","),
|
||||||
).getOrThrow()
|
fields = quoteFields.joinToString(separator = ","),
|
||||||
|
).getOrThrow()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
update {
|
update {
|
||||||
|
|
@ -106,6 +112,15 @@ internal class MarketsBatchUpdateFetcher(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private inline fun <T> catchApiError(onError: () -> Unit, block: () -> T): T {
|
||||||
|
return try {
|
||||||
|
block()
|
||||||
|
} catch (e: ApiResponseError) {
|
||||||
|
onError()
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val quoteFields = listOf(
|
private val quoteFields = listOf(
|
||||||
"price",
|
"price",
|
||||||
|
|
|
||||||
|
|
@ -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<String, String> = mapOf(),
|
||||||
|
) : AnalyticsEvent(category = "Markets", event = event, params = params), MarketsDataAnalyticsEvent {
|
||||||
|
|
||||||
|
data object Error : List(event = "Data Error")
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class Details(
|
||||||
|
event: String,
|
||||||
|
params: Map<String, String> = 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.tangem.data.markets.di
|
package com.tangem.data.markets.di
|
||||||
|
|
||||||
|
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||||
import com.tangem.data.markets.DefaultMarketsTokenRepository
|
import com.tangem.data.markets.DefaultMarketsTokenRepository
|
||||||
import com.tangem.datasource.api.markets.TangemTechMarketsApi
|
import com.tangem.datasource.api.markets.TangemTechMarketsApi
|
||||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||||
|
|
@ -24,12 +25,14 @@ internal object MarketsDataModule {
|
||||||
@DevTangemApi tangemTechApi: TangemTechApi,
|
@DevTangemApi tangemTechApi: TangemTechApi,
|
||||||
userWalletsStore: UserWalletsStore,
|
userWalletsStore: UserWalletsStore,
|
||||||
dispatchers: CoroutineDispatcherProvider,
|
dispatchers: CoroutineDispatcherProvider,
|
||||||
|
analyticsEventHandler: AnalyticsEventHandler,
|
||||||
): MarketsTokenRepository {
|
): MarketsTokenRepository {
|
||||||
return DefaultMarketsTokenRepository(
|
return DefaultMarketsTokenRepository(
|
||||||
marketsApi = marketsApi,
|
marketsApi = marketsApi,
|
||||||
tangemTechApi = tangemTechApi,
|
tangemTechApi = tangemTechApi,
|
||||||
dispatcherProvider = dispatchers,
|
dispatcherProvider = dispatchers,
|
||||||
userWalletsStore = userWalletsStore,
|
userWalletsStore = userWalletsStore,
|
||||||
|
analyticsEventHandler = analyticsEventHandler,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -9,11 +9,16 @@ class GetTokenMarketInfoUseCase(
|
||||||
private val marketsTokenRepository: MarketsTokenRepository,
|
private val marketsTokenRepository: MarketsTokenRepository,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend operator fun invoke(appCurrency: AppCurrency, tokenId: String): Either<Unit, TokenMarketInfo> {
|
suspend operator fun invoke(
|
||||||
|
appCurrency: AppCurrency,
|
||||||
|
tokenId: String,
|
||||||
|
tokenSymbol: String,
|
||||||
|
): Either<Unit, TokenMarketInfo> {
|
||||||
return Either.catch {
|
return Either.catch {
|
||||||
marketsTokenRepository.getTokenInfo(
|
marketsTokenRepository.getTokenInfo(
|
||||||
fiatCurrencyCode = appCurrency.code,
|
fiatCurrencyCode = appCurrency.code,
|
||||||
tokenId = tokenId,
|
tokenId = tokenId,
|
||||||
|
tokenSymbol = tokenSymbol,
|
||||||
languageCode = SupportedLanguages.getCurrentSupportedLanguageCode(),
|
languageCode = SupportedLanguages.getCurrentSupportedLanguageCode(),
|
||||||
)
|
)
|
||||||
}.mapLeft {}
|
}.mapLeft {}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ class GetTokenPriceChartUseCase(
|
||||||
appCurrency: AppCurrency,
|
appCurrency: AppCurrency,
|
||||||
interval: PriceChangeInterval,
|
interval: PriceChangeInterval,
|
||||||
tokenId: String,
|
tokenId: String,
|
||||||
|
tokenSymbol: String,
|
||||||
preview: Boolean,
|
preview: Boolean,
|
||||||
): Either<Unit, TokenChart> {
|
): Either<Unit, TokenChart> {
|
||||||
return Either.catch {
|
return Either.catch {
|
||||||
|
|
@ -20,12 +21,14 @@ class GetTokenPriceChartUseCase(
|
||||||
fiatCurrencyCode = appCurrency.code,
|
fiatCurrencyCode = appCurrency.code,
|
||||||
interval = interval,
|
interval = interval,
|
||||||
tokenId = tokenId,
|
tokenId = tokenId,
|
||||||
|
tokenSymbol = tokenSymbol,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
marketsTokenRepository.getChart(
|
marketsTokenRepository.getChart(
|
||||||
fiatCurrencyCode = appCurrency.code,
|
fiatCurrencyCode = appCurrency.code,
|
||||||
interval = interval,
|
interval = interval,
|
||||||
tokenId = tokenId,
|
tokenId = tokenId,
|
||||||
|
tokenSymbol = tokenSymbol,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}.mapLeft {}
|
}.mapLeft {}
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,26 @@ interface MarketsTokenRepository {
|
||||||
nextBatchSize: Int,
|
nextBatchSize: Int,
|
||||||
): TokenListBatchFlow
|
): 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
|
suspend fun getTokenQuotes(fiatCurrencyCode: String, tokenId: String): TokenQuotes
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,13 @@ interface MarketsTokenDetailsComponent : ComposableContentComponent {
|
||||||
val token: TokenMarketParams,
|
val token: TokenMarketParams,
|
||||||
val appCurrency: AppCurrency,
|
val appCurrency: AppCurrency,
|
||||||
val showPortfolio: Boolean,
|
val showPortfolio: Boolean,
|
||||||
|
val analyticsParams: AnalyticsParams?,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class AnalyticsParams(
|
||||||
|
val blockchain: String?,
|
||||||
|
val source: String,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,7 @@ interface TokenMarketBlockComponent : ComposableContentComponent {
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Params(
|
data class Params(
|
||||||
val cryptoCurrencyID: CryptoCurrency.ID,
|
val cryptoCurrency: CryptoCurrency,
|
||||||
val tokenId: String,
|
|
||||||
val tokenName: String,
|
|
||||||
val tokenSymbol: String,
|
|
||||||
val tokenImageUrl: String?,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
interface Factory {
|
interface Factory {
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,8 @@ dependencies {
|
||||||
implementation(projects.core.decompose)
|
implementation(projects.core.decompose)
|
||||||
implementation(projects.core.ui)
|
implementation(projects.core.ui)
|
||||||
implementation(projects.core.featuretoggles)
|
implementation(projects.core.featuretoggles)
|
||||||
|
implementation(projects.core.analytics)
|
||||||
|
implementation(projects.core.analytics.models)
|
||||||
|
|
||||||
/* Common */
|
/* Common */
|
||||||
implementation(projects.common.ui)
|
implementation(projects.common.ui)
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import androidx.compose.ui.unit.Dp
|
||||||
import androidx.lifecycle.compose.LifecycleStartEffect
|
import androidx.lifecycle.compose.LifecycleStartEffect
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import com.tangem.blockchainsdk.compatibility.getTokenIdIfL2Network
|
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.AppComponentContext
|
||||||
import com.tangem.core.decompose.context.child
|
import com.tangem.core.decompose.context.child
|
||||||
import com.tangem.core.decompose.model.getOrCreateModel
|
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.core.ui.res.TangemTheme
|
||||||
import com.tangem.features.markets.details.MarketsTokenDetailsComponent
|
import com.tangem.features.markets.details.MarketsTokenDetailsComponent
|
||||||
import com.tangem.features.markets.details.MarketsTokenDetailsComponent.Params
|
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.MarketsTokenDetailsModel
|
||||||
import com.tangem.features.markets.details.impl.model.state.TokenNetworksState
|
import com.tangem.features.markets.details.impl.model.state.TokenNetworksState
|
||||||
import com.tangem.features.markets.details.impl.ui.MarketsTokenDetailsContent
|
import com.tangem.features.markets.details.impl.ui.MarketsTokenDetailsContent
|
||||||
|
|
@ -29,6 +31,7 @@ import kotlinx.coroutines.launch
|
||||||
internal class DefaultMarketsTokenDetailsComponent @AssistedInject constructor(
|
internal class DefaultMarketsTokenDetailsComponent @AssistedInject constructor(
|
||||||
@Assisted appComponentContext: AppComponentContext,
|
@Assisted appComponentContext: AppComponentContext,
|
||||||
@Assisted params: Params,
|
@Assisted params: Params,
|
||||||
|
analyticsEventHandler: AnalyticsEventHandler,
|
||||||
portfolioComponentFactory: MarketsPortfolioComponent.Factory,
|
portfolioComponentFactory: MarketsPortfolioComponent.Factory,
|
||||||
) : AppComponentContext by appComponentContext, MarketsTokenDetailsComponent {
|
) : AppComponentContext by appComponentContext, MarketsTokenDetailsComponent {
|
||||||
|
|
||||||
|
|
@ -38,12 +41,17 @@ internal class DefaultMarketsTokenDetailsComponent @AssistedInject constructor(
|
||||||
id = getTokenIdIfL2Network(params.token.id),
|
id = getTokenIdIfL2Network(params.token.id),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
private val analyticsParams = params.analyticsParams
|
||||||
|
|
||||||
private val model: MarketsTokenDetailsModel = getOrCreateModel(updatedParams)
|
private val model: MarketsTokenDetailsModel = getOrCreateModel(updatedParams)
|
||||||
|
|
||||||
private val portfolioComponent: MarketsPortfolioComponent? = if (updatedParams.showPortfolio) {
|
private val portfolioComponent: MarketsPortfolioComponent? = if (updatedParams.showPortfolio) {
|
||||||
portfolioComponentFactory.create(
|
portfolioComponentFactory.create(
|
||||||
context = child("my_portfolio"),
|
context = child("my_portfolio"),
|
||||||
params = MarketsPortfolioComponent.Params(updatedParams.token),
|
params = MarketsPortfolioComponent.Params(
|
||||||
|
updatedParams.token,
|
||||||
|
analyticsParams = analyticsParams?.source?.let { MarketsPortfolioComponent.AnalyticsParams(it) },
|
||||||
|
),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
null
|
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
|
@Composable
|
||||||
|
|
|
||||||
|
|
@ -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<String, String> = 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"
|
||||||
|
}
|
||||||
|
|
@ -5,6 +5,7 @@ import arrow.core.getOrElse
|
||||||
import com.tangem.common.ui.charts.state.MarketChartData
|
import com.tangem.common.ui.charts.state.MarketChartData
|
||||||
import com.tangem.common.ui.charts.state.MarketChartDataProducer
|
import com.tangem.common.ui.charts.state.MarketChartDataProducer
|
||||||
import com.tangem.common.ui.charts.state.sorted
|
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.di.ComponentScoped
|
||||||
import com.tangem.core.decompose.model.Model
|
import com.tangem.core.decompose.model.Model
|
||||||
import com.tangem.core.decompose.model.ParamsContainer
|
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.appcurrency.model.AppCurrency
|
||||||
import com.tangem.domain.markets.*
|
import com.tangem.domain.markets.*
|
||||||
import com.tangem.features.markets.details.MarketsTokenDetailsComponent
|
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.DescriptionConverter
|
||||||
import com.tangem.features.markets.details.impl.model.converters.TokenMarketInfoConverter
|
import com.tangem.features.markets.details.impl.model.converters.TokenMarketInfoConverter
|
||||||
import com.tangem.features.markets.details.impl.model.formatter.*
|
import com.tangem.features.markets.details.impl.model.formatter.*
|
||||||
|
|
@ -56,10 +58,12 @@ internal class MarketsTokenDetailsModel @Inject constructor(
|
||||||
private val getTokenMarketInfoUseCase: GetTokenMarketInfoUseCase,
|
private val getTokenMarketInfoUseCase: GetTokenMarketInfoUseCase,
|
||||||
private val getTokenFullQuotesUseCase: GetTokenFullQuotesUseCase,
|
private val getTokenFullQuotesUseCase: GetTokenFullQuotesUseCase,
|
||||||
private val urlOpener: UrlOpener,
|
private val urlOpener: UrlOpener,
|
||||||
|
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||||
) : Model() {
|
) : Model() {
|
||||||
|
|
||||||
private var quotesJob = JobHolder()
|
private var quotesJob = JobHolder()
|
||||||
private val params = paramsContainer.require<MarketsTokenDetailsComponent.Params>()
|
private val params = paramsContainer.require<MarketsTokenDetailsComponent.Params>()
|
||||||
|
private val analyticsEventBuilder = MarketDetailsAnalyticsEvent.EventBuilder(token = params.token)
|
||||||
|
|
||||||
private val currentAppCurrency = getSelectedAppCurrencyUseCase()
|
private val currentAppCurrency = getSelectedAppCurrencyUseCase()
|
||||||
.map { maybeAppCurrency ->
|
.map { maybeAppCurrency ->
|
||||||
|
|
@ -75,14 +79,36 @@ internal class MarketsTokenDetailsModel @Inject constructor(
|
||||||
onInfoClick = {
|
onInfoClick = {
|
||||||
showInfoBottomSheet(it)
|
showInfoBottomSheet(it)
|
||||||
},
|
},
|
||||||
onLinkClick = {
|
onLinkClick = { link ->
|
||||||
urlOpener.openUrl(it.url)
|
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(
|
private val descriptionConverter = DescriptionConverter(
|
||||||
onReadModeClicked = {
|
onReadModeClicked = {
|
||||||
showInfoBottomSheet(it)
|
showInfoBottomSheet(it)
|
||||||
|
// === Analytics ===
|
||||||
|
analyticsEventHandler.send(analyticsEventBuilder.readMoreClicked())
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -167,6 +193,14 @@ internal class MarketsTokenDetailsModel @Inject constructor(
|
||||||
currentQuotes = currentQuotes,
|
currentQuotes = currentQuotes,
|
||||||
lastUpdatedTimestamp = lastUpdatedTimestamp,
|
lastUpdatedTimestamp = lastUpdatedTimestamp,
|
||||||
currentTokenInfo = currentTokenInfo,
|
currentTokenInfo = currentTokenInfo,
|
||||||
|
onPricePerformanceIntervalChanged = {
|
||||||
|
analyticsEventHandler.send(
|
||||||
|
analyticsEventBuilder.intervalChanged(
|
||||||
|
intervalType = MarketDetailsAnalyticsEvent.IntervalType.PricePerformance,
|
||||||
|
interval = it,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
private val loadChartJobHolder = JobHolder()
|
private val loadChartJobHolder = JobHolder()
|
||||||
|
|
@ -221,6 +255,7 @@ internal class MarketsTokenDetailsModel @Inject constructor(
|
||||||
appCurrency = currentAppCurrency.value,
|
appCurrency = currentAppCurrency.value,
|
||||||
interval = interval,
|
interval = interval,
|
||||||
tokenId = params.token.id,
|
tokenId = params.token.id,
|
||||||
|
tokenSymbol = params.token.symbol,
|
||||||
preview = false,
|
preview = false,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -290,6 +325,7 @@ internal class MarketsTokenDetailsModel @Inject constructor(
|
||||||
val tokenMarketInfo = getTokenMarketInfoUseCase(
|
val tokenMarketInfo = getTokenMarketInfoUseCase(
|
||||||
appCurrency = currentAppCurrency.value,
|
appCurrency = currentAppCurrency.value,
|
||||||
tokenId = params.token.id,
|
tokenId = params.token.id,
|
||||||
|
tokenSymbol = params.token.symbol,
|
||||||
)
|
)
|
||||||
|
|
||||||
tokenMarketInfo.fold(
|
tokenMarketInfo.fold(
|
||||||
|
|
@ -368,6 +404,15 @@ internal class MarketsTokenDetailsModel @Inject constructor(
|
||||||
private fun onSelectedIntervalChange(interval: PriceChangeInterval) {
|
private fun onSelectedIntervalChange(interval: PriceChangeInterval) {
|
||||||
if (state.value.selectedInterval == interval) return
|
if (state.value.selectedInterval == interval) return
|
||||||
|
|
||||||
|
// === Analytics ===
|
||||||
|
analyticsEventHandler.send(
|
||||||
|
analyticsEventBuilder.intervalChanged(
|
||||||
|
intervalType = MarketDetailsAnalyticsEvent.IntervalType.Chart,
|
||||||
|
interval = interval,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
// ==================
|
||||||
|
|
||||||
val quotes = currentQuotes.value
|
val quotes = currentQuotes.value
|
||||||
val priceChangePercent = quotes.getFormattedPercentByInterval(interval)
|
val priceChangePercent = quotes.getFormattedPercentByInterval(interval)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ internal class DescriptionConverter(
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
body = stringReference(value.fullDescription ?: ""),
|
body = stringReference(value.fullDescription ?: ""),
|
||||||
|
showGeneratedAINotification = true,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import com.tangem.core.ui.extensions.resourceReference
|
||||||
import com.tangem.core.ui.extensions.wrappedList
|
import com.tangem.core.ui.extensions.wrappedList
|
||||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||||
|
import com.tangem.domain.markets.PriceChangeInterval
|
||||||
import com.tangem.domain.markets.TokenMarketInfo
|
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.InfoBottomSheetContent
|
||||||
import com.tangem.features.markets.details.impl.ui.state.InfoPointUM
|
import com.tangem.features.markets.details.impl.ui.state.InfoPointUM
|
||||||
|
|
@ -21,6 +22,7 @@ import java.math.BigDecimal
|
||||||
internal class InsightsConverter(
|
internal class InsightsConverter(
|
||||||
private val appCurrency: Provider<AppCurrency>,
|
private val appCurrency: Provider<AppCurrency>,
|
||||||
private val onInfoClick: (InfoBottomSheetContent) -> Unit,
|
private val onInfoClick: (InfoBottomSheetContent) -> Unit,
|
||||||
|
private val onIntervalChanged: (PriceChangeInterval) -> Unit,
|
||||||
) : Converter<TokenMarketInfo.Insights, InsightsUM> {
|
) : Converter<TokenMarketInfo.Insights, InsightsUM> {
|
||||||
|
|
||||||
override fun convert(value: TokenMarketInfo.Insights): InsightsUM {
|
override fun convert(value: TokenMarketInfo.Insights): InsightsUM {
|
||||||
|
|
@ -55,6 +57,7 @@ internal class InsightsConverter(
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
onIntervalChanged = onIntervalChanged,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
package com.tangem.features.markets.details.impl.model.converters
|
package com.tangem.features.markets.details.impl.model.converters
|
||||||
|
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
import com.tangem.core.ui.extensions.stringReference
|
|
||||||
import com.tangem.domain.markets.TokenMarketInfo
|
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
|
||||||
|
import com.tangem.features.markets.details.impl.ui.state.LinksUM.Link
|
||||||
import com.tangem.features.markets.impl.R
|
import com.tangem.features.markets.impl.R
|
||||||
import com.tangem.utils.converter.Converter
|
import com.tangem.utils.converter.Converter
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
|
@ -25,7 +25,7 @@ internal class LinksConverter(
|
||||||
|
|
||||||
private fun TokenMarketInfo.Link.convert(): LinksUM.Link {
|
private fun TokenMarketInfo.Link.convert(): LinksUM.Link {
|
||||||
return LinksUM.Link(
|
return LinksUM.Link(
|
||||||
title = stringReference(title),
|
title = title,
|
||||||
iconRes = getIconById(id),
|
iconRes = getIconById(id),
|
||||||
url = link,
|
url = link,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package com.tangem.features.markets.details.impl.model.converters
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||||
|
import com.tangem.domain.markets.PriceChangeInterval
|
||||||
import com.tangem.domain.markets.TokenMarketInfo
|
import com.tangem.domain.markets.TokenMarketInfo
|
||||||
import com.tangem.features.markets.details.impl.ui.state.PricePerformanceUM
|
import com.tangem.features.markets.details.impl.ui.state.PricePerformanceUM
|
||||||
import com.tangem.utils.Provider
|
import com.tangem.utils.Provider
|
||||||
|
|
@ -13,6 +14,7 @@ import java.math.RoundingMode
|
||||||
@Stable
|
@Stable
|
||||||
internal class PricePerformanceConverter(
|
internal class PricePerformanceConverter(
|
||||||
private val appCurrency: Provider<AppCurrency>,
|
private val appCurrency: Provider<AppCurrency>,
|
||||||
|
private val onIntervalChanged: (PriceChangeInterval) -> Unit,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun convert(value: TokenMarketInfo.PricePerformance, currentPrice: BigDecimal): PricePerformanceUM {
|
fun convert(value: TokenMarketInfo.PricePerformance, currentPrice: BigDecimal): PricePerformanceUM {
|
||||||
|
|
@ -20,6 +22,7 @@ internal class PricePerformanceConverter(
|
||||||
h24 = value.day.convert(currentPrice),
|
h24 = value.day.convert(currentPrice),
|
||||||
month = value.month.convert(currentPrice),
|
month = value.month.convert(currentPrice),
|
||||||
all = value.allTime.convert(currentPrice),
|
all = value.allTime.convert(currentPrice),
|
||||||
|
onIntervalChanged = onIntervalChanged,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package com.tangem.features.markets.details.impl.model.converters
|
||||||
|
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||||
|
import com.tangem.domain.markets.PriceChangeInterval
|
||||||
import com.tangem.domain.markets.TokenMarketInfo
|
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.InfoBottomSheetContent
|
||||||
import com.tangem.features.markets.details.impl.ui.state.LinksUM
|
import com.tangem.features.markets.details.impl.ui.state.LinksUM
|
||||||
|
|
@ -14,15 +15,24 @@ internal class TokenMarketInfoConverter(
|
||||||
appCurrency: Provider<AppCurrency>,
|
appCurrency: Provider<AppCurrency>,
|
||||||
onInfoClick: (InfoBottomSheetContent) -> Unit,
|
onInfoClick: (InfoBottomSheetContent) -> Unit,
|
||||||
onLinkClick: (LinksUM.Link) -> Unit,
|
onLinkClick: (LinksUM.Link) -> Unit,
|
||||||
|
onPricePerformanceIntervalChanged: (PriceChangeInterval) -> Unit,
|
||||||
|
onInsightsIntervalChanged: (PriceChangeInterval) -> Unit,
|
||||||
) : Converter<TokenMarketInfo, MarketsTokenDetailsUM.InformationBlocks> {
|
) : Converter<TokenMarketInfo, MarketsTokenDetailsUM.InformationBlocks> {
|
||||||
|
|
||||||
private val insightsConverter = InsightsConverter(appCurrency = appCurrency, onInfoClick = onInfoClick)
|
private val insightsConverter = InsightsConverter(
|
||||||
|
appCurrency = appCurrency,
|
||||||
|
onInfoClick = onInfoClick,
|
||||||
|
onIntervalChanged = onInsightsIntervalChanged,
|
||||||
|
)
|
||||||
|
|
||||||
@Suppress("UnusedPrivateMember")
|
@Suppress("UnusedPrivateMember")
|
||||||
// TODO second markets iteration
|
// TODO second markets iteration
|
||||||
private val securityScoreConverter = SecurityScoreConverter(onInfoClick = onInfoClick)
|
private val securityScoreConverter = SecurityScoreConverter(onInfoClick = onInfoClick)
|
||||||
private val metricsConverter = MetricsConverter(appCurrency = appCurrency, 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)
|
private val linksConverter = LinksConverter(onLinkClick = onLinkClick)
|
||||||
|
|
||||||
override fun convert(value: TokenMarketInfo): MarketsTokenDetailsUM.InformationBlocks {
|
override fun convert(value: TokenMarketInfo): MarketsTokenDetailsUM.InformationBlocks {
|
||||||
|
|
|
||||||
|
|
@ -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.consumedEvent
|
||||||
import com.tangem.core.ui.event.triggeredEvent
|
import com.tangem.core.ui.event.triggeredEvent
|
||||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||||
|
import com.tangem.domain.markets.PriceChangeInterval
|
||||||
import com.tangem.domain.markets.TokenMarketInfo
|
import com.tangem.domain.markets.TokenMarketInfo
|
||||||
import com.tangem.domain.markets.TokenQuotes
|
import com.tangem.domain.markets.TokenQuotes
|
||||||
import com.tangem.features.markets.details.impl.model.converters.PricePerformanceConverter
|
import com.tangem.features.markets.details.impl.model.converters.PricePerformanceConverter
|
||||||
|
|
@ -22,8 +23,12 @@ internal class QuotesStateUpdater(
|
||||||
private val currentQuotes: MutableStateFlow<TokenQuotes>,
|
private val currentQuotes: MutableStateFlow<TokenQuotes>,
|
||||||
private val lastUpdatedTimestamp: MutableStateFlow<Long>,
|
private val lastUpdatedTimestamp: MutableStateFlow<Long>,
|
||||||
private val currentTokenInfo: MutableStateFlow<TokenMarketInfo?>,
|
private val currentTokenInfo: MutableStateFlow<TokenMarketInfo?>,
|
||||||
|
private val onPricePerformanceIntervalChanged: (PriceChangeInterval) -> Unit,
|
||||||
) {
|
) {
|
||||||
private val pricePerformanceConverter = PricePerformanceConverter(currentAppCurrency)
|
private val pricePerformanceConverter = PricePerformanceConverter(
|
||||||
|
currentAppCurrency,
|
||||||
|
onIntervalChanged = onPricePerformanceIntervalChanged,
|
||||||
|
)
|
||||||
|
|
||||||
suspend fun updateQuotes(newQuotes: TokenQuotes) {
|
suspend fun updateQuotes(newQuotes: TokenQuotes) {
|
||||||
val triggerPriceChangeType = getFormattedPriceChange(
|
val triggerPriceChangeType = getFormattedPriceChange(
|
||||||
|
|
|
||||||
|
|
@ -44,11 +44,13 @@ internal fun InfoBottomSheet(config: TangemBottomSheetConfig) {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
AdditionalInfoNotification(
|
if (it.showGeneratedAINotification) {
|
||||||
modifier = Modifier
|
AdditionalInfoNotification(
|
||||||
.padding(top = TangemTheme.dimens.spacing12, bottom = TangemTheme.dimens.spacing16)
|
modifier = Modifier
|
||||||
.fillMaxWidth(),
|
.padding(top = TangemTheme.dimens.spacing12, bottom = TangemTheme.dimens.spacing16)
|
||||||
)
|
.fillMaxWidth(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
SpacerH(bottomBarHeight)
|
SpacerH(bottomBarHeight)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,10 @@ internal fun InsightsBlock(state: InsightsUM, modifier: Modifier = Modifier) {
|
||||||
PriceChangeInterval.MONTH,
|
PriceChangeInterval.MONTH,
|
||||||
),
|
),
|
||||||
initialSelectedItem = PriceChangeInterval.H24,
|
initialSelectedItem = PriceChangeInterval.H24,
|
||||||
onClick = { currentInterval = it },
|
onClick = {
|
||||||
|
currentInterval = it
|
||||||
|
state.onIntervalChanged(it)
|
||||||
|
},
|
||||||
) {
|
) {
|
||||||
Box(
|
Box(
|
||||||
Modifier
|
Modifier
|
||||||
|
|
@ -177,6 +180,7 @@ private fun ContentPreview() {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onInfoClick = {},
|
onInfoClick = {},
|
||||||
|
onIntervalChanged = {},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@ private fun SubBlock(
|
||||||
) {
|
) {
|
||||||
links.fastForEach {
|
links.fastForEach {
|
||||||
Chip(
|
Chip(
|
||||||
text = it.title,
|
text = stringReference(it.title),
|
||||||
iconResId = it.iconRes,
|
iconResId = it.iconRes,
|
||||||
onClick = { onLinkClick(it) },
|
onClick = { onLinkClick(it) },
|
||||||
)
|
)
|
||||||
|
|
@ -163,36 +163,36 @@ private fun ContentPreview() {
|
||||||
state = LinksUM(
|
state = LinksUM(
|
||||||
officialLinks = persistentListOf(
|
officialLinks = persistentListOf(
|
||||||
LinksUM.Link(
|
LinksUM.Link(
|
||||||
title = stringReference("Website"),
|
title = "Website",
|
||||||
iconRes = R.drawable.ic_plus_24,
|
iconRes = R.drawable.ic_plus_24,
|
||||||
url = "https://tangem.com",
|
url = "https://tangem.com",
|
||||||
),
|
),
|
||||||
LinksUM.Link(
|
LinksUM.Link(
|
||||||
title = stringReference("Website"),
|
title = "Website",
|
||||||
iconRes = R.drawable.ic_plus_24,
|
iconRes = R.drawable.ic_plus_24,
|
||||||
url = "https://tangem.com",
|
url = "https://tangem.com",
|
||||||
),
|
),
|
||||||
LinksUM.Link(
|
LinksUM.Link(
|
||||||
title = stringReference("Website"),
|
title = "Website",
|
||||||
iconRes = R.drawable.ic_plus_24,
|
iconRes = R.drawable.ic_plus_24,
|
||||||
url = "https://tangem.com",
|
url = "https://tangem.com",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
social = persistentListOf(
|
social = persistentListOf(
|
||||||
LinksUM.Link(
|
LinksUM.Link(
|
||||||
title = stringReference("Twitter"),
|
title = "Twitter",
|
||||||
iconRes = R.drawable.ic_plus_24,
|
iconRes = R.drawable.ic_plus_24,
|
||||||
url = "https://tangem.com",
|
url = "https://tangem.com",
|
||||||
),
|
),
|
||||||
LinksUM.Link(
|
LinksUM.Link(
|
||||||
title = stringReference("Facebook"),
|
title = "Facebook",
|
||||||
iconRes = R.drawable.ic_plus_24,
|
iconRes = R.drawable.ic_plus_24,
|
||||||
url = "https://tangem.com",
|
url = "https://tangem.com",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
repository = persistentListOf(
|
repository = persistentListOf(
|
||||||
LinksUM.Link(
|
LinksUM.Link(
|
||||||
title = stringReference("Github"),
|
title = "Github",
|
||||||
iconRes = R.drawable.ic_plus_24,
|
iconRes = R.drawable.ic_plus_24,
|
||||||
url = "https://tangem.com",
|
url = "https://tangem.com",
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,10 @@ internal fun PricePerformanceBlock(state: PricePerformanceUM, modifier: Modifier
|
||||||
PriceChangeInterval.ALL_TIME,
|
PriceChangeInterval.ALL_TIME,
|
||||||
),
|
),
|
||||||
initialSelectedItem = PriceChangeInterval.H24,
|
initialSelectedItem = PriceChangeInterval.H24,
|
||||||
onClick = { currentInterval = it },
|
onClick = {
|
||||||
|
currentInterval = it
|
||||||
|
state.onIntervalChanged(it)
|
||||||
|
},
|
||||||
) {
|
) {
|
||||||
Box(
|
Box(
|
||||||
Modifier
|
Modifier
|
||||||
|
|
@ -227,6 +230,7 @@ private fun ContentPreview() {
|
||||||
high = "\$580,5M",
|
high = "\$580,5M",
|
||||||
indicatorFraction = 0.2f,
|
indicatorFraction = 0.2f,
|
||||||
),
|
),
|
||||||
|
onIntervalChanged = {},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,4 +6,5 @@ import com.tangem.core.ui.extensions.TextReference
|
||||||
internal data class InfoBottomSheetContent(
|
internal data class InfoBottomSheetContent(
|
||||||
val title: TextReference,
|
val title: TextReference,
|
||||||
val body: TextReference,
|
val body: TextReference,
|
||||||
|
val showGeneratedAINotification: Boolean = false,
|
||||||
) : TangemBottomSheetConfigContent
|
) : TangemBottomSheetConfigContent
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.tangem.features.markets.details.impl.ui.state
|
package com.tangem.features.markets.details.impl.ui.state
|
||||||
|
|
||||||
|
import com.tangem.domain.markets.PriceChangeInterval
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
|
||||||
internal data class InsightsUM(
|
internal data class InsightsUM(
|
||||||
|
|
@ -7,4 +8,5 @@ internal data class InsightsUM(
|
||||||
val weekInfo: ImmutableList<InfoPointUM>,
|
val weekInfo: ImmutableList<InfoPointUM>,
|
||||||
val monthInfo: ImmutableList<InfoPointUM>,
|
val monthInfo: ImmutableList<InfoPointUM>,
|
||||||
val onInfoClick: () -> Unit,
|
val onInfoClick: () -> Unit,
|
||||||
|
val onIntervalChanged: (PriceChangeInterval) -> Unit,
|
||||||
)
|
)
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package com.tangem.features.markets.details.impl.ui.state
|
package com.tangem.features.markets.details.impl.ui.state
|
||||||
|
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import com.tangem.core.ui.extensions.TextReference
|
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
|
||||||
internal data class LinksUM(
|
internal data class LinksUM(
|
||||||
|
|
@ -13,7 +12,7 @@ internal data class LinksUM(
|
||||||
) {
|
) {
|
||||||
data class Link(
|
data class Link(
|
||||||
@DrawableRes val iconRes: Int,
|
@DrawableRes val iconRes: Int,
|
||||||
val title: TextReference,
|
val title: String,
|
||||||
val url: String,
|
val url: String,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
package com.tangem.features.markets.details.impl.ui.state
|
package com.tangem.features.markets.details.impl.ui.state
|
||||||
|
|
||||||
import androidx.annotation.FloatRange
|
import androidx.annotation.FloatRange
|
||||||
|
import com.tangem.domain.markets.PriceChangeInterval
|
||||||
|
|
||||||
internal data class PricePerformanceUM(
|
internal data class PricePerformanceUM(
|
||||||
val h24: Value,
|
val h24: Value,
|
||||||
val month: Value,
|
val month: Value,
|
||||||
val all: Value,
|
val all: Value,
|
||||||
|
val onIntervalChanged: (PriceChangeInterval) -> Unit,
|
||||||
) {
|
) {
|
||||||
data class Value(
|
data class Value(
|
||||||
val low: String,
|
val low: String,
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,10 @@ internal class DefaultMarketsEntryComponent @AssistedInject constructor(
|
||||||
token = token.toSerializableParam(),
|
token = token.toSerializableParam(),
|
||||||
appCurrency = appCurrency,
|
appCurrency = appCurrency,
|
||||||
showPortfolio = true,
|
showPortfolio = true,
|
||||||
|
analyticsParams = MarketsTokenDetailsComponent.AnalyticsParams(
|
||||||
|
blockchain = null,
|
||||||
|
source = "Market",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,15 @@ import kotlinx.serialization.Serializable
|
||||||
interface MarketsPortfolioComponent : ComposableContentComponent {
|
interface MarketsPortfolioComponent : ComposableContentComponent {
|
||||||
|
|
||||||
@Serializable
|
@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<TokenMarketInfo.Network>)
|
fun setTokenNetworks(networks: List<TokenMarketInfo.Network>)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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<String, String> = 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<String>) = 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)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,7 @@ package com.tangem.features.markets.portfolio.impl.model
|
||||||
|
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
import arrow.core.getOrElse
|
import arrow.core.getOrElse
|
||||||
|
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||||
import com.tangem.core.decompose.di.ComponentScoped
|
import com.tangem.core.decompose.di.ComponentScoped
|
||||||
import com.tangem.core.decompose.model.Model
|
import com.tangem.core.decompose.model.Model
|
||||||
import com.tangem.core.decompose.model.ParamsContainer
|
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.domain.wallets.usecase.GetSelectedWalletUseCase
|
||||||
import com.tangem.features.markets.impl.R
|
import com.tangem.features.markets.impl.R
|
||||||
import com.tangem.features.markets.portfolio.api.MarketsPortfolioComponent
|
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.loader.PortfolioDataLoader
|
||||||
import com.tangem.features.markets.portfolio.impl.ui.WarningDialog
|
import com.tangem.features.markets.portfolio.impl.ui.WarningDialog
|
||||||
import com.tangem.features.markets.portfolio.impl.ui.state.MyPortfolioUM
|
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.Provider
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
|
|
@ -49,12 +52,17 @@ internal class MarketsPortfolioModel @Inject constructor(
|
||||||
private val hasMissedDerivationsUseCase: HasMissedDerivationsUseCase,
|
private val hasMissedDerivationsUseCase: HasMissedDerivationsUseCase,
|
||||||
private val saveMarketTokensUseCase: SaveMarketTokensUseCase,
|
private val saveMarketTokensUseCase: SaveMarketTokensUseCase,
|
||||||
private val addToPortfolioManager: AddToPortfolioManager,
|
private val addToPortfolioManager: AddToPortfolioManager,
|
||||||
|
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||||
) : Model() {
|
) : Model() {
|
||||||
|
|
||||||
val state: StateFlow<MyPortfolioUM> get() = _state
|
val state: StateFlow<MyPortfolioUM> get() = _state
|
||||||
private val _state: MutableStateFlow<MyPortfolioUM> = MutableStateFlow(value = MyPortfolioUM.Loading)
|
private val _state: MutableStateFlow<MyPortfolioUM> = MutableStateFlow(value = MyPortfolioUM.Loading)
|
||||||
|
|
||||||
private val params = paramsContainer.require<MarketsPortfolioComponent.Params>()
|
private val params = paramsContainer.require<MarketsPortfolioComponent.Params>()
|
||||||
|
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 */
|
/** Multi-wallet [UserWalletId] that user uses to add new tokens in AddToPortfolio bottom sheet */
|
||||||
private val selectedMultiWalletIdFlow = MutableStateFlow<UserWalletId?>(value = null)
|
private val selectedMultiWalletIdFlow = MutableStateFlow<UserWalletId?>(value = null)
|
||||||
|
|
@ -72,14 +80,37 @@ internal class MarketsPortfolioModel @Inject constructor(
|
||||||
)
|
)
|
||||||
|
|
||||||
private val factory = MyPortfolioUMFactory(
|
private val factory = MyPortfolioUMFactory(
|
||||||
onAddClick = { onAddToPortfolioBSVisibilityChange(isShow = true) },
|
onAddClick = {
|
||||||
|
onAddToPortfolioBSVisibilityChange(isShow = true)
|
||||||
|
// === Analytics ===
|
||||||
|
analyticsEventHandler.send(
|
||||||
|
analyticsEventBuilder.addToPortfolioClicked(),
|
||||||
|
)
|
||||||
|
},
|
||||||
addToPortfolioBSContentUMFactory = AddToPortfolioBSContentUMFactory(
|
addToPortfolioBSContentUMFactory = AddToPortfolioBSContentUMFactory(
|
||||||
token = params.token,
|
token = params.token,
|
||||||
onAddToPortfolioVisibilityChange = ::onAddToPortfolioBSVisibilityChange,
|
onAddToPortfolioVisibilityChange = ::onAddToPortfolioBSVisibilityChange,
|
||||||
onWalletSelectorVisibilityChange = ::onWalletSelectorVisibilityChange,
|
onWalletSelectorVisibilityChange = ::onWalletSelectorVisibilityChange,
|
||||||
onNetworkSwitchClick = ::onNetworkSwitchClick,
|
onNetworkSwitchClick = ::onNetworkSwitchClick,
|
||||||
onWalletSelect = ::onWalletSelect,
|
onWalletSelect = {
|
||||||
onContinueClick = ::onContinueClick,
|
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 },
|
currentState = Provider { _state.value },
|
||||||
tokenActionsHandler = tokenActionsIntentsFactory.create(
|
tokenActionsHandler = tokenActionsIntentsFactory.create(
|
||||||
|
|
@ -87,6 +118,14 @@ internal class MarketsPortfolioModel @Inject constructor(
|
||||||
updateTokenReceiveBSConfig = { updateBlock ->
|
updateTokenReceiveBSConfig = { updateBlock ->
|
||||||
updateTokensState { it.copy(tokenReceiveBSConfig = updateBlock(it.tokenReceiveBSConfig)) }
|
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 ->
|
updateTokens = { updateBlock ->
|
||||||
updateTokensState { state ->
|
updateTokensState { state ->
|
||||||
|
|
|
||||||
|
|
@ -34,10 +34,9 @@ internal class TokenActionsHandler @AssistedInject constructor(
|
||||||
private val clipboardManager: ClipboardManager,
|
private val clipboardManager: ClipboardManager,
|
||||||
private val uiMessageSender: UiMessageSender,
|
private val uiMessageSender: UiMessageSender,
|
||||||
private val reduxStateHolder: ReduxStateHolder,
|
private val reduxStateHolder: ReduxStateHolder,
|
||||||
@Assisted
|
@Assisted private val currentAppCurrency: Provider<AppCurrency>,
|
||||||
private val currentAppCurrency: Provider<AppCurrency>,
|
@Assisted private val updateTokenReceiveBSConfig: ((TangemBottomSheetConfig) -> TangemBottomSheetConfig) -> Unit,
|
||||||
@Assisted("updateTokenReceiveBSConfig")
|
@Assisted private val onHandleQuickAction: (HandledQuickAction) -> Unit,
|
||||||
private val updateTokenReceiveBSConfig: ((TangemBottomSheetConfig) -> TangemBottomSheetConfig) -> Unit,
|
|
||||||
private val isDemoCardUseCase: IsDemoCardUseCase,
|
private val isDemoCardUseCase: IsDemoCardUseCase,
|
||||||
private val messageSender: UiMessageSender,
|
private val messageSender: UiMessageSender,
|
||||||
) {
|
) {
|
||||||
|
|
@ -48,6 +47,12 @@ internal class TokenActionsHandler @AssistedInject constructor(
|
||||||
)
|
)
|
||||||
|
|
||||||
fun handle(action: TokenActionsBSContentUM.Action, cryptoCurrencyData: PortfolioData.CryptoCurrencyData) {
|
fun handle(action: TokenActionsBSContentUM.Action, cryptoCurrencyData: PortfolioData.CryptoCurrencyData) {
|
||||||
|
onHandleQuickAction(
|
||||||
|
HandledQuickAction(
|
||||||
|
action = action,
|
||||||
|
cryptoCurrencyData = cryptoCurrencyData,
|
||||||
|
),
|
||||||
|
)
|
||||||
if (handleDemoMode(action, cryptoCurrencyData.userWallet)) return
|
if (handleDemoMode(action, cryptoCurrencyData.userWallet)) return
|
||||||
|
|
||||||
when (action) {
|
when (action) {
|
||||||
|
|
@ -177,8 +182,13 @@ internal class TokenActionsHandler @AssistedInject constructor(
|
||||||
interface Factory {
|
interface Factory {
|
||||||
fun create(
|
fun create(
|
||||||
currentAppCurrency: Provider<AppCurrency>,
|
currentAppCurrency: Provider<AppCurrency>,
|
||||||
@Assisted("updateTokenReceiveBSConfig")
|
|
||||||
updateTokenReceiveBSConfig: ((TangemBottomSheetConfig) -> TangemBottomSheetConfig) -> Unit,
|
updateTokenReceiveBSConfig: ((TangemBottomSheetConfig) -> TangemBottomSheetConfig) -> Unit,
|
||||||
|
onHandleQuickAction: (HandledQuickAction) -> Unit,
|
||||||
): TokenActionsHandler
|
): TokenActionsHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class HandledQuickAction(
|
||||||
|
val action: TokenActionsBSContentUM.Action,
|
||||||
|
val cryptoCurrencyData: PortfolioData.CryptoCurrencyData,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -40,6 +40,7 @@ internal class TokenMarketBlockModel @Inject constructor(
|
||||||
) : Model() {
|
) : Model() {
|
||||||
|
|
||||||
private val params = paramsContainer.require<TokenMarketBlockComponent.Params>()
|
private val params = paramsContainer.require<TokenMarketBlockComponent.Params>()
|
||||||
|
|
||||||
private val priceAndTimePointValuesConverter = PriceAndTimePointValuesConverter(needToFormatAxis = false)
|
private val priceAndTimePointValuesConverter = PriceAndTimePointValuesConverter(needToFormatAxis = false)
|
||||||
|
|
||||||
private val currentAppCurrency = getSelectedAppCurrencyUseCase()
|
private val currentAppCurrency = getSelectedAppCurrencyUseCase()
|
||||||
|
|
@ -56,7 +57,7 @@ internal class TokenMarketBlockModel @Inject constructor(
|
||||||
|
|
||||||
val state = MutableStateFlow(
|
val state = MutableStateFlow(
|
||||||
TokenMarketBlockUM(
|
TokenMarketBlockUM(
|
||||||
currencySymbol = params.tokenSymbol,
|
currencySymbol = params.cryptoCurrency.symbol,
|
||||||
currentPrice = null,
|
currentPrice = null,
|
||||||
h24Percent = null,
|
h24Percent = null,
|
||||||
priceChangeType = PriceChangeType.NEUTRAL,
|
priceChangeType = PriceChangeType.NEUTRAL,
|
||||||
|
|
@ -72,7 +73,7 @@ internal class TokenMarketBlockModel @Inject constructor(
|
||||||
private fun startFetching() {
|
private fun startFetching() {
|
||||||
modelScope.launch {
|
modelScope.launch {
|
||||||
getTokenQuotesUseCase(
|
getTokenQuotesUseCase(
|
||||||
currencyID = params.cryptoCurrencyID,
|
currencyID = params.cryptoCurrency.id,
|
||||||
interval = PriceChangeInterval.H24,
|
interval = PriceChangeInterval.H24,
|
||||||
refresh = true,
|
refresh = true,
|
||||||
).collect {
|
).collect {
|
||||||
|
|
@ -100,9 +101,12 @@ internal class TokenMarketBlockModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}.saveIn(quotesUpdateJobHolder)
|
}.saveIn(quotesUpdateJobHolder)
|
||||||
|
|
||||||
|
val tokenId = params.cryptoCurrency.id.rawCurrencyId ?: return
|
||||||
|
|
||||||
modelScope.launch(dispatchers.main) {
|
modelScope.launch(dispatchers.main) {
|
||||||
val result = getTokenPriceChartUseCase(
|
val result = getTokenPriceChartUseCase(
|
||||||
tokenId = params.tokenId,
|
tokenId = tokenId,
|
||||||
|
tokenSymbol = params.cryptoCurrency.symbol,
|
||||||
interval = PriceChangeInterval.H24,
|
interval = PriceChangeInterval.H24,
|
||||||
appCurrency = currentAppCurrency.value, // TODO get currency from quotes use case [REDACTED_TASK_KEY]
|
appCurrency = currentAppCurrency.value, // TODO get currency from quotes use case [REDACTED_TASK_KEY]
|
||||||
preview = true,
|
preview = true,
|
||||||
|
|
@ -128,12 +132,13 @@ internal class TokenMarketBlockModel @Inject constructor(
|
||||||
|
|
||||||
private fun navigateToMarketDetails() {
|
private fun navigateToMarketDetails() {
|
||||||
val quotes = quotesState ?: return
|
val quotes = quotesState ?: return
|
||||||
|
val tokenId = params.cryptoCurrency.id.rawCurrencyId ?: return
|
||||||
|
|
||||||
val tokenParam = TokenMarketParams(
|
val tokenParam = TokenMarketParams(
|
||||||
id = params.tokenId,
|
id = tokenId,
|
||||||
name = params.tokenSymbol,
|
name = params.cryptoCurrency.name,
|
||||||
imageUrl = params.tokenImageUrl,
|
imageUrl = params.cryptoCurrency.iconUrl,
|
||||||
symbol = params.tokenSymbol,
|
symbol = params.cryptoCurrency.symbol,
|
||||||
tokenQuotes = TokenMarketParams.Quotes(
|
tokenQuotes = TokenMarketParams.Quotes(
|
||||||
currentPrice = quotes.currentPrice,
|
currentPrice = quotes.currentPrice,
|
||||||
h24Percent = quotes.h24Percent,
|
h24Percent = quotes.h24Percent,
|
||||||
|
|
@ -147,6 +152,10 @@ internal class TokenMarketBlockModel @Inject constructor(
|
||||||
token = tokenParam,
|
token = tokenParam,
|
||||||
appCurrency = currentAppCurrency.value,
|
appCurrency = currentAppCurrency.value,
|
||||||
showPortfolio = false,
|
showPortfolio = false,
|
||||||
|
analyticsParams = AppRoute.MarketsTokenDetails.AnalyticsParams(
|
||||||
|
blockchain = params.cryptoCurrency.network.name,
|
||||||
|
source = "Token",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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<String, String> = 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"
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,7 @@ package com.tangem.features.markets.tokenlist.impl.model
|
||||||
|
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
import arrow.core.getOrElse
|
import arrow.core.getOrElse
|
||||||
|
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||||
import com.tangem.core.decompose.di.ComponentScoped
|
import com.tangem.core.decompose.di.ComponentScoped
|
||||||
import com.tangem.core.decompose.model.Model
|
import com.tangem.core.decompose.model.Model
|
||||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
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.GetMarketsTokenListFlowUseCase
|
||||||
import com.tangem.domain.markets.TokenMarket
|
import com.tangem.domain.markets.TokenMarket
|
||||||
import com.tangem.features.markets.entry.BottomSheetState
|
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.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.ListUM
|
||||||
import com.tangem.features.markets.tokenlist.impl.ui.state.MarketsListItemUM
|
import com.tangem.features.markets.tokenlist.impl.ui.state.MarketsListItemUM
|
||||||
import com.tangem.features.markets.tokenlist.impl.ui.state.SortByTypeUM
|
import com.tangem.features.markets.tokenlist.impl.ui.state.SortByTypeUM
|
||||||
|
|
@ -32,6 +34,7 @@ internal class MarketsListModel @Inject constructor(
|
||||||
override val dispatchers: CoroutineDispatcherProvider,
|
override val dispatchers: CoroutineDispatcherProvider,
|
||||||
getMarketsTokenListFlowUseCase: GetMarketsTokenListFlowUseCase,
|
getMarketsTokenListFlowUseCase: GetMarketsTokenListFlowUseCase,
|
||||||
getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||||
|
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||||
) : Model() {
|
) : Model() {
|
||||||
|
|
||||||
private var updateQuotesJob = JobHolder()
|
private var updateQuotesJob = JobHolder()
|
||||||
|
|
@ -221,10 +224,30 @@ internal class MarketsListModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// analytics
|
||||||
|
initAnalytics()
|
||||||
|
|
||||||
// initial loading
|
// initial loading
|
||||||
mainMarketsListManager.reload()
|
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) {
|
private fun onTokenUIClicked(token: MarketsListItemUM) {
|
||||||
modelScope.launch {
|
modelScope.launch {
|
||||||
activeListManager.getTokenById(token.id)?.let { found ->
|
activeListManager.getTokenById(token.id)?.let { found ->
|
||||||
|
|
|
||||||
|
|
@ -85,15 +85,9 @@ internal class TokenDetailsFragment : ComposeFragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun CryptoCurrency.toParam(): TokenMarketBlockComponent.Params? {
|
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(
|
return TokenMarketBlockComponent.Params(cryptoCurrency = this)
|
||||||
cryptoCurrencyID = id,
|
|
||||||
tokenId = tokenId,
|
|
||||||
tokenName = name,
|
|
||||||
tokenSymbol = symbol,
|
|
||||||
tokenImageUrl = iconUrl,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
|
||||||
|
|
@ -403,6 +403,10 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
||||||
token = tokenMarketParams,
|
token = tokenMarketParams,
|
||||||
appCurrency = getSelectedAppCurrencyUseCase.unwrap(),
|
appCurrency = getSelectedAppCurrencyUseCase.unwrap(),
|
||||||
showPortfolio = false,
|
showPortfolio = false,
|
||||||
|
analyticsParams = AppRoute.MarketsTokenDetails.AnalyticsParams(
|
||||||
|
blockchain = cryptoCurrencyStatus.currency.network.name,
|
||||||
|
source = "Main",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue