Updated on 2026-08-14
This commit is contained in:
parent
0bf2decb83
commit
36e7f2d07f
23 changed files with 212 additions and 70 deletions
|
|
@ -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<NetworkStatus>,
|
||||
runtimeStatuses: Set<NetworkStatus>,
|
||||
): Set<NetworkStatus> {
|
||||
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<NetworkStatus>, 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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,9 +9,9 @@ import com.tangem.utils.converter.Converter
|
|||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal object NetworkStatusDataModelConverter : Converter<NetworkStatus, NetworkStatusDM> {
|
||||
internal object NetworkStatusDataModelConverter : Converter<NetworkStatus, NetworkStatusDM?> {
|
||||
|
||||
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<NetworkStatus, Netwo
|
|||
errorMessage = status.errorMessage,
|
||||
)
|
||||
}
|
||||
else -> error("Unsupported network status: $value")
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<CryptoCurrency.RawID>) {
|
||||
runtimeStore.update(default = emptySet()) { saved ->
|
||||
val new = currenciesIds.map { Quote.Empty(it) }
|
||||
|
||||
(saved + new).distinctBy { it.rawCurrencyId }.toSet()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getCachedQuotes(currenciesIds: Set<CryptoCurrency.RawID>): Set<Quote.Value> {
|
||||
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<Quote.Value>, runtimeQuotes: Set<Quote>): Set<Quote> {
|
||||
return runtimeQuotes.map { runtimeQuote ->
|
||||
if (runtimeQuote is Quote.Empty) {
|
||||
cachedQuotes.firstOrNull { runtimeQuote.rawCurrencyId == it.rawCurrencyId } ?: runtimeQuote
|
||||
} else {
|
||||
runtimeQuote
|
||||
private fun mergeQuotes(
|
||||
currenciesIds: Set<CryptoCurrency.RawID>,
|
||||
cachedQuotes: Set<Quote.Value>,
|
||||
runtimeQuotes: Set<Quote>,
|
||||
): Set<Quote> {
|
||||
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<Quote.Value>, currencyId: CryptoCurrency.RawID): Quote {
|
||||
return cachedStatuses.firstOrNull { it.rawCurrencyId == currencyId }
|
||||
?.copy(source = StatusSource.ONLY_CACHE)
|
||||
?: Quote.Empty(currencyId)
|
||||
}
|
||||
|
||||
private suspend fun storeInRuntimeStore(response: QuotesResponse) {
|
||||
|
|
|
|||
|
|
@ -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<CryptoCurrency.RawID>)
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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<YieldBalance> {
|
||||
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<YieldBalance>, 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<YieldBalance>.getBalance(address: String?, integrationId: String?): YieldBalance? {
|
||||
return firstOrNull { yieldBalance ->
|
||||
val data = yieldBalance as? YieldBalance.Data
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue