Updated on 2026-08-14

This commit is contained in:
Tangem 2026-05-20 18:10:16 +04:00
parent f3f1d88d1b
commit f57fc16ff6
29 changed files with 107 additions and 264 deletions

View file

@ -3,20 +3,14 @@ package com.tangem.common.test.data.quote
import com.tangem.datasource.api.tangemTech.models.QuotesResponse import com.tangem.datasource.api.tangemTech.models.QuotesResponse
import com.tangem.domain.models.StatusSource import com.tangem.domain.models.StatusSource
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.FiatCurrency
import com.tangem.domain.models.quote.QuoteStatus import com.tangem.domain.models.quote.QuoteStatus
import com.tangem.utils.extensions.orZero import com.tangem.utils.extensions.orZero
fun QuotesResponse.Quote.toDomain( fun QuotesResponse.Quote.toDomain(rawCurrencyId: String, source: StatusSource = StatusSource.ACTUAL): QuoteStatus {
rawCurrencyId: String,
source: StatusSource = StatusSource.ACTUAL,
fiatCurrency: FiatCurrency = FiatCurrency.Default,
): QuoteStatus {
return QuoteStatus( return QuoteStatus(
rawCurrencyId = CryptoCurrency.RawID(rawCurrencyId), rawCurrencyId = CryptoCurrency.RawID(rawCurrencyId),
value = QuoteStatus.Data( value = QuoteStatus.Data(
source = source, source = source,
fiatCurrency = fiatCurrency,
fiatRate = price.orZero(), fiatRate = price.orZero(),
priceChange = priceChange24h.orZero().movePointLeft(2), priceChange = priceChange24h.orZero().movePointLeft(2),
fiatRateUSD = priceUsd.orZero(), fiatRateUSD = priceUsd.orZero(),
@ -24,9 +18,6 @@ fun QuotesResponse.Quote.toDomain(
) )
} }
fun Pair<String, QuotesResponse.Quote>.toDomain( fun Pair<String, QuotesResponse.Quote>.toDomain(source: StatusSource = StatusSource.ACTUAL): QuoteStatus {
source: StatusSource = StatusSource.ACTUAL, return second.toDomain(rawCurrencyId = first, source = source)
fiatCurrency: FiatCurrency = FiatCurrency.Default,
): QuoteStatus {
return second.toDomain(rawCurrencyId = first, source = source, fiatCurrency = fiatCurrency)
} }

View file

@ -8,7 +8,6 @@ import com.tangem.data.common.network.NetworkFactory
import com.tangem.datasource.api.tangemTech.models.HotCryptoResponse import com.tangem.datasource.api.tangemTech.models.HotCryptoResponse
import com.tangem.domain.models.StatusSource import com.tangem.domain.models.StatusSource
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.FiatCurrency
import com.tangem.domain.models.network.Network import com.tangem.domain.models.network.Network
import com.tangem.domain.models.quote.QuoteStatus import com.tangem.domain.models.quote.QuoteStatus
import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWallet
@ -95,7 +94,6 @@ internal class HotCryptoCurrencyConverter(
QuoteStatus( QuoteStatus(
rawCurrencyId = rawCurrencyId, rawCurrencyId = rawCurrencyId,
value = QuoteStatus.Data( value = QuoteStatus.Data(
fiatCurrency = FiatCurrency.Default, // hot crypto rates are quoted in USD
fiatRate = fiatRate, fiatRate = fiatRate,
fiatRateUSD = BigDecimal.ZERO, fiatRateUSD = BigDecimal.ZERO,
priceChange = priceChange.movePointLeft(2), priceChange = priceChange.movePointLeft(2),

View file

@ -1,20 +0,0 @@
package com.tangem.data.quotes.converter
import com.tangem.data.quotes.store.QuoteStatusDM
import com.tangem.domain.models.currency.FiatCurrency
import com.tangem.utils.converter.TwoWayConverter
/**
* Two-way converter between domain [FiatCurrency] and persisted [QuoteStatusDM.FiatCurrency].
*
* - [convert] domain DM (for persistence).
* - [convertBack] DM domain (for restore on cold start).
*/
internal object FiatCurrencyConverter : TwoWayConverter<FiatCurrency, QuoteStatusDM.FiatCurrency> {
override fun convert(value: FiatCurrency): QuoteStatusDM.FiatCurrency =
QuoteStatusDM.FiatCurrency(code = value.code, symbol = value.symbol)
override fun convertBack(value: QuoteStatusDM.FiatCurrency): FiatCurrency =
FiatCurrency(code = value.code, symbol = value.symbol)
}

View file

@ -3,7 +3,6 @@ package com.tangem.data.quotes.converter
import com.tangem.datasource.api.tangemTech.models.QuotesResponse import com.tangem.datasource.api.tangemTech.models.QuotesResponse
import com.tangem.domain.models.StatusSource import com.tangem.domain.models.StatusSource
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.FiatCurrency
import com.tangem.domain.models.quote.QuoteStatus import com.tangem.domain.models.quote.QuoteStatus
import com.tangem.utils.converter.Converter import com.tangem.utils.converter.Converter
import com.tangem.utils.extensions.orZero import com.tangem.utils.extensions.orZero
@ -11,14 +10,12 @@ import com.tangem.utils.extensions.orZero
/** /**
* Converter from [QuotesResponse.Quote] to [QuoteStatus] * Converter from [QuotesResponse.Quote] to [QuoteStatus]
* *
* @property source status source * @property source status source
* @property fiatCurrency fiat currency in which the quote is expressed
* *
[REDACTED_AUTHOR] [REDACTED_AUTHOR]
*/ */
internal class QuoteStatusConverter( internal class QuoteStatusConverter(
private val source: StatusSource, private val source: StatusSource,
private val fiatCurrency: FiatCurrency,
) : Converter<Map.Entry<String, QuotesResponse.Quote>, QuoteStatus> { ) : Converter<Map.Entry<String, QuotesResponse.Quote>, QuoteStatus> {
override fun convert(value: Map.Entry<String, QuotesResponse.Quote>): QuoteStatus { override fun convert(value: Map.Entry<String, QuotesResponse.Quote>): QuoteStatus {
@ -28,7 +25,6 @@ internal class QuoteStatusConverter(
rawCurrencyId = CryptoCurrency.RawID(currencyId), rawCurrencyId = CryptoCurrency.RawID(currencyId),
value = QuoteStatus.Data( value = QuoteStatus.Data(
source = source, source = source,
fiatCurrency = fiatCurrency,
fiatRate = quote.price.orZero(), fiatRate = quote.price.orZero(),
priceChange = quote.priceChange24h.orZero().movePointLeft(2), priceChange = quote.priceChange24h.orZero().movePointLeft(2),
fiatRateUSD = quote.priceUsd.orZero(), fiatRateUSD = quote.priceUsd.orZero(),

View file

@ -4,16 +4,16 @@ import android.content.Context
import androidx.datastore.core.DataStoreFactory import androidx.datastore.core.DataStoreFactory
import androidx.datastore.dataStoreFile import androidx.datastore.dataStoreFile
import com.squareup.moshi.Moshi import com.squareup.moshi.Moshi
import com.squareup.moshi.adapter
import com.tangem.data.quotes.multi.DefaultMultiQuoteUpdater import com.tangem.data.quotes.multi.DefaultMultiQuoteUpdater
import com.tangem.data.quotes.repository.DefaultQuotesRepository import com.tangem.data.quotes.repository.DefaultQuotesRepository
import com.tangem.data.quotes.store.DefaultQuotesStatusesStore import com.tangem.data.quotes.store.DefaultQuotesStatusesStore
import com.tangem.data.quotes.store.QuoteStatusDM
import com.tangem.data.quotes.store.QuotesStatusesStore import com.tangem.data.quotes.store.QuotesStatusesStore
import com.tangem.datasource.api.tangemTech.models.QuotesResponse
import com.tangem.datasource.appcurrency.AppCurrencyResponseStore import com.tangem.datasource.appcurrency.AppCurrencyResponseStore
import com.tangem.datasource.di.NetworkMoshi import com.tangem.datasource.di.NetworkMoshi
import com.tangem.datasource.local.datastore.RuntimeSharedStore import com.tangem.datasource.local.datastore.RuntimeSharedStore
import com.tangem.datasource.utils.MoshiDataStoreSerializer import com.tangem.datasource.utils.MoshiDataStoreSerializer
import com.tangem.datasource.utils.mapWithStringKeyTypes
import com.tangem.domain.quotes.GetCurrencyUSDQuoteUseCase import com.tangem.domain.quotes.GetCurrencyUSDQuoteUseCase
import com.tangem.domain.quotes.QuotesRepository import com.tangem.domain.quotes.QuotesRepository
import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher
@ -30,7 +30,6 @@ import javax.inject.Singleton
@InstallIn(SingletonComponent::class) @InstallIn(SingletonComponent::class)
internal object QuotesDataModule { internal object QuotesDataModule {
@OptIn(ExperimentalStdlibApi::class)
@Singleton @Singleton
@Provides @Provides
fun provideQuotesStoreV2( fun provideQuotesStoreV2(
@ -42,13 +41,13 @@ internal object QuotesDataModule {
runtimeStore = RuntimeSharedStore(), runtimeStore = RuntimeSharedStore(),
persistenceDataStore = DataStoreFactory.create( persistenceDataStore = DataStoreFactory.create(
serializer = MoshiDataStoreSerializer( serializer = MoshiDataStoreSerializer(
defaultValue = QuoteStatusDM.Empty, moshi = moshi,
adapter = moshi.adapter<QuoteStatusDM>(), types = mapWithStringKeyTypes<QuotesResponse.Quote>(),
defaultValue = emptyMap(),
), ),
produceFile = { context.dataStoreFile(fileName = "quotes_v2") }, produceFile = { context.dataStoreFile(fileName = "quotes") },
scope = appScope, scope = appScope,
), ),
legacyCacheFile = context.dataStoreFile(fileName = "quotes"),
scope = appScope, scope = appScope,
) )
} }

View file

@ -8,11 +8,9 @@ import com.tangem.data.quotes.store.QuotesStatusesStore
import com.tangem.data.quotes.store.setSourceAsCache import com.tangem.data.quotes.store.setSourceAsCache
import com.tangem.data.quotes.store.setSourceAsOnlyCache import com.tangem.data.quotes.store.setSourceAsOnlyCache
import com.tangem.data.quotes.utils.QuotesUnsupportedCurrenciesIdAdapter import com.tangem.data.quotes.utils.QuotesUnsupportedCurrenciesIdAdapter
import com.tangem.datasource.api.tangemTech.models.CurrenciesResponse
import com.tangem.datasource.appcurrency.AppCurrencyResponseStore import com.tangem.datasource.appcurrency.AppCurrencyResponseStore
import com.tangem.domain.core.utils.catchOn import com.tangem.domain.core.utils.catchOn
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.FiatCurrency
import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.logging.TangemLogger import com.tangem.utils.logging.TangemLogger
@ -52,10 +50,10 @@ internal class DefaultMultiQuoteStatusFetcher @Inject constructor(
), ),
) )
val (fiatCurrencyId, fiatCurrency) = resolveFiatCurrency() val appCurrencyId = getAppCurrencyId(params = params)
val response = quotesFetcher.fetch( val response = quotesFetcher.fetch(
fiatCurrencyId = fiatCurrencyId, fiatCurrencyId = appCurrencyId,
currenciesIds = replacementIdsResult.idsForRequest, currenciesIds = replacementIdsResult.idsForRequest,
fields = setOf(Field.PRICE, Field.PRICE_CHANGE_24H, Field.PRICE_USD), fields = setOf(Field.PRICE, Field.PRICE_CHANGE_24H, Field.PRICE_USD),
) )
@ -66,27 +64,24 @@ internal class DefaultMultiQuoteStatusFetcher @Inject constructor(
filteredIds = replacementIdsResult.idsFiltered, filteredIds = replacementIdsResult.idsFiltered,
) )
quotesStatusesStore.store(values = updatedResponse.quotes, fiatCurrency = fiatCurrency) quotesStatusesStore.store(values = updatedResponse.quotes)
} }
.onLeft { throwable -> .onLeft { throwable ->
TangemLogger.e("Error", throwable) TangemLogger.e("Error", throwable)
quotesStatusesStore.setSourceAsOnlyCache(currenciesIds = params.currenciesIds) quotesStatusesStore.setSourceAsOnlyCache(currenciesIds = params.currenciesIds)
} }
private suspend fun resolveFiatCurrency(): Pair<String, FiatCurrency> { private suspend fun getAppCurrencyId(params: MultiQuoteStatusFetcher.Params): String {
val stored = appCurrencyResponseStore.getSyncOrNull() ?: failOnMissingAppCurrency() val appCurrencyId = params.appCurrencyId
if (stored.id.isBlank()) failOnMissingAppCurrency() ?: appCurrencyResponseStore.getSyncOrNull()?.id
return stored.id to stored.toFiatCurrency() if (appCurrencyId.isNullOrBlank()) {
} val exception = IllegalStateException("Unable to get AppCurrency for updating quotes")
TangemLogger.e("Error", exception)
private fun failOnMissingAppCurrency(): Nothing { throw exception
val exception = IllegalStateException("Unable to get AppCurrency for updating quotes") }
TangemLogger.e("Error", exception)
throw exception
}
private fun CurrenciesResponse.Currency.toFiatCurrency(): FiatCurrency { return appCurrencyId
return FiatCurrency(code = code, symbol = unit)
} }
} }

View file

@ -1,13 +1,11 @@
package com.tangem.data.quotes.store package com.tangem.data.quotes.store
import androidx.datastore.core.DataStore import androidx.datastore.core.DataStore
import com.tangem.data.quotes.converter.FiatCurrencyConverter
import com.tangem.data.quotes.converter.QuoteStatusConverter import com.tangem.data.quotes.converter.QuoteStatusConverter
import com.tangem.datasource.api.tangemTech.models.QuotesResponse import com.tangem.datasource.api.tangemTech.models.QuotesResponse
import com.tangem.datasource.local.datastore.RuntimeSharedStore import com.tangem.datasource.local.datastore.RuntimeSharedStore
import com.tangem.domain.models.StatusSource import com.tangem.domain.models.StatusSource
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.FiatCurrency
import com.tangem.domain.models.quote.QuoteStatus import com.tangem.domain.models.quote.QuoteStatus
import com.tangem.utils.coroutines.AppCoroutineScope import com.tangem.utils.coroutines.AppCoroutineScope
import com.tangem.utils.extensions.addOrReplace import com.tangem.utils.extensions.addOrReplace
@ -16,7 +14,6 @@ import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.io.File
internal typealias CurrencyIdWithQuote = Map<String, QuotesResponse.Quote> internal typealias CurrencyIdWithQuote = Map<String, QuotesResponse.Quote>
@ -24,46 +21,27 @@ internal typealias CurrencyIdWithQuote = Map<String, QuotesResponse.Quote>
* Default implementation of [QuotesStatusesStore] * Default implementation of [QuotesStatusesStore]
* *
* @property runtimeStore runtime store * @property runtimeStore runtime store
* @property persistenceDataStore persistence store (keeps quotes together with the fiat currency they're expressed in) * @property persistenceDataStore persistence store
* @property legacyCacheFile pre-v2 cache file kept on disk; deleted once on init * @param dispatchers dispatchers
* @param scope app coroutine scope
*/ */
internal class DefaultQuotesStatusesStore( internal class DefaultQuotesStatusesStore(
private val runtimeStore: RuntimeSharedStore<Set<QuoteStatus>>, private val runtimeStore: RuntimeSharedStore<Set<QuoteStatus>>,
private val persistenceDataStore: DataStore<QuoteStatusDM>, private val persistenceDataStore: DataStore<CurrencyIdWithQuote>,
private val legacyCacheFile: File,
private val scope: AppCoroutineScope, private val scope: AppCoroutineScope,
) : QuotesStatusesStore { ) : QuotesStatusesStore {
init { init {
scope.launch { scope.launch {
deleteLegacyCacheFile() val cachedStatuses = persistenceDataStore.data.firstOrNull()
val cached = persistenceDataStore.data.firstOrNull() ?: return@launch if (cachedStatuses.isNullOrEmpty()) return@launch
val fiatCurrency = cached.fiatCurrency?.let(FiatCurrencyConverter::convertBack) ?: return@launch
if (cached.quotes.isEmpty()) return@launch
runtimeStore.store( runtimeStore.store(
value = QuoteStatusConverter(source = StatusSource.CACHE, fiatCurrency = fiatCurrency) value = QuoteStatusConverter(source = StatusSource.CACHE).convertSet(input = cachedStatuses.entries),
.convertSet(input = cached.quotes.entries),
) )
} }
} }
private fun deleteLegacyCacheFile() {
if (!legacyCacheFile.exists()) return
runCatching { legacyCacheFile.delete() }
.onSuccess { deleted ->
if (deleted) {
TangemLogger.i("Deleted legacy quotes cache file: ${legacyCacheFile.name}")
} else {
TangemLogger.e("Could not delete legacy quotes cache file: ${legacyCacheFile.name}")
}
}
.onFailure { TangemLogger.e("Failed to delete legacy quotes cache file", it) }
}
override fun get(): Flow<Set<QuoteStatus>> = runtimeStore.get() override fun get(): Flow<Set<QuoteStatus>> = runtimeStore.get()
override suspend fun getAllSyncOrNull(): Set<QuoteStatus>? = runtimeStore.getSyncOrNull() override suspend fun getAllSyncOrNull(): Set<QuoteStatus>? = runtimeStore.getSyncOrNull()
@ -103,33 +81,24 @@ internal class DefaultQuotesStatusesStore(
} }
} }
override suspend fun store(values: CurrencyIdWithQuote, fiatCurrency: FiatCurrency) { override suspend fun store(values: CurrencyIdWithQuote) {
if (values.isEmpty()) return if (values.isEmpty()) return
coroutineScope { coroutineScope {
launch { storeInRuntime(values = values, fiatCurrency = fiatCurrency) } launch { storeInRuntime(values = values) }
launch { storeInPersistence(values = values, fiatCurrency = fiatCurrency) } launch { storeInPersistence(values = values) }
} }
} }
private suspend fun storeInRuntime(values: CurrencyIdWithQuote, fiatCurrency: FiatCurrency) { private suspend fun storeInRuntime(values: CurrencyIdWithQuote) {
val quotes = QuoteStatusConverter(source = StatusSource.ACTUAL, fiatCurrency = fiatCurrency) val quotes = QuoteStatusConverter(source = StatusSource.ACTUAL).convertSet(input = values.entries)
.convertSet(input = values.entries)
runtimeStore.update(default = emptySet()) { saved -> runtimeStore.update(default = emptySet()) { saved ->
saved.addOrReplace(items = quotes) { prev, new -> prev.rawCurrencyId == new.rawCurrencyId } saved.addOrReplace(items = quotes) { prev, new -> prev.rawCurrencyId == new.rawCurrencyId }
} }
} }
private suspend fun storeInPersistence(values: CurrencyIdWithQuote, fiatCurrency: FiatCurrency) { private suspend fun storeInPersistence(values: CurrencyIdWithQuote) {
persistenceDataStore.updateData { stored -> persistenceDataStore.updateData { storedQuotes -> storedQuotes + values }
val isSameCurrency = stored.fiatCurrency?.code == fiatCurrency.code
val mergedQuotes = if (isSameCurrency) stored.quotes + values else values
QuoteStatusDM(
fiatCurrency = FiatCurrencyConverter.convert(fiatCurrency),
quotes = mergedQuotes,
)
}
} }
} }

View file

@ -1,28 +0,0 @@
package com.tangem.data.quotes.store
import com.squareup.moshi.JsonClass
import com.tangem.datasource.api.tangemTech.models.QuotesResponse
/**
* Persisted form of [com.tangem.domain.models.quote.QuoteStatus] cache. Carries the fiat currency
* the quotes are expressed in, so it can be restored on cold start.
*
* @property fiatCurrency fiat currency the [quotes] are expressed in; `null` for an empty default cache
* @property quotes map of currency id to its quote
*/
@JsonClass(generateAdapter = true)
internal data class QuoteStatusDM(
val fiatCurrency: FiatCurrency?,
val quotes: Map<String, QuotesResponse.Quote>,
) {
@JsonClass(generateAdapter = true)
internal data class FiatCurrency(
val code: String,
val symbol: String,
)
companion object {
val Empty = QuoteStatusDM(fiatCurrency = null, quotes = emptyMap())
}
}

View file

@ -3,7 +3,6 @@ package com.tangem.data.quotes.store
import com.tangem.datasource.api.tangemTech.models.QuotesResponse import com.tangem.datasource.api.tangemTech.models.QuotesResponse
import com.tangem.domain.models.StatusSource import com.tangem.domain.models.StatusSource
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.FiatCurrency
import com.tangem.domain.models.quote.QuoteStatus import com.tangem.domain.models.quote.QuoteStatus
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@ -39,10 +38,9 @@ internal interface QuotesStatusesStore {
/** /**
* Store quotes statuses * Store quotes statuses
* *
* @param values map of currency ids and quotes * @param values map of currency ids and quotes
* @param fiatCurrency fiat currency the [values] are expressed in
* *
* See complex methods in `QuotesStatusesStoreExt`. * See complex methods in `QuotesStatusesStoreExt`.
*/ */
suspend fun store(values: Map<String, QuotesResponse.Quote>, fiatCurrency: FiatCurrency) suspend fun store(values: Map<String, QuotesResponse.Quote>)
} }

View file

@ -6,7 +6,6 @@ import com.tangem.common.test.data.quote.toDomain
import com.tangem.datasource.api.tangemTech.models.QuotesResponse import com.tangem.datasource.api.tangemTech.models.QuotesResponse
import com.tangem.domain.models.StatusSource import com.tangem.domain.models.StatusSource
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.FiatCurrency
import com.tangem.domain.models.quote.QuoteStatus import com.tangem.domain.models.quote.QuoteStatus
import com.tangem.test.core.ProvideTestModels import com.tangem.test.core.ProvideTestModels
import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.TestInstance
@ -25,8 +24,7 @@ internal class QuoteStatusConverterTest {
@ProvideTestModels @ProvideTestModels
fun convert(model: ConvertTestModel) { fun convert(model: ConvertTestModel) {
// Act // Act
val actual = QuoteStatusConverter(source = model.source, fiatCurrency = FiatCurrency.Default) val actual = QuoteStatusConverter(source = model.source).convert(value = model.value)
.convert(value = model.value)
// Assert // Assert
val expected = model.expected val expected = model.expected
@ -64,7 +62,6 @@ internal class QuoteStatusConverterTest {
rawCurrencyId = CryptoCurrency.RawID("ETH"), rawCurrencyId = CryptoCurrency.RawID("ETH"),
value = QuoteStatus.Data( value = QuoteStatus.Data(
source = StatusSource.ACTUAL, source = StatusSource.ACTUAL,
fiatCurrency = FiatCurrency.Default,
fiatRate = BigDecimal.ZERO, fiatRate = BigDecimal.ZERO,
fiatRateUSD = BigDecimal.ZERO, fiatRateUSD = BigDecimal.ZERO,
priceChange = BigDecimal("0.00"), priceChange = BigDecimal("0.00"),
@ -86,7 +83,6 @@ internal class QuoteStatusConverterTest {
rawCurrencyId = CryptoCurrency.RawID("ETH"), rawCurrencyId = CryptoCurrency.RawID("ETH"),
value = QuoteStatus.Data( value = QuoteStatus.Data(
source = StatusSource.ACTUAL, source = StatusSource.ACTUAL,
fiatCurrency = FiatCurrency.Default,
fiatRate = BigDecimal.ZERO, fiatRate = BigDecimal.ZERO,
fiatRateUSD = BigDecimal.ZERO, fiatRateUSD = BigDecimal.ZERO,
priceChange = BigDecimal("0.00"), priceChange = BigDecimal("0.00"),
@ -108,7 +104,6 @@ internal class QuoteStatusConverterTest {
rawCurrencyId = CryptoCurrency.RawID("ETH"), rawCurrencyId = CryptoCurrency.RawID("ETH"),
value = QuoteStatus.Data( value = QuoteStatus.Data(
source = StatusSource.ACTUAL, source = StatusSource.ACTUAL,
fiatCurrency = FiatCurrency.Default,
fiatRate = BigDecimal.ONE, fiatRate = BigDecimal.ONE,
fiatRateUSD = BigDecimal.ONE, fiatRateUSD = BigDecimal.ONE,
priceChange = BigDecimal("0.01"), priceChange = BigDecimal("0.01"),

View file

@ -69,7 +69,7 @@ internal class DefaultMultiQuoteStatusFetcherTest {
quotesStore.setSourceAsCache(currenciesIds = params.currenciesIds) quotesStore.setSourceAsCache(currenciesIds = params.currenciesIds)
appCurrencyResponseStore.getSyncOrNull() appCurrencyResponseStore.getSyncOrNull()
quotesFetcher.fetch(fiatCurrencyId = "usd", currenciesIds = currenciesIds, fields = fields) quotesFetcher.fetch(fiatCurrencyId = "usd", currenciesIds = currenciesIds, fields = fields)
quotesStore.store(values = successResponse.quotes, fiatCurrency = any()) quotesStore.store(values = successResponse.quotes)
} }
coVerify(inverse = true) { coVerify(inverse = true) {
@ -93,21 +93,21 @@ internal class DefaultMultiQuoteStatusFetcherTest {
quotesStore.setSourceAsCache(currenciesIds = any()) quotesStore.setSourceAsCache(currenciesIds = any())
appCurrencyResponseStore.getSyncOrNull() appCurrencyResponseStore.getSyncOrNull()
quotesFetcher.fetch(fiatCurrencyId = any(), currenciesIds = any(), fields = any()) quotesFetcher.fetch(fiatCurrencyId = any(), currenciesIds = any(), fields = any())
quotesStore.store(values = any(), fiatCurrency = any()) quotesStore.store(values = any())
quotesStore.setSourceAsOnlyCache(currenciesIds = any()) quotesStore.setSourceAsOnlyCache(currenciesIds = any())
} }
} }
@Test @Test
fun `fetch ignores params appCurrencyId and uses stored app currency`() = runTest { fun `fetch successfully if appCurrencyId from params is not null`() = runTest {
// Arrange: params.appCurrencyId is set but different from stored — fetcher must still use stored // Arrange
val params = MultiQuoteStatusFetcher.Params(currenciesIds = currenciesIds, appCurrencyId = "eur") val appCurrencyId = "usd"
val params = MultiQuoteStatusFetcher.Params(currenciesIds = currenciesIds, appCurrencyId = appCurrencyId)
val currenciesIds = setOf("BTC", "ETH") val currenciesIds = setOf("BTC", "ETH")
coEvery { appCurrencyResponseStore.getSyncOrNull() } returns usdAppCurrency
coEvery { coEvery {
quotesFetcher.fetch(fiatCurrencyId = "usd", currenciesIds = currenciesIds, fields = fields) quotesFetcher.fetch(fiatCurrencyId = appCurrencyId, currenciesIds = currenciesIds, fields = fields)
} returns successResponse.right() } returns successResponse.right()
// Act // Act
@ -119,16 +119,42 @@ internal class DefaultMultiQuoteStatusFetcherTest {
coVerifyOrder { coVerifyOrder {
quotesStore.setSourceAsCache(currenciesIds = params.currenciesIds) quotesStore.setSourceAsCache(currenciesIds = params.currenciesIds)
appCurrencyResponseStore.getSyncOrNull() quotesFetcher.fetch(fiatCurrencyId = appCurrencyId, currenciesIds = currenciesIds, fields = fields)
quotesFetcher.fetch(fiatCurrencyId = "usd", currenciesIds = currenciesIds, fields = fields) quotesStore.store(values = successResponse.quotes)
quotesStore.store(values = successResponse.quotes, fiatCurrency = any())
} }
coVerify(inverse = true) { coVerify(inverse = true) {
appCurrencyResponseStore.getSyncOrNull()
quotesStore.setSourceAsOnlyCache(currenciesIds = any()) quotesStore.setSourceAsOnlyCache(currenciesIds = any())
} }
} }
@Test
fun `fetch failure because appCurrencyId from params is blank`() = runTest {
// Arrange
val appCurrencyId = ""
val params = MultiQuoteStatusFetcher.Params(currenciesIds = currenciesIds, appCurrencyId = appCurrencyId)
// Act
val actual = fetcher(params)
// Assert
val expected = IllegalStateException("Unable to get AppCurrency for updating quotes").left()
assertEither(actual, expected)
coVerifyOrder {
quotesStore.setSourceAsCache(currenciesIds = params.currenciesIds)
quotesStore.setSourceAsOnlyCache(currenciesIds = params.currenciesIds)
}
coVerify(inverse = true) {
appCurrencyResponseStore.getSyncOrNull()
quotesFetcher.fetch(fiatCurrencyId = any(), currenciesIds = any(), fields = any())
quotesStore.store(values = any())
}
}
@Test @Test
fun `fetch failure because api request failed`() = runTest { fun `fetch failure because api request failed`() = runTest {
// Arrange // Arrange
@ -160,7 +186,7 @@ internal class DefaultMultiQuoteStatusFetcherTest {
} }
coVerify(inverse = true) { coVerify(inverse = true) {
quotesStore.store(values = any(), fiatCurrency = any()) quotesStore.store(values = any())
} }
} }
@ -187,7 +213,7 @@ internal class DefaultMultiQuoteStatusFetcherTest {
coVerify(inverse = true) { coVerify(inverse = true) {
quotesFetcher.fetch(fiatCurrencyId = any(), currenciesIds = any(), fields = any()) quotesFetcher.fetch(fiatCurrencyId = any(), currenciesIds = any(), fields = any())
quotesStore.store(values = any(), fiatCurrency = any()) quotesStore.store(values = any())
} }
} }

View file

@ -4,7 +4,6 @@ import com.google.common.truth.Truth
import com.tangem.data.quotes.store.QuotesStatusesStore import com.tangem.data.quotes.store.QuotesStatusesStore
import com.tangem.domain.models.StatusSource import com.tangem.domain.models.StatusSource
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.FiatCurrency
import com.tangem.domain.models.quote.QuoteStatus import com.tangem.domain.models.quote.QuoteStatus
import com.tangem.domain.quotes.QuotesRepository import com.tangem.domain.quotes.QuotesRepository
import com.tangem.test.core.ProvideTestModels import com.tangem.test.core.ProvideTestModels
@ -42,7 +41,6 @@ internal class DefaultQuotesRepositoryTest {
private val ethQuote = QuoteStatus( private val ethQuote = QuoteStatus(
rawCurrencyId = ethRawId, rawCurrencyId = ethRawId,
value = QuoteStatus.Data( value = QuoteStatus.Data(
fiatCurrency = FiatCurrency.Default,
source = StatusSource.ACTUAL, source = StatusSource.ACTUAL,
fiatRate = BigDecimal.ZERO, fiatRate = BigDecimal.ZERO,
fiatRateUSD = BigDecimal.ZERO, fiatRateUSD = BigDecimal.ZERO,
@ -113,7 +111,6 @@ internal class DefaultQuotesRepositoryTest {
private val ethQuote = QuoteStatus( private val ethQuote = QuoteStatus(
rawCurrencyId = ethRawId, rawCurrencyId = ethRawId,
value = QuoteStatus.Data( value = QuoteStatus.Data(
fiatCurrency = FiatCurrency.Default,
source = StatusSource.ACTUAL, source = StatusSource.ACTUAL,
fiatRate = BigDecimal.ZERO, fiatRate = BigDecimal.ZERO,
fiatRateUSD = BigDecimal.ZERO, fiatRateUSD = BigDecimal.ZERO,

View file

@ -5,7 +5,6 @@ import com.tangem.data.quotes.store.QuotesStatusesStore
import com.tangem.domain.core.flow.FlowProducerTools import com.tangem.domain.core.flow.FlowProducerTools
import com.tangem.domain.models.StatusSource import com.tangem.domain.models.StatusSource
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.FiatCurrency
import com.tangem.domain.models.quote.QuoteStatus import com.tangem.domain.models.quote.QuoteStatus
import com.tangem.domain.quotes.single.SingleQuoteStatusProducer import com.tangem.domain.quotes.single.SingleQuoteStatusProducer
import com.tangem.test.core.getEmittedValues import com.tangem.test.core.getEmittedValues
@ -82,7 +81,6 @@ internal class DefaultSingleQuoteStatusProducerTest {
val updatedStatus = QuoteStatus( val updatedStatus = QuoteStatus(
rawCurrencyId = params.rawCurrencyId, rawCurrencyId = params.rawCurrencyId,
value = QuoteStatus.Data( value = QuoteStatus.Data(
fiatCurrency = FiatCurrency.Default,
fiatRate = BigDecimal.ONE, fiatRate = BigDecimal.ONE,
priceChange = BigDecimal.ZERO, priceChange = BigDecimal.ZERO,
fiatRateUSD = BigDecimal.ZERO, fiatRateUSD = BigDecimal.ZERO,
@ -131,7 +129,6 @@ internal class DefaultSingleQuoteStatusProducerTest {
val status = QuoteStatus( val status = QuoteStatus(
rawCurrencyId = params.rawCurrencyId, rawCurrencyId = params.rawCurrencyId,
value = QuoteStatus.Data( value = QuoteStatus.Data(
fiatCurrency = FiatCurrency.Default,
fiatRate = BigDecimal.ONE, fiatRate = BigDecimal.ONE,
fiatRateUSD = BigDecimal.ZERO, fiatRateUSD = BigDecimal.ZERO,
priceChange = BigDecimal.ZERO, priceChange = BigDecimal.ZERO,

View file

@ -25,7 +25,7 @@ import kotlin.properties.Delegates
internal class QuotesStatusesStoreExtTest { internal class QuotesStatusesStoreExtTest {
private var runtimeStore: RuntimeSharedStore<Set<QuoteStatus>> by Delegates.notNull() private var runtimeStore: RuntimeSharedStore<Set<QuoteStatus>> by Delegates.notNull()
private var persistenceStore: MockStateDataStore<QuoteStatusDM> by Delegates.notNull() private var persistenceStore: MockStateDataStore<CurrencyIdWithQuote> by Delegates.notNull()
private var store: DefaultQuotesStatusesStore by Delegates.notNull() private var store: DefaultQuotesStatusesStore by Delegates.notNull()
private val btcQuoteDM = "BTC" to MockQuoteResponseFactory.createSinglePrice(BigDecimal.ZERO) private val btcQuoteDM = "BTC" to MockQuoteResponseFactory.createSinglePrice(BigDecimal.ZERO)
@ -34,12 +34,11 @@ internal class QuotesStatusesStoreExtTest {
@BeforeEach @BeforeEach
fun resetMocks() { fun resetMocks() {
runtimeStore = RuntimeSharedStore() runtimeStore = RuntimeSharedStore()
persistenceStore = MockStateDataStore(default = QuoteStatusDM.Empty) persistenceStore = MockStateDataStore(default = emptyMap())
store = DefaultQuotesStatusesStore( store = DefaultQuotesStatusesStore(
runtimeStore = runtimeStore, runtimeStore = runtimeStore,
persistenceDataStore = persistenceStore, persistenceDataStore = persistenceStore,
legacyCacheFile = java.io.File("legacy-quotes-cache-noop"),
scope = TestAppCoroutineScope(), scope = TestAppCoroutineScope(),
) )
} }

View file

@ -6,11 +6,9 @@ import com.tangem.common.test.TestAppCoroutineScope
import com.tangem.common.test.data.quote.MockQuoteResponseFactory import com.tangem.common.test.data.quote.MockQuoteResponseFactory
import com.tangem.common.test.data.quote.toDomain import com.tangem.common.test.data.quote.toDomain
import com.tangem.common.test.datastore.MockStateDataStore import com.tangem.common.test.datastore.MockStateDataStore
import com.tangem.data.quotes.converter.FiatCurrencyConverter
import com.tangem.datasource.local.datastore.RuntimeSharedStore import com.tangem.datasource.local.datastore.RuntimeSharedStore
import com.tangem.domain.models.StatusSource import com.tangem.domain.models.StatusSource
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.FiatCurrency
import com.tangem.domain.models.quote.QuoteStatus import com.tangem.domain.models.quote.QuoteStatus
import com.tangem.test.core.ProvideTestModels import com.tangem.test.core.ProvideTestModels
import com.tangem.test.core.getEmittedValues import com.tangem.test.core.getEmittedValues
@ -33,7 +31,7 @@ import kotlin.properties.Delegates
internal class QuotesStatusesStoreTest { internal class QuotesStatusesStoreTest {
private var runtimeStore: RuntimeSharedStore<Set<QuoteStatus>> by Delegates.notNull() private var runtimeStore: RuntimeSharedStore<Set<QuoteStatus>> by Delegates.notNull()
private var persistenceStore: MockStateDataStore<QuoteStatusDM> by Delegates.notNull() private var persistenceStore: MockStateDataStore<CurrencyIdWithQuote> by Delegates.notNull()
private var store: DefaultQuotesStatusesStore by Delegates.notNull() private var store: DefaultQuotesStatusesStore by Delegates.notNull()
// region Data models // region Data models
@ -50,12 +48,11 @@ internal class QuotesStatusesStoreTest {
@BeforeEach @BeforeEach
fun resetMocks() { fun resetMocks() {
runtimeStore = RuntimeSharedStore() runtimeStore = RuntimeSharedStore()
persistenceStore = MockStateDataStore(default = QuoteStatusDM.Empty) persistenceStore = MockStateDataStore(default = emptyMap())
store = DefaultQuotesStatusesStore( store = DefaultQuotesStatusesStore(
runtimeStore = runtimeStore, runtimeStore = runtimeStore,
persistenceDataStore = persistenceStore, persistenceDataStore = persistenceStore,
legacyCacheFile = java.io.File("legacy-quotes-cache-noop"),
scope = TestAppCoroutineScope(), scope = TestAppCoroutineScope(),
) )
} }
@ -68,7 +65,7 @@ internal class QuotesStatusesStoreTest {
fun `initialization if cache store is empty`() = runTest { fun `initialization if cache store is empty`() = runTest {
// Arrange // Arrange
val runtimeStore = RuntimeSharedStore<Set<QuoteStatus>>() val runtimeStore = RuntimeSharedStore<Set<QuoteStatus>>()
val persistenceStore: DataStore<QuoteStatusDM> = mockk() val persistenceStore: DataStore<CurrencyIdWithQuote> = mockk()
every { persistenceStore.data } returns emptyFlow() every { persistenceStore.data } returns emptyFlow()
@ -76,7 +73,6 @@ internal class QuotesStatusesStoreTest {
DefaultQuotesStatusesStore( DefaultQuotesStatusesStore(
runtimeStore = runtimeStore, runtimeStore = runtimeStore,
persistenceDataStore = persistenceStore, persistenceDataStore = persistenceStore,
legacyCacheFile = java.io.File("legacy-quotes-cache-noop"),
scope = TestAppCoroutineScope(), scope = TestAppCoroutineScope(),
) )
@ -91,13 +87,12 @@ internal class QuotesStatusesStoreTest {
fun `initialization if cache store contains empty map`() = runTest { fun `initialization if cache store contains empty map`() = runTest {
// Arrange // Arrange
val runtimeStore = RuntimeSharedStore<Set<QuoteStatus>>() val runtimeStore = RuntimeSharedStore<Set<QuoteStatus>>()
val persistenceStore = MockStateDataStore<QuoteStatusDM>(default = QuoteStatusDM.Empty) val persistenceStore = MockStateDataStore<CurrencyIdWithQuote>(default = emptyMap())
// Act // Act
DefaultQuotesStatusesStore( DefaultQuotesStatusesStore(
runtimeStore = runtimeStore, runtimeStore = runtimeStore,
persistenceDataStore = persistenceStore, persistenceDataStore = persistenceStore,
legacyCacheFile = java.io.File("legacy-quotes-cache-noop"),
scope = TestAppCoroutineScope(), scope = TestAppCoroutineScope(),
) )
@ -112,20 +107,19 @@ internal class QuotesStatusesStoreTest {
fun `initialization if cache store is not empty`() = runTest { fun `initialization if cache store is not empty`() = runTest {
// Arrange // Arrange
val runtimeStore = RuntimeSharedStore<Set<QuoteStatus>>() val runtimeStore = RuntimeSharedStore<Set<QuoteStatus>>()
val persistenceStore = MockStateDataStore<QuoteStatusDM>(default = QuoteStatusDM.Empty) val persistenceStore = MockStateDataStore<CurrencyIdWithQuote>(default = emptyMap())
persistenceStore.updateData { persistenceStore.updateData {
QuoteStatusDM( it.toMutableMap().apply {
fiatCurrency = FiatCurrencyConverter.convert(FiatCurrency.Default), this += btcQuoteDM
quotes = mapOf(btcQuoteDM, ethQuoteDM), this += ethQuoteDM
) }
} }
// Act // Act
DefaultQuotesStatusesStore( DefaultQuotesStatusesStore(
runtimeStore = runtimeStore, runtimeStore = runtimeStore,
persistenceDataStore = persistenceStore, persistenceDataStore = persistenceStore,
legacyCacheFile = java.io.File("legacy-quotes-cache-noop"),
scope = TestAppCoroutineScope(), scope = TestAppCoroutineScope(),
) )
@ -477,7 +471,7 @@ internal class QuotesStatusesStoreTest {
} }
// Act // Act
store.store(values = model.values, fiatCurrency = FiatCurrency.Default) store.store(values = model.values)
val runtimeActual = runtimeStore.getSyncOrNull() val runtimeActual = runtimeStore.getSyncOrNull()
val persistenceActual = getEmittedValues(persistenceStore.data) val persistenceActual = getEmittedValues(persistenceStore.data)
@ -497,14 +491,14 @@ internal class QuotesStatusesStoreTest {
initialPersistence = null, initialPersistence = null,
values = emptyMap(), values = emptyMap(),
runtimeExpected = null, runtimeExpected = null,
persistenceExpected = QuoteStatusDM.Empty, persistenceExpected = emptyMap(),
), ),
StoreTestModel( StoreTestModel(
initialRuntime = null, initialRuntime = null,
initialPersistence = null, initialPersistence = null,
values = mapOf(btcQuoteDM), values = mapOf(btcQuoteDM),
runtimeExpected = setOf(btcQuote), runtimeExpected = setOf(btcQuote),
persistenceExpected = persistedDefault(btcQuoteDM), persistenceExpected = mapOf(btcQuoteDM),
), ),
// endregion // endregion
@ -514,48 +508,48 @@ internal class QuotesStatusesStoreTest {
initialPersistence = null, initialPersistence = null,
values = emptyMap(), values = emptyMap(),
runtimeExpected = setOf(btcQuote), runtimeExpected = setOf(btcQuote),
persistenceExpected = QuoteStatusDM.Empty, persistenceExpected = emptyMap(),
), ),
StoreTestModel( StoreTestModel(
initialRuntime = setOf(ethQuote), initialRuntime = setOf(ethQuote),
initialPersistence = null, initialPersistence = null,
values = mapOf(btcQuoteDM), values = mapOf(btcQuoteDM),
runtimeExpected = setOf(ethQuote, btcQuote), runtimeExpected = setOf(ethQuote, btcQuote),
persistenceExpected = persistedDefault(btcQuoteDM), persistenceExpected = mapOf(btcQuoteDM),
), ),
// endregion // endregion
// region runtime store is null // region runtime store is null
StoreTestModel( StoreTestModel(
initialRuntime = null, initialRuntime = null,
initialPersistence = persistedDefault(btcQuoteDM), initialPersistence = mapOf(btcQuoteDM),
values = emptyMap(), values = emptyMap(),
runtimeExpected = null, runtimeExpected = null,
persistenceExpected = persistedDefault(btcQuoteDM), persistenceExpected = mapOf(btcQuoteDM),
), ),
StoreTestModel( StoreTestModel(
initialRuntime = null, initialRuntime = null,
initialPersistence = persistedDefault(ethQuoteDM), initialPersistence = mapOf(ethQuoteDM),
values = mapOf(btcQuoteDM), values = mapOf(btcQuoteDM),
runtimeExpected = setOf(btcQuote), runtimeExpected = setOf(btcQuote),
persistenceExpected = persistedDefault(ethQuoteDM, btcQuoteDM), persistenceExpected = mapOf(ethQuoteDM, btcQuoteDM),
), ),
// endregion // endregion
// region stores contain data // region stores contain data
StoreTestModel( StoreTestModel(
initialRuntime = setOf(btcQuote), initialRuntime = setOf(btcQuote),
initialPersistence = persistedDefault(btcQuoteDM), initialPersistence = mapOf(btcQuoteDM),
values = emptyMap(), values = emptyMap(),
runtimeExpected = setOf(btcQuote), runtimeExpected = setOf(btcQuote),
persistenceExpected = persistedDefault(btcQuoteDM), persistenceExpected = mapOf(btcQuoteDM),
), ),
StoreTestModel( StoreTestModel(
initialRuntime = setOf(btcQuote), initialRuntime = setOf(btcQuote),
initialPersistence = persistedDefault(btcQuoteDM), initialPersistence = mapOf(btcQuoteDM),
values = mapOf(ethQuoteDM), values = mapOf(ethQuoteDM),
runtimeExpected = setOf(ethQuote, btcQuote), runtimeExpected = setOf(ethQuote, btcQuote),
persistenceExpected = persistedDefault(ethQuoteDM, btcQuoteDM), persistenceExpected = mapOf(ethQuoteDM, btcQuoteDM),
), ),
// endregion // endregion
) )
@ -563,18 +557,9 @@ internal class QuotesStatusesStoreTest {
data class StoreTestModel( data class StoreTestModel(
val initialRuntime: Set<QuoteStatus>?, val initialRuntime: Set<QuoteStatus>?,
val initialPersistence: QuoteStatusDM?, val initialPersistence: CurrencyIdWithQuote?,
val values: CurrencyIdWithQuote, val values: CurrencyIdWithQuote,
val persistenceExpected: QuoteStatusDM?, val persistenceExpected: CurrencyIdWithQuote?,
val runtimeExpected: Set<QuoteStatus>?, val runtimeExpected: Set<QuoteStatus>?,
) )
companion object {
private fun persistedDefault(
vararg quotes: Pair<String, com.tangem.datasource.api.tangemTech.models.QuotesResponse.Quote>,
): QuoteStatusDM = QuoteStatusDM(
fiatCurrency = FiatCurrencyConverter.convert(FiatCurrency.Default),
quotes = mapOf(*quotes),
)
}
} }

View file

@ -8,6 +8,5 @@ dependencies {
/** Project - Domain */ /** Project - Domain */
implementation(projects.core.utils) implementation(projects.core.utils)
implementation(projects.domain.core) implementation(projects.domain.core)
implementation(projects.domain.models)
implementation(projects.domain.appCurrency.models) implementation(projects.domain.appCurrency.models)
} }

View file

@ -3,7 +3,6 @@ package com.tangem.domain.appcurrency.extenstions
import arrow.core.getOrElse import arrow.core.getOrElse
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.currency.FiatCurrency
import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
@ -14,6 +13,4 @@ suspend fun GetSelectedAppCurrencyUseCase.unwrap(): AppCurrency {
} }
.firstOrNull() .firstOrNull()
?: AppCurrency.Default ?: AppCurrency.Default
} }
fun AppCurrency.toFiatCurrency(): FiatCurrency = FiatCurrency(code = code, symbol = symbol)

View file

@ -1,21 +0,0 @@
package com.tangem.domain.models.currency
import kotlinx.serialization.Serializable
/**
* Fiat currency in which a [com.tangem.domain.models.quote.QuoteStatus] is expressed. Plain business
* entity without UI metadata (icons, localized name) those live in `AppCurrency`.
*
* @property code ISO code, e.g. "USD"
* @property symbol display symbol, e.g. "$"
*/
@Serializable
data class FiatCurrency(
val code: String,
val symbol: String,
) {
companion object {
val Default = FiatCurrency(code = "USD", symbol = "$")
}
}

View file

@ -2,7 +2,6 @@ package com.tangem.domain.models.quote
import com.tangem.domain.models.StatusSource import com.tangem.domain.models.StatusSource
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.FiatCurrency
import java.math.BigDecimal import java.math.BigDecimal
/** /**
@ -39,15 +38,13 @@ data class QuoteStatus(val rawCurrencyId: CryptoCurrency.RawID, val value: Value
* Represents financial information for a specific cryptocurrency, including its fiat exchange rate and * Represents financial information for a specific cryptocurrency, including its fiat exchange rate and
* price change. * price change.
* *
* @property source status source * @property source status source
* @property fiatCurrency fiat currency in which [fiatRate] is expressed * @property fiatRate the current fiat exchange rate for the cryptocurrency
* @property fiatRate the current fiat exchange rate for the cryptocurrency * @property fiatRateUSD the current fiat exchange rate in USD for the cryptocurrency
* @property fiatRateUSD the current fiat exchange rate in USD for the cryptocurrency * @property priceChange the price change for the cryptocurrency
* @property priceChange the price change for the cryptocurrency
*/ */
data class Data( data class Data(
override val source: StatusSource, override val source: StatusSource,
val fiatCurrency: FiatCurrency,
val fiatRate: BigDecimal, val fiatRate: BigDecimal,
val fiatRateUSD: BigDecimal, val fiatRateUSD: BigDecimal,
val priceChange: BigDecimal, val priceChange: BigDecimal,

View file

@ -3,7 +3,6 @@ package com.tangem.domain.tokens.mock
import arrow.core.nonEmptySetOf import arrow.core.nonEmptySetOf
import com.tangem.domain.models.StatusSource import com.tangem.domain.models.StatusSource
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.FiatCurrency
import com.tangem.domain.models.quote.QuoteStatus import com.tangem.domain.models.quote.QuoteStatus
import java.math.BigDecimal import java.math.BigDecimal
@ -13,7 +12,6 @@ internal object MockQuotes {
val quote1 = QuoteStatus( val quote1 = QuoteStatus(
rawCurrencyId = MockTokens.token1.id.rawCurrencyId!!, rawCurrencyId = MockTokens.token1.id.rawCurrencyId!!,
value = QuoteStatus.Data( value = QuoteStatus.Data(
fiatCurrency = FiatCurrency.Default,
fiatRate = BigDecimal("1.23"), fiatRate = BigDecimal("1.23"),
fiatRateUSD = BigDecimal("1.23"), fiatRateUSD = BigDecimal("1.23"),
priceChange = BigDecimal("0.01"), priceChange = BigDecimal("0.01"),
@ -24,7 +22,6 @@ internal object MockQuotes {
val quote2 = QuoteStatus( val quote2 = QuoteStatus(
rawCurrencyId = MockTokens.token2.id.rawCurrencyId!!, rawCurrencyId = MockTokens.token2.id.rawCurrencyId!!,
value = QuoteStatus.Data( value = QuoteStatus.Data(
fiatCurrency = FiatCurrency.Default,
fiatRate = BigDecimal("2.34"), fiatRate = BigDecimal("2.34"),
fiatRateUSD = BigDecimal("2.34"), fiatRateUSD = BigDecimal("2.34"),
priceChange = BigDecimal("-0.02"), priceChange = BigDecimal("-0.02"),
@ -35,7 +32,6 @@ internal object MockQuotes {
val quote3 = QuoteStatus( val quote3 = QuoteStatus(
rawCurrencyId = MockTokens.token3.id.rawCurrencyId!!, rawCurrencyId = MockTokens.token3.id.rawCurrencyId!!,
value = QuoteStatus.Data( value = QuoteStatus.Data(
fiatCurrency = FiatCurrency.Default,
fiatRate = BigDecimal("3.45"), fiatRate = BigDecimal("3.45"),
fiatRateUSD = BigDecimal("3.45"), fiatRateUSD = BigDecimal("3.45"),
priceChange = BigDecimal("0.03"), priceChange = BigDecimal("0.03"),
@ -46,7 +42,6 @@ internal object MockQuotes {
val quote4 = QuoteStatus( val quote4 = QuoteStatus(
rawCurrencyId = MockTokens.token4.id.rawCurrencyId!!, rawCurrencyId = MockTokens.token4.id.rawCurrencyId!!,
value = QuoteStatus.Data( value = QuoteStatus.Data(
fiatCurrency = FiatCurrency.Default,
fiatRate = BigDecimal("4.56"), fiatRate = BigDecimal("4.56"),
fiatRateUSD = BigDecimal("4.56"), fiatRateUSD = BigDecimal("4.56"),
priceChange = BigDecimal("-0.04"), priceChange = BigDecimal("-0.04"),
@ -57,7 +52,6 @@ internal object MockQuotes {
val quote5 = QuoteStatus( val quote5 = QuoteStatus(
rawCurrencyId = MockTokens.token5.id.rawCurrencyId!!, rawCurrencyId = MockTokens.token5.id.rawCurrencyId!!,
value = QuoteStatus.Data( value = QuoteStatus.Data(
fiatCurrency = FiatCurrency.Default,
fiatRate = BigDecimal("5.67"), fiatRate = BigDecimal("5.67"),
fiatRateUSD = BigDecimal("5.67"), fiatRateUSD = BigDecimal("5.67"),
priceChange = BigDecimal("0.05"), priceChange = BigDecimal("0.05"),
@ -68,7 +62,6 @@ internal object MockQuotes {
val quote6 = QuoteStatus( val quote6 = QuoteStatus(
rawCurrencyId = MockTokens.token6.id.rawCurrencyId!!, rawCurrencyId = MockTokens.token6.id.rawCurrencyId!!,
value = QuoteStatus.Data( value = QuoteStatus.Data(
fiatCurrency = FiatCurrency.Default,
fiatRate = BigDecimal("6.78"), fiatRate = BigDecimal("6.78"),
fiatRateUSD = BigDecimal("6.78"), fiatRateUSD = BigDecimal("6.78"),
priceChange = BigDecimal("-0.06"), priceChange = BigDecimal("-0.06"),
@ -79,7 +72,6 @@ internal object MockQuotes {
val quote7 = QuoteStatus( val quote7 = QuoteStatus(
rawCurrencyId = MockTokens.token7.id.rawCurrencyId!!, rawCurrencyId = MockTokens.token7.id.rawCurrencyId!!,
value = QuoteStatus.Data( value = QuoteStatus.Data(
fiatCurrency = FiatCurrency.Default,
fiatRate = BigDecimal("7.89"), fiatRate = BigDecimal("7.89"),
fiatRateUSD = BigDecimal("7.89"), fiatRateUSD = BigDecimal("7.89"),
priceChange = BigDecimal("0.07"), priceChange = BigDecimal("0.07"),
@ -90,7 +82,6 @@ internal object MockQuotes {
val quote8 = QuoteStatus( val quote8 = QuoteStatus(
rawCurrencyId = MockTokens.token8.id.rawCurrencyId!!, rawCurrencyId = MockTokens.token8.id.rawCurrencyId!!,
value = QuoteStatus.Data( value = QuoteStatus.Data(
fiatCurrency = FiatCurrency.Default,
fiatRate = BigDecimal("8.90"), fiatRate = BigDecimal("8.90"),
fiatRateUSD = BigDecimal("8.90"), fiatRateUSD = BigDecimal("8.90"),
priceChange = BigDecimal("-0.08"), priceChange = BigDecimal("-0.08"),
@ -101,7 +92,6 @@ internal object MockQuotes {
val quote9 = QuoteStatus( val quote9 = QuoteStatus(
rawCurrencyId = MockTokens.token9.id.rawCurrencyId!!, rawCurrencyId = MockTokens.token9.id.rawCurrencyId!!,
value = QuoteStatus.Data( value = QuoteStatus.Data(
fiatCurrency = FiatCurrency.Default,
fiatRate = BigDecimal("9.01"), fiatRate = BigDecimal("9.01"),
fiatRateUSD = BigDecimal("9.01"), fiatRateUSD = BigDecimal("9.01"),
priceChange = BigDecimal("0.09"), priceChange = BigDecimal("0.09"),
@ -112,7 +102,6 @@ internal object MockQuotes {
val quote10 = QuoteStatus( val quote10 = QuoteStatus(
rawCurrencyId = MockTokens.token10.id.rawCurrencyId!!, rawCurrencyId = MockTokens.token10.id.rawCurrencyId!!,
value = QuoteStatus.Data( value = QuoteStatus.Data(
fiatCurrency = FiatCurrency.Default,
fiatRate = BigDecimal("10.12"), fiatRate = BigDecimal("10.12"),
fiatRateUSD = BigDecimal("10.12"), fiatRateUSD = BigDecimal("10.12"),
priceChange = BigDecimal("-0.10"), priceChange = BigDecimal("-0.10"),

View file

@ -7,7 +7,6 @@ import com.tangem.blockchain.common.Blockchain
import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory
import com.tangem.domain.models.StatusSource import com.tangem.domain.models.StatusSource
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.FiatCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.network.NetworkAddress import com.tangem.domain.models.network.NetworkAddress
import com.tangem.domain.models.network.NetworkStatus import com.tangem.domain.models.network.NetworkStatus
@ -44,7 +43,6 @@ class CryptoCurrencyStatusFactoryTest {
) )
private val fullQuote = QuoteStatus.Data( private val fullQuote = QuoteStatus.Data(
fiatCurrency = FiatCurrency.Default,
fiatRate = 1800.0.toBigDecimal(), fiatRate = 1800.0.toBigDecimal(),
fiatRateUSD = 1800.0.toBigDecimal(), fiatRateUSD = 1800.0.toBigDecimal(),
priceChange = (-2.5).toBigDecimal(), priceChange = (-2.5).toBigDecimal(),

View file

@ -7,7 +7,6 @@ import com.tangem.domain.core.utils.lceError
import com.tangem.domain.core.utils.lceLoading import com.tangem.domain.core.utils.lceLoading
import com.tangem.domain.models.StatusSource import com.tangem.domain.models.StatusSource
import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.currency.FiatCurrency
import com.tangem.domain.models.network.NetworkAddress import com.tangem.domain.models.network.NetworkAddress
import com.tangem.domain.models.quote.PriceChange import com.tangem.domain.models.quote.PriceChange
import com.tangem.domain.tokens.mock.MockTokens import com.tangem.domain.tokens.mock.MockTokens

View file

@ -8,7 +8,6 @@ import com.tangem.domain.models.StatusSource
import com.tangem.domain.models.TotalFiatBalance import com.tangem.domain.models.TotalFiatBalance
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.currency.FiatCurrency
import com.tangem.domain.models.network.NetworkAddress import com.tangem.domain.models.network.NetworkAddress
import com.tangem.domain.models.staking.BalanceItem import com.tangem.domain.models.staking.BalanceItem
import com.tangem.domain.models.staking.BalanceType import com.tangem.domain.models.staking.BalanceType

View file

@ -8,7 +8,6 @@ import com.tangem.domain.account.models.AccountList
import com.tangem.domain.account.supplier.SingleAccountListSupplier import com.tangem.domain.account.supplier.SingleAccountListSupplier
import com.tangem.domain.models.StatusSource import com.tangem.domain.models.StatusSource
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.FiatCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.network.Network import com.tangem.domain.models.network.Network
import com.tangem.domain.models.network.NetworkAddress import com.tangem.domain.models.network.NetworkAddress
@ -82,7 +81,6 @@ class YieldSupplyMinAmountUseCaseTest {
QuoteStatus( QuoteStatus(
rawCurrencyId = CryptoCurrency.RawID("polygon-ecosystem-token"), rawCurrencyId = CryptoCurrency.RawID("polygon-ecosystem-token"),
value = QuoteStatus.Data( value = QuoteStatus.Data(
fiatCurrency = FiatCurrency.Default,
source = StatusSource.ACTUAL, source = StatusSource.ACTUAL,
fiatRate = nativeFiatRate, fiatRate = nativeFiatRate,
fiatRateUSD = nativeFiatRate, fiatRateUSD = nativeFiatRate,

View file

@ -4,7 +4,6 @@ import arrow.core.Either
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.currency.FiatCurrency
import com.tangem.domain.models.network.Network import com.tangem.domain.models.network.Network
import com.tangem.domain.models.network.NetworkAddress import com.tangem.domain.models.network.NetworkAddress
import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.UserWalletId

View file

@ -8,7 +8,6 @@ import com.tangem.domain.account.models.AccountList
import com.tangem.domain.account.supplier.SingleAccountListSupplier import com.tangem.domain.account.supplier.SingleAccountListSupplier
import com.tangem.domain.models.StatusSource import com.tangem.domain.models.StatusSource
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.FiatCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.network.Network import com.tangem.domain.models.network.Network
import com.tangem.domain.models.network.NetworkAddress import com.tangem.domain.models.network.NetworkAddress
@ -78,7 +77,6 @@ class YieldSupplyGetCurrentFeeUseCaseTest {
QuoteStatus( QuoteStatus(
rawCurrencyId = nativeCoin.id.rawCurrencyId!!, rawCurrencyId = nativeCoin.id.rawCurrencyId!!,
value = QuoteStatus.Data( value = QuoteStatus.Data(
fiatCurrency = FiatCurrency.Default,
source = StatusSource.ACTUAL, source = StatusSource.ACTUAL,
fiatRate = nativeFiatRate, fiatRate = nativeFiatRate,
fiatRateUSD = nativeFiatRate, fiatRateUSD = nativeFiatRate,
@ -131,7 +129,6 @@ class YieldSupplyGetCurrentFeeUseCaseTest {
QuoteStatus( QuoteStatus(
rawCurrencyId = nativeCoin.id.rawCurrencyId!!, rawCurrencyId = nativeCoin.id.rawCurrencyId!!,
value = QuoteStatus.Data( value = QuoteStatus.Data(
fiatCurrency = FiatCurrency.Default,
source = StatusSource.ACTUAL, source = StatusSource.ACTUAL,
fiatRate = nativeFiatRate, fiatRate = nativeFiatRate,
fiatRateUSD = nativeFiatRate, fiatRateUSD = nativeFiatRate,
@ -259,7 +256,6 @@ class YieldSupplyGetCurrentFeeUseCaseTest {
QuoteStatus( QuoteStatus(
rawCurrencyId = nativeCoin.id.rawCurrencyId!!, rawCurrencyId = nativeCoin.id.rawCurrencyId!!,
value = QuoteStatus.Data( value = QuoteStatus.Data(
fiatCurrency = FiatCurrency.Default,
source = StatusSource.ACTUAL, source = StatusSource.ACTUAL,
fiatRate = BigDecimal.ZERO, // non-positive fiatRate = BigDecimal.ZERO, // non-positive
fiatRateUSD = BigDecimal.ZERO, fiatRateUSD = BigDecimal.ZERO,

View file

@ -4,7 +4,6 @@ import com.google.common.truth.Truth.assertThat
import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.currency.FiatCurrency
import com.tangem.domain.models.network.Network import com.tangem.domain.models.network.Network
import com.tangem.domain.models.network.NetworkAddress import com.tangem.domain.models.network.NetworkAddress
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test

View file

@ -8,7 +8,6 @@ import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.currency.FiatCurrency
import com.tangem.domain.models.network.Network import com.tangem.domain.models.network.Network
import com.tangem.domain.models.network.NetworkAddress import com.tangem.domain.models.network.NetworkAddress
import com.tangem.domain.yield.supply.YieldSupplyRepository import com.tangem.domain.yield.supply.YieldSupplyRepository

View file

@ -11,7 +11,6 @@ import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.blockchainsdk.utils.toNetworkId import com.tangem.blockchainsdk.utils.toNetworkId
import com.tangem.domain.models.StatusSource import com.tangem.domain.models.StatusSource
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.FiatCurrency
import com.tangem.domain.models.quote.QuoteStatus import com.tangem.domain.models.quote.QuoteStatus
import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.swap.models.SwapCurrencyStatus import com.tangem.domain.swap.models.SwapCurrencyStatus
@ -87,7 +86,6 @@ internal class SwapInteractorImplFindBestQuoteTest : SwapInteractorImplTestBase(
QuoteStatus( QuoteStatus(
rawCurrencyId = rawId, rawCurrencyId = rawId,
value = QuoteStatus.Data( value = QuoteStatus.Data(
fiatCurrency = FiatCurrency.Default,
source = StatusSource.ACTUAL, source = StatusSource.ACTUAL,
fiatRate = BigDecimal.ONE, fiatRate = BigDecimal.ONE,
fiatRateUSD = BigDecimal.ONE, fiatRateUSD = BigDecimal.ONE,