Updated on 2026-08-14
This commit is contained in:
parent
82eda99ca7
commit
5aa2af25f1
9 changed files with 110 additions and 55 deletions
|
|
@ -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() }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
@ -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 ->
|
||||
|
|
|
|||
|
|
@ -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 } }
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue