From 36e7f2d07f2b0f42e2bced06fd4fbf7a4fb287a4 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 19 Feb 2025 12:03:10 +0400 Subject: [PATCH] Updated on 2026-08-14 --- common/ui/build.gradle.kts | 1 + .../network/DefaultNetworksStatusesStore.kt | 38 +++++++++++--- .../converter/NetworkStatusConverter.kt | 5 +- .../NetworkStatusDataModelConverter.kt | 6 +-- .../local/quote/DefaultQuotesStore.kt | 44 ++++++++++++---- .../datasource/local/quote/QuotesStore.kt | 3 ++ .../local/quote/converter/QuoteConverter.kt | 3 +- .../local/token/DefaultStakingBalanceStore.kt | 22 +++++--- .../token/converter/YieldBalanceConverter.kt | 5 +- .../converters/HotCryptoCurrencyConverter.kt | 3 +- .../repository/DefaultQuotesRepository.kt | 1 + .../data/tokens/utils/NetworkStatusFactory.kt | 5 +- .../com/tangem/domain/models/StatusSource.kt | 24 +++++++++ domain/staking/models/build.gradle.kts | 1 + .../staking/model/stakekit/YieldBalance.kt | 5 +- domain/tokens/models/build.gradle.kts | 1 + .../tokens/model/CryptoCurrencyStatus.kt | 5 ++ .../domain/tokens/model/NetworkStatus.kt | 17 +++--- .../com/tangem/domain/tokens/model/Quote.kt | 5 +- .../operations/CurrencyStatusOperations.kt | 52 +++++++++++++++---- .../tangem/domain/tokens/mock/MockNetworks.kt | 9 ++-- .../tangem/domain/tokens/mock/MockQuotes.kt | 21 ++++---- .../domain/tokens/mock/MockTokensStates.kt | 6 +++ 23 files changed, 212 insertions(+), 70 deletions(-) create mode 100644 domain/models/src/main/kotlin/com/tangem/domain/models/StatusSource.kt diff --git a/common/ui/build.gradle.kts b/common/ui/build.gradle.kts index 6a6f96f728..a59d399816 100644 --- a/common/ui/build.gradle.kts +++ b/common/ui/build.gradle.kts @@ -30,6 +30,7 @@ dependencies { /** Project - Domain */ implementation(projects.domain.appCurrency.models) + implementation(projects.domain.models) implementation(projects.domain.legacy) implementation(projects.domain.staking.models) implementation(projects.domain.tokens.models) diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/network/DefaultNetworksStatusesStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/network/DefaultNetworksStatusesStore.kt index b1380981a3..2d5126dd66 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/network/DefaultNetworksStatusesStore.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/network/DefaultNetworksStatusesStore.kt @@ -5,6 +5,7 @@ import com.tangem.datasource.local.datastore.RuntimeDataStore import com.tangem.datasource.local.network.converter.NetworkStatusConverter import com.tangem.datasource.local.network.converter.NetworkStatusDataModelConverter import com.tangem.datasource.local.network.entity.NetworkStatusDM +import com.tangem.domain.models.StatusSource import com.tangem.domain.tokens.model.Network import com.tangem.domain.tokens.model.NetworkStatus import com.tangem.domain.wallets.models.UserWalletId @@ -98,21 +99,42 @@ internal class DefaultNetworksStatusesStore( cachedStatuses: Set, runtimeStatuses: Set, ): Set { - val runtimeMap = runtimeStatuses.associateBy { it.network } - val mergedCached = cachedStatuses.filter { it.network !in runtimeMap.keys } + return runtimeStatuses + .map { runtime -> + runtime.takeIf { it.value !is NetworkStatus.Unreachable } + ?: getCachedStatusIfPossible(cachedStatuses = cachedStatuses, runtime = runtime) + } + .toSet() + } - return mergedCached.toSet() + runtimeStatuses + private fun getCachedStatusIfPossible(cachedStatuses: Set, runtime: NetworkStatus): NetworkStatus { + val cached = cachedStatuses.firstOrNull { it.network == runtime.network } ?: return runtime + + val updatedCachedStatus = when (val status = cached.value) { + is NetworkStatus.NoAccount -> status.copy(source = StatusSource.ONLY_CACHE) + is NetworkStatus.Verified -> status.copy(source = StatusSource.ONLY_CACHE) + is NetworkStatus.Refreshing, + is NetworkStatus.Unreachable, + is NetworkStatus.MissedDerivation, + -> null + } + + return if (updatedCachedStatus != null) { + cached.copy(value = updatedCachedStatus) + } else { + runtime + } } private suspend fun storeNetworkStatusInPersistence(userWalletId: UserWalletId, networkStatus: NetworkStatus) { val network = networkStatus.network + // Converter will return null if the network status is not supported + val newStatus = NetworkStatusDataModelConverter.convert(value = networkStatus) ?: return + persistenceDataStore.updateData { storedStatuses -> - val userWalletStatuses = storedStatuses[userWalletId.stringValue] ?: emptySet() - val updatedStatuses = userWalletStatuses.addOrReplace( - item = NetworkStatusDataModelConverter.convert(value = networkStatus), - predicate = { it.networkId == network.id }, - ) + val userWalletStatuses = storedStatuses[userWalletId.stringValue].orEmpty() + val updatedStatuses = userWalletStatuses.addOrReplace(item = newStatus) { it.networkId == network.id } storedStatuses.toMutableMap().apply { this[userWalletId.stringValue] = updatedStatuses diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/network/converter/NetworkStatusConverter.kt b/core/datasource/src/main/java/com/tangem/datasource/local/network/converter/NetworkStatusConverter.kt index 5f01c06bec..f0a01555bf 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/network/converter/NetworkStatusConverter.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/network/converter/NetworkStatusConverter.kt @@ -1,6 +1,7 @@ package com.tangem.datasource.local.network.converter import com.tangem.datasource.local.network.entity.NetworkStatusDM +import com.tangem.domain.models.StatusSource import com.tangem.domain.tokens.model.Network import com.tangem.domain.tokens.model.NetworkStatus import com.tangem.utils.converter.Converter @@ -28,7 +29,7 @@ internal class NetworkStatusConverter( address = address, amounts = NetworkAmountsConverter.convert(value = value.amounts), pendingTransactions = mapOf(), - isCached = isCached, + source = if (isCached) StatusSource.CACHE else StatusSource.ACTUAL, ) } is NetworkStatusDM.NoAccount -> { @@ -36,7 +37,7 @@ internal class NetworkStatusConverter( address = address, amountToCreateAccount = value.amountToCreateAccount, errorMessage = value.errorMessage, - isCached = isCached, + source = if (isCached) StatusSource.CACHE else StatusSource.ACTUAL, ) } } diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/network/converter/NetworkStatusDataModelConverter.kt b/core/datasource/src/main/java/com/tangem/datasource/local/network/converter/NetworkStatusDataModelConverter.kt index 1a52b27c9e..cf42370c96 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/network/converter/NetworkStatusDataModelConverter.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/network/converter/NetworkStatusDataModelConverter.kt @@ -9,9 +9,9 @@ import com.tangem.utils.converter.Converter * [REDACTED_AUTHOR] */ -internal object NetworkStatusDataModelConverter : Converter { +internal object NetworkStatusDataModelConverter : Converter { - override fun convert(value: NetworkStatus): NetworkStatusDM { + override fun convert(value: NetworkStatus): NetworkStatusDM? { return when (val status = value.value) { is NetworkStatus.Verified -> { NetworkStatusDM.Verified( @@ -32,7 +32,7 @@ internal object NetworkStatusDataModelConverter : Converter error("Unsupported network status: $value") + else -> null } } } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/quote/DefaultQuotesStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/quote/DefaultQuotesStore.kt index 150f67eb66..d7e5299c72 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/quote/DefaultQuotesStore.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/quote/DefaultQuotesStore.kt @@ -4,6 +4,7 @@ import androidx.datastore.core.DataStore import com.tangem.datasource.api.tangemTech.models.QuotesResponse import com.tangem.datasource.local.datastore.RuntimeSharedStore import com.tangem.datasource.local.quote.converter.QuoteConverter +import com.tangem.domain.models.StatusSource import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.Quote import kotlinx.coroutines.coroutineScope @@ -32,7 +33,12 @@ internal class DefaultQuotesStore( runtimeStore.get() .onEach { - val mergedQuotes = mergeQuotes(cachedQuotes = cachedQuotes, runtimeQuotes = it) + val mergedQuotes = mergeQuotes( + currenciesIds = currenciesIds, + cachedQuotes = cachedQuotes, + runtimeQuotes = it, + ) + send(element = mergedQuotes) } .launchIn(scope = this) @@ -51,6 +57,14 @@ internal class DefaultQuotesStore( } } + override suspend fun storeEmptyQuotes(currenciesIds: Set) { + runtimeStore.update(default = emptySet()) { saved -> + val new = currenciesIds.map { Quote.Empty(it) } + + (saved + new).distinctBy { it.rawCurrencyId }.toSet() + } + } + private suspend fun getCachedQuotes(currenciesIds: Set): Set { val ids = currenciesIds.map(CryptoCurrency.RawID::value).toSet() val cachedQuotes = persistenceStore.data.firstOrNull().orEmpty().filterKeys { it in ids } @@ -58,15 +72,27 @@ internal class DefaultQuotesStore( return QuoteConverter(isCached = true).convertSet(input = cachedQuotes.entries) } - private fun mergeQuotes(cachedQuotes: Set, runtimeQuotes: Set): Set { - return runtimeQuotes.map { runtimeQuote -> - if (runtimeQuote is Quote.Empty) { - cachedQuotes.firstOrNull { runtimeQuote.rawCurrencyId == it.rawCurrencyId } ?: runtimeQuote - } else { - runtimeQuote + private fun mergeQuotes( + currenciesIds: Set, + cachedQuotes: Set, + runtimeQuotes: Set, + ): Set { + return currenciesIds + .mapTo(hashSetOf()) { currencyId -> + val runtimeQuote = runtimeQuotes.firstOrNull { it.rawCurrencyId == currencyId } + + if (runtimeQuote == null || runtimeQuote is Quote.Empty) { + getCachedQuoteIfPossible(cachedStatuses = cachedQuotes, currencyId = currencyId) + } else { + runtimeQuote + } } - } - .toSet() + } + + private fun getCachedQuoteIfPossible(cachedStatuses: Set, currencyId: CryptoCurrency.RawID): Quote { + return cachedStatuses.firstOrNull { it.rawCurrencyId == currencyId } + ?.copy(source = StatusSource.ONLY_CACHE) + ?: Quote.Empty(currencyId) } private suspend fun storeInRuntimeStore(response: QuotesResponse) { diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/quote/QuotesStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/quote/QuotesStore.kt index feb0872d92..4d632c3558 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/quote/QuotesStore.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/quote/QuotesStore.kt @@ -16,4 +16,7 @@ interface QuotesStore { /** Store [response] from remote */ suspend fun store(response: QuotesResponse) + + /** Store [Quote.Empty] for [currenciesIds] */ + suspend fun storeEmptyQuotes(currenciesIds: Set) } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/quote/converter/QuoteConverter.kt b/core/datasource/src/main/java/com/tangem/datasource/local/quote/converter/QuoteConverter.kt index 314d2ee920..120bea1730 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/quote/converter/QuoteConverter.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/quote/converter/QuoteConverter.kt @@ -1,6 +1,7 @@ package com.tangem.datasource.local.quote.converter import com.tangem.datasource.api.tangemTech.models.QuotesResponse +import com.tangem.domain.models.StatusSource import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.Quote import com.tangem.utils.converter.Converter @@ -23,7 +24,7 @@ internal class QuoteConverter(private val isCached: Boolean) : rawCurrencyId = CryptoCurrency.RawID(currencyId), fiatRate = quote.price.orZero(), priceChange = quote.priceChange24h.orZero().movePointLeft(2), - isCached = isCached, + source = if (isCached) StatusSource.CACHE else StatusSource.ACTUAL, ) } } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/token/DefaultStakingBalanceStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/token/DefaultStakingBalanceStore.kt index f5798a708a..50de014b59 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/token/DefaultStakingBalanceStore.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/token/DefaultStakingBalanceStore.kt @@ -4,6 +4,7 @@ import androidx.datastore.core.DataStore import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO import com.tangem.datasource.local.datastore.RuntimeSharedStore import com.tangem.datasource.local.token.converter.YieldBalanceConverter +import com.tangem.domain.models.StatusSource import com.tangem.domain.staking.model.stakekit.YieldBalance import com.tangem.domain.wallets.models.UserWalletId import com.tangem.utils.extensions.addOrReplace @@ -146,16 +147,25 @@ internal class DefaultStakingBalanceStore( ): Set { return runtimeBalances .map { runtime -> - if (runtime is YieldBalance.Error) { - cachedBalances.getBalance(address = runtime.address, integrationId = runtime.integrationId) - ?: runtime - } else { - runtime - } + runtime.takeIf { runtime !is YieldBalance.Error } + ?: getCachedBalanceIfPossible(cachedBalances, runtime) } .toSet() } + private fun getCachedBalanceIfPossible(cachedBalances: Set, runtime: YieldBalance): YieldBalance { + val cached = cachedBalances.getBalance(address = runtime.address, integrationId = runtime.integrationId) + ?: return runtime + + val updatedCached = when (cached) { + is YieldBalance.Data -> cached.copy(source = StatusSource.ONLY_CACHE) + is YieldBalance.Empty -> cached.copy(source = StatusSource.ONLY_CACHE) + is YieldBalance.Error -> null + } + + return updatedCached ?: runtime + } + private fun Set.getBalance(address: String?, integrationId: String?): YieldBalance? { return firstOrNull { yieldBalance -> val data = yieldBalance as? YieldBalance.Data diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/token/converter/YieldBalanceConverter.kt b/core/datasource/src/main/java/com/tangem/datasource/local/token/converter/YieldBalanceConverter.kt index 288a1f1323..9df25f3294 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/token/converter/YieldBalanceConverter.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/token/converter/YieldBalanceConverter.kt @@ -1,6 +1,7 @@ package com.tangem.datasource.local.token.converter import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO +import com.tangem.domain.models.StatusSource import com.tangem.domain.staking.model.stakekit.BalanceItem import com.tangem.domain.staking.model.stakekit.YieldBalance import com.tangem.domain.staking.model.stakekit.YieldBalanceItem @@ -13,7 +14,7 @@ internal class YieldBalanceConverter(private val isCached: Boolean) : Converter< YieldBalance.Empty( integrationId = value.integrationId, address = value.addresses.address, - isCached = isCached, + source = if (isCached) StatusSource.CACHE else StatusSource.ACTUAL, ) } else { YieldBalance.Data( @@ -39,7 +40,7 @@ internal class YieldBalanceConverter(private val isCached: Boolean) : Converter< .sortedWith(compareBy({ it.type }, { it.amount })), integrationId = value.integrationId, ), - isCached = isCached, + source = if (isCached) StatusSource.CACHE else StatusSource.ACTUAL, ) } } diff --git a/data/onramp/src/main/java/com/tangem/data/onramp/converters/HotCryptoCurrencyConverter.kt b/data/onramp/src/main/java/com/tangem/data/onramp/converters/HotCryptoCurrencyConverter.kt index 4d16a5b159..3f53fb1c9c 100644 --- a/data/onramp/src/main/java/com/tangem/data/onramp/converters/HotCryptoCurrencyConverter.kt +++ b/data/onramp/src/main/java/com/tangem/data/onramp/converters/HotCryptoCurrencyConverter.kt @@ -6,6 +6,7 @@ import com.tangem.blockchainsdk.utils.fromNetworkId import com.tangem.data.common.currency.CryptoCurrencyFactory import com.tangem.data.common.currency.getNetwork import com.tangem.datasource.api.tangemTech.models.HotCryptoResponse +import com.tangem.domain.models.StatusSource import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.onramp.model.HotCryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrency @@ -94,7 +95,7 @@ internal class HotCryptoCurrencyConverter( rawCurrencyId = rawCurrencyId, fiatRate = fiatRate, priceChange = priceChange.movePointLeft(2), - isCached = false, // It doesn't matter + source = StatusSource.ACTUAL, // It doesn't matter ) } else { Quote.Empty(rawCurrencyId) diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultQuotesRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultQuotesRepository.kt index c9b3f2ce64..e03676a2dc 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultQuotesRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultQuotesRepository.kt @@ -122,6 +122,7 @@ internal class DefaultQuotesRepository( }, onError = { error -> cacheRegistry.invalidate(rawCurrenciesIds.map { getQuoteCacheKey(it) }) + quotesStore.storeEmptyQuotes(currenciesIds = rawCurrenciesIds) throw error }, diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/NetworkStatusFactory.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/NetworkStatusFactory.kt index af8e2c7fe4..3ccfb526a8 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/NetworkStatusFactory.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/NetworkStatusFactory.kt @@ -1,5 +1,6 @@ package com.tangem.data.tokens.utils +import com.tangem.domain.models.StatusSource import com.tangem.domain.tokens.model.* import com.tangem.domain.txhistory.models.TxHistoryItem import com.tangem.domain.walletmanager.model.Address @@ -26,7 +27,7 @@ internal class NetworkStatusFactory { address = getNetworkAddress(result.selectedAddress, result.addresses), amountToCreateAccount = result.amountToCreateAccount, errorMessage = result.errorMessage, - isCached = false, + source = StatusSource.ACTUAL, ) is UpdateWalletManagerResult.Verified -> NetworkStatus.Verified( address = getNetworkAddress(result.selectedAddress, result.addresses), @@ -35,7 +36,7 @@ internal class NetworkStatusFactory { transactions = result.currentTransactions, currencies = currencies, ), - isCached = false, + source = StatusSource.ACTUAL, ) }, ) diff --git a/domain/models/src/main/kotlin/com/tangem/domain/models/StatusSource.kt b/domain/models/src/main/kotlin/com/tangem/domain/models/StatusSource.kt new file mode 100644 index 0000000000..cba099bbec --- /dev/null +++ b/domain/models/src/main/kotlin/com/tangem/domain/models/StatusSource.kt @@ -0,0 +1,24 @@ +package com.tangem.domain.models + +/** + * Source of the status of any loaded data + * +[REDACTED_AUTHOR] + */ +enum class StatusSource { + + /** + * Status is loaded from the cache. + * In most cases, it's a temporary value, then the source should become either [ACTUAL] or [ONLY_CACHE] + */ + CACHE, + + /** + * Status is updated by a data source that is of actual value. + * In most cases, this is a remote data source. But it can also be a value that we have updated programmatically. + */ + ACTUAL, + + /** Status is loaded from the cache and can't be updated with actual value */ + ONLY_CACHE, +} \ No newline at end of file diff --git a/domain/staking/models/build.gradle.kts b/domain/staking/models/build.gradle.kts index 68f2c9b6df..ca12daff07 100644 --- a/domain/staking/models/build.gradle.kts +++ b/domain/staking/models/build.gradle.kts @@ -9,6 +9,7 @@ plugins { dependencies { implementation(projects.domain.core) + implementation(projects.domain.models) implementation(deps.kotlin.serialization) implementation(deps.jodatime) diff --git a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/YieldBalance.kt b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/YieldBalance.kt index c36f0519fa..7456b68af6 100644 --- a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/YieldBalance.kt +++ b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/YieldBalance.kt @@ -1,5 +1,6 @@ package com.tangem.domain.staking.model.stakekit +import com.tangem.domain.models.StatusSource import com.tangem.domain.staking.model.stakekit.action.StakingActionType import org.joda.time.DateTime import java.math.BigDecimal @@ -13,7 +14,7 @@ sealed class YieldBalance { override val integrationId: String?, override val address: String, val balance: YieldBalanceItem, - val isCached: Boolean, + val source: StatusSource, ) : YieldBalance() { fun getTotalWithRewardsStakingBalance(): BigDecimal { return balance.items.sumOf { it.amount } @@ -42,7 +43,7 @@ sealed class YieldBalance { data class Empty( override val integrationId: String?, override val address: String, - val isCached: Boolean, + val source: StatusSource, ) : YieldBalance() data class Error(override val integrationId: String?, override val address: String?) : YieldBalance() diff --git a/domain/tokens/models/build.gradle.kts b/domain/tokens/models/build.gradle.kts index 92950e7beb..9a926341a8 100644 --- a/domain/tokens/models/build.gradle.kts +++ b/domain/tokens/models/build.gradle.kts @@ -9,6 +9,7 @@ dependencies { implementation(projects.core.analytics.models) /** Project - Domain */ + implementation(projects.domain.models) implementation(projects.domain.txhistory.models) implementation(projects.domain.staking.models) diff --git a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/CryptoCurrencyStatus.kt b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/CryptoCurrencyStatus.kt index d6db9ae7e0..d2df3fd74b 100644 --- a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/CryptoCurrencyStatus.kt +++ b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/CryptoCurrencyStatus.kt @@ -1,5 +1,6 @@ package com.tangem.domain.tokens.model +import com.tangem.domain.models.StatusSource import com.tangem.domain.staking.model.stakekit.YieldBalance import com.tangem.domain.txhistory.models.TxHistoryItem import java.math.BigDecimal @@ -83,6 +84,7 @@ data class CryptoCurrencyStatus( * Represents a state where there is no account associated with the cryptocurrency * * @property amountToCreateAccount base reserve amount for account creation + * @property source source of data */ data class NoAccount( val amountToCreateAccount: BigDecimal, @@ -90,6 +92,7 @@ data class CryptoCurrencyStatus( override val priceChange: BigDecimal?, override val fiatRate: BigDecimal?, override val networkAddress: NetworkAddress, + val source: StatusSource, ) : Value(isError = false) { override val amount: BigDecimal = BigDecimal.ZERO @@ -105,6 +108,7 @@ data class CryptoCurrencyStatus( * @property hasCurrentNetworkTransactions Indicates if there are any transactions in progress related to the * cryptocurrency network. * @property pendingTransactions The current cryptocurrency transactions. + * @property source source of data */ data class Loaded( override val amount: BigDecimal, @@ -115,6 +119,7 @@ data class CryptoCurrencyStatus( override val hasCurrentNetworkTransactions: Boolean, override val pendingTransactions: Set, override val networkAddress: NetworkAddress, + val source: StatusSource, ) : Value(isError = false) /** diff --git a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/NetworkStatus.kt b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/NetworkStatus.kt index 6b7d2c090b..1f9bd66e11 100644 --- a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/NetworkStatus.kt +++ b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/NetworkStatus.kt @@ -1,5 +1,6 @@ package com.tangem.domain.tokens.model +import com.tangem.domain.models.StatusSource import com.tangem.domain.txhistory.models.TxHistoryItem import java.math.BigDecimal @@ -21,14 +22,14 @@ data class NetworkStatus( */ sealed class Value { - abstract val isCached: Boolean + abstract val source: StatusSource } /** * Represents the state where the network is refreshing. */ data object Refreshing : Value() { - override val isCached: Boolean = false + override val source: StatusSource = StatusSource.ACTUAL } /** @@ -37,14 +38,14 @@ data class NetworkStatus( * @property address Network addresses. */ data class Unreachable(val address: NetworkAddress?) : Value() { - override val isCached: Boolean = false + override val source: StatusSource = StatusSource.ACTUAL } /** * Represents the state where a derivation has been missed. */ data object MissedDerivation : Value() { - override val isCached: Boolean = false + override val source: StatusSource = StatusSource.ACTUAL } /** @@ -54,14 +55,14 @@ data class NetworkStatus( * @property address Network addresses. * @property amounts A map containing the amounts associated with different cryptocurrencies within the network. * @property pendingTransactions A map containing pending transactions associated with different cryptocurrencies - * @property isCached flag that determines whether the status is a cache + * @property source source of data * within the network. */ data class Verified( val address: NetworkAddress, val amounts: Map, val pendingTransactions: Map>, - override val isCached: Boolean, + override val source: StatusSource, ) : Value() /** @@ -70,12 +71,12 @@ data class NetworkStatus( * @property address Network addresses. * @property amountToCreateAccount The amount required to create an account within the network. * @property errorMessage error message - * @property isCached flag that determines whether the status is a cache + * @property source source of data */ data class NoAccount( val address: NetworkAddress, val amountToCreateAccount: BigDecimal, val errorMessage: String, - override val isCached: Boolean, + override val source: StatusSource, ) : Value() } \ No newline at end of file diff --git a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/Quote.kt b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/Quote.kt index d4d7fb06c7..ebfb2e7525 100644 --- a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/Quote.kt +++ b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/Quote.kt @@ -1,5 +1,6 @@ package com.tangem.domain.tokens.model +import com.tangem.domain.models.StatusSource import java.math.BigDecimal sealed interface Quote { @@ -19,12 +20,12 @@ sealed interface Quote { * @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. - * @property isCached flag that determines whether the quote is a cache + * @property source source of data */ data class Value( override val rawCurrencyId: CryptoCurrency.RawID, val fiatRate: BigDecimal, val priceChange: BigDecimal, - val isCached: Boolean, + val source: StatusSource, ) : Quote } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrencyStatusOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrencyStatusOperations.kt index 2c73402bff..7e2dc0db71 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrencyStatusOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrencyStatusOperations.kt @@ -1,5 +1,6 @@ package com.tangem.domain.tokens.operations +import com.tangem.domain.models.StatusSource import com.tangem.domain.staking.model.stakekit.YieldBalance import com.tangem.domain.tokens.model.* import java.math.BigDecimal @@ -49,17 +50,27 @@ internal class CurrencyStatusOperations( ) } - private fun createNoAccountStatus(status: NetworkStatus.NoAccount): CryptoCurrencyStatus.NoAccount = - CryptoCurrencyStatus.NoAccount( + private fun createNoAccountStatus(status: NetworkStatus.NoAccount): CryptoCurrencyStatus.NoAccount { + return CryptoCurrencyStatus.NoAccount( amountToCreateAccount = status.amountToCreateAccount, fiatAmount = if (quote == null) null else BigDecimal.ZERO, priceChange = quote?.priceChange, fiatRate = quote?.fiatRate, networkAddress = status.address, + source = getResultStatusSource( + sources = listOf( + status.source, + (quote as? Quote.Value)?.source ?: StatusSource.ACTUAL, + ), + ), ) + } - private fun createStatus(status: NetworkStatus.Verified, yieldBalance: YieldBalance?): CryptoCurrencyStatus.Value { - val amount = when (val amount = status.amounts[currency.id]) { + private fun createStatus( + networkStatusValue: NetworkStatus.Verified, + yieldBalance: YieldBalance?, + ): CryptoCurrencyStatus.Value { + val amount = when (val amount = networkStatusValue.amounts[currency.id]) { null -> { return CryptoCurrencyStatus.Loading } @@ -69,10 +80,10 @@ internal class CurrencyStatusOperations( is CryptoCurrencyAmountStatus.Loaded -> amount.value } - val hasCurrentNetworkTransactions = status.pendingTransactions.isNotEmpty() - val currentTransactions = status.pendingTransactions.getOrElse(currency.id, ::emptySet) + val hasCurrentNetworkTransactions = networkStatusValue.pendingTransactions.isNotEmpty() + val currentTransactions = networkStatusValue.pendingTransactions.getOrElse(currency.id, ::emptySet) val yieldBalanceData = yieldBalance as? YieldBalance.Data - val isCurrentAddressStaking = yieldBalanceData?.address == status.address.defaultAddress.value + val isCurrentAddressStaking = yieldBalanceData?.address == networkStatusValue.address.defaultAddress.value val filteredTokenBalances = yieldBalanceData?.balance?.items?.filter { it.token.coinGeckoId == currency.id.rawCurrencyId?.value } @@ -94,14 +105,14 @@ internal class CurrencyStatusOperations( priceChange = quote?.priceChange, hasCurrentNetworkTransactions = hasCurrentNetworkTransactions, pendingTransactions = currentTransactions, - networkAddress = status.address, + networkAddress = networkStatusValue.address, yieldBalance = currentYieldBalance, ) quote is Quote.Empty || ignoreQuote -> CryptoCurrencyStatus.NoQuote( amount = amount, hasCurrentNetworkTransactions = hasCurrentNetworkTransactions, pendingTransactions = currentTransactions, - networkAddress = status.address, + networkAddress = networkStatusValue.address, yieldBalance = currentYieldBalance, ) quote is Quote.Value -> CryptoCurrencyStatus.Loaded( @@ -111,8 +122,15 @@ internal class CurrencyStatusOperations( priceChange = quote.priceChange, hasCurrentNetworkTransactions = hasCurrentNetworkTransactions, pendingTransactions = currentTransactions, - networkAddress = status.address, + networkAddress = networkStatusValue.address, yieldBalance = currentYieldBalance, + source = getResultStatusSource( + sources = listOf( + networkStatusValue.source, + currentYieldBalance?.source ?: StatusSource.ACTUAL, + quote.source, + ), + ), ) else -> CryptoCurrencyStatus.Loading } @@ -127,4 +145,18 @@ internal class CurrencyStatusOperations( private fun calculateFiatAmount(amount: BigDecimal, fiatRate: BigDecimal): BigDecimal { return amount * fiatRate } + + /* + * ACTUAL, ACTUAL, ACTUAL -> ACTUAL + * ACTUAL, ACTUAL, CACHE -> CACHE + * ACTUAL, ACTUAL, ONLY_CACHE -> ONLY_CACHE + * ACTUAL, CACHE, ONLY_CACHE -> ONLY_CACHE + */ + private fun getResultStatusSource(sources: List): StatusSource { + return when { + sources.any { it == StatusSource.ONLY_CACHE } -> StatusSource.ONLY_CACHE + sources.any { it == StatusSource.CACHE } -> StatusSource.CACHE + else -> StatusSource.ACTUAL + } + } } \ No newline at end of file diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockNetworks.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockNetworks.kt index 88ffcdfeaf..39e4ffa39c 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockNetworks.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockNetworks.kt @@ -2,6 +2,7 @@ package com.tangem.domain.tokens.mock import arrow.core.NonEmptySet import arrow.core.nonEmptySetOf +import com.tangem.domain.models.StatusSource import com.tangem.domain.tokens.model.CryptoCurrencyAmountStatus import com.tangem.domain.tokens.model.Network import com.tangem.domain.tokens.model.NetworkAddress @@ -76,7 +77,7 @@ internal object MockNetworks { defaultAddress = NetworkAddress.Address(value = "mock", NetworkAddress.Address.Type.Primary), ), errorMessage = "", - isCached = false, + source = StatusSource.ACTUAL, ), ) @@ -92,7 +93,7 @@ internal object MockNetworks { address = NetworkAddress.Single( defaultAddress = NetworkAddress.Address(value = "mock", NetworkAddress.Address.Type.Primary), ), - isCached = false, + source = StatusSource.ACTUAL, ), ) @@ -108,7 +109,7 @@ internal object MockNetworks { address = NetworkAddress.Single( defaultAddress = NetworkAddress.Address(value = "mock", NetworkAddress.Address.Type.Primary), ), - isCached = false, + source = StatusSource.ACTUAL, ), ) @@ -125,7 +126,7 @@ internal object MockNetworks { address = NetworkAddress.Single( defaultAddress = NetworkAddress.Address(value = "mock", NetworkAddress.Address.Type.Primary), ), - isCached = false, + source = StatusSource.ACTUAL, ), ) } \ No newline at end of file diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockQuotes.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockQuotes.kt index a3b1ee5456..885859db22 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockQuotes.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockQuotes.kt @@ -1,6 +1,7 @@ package com.tangem.domain.tokens.mock import arrow.core.nonEmptySetOf +import com.tangem.domain.models.StatusSource import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.Quote import java.math.BigDecimal @@ -12,70 +13,70 @@ internal object MockQuotes { rawCurrencyId = MockTokens.token1.id.rawCurrencyId!!, fiatRate = BigDecimal("1.23"), priceChange = BigDecimal("0.01"), - isCached = false, + source = StatusSource.ACTUAL, ) val quote2 = Quote.Value( rawCurrencyId = MockTokens.token2.id.rawCurrencyId!!, fiatRate = BigDecimal("2.34"), priceChange = BigDecimal("-0.02"), - isCached = false, + source = StatusSource.ACTUAL, ) val quote3 = Quote.Value( rawCurrencyId = MockTokens.token3.id.rawCurrencyId!!, fiatRate = BigDecimal("3.45"), priceChange = BigDecimal("0.03"), - isCached = false, + source = StatusSource.ACTUAL, ) val quote4 = Quote.Value( rawCurrencyId = MockTokens.token4.id.rawCurrencyId!!, fiatRate = BigDecimal("4.56"), priceChange = BigDecimal("-0.04"), - isCached = false, + source = StatusSource.ACTUAL, ) val quote5 = Quote.Value( rawCurrencyId = MockTokens.token5.id.rawCurrencyId!!, fiatRate = BigDecimal("5.67"), priceChange = BigDecimal("0.05"), - isCached = false, + source = StatusSource.ACTUAL, ) val quote6 = Quote.Value( rawCurrencyId = MockTokens.token6.id.rawCurrencyId!!, fiatRate = BigDecimal("6.78"), priceChange = BigDecimal("-0.06"), - isCached = false, + source = StatusSource.ACTUAL, ) val quote7 = Quote.Value( rawCurrencyId = MockTokens.token7.id.rawCurrencyId!!, fiatRate = BigDecimal("7.89"), priceChange = BigDecimal("0.07"), - isCached = false, + source = StatusSource.ACTUAL, ) val quote8 = Quote.Value( rawCurrencyId = MockTokens.token8.id.rawCurrencyId!!, fiatRate = BigDecimal("8.90"), priceChange = BigDecimal("-0.08"), - isCached = false, + source = StatusSource.ACTUAL, ) val quote9 = Quote.Value( rawCurrencyId = MockTokens.token9.id.rawCurrencyId!!, fiatRate = BigDecimal("9.01"), priceChange = BigDecimal("0.09"), - isCached = false, + source = StatusSource.ACTUAL, ) val quote10 = Quote.Value( rawCurrencyId = MockTokens.token10.id.rawCurrencyId!!, fiatRate = BigDecimal("10.12"), priceChange = BigDecimal("-0.10"), - isCached = false, + source = StatusSource.ACTUAL, ) val quote11 = Quote.Empty(CryptoCurrency.RawID("null")) diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokensStates.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokensStates.kt index a9d704b56f..c5e2c10c31 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokensStates.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokensStates.kt @@ -1,6 +1,7 @@ package com.tangem.domain.tokens.mock import arrow.core.nonEmptyListOf +import com.tangem.domain.models.StatusSource import com.tangem.domain.tokens.model.* import java.math.BigDecimal @@ -74,6 +75,7 @@ internal object MockTokensStates { networkAddress = NetworkAddress.Single( defaultAddress = NetworkAddress.Address(value = "mock", NetworkAddress.Address.Type.Primary), ), + source = StatusSource.ACTUAL, ), ) @@ -87,6 +89,7 @@ internal object MockTokensStates { networkAddress = NetworkAddress.Single( defaultAddress = NetworkAddress.Address(value = "mock", NetworkAddress.Address.Type.Primary), ), + source = StatusSource.ACTUAL, ), ) @@ -100,6 +103,7 @@ internal object MockTokensStates { networkAddress = NetworkAddress.Single( defaultAddress = NetworkAddress.Address(value = "mock", NetworkAddress.Address.Type.Primary), ), + source = StatusSource.ACTUAL, ), ) @@ -113,6 +117,7 @@ internal object MockTokensStates { networkAddress = NetworkAddress.Single( defaultAddress = NetworkAddress.Address(value = "mock", NetworkAddress.Address.Type.Primary), ), + source = StatusSource.ACTUAL, ), ) @@ -157,6 +162,7 @@ internal object MockTokensStates { hasCurrentNetworkTransactions = false, networkAddress = requireNotNull(networkStatus.value as? NetworkStatus.Verified).address, yieldBalance = null, + source = StatusSource.ACTUAL, ) } status.copy(value = value)