Updated on 2026-08-14
This commit is contained in:
parent
66aedc97cb
commit
5f1a5494dc
24 changed files with 525 additions and 353 deletions
|
|
@ -5,6 +5,7 @@ plugins {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.domain.core)
|
||||
implementation(projects.domain.tokens)
|
||||
implementation(projects.domain.wallets.models)
|
||||
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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]!!)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.tangem.domain.core.error
|
||||
|
||||
sealed class DataError : Exception() {
|
||||
|
||||
sealed class NetworkError : DataError() {
|
||||
|
||||
object NoInternetConnection : NetworkError()
|
||||
}
|
||||
}
|
||||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
@ -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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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>>
|
||||
}
|
||||
|
|
@ -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>>
|
||||
}
|
||||
|
|
@ -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>
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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 } }
|
||||
}
|
||||
}
|
||||
|
|
@ -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 } }
|
||||
}
|
||||
}
|
||||
|
|
@ -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 } }
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue