Updated on 2026-08-14

This commit is contained in:
Tangem 2024-11-21 12:12:33 +07:00
parent 82eda99ca7
commit 5aa2af25f1
9 changed files with 110 additions and 55 deletions

View file

@ -45,7 +45,7 @@ internal class DefaultQuotesRepository(
.filterNotNull()
.flatMapLatest { appCurrency ->
fetchExpiredQuotes(currenciesIds, appCurrency.id, refresh = refresh)
quotesStore.get(currenciesIds).map(quotesConverter::convertSet)
quotesStore.get(currenciesIds).map { quotesConverter.convert(currenciesIds to it) }
}
.cancellable()
.flowOn(dispatchers.io)
@ -77,14 +77,15 @@ internal class DefaultQuotesRepository(
val quotes = quotesStore.getSync(currenciesIds)
quotesConverter.convertSet(quotes)
quotesConverter.convert(currenciesIds to quotes)
}
}
override suspend fun getQuoteSync(currencyId: CryptoCurrency.ID): Quote? {
return withContext(dispatchers.io) {
val quote = quotesStore.getSync(setOf(currencyId)).firstOrNull()
quote?.let { quotesConverter.convert(it) }
val setOfCurrencyId = setOf(currencyId)
val quote = quotesStore.getSync(setOfCurrencyId).firstOrNull()
quote?.let { quotesConverter.convert(setOfCurrencyId to setOf(it)).firstOrNull() }
}
}

View file

@ -1,16 +1,28 @@
package com.tangem.data.tokens.utils
import com.tangem.datasource.local.quote.model.StoredQuote
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.Quote
import com.tangem.utils.converter.Converter
import java.math.BigDecimal
internal class QuotesConverter : Converter<StoredQuote, Quote> {
typealias QuotesConverterValue = Pair<Set<CryptoCurrency.ID>, Set<StoredQuote>>
override fun convert(value: StoredQuote): Quote {
val (rawCurrencyId, responseQuote) = value
internal class QuotesConverter : Converter<QuotesConverterValue, Set<Quote>> {
return Quote(
override fun convert(value: QuotesConverterValue): Set<Quote> {
val (setOfCurrencyId, setOfStoredQuote) = value
return setOfCurrencyId.mapTo(hashSetOf()) { id ->
setOfStoredQuote.find { id.rawCurrencyId == it.rawCurrencyId }
?.let(::convertExistStoredQuote)
?: Quote.Empty(id.rawCurrencyId)
}
}
private fun convertExistStoredQuote(storedQuote: StoredQuote): Quote {
val (rawCurrencyId, responseQuote) = storedQuote
return Quote.Value(
rawCurrencyId = rawCurrencyId,
fiatRate = responseQuote.price ?: BigDecimal.ZERO,
priceChange = (responseQuote.priceChange24h ?: BigDecimal.ZERO).movePointLeft(2),

View file

@ -15,10 +15,10 @@ class GetCurrencyQuotesUseCase(
currencyID: CryptoCurrency.ID,
interval: PriceChangeInterval,
refresh: Boolean,
): Flow<Option<Quote>> {
): Flow<Option<Quote.Value>> {
return quotesRepository.getQuotesUpdates(
currenciesIds = setOf(currencyID),
refresh = refresh,
).map { it.firstOrNull().toOption() }.catch { emit(None) }
).map { it.filterIsInstance<Quote.Value>().firstOrNull().toOption() }.catch { emit(None) }
}
}

View file

@ -2,15 +2,27 @@ package com.tangem.domain.tokens.model
import java.math.BigDecimal
/**
* Represents financial information for a specific cryptocurrency, including its fiat exchange rate and price change.
*
* @property rawCurrencyId The unique identifier of the cryptocurrency for which the financial information is provided.
* @property fiatRate The current fiat exchange rate for the cryptocurrency.
* @property priceChange The price change for the cryptocurrency.
*/
data class Quote(
val rawCurrencyId: String,
val fiatRate: BigDecimal,
val priceChange: BigDecimal,
)
sealed interface Quote {
val rawCurrencyId: String?
/**
* Represents unknown financial information for a specific cryptocurrency.
*
* @property rawCurrencyId The raw cryptocurrency ID. If it is a custom token, the value will be `null`.
*/
data class Empty(override val rawCurrencyId: String?) : Quote
/**
* Represents financial information for a specific cryptocurrency, including its fiat exchange rate and price change.
*
* @property rawCurrencyId The unique identifier of the cryptocurrency for which the financial information is provided.
* @property fiatRate The current fiat exchange rate for the cryptocurrency.
* @property priceChange The price change for the cryptocurrency.
*/
data class Value(
override val rawCurrencyId: String,
val fiatRate: BigDecimal,
val priceChange: BigDecimal,
) : Quote
}

View file

@ -12,6 +12,18 @@ internal class CurrencyStatusOperations(
private val ignoreQuote: Boolean,
) {
private val Quote?.fiatRate: BigDecimal?
get() = when (this) {
is Quote.Value -> this.fiatRate
is Quote.Empty, null -> null
}
private val Quote?.priceChange: BigDecimal?
get() = when (this) {
is Quote.Value -> this.priceChange
is Quote.Empty, null -> null
}
fun createTokenStatus(): CryptoCurrencyStatus = CryptoCurrencyStatus(currency, createStatus())
private fun createStatus(): CryptoCurrencyStatus.Value {
@ -74,7 +86,7 @@ internal class CurrencyStatusOperations(
null
}
return when {
ignoreQuote -> CryptoCurrencyStatus.NoQuote(
quote is Quote.Empty || ignoreQuote -> CryptoCurrencyStatus.NoQuote(
amount = amount,
hasCurrentNetworkTransactions = hasCurrentNetworkTransactions,
pendingTransactions = currentTransactions,
@ -91,8 +103,7 @@ internal class CurrencyStatusOperations(
networkAddress = status.address,
yieldBalance = currentYieldBalance,
)
quote == null -> CryptoCurrencyStatus.Loading
else -> CryptoCurrencyStatus.Loaded(
quote is Quote.Value -> CryptoCurrencyStatus.Loaded(
amount = amount,
fiatAmount = calculateFiatAmount(amount, quote.fiatRate),
fiatRate = quote.fiatRate,
@ -102,6 +113,7 @@ internal class CurrencyStatusOperations(
networkAddress = status.address,
yieldBalance = currentYieldBalance,
)
else -> CryptoCurrencyStatus.Loading
}
}

View file

@ -7,65 +7,71 @@ import java.math.BigDecimal
@Suppress("MemberVisibilityCanBePrivate")
internal object MockQuotes {
val quote1 = Quote(
val quote1 = Quote.Value(
rawCurrencyId = MockTokens.token1.id.rawCurrencyId!!,
fiatRate = BigDecimal("1.23"),
priceChange = BigDecimal("0.01"),
)
val quote2 = Quote(
val quote2 = Quote.Value(
rawCurrencyId = MockTokens.token2.id.rawCurrencyId!!,
fiatRate = BigDecimal("2.34"),
priceChange = BigDecimal("-0.02"),
)
val quote3 = Quote(
val quote3 = Quote.Value(
rawCurrencyId = MockTokens.token3.id.rawCurrencyId!!,
fiatRate = BigDecimal("3.45"),
priceChange = BigDecimal("0.03"),
)
val quote4 = Quote(
val quote4 = Quote.Value(
rawCurrencyId = MockTokens.token4.id.rawCurrencyId!!,
fiatRate = BigDecimal("4.56"),
priceChange = BigDecimal("-0.04"),
)
val quote5 = Quote(
val quote5 = Quote.Value(
rawCurrencyId = MockTokens.token5.id.rawCurrencyId!!,
fiatRate = BigDecimal("5.67"),
priceChange = BigDecimal("0.05"),
)
val quote6 = Quote(
val quote6 = Quote.Value(
rawCurrencyId = MockTokens.token6.id.rawCurrencyId!!,
fiatRate = BigDecimal("6.78"),
priceChange = BigDecimal("-0.06"),
)
val quote7 = Quote(
val quote7 = Quote.Value(
rawCurrencyId = MockTokens.token7.id.rawCurrencyId!!,
fiatRate = BigDecimal("7.89"),
priceChange = BigDecimal("0.07"),
)
val quote8 = Quote(
val quote8 = Quote.Value(
rawCurrencyId = MockTokens.token8.id.rawCurrencyId!!,
fiatRate = BigDecimal("8.90"),
priceChange = BigDecimal("-0.08"),
)
val quote9 = Quote(
val quote9 = Quote.Value(
rawCurrencyId = MockTokens.token9.id.rawCurrencyId!!,
fiatRate = BigDecimal("9.01"),
priceChange = BigDecimal("0.09"),
)
val quote10 = Quote(
val quote10 = Quote.Value(
rawCurrencyId = MockTokens.token10.id.rawCurrencyId!!,
fiatRate = BigDecimal("10.12"),
priceChange = BigDecimal("-0.10"),
)
val quotes = nonEmptySetOf(quote1, quote2, quote3, quote4, quote5, quote6, quote7, quote8, quote9, quote10)
val quote11 = Quote.Empty(null)
val quote12 = Quote.Empty("null")
val quotes = nonEmptySetOf(
quote1, quote2, quote3, quote4, quote5, quote6, quote7, quote8, quote9, quote10,
quote11, quote12,
)
}

View file

@ -1,10 +1,7 @@
package com.tangem.domain.tokens.mock
import arrow.core.nonEmptyListOf
import com.tangem.domain.tokens.model.CryptoCurrencyAmountStatus
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.NetworkAddress
import com.tangem.domain.tokens.model.NetworkStatus
import com.tangem.domain.tokens.model.*
import java.math.BigDecimal
@Suppress("MemberVisibilityCanBePrivate")
@ -140,20 +137,29 @@ internal object MockTokensStates {
as? CryptoCurrencyAmountStatus.Loaded
)?.value ?: BigDecimal.ZERO
val quote = MockQuotes.quotes.first { it.rawCurrencyId == status.currency.id.rawCurrencyId }
val fiatAmount = amount * quote.fiatRate
status.copy(
value = CryptoCurrencyStatus.Loaded(
val value = when (quote) {
is Quote.Empty -> CryptoCurrencyStatus.NoQuote(
amount = status.value.amount!!,
pendingTransactions = emptySet(),
hasCurrentNetworkTransactions = false,
networkAddress = requireNotNull(
value = MockNetworks.verifiedNetworksStatuses.first { it.network == status.currency.network }.value as? NetworkStatus.Verified,
).address,
yieldBalance = null,
)
is Quote.Value -> CryptoCurrencyStatus.Loaded(
amount = amount,
fiatAmount = fiatAmount,
fiatAmount = amount * quote.fiatRate,
fiatRate = quote.fiatRate,
priceChange = quote.priceChange,
pendingTransactions = emptySet(),
hasCurrentNetworkTransactions = false,
networkAddress = requireNotNull(networkStatus.value as? NetworkStatus.Verified).address,
yieldBalance = null,
),
)
)
}
status.copy(value = value)
}
val noQuotesTokensStatuses = loadedTokensStates.map { status ->

View file

@ -2178,8 +2178,8 @@ internal class SwapInteractorImpl @AssistedInject constructor(
)
}
private suspend fun getQuotes(vararg ids: CryptoCurrency.ID): Map<CryptoCurrency.ID, Quote> {
val set = ids.toSet().getQuotesOrEmpty(false)
private suspend fun getQuotes(vararg ids: CryptoCurrency.ID): Map<CryptoCurrency.ID, Quote.Value> {
val set = ids.toSet().getQuotesOrEmpty(false).filterIsInstance<Quote.Value>()
return ids
.mapNotNull { id -> set.find { it.rawCurrencyId == id.rawCurrencyId }?.let { id to it } }

View file

@ -3,9 +3,9 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.state.factory
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.core.ui.utils.toDateFormatWithTodayYesterday
import com.tangem.core.ui.utils.toTimeFormat
@ -55,16 +55,22 @@ internal class TokenDetailsSwapTransactionsStateConverter(
.forEach { swapTransaction ->
val toCryptoCurrency = swapTransaction.toCryptoCurrency
val fromCryptoCurrency = swapTransaction.fromCryptoCurrency
val toCryptoCurrencyRawId = swapTransaction.toCryptoCurrency.id.rawCurrencyId
val fromCryptoCurrencyRawId = swapTransaction.fromCryptoCurrency.id.rawCurrencyId
swapTransaction.transactions.forEach { transaction ->
val toAmount = transaction.toCryptoAmount
val fromAmount = transaction.fromCryptoAmount
val toFiatAmount = quotes.firstOrNull {
it.rawCurrencyId == swapTransaction.toCryptoCurrency.id.rawCurrencyId
}?.fiatRate?.multiply(toAmount)
val fromFiatAmount = quotes.firstOrNull {
it.rawCurrencyId == swapTransaction.fromCryptoCurrency.id.rawCurrencyId
}?.fiatRate?.multiply(fromAmount)
var toFiatAmount: BigDecimal? = null
var fromFiatAmount: BigDecimal? = null
quotes.forEach { quote ->
if (quote is Quote.Value && quote.rawCurrencyId == toCryptoCurrencyRawId) {
toFiatAmount = quote.fiatRate.multiply(toAmount)
}
if (quote is Quote.Value && quote.rawCurrencyId == fromCryptoCurrencyRawId) {
fromFiatAmount = quote.fiatRate.multiply(fromAmount)
}
}
val timestamp = transaction.timestamp
val notifications =
getNotification(transaction.status?.status, transaction.status?.txExternalUrl, null)