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 88b241c3ff..e168276a5b 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.core.StringKeyDataStoreDecorator import com.tangem.domain.tokens.model.NetworkStatus import com.tangem.domain.wallets.models.UserWalletId import com.tangem.utils.extensions.addOrReplace +import com.tangem.utils.extensions.replaceBy import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock @@ -27,4 +28,23 @@ internal class DefaultNetworksStatusesStore( store(key, newValues) } } + + override suspend fun storeAll(key: UserWalletId, values: Collection) { + mutex.withLock { + val currentValues = getSyncOrNull(key) ?: emptySet() + val updatedValues = currentValues.toMutableSet() + + values.forEach { newValue -> + val isReplaced = updatedValues.replaceBy(newValue) { + it.network == newValue.network + } + + if (!isReplaced) { + updatedValues.add(newValue) + } + } + + store(key, updatedValues) + } + } } \ No newline at end of file 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 a30a1b49ab..3ab684275f 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 @@ -11,4 +11,6 @@ interface NetworksStatusesStore { suspend fun getSyncOrNull(key: UserWalletId): Set? suspend fun store(key: UserWalletId, value: NetworkStatus) + + suspend fun storeAll(key: UserWalletId, values: Collection) } \ 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 71bd21a01e..8633e9d88c 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 @@ -2,7 +2,6 @@ package com.tangem.data.staking import android.util.Base64 import arrow.core.getOrElse -import arrow.core.raise.catch import com.squareup.moshi.Moshi import com.tangem.blockchain.common.Amount import com.tangem.blockchain.common.Blockchain @@ -30,8 +29,6 @@ import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrap import com.tangem.datasource.api.stakekit.models.response.model.transaction.tron.TronStakeKitTransaction import com.tangem.datasource.local.token.StakingBalanceStore import com.tangem.datasource.local.token.StakingYieldsStore -import com.tangem.domain.core.lce.LceFlow -import com.tangem.domain.core.lce.lceFlow import com.tangem.domain.staking.model.StakingApproval import com.tangem.domain.staking.model.StakingAvailability import com.tangem.domain.staking.model.StakingEntryInfo @@ -57,6 +54,7 @@ import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.extensions.orZero import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch +import kotlinx.coroutines.plus import kotlinx.coroutines.withContext import timber.log.Timber @@ -385,15 +383,22 @@ internal class DefaultStakingRepository( refresh: Boolean, ) = withContext(dispatchers.io) { if (!stakingFeatureToggle.isStakingEnabled) return@withContext + try { - isYieldBalanceFetching.update { - it + (userWalletId to true) - } cacheRegistry.invokeOnExpire( key = getYieldBalancesKey(userWalletId), skipCache = refresh, block = { - val yields = getEnabledYields() + isYieldBalanceFetching.update { + it + (userWalletId to true) + } + + val yields = getEnabledYields().ifEmpty { + Timber.i("No enabled yields for $userWalletId") + stakingBalanceStore.store(userWalletId, emptySet()) + + return@invokeOnExpire + } val availableCurrencies = cryptoCurrencies .mapNotNull { currency -> val addresses = walletManagersFacade.getAddresses(userWalletId, currency.network) @@ -410,10 +415,15 @@ internal class DefaultStakingRepository( } .map { getBalanceRequestData(it.first.value, it.second) } .ifEmpty { - cacheRegistry.invalidate(getYieldBalancesKey(userWalletId)) - error("No addresses found") + Timber.i("No yield balances available for $userWalletId") + stakingBalanceStore.store(userWalletId, emptySet()) + + return@invokeOnExpire } - val result = stakeKitApi.getMultipleYieldBalances(availableCurrencies).getOrThrow() + + val result = stakeKitApi + .getMultipleYieldBalances(availableCurrencies) + .getOrThrow() stakingBalanceStore.store(userWalletId, result) }, @@ -446,27 +456,22 @@ internal class DefaultStakingRepository( } }.cancellable() - override fun getMultiYieldBalanceLce( + override fun getMultiYieldBalance( userWalletId: UserWalletId, cryptoCurrencies: List, - ): LceFlow = lceFlow { + ): Flow = channelFlow { if (!stakingFeatureToggle.isStakingEnabled) { send(YieldBalanceList.Empty) } else { - launch(dispatchers.io) { - combine( - stakingBalanceStore.get(userWalletId), - isYieldBalanceFetching.map { it.getOrElse(userWalletId) { false } }, - ) { result, isFetching -> - val balances = yieldBalanceListConverter.convert(result) - send(balances, isStillLoading = isFetching) - }.collect() - } + stakingBalanceStore.get(userWalletId) + .onEach { + val balances = yieldBalanceListConverter.convert(it) + send(balances) + } + .launchIn(scope = this + dispatchers.io) + withContext(dispatchers.io) { - catch( - block = { fetchMultiYieldBalance(userWalletId, cryptoCurrencies, refresh = false) }, - catch = { raise(it) }, - ) + fetchMultiYieldBalance(userWalletId, cryptoCurrencies, refresh = false) } } } diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt index a3b8911411..4664e120ce 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt @@ -42,6 +42,7 @@ import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch +import kotlinx.coroutines.plus import kotlinx.coroutines.withContext import timber.log.Timber import com.tangem.blockchain.common.FeePaidCurrency as FeePaidSdkCurrency @@ -206,18 +207,14 @@ internal class DefaultCurrenciesRepository( } } - override fun getWalletCurrenciesUpdates(userWalletId: UserWalletId): LceFlow> { - return lceFlow { - val userWallet = catch({ getUserWallet(userWalletId) }) { - raise(it) - } + override fun getWalletCurrenciesUpdates(userWalletId: UserWalletId): Flow> { + return channelFlow { + val userWallet = getUserWallet(userWalletId) if (userWallet.isMultiCurrency) { - getMultiCurrencyWalletCurrenciesUpdatesLce(userWalletId).collect(::send) + getMultiCurrencyWalletCurrenciesUpdates(userWalletId).collect(::send) } else { - val currency = catch({ getSingleCurrencyWalletPrimaryCurrency(userWalletId) }) { - raise(it) - } + val currency = getSingleCurrencyWalletPrimaryCurrency(userWalletId) send(listOf(currency)) } } @@ -260,16 +257,14 @@ internal class DefaultCurrenciesRepository( val userWallet = getUserWallet(userWalletId) ensureIsCorrectUserWallet(userWallet, isMultiCurrencyWalletExpected = true) - launch(dispatchers.io) { - getMultiCurrencyWalletCurrencies(userWallet) - .collectLatest(::send) - } + getMultiCurrencyWalletCurrencies(userWallet) + .onEach { send(it) } + .launchIn(scope = this + dispatchers.io) withContext(dispatchers.io) { fetchTokensIfCacheExpired(userWallet, refresh = false) } } - .cancellable() } override fun getMultiCurrencyWalletCurrenciesUpdatesLce( 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 3762b4ddee..d61f12d1a3 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 @@ -54,29 +54,25 @@ internal class DefaultNetworksRepository( userWalletId: UserWalletId, networks: Set, ): Flow> = channelFlow { - launch(dispatchers.io) { - networksStatusesStore.get(userWalletId) - .collectLatest(::send) - } + networksStatusesStore.get(userWalletId) + .onEach(::send) + .launchIn(scope = this + dispatchers.io) withContext(dispatchers.io) { - fetchNetworksStatusesIfCacheExpired(userWalletId, networks, false) + fetchNetworksStatusesIfCacheExpired(userWalletId, networks, refresh = false) } } - .cancellable() override fun getNetworkStatusesUpdatesLce( userWalletId: UserWalletId, networks: Set, ): LceFlow> = lceFlow { - launch(dispatchers.io) { - combine( - networksStatusesStore.get(userWalletId), - isNetworkStatusesFetching.map { it.getOrElse(userWalletId) { false } }, - ) { statuses, isFetching -> - send(statuses, isStillLoading = isFetching) - }.collect() - } + combine( + networksStatusesStore.get(userWalletId), + isNetworkStatusesFetching.map { it.getOrElse(userWalletId) { false } }, + ) { statuses, isFetching -> + send(statuses, isStillLoading = isFetching || networks.size != statuses.size) + }.launchIn(scope = this + dispatchers.io) withContext(dispatchers.io) { catch({ fetchNetworksStatusesIfCacheExpired(userWalletId, networks, refresh = false) }) { @@ -185,25 +181,32 @@ internal class DefaultNetworksRepository( userWalletId: UserWalletId, networks: Set, refresh: Boolean, - ) { - val currencies = getCurrencies(userWalletId, networks) - val networksDeferred = networks.mapNotNull { network -> - fetchNetworkStatusIfCacheExpired(userWalletId, network, currencies, refresh) + ) = coroutineScope { + if (refresh) { + val statusesToRefresh = networks.map { NetworkStatus(it, NetworkStatus.Loading) } + networksStatusesStore.storeAll(userWalletId, statusesToRefresh) } - if (networksDeferred.isNotEmpty()) { - try { - isNetworkStatusesFetching.update { - it + (userWalletId to true) - } + val currencies = getCurrencies(userWalletId, networks) + val networksDeferred = networks.mapNotNull { network -> + coroutineScope { + val key = getNetworksStatusesCacheKey(userWalletId, network) - networksDeferred.awaitAll() - } finally { - isNetworkStatusesFetching.update { - it - userWalletId + if (refresh || cacheRegistry.isExpired(key)) { + async { + cacheRegistry.invokeOnExpire( + key = key, + skipCache = refresh, + block = { fetchNetworkStatus(userWalletId, network, currencies) }, + ) + } + } else { + null } } } + + networksDeferred.awaitAll() } private suspend fun fetchNetworksPendingTransactions( @@ -222,31 +225,13 @@ internal class DefaultNetworksRepository( } } - private suspend fun fetchNetworkStatusIfCacheExpired( - userWalletId: UserWalletId, - network: Network, - currencies: Sequence, - refresh: Boolean, - ): Deferred? = coroutineScope { - val key = getNetworksStatusesCacheKey(userWalletId, network) - if (refresh || cacheRegistry.isExpired(key)) { - async { - cacheRegistry.invokeOnExpire( - key = key, - skipCache = refresh, - block = { fetchNetworkStatus(userWalletId, network, currencies) }, - ) - } - } else { - null - } - } - private suspend fun fetchNetworkStatus( userWalletId: UserWalletId, network: Network, currencies: Sequence, ) { + networksStatusesStore.store(userWalletId, NetworkStatus(network, NetworkStatus.Loading)) + val result = walletManagersFacade.update( userWalletId = userWalletId, network = network, diff --git a/domain/core/src/main/kotlin/com/tangem/domain/core/lce/LceFlow.kt b/domain/core/src/main/kotlin/com/tangem/domain/core/lce/LceFlow.kt index 6204c49430..037775753e 100644 --- a/domain/core/src/main/kotlin/com/tangem/domain/core/lce/LceFlow.kt +++ b/domain/core/src/main/kotlin/com/tangem/domain/core/lce/LceFlow.kt @@ -1,5 +1,6 @@ package com.tangem.domain.core.lce +import arrow.atomic.AtomicBoolean import arrow.core.raise.Raise import com.tangem.domain.core.utils.lceContent import com.tangem.domain.core.utils.lceError @@ -34,6 +35,8 @@ class LceFlowScope @PublishedApi internal constructor( private val ifLoading: suspend LceFlowScope.(C?) -> Unit, ) : Raise, CoroutineScope by producerScope { + val isLoading: AtomicBoolean = AtomicBoolean(value = true) + /** * Sends a error of type [E] within the [ProducerScope] and then closes it for send. * All subsequent sends will be ignored. @@ -46,6 +49,8 @@ class LceFlowScope @PublishedApi internal constructor( * @param r Error to raise. */ override fun raise(r: E): Nothing { + isLoading.set(false) + producerScope.trySendBlocking(r.lceError()) producerScope.close() @@ -66,6 +71,8 @@ class LceFlowScope @PublishedApi internal constructor( * @param isStillLoading A flag indicating whether the content is still loading. */ suspend fun send(content: C, isStillLoading: Boolean = false) { + isLoading.set(isStillLoading) + val value = if (isStillLoading) { ifLoading(content) return @@ -89,6 +96,8 @@ class LceFlowScope @PublishedApi internal constructor( suspend fun send(value: Lce) { if (producerScope.isClosedForSend) return + isLoading.set(value.isLoading()) + producerScope.send(value) } } diff --git a/domain/core/src/main/kotlin/com/tangem/domain/core/lce/LceRaise.kt b/domain/core/src/main/kotlin/com/tangem/domain/core/lce/LceRaise.kt index 1484205426..dce3342eb3 100644 --- a/domain/core/src/main/kotlin/com/tangem/domain/core/lce/LceRaise.kt +++ b/domain/core/src/main/kotlin/com/tangem/domain/core/lce/LceRaise.kt @@ -1,6 +1,8 @@ package com.tangem.domain.core.lce import arrow.atomic.Atomic +import arrow.core.Either +import arrow.core.identity import arrow.core.raise.Raise import arrow.core.raise.RaiseDSL import arrow.core.raise.recover @@ -97,6 +99,12 @@ class LceRaise @PublishedApi internal constructor( is Lce.Content -> content is Lce.Error -> raise(r = this) } + + @RaiseDSL + fun Either.bindEither(): C = fold( + ifLeft = { raise(it) }, + ifRight = ::identity, + ) } /** diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingRepository.kt b/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingRepository.kt index 6f4ba8fd32..6e17391a27 100644 --- a/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingRepository.kt +++ b/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingRepository.kt @@ -3,7 +3,6 @@ package com.tangem.domain.staking.repositories import com.tangem.blockchain.common.Amount import com.tangem.blockchain.common.TransactionData import com.tangem.blockchain.common.transaction.Fee -import com.tangem.domain.core.lce.LceFlow import com.tangem.domain.staking.model.StakingApproval import com.tangem.domain.staking.model.StakingAvailability import com.tangem.domain.staking.model.StakingEntryInfo @@ -55,10 +54,10 @@ interface StakingRepository { cryptoCurrencies: List, ): Flow - fun getMultiYieldBalanceLce( + fun getMultiYieldBalance( userWalletId: UserWalletId, cryptoCurrencies: List, - ): LceFlow + ): Flow suspend fun getMultiYieldBalanceSync( userWalletId: UserWalletId, 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 08a746b12e..d33b011230 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 @@ -21,6 +21,11 @@ data class NetworkStatus( */ sealed class Value + /** + * Represents the state where the network is refreshing. + */ + data object Refreshing : Value() + /** * Represents the state where the network is unreachable. * diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/FetchTokenListUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/FetchTokenListUseCase.kt index 394cd7c1dc..f1ef16bca8 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/FetchTokenListUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/FetchTokenListUseCase.kt @@ -40,38 +40,39 @@ class FetchTokenListUseCase( * network statuses, and quotes for associated tokens. * * @param userWalletId The ID of the user's wallet. - * @param refresh Indicates whether to force a refresh of the token list data. + * @param mode The refresh mode to control the fetching process. * @return An [Either] representing success (Right) or an error (Left) in fetching the token list. */ - suspend operator fun invoke(userWalletId: UserWalletId, refresh: Boolean = false): Either { - return either { - val currencies = fetchCurrencies(userWalletId, refresh) + suspend operator fun invoke( + userWalletId: UserWalletId, + mode: RefreshMode = RefreshMode.NONE, + ): Either = either { + val currencies = fetchCurrencies(userWalletId, refresh = mode.refreshCurrencies) - coroutineScope { - val fetchStatuses = async { - fetchNetworksStatuses( - userWalletId, - currencies.mapTo(hashSetOf()) { it.network }, - refresh, - ) - } - val fetchQuotes = async { - fetchQuotes( - currencies.mapTo(hashSetOf()) { it.id }, - refresh, - ) - } - - val yieldBalances = async { - fetchYieldBalances( - userWalletId = userWalletId, - currencies = currencies, - refresh = refresh, - ) - } - - awaitAll(fetchStatuses, fetchQuotes, yieldBalances) + coroutineScope { + val fetchStatuses = async { + fetchNetworksStatuses( + userWalletId, + currencies.mapTo(hashSetOf()) { it.network }, + refresh = mode.refreshNetworksStatuses, + ) } + val fetchQuotes = async { + fetchQuotes( + currencies.mapTo(hashSetOf()) { it.id }, + refresh = mode.refreshQuotes, + ) + } + + val yieldBalances = async { + fetchYieldBalances( + userWalletId = userWalletId, + currencies = currencies, + refresh = mode.refreshYieldBalances, + ) + } + + awaitAll(fetchStatuses, fetchQuotes, yieldBalances) } } @@ -120,4 +121,33 @@ class FetchTokenListUseCase( catch = { /* Ignore error */ }, ) } + + /** + * Represents the refresh modes available for fetching token list information. + */ + enum class RefreshMode( + internal val refreshCurrencies: Boolean, + internal val refreshNetworksStatuses: Boolean, + internal val refreshQuotes: Boolean, + internal val refreshYieldBalances: Boolean, + ) { + NONE( + refreshCurrencies = false, + refreshNetworksStatuses = false, + refreshQuotes = false, + refreshYieldBalances = false, + ), + FULL( + refreshCurrencies = true, + refreshNetworksStatuses = true, + refreshQuotes = true, + refreshYieldBalances = true, + ), + SKIP_CURRENCIES( + refreshCurrencies = false, + refreshNetworksStatuses = true, + refreshQuotes = true, + refreshYieldBalances = true, + ), + } } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetWalletTotalBalanceUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetWalletTotalBalanceUseCase.kt index 012dd2847a..ebf6a72cd7 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetWalletTotalBalanceUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetWalletTotalBalanceUseCase.kt @@ -85,9 +85,6 @@ class GetWalletTotalBalanceUseCase( stakingRepository = stakingRepository, ) - return operations.getCurrenciesStatuses( - userWalletId = userWalletId, - isSingleCurrencyWalletsAllowed = true, - ) + return operations.getCurrenciesStatuses(userWalletId) } } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesLceOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesLceOperations.kt index 419842f017..730dbc541b 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesLceOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesLceOperations.kt @@ -1,12 +1,13 @@ 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.utils.lceError -import com.tangem.domain.core.utils.lceLoading +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 @@ -26,147 +27,126 @@ internal class CurrenciesStatusesLceOperations( private val stakingRepository: StakingRepository, ) { - fun getCurrenciesStatuses( - userWalletId: UserWalletId, - isSingleCurrencyWalletsAllowed: Boolean = false, - ): LceFlow> { + fun getCurrenciesStatuses(userWalletId: UserWalletId): LceFlow> { return transformToCurrenciesStatuses( userWalletId = userWalletId, - flow = if (isSingleCurrencyWalletsAllowed) { - getWalletCurrencies(userWalletId) - } else { - getMultiCurrencyWalletCurrencies(userWalletId) - }, + currenciesFlow = getWalletCurrencies(userWalletId), ) } @OptIn(ExperimentalCoroutinesApi::class) private fun transformToCurrenciesStatuses( userWalletId: UserWalletId, - flow: LceFlow>, - ): LceFlow> { - return flow.transformLatest transform@{ maybeCurrencies -> - val nonEmptyCurrencies = maybeCurrencies.fold( - ifLoading = { maybeContent -> - emit(createLoadingCurrenciesStatuses(maybeContent)) - return@transform - }, - ifContent = { content -> - val nonEmptyCurrencies = content.toNonEmptyListOrNull() + currenciesFlow: EitherFlow>, + ): LceFlow> = lceFlow { + currenciesFlow.collectLatest { maybeCurrencies -> + val nonEmptyCurrencies = maybeCurrencies.bind().toNonEmptyListOrNull() + ensureNotNull(nonEmptyCurrencies) { TokenListError.EmptyTokens } - if (nonEmptyCurrencies == null) { - emit(TokenListError.EmptyTokens.lceError()) - return@transform - } else { - nonEmptyCurrencies - } - }, - ifError = { error -> - emit(error.lceError()) - return@transform - }, - ) + // 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), - - ) { maybeQuotes, maybeNetworksStatuses, maybeYieldBalances -> - val statuses = createCurrenciesStatuses( - currencies = nonEmptyCurrencies, - maybeQuotes = maybeQuotes, - maybeNetworkStatuses = maybeNetworksStatuses, - maybeYieldBalances = maybeYieldBalances, - ) - emit(statuses) - }.collect() - } - } - - private fun createLoadingCurrenciesStatuses( - maybeCurrencies: List?, - ): Lce> { - val nonEmptyCurrencies = maybeCurrencies?.toNonEmptyListOrNull() - - val statuses = if (nonEmptyCurrencies == null) { - lceLoading() - } else { - createCurrenciesStatuses( - currencies = nonEmptyCurrencies, - maybeNetworkStatuses = null, - maybeQuotes = null, - maybeYieldBalances = null, + ::createCurrenciesStatuses, ) + .distinctUntilChanged() + .mapLatest { maybeCurrenciesStatuses -> + send(maybeCurrenciesStatuses) + } + .launchIn(scope = this) } - - return statuses } - private fun getWalletCurrencies(userWalletId: UserWalletId): LceFlow> { + private fun getWalletCurrencies(userWalletId: UserWalletId): EitherFlow> { return currenciesRepository.getWalletCurrenciesUpdates(userWalletId) - .map { maybeCurrencies -> - maybeCurrencies.mapError { TokenListError.DataError(it) } - } - } - - private fun getMultiCurrencyWalletCurrencies( - userWalletId: UserWalletId, - ): LceFlow> { - return currenciesRepository.getMultiCurrencyWalletCurrenciesUpdatesLce(userWalletId) + .map, Either>> { it.right() } + .catch { emit(TokenListError.DataError(it).left()) } .distinctUntilChanged() - .map { maybeCurrencies -> - maybeCurrencies.mapError { TokenListError.DataError(it) } - } } private fun createCurrenciesStatuses( currencies: NonEmptyList, maybeQuotes: Either>?, - maybeNetworkStatuses: Lce>?, - maybeYieldBalances: Lce?, + maybeNetworkStatuses: Either>?, + maybeYieldBalances: Either?, ): Lce> = lce { - isLoading.set(maybeNetworkStatuses == null) + isLoading.set(maybeNetworkStatuses == null || maybeYieldBalances == null) var quotesRetrievingFailed = false - val networksStatuses = maybeNetworkStatuses?.bindOrNull()?.toNonEmptySetOrNull() + val networksStatuses = maybeNetworkStatuses?.bindEither()?.toNonEmptySetOrNull() + val yieldBalances = maybeYieldBalances?.bindEither() val quotes = recover({ maybeQuotes?.bind()?.toNonEmptySetOrNull() }) { - quotesRetrievingFailed = true - null - }?.ifEmpty { - quotesRetrievingFailed = true null } - val yieldBalances = maybeYieldBalances?.getOrNull() + 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 address = extractAddress(networkStatus) - val supportedIntegration = stakingRepository.getSupportedIntegrationId(currency.id) - val yieldBalance = if (supportedIntegration.isNullOrBlank().not()) { - (yieldBalances as? YieldBalanceList.Data)?.getBalance( - address = address, - integrationId = supportedIntegration, - ) - } else { - null - } + val yieldBalance = findYieldBalanceOrNull(yieldBalances, currency, networkStatus) - createCurrencyStatus( + 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?, @@ -189,28 +169,27 @@ internal class CurrenciesStatusesLceOperations( return quotesRepository.getQuotesUpdates(tokensIds) .map, Either>> { it.right() } .catch { emit(TokenListError.DataError(it).left()) } + .distinctUntilChanged() } private fun getNetworksStatuses( userWalletId: UserWalletId, networks: NonEmptySet, - ): LceFlow> { - return networksRepository.getNetworkStatusesUpdatesLce(userWalletId, networks) - .map { maybeStatuses -> - maybeStatuses.mapError { TokenListError.DataError(it) } - } + ): EitherFlow> { + return networksRepository.getNetworkStatusesUpdates(userWalletId, networks) + .map, Either>> { it.right() } + .catch { emit(TokenListError.DataError(it).left()) } + .distinctUntilChanged() } private fun getYieldBalances( userWalletId: UserWalletId, cryptoCurrencies: List, - ): LceFlow { - return stakingRepository.getMultiYieldBalanceLce( - userWalletId = userWalletId, - cryptoCurrencies = cryptoCurrencies, - ).map { maybeBalances -> - maybeBalances.mapError { TokenListError.DataError(it) } - } + ): EitherFlow { + return stakingRepository.getMultiYieldBalance(userWalletId, cryptoCurrencies) + .map> { it.right() } + .catch { emit(TokenListError.DataError(it).left()) } + .distinctUntilChanged() } private fun getIds(currencies: List): Pair, NonEmptySet> { 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 6cbaf22b37..ff31ccc64a 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 @@ -16,7 +16,9 @@ internal class CurrencyStatusOperations( private fun createStatus(): CryptoCurrencyStatus.Value { return when (val status = networkStatus?.value) { - null -> CryptoCurrencyStatus.Loading + null, + is NetworkStatus.Refreshing, + -> CryptoCurrencyStatus.Loading is NetworkStatus.MissedDerivation -> createMissedDerivationStatus() is NetworkStatus.Unreachable -> createUnreachableStatus(status) is NetworkStatus.NoAccount -> createNoAccountStatus(status) diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrenciesRepository.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrenciesRepository.kt index d8c8c7cf37..8a19b705e9 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrenciesRepository.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrenciesRepository.kt @@ -81,7 +81,7 @@ interface CurrenciesRepository { * @param userWalletId The unique identifier of the user wallet. * @return A list of [CryptoCurrency]. */ - fun getWalletCurrenciesUpdates(userWalletId: UserWalletId): LceFlow> + fun getWalletCurrenciesUpdates(userWalletId: UserWalletId): Flow> /** * Retrieves the primary cryptocurrency for a specific single-currency user wallet. diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockStakingRepository.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockStakingRepository.kt index 32838c0406..9b702bacbb 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockStakingRepository.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockStakingRepository.kt @@ -4,8 +4,6 @@ import com.tangem.blockchain.common.Amount import com.tangem.blockchain.common.TransactionData import com.tangem.blockchain.common.TransactionStatus import com.tangem.blockchain.common.transaction.Fee -import com.tangem.domain.core.lce.LceFlow -import com.tangem.domain.core.lce.lceFlow import com.tangem.domain.staking.model.StakingApproval import com.tangem.domain.staking.model.StakingAvailability import com.tangem.domain.staking.model.StakingEntryInfo @@ -20,6 +18,7 @@ import com.tangem.domain.tokens.model.Network import com.tangem.domain.wallets.models.UserWalletId import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.channelFlow +import kotlinx.coroutines.flow.flowOf import org.joda.time.DateTime import java.math.BigDecimal @@ -157,16 +156,10 @@ class MockStakingRepository : StakingRepository { ) } - override fun getMultiYieldBalanceLce( + override fun getMultiYieldBalance( userWalletId: UserWalletId, cryptoCurrencies: List, - ): LceFlow = lceFlow { - send( - YieldBalanceList.Data( - balances = listOf(YieldBalance.Error), - ), - ) - } + ): Flow = flowOf() override suspend fun getMultiYieldBalanceSync( userWalletId: UserWalletId, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt index 5ea90e12d4..90b969933f 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt @@ -7,7 +7,6 @@ import com.tangem.domain.demo.IsDemoCardUseCase import com.tangem.domain.promo.PromoBanner import com.tangem.domain.settings.IsReadyToShowRateAppUseCase import com.tangem.domain.settings.ShouldShowRingPromoUseCase -import com.tangem.domain.tokens.GetTokenListUseCase import com.tangem.domain.tokens.error.TokenListError import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus @@ -30,7 +29,7 @@ import kotlin.collections.count @Suppress("LongParameterList") @ViewModelScoped internal class GetMultiWalletWarningsFactory @Inject constructor( - private val getTokenListUseCase: GetTokenListUseCase, + private val tokenListStore: MultiWalletTokenListStore, private val isDemoCardUseCase: IsDemoCardUseCase, private val isReadyToShowRateAppUseCase: IsReadyToShowRateAppUseCase, private val shouldShowRingPromoUseCase: ShouldShowRingPromoUseCase, @@ -44,7 +43,7 @@ internal class GetMultiWalletWarningsFactory @Inject constructor( val promoFlow = flow { emit(promoRepository.getRingPromoBanner()) } return combine( - flow = getTokenListUseCase.launch(userWallet.walletId), + flow = tokenListStore.getOrThrow(userWallet.walletId), flow2 = isReadyToShowRateAppUseCase(), flow3 = isNeedToBackupUseCase(userWallet.walletId), flow4 = shouldShowRingPromoUseCase(userWalletId = userWallet.walletId), @@ -129,6 +128,7 @@ internal class GetMultiWalletWarningsFactory @Inject constructor( clickIntents: WalletClickIntents, ) { val currencies = maybeTokenList.getMissingAddressCurrencies() + .ifEmpty { return } addIf( element = WalletNotification.Informational.MissingAddresses( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/MultiWalletTokenListStore.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/MultiWalletTokenListStore.kt new file mode 100644 index 0000000000..d25f60ca3e --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/MultiWalletTokenListStore.kt @@ -0,0 +1,62 @@ +package com.tangem.feature.wallet.presentation.wallet.domain + +import com.tangem.domain.core.lce.LceFlow +import com.tangem.domain.tokens.GetTokenListUseCase +import com.tangem.domain.tokens.error.TokenListError +import com.tangem.domain.tokens.model.TokenList +import com.tangem.domain.wallets.models.UserWalletId +import dagger.hilt.android.scopes.ViewModelScoped +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ensureActive +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.shareIn +import timber.log.Timber +import java.util.concurrent.ConcurrentHashMap +import javax.inject.Inject + +@ViewModelScoped +internal class MultiWalletTokenListStore @Inject constructor( + private val getTokenListUseCase: GetTokenListUseCase, +) { + + private val flows: ConcurrentHashMap> by lazy { + ConcurrentHashMap() + } + + fun addIfNot(userWalletId: UserWalletId, coroutineScope: CoroutineScope) { + if (flows[userWalletId] != null) { + Timber.d("Flow with token list for $userWalletId already exists") + return + } + + coroutineScope.ensureActive() + + flows[userWalletId] = getTokenListUseCase + .launch(userWalletId) + .shareIn( + scope = coroutineScope, + started = SharingStarted.WhileSubscribed(), + replay = 1, + ) + + Timber.d("Flow with token list for $userWalletId created") + } + + fun getOrThrow(userWalletId: UserWalletId): LceFlow { + return requireNotNull(flows[userWalletId]) { + "Flow with token list for $userWalletId doesn't exist" + } + } + + fun remove(userWalletId: UserWalletId) { + flows.remove(userWalletId) + + Timber.d("Flow with token list for $userWalletId removed") + } + + fun clear() { + flows.clear() + + Timber.d("All flows with token list cleared") + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoader.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoader.kt index 7dc59658c4..ee5cdac9f1 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoader.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoader.kt @@ -2,12 +2,12 @@ package com.tangem.feature.wallet.presentation.wallet.loaders.implementors import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.tokens.ApplyTokenListSortingUseCase -import com.tangem.domain.tokens.GetTokenListUseCase import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.domain.GetMultiWalletWarningsFactory +import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenListStore import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController import com.tangem.feature.wallet.presentation.wallet.subscribers.MultiWalletTokenListSubscriber @@ -23,7 +23,7 @@ internal class MultiWalletContentLoader( private val tokenListAnalyticsSender: TokenListAnalyticsSender, private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender, private val walletWithFundsChecker: WalletWithFundsChecker, - private val getTokenListUseCase: GetTokenListUseCase, + private val tokenListStore: MultiWalletTokenListStore, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val applyTokenListSortingUseCase: ApplyTokenListSortingUseCase, private val getMultiWalletWarningsFactory: GetMultiWalletWarningsFactory, @@ -38,7 +38,7 @@ internal class MultiWalletContentLoader( clickIntents = clickIntents, tokenListAnalyticsSender = tokenListAnalyticsSender, walletWithFundsChecker = walletWithFundsChecker, - getTokenListUseCase = getTokenListUseCase, + tokenListStore = tokenListStore, getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase, applyTokenListSortingUseCase = applyTokenListSortingUseCase, runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt index 1b405c8af6..7a70b865b2 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt @@ -2,12 +2,12 @@ package com.tangem.feature.wallet.presentation.wallet.loaders.implementors import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.tokens.ApplyTokenListSortingUseCase -import com.tangem.domain.tokens.GetTokenListUseCase import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.domain.GetMultiWalletWarningsFactory +import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenListStore import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents @@ -21,7 +21,7 @@ internal class MultiWalletContentLoaderFactory @Inject constructor( private val tokenListAnalyticsSender: TokenListAnalyticsSender, private val walletWithFundsChecker: WalletWithFundsChecker, private val getMultiWalletWarningsFactory: GetMultiWalletWarningsFactory, - private val getTokenListUseCase: GetTokenListUseCase, + private val tokenListStore: MultiWalletTokenListStore, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val applyTokenListSortingUseCase: ApplyTokenListSortingUseCase, private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender, @@ -35,7 +35,7 @@ internal class MultiWalletContentLoaderFactory @Inject constructor( stateHolder = stateHolder, tokenListAnalyticsSender = tokenListAnalyticsSender, walletWithFundsChecker = walletWithFundsChecker, - getTokenListUseCase = getTokenListUseCase, + tokenListStore = tokenListStore, getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase, getMultiWalletWarningsFactory = getMultiWalletWarningsFactory, walletWarningsAnalyticsSender = walletWarningsAnalyticsSender, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt index f074661604..9c1bbbd713 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt @@ -41,11 +41,11 @@ internal abstract class BasicTokenListSubscriber( private val sendAnalyticsJobHolder = JobHolder() private val onTokenListReceivedJobHolder = JobHolder() - protected abstract fun tokenListFlow(): LceFlow + protected abstract fun tokenListFlow(coroutineScope: CoroutineScope): LceFlow override fun create(coroutineScope: CoroutineScope): Flow<*> { return combine( - flow = tokenListFlow() + flow = tokenListFlow(coroutineScope) .onEach { maybeTokenList -> coroutineScope.launch { sendTokenListAnalytics(maybeTokenList) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt index 12d5115bcf..79cef234af 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt @@ -4,7 +4,6 @@ import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.core.lce.Lce import com.tangem.domain.core.lce.LceFlow import com.tangem.domain.tokens.ApplyTokenListSortingUseCase -import com.tangem.domain.tokens.GetTokenListUseCase import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase import com.tangem.domain.tokens.error.TokenListError import com.tangem.domain.tokens.model.CryptoCurrency @@ -12,14 +11,16 @@ import com.tangem.domain.tokens.model.TokenList import com.tangem.domain.tokens.model.TotalFiatBalance import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender +import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenListStore import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents +import kotlinx.coroutines.CoroutineScope @Suppress("LongParameterList") internal class MultiWalletTokenListSubscriber( private val userWallet: UserWallet, - private val getTokenListUseCase: GetTokenListUseCase, + private val tokenListStore: MultiWalletTokenListStore, private val applyTokenListSortingUseCase: ApplyTokenListSortingUseCase, stateHolder: WalletStateController, clickIntents: WalletClickIntents, @@ -37,8 +38,10 @@ internal class MultiWalletTokenListSubscriber( runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase, ) { - override fun tokenListFlow(): LceFlow { - return getTokenListUseCase.launch(userWallet.walletId) + override fun tokenListFlow(coroutineScope: CoroutineScope): LceFlow { + tokenListStore.addIfNot(userWallet.walletId, coroutineScope) + + return tokenListStore.getOrThrow(userWallet.walletId) } override suspend fun onTokenListReceived(maybeTokenList: Lce) { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenListSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenListSubscriber.kt index c2c561afd8..c8848d06e9 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenListSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenListSubscriber.kt @@ -12,6 +12,7 @@ import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAn import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.map @Suppress("LongParameterList") @@ -34,6 +35,9 @@ internal class SingleWalletWithTokenListSubscriber( runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase, ) { - override fun tokenListFlow(): LceFlow = getNodlTokenListUseCase(userWallet.walletId) - .map { it.toLce() } + override fun tokenListFlow(coroutineScope: CoroutineScope): LceFlow = + getNodlTokenListUseCase( + userWallet.walletId, + ) + .map { it.toLce() } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt index 87496adf65..14bb262aa5 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt @@ -16,6 +16,7 @@ import com.tangem.feature.wallet.presentation.deeplink.WalletDeepLinksHandler import com.tangem.feature.wallet.presentation.router.InnerWalletRouter import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent import com.tangem.feature.wallet.presentation.wallet.analytics.utils.SelectedWalletAnalyticsSender +import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenListStore import com.tangem.feature.wallet.presentation.wallet.domain.WalletImageResolver import com.tangem.feature.wallet.presentation.wallet.domain.WalletNameMigrationUseCase import com.tangem.feature.wallet.presentation.wallet.loaders.WalletScreenContentLoader @@ -69,6 +70,7 @@ internal class WalletViewModel @Inject constructor( private val shouldAskPermissionUseCase: ShouldAskPermissionUseCase, private val marketsFeatureToggles: MarketsFeatureToggles, private val walletImageResolver: WalletImageResolver, + private val tokenListStore: MultiWalletTokenListStore, analyticsEventsHandler: AnalyticsEventHandler, ) : ViewModel() { @@ -110,6 +112,8 @@ internal class WalletViewModel @Inject constructor( override fun onCleared() { super.onCleared() + + tokenListStore.clear() stateHolder.clear() walletScreenContentLoader.cancelAll() } @@ -280,7 +284,7 @@ internal class WalletViewModel @Inject constructor( stateHolder.update(transformer = RenameWalletTransformer(action.selectedWalletId, action.name)) } is WalletsUpdateActionResolver.Action.Unknown -> { - Timber.w("Unable to perfom action: $action") + Timber.w("Unable to perform action: $action") } } } @@ -318,6 +322,7 @@ internal class WalletViewModel @Inject constructor( private fun reinitializeWallet(action: WalletsUpdateActionResolver.Action.ReinitializeWallet) { walletScreenContentLoader.cancel(action.prevWalletId) + tokenListStore.remove(action.prevWalletId) walletScreenContentLoader.load( userWallet = action.selectedWallet, @@ -357,6 +362,7 @@ internal class WalletViewModel @Inject constructor( private suspend fun deleteWallet(action: WalletsUpdateActionResolver.Action.DeleteWallet) { walletScreenContentLoader.cancel(action.deletedWalletId) + tokenListStore.remove(action.deletedWalletId) walletScreenContentLoader.load( userWallet = action.selectedWallet, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletsUpdateActionResolver.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletsUpdateActionResolver.kt index 835a480b48..324b2fc7ca 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletsUpdateActionResolver.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletsUpdateActionResolver.kt @@ -136,7 +136,9 @@ internal class WalletsUpdateActionResolver @Inject constructor( unlockedWallets = wallets.filterNot(UserWallet::isLocked), ) } - isSelectedWalletCardsCountChanged(state, selectedWallet) -> Action.UpdateWalletCardCount(selectedWallet) + isSelectedWalletCardsCountChanged(state, selectedWallet) -> { + Action.UpdateWalletCardCount(selectedWallet) + } else -> Action.Unknown } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCardClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCardClickIntents.kt index 7c128ca00d..066fe95381 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCardClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCardClickIntents.kt @@ -12,6 +12,7 @@ import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.usecase.* import com.tangem.feature.wallet.impl.R import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent.MainScreen +import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenListStore import com.tangem.feature.wallet.presentation.wallet.loaders.WalletScreenContentLoader import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController import com.tangem.feature.wallet.presentation.wallet.state.model.WalletAlertState @@ -37,6 +38,7 @@ internal interface WalletCardClickIntents { @Suppress("LongParameterList") internal class WalletCardClickIntentsImplementor @Inject constructor( private val stateHolder: WalletStateController, + private val tokenListStore: MultiWalletTokenListStore, private val walletEventSender: WalletEventSender, private val walletScreenContentLoader: WalletScreenContentLoader, private val renameWalletUseCase: RenameWalletUseCase, @@ -99,6 +101,7 @@ internal class WalletCardClickIntentsImplementor @Inject constructor( override fun onDeleteAfterConfirmationClick(userWalletId: UserWalletId) { viewModelScope.launch(dispatchers.main) { walletScreenContentLoader.cancel(userWalletId) + tokenListStore.remove(userWalletId) val walletToDelete = getUserWalletUseCase(userWalletId).getOrNull() ?: return@launch val hasUserWallets = deleteWalletUseCase(userWalletId).getOrElse { @@ -117,6 +120,7 @@ internal class WalletCardClickIntentsImplementor @Inject constructor( reduxStateHolder.onUserWalletSelected(selectedWallet) } else { + tokenListStore.clear() stateHolder.clear() appRouter.replaceAll(AppRoute.Home) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletClickIntents.kt index ec3f2d992d..a1ddf993ae 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletClickIntents.kt @@ -8,6 +8,7 @@ import com.tangem.domain.settings.NeverToShowWalletsScrollPreview import com.tangem.domain.tokens.FetchCardTokenListUseCase import com.tangem.domain.tokens.FetchCurrencyStatusUseCase import com.tangem.domain.tokens.FetchTokenListUseCase +import com.tangem.domain.tokens.FetchTokenListUseCase.RefreshMode import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase import com.tangem.domain.wallets.usecase.SelectWalletUseCase import com.tangem.feature.wallet.presentation.router.InnerWalletRouter @@ -117,7 +118,7 @@ internal class WalletClickIntents @Inject constructor( val maybeFetchResult = if (userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken()) { fetchCardTokenListUseCase(userWalletId = userWallet.walletId, refresh = true) } else { - fetchTokenListUseCase(userWalletId = userWallet.walletId, refresh = true) + fetchTokenListUseCase(userWalletId = userWallet.walletId, mode = RefreshMode.FULL) } maybeFetchResult.onLeft { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletWarningsClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletWarningsClickIntents.kt index e7386f4925..fb53cabe3d 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletWarningsClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletWarningsClickIntents.kt @@ -13,6 +13,7 @@ import com.tangem.domain.redux.LegacyAction import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.settings.* import com.tangem.domain.tokens.FetchTokenListUseCase +import com.tangem.domain.tokens.FetchTokenListUseCase.RefreshMode import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.analytics.TokenSwapPromoAnalyticsEvent import com.tangem.domain.wallets.legacy.UserWalletsListManager.Lockable.UnlockType @@ -124,18 +125,19 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor( analyticsEventHandler.send(Basic.CardWasScanned(AnalyticsParam.ScreensSources.Main)) analyticsEventHandler.send(MainScreen.NoticeScanYourCardTapped) - viewModelScope.launch(dispatchers.main) { + viewModelScope.launch { val userWallet = getSelectedUserWallet() ?: return@launch derivePublicKeysUseCase( userWalletId = userWallet.walletId, currencies = missedAddressCurrencies, - ) - .onRight { - // Refresh must be set to true to ensure that yield balances are updated - fetchTokenListUseCase(userWalletId = userWallet.walletId, refresh = true) - } - .onLeft { Timber.e("Failed to derive public keys: $it") } + ).onLeft { + Timber.e(it, "Failed to derive public keys") + return@launch + } + + // Refresh must be set to true to ensure that yield balances are updated + fetchTokenListUseCase(userWallet.walletId, mode = RefreshMode.SKIP_CURRENCIES) } }