Updated on 2026-08-14
This commit is contained in:
parent
46ff455a6f
commit
e3eada68fd
35 changed files with 757 additions and 13 deletions
1
data/markets/.gitignore
vendored
Normal file
1
data/markets/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
32
data/markets/build.gradle.kts
Normal file
32
data/markets/build.gradle.kts
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
plugins {
|
||||
alias(deps.plugins.android.library)
|
||||
alias(deps.plugins.kotlin.android)
|
||||
alias(deps.plugins.kotlin.kapt)
|
||||
alias(deps.plugins.hilt.android)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.tangem.data.markets"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.core.datasource)
|
||||
implementation(projects.core.utils)
|
||||
implementation(projects.core.pagination)
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.markets)
|
||||
|
||||
// region DI
|
||||
implementation(deps.hilt.android)
|
||||
kapt(deps.hilt.kapt)
|
||||
// endregion
|
||||
|
||||
// region Others dependencies
|
||||
implementation(deps.kotlin.coroutines)
|
||||
implementation(deps.moshi)
|
||||
implementation(deps.moshi.kotlin)
|
||||
|
||||
implementation(projects.libs.blockchainSdk)
|
||||
// endregion
|
||||
}
|
||||
|
|
@ -0,0 +1,121 @@
|
|||
package com.tangem.data.markets
|
||||
|
||||
import com.tangem.data.markets.converters.*
|
||||
import com.tangem.datasource.api.common.response.getOrThrow
|
||||
import com.tangem.datasource.api.markets.TangemTechMarketsApi
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.domain.markets.*
|
||||
import com.tangem.domain.markets.repositories.MarketsTokenRepository
|
||||
import com.tangem.pagination.*
|
||||
import com.tangem.pagination.fetcher.LimitOffsetBatchFetcher
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
internal class DefaultMarketsTokenRepository(
|
||||
private val marketsApi: TangemTechMarketsApi,
|
||||
private val tangemTechApi: TangemTechApi,
|
||||
private val dispatcherProvider: CoroutineDispatcherProvider,
|
||||
) : MarketsTokenRepository {
|
||||
|
||||
private val tokenListConverter = TokenMarketListConverter()
|
||||
private val tokenListChartsConverter = TokenMarketChartsConverter(TokenListChartConverter())
|
||||
private val tokenQuotesConverter = TokenQuotesConverter()
|
||||
|
||||
private val tokenMarketsFetcher
|
||||
get() = LimitOffsetBatchFetcher<TokenMarketListConfig, List<TokenMarket>>(
|
||||
prefetchDistance = 50,
|
||||
batchSize = 30,
|
||||
fetch = { params ->
|
||||
withContext(dispatcherProvider.io) {
|
||||
val res = marketsApi.getCoinsList(
|
||||
currency = params.request.fiatPriceCurrency,
|
||||
interval = params.request.priceChangeInterval.toRequestParam(),
|
||||
order = params.request.priceChangeInterval.toRequestParam(),
|
||||
search = params.request.searchText,
|
||||
generalCoins = params.request.showUnder100kMarketCapTokens.not(),
|
||||
offset = params.offset,
|
||||
limit = params.limit,
|
||||
).getOrThrow()
|
||||
|
||||
val last = res.tokens.size < params.limit
|
||||
|
||||
BatchFetchResult.Success(
|
||||
data = tokenListConverter.convert(res),
|
||||
last = last,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
private val tokenMarketsUpdateFetcher
|
||||
get() = BatchUpdateFetcher<Int, List<TokenMarket>, TokenMarketUpdateRequest> { toUpdate, updateRequest ->
|
||||
withContext(dispatcherProvider.io) {
|
||||
val idsToUpdate = toUpdate.map { batch ->
|
||||
batch.data.map { it.id }
|
||||
}.flatten()
|
||||
|
||||
val updatedBatches = when (updateRequest) {
|
||||
is TokenMarketUpdateRequest.UpdateChart -> {
|
||||
val res = marketsApi.getCoinsListCharts(
|
||||
coinIds = idsToUpdate,
|
||||
interval = updateRequest.interval.toRequestParam(),
|
||||
currency = updateRequest.currency,
|
||||
).getOrThrow()
|
||||
|
||||
toUpdate.map { batch ->
|
||||
batch.copy(
|
||||
data = batch.data.map {
|
||||
it.copy(
|
||||
tokenCharts = tokenListChartsConverter.convert(
|
||||
chartsToCopy = it.tokenCharts,
|
||||
tokenId = it.id,
|
||||
interval = updateRequest.interval,
|
||||
value = res,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
is TokenMarketUpdateRequest.UpdateQuotes -> {
|
||||
val quotesRes = tangemTechApi.getQuotes(
|
||||
currencyId = updateRequest.currencyId,
|
||||
coinIds = idsToUpdate.joinToString(separator = ","),
|
||||
fields = quoteFields.joinToString(separator = ","),
|
||||
).getOrThrow()
|
||||
|
||||
toUpdate.map { batch ->
|
||||
batch.copy(
|
||||
data = batch.data.map {
|
||||
it.copy(tokenQuotes = tokenQuotesConverter.convert(it.id, quotesRes))
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BatchUpdateResult.Success(updatedBatches)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getTokenListFlow(
|
||||
batchingContext: BatchingContext<Int, TokenMarketListConfig, TokenMarketUpdateRequest>,
|
||||
): BatchFlow<Int, List<TokenMarket>, TokenMarketUpdateRequest> {
|
||||
return BatchListSource(
|
||||
fetchDispatcher = dispatcherProvider.io,
|
||||
context = batchingContext,
|
||||
generateNewKey = { it.size },
|
||||
batchFetcher = tokenMarketsFetcher,
|
||||
updateFetcher = tokenMarketsUpdateFetcher,
|
||||
).toBatchFlow()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val quoteFields = listOf(
|
||||
"price",
|
||||
"priceChange24h",
|
||||
"priceChange1w",
|
||||
"priceChange30d",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.data.markets.converters
|
||||
|
||||
import com.tangem.datasource.api.markets.models.response.TokenMarketChartResponse
|
||||
import com.tangem.domain.markets.PriceChangeInterval
|
||||
import com.tangem.domain.markets.TokenChart
|
||||
|
||||
class TokenListChartConverter {
|
||||
|
||||
fun convert(interval: PriceChangeInterval, value: TokenMarketChartResponse): TokenChart {
|
||||
return TokenChart(
|
||||
interval = interval,
|
||||
priceY = value.prices.values.toList(),
|
||||
timeStamp = value.prices.keys.toList(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package com.tangem.data.markets.converters
|
||||
|
||||
import com.tangem.domain.markets.PriceChangeInterval
|
||||
import com.tangem.domain.markets.TokenMarketListConfig
|
||||
|
||||
fun TokenMarketListConfig.Interval.toRequestParam(): String = when (this) {
|
||||
TokenMarketListConfig.Interval.H24 -> "24h"
|
||||
TokenMarketListConfig.Interval.WEEK -> "1w"
|
||||
TokenMarketListConfig.Interval.MONTH -> "30d"
|
||||
}
|
||||
|
||||
fun TokenMarketListConfig.Order.toRequestParam(): String = when (this) {
|
||||
TokenMarketListConfig.Order.ByRating -> "rating"
|
||||
TokenMarketListConfig.Order.Trending -> "trending"
|
||||
TokenMarketListConfig.Order.Buyers -> "buyers"
|
||||
TokenMarketListConfig.Order.TopGainers -> "gainers"
|
||||
TokenMarketListConfig.Order.TopLosers -> "losers"
|
||||
}
|
||||
|
||||
fun PriceChangeInterval.toRequestParam(): String = when (this) {
|
||||
PriceChangeInterval.H24 -> "24h"
|
||||
PriceChangeInterval.WEEK -> "1w"
|
||||
PriceChangeInterval.MONTH -> "1m"
|
||||
PriceChangeInterval.MONTH3 -> "3m"
|
||||
PriceChangeInterval.MONTH6 -> "6m"
|
||||
PriceChangeInterval.YEAR -> "1y"
|
||||
PriceChangeInterval.ALL_TIME -> "all"
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package com.tangem.data.markets.converters
|
||||
|
||||
import com.tangem.datasource.api.markets.models.response.TokenMarketChartListResponse
|
||||
import com.tangem.domain.markets.PriceChangeInterval
|
||||
import com.tangem.domain.markets.TokenMarket
|
||||
|
||||
class TokenMarketChartsConverter(
|
||||
private val tokenListChartConverter: TokenListChartConverter,
|
||||
) {
|
||||
|
||||
fun convert(
|
||||
chartsToCopy: TokenMarket.Charts,
|
||||
tokenId: String,
|
||||
interval: PriceChangeInterval,
|
||||
value: TokenMarketChartListResponse,
|
||||
): TokenMarket.Charts {
|
||||
val prices = requireNotNull(value.tokens[tokenId]) {
|
||||
"$tokenId is not found in the response. This shouldn't have happened."
|
||||
}
|
||||
return when (interval) {
|
||||
PriceChangeInterval.H24 -> chartsToCopy.copy(
|
||||
h24 = tokenListChartConverter.convert(interval, prices),
|
||||
)
|
||||
PriceChangeInterval.WEEK -> chartsToCopy.copy(
|
||||
week = tokenListChartConverter.convert(interval, prices),
|
||||
)
|
||||
PriceChangeInterval.MONTH -> chartsToCopy.copy(
|
||||
month = tokenListChartConverter.convert(interval, prices),
|
||||
)
|
||||
else -> error("unsupported interval=$interval. This shouldn't have happened.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package com.tangem.data.markets.converters
|
||||
|
||||
import com.tangem.datasource.api.markets.models.response.TokenMarketListResponse
|
||||
import com.tangem.domain.markets.PriceChangeInterval
|
||||
import com.tangem.domain.markets.TokenMarket
|
||||
import com.tangem.domain.markets.TokenQuotes
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
class TokenMarketListConverter : Converter<TokenMarketListResponse, List<TokenMarket>> {
|
||||
|
||||
override fun convert(value: TokenMarketListResponse): List<TokenMarket> {
|
||||
return value.tokens.map { token ->
|
||||
TokenMarket(
|
||||
id = token.id,
|
||||
name = token.name,
|
||||
symbol = token.symbol,
|
||||
marketRating = token.marketRating,
|
||||
marketCap = token.marketCap,
|
||||
imageHost = value.imageHost,
|
||||
tokenQuotes = TokenQuotes(
|
||||
currentPrice = token.currentPrice,
|
||||
priceChanges = mapOf(
|
||||
PriceChangeInterval.H24 to token.priceChangePercentage.h24,
|
||||
PriceChangeInterval.WEEK to token.priceChangePercentage.week1,
|
||||
PriceChangeInterval.MONTH to token.priceChangePercentage.day30,
|
||||
),
|
||||
),
|
||||
tokenCharts = TokenMarket.Charts(null, null, null),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package com.tangem.data.markets.converters
|
||||
|
||||
import com.tangem.datasource.api.tangemTech.models.QuotesResponse
|
||||
import com.tangem.domain.markets.PriceChangeInterval
|
||||
import com.tangem.domain.markets.TokenQuotes
|
||||
|
||||
class TokenQuotesConverter {
|
||||
|
||||
fun convert(tokenId: String, value: QuotesResponse): TokenQuotes {
|
||||
val quote = requireNotNull(value.quotes[tokenId]) {
|
||||
"$tokenId is not found in the response. This shouldn't have happened."
|
||||
}
|
||||
return TokenQuotes(
|
||||
currentPrice = requireNotNull(quote.price) {
|
||||
"Price is not found in the QuotesResponse. This shouldn't have happened."
|
||||
},
|
||||
priceChanges = mapOf(
|
||||
PriceChangeInterval.H24 to requireNotNull(quote.priceChange1w) {
|
||||
"priceChange1w is not found in the QuotesResponse. This shouldn't have happened."
|
||||
},
|
||||
PriceChangeInterval.WEEK to requireNotNull(quote.priceChange1w) {
|
||||
"priceChange1w is not found in the QuotesResponse. This shouldn't have happened."
|
||||
},
|
||||
PriceChangeInterval.MONTH to requireNotNull(quote.priceChange30d) {
|
||||
"priceChange30d is not found in the QuotesResponse. This shouldn't have happened."
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package com.tangem.data.markets.di
|
||||
|
||||
import com.tangem.data.markets.DefaultMarketsTokenRepository
|
||||
import com.tangem.datasource.api.markets.TangemTechMarketsApi
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.domain.markets.repositories.MarketsTokenRepository
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object MarketsDataModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideMarketsRepository(
|
||||
marketsApi: TangemTechMarketsApi,
|
||||
tangemTechApi: TangemTechApi,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): MarketsTokenRepository {
|
||||
return DefaultMarketsTokenRepository(
|
||||
marketsApi = marketsApi,
|
||||
tangemTechApi = tangemTechApi,
|
||||
dispatcherProvider = dispatchers,
|
||||
)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue