From 5f1a5494dc4f86cbe86bd19128d66bb14438d387 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sat, 15 Jul 2023 01:47:52 +0300 Subject: [PATCH] Updated on 2026-08-14 --- data/tokens/build.gradle.kts | 1 + .../repository/MockNetworksRepository.kt | 11 +- .../tokens/repository/MockQuotesRepository.kt | 8 +- .../tokens/repository/MockTokensRepository.kt | 18 +- .../com/tangem/domain/core/error/DataError.kt | 9 + .../domain/core/raise/DelegatedRaise.kt | 13 ++ .../domain/tokens/GetTokenListUseCase.kt | 55 ++--- .../domain/tokens/error/TokenListError.kt | 31 +++ .../tangem/domain/tokens/error/TokensError.kt | 21 -- .../TokenListFiatBalanceOperations.kt | 7 +- .../tokens/operations/TokenListOperations.kt | 188 ++++++++++++++++++ .../operations/TokenListSortingOperations.kt | 115 +++++++++++ .../TokenStatusOperations.kt | 16 +- .../TokensStatusesOperations.kt | 74 +++++-- .../tokens/repository/NetworksRepository.kt | 6 +- .../tokens/repository/QuotesRepository.kt | 4 +- .../tokens/repository/TokensRepository.kt | 10 +- .../tokens/utils/TokenListOperations.kt | 163 --------------- .../domain/tokens/GetTokenListUseCaseTest.kt | 59 +++--- .../repository/MockNetworksRepository.kt | 16 +- .../tokens/repository/MockQuotesRepository.kt | 10 +- .../tokens/repository/MockTokensRepository.kt | 25 +-- ...> TokenListErrorToWalletStateConverter.kt} | 8 +- .../wallet/viewmodels/WalletViewModel.kt | 10 +- 24 files changed, 525 insertions(+), 353 deletions(-) create mode 100644 domain/core/src/main/kotlin/com/tangem/domain/core/error/DataError.kt create mode 100644 domain/core/src/main/kotlin/com/tangem/domain/core/raise/DelegatedRaise.kt create mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/TokenListError.kt delete mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/TokensError.kt rename domain/tokens/src/main/kotlin/com/tangem/domain/tokens/{utils => operations}/TokenListFiatBalanceOperations.kt (94%) create mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListOperations.kt create mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListSortingOperations.kt rename domain/tokens/src/main/kotlin/com/tangem/domain/tokens/{utils => operations}/TokenStatusOperations.kt (84%) rename domain/tokens/src/main/kotlin/com/tangem/domain/tokens/{utils => operations}/TokensStatusesOperations.kt (60%) delete mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/TokenListOperations.kt rename features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/{TokenErrorToWalletStateConverter.kt => TokenListErrorToWalletStateConverter.kt} (56%) diff --git a/data/tokens/build.gradle.kts b/data/tokens/build.gradle.kts index 95abf3ed40..2d8aaaf35d 100644 --- a/data/tokens/build.gradle.kts +++ b/data/tokens/build.gradle.kts @@ -5,6 +5,7 @@ plugins { } dependencies { + implementation(projects.domain.core) implementation(projects.domain.tokens) implementation(projects.domain.wallets.models) diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/MockNetworksRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/MockNetworksRepository.kt index a328f0a544..26bc58aa70 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/MockNetworksRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/MockNetworksRepository.kt @@ -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): Either> { + override fun getNetworks(networksIds: Set): Set { return MockNetworks.networks .filter { it.id in networksIds } .toSet() - .right() } override fun getNetworkStatuses( userWalletId: UserWalletId, networks: Map>, refresh: Boolean, - ): Flow>> { + ): Flow> { return flowOf( MockNetworks.networksStatuses .filter { it.networkId in networks.keys } - .toSet() - .right(), + .toSet(), ) } } \ No newline at end of file diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/MockQuotesRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/MockQuotesRepository.kt index 1e6cbaa1d8..2510893558 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/MockQuotesRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/MockQuotesRepository.kt @@ -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, refresh: Boolean): Flow>> { + override fun getQuotes(tokensIds: Set, refresh: Boolean): Flow> { return flowOf( MockQuotes.quotes .filter { it.tokenId in tokensIds } - .toSet() - .right(), + .toSet(), ) } } \ No newline at end of file diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/MockTokensRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/MockTokensRepository.kt index 92092f1046..e6b7760a7c 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/MockTokensRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/MockTokensRepository.kt @@ -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, isGrouped: Boolean, isSortedByBalance: Boolean, - ): Either = Unit.right() + ) = Unit - override fun getTokens(userWalletId: UserWalletId, refresh: Boolean): Flow>> { - return flowOf(value = MockTokens.tokens[userWalletId]?.right() ?: TokensError.EmptyTokens.left()) + override fun getTokens(userWalletId: UserWalletId, refresh: Boolean): Flow> { + return flowOf(value = MockTokens.tokens[userWalletId]!!) } - override fun isTokensGrouped(userWalletId: UserWalletId): Flow> { - return flowOf(MockTokens.isGrouped[userWalletId]?.right() ?: TokensError.EmptyTokens.left()) + override fun isTokensGrouped(userWalletId: UserWalletId): Flow { + return flowOf(value = MockTokens.isGrouped[userWalletId]!!) } - override fun isTokensSortedByBalance(userWalletId: UserWalletId): Flow> { - return flowOf(MockTokens.isSortedByBalance[userWalletId]?.right() ?: TokensError.EmptyTokens.left()) + override fun isTokensSortedByBalance(userWalletId: UserWalletId): Flow { + return flowOf(value = MockTokens.isSortedByBalance[userWalletId]!!) } } \ No newline at end of file diff --git a/domain/core/src/main/kotlin/com/tangem/domain/core/error/DataError.kt b/domain/core/src/main/kotlin/com/tangem/domain/core/error/DataError.kt new file mode 100644 index 0000000000..11567ca43b --- /dev/null +++ b/domain/core/src/main/kotlin/com/tangem/domain/core/error/DataError.kt @@ -0,0 +1,9 @@ +package com.tangem.domain.core.error + +sealed class DataError : Exception() { + + sealed class NetworkError : DataError() { + + object NoInternetConnection : NetworkError() + } +} \ No newline at end of file diff --git a/domain/core/src/main/kotlin/com/tangem/domain/core/raise/DelegatedRaise.kt b/domain/core/src/main/kotlin/com/tangem/domain/core/raise/DelegatedRaise.kt new file mode 100644 index 0000000000..ffde660eb6 --- /dev/null +++ b/domain/core/src/main/kotlin/com/tangem/domain/core/raise/DelegatedRaise.kt @@ -0,0 +1,13 @@ +package com.tangem.domain.core.raise + +import arrow.core.raise.Raise + +abstract class DelegatedRaise( + private val otherRaise: Raise, + private val transformError: (Error) -> OtherError, +) : Raise { + + override fun raise(r: Error): Nothing { + otherRaise.raise(transformError(r)) + } +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetTokenListUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetTokenListUseCase.kt index 024c43ef42..3f42be91fc 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetTokenListUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetTokenListUseCase.kt @@ -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> { + operator fun invoke(userWalletId: UserWalletId, refresh: Boolean = true): Flow> { return channelFlow { recover( block = { @@ -41,53 +40,39 @@ class GetTokenListUseCase( ) } } - private fun Raise.getTokenList(userWalletId: UserWalletId, refresh: Boolean): Flow { + private fun Raise.getTokenList(userWalletId: UserWalletId, refresh: Boolean): Flow { return getTokensStatuses(userWalletId, refresh).flatMapConcat { tokens -> createTokenList(userWalletId, tokens) } } - private fun Raise.getTokensStatuses( + private fun Raise.getTokensStatuses( userWalletId: UserWalletId, refresh: Boolean, ): Flow> { 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.createTokenList( + private fun Raise.createTokenList( userWalletId: UserWalletId, tokens: Set, ): Flow { - 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, - isAnyTokenLoading: Boolean, - ): TokenList.FiatBalance { - val operations = TokenListFiatBalanceOperations(tokens, isAnyTokenLoading, dispatchers) - - return operations.calculateFiatBalance() + return operations.getTokenListFlow() } } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/TokenListError.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/TokenListError.kt new file mode 100644 index 0000000000..06897839f6 --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/TokenListError.kt @@ -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) + } + } +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/TokensError.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/TokensError.kt deleted file mode 100644 index 6571bc1d78..0000000000 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/TokensError.kt +++ /dev/null @@ -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() -} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/TokenListFiatBalanceOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListFiatBalanceOperations.kt similarity index 94% rename from domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/TokenListFiatBalanceOperations.kt rename to domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListFiatBalanceOperations.kt index 6d3360512e..69378510f3 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/TokenListFiatBalanceOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListFiatBalanceOperations.kt @@ -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, + private val tokens: NonEmptySet, 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) { diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListOperations.kt new file mode 100644 index 0000000000..87253155db --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListOperations.kt @@ -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( + private val tokensRepository: TokensRepository, + private val networksRepository: NetworksRepository, + private val userWalletId: UserWalletId, + private val tokens: Set, + private val dispatchers: CoroutineDispatcherProvider, + raise: Raise, + transform: (Error) -> E, +) : DelegatedRaise(raise, transform) { + + constructor( + userWalletId: UserWalletId, + tokens: Set, + useCase: GetTokenListUseCase, + raise: Raise, + transform: (Error) -> E, + ) : this( + tokensRepository = useCase.tokensRepository, + networksRepository = useCase.networksRepository, + userWalletId = userWalletId, + tokens = tokens, + dispatchers = useCase.dispatchers, + raise = raise, + transform = transform, + ) + + fun getTokenListFlow(): Flow { + return combine(getIsGrouped(), getIsSortedByBalance()) { isGrouped, isSortedByBalance -> + createTokenList(isGrouped, isSortedByBalance) + } + } + + 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, + 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, + 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): Set { + 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, + ): TokenList.GroupedByNetwork = TokenList.GroupedByNetwork( + sortedBy = sortingOperations.getSortType(), + totalFiatBalance = fiatBalance, + groups = sortingOperations.getGroupedTokens(networks), + ) + + private fun createUnsortedUngroupedTokenList( + tokens: NonEmptySet, + fiatBalance: TokenList.FiatBalance, + ): TokenList.Ungrouped { + return TokenList.Ungrouped( + sortedBy = TokenList.SortType.NONE, + totalFiatBalance = fiatBalance, + tokens = tokens, + ) + } + + private fun getIsGrouped(): Flow { + return tokensRepository.isTokensGrouped(userWalletId) + .catch { raise(Error.DataError(it)) } + .onEmpty { emit(value = false) } + .flowOn(dispatchers.io) + } + + private fun getIsSortedByBalance(): Flow { + 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(), + ) + } + } + } +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListSortingOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListSortingOperations.kt new file mode 100644 index 0000000000..5d1b4516bb --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListSortingOperations.kt @@ -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( + private val tokens: Set, + private val isAnyTokenLoading: Boolean, + private val isSortedByBalance: Boolean, + private val dispatchers: CoroutineDispatcherProvider, + raise: Raise, + transformError: (Error) -> E, +) : DelegatedRaise(raise, transformError) { + + suspend fun getGroupedTokens(networks: Set): NonEmptySet { + 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 { + 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): NonEmptySet { + 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): NonEmptySet { + val groupsWithSortedTokens = groupTokens(networks) + .map { group -> + val tokens = group.tokens as? NonEmptySet + ?: 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): NonEmptySet { + 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): NonEmptySet { + 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() + } +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/TokenStatusOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenStatusOperations.kt similarity index 84% rename from domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/TokenStatusOperations.kt rename to domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenStatusOperations.kt index c0ebeeafa7..072b541737 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/TokenStatusOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenStatusOperations.kt @@ -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, -) : Raise 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 } } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/TokensStatusesOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokensStatusesOperations.kt similarity index 60% rename from domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/TokensStatusesOperations.kt rename to domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokensStatusesOperations.kt index 0c90a3c80b..e5dfd69fa0 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/TokensStatusesOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokensStatusesOperations.kt @@ -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( 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, -) : Raise by raise { + raise: Raise, + transformError: (Error) -> E, +) : DelegatedRaise(raise, transformError) { + + constructor( + userWalletId: UserWalletId, + refresh: Boolean, + useCase: GetTokenListUseCase, + raise: Raise, + 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> { return getTokens().flatMapConcat { @@ -46,37 +65,43 @@ internal class TokensStatusesOperations( quotes: Set, networkStatuses: Set, ): Set = 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> { 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): Flow> { 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>): Flow> { 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() + } } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/NetworksRepository.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/NetworksRepository.kt index df231dd283..93f7f7c95b 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/NetworksRepository.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/NetworksRepository.kt @@ -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): Either> + fun getNetworks(networksIds: Set): Set fun getNetworkStatuses( userWalletId: UserWalletId, networks: Map>, refresh: Boolean, - ): Flow>> + ): Flow> } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/QuotesRepository.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/QuotesRepository.kt index 314c1d1177..984ffb6608 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/QuotesRepository.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/QuotesRepository.kt @@ -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, refresh: Boolean): Flow>> + fun getQuotes(tokensIds: Set, refresh: Boolean): Flow> } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/TokensRepository.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/TokensRepository.kt index 4b1ed804c3..b544ecb8f5 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/TokensRepository.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/TokensRepository.kt @@ -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, isGrouped: Boolean, isSortedByBalance: Boolean, - ): Either + ) - fun getTokens(userWalletId: UserWalletId, refresh: Boolean): Flow>> + fun getTokens(userWalletId: UserWalletId, refresh: Boolean): Flow> - fun isTokensGrouped(userWalletId: UserWalletId): Flow> + fun isTokensGrouped(userWalletId: UserWalletId): Flow - fun isTokensSortedByBalance(userWalletId: UserWalletId): Flow> + fun isTokensSortedByBalance(userWalletId: UserWalletId): Flow } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/TokenListOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/TokenListOperations.kt deleted file mode 100644 index f5d8bf0d5c..0000000000 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/TokenListOperations.kt +++ /dev/null @@ -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, -) : Raise by raise { - - fun getTokenListFlow(tokens: Set): Flow { - return combine(getIsGrouped(), getIsSortedByBalance()) { isGrouped, isSortedByBalance -> - createTokenList(tokens, isGrouped, isSortedByBalance) - } - } - - private suspend fun createTokenList( - tokens: Set, - 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, - isSortedByBalance: Boolean, - ): TokenList.Ungrouped { - return TokenList.Ungrouped( - sortedBy = getSortType(isSortedByBalance), - totalFiatBalance = totalFiatBalance, - tokens = if (isSortedByBalance) sortTokensByBalance(tokens) else tokens, - ) - } - - private fun createGroupedTokenList( - tokens: NonEmptySet, - networks: NonEmptySet, - 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, - networks: NonEmptySet, - ): NonEmptySet { - val groupsWithSortedTokens = groupTokens(tokens, networks) - .map { group -> - group.copy(tokens = sortTokensByBalance(group.tokens as NonEmptySet)) - } - .toNonEmptySet() - val sortedGroups = if (isAnyTokenLoading) { - groupsWithSortedTokens - } else { - sortGroupsByBalance(groupsWithSortedTokens) - } - - return sortedGroups - } - - private fun sortTokensByBalance(tokens: NonEmptySet): NonEmptySet { - val sortedTokens = if (isAnyTokenLoading) { - tokens - } else { - tokens - .sortedByDescending { it.value.fiatAmount ?: BigDecimal.ZERO } - .toNonEmptySetOrNull() - } - - return sortedTokens!! - } - - private fun groupTokens( - tokens: NonEmptySet, - networks: NonEmptySet, - ): NonEmptySet { - 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): NonEmptySet { - 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 { - return tokensRepository.isTokensGrouped(userWalletId) - .map { it.bind() } - .flowOn(dispatchers.io) - } - - private fun getIsSortedByBalance(): Flow { - return tokensRepository.isTokensSortedByBalance(userWalletId) - .map { it.bind() } - .flowOn(dispatchers.io) - } - - private fun sortGroupsByBalance(groupsWithSortedTokens: NonEmptySet): NonEmptySet { - 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 -} \ No newline at end of file diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetTokenListUseCaseTest.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetTokenListUseCaseTest.kt index e876c1924d..fb26a27895 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetTokenListUseCaseTest.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetTokenListUseCaseTest.kt @@ -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().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>> = flowOf(MockTokens.tokens.right()), - quotes: Flow>> = flowOf(MockQuotes.quotes.right()), - networks: Either> = MockNetworks.networks.right(), - statuses: Flow>> = flowOf(MockNetworks.errorNetworksStatuses.right()), - isGrouped: Flow> = flowOf(MockTokenLists.isGrouped.right()), - isSortedByBalance: Flow> = flowOf(MockTokenLists.isSortedByBalance.right()), + tokens: Flow>> = flowOf(MockTokens.tokens.right()), + quotes: Flow>> = flowOf(MockQuotes.quotes.right()), + networks: Either> = MockNetworks.networks.right(), + statuses: Flow>> = flowOf(MockNetworks.errorNetworksStatuses.right()), + isGrouped: Flow> = flowOf(MockTokenLists.isGrouped.right()), + isSortedByBalance: Flow> = flowOf(MockTokenLists.isSortedByBalance.right()), ) = GetTokenListUseCase( dispatchers = dispatchers, tokensRepository = MockTokensRepository(tokens, isGrouped, isSortedByBalance), diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockNetworksRepository.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockNetworksRepository.kt index 3267a30ba2..98c7002327 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockNetworksRepository.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockNetworksRepository.kt @@ -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>, - private val statuses: Flow>>, + private val networks: Either>, + private val statuses: Flow>>, ) : NetworksRepository { - override fun getNetworks(networksIds: Set): Either> { - return networks + override fun getNetworks(networksIds: Set): Set { + return networks.getOrElse { throw it } } override fun getNetworkStatuses( userWalletId: UserWalletId, networks: Map>, refresh: Boolean, - ): Flow>> { - return statuses + ): Flow> { + return statuses.map { it.getOrElse { e -> throw e } } } } \ No newline at end of file diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockQuotesRepository.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockQuotesRepository.kt index e82b0c0c82..fcf52e44e4 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockQuotesRepository.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockQuotesRepository.kt @@ -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>>, + private val quotes: Flow>>, ) : QuotesRepository { - override fun getQuotes(tokensIds: Set, refresh: Boolean): Flow>> { - return quotes + override fun getQuotes(tokensIds: Set, refresh: Boolean): Flow> { + return quotes.map { it.getOrElse { e -> throw e } } } } \ No newline at end of file diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockTokensRepository.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockTokensRepository.kt index 5a7a757179..813816c52a 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockTokensRepository.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockTokensRepository.kt @@ -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>>, - private val isGrouped: Flow>, - private val isSortedByBalance: Flow>, + private val tokens: Flow>>, + private val isGrouped: Flow>, + private val isSortedByBalance: Flow>, ) : TokensRepository { override suspend fun sortTokens( @@ -18,17 +19,17 @@ internal class MockTokensRepository( sortedTokensIds: Set, isGrouped: Boolean, isSortedByBalance: Boolean, - ): Either = Unit.right() + ) = Unit - override fun getTokens(userWalletId: UserWalletId, refresh: Boolean): Flow>> { - return tokens + override fun getTokens(userWalletId: UserWalletId, refresh: Boolean): Flow> { + return tokens.map { it.getOrElse { e -> throw e } } } - override fun isTokensGrouped(userWalletId: UserWalletId): Flow> { - return isGrouped + override fun isTokensGrouped(userWalletId: UserWalletId): Flow { + return isGrouped.map { it.getOrElse { e -> throw e } } } - override fun isTokensSortedByBalance(userWalletId: UserWalletId): Flow> { - return isSortedByBalance + override fun isTokensSortedByBalance(userWalletId: UserWalletId): Flow { + return isSortedByBalance.map { it.getOrElse { e -> throw e } } } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenErrorToWalletStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListErrorToWalletStateConverter.kt similarity index 56% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenErrorToWalletStateConverter.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListErrorToWalletStateConverter.kt index 0f8cb0ca2f..d6f3112be9 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenErrorToWalletStateConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListErrorToWalletStateConverter.kt @@ -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 { +) : Converter { // TODO: [REDACTED_JIRA] - override fun convert(value: TokensError): WalletStateHolder { + override fun convert(value: TokenListError): WalletStateHolder { return currentState } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt index d47726ddeb..c606c9e404 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt @@ -16,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): WalletStateHolder { - val updateStateWithError = { error: TokensError -> - val converter = TokenErrorToWalletStateConverter(uiState) + private fun updateStateWithTokenListOrError(tokenList: Either): WalletStateHolder { + val updateStateWithError = { error: TokenListError -> + val converter = TokenListErrorToWalletStateConverter(uiState) converter.convert(error) }