Updated on 2026-08-14
This commit is contained in:
parent
b32cc4a928
commit
2ba0f3d4a1
42 changed files with 581 additions and 74 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 <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.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<Int, List<TokenMarket>, TokenMarketUpdateRequest> {
|
||||
|
||||
override suspend fun BatchUpdateFetcher.UpdateContext<Int, List<TokenMarket>>.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 <T> catchApiError(onError: () -> Unit, block: () -> T): T {
|
||||
return try {
|
||||
block()
|
||||
} catch (e: ApiResponseError) {
|
||||
onError()
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val quoteFields = listOf(
|
||||
"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
|
||||
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue