Updated on 2026-08-14

This commit is contained in:
Tangem 2023-07-15 01:47:52 +03:00
parent 66aedc97cb
commit 5f1a5494dc
24 changed files with 525 additions and 353 deletions

View file

@ -5,6 +5,7 @@ plugins {
}
dependencies {
implementation(projects.domain.core)
implementation(projects.domain.tokens)
implementation(projects.domain.wallets.models)

View file

@ -1,9 +1,6 @@
package com.tangem.data.tokens.repository
import arrow.core.Either
import arrow.core.right
import com.tangem.data.tokens.mock.MockNetworks
import com.tangem.domain.tokens.error.TokensError
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.tokens.model.NetworkStatus
import com.tangem.domain.tokens.model.Token
@ -14,23 +11,21 @@ import kotlinx.coroutines.flow.flowOf
internal class MockNetworksRepository : NetworksRepository {
override fun getNetworks(networksIds: Set<Network.ID>): Either<TokensError, Set<Network>> {
override fun getNetworks(networksIds: Set<Network.ID>): Set<Network> {
return MockNetworks.networks
.filter { it.id in networksIds }
.toSet()
.right()
}
override fun getNetworkStatuses(
userWalletId: UserWalletId,
networks: Map<Network.ID, Set<Token.ID>>,
refresh: Boolean,
): Flow<Either<TokensError, Set<NetworkStatus>>> {
): Flow<Set<NetworkStatus>> {
return flowOf(
MockNetworks.networksStatuses
.filter { it.networkId in networks.keys }
.toSet()
.right(),
.toSet(),
)
}
}

View file

@ -1,9 +1,6 @@
package com.tangem.data.tokens.repository
import arrow.core.Either
import arrow.core.right
import com.tangem.data.tokens.mock.MockQuotes
import com.tangem.domain.tokens.error.TokensError
import com.tangem.domain.tokens.model.Quote
import com.tangem.domain.tokens.model.Token
import com.tangem.domain.tokens.repository.QuotesRepository
@ -12,12 +9,11 @@ import kotlinx.coroutines.flow.flowOf
internal class MockQuotesRepository : QuotesRepository {
override fun getQuotes(tokensIds: Set<Token.ID>, refresh: Boolean): Flow<Either<TokensError, Set<Quote>>> {
override fun getQuotes(tokensIds: Set<Token.ID>, refresh: Boolean): Flow<Set<Quote>> {
return flowOf(
MockQuotes.quotes
.filter { it.tokenId in tokensIds }
.toSet()
.right(),
.toSet(),
)
}
}

View file

@ -1,10 +1,6 @@
package com.tangem.data.tokens.repository
import arrow.core.Either
import arrow.core.left
import arrow.core.right
import com.tangem.data.tokens.mock.MockTokens
import com.tangem.domain.tokens.error.TokensError
import com.tangem.domain.tokens.model.Token
import com.tangem.domain.tokens.repository.TokensRepository
import com.tangem.domain.wallets.models.UserWalletId
@ -18,17 +14,17 @@ internal class MockTokensRepository : TokensRepository {
sortedTokensIds: Set<Token.ID>,
isGrouped: Boolean,
isSortedByBalance: Boolean,
): Either<TokensError, Unit> = Unit.right()
) = Unit
override fun getTokens(userWalletId: UserWalletId, refresh: Boolean): Flow<Either<TokensError, Set<Token>>> {
return flowOf(value = MockTokens.tokens[userWalletId]?.right() ?: TokensError.EmptyTokens.left())
override fun getTokens(userWalletId: UserWalletId, refresh: Boolean): Flow<Set<Token>> {
return flowOf(value = MockTokens.tokens[userWalletId]!!)
}
override fun isTokensGrouped(userWalletId: UserWalletId): Flow<Either<TokensError, Boolean>> {
return flowOf(MockTokens.isGrouped[userWalletId]?.right() ?: TokensError.EmptyTokens.left())
override fun isTokensGrouped(userWalletId: UserWalletId): Flow<Boolean> {
return flowOf(value = MockTokens.isGrouped[userWalletId]!!)
}
override fun isTokensSortedByBalance(userWalletId: UserWalletId): Flow<Either<TokensError, Boolean>> {
return flowOf(MockTokens.isSortedByBalance[userWalletId]?.right() ?: TokensError.EmptyTokens.left())
override fun isTokensSortedByBalance(userWalletId: UserWalletId): Flow<Boolean> {
return flowOf(value = MockTokens.isSortedByBalance[userWalletId]!!)
}
}

View file

@ -0,0 +1,9 @@
package com.tangem.domain.core.error
sealed class DataError : Exception() {
sealed class NetworkError : DataError() {
object NoInternetConnection : NetworkError()
}
}

View file

@ -0,0 +1,13 @@
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

@ -5,15 +5,14 @@ import arrow.core.left
import arrow.core.raise.Raise
import arrow.core.raise.recover
import arrow.core.right
import com.tangem.domain.tokens.error.TokensError
import com.tangem.domain.tokens.error.TokenListError
import com.tangem.domain.tokens.model.TokenList
import com.tangem.domain.tokens.model.TokenStatus
import com.tangem.domain.tokens.operations.TokenListOperations
import com.tangem.domain.tokens.operations.TokensStatusesOperations
import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.tokens.repository.QuotesRepository
import com.tangem.domain.tokens.repository.TokensRepository
import com.tangem.domain.tokens.utils.TokenListFiatBalanceOperations
import com.tangem.domain.tokens.utils.TokenListOperations
import com.tangem.domain.tokens.utils.TokensStatusesOperations
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.Flow
@ -21,13 +20,13 @@ import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.flatMapConcat
class GetTokenListUseCase(
private val tokensRepository: TokensRepository,
private val quotesRepository: QuotesRepository,
private val networksRepository: NetworksRepository,
private val dispatchers: CoroutineDispatcherProvider,
internal val tokensRepository: TokensRepository,
internal val quotesRepository: QuotesRepository,
internal val networksRepository: NetworksRepository,
internal val dispatchers: CoroutineDispatcherProvider,
) {
operator fun invoke(userWalletId: UserWalletId, refresh: Boolean = true): Flow<Either<TokensError, TokenList>> {
operator fun invoke(userWalletId: UserWalletId, refresh: Boolean = true): Flow<Either<TokenListError, TokenList>> {
return channelFlow {
recover(
block = {
@ -41,53 +40,39 @@ class GetTokenListUseCase(
)
}
}
private fun Raise<TokensError>.getTokenList(userWalletId: UserWalletId, refresh: Boolean): Flow<TokenList> {
private fun Raise<TokenListError>.getTokenList(userWalletId: UserWalletId, refresh: Boolean): Flow<TokenList> {
return getTokensStatuses(userWalletId, refresh).flatMapConcat { tokens ->
createTokenList(userWalletId, tokens)
}
}
private fun Raise<TokensError>.getTokensStatuses(
private fun Raise<TokenListError>.getTokensStatuses(
userWalletId: UserWalletId,
refresh: Boolean,
): Flow<Set<TokenStatus>> {
val operations = TokensStatusesOperations(
tokensRepository,
quotesRepository,
networksRepository,
userWalletId,
refresh,
dispatchers,
userWalletId = userWalletId,
refresh = refresh,
useCase = this@GetTokenListUseCase,
raise = this,
transformError = TokenListError::fromTokenStatusesOperations,
)
return operations.getTokensStatusesFlow()
}
private suspend fun Raise<TokensError>.createTokenList(
private fun Raise<TokenListError>.createTokenList(
userWalletId: UserWalletId,
tokens: Set<TokenStatus>,
): Flow<TokenList> {
val isAnyTokenLoading = tokens.any { it.value is TokenStatus.Loading }
val operations = TokenListOperations(
tokensRepository,
networksRepository,
userWalletId,
calculateFiatBalance(tokens, isAnyTokenLoading),
isAnyTokenLoading,
dispatchers,
userWalletId = userWalletId,
tokens = tokens,
useCase = this@GetTokenListUseCase,
raise = this,
transform = TokenListError::fromTokenListOperations,
)
return operations.getTokenListFlow(tokens)
}
private suspend fun calculateFiatBalance(
tokens: Set<TokenStatus>,
isAnyTokenLoading: Boolean,
): TokenList.FiatBalance {
val operations = TokenListFiatBalanceOperations(tokens, isAnyTokenLoading, dispatchers)
return operations.calculateFiatBalance()
return operations.getTokenListFlow()
}
}

View file

@ -0,0 +1,31 @@
package com.tangem.domain.tokens.error
import com.tangem.domain.tokens.model.TokenList
import com.tangem.domain.tokens.operations.TokenListOperations
import com.tangem.domain.tokens.operations.TokensStatusesOperations
sealed class TokenListError {
object EmptyTokens : TokenListError()
data class UnableToSortTokenList(val unsortedTokenList: TokenList.Ungrouped) : TokenListError()
data class DataError(val cause: Throwable) : TokenListError()
internal companion object {
fun fromTokenStatusesOperations(error: TokensStatusesOperations.Error): TokenListError = when (error) {
is TokensStatusesOperations.Error.DataError -> DataError(error.cause)
is TokensStatusesOperations.Error.EmptyNetworksStatuses,
is TokensStatusesOperations.Error.EmptyQuotes,
is TokensStatusesOperations.Error.EmptyTokens,
-> EmptyTokens
}
fun fromTokenListOperations(error: TokenListOperations.Error): TokenListError = when (error) {
is TokenListOperations.Error.DataError -> DataError(error.cause)
is TokenListOperations.Error.UnableToSortTokenList -> UnableToSortTokenList(error.unsortedTokenList)
is TokenListOperations.Error.UnableToGroupTokenList -> UnableToSortTokenList(error.ungroupedTokenList)
}
}
}

View file

@ -1,21 +0,0 @@
package com.tangem.domain.tokens.error
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.tokens.model.Token
sealed class TokensError {
object EmptyTokens : TokensError()
object EmptyQuotes : TokensError()
object EmptyNetworks : TokensError()
object EmptyNetworkStatues : TokensError()
data class TokenAmountNotFound(val tokenId: Token.ID) : TokensError()
data class TokenFiatAmountLessThenZero(val tokenId: Token.ID) : TokensError()
data class NetworkNotFound(val networkId: Network.ID) : TokensError()
}

View file

@ -1,5 +1,6 @@
package com.tangem.domain.tokens.utils
package com.tangem.domain.tokens.operations
import arrow.core.NonEmptySet
import com.tangem.domain.tokens.model.TokenList
import com.tangem.domain.tokens.model.TokenStatus
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
@ -7,7 +8,7 @@ import kotlinx.coroutines.withContext
import java.math.BigDecimal
internal class TokenListFiatBalanceOperations(
private val tokens: Set<TokenStatus>,
private val tokens: NonEmptySet<TokenStatus>,
private val isAnyTokenLoading: Boolean,
private val dispatcher: CoroutineDispatcherProvider,
) {
@ -15,7 +16,7 @@ internal class TokenListFiatBalanceOperations(
suspend fun calculateFiatBalance(): TokenList.FiatBalance {
return withContext(dispatcher.single) {
var fiatBalance: TokenList.FiatBalance = TokenList.FiatBalance.Loading
if (tokens.isEmpty() || isAnyTokenLoading) return@withContext fiatBalance
if (isAnyTokenLoading) return@withContext fiatBalance
for (token in tokens) {
when (val status = token.value) {

View file

@ -0,0 +1,188 @@
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 com.tangem.domain.tokens.GetTokenListUseCase
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.tokens.model.TokenList
import com.tangem.domain.tokens.model.TokenStatus
import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.tokens.repository.TokensRepository
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>(
private val tokensRepository: TokensRepository,
private val networksRepository: NetworksRepository,
private val userWalletId: UserWalletId,
private val tokens: Set<TokenStatus>,
private val dispatchers: CoroutineDispatcherProvider,
raise: Raise<E>,
transform: (Error) -> E,
) : DelegatedRaise<TokenListOperations.Error, E>(raise, transform) {
constructor(
userWalletId: UserWalletId,
tokens: Set<TokenStatus>,
useCase: GetTokenListUseCase,
raise: Raise<E>,
transform: (Error) -> E,
) : this(
tokensRepository = useCase.tokensRepository,
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)
}
}
private suspend fun createTokenList(isGrouped: Boolean, isSortedByBalance: Boolean): TokenList {
return withContext(dispatchers.single) {
val tokensNes = tokens.toNonEmptySetOrNull()
?: return@withContext TokenList.NotInitialized
val isAnyTokenLoading = tokensNes.any { it.value is TokenStatus.Loading }
val fiatBalanceOperations = TokenListFiatBalanceOperations(tokensNes, isAnyTokenLoading, dispatchers)
createTokenList(
tokens = tokensNes,
fiatBalance = fiatBalanceOperations.calculateFiatBalance(),
isAnyTokenLoading = isAnyTokenLoading,
isGrouped = isGrouped,
isSortedByBalance = isSortedByBalance,
)
}
}
private suspend fun createTokenList(
tokens: NonEmptySet<TokenStatus>,
fiatBalance: TokenList.FiatBalance,
isAnyTokenLoading: Boolean,
isGrouped: Boolean,
isSortedByBalance: Boolean,
): TokenList {
val sortingOperations = TokenListSortingOperations(
tokens = tokens,
isAnyTokenLoading = isAnyTokenLoading,
isSortedByBalance = isSortedByBalance,
dispatchers = dispatchers,
raise = this,
transformError = { e ->
Error.fromTokenListOperations(e) { createUnsortedUngroupedTokenList(tokens, fiatBalance) }
},
)
return createTokenList(tokens, sortingOperations, fiatBalance, isGrouped)
}
private suspend fun createTokenList(
tokens: NonEmptySet<TokenStatus>,
sortingOperations: TokenListSortingOperations<*>,
fiatBalance: TokenList.FiatBalance,
isGrouped: Boolean,
): TokenList {
return if (isGrouped) {
val networks = ensureNotNull(getNetworks(tokens).toNonEmptySetOrNull()) {
Error.UnableToGroupTokenList(
ungroupedTokenList = createUngroupedTokenList(sortingOperations, fiatBalance),
)
}
createGroupedTokenList(sortingOperations, fiatBalance, networks)
} else {
createUngroupedTokenList(sortingOperations, fiatBalance)
}
}
private suspend fun getNetworks(tokensNes: NonEmptySet<TokenStatus>): Set<Network> {
return withContext(dispatchers.io) {
val networksIds = tokensNes.map { it.networkId }.toNonEmptySet()
catch(
block = { networksRepository.getNetworks(networksIds) },
catch = { raise(Error.DataError(it)) },
)
}
}
private suspend fun createUngroupedTokenList(
sortingOperations: TokenListSortingOperations<*>,
fiatBalance: TokenList.FiatBalance,
): TokenList.Ungrouped = TokenList.Ungrouped(
sortedBy = sortingOperations.getSortType(),
totalFiatBalance = fiatBalance,
tokens = sortingOperations.getTokens(),
)
private suspend fun createGroupedTokenList(
sortingOperations: TokenListSortingOperations<*>,
fiatBalance: TokenList.FiatBalance,
networks: NonEmptySet<Network>,
): TokenList.GroupedByNetwork = TokenList.GroupedByNetwork(
sortedBy = sortingOperations.getSortType(),
totalFiatBalance = fiatBalance,
groups = sortingOperations.getGroupedTokens(networks),
)
private fun createUnsortedUngroupedTokenList(
tokens: NonEmptySet<TokenStatus>,
fiatBalance: TokenList.FiatBalance,
): TokenList.Ungrouped {
return TokenList.Ungrouped(
sortedBy = TokenList.SortType.NONE,
totalFiatBalance = fiatBalance,
tokens = tokens,
)
}
private fun getIsGrouped(): Flow<Boolean> {
return tokensRepository.isTokensGrouped(userWalletId)
.catch { raise(Error.DataError(it)) }
.onEmpty { emit(value = false) }
.flowOn(dispatchers.io)
}
private fun getIsSortedByBalance(): Flow<Boolean> {
return tokensRepository.isTokensSortedByBalance(userWalletId)
.catch { raise(Error.DataError(it)) }
.onEmpty { emit(value = false) }
.flowOn(dispatchers.io)
}
sealed class Error {
data class UnableToSortTokenList(val unsortedTokenList: TokenList.Ungrouped) : Error()
data class UnableToGroupTokenList(val ungroupedTokenList: TokenList.Ungrouped) : Error()
data class DataError(val cause: Throwable) : Error()
internal companion object {
fun fromTokenListOperations(
e: TokenListSortingOperations.Error,
createUnsortedUngroupedTokenList: () -> TokenList.Ungrouped,
): Error = when (e) {
is TokenListSortingOperations.Error.EmptyNetworks,
is TokenListSortingOperations.Error.EmptyTokens,
is TokenListSortingOperations.Error.NetworkNotFound,
-> UnableToSortTokenList(
unsortedTokenList = createUnsortedUngroupedTokenList(),
)
}
}
}
}

View file

@ -0,0 +1,115 @@
package com.tangem.domain.tokens.operations
import arrow.core.NonEmptySet
import arrow.core.raise.Raise
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.Network
import com.tangem.domain.tokens.model.NetworkGroup
import com.tangem.domain.tokens.model.TokenList
import com.tangem.domain.tokens.model.TokenStatus
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.withContext
import java.math.BigDecimal
internal class TokenListSortingOperations<E>(
private val tokens: Set<TokenStatus>,
private val isAnyTokenLoading: Boolean,
private val isSortedByBalance: Boolean,
private val dispatchers: CoroutineDispatcherProvider,
raise: Raise<E>,
transformError: (Error) -> E,
) : DelegatedRaise<TokenListSortingOperations.Error, E>(raise, transformError) {
suspend fun getGroupedTokens(networks: Set<Network>): NonEmptySet<NetworkGroup> {
return withContext(dispatchers.single) {
ensure(tokens.isNotEmpty()) { Error.EmptyTokens }
val networksNes = ensureNotNull(networks.toNonEmptySetOrNull()) {
Error.EmptyNetworks
}
if (isSortedByBalance) {
groupAndSortTokensByBalance(networksNes)
} else {
groupTokens(networksNes)
}
}
}
suspend fun getTokens(): NonEmptySet<TokenStatus> {
return withContext(dispatchers.single) {
val tokensNes = ensureNotNull(tokens.toNonEmptySetOrNull()) {
Error.EmptyTokens
}
if (isSortedByBalance) sortTokensByBalance(tokensNes) else tokensNes
}
}
fun getSortType() = if (isSortedByBalance) TokenList.SortType.BALANCE else TokenList.SortType.NONE
private fun groupTokens(networks: NonEmptySet<Network>): NonEmptySet<NetworkGroup> {
val groupedTokens = tokens
.groupBy { it.networkId }
.map { (networkId, tokens) ->
val network = ensureNotNull(networks.firstOrNull { it.id == networkId }) {
Error.NetworkNotFound(networkId)
}
NetworkGroup(
networkId = network.id,
name = network.name,
tokens = ensureNotNull(tokens.toNonEmptySetOrNull()) { Error.EmptyTokens },
)
}
.toNonEmptySetOrNull()
return ensureNotNull(groupedTokens) { Error.EmptyTokens }
}
private fun groupAndSortTokensByBalance(networks: NonEmptySet<Network>): NonEmptySet<NetworkGroup> {
val groupsWithSortedTokens = groupTokens(networks)
.map { group ->
val tokens = group.tokens as? NonEmptySet<TokenStatus>
?: error("Tokens can not be empty here")
group.copy(tokens = sortTokensByBalance(tokens))
}
.toNonEmptySet()
return if (isAnyTokenLoading) {
groupsWithSortedTokens
} else {
sortGroupsByBalance(groupsWithSortedTokens)
}
}
private fun sortTokensByBalance(tokens: NonEmptySet<TokenStatus>): NonEmptySet<TokenStatus> {
return if (isAnyTokenLoading) {
tokens
} else {
tokens.sortedByDescending { it.value.fiatAmount ?: BigDecimal.ZERO }
.toNonEmptySetOrNull()
?: error("Tokens can not be empty here")
}
}
private fun sortGroupsByBalance(groupsWithSortedTokens: NonEmptySet<NetworkGroup>): NonEmptySet<NetworkGroup> {
return groupsWithSortedTokens
.sortedByDescending { group ->
group.tokens.sumOf { it.value.fiatAmount ?: BigDecimal.ZERO }
}
.toNonEmptySetOrNull()
?: error("Tokens can not be empty here")
}
sealed class Error {
object EmptyTokens : Error()
object EmptyNetworks : Error()
data class NetworkNotFound(val networkId: Network.ID) : Error()
}
}

View file

@ -1,9 +1,5 @@
package com.tangem.domain.tokens.utils
package com.tangem.domain.tokens.operations
import arrow.core.raise.Raise
import arrow.core.raise.ensure
import arrow.core.raise.ensureNotNull
import com.tangem.domain.tokens.error.TokensError
import com.tangem.domain.tokens.model.NetworkStatus
import com.tangem.domain.tokens.model.Quote
import com.tangem.domain.tokens.model.Token
@ -17,8 +13,7 @@ internal class TokenStatusOperations(
private val quote: Quote?,
private val networkStatus: NetworkStatus?,
private val dispatchers: CoroutineDispatcherProvider,
raise: Raise<TokensError>,
) : Raise<TokensError> by raise {
) {
suspend fun createTokenStatus(): TokenStatus = withContext(dispatchers.single) {
TokenStatus(
@ -68,7 +63,7 @@ internal class TokenStatusOperations(
private fun getTokenAmount(): BigDecimal {
val amount = networkStatus?.value?.amounts?.get(token.id)
return ensureNotNull(amount) { TokensError.TokenAmountNotFound(token.id) }
return amount ?: error("Incorrect network status: $networkStatus")
}
private fun calculateFiatAmountOrNull(amount: BigDecimal, fiatRate: BigDecimal?): BigDecimal? {
@ -78,9 +73,6 @@ internal class TokenStatusOperations(
}
private fun calculateFiatAmount(amount: BigDecimal, fiatRate: BigDecimal): BigDecimal {
val fiatAmount = amount * fiatRate
ensure(condition = fiatAmount >= BigDecimal.ZERO) { TokensError.TokenFiatAmountLessThenZero(token.id) }
return fiatAmount
return amount * fiatRate
}
}

View file

@ -1,9 +1,10 @@
package com.tangem.domain.tokens.utils
package com.tangem.domain.tokens.operations
import arrow.core.NonEmptySet
import arrow.core.raise.Raise
import arrow.core.toNonEmptySetOrNull
import com.tangem.domain.tokens.error.TokensError
import com.tangem.domain.core.raise.DelegatedRaise
import com.tangem.domain.tokens.GetTokenListUseCase
import com.tangem.domain.tokens.model.*
import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.tokens.repository.QuotesRepository
@ -14,15 +15,33 @@ import kotlinx.coroutines.flow.*
import kotlinx.coroutines.withContext
@Suppress("LongParameterList")
internal class TokensStatusesOperations(
internal class TokensStatusesOperations<E>(
private val tokensRepository: TokensRepository,
private val quotesRepository: QuotesRepository,
private val networksRepository: NetworksRepository,
private val userWalletId: UserWalletId,
private val refresh: Boolean,
private val dispatchers: CoroutineDispatcherProvider,
raise: Raise<TokensError>,
) : Raise<TokensError> by raise {
raise: Raise<E>,
transformError: (Error) -> E,
) : DelegatedRaise<TokensStatusesOperations.Error, E>(raise, transformError) {
constructor(
userWalletId: UserWalletId,
refresh: Boolean,
useCase: GetTokenListUseCase,
raise: Raise<E>,
transformError: (Error) -> E,
) : this(
tokensRepository = useCase.tokensRepository,
quotesRepository = useCase.quotesRepository,
networksRepository = useCase.networksRepository,
userWalletId = userWalletId,
refresh = refresh,
dispatchers = useCase.dispatchers,
raise = raise,
transformError = transformError,
)
fun getTokensStatusesFlow(): Flow<Set<TokenStatus>> {
return getTokens().flatMapConcat {
@ -46,37 +65,43 @@ internal class TokensStatusesOperations(
quotes: Set<Quote>,
networkStatuses: Set<NetworkStatus>,
): Set<TokenStatus> = withContext(dispatchers.single) {
tokens.map { token ->
tokens.mapTo(hashSetOf()) { token ->
val quote = quotes.firstOrNull { it.tokenId == token.id }
val networkStatus = networkStatuses.firstOrNull { it.networkId == token.networkId }
createStatus(token, quote, networkStatus)
}.toSet()
}
}
private suspend fun createStatus(token: Token, quote: Quote?, networkStatus: NetworkStatus?): TokenStatus {
val operations = TokenStatusOperations(token, quote, networkStatus, dispatchers, raise = this)
return operations.createTokenStatus()
val tokenStatusOperations = TokenStatusOperations(
token = token,
quote = quote,
networkStatus = networkStatus,
dispatchers = dispatchers,
)
return tokenStatusOperations.createTokenStatus()
}
private fun getTokens(): Flow<Set<Token>> {
return tokensRepository.getTokens(userWalletId, refresh)
.onEmpty { raise(TokensError.EmptyTokens) }
.map { it.bind() }
.catch { raise(Error.DataError(it)) }
.onEmpty { raise(Error.EmptyTokens) }
.flowOn(dispatchers.io)
}
private fun getQuotes(tokensIds: NonEmptySet<Token.ID>): Flow<Set<Quote>> {
return quotesRepository.getQuotes(tokensIds, refresh)
.onEmpty { raise(TokensError.EmptyQuotes) }
.map { it.bind() }
.catch { raise(Error.DataError(it)) }
.onEmpty { raise(Error.EmptyQuotes) }
.flowOn(dispatchers.io)
}
private fun getNetworksStatues(groupedTokens: Map<Network.ID, NonEmptySet<Token.ID>>): Flow<Set<NetworkStatus>> {
return networksRepository.getNetworkStatuses(userWalletId, groupedTokens, refresh)
.onEmpty { raise(TokensError.EmptyNetworkStatues) }
.map { it.bind() }
.catch { raise(Error.DataError(it)) }
.onEmpty { raise(Error.EmptyNetworksStatuses) }
.flowOn(dispatchers.io)
}
@ -85,10 +110,21 @@ internal class TokensStatusesOperations(
tokens
.groupBy { it.networkId }
.mapValues { (_, tokens) ->
tokens.toNonEmptySetOrNull()
?.map { it.id }
?.toNonEmptySet()
?: raise(TokensError.EmptyTokens)
// Can not be empty
tokens.toNonEmptySetOrNull()!!
.map { it.id }
.toNonEmptySet()
}
}
sealed class Error {
object EmptyTokens : Error()
object EmptyQuotes : Error()
object EmptyNetworksStatuses : Error()
data class DataError(val cause: Throwable) : Error()
}
}

View file

@ -1,7 +1,5 @@
package com.tangem.domain.tokens.repository
import arrow.core.Either
import com.tangem.domain.tokens.error.TokensError
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.tokens.model.NetworkStatus
import com.tangem.domain.tokens.model.Token
@ -16,11 +14,11 @@ import kotlinx.coroutines.flow.Flow
* */
interface NetworksRepository {
fun getNetworks(networksIds: Set<Network.ID>): Either<TokensError, Set<Network>>
fun getNetworks(networksIds: Set<Network.ID>): Set<Network>
fun getNetworkStatuses(
userWalletId: UserWalletId,
networks: Map<Network.ID, Set<Token.ID>>,
refresh: Boolean,
): Flow<Either<TokensError, Set<NetworkStatus>>>
): Flow<Set<NetworkStatus>>
}

View file

@ -1,7 +1,5 @@
package com.tangem.domain.tokens.repository
import arrow.core.Either
import com.tangem.domain.tokens.error.TokensError
import com.tangem.domain.tokens.model.Quote
import com.tangem.domain.tokens.model.Token
import kotlinx.coroutines.flow.Flow
@ -14,5 +12,5 @@ import kotlinx.coroutines.flow.Flow
* */
interface QuotesRepository {
fun getQuotes(tokensIds: Set<Token.ID>, refresh: Boolean): Flow<Either<TokensError, Set<Quote>>>
fun getQuotes(tokensIds: Set<Token.ID>, refresh: Boolean): Flow<Set<Quote>>
}

View file

@ -1,7 +1,5 @@
package com.tangem.domain.tokens.repository
import arrow.core.Either
import com.tangem.domain.tokens.error.TokensError
import com.tangem.domain.tokens.model.Token
import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.flow.Flow
@ -19,11 +17,11 @@ interface TokensRepository {
sortedTokensIds: Set<Token.ID>,
isGrouped: Boolean,
isSortedByBalance: Boolean,
): Either<TokensError, Unit>
)
fun getTokens(userWalletId: UserWalletId, refresh: Boolean): Flow<Either<TokensError, Set<Token>>>
fun getTokens(userWalletId: UserWalletId, refresh: Boolean): Flow<Set<Token>>
fun isTokensGrouped(userWalletId: UserWalletId): Flow<Either<TokensError, Boolean>>
fun isTokensGrouped(userWalletId: UserWalletId): Flow<Boolean>
fun isTokensSortedByBalance(userWalletId: UserWalletId): Flow<Either<TokensError, Boolean>>
fun isTokensSortedByBalance(userWalletId: UserWalletId): Flow<Boolean>
}

View file

@ -1,163 +0,0 @@
package com.tangem.domain.tokens.utils
import arrow.core.NonEmptySet
import arrow.core.raise.Raise
import arrow.core.raise.ensureNotNull
import arrow.core.toNonEmptySetOrNull
import com.tangem.domain.tokens.error.TokensError
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.tokens.model.NetworkGroup
import com.tangem.domain.tokens.model.TokenList
import com.tangem.domain.tokens.model.TokenStatus
import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.tokens.repository.TokensRepository
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext
import java.math.BigDecimal
@Suppress("LongParameterList")
internal class TokenListOperations(
private val tokensRepository: TokensRepository,
private val networksRepository: NetworksRepository,
private val userWalletId: UserWalletId,
private val totalFiatBalance: TokenList.FiatBalance,
private val isAnyTokenLoading: Boolean,
private val dispatchers: CoroutineDispatcherProvider,
raise: Raise<TokensError>,
) : Raise<TokensError> by raise {
fun getTokenListFlow(tokens: Set<TokenStatus>): Flow<TokenList> {
return combine(getIsGrouped(), getIsSortedByBalance()) { isGrouped, isSortedByBalance ->
createTokenList(tokens, isGrouped, isSortedByBalance)
}
}
private suspend fun createTokenList(
tokens: Set<TokenStatus>,
isGrouped: Boolean,
isSortedByBalance: Boolean,
): TokenList = withContext(dispatchers.single) {
val tokensNes = tokens.toNonEmptySetOrNull()
when {
tokensNes == null -> TokenList.NotInitialized
isGrouped -> {
val networks = getNetworks(tokensNes)
createGroupedTokenList(tokensNes, networks, isSortedByBalance)
}
else -> createUngroupedTokenList(tokensNes, isSortedByBalance)
}
}
private fun createUngroupedTokenList(
tokens: NonEmptySet<TokenStatus>,
isSortedByBalance: Boolean,
): TokenList.Ungrouped {
return TokenList.Ungrouped(
sortedBy = getSortType(isSortedByBalance),
totalFiatBalance = totalFiatBalance,
tokens = if (isSortedByBalance) sortTokensByBalance(tokens) else tokens,
)
}
private fun createGroupedTokenList(
tokens: NonEmptySet<TokenStatus>,
networks: NonEmptySet<Network>,
isSortedByBalance: Boolean,
): TokenList.GroupedByNetwork {
return TokenList.GroupedByNetwork(
sortedBy = getSortType(isSortedByBalance),
totalFiatBalance = totalFiatBalance,
groups = if (isSortedByBalance) groupAndSortTokens(tokens, networks) else groupTokens(tokens, networks),
)
}
private fun groupAndSortTokens(
tokens: NonEmptySet<TokenStatus>,
networks: NonEmptySet<Network>,
): NonEmptySet<NetworkGroup> {
val groupsWithSortedTokens = groupTokens(tokens, networks)
.map { group ->
group.copy(tokens = sortTokensByBalance(group.tokens as NonEmptySet<TokenStatus>))
}
.toNonEmptySet()
val sortedGroups = if (isAnyTokenLoading) {
groupsWithSortedTokens
} else {
sortGroupsByBalance(groupsWithSortedTokens)
}
return sortedGroups
}
private fun sortTokensByBalance(tokens: NonEmptySet<TokenStatus>): NonEmptySet<TokenStatus> {
val sortedTokens = if (isAnyTokenLoading) {
tokens
} else {
tokens
.sortedByDescending { it.value.fiatAmount ?: BigDecimal.ZERO }
.toNonEmptySetOrNull()
}
return sortedTokens!!
}
private fun groupTokens(
tokens: NonEmptySet<TokenStatus>,
networks: NonEmptySet<Network>,
): NonEmptySet<NetworkGroup> {
val groups = tokens
.groupBy { it.networkId }
.map { (networkId, tokens) ->
val network = ensureNotNull(networks.firstOrNull { it.id == networkId }) {
TokensError.NetworkNotFound(networkId)
}
NetworkGroup(
networkId = network.id,
name = network.name,
tokens = tokens.toNonEmptySetOrNull()!!,
)
}
.toNonEmptySetOrNull()
return groups!!
}
private suspend fun getNetworks(tokens: NonEmptySet<TokenStatus>): NonEmptySet<Network> {
return withContext(dispatchers.io) {
val networksIds = tokens.map { it.networkId }.toNonEmptySet()
val networks = networksRepository.getNetworks(networksIds).bind()
ensureNotNull(networks.toNonEmptySetOrNull()) { TokensError.EmptyNetworks }
}
}
private fun getIsGrouped(): Flow<Boolean> {
return tokensRepository.isTokensGrouped(userWalletId)
.map { it.bind() }
.flowOn(dispatchers.io)
}
private fun getIsSortedByBalance(): Flow<Boolean> {
return tokensRepository.isTokensSortedByBalance(userWalletId)
.map { it.bind() }
.flowOn(dispatchers.io)
}
private fun sortGroupsByBalance(groupsWithSortedTokens: NonEmptySet<NetworkGroup>): NonEmptySet<NetworkGroup> {
return groupsWithSortedTokens
.sortedByDescending { group ->
group.tokens.sumOf { it.value.fiatAmount ?: BigDecimal.ZERO }
}
.toNonEmptySetOrNull()!!
}
private fun getSortType(isSortedByBalance: Boolean) =
if (isSortedByBalance) TokenList.SortType.BALANCE else TokenList.SortType.NONE
}

View file

@ -3,7 +3,8 @@ package com.tangem.domain.tokens
import arrow.core.Either
import arrow.core.left
import arrow.core.right
import com.tangem.domain.tokens.error.TokensError
import com.tangem.domain.core.error.DataError
import com.tangem.domain.tokens.error.TokenListError
import com.tangem.domain.tokens.mock.MockNetworks
import com.tangem.domain.tokens.mock.MockQuotes
import com.tangem.domain.tokens.mock.MockTokenLists
@ -18,12 +19,10 @@ import com.tangem.domain.tokens.repository.MockTokensRepository
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
import junit.framework.TestCase.assertEquals
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.test.runTest
import org.junit.Test
@OptIn(ExperimentalCoroutinesApi::class)
internal class GetTokenListUseCaseTest {
private val dispatchers = TestingCoroutineDispatcherProvider()
@ -49,9 +48,9 @@ internal class GetTokenListUseCaseTest {
@Test
fun `when tokens getting failed then error should be received`() = runTest {
// Given
val expectedResult = TokensError.EmptyTokens.left()
val expectedResult = TokenListError.DataError(DataError.NetworkError.NoInternetConnection).left()
val useCase = getUseCase(tokens = flowOf(expectedResult))
val useCase = getUseCase(tokens = flowOf(DataError.NetworkError.NoInternetConnection.left()))
// When
val result = useCase(userWalletId).first()
@ -63,9 +62,9 @@ internal class GetTokenListUseCaseTest {
@Test
fun `when quotes getting failed then error should be received`() = runTest {
// Given
val expectedResult = TokensError.EmptyQuotes.left()
val expectedResult = TokenListError.DataError(DataError.NetworkError.NoInternetConnection).left()
val useCase = getUseCase(quotes = flowOf(expectedResult))
val useCase = getUseCase(quotes = flowOf(DataError.NetworkError.NoInternetConnection.left()))
// When
val result = useCase(userWalletId).first()
@ -77,10 +76,10 @@ internal class GetTokenListUseCaseTest {
@Test
fun `when networks getting failed and list is groped then error should be received`() = runTest {
// Given
val expectedResult = TokensError.EmptyNetworkStatues.left()
val expectedResult = TokenListError.DataError(DataError.NetworkError.NoInternetConnection).left()
val useCase = getUseCase(
networks = expectedResult,
networks = DataError.NetworkError.NoInternetConnection.left(),
isGrouped = flowOf(true.right()),
)
@ -94,9 +93,9 @@ internal class GetTokenListUseCaseTest {
@Test
fun `when networks statuses getting failed then error should be received`() = runTest {
// Given
val expectedResult = TokensError.EmptyNetworkStatues.left()
val expectedResult = TokenListError.DataError(DataError.NetworkError.NoInternetConnection).left()
val useCase = getUseCase(statuses = flowOf(expectedResult))
val useCase = getUseCase(statuses = flowOf(DataError.NetworkError.NoInternetConnection.left()))
// When
val result = useCase(userWalletId).first()
@ -108,9 +107,9 @@ internal class GetTokenListUseCaseTest {
@Test
fun `when grouping type getting failed then error should be received`() = runTest {
// Given
val expectedResult = TokensError.EmptyTokens.left()
val expectedResult = TokenListError.DataError(DataError.NetworkError.NoInternetConnection).left()
val useCase = getUseCase(isGrouped = flowOf(expectedResult))
val useCase = getUseCase(isGrouped = flowOf(DataError.NetworkError.NoInternetConnection.left()))
// When
val result = useCase(userWalletId).first()
@ -122,9 +121,9 @@ internal class GetTokenListUseCaseTest {
@Test
fun `when sorting type getting failed then error should be received`() = runTest {
// Given
val expectedResult = TokensError.EmptyTokens.left()
val expectedResult = TokenListError.DataError(DataError.NetworkError.NoInternetConnection).left()
val useCase = getUseCase(isSortedByBalance = flowOf(expectedResult))
val useCase = getUseCase(isSortedByBalance = flowOf(DataError.NetworkError.NoInternetConnection.left()))
// When
val result = useCase(userWalletId).first()
@ -136,10 +135,10 @@ internal class GetTokenListUseCaseTest {
@Test
fun `when tokens getting failed on second emit then error should be received`() = runTest {
// Given
val error = TokensError.EmptyTokens.left()
val error = DataError.NetworkError.NoInternetConnection.left()
val expectedResult = listOf(
MockTokenLists.ungroupedTokenList.right(),
error,
TokenListError.DataError(DataError.NetworkError.NoInternetConnection).left(),
)
val useCase = getUseCase(
@ -173,10 +172,10 @@ internal class GetTokenListUseCaseTest {
@Test
fun `when list is grouped and networks getting failed then error should be received`() = runTest {
val expectedResult = TokensError.EmptyNetworks.left()
val expectedResult = TokenListError.DataError(DataError.NetworkError.NoInternetConnection).left()
val useCase = getUseCase(
networks = expectedResult,
networks = DataError.NetworkError.NoInternetConnection.left(),
isGrouped = flowOf(true.right()),
)
@ -235,8 +234,8 @@ internal class GetTokenListUseCaseTest {
}
@Test
fun `when networks is empty and list is grouped then error should be received`() = runTest {
val expectedResult = TokensError.EmptyNetworks.left()
fun `when networks is empty and list is grouped then ungrouped list should be received`() = runTest {
val expectedResult = TokenListError.UnableToSortTokenList(MockTokenLists.ungroupedTokenList).left()
val useCase = getUseCase(
networks = emptySet<Network>().right(),
@ -252,7 +251,7 @@ internal class GetTokenListUseCaseTest {
@Test
fun `when tokens flow is empty then error should be received`() = runTest {
val expectedResult = TokensError.EmptyTokens.left()
val expectedResult = TokenListError.EmptyTokens.left()
val useCase = getUseCase(tokens = flowOf())
@ -265,7 +264,7 @@ internal class GetTokenListUseCaseTest {
@Test
fun `when networks statuses flow is empty then error should be received`() = runTest {
val expectedResult = TokensError.EmptyNetworkStatues.left()
val expectedResult = TokenListError.EmptyTokens.left()
val useCase = getUseCase(statuses = flowOf())
@ -291,7 +290,7 @@ internal class GetTokenListUseCaseTest {
@Test
fun `when quotes flow is empty then error should be received`() = runTest {
val expectedResult = TokensError.EmptyQuotes.left()
val expectedResult = TokenListError.EmptyTokens.left()
val useCase = getUseCase(quotes = flowOf())
@ -319,12 +318,12 @@ internal class GetTokenListUseCaseTest {
}
private fun getUseCase(
tokens: Flow<Either<TokensError, Set<Token>>> = flowOf(MockTokens.tokens.right()),
quotes: Flow<Either<TokensError, Set<Quote>>> = flowOf(MockQuotes.quotes.right()),
networks: Either<TokensError, Set<Network>> = MockNetworks.networks.right(),
statuses: Flow<Either<TokensError, Set<NetworkStatus>>> = flowOf(MockNetworks.errorNetworksStatuses.right()),
isGrouped: Flow<Either<TokensError, Boolean>> = flowOf(MockTokenLists.isGrouped.right()),
isSortedByBalance: Flow<Either<TokensError, Boolean>> = flowOf(MockTokenLists.isSortedByBalance.right()),
tokens: Flow<Either<DataError, Set<Token>>> = flowOf(MockTokens.tokens.right()),
quotes: Flow<Either<DataError, Set<Quote>>> = flowOf(MockQuotes.quotes.right()),
networks: Either<DataError, Set<Network>> = MockNetworks.networks.right(),
statuses: Flow<Either<DataError, Set<NetworkStatus>>> = flowOf(MockNetworks.errorNetworksStatuses.right()),
isGrouped: Flow<Either<DataError, Boolean>> = flowOf(MockTokenLists.isGrouped.right()),
isSortedByBalance: Flow<Either<DataError, Boolean>> = flowOf(MockTokenLists.isSortedByBalance.right()),
) = GetTokenListUseCase(
dispatchers = dispatchers,
tokensRepository = MockTokensRepository(tokens, isGrouped, isSortedByBalance),

View file

@ -1,27 +1,29 @@
package com.tangem.domain.tokens.repository
import arrow.core.Either
import com.tangem.domain.tokens.error.TokensError
import arrow.core.getOrElse
import com.tangem.domain.core.error.DataError
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.tokens.model.NetworkStatus
import com.tangem.domain.tokens.model.Token
import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
internal class MockNetworksRepository(
private val networks: Either<TokensError, Set<Network>>,
private val statuses: Flow<Either<TokensError, Set<NetworkStatus>>>,
private val networks: Either<DataError, Set<Network>>,
private val statuses: Flow<Either<DataError, Set<NetworkStatus>>>,
) : NetworksRepository {
override fun getNetworks(networksIds: Set<Network.ID>): Either<TokensError, Set<Network>> {
return networks
override fun getNetworks(networksIds: Set<Network.ID>): Set<Network> {
return networks.getOrElse { throw it }
}
override fun getNetworkStatuses(
userWalletId: UserWalletId,
networks: Map<Network.ID, Set<Token.ID>>,
refresh: Boolean,
): Flow<Either<TokensError, Set<NetworkStatus>>> {
return statuses
): Flow<Set<NetworkStatus>> {
return statuses.map { it.getOrElse { e -> throw e } }
}
}

View file

@ -1,16 +1,18 @@
package com.tangem.domain.tokens.repository
import arrow.core.Either
import com.tangem.domain.tokens.error.TokensError
import arrow.core.getOrElse
import com.tangem.domain.core.error.DataError
import com.tangem.domain.tokens.model.Quote
import com.tangem.domain.tokens.model.Token
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
internal class MockQuotesRepository(
private val quotes: Flow<Either<TokensError, Set<Quote>>>,
private val quotes: Flow<Either<DataError, Set<Quote>>>,
) : QuotesRepository {
override fun getQuotes(tokensIds: Set<Token.ID>, refresh: Boolean): Flow<Either<TokensError, Set<Quote>>> {
return quotes
override fun getQuotes(tokensIds: Set<Token.ID>, refresh: Boolean): Flow<Set<Quote>> {
return quotes.map { it.getOrElse { e -> throw e } }
}
}

View file

@ -1,16 +1,17 @@
package com.tangem.domain.tokens.repository
import arrow.core.Either
import arrow.core.right
import com.tangem.domain.tokens.error.TokensError
import arrow.core.getOrElse
import com.tangem.domain.core.error.DataError
import com.tangem.domain.tokens.model.Token
import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
internal class MockTokensRepository(
private val tokens: Flow<Either<TokensError, Set<Token>>>,
private val isGrouped: Flow<Either<TokensError, Boolean>>,
private val isSortedByBalance: Flow<Either<TokensError, Boolean>>,
private val tokens: Flow<Either<DataError, Set<Token>>>,
private val isGrouped: Flow<Either<DataError, Boolean>>,
private val isSortedByBalance: Flow<Either<DataError, Boolean>>,
) : TokensRepository {
override suspend fun sortTokens(
@ -18,17 +19,17 @@ internal class MockTokensRepository(
sortedTokensIds: Set<Token.ID>,
isGrouped: Boolean,
isSortedByBalance: Boolean,
): Either<TokensError, Unit> = Unit.right()
) = Unit
override fun getTokens(userWalletId: UserWalletId, refresh: Boolean): Flow<Either<TokensError, Set<Token>>> {
return tokens
override fun getTokens(userWalletId: UserWalletId, refresh: Boolean): Flow<Set<Token>> {
return tokens.map { it.getOrElse { e -> throw e } }
}
override fun isTokensGrouped(userWalletId: UserWalletId): Flow<Either<TokensError, Boolean>> {
return isGrouped
override fun isTokensGrouped(userWalletId: UserWalletId): Flow<Boolean> {
return isGrouped.map { it.getOrElse { e -> throw e } }
}
override fun isTokensSortedByBalance(userWalletId: UserWalletId): Flow<Either<TokensError, Boolean>> {
return isSortedByBalance
override fun isTokensSortedByBalance(userWalletId: UserWalletId): Flow<Boolean> {
return isSortedByBalance.map { it.getOrElse { e -> throw e } }
}
}

View file

@ -1,15 +1,15 @@
package com.tangem.feature.wallet.presentation.wallet.utils
import com.tangem.domain.tokens.error.TokensError
import com.tangem.domain.tokens.error.TokenListError
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder
import com.tangem.utils.converter.Converter
internal class TokenErrorToWalletStateConverter(
internal class TokenListErrorToWalletStateConverter(
private val currentState: WalletStateHolder,
) : Converter<TokensError, WalletStateHolder> {
) : Converter<TokenListError, WalletStateHolder> {
// TODO: [REDACTED_JIRA]
override fun convert(value: TokensError): WalletStateHolder {
override fun convert(value: TokenListError): WalletStateHolder {
return currentState
}
}

View file

@ -16,7 +16,7 @@ import com.tangem.domain.card.SetAccessCodeRequestPolicyUseCase
import com.tangem.domain.tokens.GetTokenListUseCase
import com.tangem.domain.userwallets.UserWalletBuilder
import com.tangem.domain.wallets.usecase.SaveWalletUseCase
import com.tangem.domain.tokens.error.TokensError
import com.tangem.domain.tokens.error.TokenListError
import com.tangem.domain.tokens.model.TokenList
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.feature.wallet.presentation.common.WalletPreviewData
@ -24,7 +24,7 @@ import com.tangem.feature.wallet.presentation.router.InnerWalletRouter
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder
import com.tangem.feature.wallet.presentation.wallet.state.WalletTopBarConfig
import com.tangem.feature.wallet.presentation.wallet.utils.LoadingItemsProvider.getLoadingMultiCurrencyTokens
import com.tangem.feature.wallet.presentation.wallet.utils.TokenErrorToWalletStateConverter
import com.tangem.feature.wallet.presentation.wallet.utils.TokenListErrorToWalletStateConverter
import com.tangem.feature.wallet.presentation.wallet.utils.TokenListToWalletStateConverter
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.hilt.android.lifecycle.HiltViewModel
@ -152,9 +152,9 @@ internal class WalletViewModel @Inject constructor(
.launchIn(viewModelScope)
}
private fun updateStateWithTokenListOrError(tokenList: Either<TokensError, TokenList>): WalletStateHolder {
val updateStateWithError = { error: TokensError ->
val converter = TokenErrorToWalletStateConverter(uiState)
private fun updateStateWithTokenListOrError(tokenList: Either<TokenListError, TokenList>): WalletStateHolder {
val updateStateWithError = { error: TokenListError ->
val converter = TokenListErrorToWalletStateConverter(uiState)
converter.convert(error)
}