diff --git a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt index bda1a7734d..a43ebfeb6c 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt @@ -1,6 +1,6 @@ package com.tangem.tap.di.domain -import com.tangem.domain.tokens.GetTokenListUseCase +import com.tangem.domain.tokens.* import com.tangem.domain.tokens.repository.NetworksRepository import com.tangem.domain.tokens.repository.QuotesRepository import com.tangem.domain.tokens.repository.TokensRepository @@ -22,5 +22,13 @@ internal object TokensDomainModule { quotesRepository: QuotesRepository, networksRepository: NetworksRepository, dispatchers: CoroutineDispatcherProvider, - ) = GetTokenListUseCase(tokensRepository, quotesRepository, networksRepository, dispatchers) + ): GetTokenListUseCase { + return GetTokenListUseCase(tokensRepository, quotesRepository, networksRepository, dispatchers) + } + + @Provides + @ViewModelScoped + fun provideToggleTokenListSortingUseCase(dispatchers: CoroutineDispatcherProvider): ToggleTokenListSortingUseCase { + return ToggleTokenListSortingUseCase(dispatchers) + } } \ No newline at end of file diff --git a/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineDispatcherProvider.kt b/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineDispatcherProvider.kt index 6f28c0350e..f15bce01ea 100644 --- a/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineDispatcherProvider.kt +++ b/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineDispatcherProvider.kt @@ -9,18 +9,21 @@ import javax.inject.Inject interface CoroutineDispatcherProvider { val main: CoroutineDispatcher val io: CoroutineDispatcher + val default: CoroutineDispatcher val single: CoroutineDispatcher } class AppCoroutineDispatcherProvider @Inject constructor() : CoroutineDispatcherProvider { override val main: CoroutineDispatcher = Dispatchers.Main override val io: CoroutineDispatcher = Dispatchers.IO + override val default: CoroutineDispatcher = Dispatchers.Default override val single: CoroutineDispatcher = Executors.newFixedThreadPool(1).asCoroutineDispatcher() } class TestingCoroutineDispatcherProvider( override val main: CoroutineDispatcher = Dispatchers.Unconfined, override val io: CoroutineDispatcher = Dispatchers.Unconfined, + override val default: CoroutineDispatcher = Dispatchers.Unconfined, override val single: CoroutineDispatcher = Executors.newFixedThreadPool(1).asCoroutineDispatcher(), ) : CoroutineDispatcherProvider 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 3f42be91fc..fcd8578f39 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 @@ -6,6 +6,7 @@ import arrow.core.raise.Raise import arrow.core.raise.recover import arrow.core.right import com.tangem.domain.tokens.error.TokenListError +import com.tangem.domain.tokens.error.mapper.mapToTokenListError import com.tangem.domain.tokens.model.TokenList import com.tangem.domain.tokens.model.TokenStatus import com.tangem.domain.tokens.operations.TokenListOperations @@ -55,7 +56,7 @@ class GetTokenListUseCase( refresh = refresh, useCase = this@GetTokenListUseCase, raise = this, - transformError = TokenListError::fromTokenStatusesOperations, + transformError = TokensStatusesOperations.Error::mapToTokenListError, ) return operations.getTokensStatusesFlow() @@ -70,7 +71,7 @@ class GetTokenListUseCase( tokens = tokens, useCase = this@GetTokenListUseCase, raise = this, - transform = TokenListError::fromTokenListOperations, + transform = TokenListOperations.Error::mapToTokenListError, ) return operations.getTokenListFlow() diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/ToggleTokenListSortingUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/ToggleTokenListSortingUseCase.kt new file mode 100644 index 0000000000..4b0b8664f4 --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/ToggleTokenListSortingUseCase.kt @@ -0,0 +1,66 @@ +package com.tangem.domain.tokens + +import arrow.core.Either +import arrow.core.raise.Raise +import arrow.core.raise.either +import arrow.core.raise.ensure +import com.tangem.domain.tokens.error.TokenListSortingError +import com.tangem.domain.tokens.error.mapper.mapToTokenListSortingError +import com.tangem.domain.tokens.model.TokenList +import com.tangem.domain.tokens.operations.TokenListSortingOperations +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.withContext + +class ToggleTokenListSortingUseCase( + private val dispatchers: CoroutineDispatcherProvider, +) { + + suspend operator fun invoke(tokenList: TokenList): Either { + return withContext(dispatchers.default) { + either { + ensure(tokenList.totalFiatBalance !is TokenList.FiatBalance.Loading) { + TokenListSortingError.TokenListIsLoading + } + + when (tokenList) { + is TokenList.GroupedByNetwork -> sortGroupedTokenList(tokenList) + is TokenList.Ungrouped -> sortUngroupedTokenList(tokenList) + is TokenList.NotInitialized -> raise(TokenListSortingError.TokenListIsEmpty) + } + } + } + } + + private suspend fun Raise.sortGroupedTokenList( + tokenList: TokenList.GroupedByNetwork, + ): TokenList.GroupedByNetwork { + val operations = getSortingOperations(tokenList) + val networks = tokenList.groups.map { it.network }.toSet() + + return tokenList.copy( + groups = operations.getGroupedTokens(networks), + sortedBy = operations.getSortType(), + ) + } + + private suspend fun Raise.sortUngroupedTokenList( + tokenList: TokenList.Ungrouped, + ): TokenList.Ungrouped { + val operations = getSortingOperations(tokenList) + + return tokenList.copy( + tokens = operations.getTokens(), + sortedBy = operations.getSortType(), + ) + } + + private fun Raise.getSortingOperations(tokenList: TokenList): TokenListSortingOperations<*> { + return TokenListSortingOperations( + tokenList = tokenList, + sortByBalance = tokenList.sortedBy != TokenList.SortType.BALANCE, + dispatchers = dispatchers, + raise = this, + transformError = TokenListSortingOperations.Error::mapToTokenListSortingError, + ) + } +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/TokenListError.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/TokenListError.kt index 06897839f6..0a321a7874 100644 --- 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 @@ -1,8 +1,6 @@ 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 { @@ -11,21 +9,4 @@ sealed class 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/TokenListSortingError.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/TokenListSortingError.kt new file mode 100644 index 0000000000..ae49dfb209 --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/TokenListSortingError.kt @@ -0,0 +1,10 @@ +package com.tangem.domain.tokens.error + +sealed class TokenListSortingError { + + object TokenListIsLoading : TokenListSortingError() + + object TokenListIsEmpty : TokenListSortingError() + + object UnableToSortTokenList : TokenListSortingError() +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/mapper/TokenListErrorMappers.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/mapper/TokenListErrorMappers.kt new file mode 100644 index 0000000000..2d0798a489 --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/mapper/TokenListErrorMappers.kt @@ -0,0 +1,25 @@ +package com.tangem.domain.tokens.error.mapper + +import com.tangem.domain.tokens.error.TokenListError +import com.tangem.domain.tokens.operations.TokenListOperations +import com.tangem.domain.tokens.operations.TokensStatusesOperations + +internal fun TokensStatusesOperations.Error.mapToTokenListError(): TokenListError { + return when (this) { + is TokensStatusesOperations.Error.DataError -> TokenListError.DataError(this.cause) + is TokensStatusesOperations.Error.EmptyNetworksStatuses, + is TokensStatusesOperations.Error.EmptyQuotes, + is TokensStatusesOperations.Error.EmptyTokens, + -> TokenListError.EmptyTokens + } +} + +internal fun TokenListOperations.Error.mapToTokenListError(): TokenListError { + return when (this) { + is TokenListOperations.Error.DataError -> TokenListError.DataError(this.cause) + is TokenListOperations.Error.UnableToSortTokenList -> + TokenListError.UnableToSortTokenList(this.unsortedTokenList) + is TokenListOperations.Error.UnableToGroupTokenList -> + TokenListError.UnableToSortTokenList(this.ungroupedTokenList) + } +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/mapper/TokenListSortingErrorMappers.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/mapper/TokenListSortingErrorMappers.kt new file mode 100644 index 0000000000..2b492ad7ee --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/mapper/TokenListSortingErrorMappers.kt @@ -0,0 +1,13 @@ +package com.tangem.domain.tokens.error.mapper + +import com.tangem.domain.tokens.error.TokenListSortingError +import com.tangem.domain.tokens.operations.TokenListSortingOperations + +internal fun TokenListSortingOperations.Error.mapToTokenListSortingError(): TokenListSortingError { + return when (this) { + is TokenListSortingOperations.Error.EmptyTokens -> TokenListSortingError.TokenListIsEmpty + is TokenListSortingOperations.Error.EmptyNetworks, + is TokenListSortingOperations.Error.NetworkNotFound, + -> TokenListSortingError.UnableToSortTokenList + } +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/NetworkGroup.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/NetworkGroup.kt index acc05ff374..d1cc917d38 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/NetworkGroup.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/NetworkGroup.kt @@ -1,7 +1,6 @@ package com.tangem.domain.tokens.model data class NetworkGroup( - val networkId: Network.ID, - val name: String, + val network: Network, val tokens: Set, ) \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListOperations.kt index 87253155db..9283eacb78 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListOperations.kt @@ -51,7 +51,7 @@ internal class TokenListOperations( } private suspend fun createTokenList(isGrouped: Boolean, isSortedByBalance: Boolean): TokenList { - return withContext(dispatchers.single) { + return withContext(dispatchers.default) { val tokensNes = tokens.toNonEmptySetOrNull() ?: return@withContext TokenList.NotInitialized @@ -78,7 +78,7 @@ internal class TokenListOperations( val sortingOperations = TokenListSortingOperations( tokens = tokens, isAnyTokenLoading = isAnyTokenLoading, - isSortedByBalance = isSortedByBalance, + sortByBalance = isSortedByBalance, dispatchers = dispatchers, raise = this, transformError = { e -> diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListSortingOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListSortingOperations.kt index 5d1b4516bb..e40520ec97 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListSortingOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListSortingOperations.kt @@ -17,20 +17,40 @@ import java.math.BigDecimal internal class TokenListSortingOperations( private val tokens: Set, private val isAnyTokenLoading: Boolean, - private val isSortedByBalance: Boolean, + private val sortByBalance: Boolean, private val dispatchers: CoroutineDispatcherProvider, raise: Raise, transformError: (Error) -> E, ) : DelegatedRaise(raise, transformError) { + constructor( + tokenList: TokenList, + dispatchers: CoroutineDispatcherProvider, + raise: Raise, + transformError: (Error) -> E, + sortByBalance: Boolean = tokenList.sortedBy == TokenList.SortType.BALANCE, + isAnyTokenLoading: Boolean = tokenList.totalFiatBalance is TokenList.FiatBalance.Loading, + ) : this( + tokens = when (tokenList) { + is TokenList.GroupedByNetwork -> tokenList.groups.flatMap { it.tokens }.toSet() + is TokenList.Ungrouped -> tokenList.tokens + is TokenList.NotInitialized -> emptySet() + }, + isAnyTokenLoading = isAnyTokenLoading, + sortByBalance = sortByBalance, + dispatchers = dispatchers, + raise = raise, + transformError = transformError, + ) + suspend fun getGroupedTokens(networks: Set): NonEmptySet { - return withContext(dispatchers.single) { + return withContext(dispatchers.default) { ensure(tokens.isNotEmpty()) { Error.EmptyTokens } val networksNes = ensureNotNull(networks.toNonEmptySetOrNull()) { Error.EmptyNetworks } - if (isSortedByBalance) { + if (sortByBalance) { groupAndSortTokensByBalance(networksNes) } else { groupTokens(networksNes) @@ -39,16 +59,16 @@ internal class TokenListSortingOperations( } suspend fun getTokens(): NonEmptySet { - return withContext(dispatchers.single) { + return withContext(dispatchers.default) { val tokensNes = ensureNotNull(tokens.toNonEmptySetOrNull()) { Error.EmptyTokens } - if (isSortedByBalance) sortTokensByBalance(tokensNes) else tokensNes + if (sortByBalance) sortTokensByBalance(tokensNes) else tokensNes } } - fun getSortType() = if (isSortedByBalance) TokenList.SortType.BALANCE else TokenList.SortType.NONE + fun getSortType() = if (sortByBalance) TokenList.SortType.BALANCE else TokenList.SortType.NONE private fun groupTokens(networks: NonEmptySet): NonEmptySet { val groupedTokens = tokens @@ -59,8 +79,7 @@ internal class TokenListSortingOperations( } NetworkGroup( - networkId = network.id, - name = network.name, + network = network, tokens = ensureNotNull(tokens.toNonEmptySetOrNull()) { Error.EmptyTokens }, ) } diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenStatusOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenStatusOperations.kt index 072b541737..04478c7917 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenStatusOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenStatusOperations.kt @@ -15,7 +15,7 @@ internal class TokenStatusOperations( private val dispatchers: CoroutineDispatcherProvider, ) { - suspend fun createTokenStatus(): TokenStatus = withContext(dispatchers.single) { + suspend fun createTokenStatus(): TokenStatus = withContext(dispatchers.default) { TokenStatus( id = token.id, networkId = token.networkId, diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokensStatusesOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokensStatusesOperations.kt index e5dfd69fa0..cbe9d303ad 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokensStatusesOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokensStatusesOperations.kt @@ -64,7 +64,7 @@ internal class TokensStatusesOperations( tokens: Set, quotes: Set, networkStatuses: Set, - ): Set = withContext(dispatchers.single) { + ): Set = withContext(dispatchers.default) { tokens.mapTo(hashSetOf()) { token -> val quote = quotes.firstOrNull { it.tokenId == token.id } val networkStatus = networkStatuses.firstOrNull { it.networkId == token.networkId } @@ -106,7 +106,7 @@ internal class TokensStatusesOperations( } private suspend fun groupTokens(tokens: NonEmptySet): Map> = - withContext(dispatchers.single) { + withContext(dispatchers.default) { tokens .groupBy { it.networkId } .mapValues { (_, tokens) -> diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokenLists.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokenLists.kt index c705c614f2..b2befbeeb1 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokenLists.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokenLists.kt @@ -14,24 +14,21 @@ internal object MockTokenLists { const val isSortedByBalance = false val networkGroup1 = NetworkGroup( - networkId = MockNetworks.network1.id, - name = MockNetworks.network1.name, + network = MockNetworks.network1, tokens = MockTokensStates.tokenStates .filter { it.networkId == MockNetworks.network1.id } .toNonEmptySetOrNull()!!, ) val networkGroup2 = NetworkGroup( - networkId = MockNetworks.network2.id, - name = MockNetworks.network2.name, + network = MockNetworks.network2, tokens = MockTokensStates.tokenStates .filter { it.networkId == MockNetworks.network2.id } .toNonEmptySetOrNull()!!, ) val networkGroup3 = NetworkGroup( - networkId = MockNetworks.network3.id, - name = MockNetworks.network3.name, + network = MockNetworks.network3, tokens = MockTokensStates.tokenStates .filter { it.networkId == MockNetworks.network3.id } .toNonEmptySetOrNull()!!, @@ -42,7 +39,7 @@ internal object MockTokenLists { val sortedNetworksGroups = networksGroups.map { group -> group.copy( tokens = MockTokensStates.loadedTokensStates - .filter { it.networkId == group.networkId } + .filter { it.networkId == group.network.id } .sortedByDescending { it.value.fiatAmount } .toNonEmptySetOrNull()!!, ) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListToContentItemsConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListToContentItemsConverter.kt index 731c766675..e488aa7b7f 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListToContentItemsConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListToContentItemsConverter.kt @@ -44,7 +44,7 @@ internal class TokenListToContentItemsConverter( } private fun MutableList.addGroup(group: NetworkGroup): List { - this.add(MultiCurrencyItem.NetworkGroupTitle(group.name)) + this.add(MultiCurrencyItem.NetworkGroupTitle(group.network.name)) group.tokens.forEach { token -> this.addToken(token)