Updated on 2026-08-14

This commit is contained in:
Tangem 2024-11-21 12:09:33 +03:00
commit 85423227c4
390 changed files with 11140 additions and 1722 deletions

View file

@ -33,6 +33,7 @@ dependencies {
// region Others dependencies
implementation(deps.kotlin.coroutines)
implementation(deps.jodatime)
implementation(deps.moshi)
implementation(deps.moshi.kotlin)
implementation(deps.timber)

View file

@ -5,6 +5,7 @@ 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.cache.CacheRegistry
import com.tangem.data.common.currency.CryptoCurrencyFactory
import com.tangem.data.common.currency.getNetwork
import com.tangem.data.common.utils.retryOnError
@ -13,8 +14,10 @@ import com.tangem.data.markets.converters.*
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.TokenMarketExchangesResponse
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.api.tangemTech.TangemTechApi.Companion.marketsQuoteFields
import com.tangem.datasource.local.datastore.RuntimeStateStore
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.markets.*
import com.tangem.domain.markets.repositories.MarketsTokenRepository
@ -27,12 +30,15 @@ import kotlinx.coroutines.withContext
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.atomic.AtomicLong
@Suppress("LongParameterList")
internal class DefaultMarketsTokenRepository(
private val marketsApi: TangemTechMarketsApi,
private val tangemTechApi: TangemTechApi,
private val userWalletsStore: UserWalletsStore,
private val dispatcherProvider: CoroutineDispatcherProvider,
private val analyticsEventHandler: AnalyticsEventHandler,
private val cacheRegistry: CacheRegistry,
private val tokenExchangesStore: RuntimeStateStore<List<TokenMarketExchangesResponse.Exchange>>,
) : MarketsTokenRepository {
private fun createTokenMarketsFetcher(firstBatchSize: Int, nextBatchSize: Int) = LimitOffsetBatchFetcher(
@ -253,9 +259,13 @@ internal class DefaultMarketsTokenRepository(
override suspend fun getTokenExchanges(tokenId: String): List<TokenMarketExchange> {
return withContext(dispatcherProvider.io) {
val response = marketsApi.getCoinExchanges(coinId = tokenId).getOrThrow()
cacheRegistry.invokeOnExpire(key = "coins/$tokenId/exchanges", skipCache = false) {
val response = marketsApi.getCoinExchanges(coinId = tokenId).getOrThrow()
TokenMarketExchangeConverter.convertList(input = response.exchanges)
tokenExchangesStore.store(value = response.exchanges)
}
TokenMarketExchangeConverter.convertList(input = tokenExchangesStore.get().value)
}
}

View file

@ -1,9 +1,11 @@
package com.tangem.data.markets.converters
import android.net.Uri
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchainsdk.utils.fromNetworkId
import com.tangem.blockchainsdk.utils.isSupportedInApp
import com.tangem.datasource.api.markets.models.response.TokenMarketInfoResponse
import com.tangem.domain.markets.BuildConfig
import com.tangem.domain.markets.TokenMarketInfo
import com.tangem.domain.markets.TokenQuotes
import com.tangem.utils.converter.Converter
@ -11,6 +13,9 @@ import java.math.BigDecimal
internal object TokenMarketInfoConverter : Converter<TokenMarketInfoResponse, TokenMarketInfo> {
private const val PROD_IMAGE_HOST = "https://s3.eu-central-1.amazonaws.com/tangem.api/security_provider/"
private const val DEV_IMAGE_HOST = "https://s3.eu-central-1.amazonaws.com/tangem.api.dev/security_provider/"
override fun convert(value: TokenMarketInfoResponse): TokenMarketInfo {
return with(value) {
TokenMarketInfo(
@ -23,6 +28,7 @@ internal object TokenMarketInfoConverter : Converter<TokenMarketInfoResponse, To
fullDescription = fullDescription,
insights = insights?.convert(),
metrics = metrics?.convert(),
securityData = securityData?.convert(),
links = links?.convert(),
pricePerformance = pricePerformance?.convert(),
exchangesAmount = exchangesAmount,
@ -105,6 +111,31 @@ internal object TokenMarketInfoConverter : Converter<TokenMarketInfoResponse, To
)
}
private fun TokenMarketInfoResponse.SecurityData.convert(): TokenMarketInfo.SecurityData {
return TokenMarketInfo.SecurityData(
totalSecurityScore = totalSecurityScore,
securityScoreProviderData = providerData.map { it.convert() },
)
}
private fun TokenMarketInfoResponse.ProviderData.convert(): TokenMarketInfo.SecurityScoreProvider {
return TokenMarketInfo.SecurityScoreProvider(
providerId = providerId,
providerName = providerName,
urlData = link?.convertToUrlData(),
securityScore = securityScore,
lastAuditDate = lastAuditDate,
iconUrl = "${getImageHost()}large/$providerId.png",
)
}
private fun String.convertToUrlData(): TokenMarketInfo.SecurityScoreProvider.UrlData {
return TokenMarketInfo.SecurityScoreProvider.UrlData(
fullUrl = this,
rootHost = this.extractMainDomainOrNull(),
)
}
private fun TokenMarketInfoResponse.Links.convert(): TokenMarketInfo.Links {
return TokenMarketInfo.Links(
officialLinks = officialLinks?.convert(),
@ -139,4 +170,27 @@ internal object TokenMarketInfoConverter : Converter<TokenMarketInfoResponse, To
high = high,
)
}
private fun String.extractMainDomainOrNull(): String? {
return runCatching {
val uri = Uri.parse(this)
val host = uri.host ?: return null
val parts = host.split(".")
if (parts.size >= 2) {
parts.takeLast(2).joinToString(".")
} else {
host
}
}.getOrNull()
}
private fun getImageHost(): String {
return if (BuildConfig.TESTER_MENU_ENABLED) {
DEV_IMAGE_HOST
} else {
PROD_IMAGE_HOST
}
}
}

View file

@ -1,9 +1,11 @@
package com.tangem.data.markets.di
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.data.common.cache.CacheRegistry
import com.tangem.data.markets.DefaultMarketsTokenRepository
import com.tangem.datasource.api.markets.TangemTechMarketsApi
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.local.datastore.RuntimeStateStore
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.markets.repositories.MarketsTokenRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
@ -25,6 +27,7 @@ internal object MarketsDataModule {
userWalletsStore: UserWalletsStore,
dispatchers: CoroutineDispatcherProvider,
analyticsEventHandler: AnalyticsEventHandler,
cacheRegistry: CacheRegistry,
): MarketsTokenRepository {
return DefaultMarketsTokenRepository(
marketsApi = marketsApi,
@ -32,6 +35,8 @@ internal object MarketsDataModule {
dispatcherProvider = dispatchers,
userWalletsStore = userWalletsStore,
analyticsEventHandler = analyticsEventHandler,
cacheRegistry = cacheRegistry,
tokenExchangesStore = RuntimeStateStore(defaultValue = emptyList()),
)
}
}