From 054a7f982f68ac529d9343ac3ad137848daec3a7 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 20 Feb 2025 14:50:16 +0400 Subject: [PATCH] Updated on 2026-08-14 --- .../network/DefaultNetworksStatusesStore.kt | 37 +++++++++++++++---- .../local/network/NetworksStatusesStore.kt | 2 + .../local/quote/DefaultQuotesStore.kt | 29 ++++++++++----- .../datasource/local/quote/QuotesStore.kt | 2 + .../local/token/DefaultStakingBalanceStore.kt | 32 +++++++++++----- .../local/token/StakingBalanceStore.kt | 2 + .../data/staking/DefaultStakingRepository.kt | 4 ++ .../repository/DefaultNetworksRepository.kt | 13 +------ .../repository/DefaultQuotesRepository.kt | 4 ++ .../domain/tokens/model/NetworkStatus.kt | 15 +++++--- .../operations/CurrencyStatusOperations.kt | 1 - 11 files changed, 97 insertions(+), 44 deletions(-) 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 f3e3d06f65..8a2e170bb9 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 @@ -72,14 +72,26 @@ internal class DefaultNetworksStatusesStore( override suspend fun storeAll(key: UserWalletId, values: Set) { coroutineScope { - val updatedValues = getSyncOrNull(key).orEmpty() - .addOrReplace(items = values) { prev, new -> prev.network == new.network } - - launch { runtimeDataStore.store(key = provideStringKey(key), value = updatedValues) } - launch { storeNetworkStatusInPersistence(userWalletId = key, statuses = updatedValues) } + launch { storeInRuntimeStore(key = key, statuses = values) } + launch { storeInPersistenceStore(userWalletId = key, statuses = values) } } } + override suspend fun refresh(key: UserWalletId, networks: Set) { + val currentStatuses = getSyncOrNull(key).orEmpty() + + storeInRuntimeStore( + key = key, + statuses = networks.mapNotNullTo(hashSetOf()) { network -> + val status = currentStatuses.firstOrNull { it.network.id == network.id } ?: return@mapNotNullTo null + + status.copy( + value = status.value.copySealed(source = StatusSource.CACHE), + ) + }, + ) + } + /** * Merge [cachedStatuses] with [runtimeStatuses] * The resulting set contains statuses from both sets. @@ -108,7 +120,6 @@ internal class DefaultNetworksStatusesStore( 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 @@ -121,13 +132,23 @@ internal class DefaultNetworksStatusesStore( } } - private suspend fun storeNetworkStatusInPersistence(userWalletId: UserWalletId, statuses: Set) { + private suspend fun storeInRuntimeStore(key: UserWalletId, statuses: Set) { + val updatedValues = getSyncOrNull(key).orEmpty() + .addOrReplace(items = statuses) { prev, new -> prev.network == new.network } + + runtimeDataStore.store(key = provideStringKey(key), value = updatedValues) + } + + private suspend fun storeInPersistenceStore(userWalletId: UserWalletId, statuses: Set) { // Converter will return null if the network status is not supported val newStatuses = NetworkStatusDataModelConverter.convertSet(input = statuses).filterNotNull().toSet() persistenceDataStore.updateData { storedStatuses -> storedStatuses.toMutableMap().apply { - this[userWalletId.stringValue] = newStatuses + val updatedValues = this[userWalletId.stringValue].orEmpty() + .addOrReplace(newStatuses) { prev, new -> prev.networkId == new.networkId } + + this[userWalletId.stringValue] = updatedValues } } } diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/network/NetworksStatusesStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/network/NetworksStatusesStore.kt index 81838f97aa..a1d92c60cc 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/network/NetworksStatusesStore.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/network/NetworksStatusesStore.kt @@ -16,4 +16,6 @@ interface NetworksStatusesStore { suspend fun store(key: UserWalletId, value: NetworkStatus) suspend fun storeAll(key: UserWalletId, values: Set) + + suspend fun refresh(key: UserWalletId, networks: Set) } \ 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 d7e5299c72..8cc69f2fda 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 @@ -52,17 +52,30 @@ internal class DefaultQuotesStore( override suspend fun store(response: QuotesResponse) { coroutineScope { - launch { storeInRuntimeStore(response = response) } + launch { + storeInRuntimeStore( + values = QuoteConverter(isCached = false).convertSet(input = response.quotes.entries), + ) + } launch { storeInPersistenceStore(response = response) } } } override suspend fun storeEmptyQuotes(currenciesIds: Set) { - runtimeStore.update(default = emptySet()) { saved -> - val new = currenciesIds.map { Quote.Empty(it) } + storeInRuntimeStore(values = currenciesIds.map(Quote::Empty).toSet()) + } - (saved + new).distinctBy { it.rawCurrencyId }.toSet() - } + override suspend fun refresh(currenciesIds: Set) { + val currentStatuses = getSync(currenciesIds) + + storeInRuntimeStore( + values = currentStatuses.mapTo(hashSetOf()) { quote -> + when (quote) { + is Quote.Empty -> quote + is Quote.Value -> quote.copy(source = StatusSource.CACHE) + } + }, + ) } private suspend fun getCachedQuotes(currenciesIds: Set): Set { @@ -95,11 +108,9 @@ internal class DefaultQuotesStore( ?: Quote.Empty(currencyId) } - private suspend fun storeInRuntimeStore(response: QuotesResponse) { - val new = QuoteConverter(isCached = false).convertSet(input = response.quotes.entries) - + private suspend fun storeInRuntimeStore(values: Set) { runtimeStore.update(default = emptySet()) { saved -> - (saved + new).distinctBy { it.rawCurrencyId }.toSet() + (saved + values).distinctBy { it.rawCurrencyId }.toSet() } } 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 4d632c3558..69b76b154b 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 @@ -19,4 +19,6 @@ interface QuotesStore { /** Store [Quote.Empty] for [currenciesIds] */ suspend fun storeEmptyQuotes(currenciesIds: Set) + + suspend fun refresh(currenciesIds: Set) } \ 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 50de014b59..aaadb1d435 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 @@ -72,15 +72,26 @@ internal class DefaultStakingBalanceStore( override suspend fun store(userWalletId: UserWalletId, items: Set) { coroutineScope { launch { - storeInRuntimeStore( - userWalletId = userWalletId, - items = YieldBalanceConverter(isCached = false).convertSet(input = items), - ) + updateRuntimeStore(userWalletId = userWalletId) { + YieldBalanceConverter(isCached = false).convertSet(input = items) + } } launch { storeInPersistenceStore(userWalletId = userWalletId, items = items) } } } + override suspend fun refresh(userWalletId: UserWalletId) { + updateRuntimeStore(userWalletId = userWalletId) { saved -> + saved.mapTo(hashSetOf()) { + when (it) { + is YieldBalance.Data -> it.copy(source = StatusSource.CACHE) + is YieldBalance.Empty -> it.copy(source = StatusSource.CACHE) + is YieldBalance.Error -> it + } + } + } + } + override suspend fun store( userWalletId: UserWalletId, integrationId: String, @@ -107,13 +118,16 @@ internal class DefaultStakingBalanceStore( ?.addOrReplace(newBalance) { it.integrationId == integrationId && it.address == address } ?: setOf(newBalance) - storeInRuntimeStore(userWalletId = userWalletId, items = balances) + updateRuntimeStore(userWalletId = userWalletId) { balances } } - private suspend fun storeInRuntimeStore(userWalletId: UserWalletId, items: Set) { - runtimeStore.update(default = emptyMap()) { - it.toMutableMap().apply { - this[userWalletId] = items + private suspend fun updateRuntimeStore( + userWalletId: UserWalletId, + function: (Set) -> Set, + ) { + runtimeStore.update(default = emptyMap()) { saved -> + saved.toMutableMap().apply { + this[userWalletId] = function(this[userWalletId].orEmpty()) } } } diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/token/StakingBalanceStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/token/StakingBalanceStore.kt index 0638d1ac9f..c09987f7f4 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/token/StakingBalanceStore.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/token/StakingBalanceStore.kt @@ -26,4 +26,6 @@ interface StakingBalanceStore { /** Store [item] by [userWalletId], [integrationId] and [address] */ suspend fun store(userWalletId: UserWalletId, integrationId: String, address: String, item: YieldBalanceWrapperDTO) + + suspend fun refresh(userWalletId: UserWalletId) } \ No newline at end of file diff --git a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt index 4e4c6e7833..620bad0ce9 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt @@ -402,6 +402,10 @@ internal class DefaultStakingRepository( cryptoCurrencies: List, refresh: Boolean, ) = withContext(dispatchers.io) { + if (refresh) { + stakingBalanceStore.refresh(userWalletId = userWalletId) + } + cacheRegistry.invokeOnExpire( key = getYieldBalancesKey(userWalletId), skipCache = refresh, diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt index 0760786a7d..85fce53032 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt @@ -50,6 +50,7 @@ internal class DefaultNetworksRepository( networks: Set, ): Flow> { return networksStatusesStore.get(userWalletId, networks) + .distinctUntilChanged() .flowOn(dispatchers.io) } @@ -120,17 +121,7 @@ internal class DefaultNetworksRepository( refresh: Boolean, ) = coroutineScope { if (refresh) { - val statusesToRefresh = networksStatusesStore.getSyncOrNull(userWalletId)?.mapNotNull { - if (it.network in networks) { - it.copy(value = NetworkStatus.Refreshing) - } else { - null - } - } - - if (statusesToRefresh != null) { - networksStatusesStore.storeAll(key = userWalletId, values = statusesToRefresh.toSet()) - } + networksStatusesStore.refresh(key = userWalletId, networks = networks) } val currencies = getCurrencies(userWalletId, networks) 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 8c7ce6b8bf..0874594282 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 @@ -100,6 +100,10 @@ internal class DefaultQuotesRepository( // it changes after filterExpiredCurrenciesIds // calls with different coroutines and lead to fetchQuotes mutex.withLock { + if (refresh) { + quotesStore.refresh(currenciesIds) + } + val expiredCurrenciesIds = filterExpiredCurrenciesIds( currenciesIds = currenciesIds, refresh = refresh || quotesFetchedForAppCurrency != appCurrencyId, 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 1f9bd66e11..6bacb5067b 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 @@ -23,13 +23,16 @@ data class NetworkStatus( sealed class Value { abstract val source: StatusSource - } - /** - * Represents the state where the network is refreshing. - */ - data object Refreshing : Value() { - override val source: StatusSource = StatusSource.ACTUAL + fun copySealed(source: StatusSource): Value { + return when (this) { + is NoAccount -> copy(source = source) + is Verified -> copy(source = source) + is Unreachable, + is MissedDerivation, + -> this + } + } } /** 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 7e2dc0db71..d160103afe 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 @@ -30,7 +30,6 @@ internal class CurrencyStatusOperations( private fun createStatus(): CryptoCurrencyStatus.Value { return when (val status = networkStatus?.value) { null, - is NetworkStatus.Refreshing, -> CryptoCurrencyStatus.Loading is NetworkStatus.MissedDerivation -> createMissedDerivationStatus() is NetworkStatus.Unreachable -> createUnreachableStatus(status)