Updated on 2026-08-14
This commit is contained in:
parent
e2ce1e4a97
commit
584d99e95e
15 changed files with 168 additions and 46 deletions
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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<TokenListSortingError, TokenList> {
|
||||
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<TokenListSortingError>.sortGroupedTokenList(
|
||||
tokenList: TokenList.GroupedByNetwork,
|
||||
): TokenList.GroupedByNetwork {
|
||||
val operations = getSortingOperations(tokenList)
|
||||
val networks = tokenList.groups.map { it.network }.toSet()
|
||||
|
||||
return tokenList.copy(
|
||||
groups = operations.getGroupedTokens(networks),
|
||||
sortedBy = operations.getSortType(),
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun Raise<TokenListSortingError>.sortUngroupedTokenList(
|
||||
tokenList: TokenList.Ungrouped,
|
||||
): TokenList.Ungrouped {
|
||||
val operations = getSortingOperations(tokenList)
|
||||
|
||||
return tokenList.copy(
|
||||
tokens = operations.getTokens(),
|
||||
sortedBy = operations.getSortType(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun Raise<TokenListSortingError>.getSortingOperations(tokenList: TokenList): TokenListSortingOperations<*> {
|
||||
return TokenListSortingOperations(
|
||||
tokenList = tokenList,
|
||||
sortByBalance = tokenList.sortedBy != TokenList.SortType.BALANCE,
|
||||
dispatchers = dispatchers,
|
||||
raise = this,
|
||||
transformError = TokenListSortingOperations.Error::mapToTokenListSortingError,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.domain.tokens.error
|
||||
|
||||
sealed class TokenListSortingError {
|
||||
|
||||
object TokenListIsLoading : TokenListSortingError()
|
||||
|
||||
object TokenListIsEmpty : TokenListSortingError()
|
||||
|
||||
object UnableToSortTokenList : TokenListSortingError()
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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<TokenStatus>,
|
||||
)
|
||||
|
|
@ -51,7 +51,7 @@ internal class TokenListOperations<E>(
|
|||
}
|
||||
|
||||
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<E>(
|
|||
val sortingOperations = TokenListSortingOperations(
|
||||
tokens = tokens,
|
||||
isAnyTokenLoading = isAnyTokenLoading,
|
||||
isSortedByBalance = isSortedByBalance,
|
||||
sortByBalance = isSortedByBalance,
|
||||
dispatchers = dispatchers,
|
||||
raise = this,
|
||||
transformError = { e ->
|
||||
|
|
|
|||
|
|
@ -17,20 +17,40 @@ import java.math.BigDecimal
|
|||
internal class TokenListSortingOperations<E>(
|
||||
private val tokens: Set<TokenStatus>,
|
||||
private val isAnyTokenLoading: Boolean,
|
||||
private val isSortedByBalance: Boolean,
|
||||
private val sortByBalance: Boolean,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
raise: Raise<E>,
|
||||
transformError: (Error) -> E,
|
||||
) : DelegatedRaise<TokenListSortingOperations.Error, E>(raise, transformError) {
|
||||
|
||||
constructor(
|
||||
tokenList: TokenList,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
raise: Raise<E>,
|
||||
transformError: (Error) -> E,
|
||||
sortByBalance: Boolean = tokenList.sortedBy == TokenList.SortType.BALANCE,
|
||||
isAnyTokenLoading: Boolean = tokenList.totalFiatBalance is TokenList.FiatBalance.Loading,
|
||||
) : this(
|
||||
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<Network>): NonEmptySet<NetworkGroup> {
|
||||
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<E>(
|
|||
}
|
||||
|
||||
suspend fun getTokens(): NonEmptySet<TokenStatus> {
|
||||
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<Network>): NonEmptySet<NetworkGroup> {
|
||||
val groupedTokens = tokens
|
||||
|
|
@ -59,8 +79,7 @@ internal class TokenListSortingOperations<E>(
|
|||
}
|
||||
|
||||
NetworkGroup(
|
||||
networkId = network.id,
|
||||
name = network.name,
|
||||
network = network,
|
||||
tokens = ensureNotNull(tokens.toNonEmptySetOrNull()) { Error.EmptyTokens },
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ internal class TokensStatusesOperations<E>(
|
|||
tokens: Set<Token>,
|
||||
quotes: Set<Quote>,
|
||||
networkStatuses: Set<NetworkStatus>,
|
||||
): Set<TokenStatus> = withContext(dispatchers.single) {
|
||||
): Set<TokenStatus> = 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<E>(
|
|||
}
|
||||
|
||||
private suspend fun groupTokens(tokens: NonEmptySet<Token>): Map<Network.ID, NonEmptySet<Token.ID>> =
|
||||
withContext(dispatchers.single) {
|
||||
withContext(dispatchers.default) {
|
||||
tokens
|
||||
.groupBy { it.networkId }
|
||||
.mapValues { (_, tokens) ->
|
||||
|
|
|
|||
|
|
@ -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()!!,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ internal class TokenListToContentItemsConverter(
|
|||
}
|
||||
|
||||
private fun MutableList<MultiCurrencyItem>.addGroup(group: NetworkGroup): List<MultiCurrencyItem> {
|
||||
this.add(MultiCurrencyItem.NetworkGroupTitle(group.name))
|
||||
this.add(MultiCurrencyItem.NetworkGroupTitle(group.network.name))
|
||||
|
||||
group.tokens.forEach { token ->
|
||||
this.addToken(token)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue