diff --git a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt index 73ecbf344e..6f4c7f7bcc 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt @@ -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, + ) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/DefaultTokensFeatureToggles.kt b/app/src/main/java/com/tangem/tap/domain/tokens/DefaultTokensFeatureToggles.kt index 0aae1ffb84..2927924f8c 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/DefaultTokensFeatureToggles.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/DefaultTokensFeatureToggles.kt @@ -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") } \ No newline at end of file diff --git a/core/config-toggles/src/main/assets/configs/feature_toggles_config.json b/core/config-toggles/src/main/assets/configs/feature_toggles_config.json index df8f975239..e051641afc 100644 --- a/core/config-toggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/config-toggles/src/main/assets/configs/feature_toggles_config.json @@ -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" } ] diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/TokensFeatureToggles.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/TokensFeatureToggles.kt index faf7427071..b48aaa3c4d 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/TokensFeatureToggles.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/TokensFeatureToggles.kt @@ -7,5 +7,5 @@ package com.tangem.domain.tokens */ interface TokensFeatureToggles { - val isBalancesCachingEnabled: Boolean + val isNetworksLoadingRefactoringEnabled: Boolean } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CachedCurrenciesStatusesOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CachedCurrenciesStatusesOperations.kt index a14f37e601..cab4aff110 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CachedCurrenciesStatusesOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CachedCurrenciesStatusesOperations.kt @@ -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( diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesOperations.kt index eeeb5b4e59..2679e1a047 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesOperations.kt @@ -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>> { - return quotesRepository.getQuotesUpdatesLegacy(setOf(id)) - .map, Either>> { 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> { - return networksRepository.getNetworkStatusesUpdatesLegacy(userWalletId, setOf(network)) - .map, Either>> { it.right() } - .catch { emit(Error.DataError(it).left()) } - .onEmpty { emit(Error.EmptyNetworksStatuses.left()) } - } - - override suspend fun fetchComponents( - userWalletId: UserWalletId, - networks: Set, - currenciesIds: Set, - currencies: List, - ): Either = Unit.right() +class CurrenciesStatusesOperations { sealed class Error { diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/LceCurrenciesStatusesOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/LceCurrenciesStatusesOperations.kt deleted file mode 100644 index 255d2f86b5..0000000000 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/LceCurrenciesStatusesOperations.kt +++ /dev/null @@ -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> { - return transformToCurrenciesStatuses( - userWalletId = userWalletId, - currenciesFlow = getWalletCurrencies(userWalletId), - ) - } - - private fun transformToCurrenciesStatuses( - userWalletId: UserWalletId, - currenciesFlow: EitherFlow>, - ): LceFlow> = 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>?, - maybeNetworkStatuses: Either>?, - maybeYieldBalances: Either?, - ): Lce> = 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> { - return currenciesRepository.getWalletCurrenciesUpdates(userWalletId) - .map, Either>> { it.right() } - .catch { emit(TokenListError.DataError(it).left()) } - .distinctUntilChanged() - } - - private fun createCurrenciesStatuses( - currencies: NonEmptyList, - maybeQuotes: Either>?, - maybeNetworkStatuses: Either>?, - maybeYieldBalances: Either?, - ): Lce> = 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): Flow>> { - return quotesRepository.getQuotesUpdatesLegacy(tokensIds.mapNotNull { it.rawCurrencyId }.toSet()) - .map, Either>> { 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, - ): EitherFlow> { - return networksRepository.getNetworkStatusesUpdatesLegacy(userWalletId, networks) - .map, Either>> { it.right() } - .catch { emit(TokenListError.DataError(it).left()) } - .distinctUntilChanged() - } - - private fun getYieldBalances( - userWalletId: UserWalletId, - cryptoCurrencies: List, - ): EitherFlow { - return stakingRepository.getMultiYieldBalanceUpdatesLegacy(userWalletId, cryptoCurrencies) - .map> { it.right() } - .catch { emit(TokenListError.DataError(it).left()) } - .distinctUntilChanged() - } - - private fun getIds(currencies: List): Pair, NonEmptySet> { - 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 - } -} \ No newline at end of file diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetPrimaryCurrencyStatusUpdatesUseCaseTest.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetPrimaryCurrencyStatusUpdatesUseCaseTest.kt index c8879e30a3..d86abc8442 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetPrimaryCurrencyStatusUpdatesUseCaseTest.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetPrimaryCurrencyStatusUpdatesUseCaseTest.kt @@ -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>> = flowOf(MockQuotes.quotes.right()), statuses: Flow>> = 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, )