Updated on 2026-08-14

This commit is contained in:
Tangem 2023-08-09 13:24:48 +03:00
parent d1a09370c7
commit 3b33d4db0a
24 changed files with 363 additions and 484 deletions

View file

@ -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

View file

@ -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
}
}

View file

@ -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
}

View file

@ -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<List<WalletManager>>,
@ -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
}

View file

@ -18,95 +18,4 @@ fun <T> Collection<T>.isSingleItem(): Boolean = this.size == 1
*/
fun <T> Collection<T>.copy(): Collection<T> {
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 <T> Collection<T>.plusOrReplace(item: T, predicate: (T) -> Boolean): List<T> {
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 <T> MutableCollection<T>.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 <T> MutableCollection<T>.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 <T> MutableList<T>.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 <T> MutableCollection<T>.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
}

View file

@ -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 <T> MutableList<T>.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 <T> MutableList<T>.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 <T> List<T>.addOrReplace(item: T, predicate: (T) -> Boolean): List<T> {
val mutableList = this.toMutableList()
val isReplaced = mutableList.replaceBy(item, predicate)
if (!isReplaced) {
mutableList.add(item)
}
return mutableList
}

View file

@ -36,7 +36,7 @@ internal class DefaultNetworksRepository(
private val responseCurrenciesFactory by lazy { ResponseCurrenciesFactory(DemoConfig()) }
private val networkStatusFactory by lazy { NetworkStatusFactory() }
private val networksStatuses: MutableStateFlow<HashSet<NetworkStatus>> = MutableStateFlow(hashSetOf())
private val networksStatuses: MutableStateFlow<List<NetworkStatus>> = MutableStateFlow(emptyList())
override fun getNetworks(networksIds: Set<Network.ID>): Set<Network> {
return networkConverter.convertSet(networksIds)
@ -48,7 +48,9 @@ internal class DefaultNetworksRepository(
refresh: Boolean,
): Flow<Set<NetworkStatus>> = 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 }
}
}

View file

@ -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<CryptoCurrency.ID>, refresh: Boolean): Flow<Set<Quote>> {
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)
}
}
}

View file

@ -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<CryptoCurrencyAmount>,
currencies: Set<CryptoCurrency>,
): Map<CryptoCurrency.ID, BigDecimal> {
val formattedAmounts = hashMapOf<CryptoCurrency.ID, BigDecimal>()
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()
}
}

View file

@ -1,13 +0,0 @@
package com.tangem.domain.core.raise
import arrow.core.raise.Raise
abstract class DelegatedRaise<Error, OtherError>(
private val otherRaise: Raise<OtherError>,
private val transformError: (Error) -> OtherError,
) : Raise<Error> {
override fun raise(r: Error): Nothing {
otherRaise.raise(transformError(r))
}
}

View file

@ -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<Either<CurrencyError, CryptoCurrencyStatus>> {
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<CurrencyError>.getCurrency(
private suspend fun getCurrency(
userWalletId: UserWalletId,
currencyId: CryptoCurrency.ID,
refresh: Boolean,
): Flow<CryptoCurrencyStatus> {
): Flow<Either<CurrencyError, CryptoCurrencyStatus>> {
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)
}
}
}

View file

@ -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<Either<CurrencyError, CryptoCurrencyStatus>> {
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<CurrencyError>.getCurrency(
private suspend fun getPrimaryCurrency(
userWalletId: UserWalletId,
refresh: Boolean,
): Flow<CryptoCurrencyStatus> {
): Flow<Either<CurrencyError, CryptoCurrencyStatus>> {
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)
}
}
}

View file

@ -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<Either<TokenListError, TokenList>> {
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<TokenListError>.getTokenList(userWalletId: UserWalletId, refresh: Boolean): Flow<TokenList> {
return getTokensStatuses(userWalletId, refresh).flatMapConcat { tokens ->
createTokenList(userWalletId, tokens)
}
}
private fun Raise<TokenListError>.getTokensStatuses(
private fun getTokensStatuses(
userWalletId: UserWalletId,
refresh: Boolean,
): Flow<Set<CryptoCurrencyStatus>> {
): Flow<Either<TokenListError, Set<CryptoCurrencyStatus>>> {
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<TokenListError>.createTokenList(
private fun createTokenList(
userWalletId: UserWalletId,
tokens: Set<CryptoCurrencyStatus>,
): Flow<TokenList> {
): Flow<Either<TokenListError, TokenList>> {
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)
}
}
}

View file

@ -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<TokenListSortingError>.groupTokens(
tokenList: TokenList.Ungrouped,
): TokenList.GroupedByNetwork {
val sortingOperations = getSortingOperations(tokenList)
val tokens = sortingOperations.getTokens()
private fun Raise<TokenListSortingError>.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<TokenListSortingError>.ungroupTokens(
private fun Raise<TokenListSortingError>.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<TokenListSortingError>.getSortingOperations(tokenList: TokenList): TokenListSortingOperations<*> {
return TokenListSortingOperations(
tokenList = tokenList,
dispatchers = dispatchers,
raise = this,
transformError = TokenListSortingOperations.Error::mapToTokenListSortingError,
private fun Raise<TokenListSortingError>.getNetworks(networksIds: Set<Network.ID>): Set<Network> {
return catch(
block = { networksRepository.getNetworks(networksIds) },
catch = { raise(TokenListSortingError.DataError(it)) },
)
}
private suspend fun Raise<TokenListSortingError>.getNetworks(networksIds: Set<Network.ID>): Set<Network> {
return withContext(dispatchers.io) {
catch(
block = { networksRepository.getNetworks(networksIds) },
catch = { raise(TokenListSortingError.DataError(it)) },
)
}
}
}

View file

@ -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<TokenListSortingError>.sortGroupedTokenList(
private fun Raise<TokenListSortingError>.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<TokenListSortingError>.sortUngroupedTokenList(
private fun Raise<TokenListSortingError>.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<TokenListSortingError>.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,
)
}
}

View file

@ -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,

View file

@ -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<E>(
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<E>,
transformError: (Error) -> E,
) : DelegatedRaise<CurrenciesStatusesOperations.Error, E>(raise, transformError) {
) {
constructor(
userWalletId: UserWalletId,
refresh: Boolean,
useCase: GetTokenListUseCase,
raise: Raise<E>,
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<Set<CryptoCurrencyStatus>> {
return getMultiCurrencyWalletCurrencies().flatMapConcat {
val currencies = it.toNonEmptySetOrNull()
@OptIn(ExperimentalCoroutinesApi::class)
fun getCurrenciesStatusesFlow(): Flow<Either<Error, Set<CryptoCurrencyStatus>>> {
return getMultiCurrencyWalletCurrencies().flatMapMerge flatMap@{ maybeCurrencies ->
val nonEmptyCurrencies = maybeCurrencies.fold(
ifLeft = { error ->
return@flatMap flowOf(error.left())
},
ifRight = { it.toNonEmptySetOrNull() },
) ?: return@flatMap flowOf(emptySet<CryptoCurrencyStatus>().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<CryptoCurrencyStatus> {
val currency = getMultiCurrencyWalletCurrency(currencyId)
suspend fun getCurrencyStatusFlow(currencyId: CryptoCurrency.ID): Flow<Either<Error, CryptoCurrencyStatus>> {
val currency = recover(
block = { getMultiCurrencyWalletCurrency(currencyId) },
recover = { return flowOf(it.left()) },
)
return getCurrencyStatusFlow(currency)
}
suspend fun getPrimaryCurrencyStatusFlow(): Flow<CryptoCurrencyStatus> {
val currency = getPrimaryCurrency()
suspend fun getPrimaryCurrencyStatusFlow(): Flow<Either<Error, CryptoCurrencyStatus>> {
val currency = recover(
block = { getPrimaryCurrency() },
recover = { return flowOf(it.left()) },
)
return getCurrencyStatusFlow(currency)
}
private fun getCurrencyStatusFlow(currency: CryptoCurrency): Flow<CryptoCurrencyStatus> {
private fun getCurrencyStatusFlow(currency: CryptoCurrency): Flow<Either<Error, CryptoCurrencyStatus>> {
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<CryptoCurrency>,
private fun createCurrenciesStatuses(
currencies: NonEmptySet<CryptoCurrency>,
quotes: Set<Quote>,
networkStatuses: Set<NetworkStatus>,
): Set<CryptoCurrencyStatus> = withContext(dispatchers.default) {
tokens.mapTo(hashSetOf()) { token ->
): Set<CryptoCurrencyStatus> {
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<E>(
}
}
private suspend fun createStatus(
private fun createStatus(
token: CryptoCurrency,
quote: Quote?,
networkStatus: NetworkStatus?,
@ -118,44 +117,58 @@ internal class CurrenciesStatusesOperations<E>(
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<Set<CryptoCurrency>> {
private fun getMultiCurrencyWalletCurrencies(): Flow<Either<Error, Set<CryptoCurrency>>> {
return currenciesRepository.getMultiCurrencyWalletCurrencies(userWalletId, refresh)
.catch { raise(Error.DataError(it)) }
.onEmpty { raise(Error.EmptyCurrencies) }
.map<Set<CryptoCurrency>, Either<Error, Set<CryptoCurrency>>> { it.right() }
.catch { emit(Error.DataError(it).left()) }
.onEmpty { emit(Error.EmptyCurrencies.left()) }
}
private suspend fun getPrimaryCurrency(): CryptoCurrency {
private suspend fun Raise<Error>.getMultiCurrencyWalletCurrency(currencyId: CryptoCurrency.ID): CryptoCurrency {
return Either.catch { currenciesRepository.getMultiCurrencyWalletCurrency(userWalletId, currencyId) }
.mapLeft { Error.DataError(it) }
.bind()
}
private suspend fun Raise<Error>.getPrimaryCurrency(): CryptoCurrency {
return catch(
block = { currenciesRepository.getSingleCurrencyWalletPrimaryCurrency(userWalletId) },
catch = { raise(Error.DataError(it)) },
)
}
private fun getQuotes(tokensIds: NonEmptySet<CryptoCurrency.ID>): Flow<Set<Quote>> {
private fun getQuotes(tokensIds: NonEmptySet<CryptoCurrency.ID>): Flow<Either<Error, Set<Quote>>> {
return quotesRepository.getQuotes(tokensIds, refresh)
.catch { raise(Error.DataError(it)) }
.onEmpty { raise(Error.EmptyQuotes) }
.map<Set<Quote>, Either<Error, Set<Quote>>> { it.right() }
.catch { emit(Error.DataError(it).left()) }
.onEmpty { emit(Error.EmptyQuotes.left()) }
}
private fun getNetworksStatues(networks: NonEmptySet<Network.ID>): Flow<Set<NetworkStatus>> {
private fun getNetworksStatuses(networks: NonEmptySet<Network.ID>): Flow<Either<Error, Set<NetworkStatus>>> {
return networksRepository.getNetworkStatuses(userWalletId, networks, refresh)
.catch { raise(Error.DataError(it)) }
.onEmpty { raise(Error.EmptyNetworksStatuses) }
.map<Set<NetworkStatus>, Either<Error, Set<NetworkStatus>>> { it.right() }
.catch { emit(Error.DataError(it).left()) }
.onEmpty { emit(Error.EmptyNetworksStatuses.left()) }
}
private fun getIds(
currencies: NonEmptySet<CryptoCurrency>,
): Pair<NonEmptySet<Network.ID>, NonEmptySet<CryptoCurrency.ID>> {
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 {

View file

@ -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<OtherError>(
internal class CurrencyStatusOperations(
private val currency: CryptoCurrency,
private val quote: Quote?,
private val networkStatus: NetworkStatus?,
private val dispatchers: CoroutineDispatcherProvider,
raise: Raise<OtherError>,
transformError: (Error) -> OtherError,
) : DelegatedRaise<CurrencyStatusOperations.Error, OtherError>(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<OtherError>(
}
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<OtherError>(
private fun calculateFiatAmount(amount: BigDecimal, fiatRate: BigDecimal): BigDecimal {
return amount * fiatRate
}
sealed class Error {
data class UnableToFindAmount(val currencyId: CryptoCurrency.ID) : Error()
}
}

View file

@ -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<CryptoCurrencyStatus>,
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(

View file

@ -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<E>(
internal class TokenListOperations(
private val currenciesRepository: CurrenciesRepository,
private val networksRepository: NetworksRepository,
private val userWalletId: UserWalletId,
private val tokens: Set<CryptoCurrencyStatus>,
private val dispatchers: CoroutineDispatcherProvider,
raise: Raise<E>,
transform: (Error) -> E,
) : DelegatedRaise<TokenListOperations.Error, E>(raise, transform) {
) {
constructor(
userWalletId: UserWalletId,
tokens: Set<CryptoCurrencyStatus>,
useCase: GetTokenListUseCase,
raise: Raise<E>,
transform: (Error) -> E,
) : this(
currenciesRepository = useCase.currenciesRepository,
networksRepository = useCase.networksRepository,
userWalletId = userWalletId,
tokens = tokens,
dispatchers = useCase.dispatchers,
raise = raise,
transform = transform,
)
fun getTokenListFlow(): Flow<TokenList> {
return combine(getIsGrouped(), getIsSortedByBalance()) { isGrouped, isSortedByBalance ->
createTokenList(isGrouped, isSortedByBalance)
fun getTokenListFlow(): Flow<Either<Error, TokenList>> {
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<Error>.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<Error>.createTokenList(
tokens: NonEmptySet<CryptoCurrencyStatus>,
fiatBalance: TokenList.FiatBalance,
isAnyTokenLoading: Boolean,
@ -79,19 +68,14 @@ internal class TokenListOperations<E>(
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<Error>.createTokenList(
tokens: NonEmptySet<CryptoCurrencyStatus>,
sortingOperations: TokenListSortingOperations<*>,
sortingOperations: TokenListSortingOperations,
fiatBalance: TokenList.FiatBalance,
isGrouped: Boolean,
): TokenList {
@ -108,37 +92,46 @@ internal class TokenListOperations<E>(
}
}
private suspend fun getNetworks(tokensNes: NonEmptySet<CryptoCurrencyStatus>): Set<Network> {
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<Error>.getNetworks(tokensNes: NonEmptySet<CryptoCurrencyStatus>): Set<Network> {
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<Error>.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<Error>.createGroupedTokenList(
sortingOperations: TokenListSortingOperations,
fiatBalance: TokenList.FiatBalance,
networks: NonEmptySet<Network>,
): 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<CryptoCurrencyStatus>,
tokens: Set<CryptoCurrencyStatus>,
fiatBalance: TokenList.FiatBalance,
): TokenList.Ungrouped {
return TokenList.Ungrouped(
@ -148,18 +141,18 @@ internal class TokenListOperations<E>(
)
}
private fun getIsGrouped(): Flow<Boolean> {
private fun getIsGrouped(): Flow<Either<Error, Boolean>> {
return currenciesRepository.isTokensGrouped(userWalletId)
.catch { raise(Error.DataError(it)) }
.onEmpty { emit(value = false) }
.flowOn(dispatchers.io)
.map<Boolean, Either<Error, Boolean>> { it.right() }
.catch { emit(Error.DataError(it).left()) }
.onEmpty { emit(value = false.right()) }
}
private fun getIsSortedByBalance(): Flow<Boolean> {
private fun getIsSortedByBalance(): Flow<Either<Error, Boolean>> {
return currenciesRepository.isTokensSortedByBalance(userWalletId)
.catch { raise(Error.DataError(it)) }
.onEmpty { emit(value = false) }
.flowOn(dispatchers.io)
.map<Boolean, Either<Error, Boolean>> { it.right() }
.catch { emit(Error.DataError(it).left()) }
.onEmpty { emit(value = false.right()) }
}
sealed class Error {

View file

@ -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<E>(
internal class TokenListSortingOperations(
private val currencies: Set<CryptoCurrencyStatus>,
private val isAnyTokenLoading: Boolean,
private val sortByBalance: Boolean,
private val dispatchers: CoroutineDispatcherProvider,
raise: Raise<E>,
transformError: (Error) -> E,
) : DelegatedRaise<TokenListSortingOperations.Error, E>(raise, transformError) {
) {
constructor(
tokenList: TokenList,
dispatchers: CoroutineDispatcherProvider,
raise: Raise<E>,
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<E>(
},
isAnyTokenLoading = isAnyTokenLoading,
sortByBalance = sortByBalance,
dispatchers = dispatchers,
raise = raise,
transformError = transformError,
)
suspend fun getGroupedTokens(networks: Set<Network>): NonEmptySet<NetworkGroup> {
return withContext(dispatchers.default) {
ensure(currencies.isNotEmpty()) { Error.EmptyTokens }
val networksNes = ensureNotNull(networks.toNonEmptySetOrNull()) {
Error.EmptyNetworks
}
fun getGroupedTokens(networks: Set<Network>): Either<Error, NonEmptySet<NetworkGroup>> = 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<CryptoCurrencyStatus> {
return withContext(dispatchers.default) {
val tokensNes = ensureNotNull(currencies.toNonEmptySetOrNull()) {
Error.EmptyTokens
}
if (sortByBalance) sortTokensByBalance(tokensNes) else tokensNes
fun getTokens(): Either<Error, NonEmptySet<CryptoCurrencyStatus>> = 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<Network>): NonEmptySet<NetworkGroup> {
private fun Raise<Error>.groupTokens(networks: NonEmptySet<Network>): NonEmptySet<NetworkGroup> {
val groupedTokens = currencies
.groupBy { it.currency.networkId }
.map { (networkId, tokens) ->
@ -88,7 +74,7 @@ internal class TokenListSortingOperations<E>(
return ensureNotNull(groupedTokens) { Error.EmptyTokens }
}
private fun groupAndSortTokensByBalance(networks: NonEmptySet<Network>): NonEmptySet<NetworkGroup> {
private fun Raise<Error>.groupAndSortTokensByBalance(networks: NonEmptySet<Network>): NonEmptySet<NetworkGroup> {
val groupsWithSortedTokens = groupTokens(networks)
.map { group ->
val tokens = group.currencies as? NonEmptySet<CryptoCurrencyStatus>

View file

@ -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

View file

@ -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

View file

@ -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"