Updated on 2026-08-14
This commit is contained in:
parent
4c7016557f
commit
d10f285f44
8 changed files with 33 additions and 316 deletions
|
|
@ -5,7 +5,9 @@ import com.tangem.domain.exchange.RampStateManager
|
|||
import com.tangem.domain.promo.PromoRepository
|
||||
import com.tangem.domain.staking.repositories.StakingRepository
|
||||
import com.tangem.domain.tokens.*
|
||||
import com.tangem.domain.tokens.operations.*
|
||||
import com.tangem.domain.tokens.operations.BaseCurrenciesStatusesOperations
|
||||
import com.tangem.domain.tokens.operations.BaseCurrencyStatusOperations
|
||||
import com.tangem.domain.tokens.operations.CachedCurrenciesStatusesOperations
|
||||
import com.tangem.domain.tokens.repository.*
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.features.swap.SwapFeatureToggles
|
||||
|
|
@ -351,21 +353,13 @@ internal object TokensDomainModule {
|
|||
networksRepository: NetworksRepository,
|
||||
stakingRepository: StakingRepository,
|
||||
): BaseCurrenciesStatusesOperations {
|
||||
return if (tokensFeatureToggles.isBalancesCachingEnabled) {
|
||||
CachedCurrenciesStatusesOperations(
|
||||
currenciesRepository = currenciesRepository,
|
||||
quotesRepository = quotesRepository,
|
||||
networksRepository = networksRepository,
|
||||
stakingRepository = stakingRepository,
|
||||
)
|
||||
} else {
|
||||
LceCurrenciesStatusesOperations(
|
||||
currenciesRepository = currenciesRepository,
|
||||
quotesRepository = quotesRepository,
|
||||
networksRepository = networksRepository,
|
||||
stakingRepository = stakingRepository,
|
||||
)
|
||||
}
|
||||
return CachedCurrenciesStatusesOperations(
|
||||
currenciesRepository = currenciesRepository,
|
||||
quotesRepository = quotesRepository,
|
||||
networksRepository = networksRepository,
|
||||
stakingRepository = stakingRepository,
|
||||
tokensFeatureToggles = tokensFeatureToggles,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
@ -377,20 +371,12 @@ internal object TokensDomainModule {
|
|||
networksRepository: NetworksRepository,
|
||||
stakingRepository: StakingRepository,
|
||||
): BaseCurrencyStatusOperations {
|
||||
return if (tokensFeatureToggles.isBalancesCachingEnabled) {
|
||||
CachedCurrenciesStatusesOperations(
|
||||
currenciesRepository = currenciesRepository,
|
||||
quotesRepository = quotesRepository,
|
||||
networksRepository = networksRepository,
|
||||
stakingRepository = stakingRepository,
|
||||
)
|
||||
} else {
|
||||
CurrenciesStatusesOperations(
|
||||
currenciesRepository = currenciesRepository,
|
||||
quotesRepository = quotesRepository,
|
||||
networksRepository = networksRepository,
|
||||
stakingRepository = stakingRepository,
|
||||
)
|
||||
}
|
||||
return CachedCurrenciesStatusesOperations(
|
||||
currenciesRepository = currenciesRepository,
|
||||
quotesRepository = quotesRepository,
|
||||
networksRepository = networksRepository,
|
||||
stakingRepository = stakingRepository,
|
||||
tokensFeatureToggles = tokensFeatureToggles,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,6 @@ internal class DefaultTokensFeatureToggles(
|
|||
private val featureTogglesManager: FeatureTogglesManager,
|
||||
) : TokensFeatureToggles {
|
||||
|
||||
override val isBalancesCachingEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled(name = "BALANCES_CACHING_ENABLED")
|
||||
override val isNetworksLoadingRefactoringEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled(name = "NETWORKS_LOADING_REFACTORING_ENABLED")
|
||||
}
|
||||
|
|
@ -43,10 +43,6 @@
|
|||
"name": "STAKING_TON_ENABLED",
|
||||
"version": "undefined"
|
||||
},
|
||||
{
|
||||
"name": "BALANCES_CACHING_ENABLED",
|
||||
"version": "5.21.0"
|
||||
},
|
||||
{
|
||||
"name": "NFT_ENABLED",
|
||||
"version": "undefined"
|
||||
|
|
@ -82,5 +78,9 @@
|
|||
{
|
||||
"name": "WALLET_CONNECT_REDESIGN_ENABLED",
|
||||
"version": "undefined"
|
||||
},
|
||||
{
|
||||
"name": "NETWORKS_LOADING_REFACTORING_ENABLED",
|
||||
"version": "undefined"
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -7,5 +7,5 @@ package com.tangem.domain.tokens
|
|||
*/
|
||||
interface TokensFeatureToggles {
|
||||
|
||||
val isBalancesCachingEnabled: Boolean
|
||||
val isNetworksLoadingRefactoringEnabled: Boolean
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@ import com.tangem.domain.core.utils.lceError
|
|||
import com.tangem.domain.staking.model.stakekit.YieldBalance
|
||||
import com.tangem.domain.staking.model.stakekit.YieldBalanceList
|
||||
import com.tangem.domain.staking.repositories.StakingRepository
|
||||
import com.tangem.domain.tokens.TokensFeatureToggles
|
||||
import com.tangem.domain.tokens.error.TokenListError
|
||||
import com.tangem.domain.tokens.model.*
|
||||
import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations.Error
|
||||
|
|
@ -28,6 +29,7 @@ class CachedCurrenciesStatusesOperations(
|
|||
private val quotesRepository: QuotesRepository,
|
||||
private val networksRepository: NetworksRepository,
|
||||
private val stakingRepository: StakingRepository,
|
||||
@Suppress("UnusedPrivateMember") private val tokensFeatureToggles: TokensFeatureToggles,
|
||||
) : BaseCurrenciesStatusesOperations,
|
||||
BaseCurrencyStatusOperations(currenciesRepository, quotesRepository, networksRepository, stakingRepository) {
|
||||
|
||||
|
|
@ -258,6 +260,7 @@ class CachedCurrenciesStatusesOperations(
|
|||
true
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
.onEmpty { emit(Error.EmptyNetworksStatuses.left()) }
|
||||
}
|
||||
|
||||
private fun getNetworksStatuses(
|
||||
|
|
|
|||
|
|
@ -1,57 +1,6 @@
|
|||
package com.tangem.domain.tokens.operations
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.tangem.domain.core.utils.EitherFlow
|
||||
import com.tangem.domain.staking.repositories.StakingRepository
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.tokens.model.NetworkStatus
|
||||
import com.tangem.domain.tokens.model.Quote
|
||||
import com.tangem.domain.tokens.operations.CachedCurrenciesStatusesOperations.Companion.RETRY_DELAY
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.tokens.repository.NetworksRepository
|
||||
import com.tangem.domain.tokens.repository.QuotesRepository
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
class CurrenciesStatusesOperations(
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
private val quotesRepository: QuotesRepository,
|
||||
private val networksRepository: NetworksRepository,
|
||||
stakingRepository: StakingRepository,
|
||||
) : BaseCurrencyStatusOperations(currenciesRepository, quotesRepository, networksRepository, stakingRepository) {
|
||||
|
||||
override fun getQuotes(id: CryptoCurrency.RawID): Flow<Either<Error, Set<Quote>>> {
|
||||
return quotesRepository.getQuotesUpdatesLegacy(setOf(id))
|
||||
.map<Set<Quote>, Either<Error, Set<Quote>>> { quotes ->
|
||||
if (quotes.isEmpty()) Error.EmptyQuotes.left() else quotes.right()
|
||||
}
|
||||
.retryWhen { cause, _ ->
|
||||
emit(Error.DataError(cause).left())
|
||||
delay(RETRY_DELAY)
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
override fun getNetworksStatuses(
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
): EitherFlow<Error, Set<NetworkStatus>> {
|
||||
return networksRepository.getNetworkStatusesUpdatesLegacy(userWalletId, setOf(network))
|
||||
.map<Set<NetworkStatus>, Either<Error, Set<NetworkStatus>>> { it.right() }
|
||||
.catch { emit(Error.DataError(it).left()) }
|
||||
.onEmpty { emit(Error.EmptyNetworksStatuses.left()) }
|
||||
}
|
||||
|
||||
override suspend fun fetchComponents(
|
||||
userWalletId: UserWalletId,
|
||||
networks: Set<Network>,
|
||||
currenciesIds: Set<CryptoCurrency.ID>,
|
||||
currencies: List<CryptoCurrency>,
|
||||
): Either<Throwable, Unit> = Unit.right()
|
||||
class CurrenciesStatusesOperations {
|
||||
|
||||
sealed class Error {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,224 +0,0 @@
|
|||
package com.tangem.domain.tokens.operations
|
||||
|
||||
import arrow.core.*
|
||||
import arrow.core.raise.ensureNotNull
|
||||
import arrow.core.raise.recover
|
||||
import com.tangem.domain.core.lce.Lce
|
||||
import com.tangem.domain.core.lce.LceFlow
|
||||
import com.tangem.domain.core.lce.lce
|
||||
import com.tangem.domain.core.lce.lceFlow
|
||||
import com.tangem.domain.core.utils.EitherFlow
|
||||
import com.tangem.domain.staking.model.stakekit.YieldBalance
|
||||
import com.tangem.domain.staking.model.stakekit.YieldBalanceList
|
||||
import com.tangem.domain.staking.repositories.StakingRepository
|
||||
import com.tangem.domain.tokens.error.TokenListError
|
||||
import com.tangem.domain.tokens.model.*
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.tokens.repository.NetworksRepository
|
||||
import com.tangem.domain.tokens.repository.QuotesRepository
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
class LceCurrenciesStatusesOperations(
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val quotesRepository: QuotesRepository,
|
||||
private val networksRepository: NetworksRepository,
|
||||
private val stakingRepository: StakingRepository,
|
||||
) : BaseCurrenciesStatusesOperations {
|
||||
|
||||
override fun getCurrenciesStatuses(
|
||||
userWalletId: UserWalletId,
|
||||
): LceFlow<TokenListError, List<CryptoCurrencyStatus>> {
|
||||
return transformToCurrenciesStatuses(
|
||||
userWalletId = userWalletId,
|
||||
currenciesFlow = getWalletCurrencies(userWalletId),
|
||||
)
|
||||
}
|
||||
|
||||
private fun transformToCurrenciesStatuses(
|
||||
userWalletId: UserWalletId,
|
||||
currenciesFlow: EitherFlow<TokenListError, List<CryptoCurrency>>,
|
||||
): LceFlow<TokenListError, List<CryptoCurrencyStatus>> = lceFlow {
|
||||
currenciesFlow.collectLatest { maybeCurrencies ->
|
||||
val nonEmptyCurrencies = maybeCurrencies.bind().toNonEmptyListOrNull()
|
||||
ensureNotNull(nonEmptyCurrencies) { TokenListError.EmptyTokens }
|
||||
|
||||
// This is only 'true' when the flow here is empty, such as during initial loading
|
||||
if (isLoading.get()) {
|
||||
val loadingCurrencies = createCurrenciesStatuses(
|
||||
currencies = nonEmptyCurrencies,
|
||||
maybeNetworkStatuses = null,
|
||||
maybeQuotes = null,
|
||||
maybeYieldBalances = null,
|
||||
)
|
||||
send(loadingCurrencies)
|
||||
}
|
||||
|
||||
val (networks, currenciesIds) = getIds(nonEmptyCurrencies)
|
||||
|
||||
fun createCurrenciesStatuses(
|
||||
maybeQuotes: Either<TokenListError, Set<Quote>>?,
|
||||
maybeNetworkStatuses: Either<TokenListError, Set<NetworkStatus>>?,
|
||||
maybeYieldBalances: Either<TokenListError, YieldBalanceList>?,
|
||||
): Lce<TokenListError, List<CryptoCurrencyStatus>> = createCurrenciesStatuses(
|
||||
currencies = nonEmptyCurrencies,
|
||||
maybeQuotes = maybeQuotes,
|
||||
maybeNetworkStatuses = maybeNetworkStatuses,
|
||||
maybeYieldBalances = maybeYieldBalances,
|
||||
)
|
||||
|
||||
combine(
|
||||
getQuotes(currenciesIds),
|
||||
getNetworksStatuses(userWalletId, networks),
|
||||
getYieldBalances(userWalletId, nonEmptyCurrencies),
|
||||
::createCurrenciesStatuses,
|
||||
)
|
||||
.distinctUntilChanged()
|
||||
.collectLatest { maybeCurrenciesStatuses ->
|
||||
send(maybeCurrenciesStatuses)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getWalletCurrencies(userWalletId: UserWalletId): EitherFlow<TokenListError, List<CryptoCurrency>> {
|
||||
return currenciesRepository.getWalletCurrenciesUpdates(userWalletId)
|
||||
.map<List<CryptoCurrency>, Either<TokenListError, List<CryptoCurrency>>> { it.right() }
|
||||
.catch { emit(TokenListError.DataError(it).left()) }
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
|
||||
private fun createCurrenciesStatuses(
|
||||
currencies: NonEmptyList<CryptoCurrency>,
|
||||
maybeQuotes: Either<TokenListError, Set<Quote>>?,
|
||||
maybeNetworkStatuses: Either<TokenListError, Set<NetworkStatus>>?,
|
||||
maybeYieldBalances: Either<TokenListError, YieldBalanceList>?,
|
||||
): Lce<TokenListError, List<CryptoCurrencyStatus>> = lce {
|
||||
isLoading.set(maybeNetworkStatuses == null || maybeYieldBalances == null)
|
||||
|
||||
var quotesRetrievingFailed = false
|
||||
|
||||
val networksStatuses = maybeNetworkStatuses?.bindEither()?.toNonEmptySetOrNull()
|
||||
val yieldBalances = maybeYieldBalances?.bindEither()
|
||||
val quotes = recover({ maybeQuotes?.bind()?.toNonEmptySetOrNull() }) {
|
||||
null
|
||||
}
|
||||
|
||||
if (quotes == null) {
|
||||
quotesRetrievingFailed = true
|
||||
}
|
||||
|
||||
currencies.map { currency ->
|
||||
val quote = quotes?.firstOrNull { it.rawCurrencyId == currency.id.rawCurrencyId }
|
||||
val networkStatus = networksStatuses?.firstOrNull { it.network == currency.network }
|
||||
val yieldBalance = findYieldBalanceOrNull(yieldBalances, currency, networkStatus)
|
||||
|
||||
val currencyStatus = createCurrencyStatus(
|
||||
currency = currency,
|
||||
quote = quote,
|
||||
networkStatus = networkStatus,
|
||||
yieldBalance = yieldBalance,
|
||||
ignoreQuote = quotesRetrievingFailed,
|
||||
)
|
||||
|
||||
if (currencyStatus.value is CryptoCurrencyStatus.Loading) {
|
||||
isLoading.set(true)
|
||||
}
|
||||
|
||||
currencyStatus
|
||||
}
|
||||
}
|
||||
|
||||
private fun findYieldBalanceOrNull(
|
||||
yieldBalances: YieldBalanceList?,
|
||||
currency: CryptoCurrency,
|
||||
networkStatus: NetworkStatus?,
|
||||
): YieldBalance? {
|
||||
if (yieldBalances !is YieldBalanceList.Data) return null
|
||||
|
||||
val supportedIntegration = stakingRepository.getSupportedIntegrationId(currency.id)
|
||||
|
||||
if (supportedIntegration.isNullOrBlank()) return null
|
||||
|
||||
return yieldBalances.getBalance(
|
||||
address = extractAddress(networkStatus),
|
||||
integrationId = supportedIntegration,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createCurrencyStatus(
|
||||
currency: CryptoCurrency,
|
||||
quote: Quote?,
|
||||
networkStatus: NetworkStatus?,
|
||||
yieldBalance: YieldBalance?,
|
||||
ignoreQuote: Boolean,
|
||||
): CryptoCurrencyStatus {
|
||||
val currencyStatusOperations = CurrencyStatusOperations(
|
||||
currency = currency,
|
||||
quote = quote,
|
||||
networkStatus = networkStatus,
|
||||
yieldBalance = yieldBalance,
|
||||
ignoreQuote = ignoreQuote,
|
||||
)
|
||||
|
||||
return currencyStatusOperations.createTokenStatus()
|
||||
}
|
||||
|
||||
private fun getQuotes(tokensIds: NonEmptySet<CryptoCurrency.ID>): Flow<Either<TokenListError, Set<Quote>>> {
|
||||
return quotesRepository.getQuotesUpdatesLegacy(tokensIds.mapNotNull { it.rawCurrencyId }.toSet())
|
||||
.map<Set<Quote>, Either<TokenListError, Set<Quote>>> { it.right() }
|
||||
.retryWhen { cause, _ ->
|
||||
emit(TokenListError.DataError(cause).left())
|
||||
// adding delay before retry to avoid spam when flow restarted
|
||||
delay(RETRY_QUOTES_DELAY)
|
||||
true
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
|
||||
private fun getNetworksStatuses(
|
||||
userWalletId: UserWalletId,
|
||||
networks: NonEmptySet<Network>,
|
||||
): EitherFlow<TokenListError, Set<NetworkStatus>> {
|
||||
return networksRepository.getNetworkStatusesUpdatesLegacy(userWalletId, networks)
|
||||
.map<Set<NetworkStatus>, Either<TokenListError, Set<NetworkStatus>>> { it.right() }
|
||||
.catch { emit(TokenListError.DataError(it).left()) }
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
|
||||
private fun getYieldBalances(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrencies: List<CryptoCurrency>,
|
||||
): EitherFlow<TokenListError, YieldBalanceList> {
|
||||
return stakingRepository.getMultiYieldBalanceUpdatesLegacy(userWalletId, cryptoCurrencies)
|
||||
.map<YieldBalanceList, Either<TokenListError, YieldBalanceList>> { it.right() }
|
||||
.catch { emit(TokenListError.DataError(it).left()) }
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
|
||||
private fun getIds(currencies: List<CryptoCurrency>): Pair<NonEmptySet<Network>, NonEmptySet<CryptoCurrency.ID>> {
|
||||
val currencyIdToNetworkId = currencies.associate { currency ->
|
||||
currency.id to currency.network
|
||||
}
|
||||
val currenciesIds = currencyIdToNetworkId.keys.toNonEmptySetOrNull()
|
||||
val networks = currencyIdToNetworkId.values.toNonEmptySetOrNull()
|
||||
|
||||
requireNotNull(currenciesIds) { "Currencies IDs cannot be empty" }
|
||||
requireNotNull(networks) { "Networks IDs cannot be empty" }
|
||||
|
||||
return networks to currenciesIds
|
||||
}
|
||||
|
||||
private fun extractAddress(networkStatus: NetworkStatus?): String? {
|
||||
return when (val value = networkStatus?.value) {
|
||||
is NetworkStatus.NoAccount -> value.address.defaultAddress.value
|
||||
is NetworkStatus.Unreachable -> value.address?.defaultAddress?.value
|
||||
is NetworkStatus.Verified -> value.address.defaultAddress.value
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val RETRY_QUOTES_DELAY = 2000L
|
||||
}
|
||||
}
|
||||
|
|
@ -10,7 +10,7 @@ import com.tangem.domain.tokens.mock.MockQuotes
|
|||
import com.tangem.domain.tokens.mock.MockTokens
|
||||
import com.tangem.domain.tokens.mock.MockTokensStates
|
||||
import com.tangem.domain.tokens.model.*
|
||||
import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations
|
||||
import com.tangem.domain.tokens.operations.CachedCurrenciesStatusesOperations
|
||||
import com.tangem.domain.tokens.repository.MockCurrenciesRepository
|
||||
import com.tangem.domain.tokens.repository.MockNetworksRepository
|
||||
import com.tangem.domain.tokens.repository.MockQuotesRepository
|
||||
|
|
@ -164,7 +164,7 @@ internal class GetPrimaryCurrencyStatusUpdatesUseCaseTest {
|
|||
quotes: Flow<Either<DataError, Set<Quote>>> = flowOf(MockQuotes.quotes.right()),
|
||||
statuses: Flow<Either<DataError, Set<NetworkStatus>>> = flowOf(MockNetworks.verifiedNetworksStatuses.right()),
|
||||
) = GetPrimaryCurrencyStatusUpdatesUseCase(
|
||||
currencyStatusOperations = CurrenciesStatusesOperations(
|
||||
currencyStatusOperations = CachedCurrenciesStatusesOperations(
|
||||
currenciesRepository = MockCurrenciesRepository(
|
||||
sortTokensResult = Unit.right(),
|
||||
removeCurrencyResult = removeCurrencyResult,
|
||||
|
|
@ -176,6 +176,9 @@ internal class GetPrimaryCurrencyStatusUpdatesUseCaseTest {
|
|||
quotesRepository = MockQuotesRepository(quotes),
|
||||
networksRepository = MockNetworksRepository(statuses),
|
||||
stakingRepository = MockStakingRepository(),
|
||||
tokensFeatureToggles = object : TokensFeatureToggles {
|
||||
override val isNetworksLoadingRefactoringEnabled: Boolean = false
|
||||
},
|
||||
),
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue