diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessagesManager.kt b/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessagesManager.kt index 01bb883f69..183ba76207 100644 --- a/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessagesManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessagesManager.kt @@ -1,7 +1,7 @@ package com.tangem.tap.domain.configurable.warningMessage import com.tangem.blockchain.common.Blockchain -import com.tangem.utils.extensions.removeByReplace +import com.tangem.utils.extensions.removeBy import com.tangem.wallet.R import java.util.concurrent.CopyOnWriteArrayList @@ -44,12 +44,12 @@ class WarningMessagesManager { } fun removeWarnings(origin: WarningMessage.Origin) { - warningsList.removeByReplace { it.origin == origin } + warningsList.removeBy { it.origin == origin } sortByPriority() } fun removeWarnings(messageRes: Int) { - warningsList.removeByReplace { it.messageResId == messageRes } + warningsList.removeBy { it.messageResId == messageRes } } fun containsWarning(warning: WarningMessage) = warning in warningsList diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsPublicInformationRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsPublicInformationRepository.kt index 4ab48a80a9..1f64334fd7 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsPublicInformationRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsPublicInformationRepository.kt @@ -12,7 +12,7 @@ import com.tangem.domain.wallets.models.UserWalletId import com.tangem.tap.domain.userWalletList.model.UserWalletPublicInformation import com.tangem.tap.domain.userWalletList.repository.UserWalletsPublicInformationRepository import com.tangem.tap.domain.userWalletList.utils.publicInformation -import com.tangem.utils.extensions.plusOrReplace +import com.tangem.utils.extensions.addOrReplace import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -29,7 +29,7 @@ internal class DefaultUserWalletsPublicInformationRepository( getAll() .flatMap { savedInformation -> val infoToSave = withContext(Dispatchers.Default) { - savedInformation.plusOrReplace(userWallet.publicInformation) { + savedInformation.addOrReplace(userWallet.publicInformation) { userWallet.walletId == it.walletId } } diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt index 6fa441f106..0203abef4b 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt @@ -31,7 +31,7 @@ import com.tangem.tap.features.wallet.models.getPendingTransactions import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.tap.store import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import com.tangem.utils.extensions.plusOrReplace +import com.tangem.utils.extensions.addOrReplace import kotlinx.coroutines.* import kotlinx.coroutines.flow.firstOrNull import timber.log.Timber @@ -429,7 +429,7 @@ internal class DefaultWalletAmountsRepository( withContext(Dispatchers.Default) { WalletManagerStorage.update { prevManagers -> val newManagersForUserWallet = prevManagers[userWalletId].orEmpty() - .plusOrReplace(walletManager) { + .addOrReplace(walletManager) { it.wallet.blockchain == walletManager.wallet.blockchain } diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/walletmanager/DefaultWalletManagersStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/walletmanager/DefaultWalletManagersStore.kt index bed4e6a55c..36bc7723c2 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/walletmanager/DefaultWalletManagersStore.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/walletmanager/DefaultWalletManagersStore.kt @@ -5,7 +5,7 @@ import com.tangem.blockchain.common.WalletManager import com.tangem.datasource.local.datastore.core.StringKeyDataStore import com.tangem.datasource.local.datastore.core.StringKeyDataStoreDecorator import com.tangem.domain.wallets.models.UserWalletId -import com.tangem.utils.extensions.plusOrReplace +import com.tangem.utils.extensions.addOrReplace internal class DefaultWalletManagersStore( dataStore: StringKeyDataStore>, @@ -32,7 +32,7 @@ internal class DefaultWalletManagersStore( val walletManagers = getSyncOrNull(userWalletId) val updatedWalletManagers = walletManagers - ?.plusOrReplace(walletManager) { + ?.addOrReplace(walletManager) { it.wallet.blockchain == walletManager.wallet.blockchain && it.wallet.publicKey == walletManager.wallet.publicKey } diff --git a/core/utils/src/main/java/com/tangem/utils/extensions/Collection.kt b/core/utils/src/main/java/com/tangem/utils/extensions/Collection.kt index 36644cfda9..9930c5cb77 100644 --- a/core/utils/src/main/java/com/tangem/utils/extensions/Collection.kt +++ b/core/utils/src/main/java/com/tangem/utils/extensions/Collection.kt @@ -18,95 +18,4 @@ fun Collection.isSingleItem(): Boolean = this.size == 1 */ fun Collection.copy(): Collection { return this.map { it } -} - -/** - * Adds the specified element to the collection or replaces an existing element. - * The predicate defines the condition to replace the existing element. - * - * @param item The element to be added or replace the existing one. - * @param predicate The condition to replace an existing element. - * @return The modified [List] after adding or replacing the element. - */ -inline fun Collection.plusOrReplace(item: T, predicate: (T) -> Boolean): List { - val mutableList = this as? MutableList ?: ArrayList(this) - - mutableList.addOrReplace(item, predicate) - - return mutableList -} - -/** - * Adds the specified element to the collection or replaces an existing element. - * The predicate defines the condition to replace the existing element. - * - * @param item The element to be added or replace the existing one. - * @param predicate The condition to replace an existing element. - */ -inline fun MutableCollection.addOrReplace(item: T, predicate: (T) -> Boolean) { - val isReplaced = replaceBy(item, predicate) - - if (!isReplaced) { - add(item) - } -} - -/** - * Removes an element from the collection based on the provided predicate. - * Uses iterator, avoid using it in COW collections - * - * @param predicate The condition to remove an element. - * @return [Boolean] indicating whether an element was removed. - */ -inline fun MutableCollection.removeByIterate(predicate: (T) -> Boolean): Boolean { - var removed = false - val iterator = this.iterator() - - for (e in iterator) { - if (predicate(e)) { - iterator.remove() - removed = true - - break - } - } - - return removed -} - -/** - * Removes an element from the collection based on the provided predicate. - * Uses removeAll() method and could be used for COW collections - * - * @param predicate The condition to remove an element. - * @return [Boolean] indicating whether an element was removed. - */ -fun MutableList.removeByReplace(predicate: (T) -> Boolean): Boolean { - val toRemove = this.filter(predicate) - this.removeAll(toRemove) - return toRemove.isNotEmpty() -} - -/** - * Replaces an element in the collection with the provided item based on the predicate. - * - * @param item The element to replace the existing one. - * @param predicate The condition to replace an existing element. - * @return [Boolean] indicating whether an element was replaced. - */ -inline fun MutableCollection.replaceBy(item: T, predicate: (T) -> Boolean): Boolean { - var replaced = false - val mutableList = this as? MutableList ?: ArrayList(this) - val iterator = mutableList.listIterator() - - for (e in iterator) { - if (predicate(e)) { - iterator.set(item) - replaced = true - - break - } - } - - return replaced } \ No newline at end of file diff --git a/core/utils/src/main/java/com/tangem/utils/extensions/List.kt b/core/utils/src/main/java/com/tangem/utils/extensions/List.kt new file mode 100644 index 0000000000..ee1c005d8c --- /dev/null +++ b/core/utils/src/main/java/com/tangem/utils/extensions/List.kt @@ -0,0 +1,51 @@ +package com.tangem.utils.extensions + +/** + * Removes an element from the collection based on the provided predicate. + * + * @param predicate The condition to remove an element. + * @return [Boolean] indicating whether an element was removed. + */ +fun MutableList.removeBy(predicate: (T) -> Boolean): Boolean { + val toRemove = this.filter(predicate) + this.removeAll(toRemove) + return toRemove.isNotEmpty() +} + +/** + * Replaces an element in the list with the provided item based on the predicate. + * + * @param item The element to replace the existing one. + * @param predicate The condition to replace an existing element. + * @return [Boolean] indicating whether an element was replaced. + */ +inline fun MutableList.replaceBy(item: T, predicate: (T) -> Boolean): Boolean { + val index = indexOfFirst(predicate) + + if (index == -1) { + return false + } + + this[index] = item + + return true +} + +/** + * Adds the specified element to the list or replaces an existing element. + * The predicate defines the condition to replace the existing element. + * + * @param item The element to be added or replace the existing one. + * @param predicate The condition to replace an existing element. + * @return The modified [List] after adding or replacing the element. + */ +inline fun List.addOrReplace(item: T, predicate: (T) -> Boolean): List { + val mutableList = this.toMutableList() + val isReplaced = mutableList.replaceBy(item, predicate) + + if (!isReplaced) { + mutableList.add(item) + } + + return mutableList +} \ No newline at end of file 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 bf4206d543..398586bf6c 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 @@ -36,7 +36,7 @@ internal class DefaultNetworksRepository( private val responseCurrenciesFactory by lazy { ResponseCurrenciesFactory(DemoConfig()) } private val networkStatusFactory by lazy { NetworkStatusFactory() } - private val networksStatuses: MutableStateFlow> = MutableStateFlow(hashSetOf()) + private val networksStatuses: MutableStateFlow> = MutableStateFlow(emptyList()) override fun getNetworks(networksIds: Set): Set { return networkConverter.convertSet(networksIds) @@ -48,7 +48,9 @@ internal class DefaultNetworksRepository( refresh: Boolean, ): Flow> = channelFlow { launch(dispatchers.io) { - networksStatuses.collect(::send) + networksStatuses.collect { + send(it.toSet()) + } } launch(dispatchers.io) { @@ -82,17 +84,22 @@ internal class DefaultNetworksRepository( private suspend fun fetchNetworkStatus(userWalletId: UserWalletId, networkId: Network.ID) { val currencies = getCurrencies(userWalletId) + .asSequence() + .filter { it.networkId == networkId } + val result = walletManagersFacade.update( userWalletId = userWalletId, networkId = networkId, extraTokens = currencies.filterIsInstanceTo(hashSetOf()), ) - val networkStatus = networkStatusFactory.createNetworkStatus(networkId, result, currencies) + val networkStatus = networkStatusFactory.createNetworkStatus( + networkId = networkId, + result = result, + currencies = currencies.toSet(), + ) networksStatuses.update { statuses -> - statuses.apply { - addOrReplace(networkStatus) { it.networkId == networkStatus.networkId } - } + statuses.addOrReplace(networkStatus) { it.networkId == networkStatus.networkId } } } diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/MockQuotesRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/MockQuotesRepository.kt index 841ce3207f..e37146cbba 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/MockQuotesRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/MockQuotesRepository.kt @@ -3,21 +3,27 @@ package com.tangem.data.tokens.repository import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.Quote import com.tangem.domain.tokens.repository.QuotesRepository +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.channelFlow import java.math.BigDecimal +import kotlin.random.Random internal class MockQuotesRepository : QuotesRepository { override fun getQuotes(currenciesIds: Set, refresh: Boolean): Flow> { - return flowOf( - currenciesIds.map { + return channelFlow { + val quotes = currenciesIds.map { Quote( currencyId = it, fiatRate = BigDecimal.ZERO, priceChange = BigDecimal.ZERO, ) - }.toSet(), - ) + }.toSet() + + delay(Random.nextLong(from = 200, until = 2_000)) + + send(quotes) + } } } \ No newline at end of file diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/NetworkStatusFactory.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/NetworkStatusFactory.kt index f76f363033..6dc955d4e5 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/NetworkStatusFactory.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/NetworkStatusFactory.kt @@ -5,7 +5,6 @@ import com.tangem.domain.tokens.model.NetworkStatus import com.tangem.domain.tokens.models.Network import com.tangem.domain.walletmanager.model.CryptoCurrencyAmount import com.tangem.domain.walletmanager.model.UpdateWalletManagerResult -import timber.log.Timber import java.math.BigDecimal internal class NetworkStatusFactory { @@ -33,25 +32,20 @@ internal class NetworkStatusFactory { amounts: Set, currencies: Set, ): Map { - val formattedAmounts = hashMapOf() - - currencies.forEach { currency -> - val amount = when (currency) { - is CryptoCurrency.Coin -> amounts.singleOrNull { it is CryptoCurrencyAmount.Coin } - is CryptoCurrency.Token -> amounts.singleOrNull { - it is CryptoCurrencyAmount.Token && - it.id == getTokenIdString(currency.id) && - it.tokenContractAddress == currency.contractAddress + return amounts + .asSequence() + .mapNotNull { amount -> + val currency = when (amount) { + is CryptoCurrencyAmount.Coin -> currencies.singleOrNull { it is CryptoCurrency.Coin } + is CryptoCurrencyAmount.Token -> currencies.firstOrNull { + it is CryptoCurrency.Token && + getTokenIdString(it.id) == amount.id && + it.contractAddress == amount.tokenContractAddress + } } - }?.value - if (amount == null) { - Timber.e("Unable to find a token amount for: ${currency.name}") - } else { - formattedAmounts[currency.id] = amount + currency?.id?.let { it to amount.value } } - } - - return formattedAmounts + .toMap() } } \ No newline at end of file diff --git a/domain/core/src/main/kotlin/com/tangem/domain/core/raise/DelegatedRaise.kt b/domain/core/src/main/kotlin/com/tangem/domain/core/raise/DelegatedRaise.kt deleted file mode 100644 index ffde660eb6..0000000000 --- a/domain/core/src/main/kotlin/com/tangem/domain/core/raise/DelegatedRaise.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.tangem.domain.core.raise - -import arrow.core.raise.Raise - -abstract class DelegatedRaise( - private val otherRaise: Raise, - private val transformError: (Error) -> OtherError, -) : Raise { - - override fun raise(r: Error): Nothing { - otherRaise.raise(transformError(r)) - } -} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyUseCase.kt index f49378314a..6c9aa54e98 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyUseCase.kt @@ -1,12 +1,8 @@ package com.tangem.domain.tokens import arrow.core.Either -import arrow.core.left -import arrow.core.raise.Raise -import arrow.core.raise.recover -import arrow.core.right import com.tangem.domain.tokens.error.CurrencyError -import com.tangem.domain.tokens.error.mapper.mapToTokenError +import com.tangem.domain.tokens.error.mapper.mapToCurrencyError import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations @@ -15,9 +11,7 @@ import com.tangem.domain.tokens.repository.NetworksRepository import com.tangem.domain.tokens.repository.QuotesRepository import com.tangem.domain.wallets.models.UserWalletId import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.channelFlow -import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.* /** * Use case for fetching the status of a specific cryptocurrency associated with a user wallet. @@ -47,36 +41,26 @@ class GetCurrencyUseCase( currencyId: CryptoCurrency.ID, refresh: Boolean = false, ): Flow> { - return channelFlow { - recover( - block = { - getCurrency(userWalletId, currencyId, refresh).collectLatest { currencyStatus -> - send(currencyStatus.right()) - } - }, - recover = { error -> - send(error.left()) - }, - ) - } + return flow { + emitAll(getCurrency(userWalletId, currencyId, refresh)) + }.flowOn(dispatchers.io) } - private suspend fun Raise.getCurrency( + private suspend fun getCurrency( userWalletId: UserWalletId, currencyId: CryptoCurrency.ID, refresh: Boolean, - ): Flow { + ): Flow> { val operations = CurrenciesStatusesOperations( currenciesRepository = currenciesRepository, quotesRepository = quotesRepository, networksRepository = networksRepository, userWalletId = userWalletId, refresh = refresh, - dispatchers = dispatchers, - raise = this, - transformError = CurrenciesStatusesOperations.Error::mapToTokenError, ) - return operations.getCurrencyStatusFlow(currencyId) + return operations.getCurrencyStatusFlow(currencyId).map { maybeCurrency -> + maybeCurrency.mapLeft(CurrenciesStatusesOperations.Error::mapToCurrencyError) + } } } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetPrimaryCurrencyUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetPrimaryCurrencyUseCase.kt index 3d0f9c2011..58a8bba978 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetPrimaryCurrencyUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetPrimaryCurrencyUseCase.kt @@ -1,12 +1,8 @@ package com.tangem.domain.tokens import arrow.core.Either -import arrow.core.left -import arrow.core.raise.Raise -import arrow.core.raise.recover -import arrow.core.right import com.tangem.domain.tokens.error.CurrencyError -import com.tangem.domain.tokens.error.mapper.mapToTokenError +import com.tangem.domain.tokens.error.mapper.mapToCurrencyError import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations import com.tangem.domain.tokens.repository.CurrenciesRepository @@ -14,9 +10,7 @@ import com.tangem.domain.tokens.repository.NetworksRepository import com.tangem.domain.tokens.repository.QuotesRepository import com.tangem.domain.wallets.models.UserWalletId import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.channelFlow -import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.* /** * Use case for fetching the status of the primary cryptocurrency associated with a user wallet. @@ -44,35 +38,25 @@ class GetPrimaryCurrencyUseCase( userWalletId: UserWalletId, refresh: Boolean = false, ): Flow> { - return channelFlow { - recover( - block = { - getCurrency(userWalletId, refresh).collectLatest { currencyStatus -> - send(currencyStatus.right()) - } - }, - recover = { error -> - send(error.left()) - }, - ) - } + return flow { + emitAll(getPrimaryCurrency(userWalletId, refresh)) + }.flowOn(dispatchers.io) } - private suspend fun Raise.getCurrency( + private suspend fun getPrimaryCurrency( userWalletId: UserWalletId, refresh: Boolean, - ): Flow { + ): Flow> { val operations = CurrenciesStatusesOperations( currenciesRepository = currenciesRepository, quotesRepository = quotesRepository, networksRepository = networksRepository, userWalletId = userWalletId, refresh = refresh, - dispatchers = dispatchers, - raise = this, - transformError = CurrenciesStatusesOperations.Error::mapToTokenError, ) - return operations.getPrimaryCurrencyStatusFlow() + return operations.getPrimaryCurrencyStatusFlow().map { maybeCurrency -> + maybeCurrency.mapLeft(CurrenciesStatusesOperations.Error::mapToCurrencyError) + } } } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetTokenListUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetTokenListUseCase.kt index ea19873b22..1b046264dd 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetTokenListUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetTokenListUseCase.kt @@ -2,9 +2,6 @@ package com.tangem.domain.tokens import arrow.core.Either import arrow.core.left -import arrow.core.raise.Raise -import arrow.core.raise.recover -import arrow.core.right import com.tangem.domain.tokens.error.TokenListError import com.tangem.domain.tokens.error.mapper.mapToTokenListError import com.tangem.domain.tokens.model.CryptoCurrencyStatus @@ -16,10 +13,11 @@ import com.tangem.domain.tokens.repository.NetworksRepository import com.tangem.domain.tokens.repository.QuotesRepository import com.tangem.domain.wallets.models.UserWalletId import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.channelFlow -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.flow.flatMapConcat +import kotlinx.coroutines.flow.flatMapMerge +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map class GetTokenListUseCase( internal val currenciesRepository: CurrenciesRepository, @@ -28,53 +26,48 @@ class GetTokenListUseCase( internal val dispatchers: CoroutineDispatcherProvider, ) { + @OptIn(ExperimentalCoroutinesApi::class) operator fun invoke(userWalletId: UserWalletId, refresh: Boolean = true): Flow> { - return channelFlow { - recover( - block = { - getTokenList(userWalletId, refresh).collectLatest { list -> - send(list.right()) - } + return getTokensStatuses(userWalletId, refresh).flatMapMerge flatMap@{ maybeTokens -> + maybeTokens.fold( + ifLeft = { error -> + flowOf(error.left()) }, - recover = { error -> - send(error.left()) + ifRight = { tokens -> + createTokenList(userWalletId, tokens) }, ) } } - private fun Raise.getTokenList(userWalletId: UserWalletId, refresh: Boolean): Flow { - return getTokensStatuses(userWalletId, refresh).flatMapConcat { tokens -> - createTokenList(userWalletId, tokens) - } - } - private fun Raise.getTokensStatuses( + private fun getTokensStatuses( userWalletId: UserWalletId, refresh: Boolean, - ): Flow> { + ): Flow>> { val operations = CurrenciesStatusesOperations( userWalletId = userWalletId, refresh = refresh, useCase = this@GetTokenListUseCase, - raise = this, - transformError = CurrenciesStatusesOperations.Error::mapToTokenListError, ) return operations.getCurrenciesStatusesFlow() + .map { maybeCurrenciesStatuses -> + maybeCurrenciesStatuses.mapLeft(CurrenciesStatusesOperations.Error::mapToTokenListError) + } } - private fun Raise.createTokenList( + private fun createTokenList( userWalletId: UserWalletId, tokens: Set, - ): Flow { + ): Flow> { val operations = TokenListOperations( userWalletId = userWalletId, tokens = tokens, useCase = this@GetTokenListUseCase, - raise = this, - transform = TokenListOperations.Error::mapToTokenListError, ) - return operations.getTokenListFlow() + return operations.getTokenListFlow().map { maybeTokenList -> + maybeTokenList.mapLeft(TokenListOperations.Error::mapToTokenListError) + } } } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/ToggleTokenListGroupingUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/ToggleTokenListGroupingUseCase.kt index fe70f6aacd..07f86f5069 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/ToggleTokenListGroupingUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/ToggleTokenListGroupingUseCase.kt @@ -1,10 +1,7 @@ package com.tangem.domain.tokens import arrow.core.Either -import arrow.core.raise.Raise -import arrow.core.raise.catch -import arrow.core.raise.either -import arrow.core.raise.ensure +import arrow.core.raise.* import com.tangem.domain.tokens.error.TokenListSortingError import com.tangem.domain.tokens.error.mapper.mapToTokenListSortingError import com.tangem.domain.tokens.model.TokenList @@ -35,47 +32,40 @@ class ToggleTokenListGroupingUseCase( } } - private suspend fun Raise.groupTokens( - tokenList: TokenList.Ungrouped, - ): TokenList.GroupedByNetwork { - val sortingOperations = getSortingOperations(tokenList) - val tokens = sortingOperations.getTokens() + private fun Raise.groupTokens(tokenList: TokenList.Ungrouped): TokenList.GroupedByNetwork { + val sortingOperations = TokenListSortingOperations(tokenList) + val tokens = withError(TokenListSortingOperations.Error::mapToTokenListSortingError) { + sortingOperations.getTokens().bind() + } val networks = getNetworks(tokens.map { it.currency.networkId }.toSet()) return TokenList.GroupedByNetwork( - groups = sortingOperations.getGroupedTokens(networks), + groups = withError(TokenListSortingOperations.Error::mapToTokenListSortingError) { + sortingOperations.getGroupedTokens(networks).bind() + }, totalFiatBalance = tokenList.totalFiatBalance, sortedBy = sortingOperations.getSortType(), ) } - private suspend fun Raise.ungroupTokens( + private fun Raise.ungroupTokens( tokenList: TokenList.GroupedByNetwork, ): TokenList.Ungrouped { - val sortingOperations = getSortingOperations(tokenList) + val sortingOperations = TokenListSortingOperations(tokenList) return TokenList.Ungrouped( - currencies = sortingOperations.getTokens(), + currencies = withError(TokenListSortingOperations.Error::mapToTokenListSortingError) { + sortingOperations.getTokens().bind() + }, totalFiatBalance = tokenList.totalFiatBalance, sortedBy = sortingOperations.getSortType(), ) } - private fun Raise.getSortingOperations(tokenList: TokenList): TokenListSortingOperations<*> { - return TokenListSortingOperations( - tokenList = tokenList, - dispatchers = dispatchers, - raise = this, - transformError = TokenListSortingOperations.Error::mapToTokenListSortingError, + private fun Raise.getNetworks(networksIds: Set): Set { + return catch( + block = { networksRepository.getNetworks(networksIds) }, + catch = { raise(TokenListSortingError.DataError(it)) }, ) } - - private suspend fun Raise.getNetworks(networksIds: Set): Set { - return withContext(dispatchers.io) { - catch( - block = { networksRepository.getNetworks(networksIds) }, - catch = { raise(TokenListSortingError.DataError(it)) }, - ) - } - } } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/ToggleTokenListSortingUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/ToggleTokenListSortingUseCase.kt index b91ce9521a..483b822928 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/ToggleTokenListSortingUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/ToggleTokenListSortingUseCase.kt @@ -4,6 +4,7 @@ import arrow.core.Either import arrow.core.raise.Raise import arrow.core.raise.either import arrow.core.raise.ensure +import arrow.core.raise.withError import com.tangem.domain.tokens.error.TokenListSortingError import com.tangem.domain.tokens.error.mapper.mapToTokenListSortingError import com.tangem.domain.tokens.model.TokenList @@ -31,36 +32,37 @@ class ToggleTokenListSortingUseCase( } } - private suspend fun Raise.sortGroupedTokenList( + private fun Raise.sortGroupedTokenList( tokenList: TokenList.GroupedByNetwork, ): TokenList.GroupedByNetwork { val operations = getSortingOperations(tokenList) val networks = tokenList.groups.map { it.network }.toSet() return tokenList.copy( - groups = operations.getGroupedTokens(networks), + groups = withError(TokenListSortingOperations.Error::mapToTokenListSortingError) { + operations.getGroupedTokens(networks).bind() + }, sortedBy = operations.getSortType(), ) } - private suspend fun Raise.sortUngroupedTokenList( + private fun Raise.sortUngroupedTokenList( tokenList: TokenList.Ungrouped, ): TokenList.Ungrouped { val operations = getSortingOperations(tokenList) return tokenList.copy( - currencies = operations.getTokens(), + currencies = withError(TokenListSortingOperations.Error::mapToTokenListSortingError) { + operations.getTokens().bind() + }, sortedBy = operations.getSortType(), ) } - private fun Raise.getSortingOperations(tokenList: TokenList): TokenListSortingOperations<*> { + private fun getSortingOperations(tokenList: TokenList): TokenListSortingOperations { return TokenListSortingOperations( tokenList = tokenList, sortByBalance = tokenList.sortedBy != TokenList.SortType.BALANCE, - dispatchers = dispatchers, - raise = this, - transformError = TokenListSortingOperations.Error::mapToTokenListSortingError, ) } } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/mapper/GetWalletTokenErrorMappers.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/mapper/GetWalletTokenErrorMappers.kt index 981f8785f2..51fc33c463 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/mapper/GetWalletTokenErrorMappers.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/mapper/GetWalletTokenErrorMappers.kt @@ -3,7 +3,7 @@ package com.tangem.domain.tokens.error.mapper import com.tangem.domain.tokens.error.CurrencyError import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations -internal fun CurrenciesStatusesOperations.Error.mapToTokenError(): CurrencyError { +internal fun CurrenciesStatusesOperations.Error.mapToCurrencyError(): CurrencyError { return when (this) { is CurrenciesStatusesOperations.Error.DataError -> CurrencyError.DataError(this.cause) is CurrenciesStatusesOperations.Error.EmptyNetworksStatuses, 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 1b23b9910c..6e1032e5c1 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,9 +1,7 @@ package com.tangem.domain.tokens.operations import arrow.core.* -import arrow.core.raise.Raise -import arrow.core.raise.catch -import com.tangem.domain.core.raise.DelegatedRaise +import arrow.core.raise.* import com.tangem.domain.tokens.GetTokenListUseCase import com.tangem.domain.tokens.model.* import com.tangem.domain.tokens.models.Network @@ -11,97 +9,98 @@ 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 com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.* -import kotlinx.coroutines.withContext -@Suppress("LongParameterList") -internal class CurrenciesStatusesOperations( +internal class CurrenciesStatusesOperations( private val currenciesRepository: CurrenciesRepository, private val quotesRepository: QuotesRepository, private val networksRepository: NetworksRepository, private val userWalletId: UserWalletId, private val refresh: Boolean, - private val dispatchers: CoroutineDispatcherProvider, - raise: Raise, - transformError: (Error) -> E, -) : DelegatedRaise(raise, transformError) { +) { constructor( userWalletId: UserWalletId, refresh: Boolean, useCase: GetTokenListUseCase, - raise: Raise, - transformError: (Error) -> E, ) : this( currenciesRepository = useCase.currenciesRepository, quotesRepository = useCase.quotesRepository, networksRepository = useCase.networksRepository, userWalletId = userWalletId, refresh = refresh, - dispatchers = useCase.dispatchers, - raise = raise, - transformError = transformError, ) - fun getCurrenciesStatusesFlow(): Flow> { - return getMultiCurrencyWalletCurrencies().flatMapConcat { - val currencies = it.toNonEmptySetOrNull() + @OptIn(ExperimentalCoroutinesApi::class) + fun getCurrenciesStatusesFlow(): Flow>> { + return getMultiCurrencyWalletCurrencies().flatMapMerge flatMap@{ maybeCurrencies -> + val nonEmptyCurrencies = maybeCurrencies.fold( + ifLeft = { error -> + return@flatMap flowOf(error.left()) + }, + ifRight = { it.toNonEmptySetOrNull() }, + ) ?: return@flatMap flowOf(emptySet().right()) - if (currencies == null) { - flowOf(emptySet()) - } else { - val currencyIdToNetworkId = currencies.associate { currency -> - currency.id to currency.networkId - } - val currenciesIds = requireNotNull(currencyIdToNetworkId.keys.toNonEmptySetOrNull()) { - "Currencies IDs cannot be empty" - } - val networksIds = requireNotNull(currencyIdToNetworkId.values.toNonEmptySetOrNull()) { - "Networks IDs cannot be empty" - } + val (networksIds, currenciesIds) = getIds(nonEmptyCurrencies) - combine(getQuotes(currenciesIds), getNetworksStatues(networksIds)) { quotes, networksStatuses -> - createTokensStatuses(currencies, quotes, networksStatuses) + combine( + getQuotes(currenciesIds), + getNetworksStatuses(networksIds), + ) { maybeQuotes, maybeNetworksStatuses -> + either { + createCurrenciesStatuses(nonEmptyCurrencies, maybeQuotes.bind(), maybeNetworksStatuses.bind()) } } } } - suspend fun getCurrencyStatusFlow(currencyId: CryptoCurrency.ID): Flow { - val currency = getMultiCurrencyWalletCurrency(currencyId) + suspend fun getCurrencyStatusFlow(currencyId: CryptoCurrency.ID): Flow> { + val currency = recover( + block = { getMultiCurrencyWalletCurrency(currencyId) }, + recover = { return flowOf(it.left()) }, + ) return getCurrencyStatusFlow(currency) } - suspend fun getPrimaryCurrencyStatusFlow(): Flow { - val currency = getPrimaryCurrency() + suspend fun getPrimaryCurrencyStatusFlow(): Flow> { + val currency = recover( + block = { getPrimaryCurrency() }, + recover = { return flowOf(it.left()) }, + ) return getCurrencyStatusFlow(currency) } - private fun getCurrencyStatusFlow(currency: CryptoCurrency): Flow { + private fun getCurrencyStatusFlow(currency: CryptoCurrency): Flow> { val quoteFlow = getQuotes(nonEmptySetOf(currency.id)) - .map { quotes -> - quotes.singleOrNull { it.currencyId == currency.id } + .map { maybeQuotes -> + maybeQuotes.map { quotes -> + quotes.singleOrNull { it.currencyId == currency.id } + } } - val statusFlow = getNetworksStatues(nonEmptySetOf(currency.networkId)) - .map { statuses -> - statuses.singleOrNull { it.networkId == currency.networkId } + val statusFlow = getNetworksStatuses(nonEmptySetOf(currency.networkId)) + .map { maybeStatuses -> + maybeStatuses.map { statuses -> + statuses.singleOrNull { it.networkId == currency.networkId } + } } - return combine(quoteFlow, statusFlow) { quote, networkStatus -> - createStatus(currency, quote, networkStatus) + return combine(quoteFlow, statusFlow) { maybeQuote, maybeNetworkStatus -> + either { + createStatus(currency, maybeQuote.bind(), maybeNetworkStatus.bind()) + } } } - private suspend fun createTokensStatuses( - tokens: Set, + private fun createCurrenciesStatuses( + currencies: NonEmptySet, quotes: Set, networkStatuses: Set, - ): Set = withContext(dispatchers.default) { - tokens.mapTo(hashSetOf()) { token -> + ): Set { + return currencies.mapTo(hashSetOf()) { token -> val quote = quotes.firstOrNull { it.currencyId == token.id } val networkStatus = networkStatuses.firstOrNull { it.networkId == token.networkId } @@ -109,7 +108,7 @@ internal class CurrenciesStatusesOperations( } } - private suspend fun createStatus( + private fun createStatus( token: CryptoCurrency, quote: Quote?, networkStatus: NetworkStatus?, @@ -118,44 +117,58 @@ internal class CurrenciesStatusesOperations( currency = token, quote = quote, networkStatus = networkStatus, - dispatchers = dispatchers, - raise = this, - transformError = { Error.UnableToCreateCurrencyStatus }, ) return currencyStatusOperations.createTokenStatus() } - private suspend fun getMultiCurrencyWalletCurrency(currencyId: CryptoCurrency.ID): CryptoCurrency { - return catch( - block = { currenciesRepository.getMultiCurrencyWalletCurrency(userWalletId, currencyId) }, - catch = { raise(Error.DataError(it)) }, - ) - } - - private fun getMultiCurrencyWalletCurrencies(): Flow> { + private fun getMultiCurrencyWalletCurrencies(): Flow>> { return currenciesRepository.getMultiCurrencyWalletCurrencies(userWalletId, refresh) - .catch { raise(Error.DataError(it)) } - .onEmpty { raise(Error.EmptyCurrencies) } + .map, Either>> { it.right() } + .catch { emit(Error.DataError(it).left()) } + .onEmpty { emit(Error.EmptyCurrencies.left()) } } - private suspend fun getPrimaryCurrency(): CryptoCurrency { + private suspend fun Raise.getMultiCurrencyWalletCurrency(currencyId: CryptoCurrency.ID): CryptoCurrency { + return Either.catch { currenciesRepository.getMultiCurrencyWalletCurrency(userWalletId, currencyId) } + .mapLeft { Error.DataError(it) } + .bind() + } + + private suspend fun Raise.getPrimaryCurrency(): CryptoCurrency { return catch( block = { currenciesRepository.getSingleCurrencyWalletPrimaryCurrency(userWalletId) }, catch = { raise(Error.DataError(it)) }, ) } - private fun getQuotes(tokensIds: NonEmptySet): Flow> { + private fun getQuotes(tokensIds: NonEmptySet): Flow>> { return quotesRepository.getQuotes(tokensIds, refresh) - .catch { raise(Error.DataError(it)) } - .onEmpty { raise(Error.EmptyQuotes) } + .map, Either>> { it.right() } + .catch { emit(Error.DataError(it).left()) } + .onEmpty { emit(Error.EmptyQuotes.left()) } } - private fun getNetworksStatues(networks: NonEmptySet): Flow> { + private fun getNetworksStatuses(networks: NonEmptySet): Flow>> { return networksRepository.getNetworkStatuses(userWalletId, networks, refresh) - .catch { raise(Error.DataError(it)) } - .onEmpty { raise(Error.EmptyNetworksStatuses) } + .map, Either>> { it.right() } + .catch { emit(Error.DataError(it).left()) } + .onEmpty { emit(Error.EmptyNetworksStatuses.left()) } + } + + private fun getIds( + currencies: NonEmptySet, + ): Pair, NonEmptySet> { + val currencyIdToNetworkId = currencies.associate { currency -> + currency.id to currency.networkId + } + val currenciesIds = currencyIdToNetworkId.keys.toNonEmptySetOrNull() + val networksIds = currencyIdToNetworkId.values.toNonEmptySetOrNull() + + requireNotNull(currenciesIds) { "Currencies IDs cannot be empty" } + requireNotNull(networksIds) { "Networks IDs cannot be empty" } + + return networksIds to currenciesIds } sealed class Error { 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 50f348f3d3..d654ee6cd5 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 @@ -1,28 +1,18 @@ package com.tangem.domain.tokens.operations -import arrow.core.raise.Raise -import arrow.core.raise.ensureNotNull -import com.tangem.domain.core.raise.DelegatedRaise import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.NetworkStatus import com.tangem.domain.tokens.model.Quote -import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import kotlinx.coroutines.withContext import java.math.BigDecimal -internal class CurrencyStatusOperations( +internal class CurrencyStatusOperations( private val currency: CryptoCurrency, private val quote: Quote?, private val networkStatus: NetworkStatus?, - private val dispatchers: CoroutineDispatcherProvider, - raise: Raise, - transformError: (Error) -> OtherError, -) : DelegatedRaise(raise, transformError) { +) { - suspend fun createTokenStatus(): CryptoCurrencyStatus = withContext(dispatchers.default) { - CryptoCurrencyStatus(currency, createStatus()) - } + fun createTokenStatus(): CryptoCurrencyStatus = CryptoCurrencyStatus(currency, createStatus()) private fun createStatus(): CryptoCurrencyStatus.Status { return when (val status = networkStatus?.value) { @@ -35,9 +25,7 @@ internal class CurrencyStatusOperations( } private fun createStatus(status: NetworkStatus.Verified): CryptoCurrencyStatus.Status { - val amount = ensureNotNull(status.amounts[currency.id]) { - Error.UnableToFindAmount(currency.id) - } + val amount = status.amounts[currency.id] ?: return CryptoCurrencyStatus.Unreachable return when { currency is CryptoCurrency.Token && currency.isCustom -> CryptoCurrencyStatus.Custom( @@ -67,9 +55,4 @@ internal class CurrencyStatusOperations( private fun calculateFiatAmount(amount: BigDecimal, fiatRate: BigDecimal): BigDecimal { return amount * fiatRate } - - sealed class Error { - - data class UnableToFindAmount(val currencyId: CryptoCurrency.ID) : Error() - } } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListFiatBalanceOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListFiatBalanceOperations.kt index dd3cb367d5..2373f7f6ef 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListFiatBalanceOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListFiatBalanceOperations.kt @@ -3,48 +3,44 @@ package com.tangem.domain.tokens.operations import arrow.core.NonEmptySet import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.TokenList -import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import kotlinx.coroutines.withContext import java.math.BigDecimal internal class TokenListFiatBalanceOperations( private val currencies: NonEmptySet, private val isAnyTokenLoading: Boolean, - private val dispatcher: CoroutineDispatcherProvider, ) { - suspend fun calculateFiatBalance(): TokenList.FiatBalance { - return withContext(dispatcher.single) { - var fiatBalance: TokenList.FiatBalance = TokenList.FiatBalance.Loading - if (isAnyTokenLoading) return@withContext fiatBalance + fun calculateFiatBalance(): TokenList.FiatBalance { + var fiatBalance: TokenList.FiatBalance = TokenList.FiatBalance.Loading + if (isAnyTokenLoading) return fiatBalance - for (token in currencies) { - when (val status = token.value) { - is CryptoCurrencyStatus.Loading -> { - fiatBalance = TokenList.FiatBalance.Loading - break - } - is CryptoCurrencyStatus.MissedDerivation, - is CryptoCurrencyStatus.Unreachable, - -> { - fiatBalance = TokenList.FiatBalance.Failed - break - } - is CryptoCurrencyStatus.NoAccount -> { - fiatBalance = recalculateBalanceForNoAccountStatus(fiatBalance) - } - is CryptoCurrencyStatus.Loaded -> { - fiatBalance = recalculateBalance(status, fiatBalance) - } - is CryptoCurrencyStatus.Custom -> { - fiatBalance = recalculateBalance(status, fiatBalance) - } + for (token in currencies) { + when (val status = token.value) { + is CryptoCurrencyStatus.Loading -> { + fiatBalance = TokenList.FiatBalance.Loading + break + } + is CryptoCurrencyStatus.MissedDerivation, + is CryptoCurrencyStatus.Unreachable, + -> { + fiatBalance = TokenList.FiatBalance.Failed + break + } + is CryptoCurrencyStatus.NoAccount -> { + fiatBalance = recalculateBalanceForNoAccountStatus(fiatBalance) + } + is CryptoCurrencyStatus.Loaded -> { + fiatBalance = recalculateBalance(status, fiatBalance) + } + is CryptoCurrencyStatus.Custom -> { + fiatBalance = recalculateBalance(status, fiatBalance) } } - - fiatBalance } + + return fiatBalance } + private fun recalculateBalanceForNoAccountStatus(currentBalance: TokenList.FiatBalance): TokenList.FiatBalance { return with(currentBalance) { (this as? TokenList.FiatBalance.Loaded)?.copy( diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListOperations.kt index da6c780e24..ada01f316b 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListOperations.kt @@ -1,11 +1,7 @@ package com.tangem.domain.tokens.operations -import arrow.core.NonEmptySet -import arrow.core.raise.Raise -import arrow.core.raise.catch -import arrow.core.raise.ensureNotNull -import arrow.core.toNonEmptySetOrNull -import com.tangem.domain.core.raise.DelegatedRaise +import arrow.core.* +import arrow.core.raise.* import com.tangem.domain.tokens.GetTokenListUseCase import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.TokenList @@ -13,62 +9,55 @@ import com.tangem.domain.tokens.models.Network import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.tokens.repository.NetworksRepository import com.tangem.domain.wallets.models.UserWalletId -import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.flow.* -import kotlinx.coroutines.withContext @Suppress("LongParameterList") -internal class TokenListOperations( +internal class TokenListOperations( private val currenciesRepository: CurrenciesRepository, private val networksRepository: NetworksRepository, private val userWalletId: UserWalletId, private val tokens: Set, - private val dispatchers: CoroutineDispatcherProvider, - raise: Raise, - transform: (Error) -> E, -) : DelegatedRaise(raise, transform) { +) { constructor( userWalletId: UserWalletId, tokens: Set, useCase: GetTokenListUseCase, - raise: Raise, - transform: (Error) -> E, ) : this( currenciesRepository = useCase.currenciesRepository, networksRepository = useCase.networksRepository, userWalletId = userWalletId, tokens = tokens, - dispatchers = useCase.dispatchers, - raise = raise, - transform = transform, ) - fun getTokenListFlow(): Flow { - return combine(getIsGrouped(), getIsSortedByBalance()) { isGrouped, isSortedByBalance -> - createTokenList(isGrouped, isSortedByBalance) + fun getTokenListFlow(): Flow> { + return combine( + getIsGrouped(), + getIsSortedByBalance(), + ) { isGrouped, isSortedByBalance -> + either { + createTokenList(isGrouped.bind(), isSortedByBalance.bind()) + } } } - private suspend fun createTokenList(isGrouped: Boolean, isSortedByBalance: Boolean): TokenList { - return withContext(dispatchers.default) { - val tokensNes = tokens.toNonEmptySetOrNull() - ?: return@withContext TokenList.NotInitialized + private fun Raise.createTokenList(isGrouped: Boolean, isSortedByBalance: Boolean): TokenList { + val tokensNes = tokens.toNonEmptySetOrNull() + ?: return TokenList.NotInitialized - val isAnyTokenLoading = tokensNes.any { it.value is CryptoCurrencyStatus.Loading } - val fiatBalanceOperations = TokenListFiatBalanceOperations(tokensNes, isAnyTokenLoading, dispatchers) + val isAnyTokenLoading = tokensNes.any { it.value is CryptoCurrencyStatus.Loading } + val fiatBalanceOperations = TokenListFiatBalanceOperations(tokensNes, isAnyTokenLoading) - createTokenList( - tokens = tokensNes, - fiatBalance = fiatBalanceOperations.calculateFiatBalance(), - isAnyTokenLoading = isAnyTokenLoading, - isGrouped = isGrouped, - isSortedByBalance = isSortedByBalance, - ) - } + return createTokenList( + tokens = tokensNes, + fiatBalance = fiatBalanceOperations.calculateFiatBalance(), + isAnyTokenLoading = isAnyTokenLoading, + isGrouped = isGrouped, + isSortedByBalance = isSortedByBalance, + ) } - private suspend fun createTokenList( + private fun Raise.createTokenList( tokens: NonEmptySet, fiatBalance: TokenList.FiatBalance, isAnyTokenLoading: Boolean, @@ -79,19 +68,14 @@ internal class TokenListOperations( currencies = tokens, isAnyTokenLoading = isAnyTokenLoading, sortByBalance = isSortedByBalance, - dispatchers = dispatchers, - raise = this, - transformError = { e -> - Error.fromTokenListOperations(e) { createUnsortedUngroupedTokenList(tokens, fiatBalance) } - }, ) return createTokenList(tokens, sortingOperations, fiatBalance, isGrouped) } - private suspend fun createTokenList( + private fun Raise.createTokenList( tokens: NonEmptySet, - sortingOperations: TokenListSortingOperations<*>, + sortingOperations: TokenListSortingOperations, fiatBalance: TokenList.FiatBalance, isGrouped: Boolean, ): TokenList { @@ -108,37 +92,46 @@ internal class TokenListOperations( } } - private suspend fun getNetworks(tokensNes: NonEmptySet): Set { - return withContext(dispatchers.io) { - val networksIds = tokensNes.map { it.currency.networkId }.toNonEmptySet() - catch( - block = { networksRepository.getNetworks(networksIds) }, - catch = { raise(Error.DataError(it)) }, - ) - } + private fun Raise.getNetworks(tokensNes: NonEmptySet): Set { + val networksIds = tokensNes.map { it.currency.networkId }.toNonEmptySet() + + return catch( + block = { networksRepository.getNetworks(networksIds) }, + catch = { raise(Error.DataError(it)) }, + ) } - private suspend fun createUngroupedTokenList( - sortingOperations: TokenListSortingOperations<*>, + private fun Raise.createUngroupedTokenList( + sortingOperations: TokenListSortingOperations, fiatBalance: TokenList.FiatBalance, ): TokenList.Ungrouped = TokenList.Ungrouped( sortedBy = sortingOperations.getSortType(), totalFiatBalance = fiatBalance, - currencies = sortingOperations.getTokens(), + currencies = withError( + transform = { e -> + Error.fromTokenListOperations(e) { createUnsortedUngroupedTokenList(tokens, fiatBalance) } + }, + block = { sortingOperations.getTokens().bind() }, + ), ) - private suspend fun createGroupedTokenList( - sortingOperations: TokenListSortingOperations<*>, + private fun Raise.createGroupedTokenList( + sortingOperations: TokenListSortingOperations, fiatBalance: TokenList.FiatBalance, networks: NonEmptySet, ): TokenList.GroupedByNetwork = TokenList.GroupedByNetwork( sortedBy = sortingOperations.getSortType(), totalFiatBalance = fiatBalance, - groups = sortingOperations.getGroupedTokens(networks), + groups = withError( + transform = { e -> + Error.fromTokenListOperations(e) { createUnsortedUngroupedTokenList(tokens, fiatBalance) } + }, + block = { sortingOperations.getGroupedTokens(networks).bind() }, + ), ) private fun createUnsortedUngroupedTokenList( - tokens: NonEmptySet, + tokens: Set, fiatBalance: TokenList.FiatBalance, ): TokenList.Ungrouped { return TokenList.Ungrouped( @@ -148,18 +141,18 @@ internal class TokenListOperations( ) } - private fun getIsGrouped(): Flow { + private fun getIsGrouped(): Flow> { return currenciesRepository.isTokensGrouped(userWalletId) - .catch { raise(Error.DataError(it)) } - .onEmpty { emit(value = false) } - .flowOn(dispatchers.io) + .map> { it.right() } + .catch { emit(Error.DataError(it).left()) } + .onEmpty { emit(value = false.right()) } } - private fun getIsSortedByBalance(): Flow { + private fun getIsSortedByBalance(): Flow> { return currenciesRepository.isTokensSortedByBalance(userWalletId) - .catch { raise(Error.DataError(it)) } - .onEmpty { emit(value = false) } - .flowOn(dispatchers.io) + .map> { it.right() } + .catch { emit(Error.DataError(it).left()) } + .onEmpty { emit(value = false.right()) } } sealed class Error { diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListSortingOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListSortingOperations.kt index 06c3041651..5bf58c6cf7 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListSortingOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListSortingOperations.kt @@ -1,33 +1,26 @@ package com.tangem.domain.tokens.operations +import arrow.core.Either import arrow.core.NonEmptySet import arrow.core.raise.Raise +import arrow.core.raise.either import arrow.core.raise.ensure import arrow.core.raise.ensureNotNull import arrow.core.toNonEmptySetOrNull -import com.tangem.domain.core.raise.DelegatedRaise import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.NetworkGroup import com.tangem.domain.tokens.model.TokenList import com.tangem.domain.tokens.models.Network -import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import kotlinx.coroutines.withContext import java.math.BigDecimal -internal class TokenListSortingOperations( +internal class TokenListSortingOperations( private val currencies: Set, private val isAnyTokenLoading: Boolean, private val sortByBalance: Boolean, - private val dispatchers: CoroutineDispatcherProvider, - raise: Raise, - transformError: (Error) -> E, -) : DelegatedRaise(raise, transformError) { +) { constructor( tokenList: TokenList, - dispatchers: CoroutineDispatcherProvider, - raise: Raise, - transformError: (Error) -> E, sortByBalance: Boolean = tokenList.sortedBy == TokenList.SortType.BALANCE, isAnyTokenLoading: Boolean = tokenList.totalFiatBalance is TokenList.FiatBalance.Loading, ) : this( @@ -38,39 +31,32 @@ internal class TokenListSortingOperations( }, isAnyTokenLoading = isAnyTokenLoading, sortByBalance = sortByBalance, - dispatchers = dispatchers, - raise = raise, - transformError = transformError, ) - suspend fun getGroupedTokens(networks: Set): NonEmptySet { - return withContext(dispatchers.default) { - ensure(currencies.isNotEmpty()) { Error.EmptyTokens } - val networksNes = ensureNotNull(networks.toNonEmptySetOrNull()) { - Error.EmptyNetworks - } + fun getGroupedTokens(networks: Set): Either> = either { + ensure(currencies.isNotEmpty()) { Error.EmptyTokens } + val networksNes = ensureNotNull(networks.toNonEmptySetOrNull()) { + Error.EmptyNetworks + } - if (sortByBalance) { - groupAndSortTokensByBalance(networksNes) - } else { - groupTokens(networksNes) - } + if (sortByBalance) { + groupAndSortTokensByBalance(networksNes) + } else { + groupTokens(networksNes) } } - suspend fun getTokens(): NonEmptySet { - return withContext(dispatchers.default) { - val tokensNes = ensureNotNull(currencies.toNonEmptySetOrNull()) { - Error.EmptyTokens - } - - if (sortByBalance) sortTokensByBalance(tokensNes) else tokensNes + fun getTokens(): Either> = either { + val tokensNes = ensureNotNull(currencies.toNonEmptySetOrNull()) { + Error.EmptyTokens } + + if (sortByBalance) sortTokensByBalance(tokensNes) else tokensNes } - fun getSortType() = if (sortByBalance) TokenList.SortType.BALANCE else TokenList.SortType.NONE + fun getSortType(): TokenList.SortType = if (sortByBalance) TokenList.SortType.BALANCE else TokenList.SortType.NONE - private fun groupTokens(networks: NonEmptySet): NonEmptySet { + private fun Raise.groupTokens(networks: NonEmptySet): NonEmptySet { val groupedTokens = currencies .groupBy { it.currency.networkId } .map { (networkId, tokens) -> @@ -88,7 +74,7 @@ internal class TokenListSortingOperations( return ensureNotNull(groupedTokens) { Error.EmptyTokens } } - private fun groupAndSortTokensByBalance(networks: NonEmptySet): NonEmptySet { + private fun Raise.groupAndSortTokensByBalance(networks: NonEmptySet): NonEmptySet { val groupsWithSortedTokens = groupTokens(networks) .map { group -> val tokens = group.currencies as? NonEmptySet diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetTokenListUseCaseTest.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetTokenListUseCaseTest.kt index 1d5cfd09c5..e7dfb90fd8 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetTokenListUseCaseTest.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetTokenListUseCaseTest.kt @@ -19,6 +19,7 @@ import com.tangem.domain.tokens.repository.MockQuotesRepository import com.tangem.domain.wallets.models.UserWalletId import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider import junit.framework.TestCase.assertEquals +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.* import kotlinx.coroutines.test.runTest import org.junit.Test @@ -145,7 +146,7 @@ internal class GetTokenListUseCaseTest { tokens = flowOf( MockTokens.tokens.right(), error, - ), + ).map { delay(timeMillis = 1_000); it }, ) // When diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/CryptoCurrencyStatusToTokenItemConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/CryptoCurrencyStatusToTokenItemConverter.kt index 75084dad74..b67250b33b 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/CryptoCurrencyStatusToTokenItemConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/CryptoCurrencyStatusToTokenItemConverter.kt @@ -19,7 +19,7 @@ internal class CryptoCurrencyStatusToTokenItemConverter( private val CryptoCurrencyStatus.networkIconResId: Int? @DrawableRes get() { // TODO: [REDACTED_JIRA] - return if (currency is CryptoCurrency.Token) null else R.drawable.img_eth_22 + return if (currency is CryptoCurrency.Coin) null else R.drawable.img_eth_22 } private val CryptoCurrencyStatus.tokenIconResId: Int diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 06a5bc01d8..790f87d7f0 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -40,7 +40,7 @@ appsflyer = "6.5.1" armadillo = "0.9.0" coil = "2.1.0" compose-shimmer = "1.0.3" -coroutine = "1.5.2" +coroutine = "1.7.2" desugarJdkLibs = "1.1.5" firebase = "26.0.0" googleMaterialComponent = "1.6.1"