Updated on 2026-08-14

This commit is contained in:
Tangem 2023-08-07 13:01:47 +03:00
parent dd186d6fbc
commit 3f5f3ef68b
23 changed files with 351 additions and 128 deletions

View file

@ -1,9 +1,9 @@
package com.tangem.tap.di.domain package com.tangem.tap.di.domain
import com.tangem.domain.tokens.* import com.tangem.domain.tokens.*
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.NetworksRepository import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.tokens.repository.QuotesRepository import com.tangem.domain.tokens.repository.QuotesRepository
import com.tangem.domain.tokens.repository.TokensRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
@ -18,23 +18,23 @@ internal object TokensDomainModule {
@Provides @Provides
@ViewModelScoped @ViewModelScoped
fun provideGetTokenListUseCase( fun provideGetTokenListUseCase(
tokensRepository: TokensRepository, currenciesRepository: CurrenciesRepository,
quotesRepository: QuotesRepository, quotesRepository: QuotesRepository,
networksRepository: NetworksRepository, networksRepository: NetworksRepository,
dispatchers: CoroutineDispatcherProvider, dispatchers: CoroutineDispatcherProvider,
): GetTokenListUseCase { ): GetTokenListUseCase {
return GetTokenListUseCase(tokensRepository, quotesRepository, networksRepository, dispatchers) return GetTokenListUseCase(currenciesRepository, quotesRepository, networksRepository, dispatchers)
} }
@Provides @Provides
@ViewModelScoped @ViewModelScoped
fun provideGetPrimaryCurrencyUseCase( fun provideGetPrimaryCurrencyUseCase(
tokensRepository: TokensRepository, currenciesRepository: CurrenciesRepository,
quotesRepository: QuotesRepository, quotesRepository: QuotesRepository,
networksRepository: NetworksRepository, networksRepository: NetworksRepository,
dispatchers: CoroutineDispatcherProvider, dispatchers: CoroutineDispatcherProvider,
): GetPrimaryCurrencyUseCase { ): GetCurrencyUseCase {
return GetPrimaryCurrencyUseCase(tokensRepository, quotesRepository, networksRepository, dispatchers) return GetCurrencyUseCase(currenciesRepository, quotesRepository, networksRepository, dispatchers)
} }
@Provides @Provides
@ -55,9 +55,9 @@ internal object TokensDomainModule {
@Provides @Provides
@ViewModelScoped @ViewModelScoped
fun provideApplyTokenListSortingUseCase( fun provideApplyTokenListSortingUseCase(
tokensRepository: TokensRepository, currenciesRepository: CurrenciesRepository,
dispatchers: CoroutineDispatcherProvider, dispatchers: CoroutineDispatcherProvider,
): ApplyTokenListSortingUseCase { ): ApplyTokenListSortingUseCase {
return ApplyTokenListSortingUseCase(tokensRepository, dispatchers) return ApplyTokenListSortingUseCase(currenciesRepository, dispatchers)
} }
} }

View file

@ -1,15 +1,15 @@
package com.tangem.data.tokens.di package com.tangem.data.tokens.di
import com.tangem.data.common.cache.CacheRegistry import com.tangem.data.common.cache.CacheRegistry
import com.tangem.data.tokens.repository.DefaultCurrenciesRepository
import com.tangem.data.tokens.repository.DefaultNetworksRepository import com.tangem.data.tokens.repository.DefaultNetworksRepository
import com.tangem.data.tokens.repository.DefaultTokensRepository
import com.tangem.data.tokens.repository.MockQuotesRepository import com.tangem.data.tokens.repository.MockQuotesRepository
import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.local.token.UserTokensStore import com.tangem.datasource.local.token.UserTokensStore
import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.NetworksRepository import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.tokens.repository.QuotesRepository import com.tangem.domain.tokens.repository.QuotesRepository
import com.tangem.domain.tokens.repository.TokensRepository
import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module import dagger.Module
@ -24,14 +24,14 @@ internal object TokensDataModule {
@Provides @Provides
@Singleton @Singleton
fun provideTokensRepository( fun provideCurrenciesRepository(
tangemTechApi: TangemTechApi, tangemTechApi: TangemTechApi,
userTokensStore: UserTokensStore, userTokensStore: UserTokensStore,
userWalletsStore: UserWalletsStore, userWalletsStore: UserWalletsStore,
cacheRegistry: CacheRegistry, cacheRegistry: CacheRegistry,
dispatchers: CoroutineDispatcherProvider, dispatchers: CoroutineDispatcherProvider,
): TokensRepository { ): CurrenciesRepository {
return DefaultTokensRepository(tangemTechApi, userTokensStore, userWalletsStore, cacheRegistry, dispatchers) return DefaultCurrenciesRepository(tangemTechApi, userTokensStore, userWalletsStore, cacheRegistry, dispatchers)
} }
@Provides @Provides

View file

@ -8,23 +8,27 @@ import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.datasource.local.token.UserTokensStore import com.tangem.datasource.local.token.UserTokensStore
import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.core.error.DataError
import com.tangem.domain.demo.DemoConfig import com.tangem.domain.demo.DemoConfig
import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.repository.TokensRepository import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import timber.log.Timber
internal class DefaultTokensRepository( internal class DefaultCurrenciesRepository(
private val tangemTechApi: TangemTechApi, private val tangemTechApi: TangemTechApi,
private val userTokensStore: UserTokensStore, private val userTokensStore: UserTokensStore,
private val userWalletsStore: UserWalletsStore, private val userWalletsStore: UserWalletsStore,
private val cacheRegistry: CacheRegistry, private val cacheRegistry: CacheRegistry,
private val dispatchers: CoroutineDispatcherProvider, private val dispatchers: CoroutineDispatcherProvider,
) : TokensRepository { ) : CurrenciesRepository {
private val demoConfig = DemoConfig() private val demoConfig = DemoConfig()
private val responseCurrenciesFactory = ResponseCurrenciesFactory(demoConfig) private val responseCurrenciesFactory = ResponseCurrenciesFactory(demoConfig)
@ -37,6 +41,8 @@ internal class DefaultTokensRepository(
isGroupedByNetwork: Boolean, isGroupedByNetwork: Boolean,
isSortedByBalance: Boolean, isSortedByBalance: Boolean,
) = withContext(dispatchers.io) { ) = withContext(dispatchers.io) {
ensureIsCorrectUserWallet(userWalletId, isMultiCurrencyWalletExpected = true)
val response = userTokensResponseFactory.createUserTokensResponse( val response = userTokensResponseFactory.createUserTokensResponse(
currencies = currencies, currencies = currencies,
isGroupedByNetwork = isGroupedByNetwork, isGroupedByNetwork = isGroupedByNetwork,
@ -46,58 +52,72 @@ internal class DefaultTokensRepository(
storeAndPushTokens(userWalletId, response) storeAndPushTokens(userWalletId, response)
} }
override suspend fun getPrimaryCurrency(userWalletId: UserWalletId): CryptoCurrency { override suspend fun getSingleCurrencyWalletPrimaryCurrency(userWalletId: UserWalletId): CryptoCurrency {
val userWallet = withContext(dispatchers.io) { return withContext(dispatchers.io) {
requireNotNull(userWalletsStore.getSyncOrNull(userWalletId)) { val userWallet = getUserWallet(userWalletId)
"Unable to find a user wallet with provided ID: $userWalletId" ensureIsCorrectUserWallet(userWallet, isMultiCurrencyWalletExpected = false)
}
}
require(!userWallet.isMultiCurrency) {
"Single currency wallet excepted, but multi currency wallet was found: $userWalletId"
}
return cardCurrenciesFactory.createPrimaryCurrencyForSingleCurrencyCard(userWallet.scanResponse) cardCurrenciesFactory.createPrimaryCurrencyForSingleCurrencyCard(userWallet.scanResponse)
}
} }
override fun getMultiCurrencyWalletCurrencies( override fun getMultiCurrencyWalletCurrencies(
userWalletId: UserWalletId, userWalletId: UserWalletId,
refresh: Boolean, refresh: Boolean,
): Flow<Set<CryptoCurrency>> { ): Flow<Set<CryptoCurrency>> = channelFlow {
val userWallet = getUserWallet(userWalletId)
ensureIsCorrectUserWallet(userWallet, isMultiCurrencyWalletExpected = true)
launch(dispatchers.io) {
getMultiCurrencyWalletCurrencies(userWallet).collect(::send)
}
launch(dispatchers.io) {
fetchTokensIfCacheExpired(userWallet, refresh)
}
}
override suspend fun getMultiCurrencyWalletCurrency(
userWalletId: UserWalletId,
id: CryptoCurrency.ID,
): CryptoCurrency = withContext(dispatchers.io) {
val userWallet = getUserWallet(userWalletId)
ensureIsCorrectUserWallet(userWallet, isMultiCurrencyWalletExpected = true)
val response = requireNotNull(userTokensStore.getSyncOrNull(userWalletId)) {
"Unable to find tokens response for user wallet with provided ID: $userWalletId"
}
responseCurrenciesFactory.createCurrency(id, response, userWallet.scanResponse.card)
}
override fun isTokensGrouped(userWalletId: UserWalletId): Flow<Boolean> {
return channelFlow { return channelFlow {
val userWallet = withContext(dispatchers.io) { ensureIsCorrectUserWallet(userWalletId, isMultiCurrencyWalletExpected = true)
requireNotNull(userWalletsStore.getSyncOrNull(userWalletId)) {
"Unable to find a user wallet with provided ID: $userWalletId"
}
}
require(userWallet.isMultiCurrency) {
"Multi currency wallet excepted, but single currency wallet was found: $userWalletId"
}
launch(dispatchers.io) { launch(dispatchers.io) {
getMultiCurrencyWalletCurrencies(userWallet).collectLatest(::send) userTokensStore.get(userWalletId)
} .map { it.group == UserTokensResponse.GroupType.NETWORK }
.collect(::send)
launch(dispatchers.io) {
fetchTokensIfCacheExpired(userWallet, refresh)
} }
} }
} }
override fun isTokensGrouped(userWalletId: UserWalletId): Flow<Boolean> {
return userTokensStore.get(userWalletId)
.map { it.group == UserTokensResponse.GroupType.NETWORK }
.flowOn(dispatchers.io)
}
override fun isTokensSortedByBalance(userWalletId: UserWalletId): Flow<Boolean> { override fun isTokensSortedByBalance(userWalletId: UserWalletId): Flow<Boolean> {
return userTokensStore.get(userWalletId) return channelFlow {
.map { it.sort == UserTokensResponse.SortType.BALANCE } ensureIsCorrectUserWallet(userWalletId, isMultiCurrencyWalletExpected = true)
.flowOn(dispatchers.io)
launch(dispatchers.io) {
userTokensStore.get(userWalletId)
.map { it.sort == UserTokensResponse.SortType.BALANCE }
.collect(::send)
}
}
} }
private fun getMultiCurrencyWalletCurrencies(userWallet: UserWallet): Flow<Set<CryptoCurrency>> { private fun getMultiCurrencyWalletCurrencies(userWallet: UserWallet): Flow<Set<CryptoCurrency>> {
return userTokensStore.get(userWallet.walletId).map { storedTokens -> return userTokensStore.get(userWallet.walletId).map { storedTokens ->
responseCurrenciesFactory.createTokens( responseCurrenciesFactory.createCurrencies(
response = storedTokens, response = storedTokens,
card = userWallet.scanResponse.card, card = userWallet.scanResponse.card,
) )
@ -146,6 +166,39 @@ internal class DefaultTokensRepository(
} }
} }
private suspend fun getUserWallet(userWalletId: UserWalletId): UserWallet {
return requireNotNull(userWalletsStore.getSyncOrNull(userWalletId)) {
"Unable to find a user wallet with provided ID: $userWalletId"
}
}
private suspend fun ensureIsCorrectUserWallet(userWalletId: UserWalletId, isMultiCurrencyWalletExpected: Boolean) {
val userWallet = getUserWallet(userWalletId)
ensureIsCorrectUserWallet(userWallet, isMultiCurrencyWalletExpected)
}
private fun ensureIsCorrectUserWallet(userWallet: UserWallet, isMultiCurrencyWalletExpected: Boolean) {
val userWalletId = userWallet.walletId
val message = when {
!userWallet.isMultiCurrency && isMultiCurrencyWalletExpected -> {
"Multi currency wallet expected, but single currency wallet was found: $userWalletId"
}
userWallet.isMultiCurrency && !isMultiCurrencyWalletExpected -> {
"Single currency wallet expected, but multi currency wallet was found: $userWalletId"
}
else -> null
}
if (message != null) {
val error = DataError.UserWalletError.WrongUserWallet(message)
Timber.e(error)
throw error
}
}
private fun getTokensCacheKey(userWalletId: UserWalletId): String = "tokens_cache_key_${userWalletId.stringValue}" private fun getTokensCacheKey(userWalletId: UserWalletId): String = "tokens_cache_key_${userWalletId.stringValue}"
private companion object { private companion object {

View file

@ -18,7 +18,10 @@ import com.tangem.utils.extensions.addOrReplace
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
internal class DefaultNetworksRepository( internal class DefaultNetworksRepository(
@ -44,7 +47,9 @@ internal class DefaultNetworksRepository(
networks: Set<Network.ID>, networks: Set<Network.ID>,
refresh: Boolean, refresh: Boolean,
): Flow<Set<NetworkStatus>> = channelFlow { ): Flow<Set<NetworkStatus>> = channelFlow {
networksStatuses.collectLatest(::send) launch(dispatchers.io) {
networksStatuses.collect(::send)
}
launch(dispatchers.io) { launch(dispatchers.io) {
fetchNetworksStatusesIfCacheExpired(userWalletId, networks, refresh) fetchNetworksStatusesIfCacheExpired(userWalletId, networks, refresh)
@ -99,7 +104,7 @@ internal class DefaultNetworksRepository(
"Unable to find tokens response for user wallet with provided ID: $userWalletId" "Unable to find tokens response for user wallet with provided ID: $userWalletId"
} }
return responseCurrenciesFactory.createTokens(response, userWallet.scanResponse.card) return responseCurrenciesFactory.createCurrencies(response, userWallet.scanResponse.card)
} }
private fun getNetworksStatusesCacheKey(userWalletId: UserWalletId): String = "network_status_$userWalletId" private fun getNetworksStatusesCacheKey(userWalletId: UserWalletId): String = "network_status_$userWalletId"

View file

@ -40,7 +40,7 @@ internal class NetworkStatusFactory {
is CryptoCurrency.Coin -> amounts.singleOrNull { it is CryptoCurrencyAmount.Coin } is CryptoCurrency.Coin -> amounts.singleOrNull { it is CryptoCurrencyAmount.Coin }
is CryptoCurrency.Token -> amounts.singleOrNull { is CryptoCurrency.Token -> amounts.singleOrNull {
it is CryptoCurrencyAmount.Token && it is CryptoCurrencyAmount.Token &&
it.id == getTokenIdString(currency) && it.id == getTokenIdString(currency.id) &&
it.tokenContractAddress == currency.contractAddress it.tokenContractAddress == currency.contractAddress
} }
}?.value }?.value

View file

@ -12,11 +12,23 @@ import com.tangem.blockchain.common.Token as SdkToken
internal class ResponseCurrenciesFactory(private val demoConfig: DemoConfig) { internal class ResponseCurrenciesFactory(private val demoConfig: DemoConfig) {
fun createTokens(response: UserTokensResponse, card: CardDTO): Set<CryptoCurrency> { fun createCurrency(currencyId: CryptoCurrency.ID, response: UserTokensResponse, card: CardDTO): CryptoCurrency {
return response.tokens.mapNotNull { createToken(it, card) }.toSet() val responseTokenId = getTokenIdString(currencyId)
val token = requireNotNull(response.tokens.firstOrNull { it.id == responseTokenId }) {
"Unable find a token with provided ID: $responseTokenId"
}
return requireNotNull(createCurrency(token, card)) {
"Unable to create a currency with provided ID: $currencyId"
}
} }
private fun createToken(responseToken: UserTokensResponse.Token, card: CardDTO): CryptoCurrency? { fun createCurrencies(response: UserTokensResponse, card: CardDTO): Set<CryptoCurrency> {
return response.tokens.mapNotNull { createCurrency(it, card) }.toSet()
}
private fun createCurrency(responseToken: UserTokensResponse.Token, card: CardDTO): CryptoCurrency? {
var blockchain = Blockchain.fromNetworkId(responseToken.networkId) var blockchain = Blockchain.fromNetworkId(responseToken.networkId)
if (blockchain == null || blockchain == Blockchain.Unknown) { if (blockchain == null || blockchain == Blockchain.Unknown) {
Timber.e("Unable to find a blockchain with the network ID: ${responseToken.networkId}") Timber.e("Unable to find a blockchain with the network ID: ${responseToken.networkId}")

View file

@ -49,9 +49,14 @@ internal fun getTokenId(blockchain: Blockchain, token: SdkToken): CryptoCurrency
return getTokenOrCoinId(blockchain, token) return getTokenOrCoinId(blockchain, token)
} }
internal fun getTokenIdString(currency: CryptoCurrency): String? { internal fun getTokenIdString(currencyId: CryptoCurrency.ID): String? {
return currency.id.value.substringAfter(TOKEN_ID_DELIMITER) val idValue = currencyId.value
.takeUnless { currency is CryptoCurrency.Token && currency.isCustom }
return if (idValue.startsWith(CUSTOM_TOKEN_ID_PREFIX)) {
null
} else {
idValue.substringAfter(TOKEN_ID_DELIMITER)
}
} }
internal fun getTokenIconUrl(blockchain: Blockchain, token: SdkToken): String? { internal fun getTokenIconUrl(blockchain: Blockchain, token: SdkToken): String? {

View file

@ -30,7 +30,7 @@ internal class UserTokensResponseFactory {
val blockchain = getBlockchain(currency.networkId) val blockchain = getBlockchain(currency.networkId)
return UserTokensResponse.Token( return UserTokensResponse.Token(
id = getTokenIdString(currency), id = getTokenIdString(currency.id),
networkId = blockchain.toNetworkId(), networkId = blockchain.toNetworkId(),
derivationPath = currency.derivationPath, derivationPath = currency.derivationPath,
name = currency.name, name = currency.name,

View file

@ -6,4 +6,9 @@ sealed class DataError : Exception() {
object NoInternetConnection : NetworkError() object NoInternetConnection : NetworkError()
} }
sealed class UserWalletError : DataError() {
data class WrongUserWallet(override val message: String) : UserWalletError()
}
} }

View file

@ -9,14 +9,14 @@ import arrow.core.toNonEmptySetOrNull
import com.tangem.domain.tokens.error.TokenListSortingError import com.tangem.domain.tokens.error.TokenListSortingError
import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.models.Network import com.tangem.domain.tokens.models.Network
import com.tangem.domain.tokens.repository.TokensRepository import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
class ApplyTokenListSortingUseCase( class ApplyTokenListSortingUseCase(
private val tokensRepository: TokensRepository, private val currenciesRepository: CurrenciesRepository,
private val dispatchers: CoroutineDispatcherProvider, private val dispatchers: CoroutineDispatcherProvider,
) { ) {
@ -67,7 +67,9 @@ class ApplyTokenListSortingUseCase(
private suspend fun Raise<TokenListSortingError>.getCurrencies(userWalletId: UserWalletId): Set<CryptoCurrency> { private suspend fun Raise<TokenListSortingError>.getCurrencies(userWalletId: UserWalletId): Set<CryptoCurrency> {
val tokens = catch( val tokens = catch(
block = { tokensRepository.getMultiCurrencyWalletCurrencies(userWalletId, refresh = false).firstOrNull() }, block = {
currenciesRepository.getMultiCurrencyWalletCurrencies(userWalletId, refresh = false).firstOrNull()
},
catch = { raise(TokenListSortingError.DataError(it)) }, catch = { raise(TokenListSortingError.DataError(it)) },
) )
@ -83,7 +85,7 @@ class ApplyTokenListSortingUseCase(
isSortedByBalance: Boolean, isSortedByBalance: Boolean,
) = withContext(dispatchers.io) { ) = withContext(dispatchers.io) {
catch( catch(
block = { tokensRepository.saveTokens(userWalletId, tokens, isGrouped, isSortedByBalance) }, block = { currenciesRepository.saveTokens(userWalletId, tokens, isGrouped, isSortedByBalance) },
catch = { raise(TokenListSortingError.DataError(it)) }, catch = { raise(TokenListSortingError.DataError(it)) },
) )
} }

View file

@ -0,0 +1,82 @@
package com.tangem.domain.tokens
import arrow.core.Either
import arrow.core.left
import arrow.core.raise.Raise
import arrow.core.raise.recover
import arrow.core.right
import com.tangem.domain.tokens.error.CurrencyError
import com.tangem.domain.tokens.error.mapper.mapToTokenError
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.tokens.repository.QuotesRepository
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.collectLatest
/**
* Use case for fetching the status of a specific cryptocurrency associated with a user wallet.
*
* @property currenciesRepository Repository for managing and fetching cryptocurrencies.
* @property quotesRepository Repository for managing and fetching cryptocurrency quotes.
* @property networksRepository Repository for managing and fetching information related to blockchain networks.
* @property dispatchers Provides coroutine dispatchers.
*/
class GetCurrencyUseCase(
private val currenciesRepository: CurrenciesRepository,
private val quotesRepository: QuotesRepository,
private val networksRepository: NetworksRepository,
private val dispatchers: CoroutineDispatcherProvider,
) {
/**
* Invokes the use case.
*
* @param userWalletId The unique identifier of the user's wallet.
* @param currencyId The unique identifier of the cryptocurrency.
* @param refresh A boolean flag indicating whether the data should be refreshed.
* @return A [Flow] emitting either a [CurrencyError] or a [CryptoCurrencyStatus], indicating the result of the fetch operation.
*/
operator fun invoke(
userWalletId: UserWalletId,
currencyId: CryptoCurrency.ID,
refresh: Boolean = false,
): Flow<Either<CurrencyError, CryptoCurrencyStatus>> {
return channelFlow {
recover(
block = {
getCurrency(userWalletId, currencyId, refresh).collectLatest { currencyStatus ->
send(currencyStatus.right())
}
},
recover = { error ->
send(error.left())
},
)
}
}
private suspend fun Raise<CurrencyError>.getCurrency(
userWalletId: UserWalletId,
currencyId: CryptoCurrency.ID,
refresh: Boolean,
): Flow<CryptoCurrencyStatus> {
val operations = CurrenciesStatusesOperations(
currenciesRepository = currenciesRepository,
quotesRepository = quotesRepository,
networksRepository = networksRepository,
userWalletId = userWalletId,
refresh = refresh,
dispatchers = dispatchers,
raise = this,
transformError = CurrenciesStatusesOperations.Error::mapToTokenError,
)
return operations.getCurrencyStatusFlow(currencyId)
}
}

View file

@ -5,35 +5,50 @@ import arrow.core.left
import arrow.core.raise.Raise import arrow.core.raise.Raise
import arrow.core.raise.recover import arrow.core.raise.recover
import arrow.core.right import arrow.core.right
import com.tangem.domain.tokens.error.TokenError import com.tangem.domain.tokens.error.CurrencyError
import com.tangem.domain.tokens.error.mapper.mapToTokenError import com.tangem.domain.tokens.error.mapper.mapToTokenError
import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.NetworksRepository import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.tokens.repository.QuotesRepository import com.tangem.domain.tokens.repository.QuotesRepository
import com.tangem.domain.tokens.repository.TokensRepository
import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
/**
* Use case for fetching the status of the primary cryptocurrency associated with a user wallet.
*
* @property currenciesRepository Repository for managing and fetching cryptocurrencies.
* @property quotesRepository Repository for managing and fetching cryptocurrency quotes.
* @property networksRepository Repository for managing and fetching information related to blockchain networks.
* @property dispatchers Provides coroutine dispatchers.
*/
class GetPrimaryCurrencyUseCase( class GetPrimaryCurrencyUseCase(
private val tokensRepository: TokensRepository, private val currenciesRepository: CurrenciesRepository,
private val quotesRepository: QuotesRepository, private val quotesRepository: QuotesRepository,
private val networksRepository: NetworksRepository, private val networksRepository: NetworksRepository,
private val dispatchers: CoroutineDispatcherProvider, private val dispatchers: CoroutineDispatcherProvider,
) { ) {
/**
* Invokes the use case.
*
* @param userWalletId The unique identifier of the user's wallet.
* @param refresh A boolean flag indicating whether the data should be refreshed.
* @return A [Flow] emitting either a [CurrencyError] or a [CryptoCurrencyStatus], indicating the result of the fetch operation.
*/
operator fun invoke( operator fun invoke(
userWalletId: UserWalletId, userWalletId: UserWalletId,
refresh: Boolean = false, refresh: Boolean = false,
): Flow<Either<TokenError, CryptoCurrencyStatus>> { ): Flow<Either<CurrencyError, CryptoCurrencyStatus>> {
return channelFlow { return channelFlow {
recover( recover(
block = { block = {
getToken(userWalletId, refresh).collectLatest { token -> getCurrency(userWalletId, refresh).collectLatest { currencyStatus ->
send(token.right()) send(currencyStatus.right())
} }
}, },
recover = { error -> recover = { error ->
@ -43,12 +58,12 @@ class GetPrimaryCurrencyUseCase(
} }
} }
private suspend fun Raise<TokenError>.getToken( private suspend fun Raise<CurrencyError>.getCurrency(
userWalletId: UserWalletId, userWalletId: UserWalletId,
refresh: Boolean, refresh: Boolean,
): Flow<CryptoCurrencyStatus> { ): Flow<CryptoCurrencyStatus> {
val operations = CurrenciesStatusesOperations( val operations = CurrenciesStatusesOperations(
tokensRepository = tokensRepository, currenciesRepository = currenciesRepository,
quotesRepository = quotesRepository, quotesRepository = quotesRepository,
networksRepository = networksRepository, networksRepository = networksRepository,
userWalletId = userWalletId, userWalletId = userWalletId,

View file

@ -11,9 +11,9 @@ import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.TokenList import com.tangem.domain.tokens.model.TokenList
import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations
import com.tangem.domain.tokens.operations.TokenListOperations import com.tangem.domain.tokens.operations.TokenListOperations
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.NetworksRepository import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.tokens.repository.QuotesRepository import com.tangem.domain.tokens.repository.QuotesRepository
import com.tangem.domain.tokens.repository.TokensRepository
import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@ -22,7 +22,7 @@ import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.flatMapConcat import kotlinx.coroutines.flow.flatMapConcat
class GetTokenListUseCase( class GetTokenListUseCase(
internal val tokensRepository: TokensRepository, internal val currenciesRepository: CurrenciesRepository,
internal val quotesRepository: QuotesRepository, internal val quotesRepository: QuotesRepository,
internal val networksRepository: NetworksRepository, internal val networksRepository: NetworksRepository,
internal val dispatchers: CoroutineDispatcherProvider, internal val dispatchers: CoroutineDispatcherProvider,
@ -60,7 +60,7 @@ class GetTokenListUseCase(
transformError = CurrenciesStatusesOperations.Error::mapToTokenListError, transformError = CurrenciesStatusesOperations.Error::mapToTokenListError,
) )
return operations.getMultiCurrencyWalletStatusesFlow() return operations.getCurrenciesStatusesFlow()
} }
private fun Raise<TokenListError>.createTokenList( private fun Raise<TokenListError>.createTokenList(

View file

@ -0,0 +1,8 @@
package com.tangem.domain.tokens.error
sealed class CurrencyError {
object UnableToCreateCurrency : CurrencyError()
data class DataError(val cause: Throwable) : CurrencyError()
}

View file

@ -1,8 +0,0 @@
package com.tangem.domain.tokens.error
sealed class TokenError {
object UnableToCreateToken : TokenError()
data class DataError(val cause: Throwable) : TokenError()
}

View file

@ -1,15 +1,15 @@
package com.tangem.domain.tokens.error.mapper package com.tangem.domain.tokens.error.mapper
import com.tangem.domain.tokens.error.TokenError import com.tangem.domain.tokens.error.CurrencyError
import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations
internal fun CurrenciesStatusesOperations.Error.mapToTokenError(): TokenError { internal fun CurrenciesStatusesOperations.Error.mapToTokenError(): CurrencyError {
return when (this) { return when (this) {
is CurrenciesStatusesOperations.Error.DataError -> TokenError.DataError(this.cause) is CurrenciesStatusesOperations.Error.DataError -> CurrencyError.DataError(this.cause)
is CurrenciesStatusesOperations.Error.EmptyNetworksStatuses, is CurrenciesStatusesOperations.Error.EmptyNetworksStatuses,
is CurrenciesStatusesOperations.Error.EmptyQuotes, is CurrenciesStatusesOperations.Error.EmptyQuotes,
is CurrenciesStatusesOperations.Error.EmptyCurrencies, is CurrenciesStatusesOperations.Error.EmptyCurrencies,
is CurrenciesStatusesOperations.Error.UnableToCreateCurrencyStatus, is CurrenciesStatusesOperations.Error.UnableToCreateCurrencyStatus,
-> TokenError.UnableToCreateToken -> CurrencyError.UnableToCreateCurrency
} }
} }

View file

@ -7,9 +7,9 @@ import com.tangem.domain.core.raise.DelegatedRaise
import com.tangem.domain.tokens.GetTokenListUseCase import com.tangem.domain.tokens.GetTokenListUseCase
import com.tangem.domain.tokens.model.* import com.tangem.domain.tokens.model.*
import com.tangem.domain.tokens.models.Network import com.tangem.domain.tokens.models.Network
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.NetworksRepository import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.tokens.repository.QuotesRepository import com.tangem.domain.tokens.repository.QuotesRepository
import com.tangem.domain.tokens.repository.TokensRepository
import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
@ -17,7 +17,7 @@ import kotlinx.coroutines.withContext
@Suppress("LongParameterList") @Suppress("LongParameterList")
internal class CurrenciesStatusesOperations<E>( internal class CurrenciesStatusesOperations<E>(
private val tokensRepository: TokensRepository, private val currenciesRepository: CurrenciesRepository,
private val quotesRepository: QuotesRepository, private val quotesRepository: QuotesRepository,
private val networksRepository: NetworksRepository, private val networksRepository: NetworksRepository,
private val userWalletId: UserWalletId, private val userWalletId: UserWalletId,
@ -34,7 +34,7 @@ internal class CurrenciesStatusesOperations<E>(
raise: Raise<E>, raise: Raise<E>,
transformError: (Error) -> E, transformError: (Error) -> E,
) : this( ) : this(
tokensRepository = useCase.tokensRepository, currenciesRepository = useCase.currenciesRepository,
quotesRepository = useCase.quotesRepository, quotesRepository = useCase.quotesRepository,
networksRepository = useCase.networksRepository, networksRepository = useCase.networksRepository,
userWalletId = userWalletId, userWalletId = userWalletId,
@ -44,7 +44,7 @@ internal class CurrenciesStatusesOperations<E>(
transformError = transformError, transformError = transformError,
) )
fun getMultiCurrencyWalletStatusesFlow(): Flow<Set<CryptoCurrencyStatus>> { fun getCurrenciesStatusesFlow(): Flow<Set<CryptoCurrencyStatus>> {
return getMultiCurrencyWalletCurrencies().flatMapConcat { return getMultiCurrencyWalletCurrencies().flatMapConcat {
val currencies = it.toNonEmptySetOrNull() val currencies = it.toNonEmptySetOrNull()
@ -68,9 +68,19 @@ internal class CurrenciesStatusesOperations<E>(
} }
} }
suspend fun getCurrencyStatusFlow(currencyId: CryptoCurrency.ID): Flow<CryptoCurrencyStatus> {
val currency = getMultiCurrencyWalletCurrency(currencyId)
return getCurrencyStatusFlow(currency)
}
suspend fun getPrimaryCurrencyStatusFlow(): Flow<CryptoCurrencyStatus> { suspend fun getPrimaryCurrencyStatusFlow(): Flow<CryptoCurrencyStatus> {
val currency = getPrimaryCurrency() val currency = getPrimaryCurrency()
return getCurrencyStatusFlow(currency)
}
private fun getCurrencyStatusFlow(currency: CryptoCurrency): Flow<CryptoCurrencyStatus> {
val quoteFlow = getQuotes(nonEmptySetOf(currency.id)) val quoteFlow = getQuotes(nonEmptySetOf(currency.id))
.map { quotes -> .map { quotes ->
quotes.singleOrNull { it.currencyId == currency.id } quotes.singleOrNull { it.currencyId == currency.id }
@ -116,34 +126,36 @@ internal class CurrenciesStatusesOperations<E>(
return currencyStatusOperations.createTokenStatus() return currencyStatusOperations.createTokenStatus()
} }
private suspend fun getMultiCurrencyWalletCurrency(currencyId: CryptoCurrency.ID): CryptoCurrency {
return catch(
block = { currenciesRepository.getMultiCurrencyWalletCurrency(userWalletId, currencyId) },
catch = { raise(Error.DataError(it)) },
)
}
private fun getMultiCurrencyWalletCurrencies(): Flow<Set<CryptoCurrency>> { private fun getMultiCurrencyWalletCurrencies(): Flow<Set<CryptoCurrency>> {
return tokensRepository.getMultiCurrencyWalletCurrencies(userWalletId, refresh) return currenciesRepository.getMultiCurrencyWalletCurrencies(userWalletId, refresh)
.catch { raise(Error.DataError(it)) } .catch { raise(Error.DataError(it)) }
.onEmpty { raise(Error.EmptyCurrencies) } .onEmpty { raise(Error.EmptyCurrencies) }
.flowOn(dispatchers.io)
} }
private suspend fun getPrimaryCurrency(): CryptoCurrency { private suspend fun getPrimaryCurrency(): CryptoCurrency {
return withContext(dispatchers.io) { return catch(
catch( block = { currenciesRepository.getSingleCurrencyWalletPrimaryCurrency(userWalletId) },
block = { tokensRepository.getPrimaryCurrency(userWalletId) }, catch = { raise(Error.DataError(it)) },
catch = { raise(Error.DataError(it)) }, )
)
}
} }
private fun getQuotes(tokensIds: NonEmptySet<CryptoCurrency.ID>): Flow<Set<Quote>> { private fun getQuotes(tokensIds: NonEmptySet<CryptoCurrency.ID>): Flow<Set<Quote>> {
return quotesRepository.getQuotes(tokensIds, refresh) return quotesRepository.getQuotes(tokensIds, refresh)
.catch { raise(Error.DataError(it)) } .catch { raise(Error.DataError(it)) }
.onEmpty { raise(Error.EmptyQuotes) } .onEmpty { raise(Error.EmptyQuotes) }
.flowOn(dispatchers.io)
} }
private fun getNetworksStatues(networks: NonEmptySet<Network.ID>): Flow<Set<NetworkStatus>> { private fun getNetworksStatues(networks: NonEmptySet<Network.ID>): Flow<Set<NetworkStatus>> {
return networksRepository.getNetworkStatuses(userWalletId, networks, refresh) return networksRepository.getNetworkStatuses(userWalletId, networks, refresh)
.catch { raise(Error.DataError(it)) } .catch { raise(Error.DataError(it)) }
.onEmpty { raise(Error.EmptyNetworksStatuses) } .onEmpty { raise(Error.EmptyNetworksStatuses) }
.flowOn(dispatchers.io)
} }
sealed class Error { sealed class Error {

View file

@ -10,8 +10,8 @@ import com.tangem.domain.tokens.GetTokenListUseCase
import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.TokenList import com.tangem.domain.tokens.model.TokenList
import com.tangem.domain.tokens.models.Network import com.tangem.domain.tokens.models.Network
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.NetworksRepository import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.tokens.repository.TokensRepository
import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
@ -19,7 +19,7 @@ import kotlinx.coroutines.withContext
@Suppress("LongParameterList") @Suppress("LongParameterList")
internal class TokenListOperations<E>( internal class TokenListOperations<E>(
private val tokensRepository: TokensRepository, private val currenciesRepository: CurrenciesRepository,
private val networksRepository: NetworksRepository, private val networksRepository: NetworksRepository,
private val userWalletId: UserWalletId, private val userWalletId: UserWalletId,
private val tokens: Set<CryptoCurrencyStatus>, private val tokens: Set<CryptoCurrencyStatus>,
@ -35,7 +35,7 @@ internal class TokenListOperations<E>(
raise: Raise<E>, raise: Raise<E>,
transform: (Error) -> E, transform: (Error) -> E,
) : this( ) : this(
tokensRepository = useCase.tokensRepository, currenciesRepository = useCase.currenciesRepository,
networksRepository = useCase.networksRepository, networksRepository = useCase.networksRepository,
userWalletId = userWalletId, userWalletId = userWalletId,
tokens = tokens, tokens = tokens,
@ -149,14 +149,14 @@ internal class TokenListOperations<E>(
} }
private fun getIsGrouped(): Flow<Boolean> { private fun getIsGrouped(): Flow<Boolean> {
return tokensRepository.isTokensGrouped(userWalletId) return currenciesRepository.isTokensGrouped(userWalletId)
.catch { raise(Error.DataError(it)) } .catch { raise(Error.DataError(it)) }
.onEmpty { emit(value = false) } .onEmpty { emit(value = false) }
.flowOn(dispatchers.io) .flowOn(dispatchers.io)
} }
private fun getIsSortedByBalance(): Flow<Boolean> { private fun getIsSortedByBalance(): Flow<Boolean> {
return tokensRepository.isTokensSortedByBalance(userWalletId) return currenciesRepository.isTokensSortedByBalance(userWalletId)
.catch { raise(Error.DataError(it)) } .catch { raise(Error.DataError(it)) }
.onEmpty { emit(value = false) } .onEmpty { emit(value = false) }
.flowOn(dispatchers.io) .flowOn(dispatchers.io)

View file

@ -7,7 +7,7 @@ import kotlinx.coroutines.flow.Flow
/** /**
* Repository for everything related to the tokens of user wallet * Repository for everything related to the tokens of user wallet
* */ * */
interface TokensRepository { interface CurrenciesRepository {
/** /**
* Saves the given set of cryptocurrencies, along with the preferences for grouping and sorting, for a specific * Saves the given set of cryptocurrencies, along with the preferences for grouping and sorting, for a specific
@ -17,6 +17,8 @@ interface TokensRepository {
* @param currencies The set of cryptocurrencies to be saved. * @param currencies The set of cryptocurrencies to be saved.
* @param isGroupedByNetwork A boolean flag indicating whether the tokens should be grouped by network. * @param isGroupedByNetwork A boolean flag indicating whether the tokens should be grouped by network.
* @param isSortedByBalance A boolean flag indicating whether the tokens should be sorted by balance. * @param isSortedByBalance A boolean flag indicating whether the tokens should be sorted by balance.
* @throws com.tangem.domain.core.error.DataError.UserWalletError.WrongUserWallet If single-currency user wallet
* ID provided.
*/ */
suspend fun saveTokens( suspend fun saveTokens(
userWalletId: UserWalletId, userWalletId: UserWalletId,
@ -30,8 +32,10 @@ interface TokensRepository {
* *
* @param userWalletId The unique identifier of the user wallet. * @param userWalletId The unique identifier of the user wallet.
* @return The primary cryptocurrency associated with the user wallet. * @return The primary cryptocurrency associated with the user wallet.
* @throws com.tangem.domain.core.error.DataError.UserWalletError.WrongUserWallet If multi-currency user wallet
* ID provided.
*/ */
suspend fun getPrimaryCurrency(userWalletId: UserWalletId): CryptoCurrency suspend fun getSingleCurrencyWalletPrimaryCurrency(userWalletId: UserWalletId): CryptoCurrency
/** /**
* Retrieves the set of cryptocurrencies within a multi-currency wallet. * Retrieves the set of cryptocurrencies within a multi-currency wallet.
@ -39,14 +43,29 @@ interface TokensRepository {
* @param userWalletId The unique identifier of the user wallet. * @param userWalletId The unique identifier of the user wallet.
* @param refresh A boolean flag indicating whether the data should be refreshed. * @param refresh A boolean flag indicating whether the data should be refreshed.
* @return A [Flow] emitting the set of cryptocurrencies associated with the user wallet. * @return A [Flow] emitting the set of cryptocurrencies associated with the user wallet.
* @throws com.tangem.domain.core.error.DataError.UserWalletError.WrongUserWallet If single-currency user wallet
* ID provided.
*/ */
fun getMultiCurrencyWalletCurrencies(userWalletId: UserWalletId, refresh: Boolean): Flow<Set<CryptoCurrency>> fun getMultiCurrencyWalletCurrencies(userWalletId: UserWalletId, refresh: Boolean): Flow<Set<CryptoCurrency>>
/**
* Retrieves the cryptocurrency for a specific multi-currency user wallet.
*
* @param userWalletId The unique identifier of the user wallet.
* @param id The unique identifier of the cryptocurrency to be retrieved.
* @return The cryptocurrency associated with the user wallet and ID.
* @throws com.tangem.domain.core.error.DataError.UserWalletError.WrongUserWallet If single-currency user wallet
* ID provided.
*/
suspend fun getMultiCurrencyWalletCurrency(userWalletId: UserWalletId, id: CryptoCurrency.ID): CryptoCurrency
/** /**
* Determines whether the tokens within a specific multi-currency user wallet are grouped. * Determines whether the tokens within a specific multi-currency user wallet are grouped.
* *
* @param userWalletId The unique identifier of the user wallet. * @param userWalletId The unique identifier of the user wallet.
* @return A [Flow] emitting a boolean value indicating whether the tokens are grouped. * @return A [Flow] emitting a boolean value indicating whether the tokens are grouped.
* @throws com.tangem.domain.core.error.DataError.UserWalletError.WrongUserWallet If single-currency user wallet
* ID provided.
*/ */
fun isTokensGrouped(userWalletId: UserWalletId): Flow<Boolean> fun isTokensGrouped(userWalletId: UserWalletId): Flow<Boolean>
@ -55,6 +74,8 @@ interface TokensRepository {
* *
* @param userWalletId The unique identifier of the user wallet. * @param userWalletId The unique identifier of the user wallet.
* @return A [Flow] emitting a boolean value indicating whether the tokens are sorted by balance. * @return A [Flow] emitting a boolean value indicating whether the tokens are sorted by balance.
* @throws com.tangem.domain.core.error.DataError.UserWalletError.WrongUserWallet If single-currency user wallet
* ID provided.
*/ */
fun isTokensSortedByBalance(userWalletId: UserWalletId): Flow<Boolean> fun isTokensSortedByBalance(userWalletId: UserWalletId): Flow<Boolean>
} }

View file

@ -7,7 +7,7 @@ import com.tangem.domain.core.error.DataError
import com.tangem.domain.tokens.error.TokenListSortingError import com.tangem.domain.tokens.error.TokenListSortingError
import com.tangem.domain.tokens.mock.MockTokens import com.tangem.domain.tokens.mock.MockTokens
import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.repository.MockTokensRepository import com.tangem.domain.tokens.repository.MockCurrenciesRepository
import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
import junit.framework.TestCase.assertEquals import junit.framework.TestCase.assertEquals
@ -183,16 +183,16 @@ internal class ApplyTokenListSortingUseCaseTest {
.sortedBy { Random.nextInt(0, MockTokens.tokens.size) } .sortedBy { Random.nextInt(0, MockTokens.tokens.size) }
.toSet() .toSet()
private fun getUseCase(tokensRepository: MockTokensRepository = getTokensRepository()) = private fun getUseCase(tokensRepository: MockCurrenciesRepository = getTokensRepository()) =
ApplyTokenListSortingUseCase( ApplyTokenListSortingUseCase(
tokensRepository = tokensRepository, currenciesRepository = tokensRepository,
dispatchers = TestingCoroutineDispatcherProvider(), dispatchers = TestingCoroutineDispatcherProvider(),
) )
private fun getTokensRepository( private fun getTokensRepository(
sortTokensResult: Either<DataError, Unit> = Unit.right(), sortTokensResult: Either<DataError, Unit> = Unit.right(),
tokens: Flow<Either<DataError, Set<CryptoCurrency>>> = flowOf(MockTokens.tokens.right()), tokens: Flow<Either<DataError, Set<CryptoCurrency>>> = flowOf(MockTokens.tokens.right()),
): MockTokensRepository { ): MockCurrenciesRepository {
return MockTokensRepository(sortTokensResult, MockTokens.token1.right(), tokens, emptyFlow(), emptyFlow()) return MockCurrenciesRepository(sortTokensResult, MockTokens.token1.right(), tokens, emptyFlow(), emptyFlow())
} }
} }

View file

@ -4,7 +4,7 @@ import arrow.core.Either
import arrow.core.left import arrow.core.left
import arrow.core.right import arrow.core.right
import com.tangem.domain.core.error.DataError import com.tangem.domain.core.error.DataError
import com.tangem.domain.tokens.error.TokenError import com.tangem.domain.tokens.error.CurrencyError
import com.tangem.domain.tokens.mock.MockNetworks import com.tangem.domain.tokens.mock.MockNetworks
import com.tangem.domain.tokens.mock.MockQuotes import com.tangem.domain.tokens.mock.MockQuotes
import com.tangem.domain.tokens.mock.MockTokens import com.tangem.domain.tokens.mock.MockTokens
@ -13,9 +13,9 @@ import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.NetworkStatus import com.tangem.domain.tokens.model.NetworkStatus
import com.tangem.domain.tokens.model.Quote import com.tangem.domain.tokens.model.Quote
import com.tangem.domain.tokens.repository.MockCurrenciesRepository
import com.tangem.domain.tokens.repository.MockNetworksRepository import com.tangem.domain.tokens.repository.MockNetworksRepository
import com.tangem.domain.tokens.repository.MockQuotesRepository import com.tangem.domain.tokens.repository.MockQuotesRepository
import com.tangem.domain.tokens.repository.MockTokensRepository
import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
import junit.framework.TestCase.assertEquals import junit.framework.TestCase.assertEquals
@ -47,7 +47,7 @@ internal class GetPrimaryCurrencyUseCaseTest {
@Test @Test
fun `when token getting failed then error should be received`() = runTest { fun `when token getting failed then error should be received`() = runTest {
// Given // Given
val expectedResult = TokenError.DataError(DataError.NetworkError.NoInternetConnection).left() val expectedResult = CurrencyError.DataError(DataError.NetworkError.NoInternetConnection).left()
val useCase = getUseCase(token = DataError.NetworkError.NoInternetConnection.left()) val useCase = getUseCase(token = DataError.NetworkError.NoInternetConnection.left())
@ -61,7 +61,7 @@ internal class GetPrimaryCurrencyUseCaseTest {
@Test @Test
fun `when quotes getting failed then error should be received`() = runTest { fun `when quotes getting failed then error should be received`() = runTest {
// Given // Given
val expectedResult = TokenError.DataError(DataError.NetworkError.NoInternetConnection).left() val expectedResult = CurrencyError.DataError(DataError.NetworkError.NoInternetConnection).left()
val useCase = getUseCase(quotes = flowOf(DataError.NetworkError.NoInternetConnection.left())) val useCase = getUseCase(quotes = flowOf(DataError.NetworkError.NoInternetConnection.left()))
@ -75,7 +75,7 @@ internal class GetPrimaryCurrencyUseCaseTest {
@Test @Test
fun `when networks statuses getting failed then error should be received`() = runTest { fun `when networks statuses getting failed then error should be received`() = runTest {
// Given // Given
val expectedResult = TokenError.DataError(DataError.NetworkError.NoInternetConnection).left() val expectedResult = CurrencyError.DataError(DataError.NetworkError.NoInternetConnection).left()
val useCase = getUseCase(statuses = flowOf(DataError.NetworkError.NoInternetConnection.left())) val useCase = getUseCase(statuses = flowOf(DataError.NetworkError.NoInternetConnection.left()))
@ -88,7 +88,7 @@ internal class GetPrimaryCurrencyUseCaseTest {
@Test @Test
fun `when networks statuses flow is empty then error should be received`() = runTest { fun `when networks statuses flow is empty then error should be received`() = runTest {
val expectedResult = TokenError.UnableToCreateToken.left() val expectedResult = CurrencyError.UnableToCreateCurrency.left()
val useCase = getUseCase(statuses = flowOf()) val useCase = getUseCase(statuses = flowOf())
@ -101,7 +101,7 @@ internal class GetPrimaryCurrencyUseCaseTest {
@Test @Test
fun `when quotes flow is empty then error should be received`() = runTest { fun `when quotes flow is empty then error should be received`() = runTest {
val expectedResult = TokenError.UnableToCreateToken.left() val expectedResult = CurrencyError.UnableToCreateCurrency.left()
val useCase = getUseCase(quotes = flowOf()) val useCase = getUseCase(quotes = flowOf())
@ -154,7 +154,7 @@ internal class GetPrimaryCurrencyUseCaseTest {
statuses: Flow<Either<DataError, Set<NetworkStatus>>> = flowOf(MockNetworks.verifiedNetworksStatuses.right()), statuses: Flow<Either<DataError, Set<NetworkStatus>>> = flowOf(MockNetworks.verifiedNetworksStatuses.right()),
) = GetPrimaryCurrencyUseCase( ) = GetPrimaryCurrencyUseCase(
dispatchers = dispatchers, dispatchers = dispatchers,
tokensRepository = MockTokensRepository( currenciesRepository = MockCurrenciesRepository(
sortTokensResult = Unit.right(), sortTokensResult = Unit.right(),
token = token, token = token,
tokens = flowOf(), tokens = flowOf(),

View file

@ -13,9 +13,9 @@ import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.NetworkStatus import com.tangem.domain.tokens.model.NetworkStatus
import com.tangem.domain.tokens.model.Quote import com.tangem.domain.tokens.model.Quote
import com.tangem.domain.tokens.models.Network import com.tangem.domain.tokens.models.Network
import com.tangem.domain.tokens.repository.MockCurrenciesRepository
import com.tangem.domain.tokens.repository.MockNetworksRepository import com.tangem.domain.tokens.repository.MockNetworksRepository
import com.tangem.domain.tokens.repository.MockQuotesRepository import com.tangem.domain.tokens.repository.MockQuotesRepository
import com.tangem.domain.tokens.repository.MockTokensRepository
import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
import junit.framework.TestCase.assertEquals import junit.framework.TestCase.assertEquals
@ -326,7 +326,7 @@ internal class GetTokenListUseCaseTest {
isSortedByBalance: Flow<Either<DataError, Boolean>> = flowOf(MockTokenLists.isSortedByBalance.right()), isSortedByBalance: Flow<Either<DataError, Boolean>> = flowOf(MockTokenLists.isSortedByBalance.right()),
) = GetTokenListUseCase( ) = GetTokenListUseCase(
dispatchers = dispatchers, dispatchers = dispatchers,
tokensRepository = MockTokensRepository( currenciesRepository = MockCurrenciesRepository(
sortTokensResult = Unit.right(), sortTokensResult = Unit.right(),
token = MockTokens.token1.right(), token = MockTokens.token1.right(),
tokens = tokens, tokens = tokens,

View file

@ -8,13 +8,13 @@ import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
internal class MockTokensRepository( internal class MockCurrenciesRepository(
private val sortTokensResult: Either<DataError, Unit>, private val sortTokensResult: Either<DataError, Unit>,
private val token: Either<DataError, CryptoCurrency>, private val token: Either<DataError, CryptoCurrency>,
private val tokens: Flow<Either<DataError, Set<CryptoCurrency>>>, private val tokens: Flow<Either<DataError, Set<CryptoCurrency>>>,
private val isGrouped: Flow<Either<DataError, Boolean>>, private val isGrouped: Flow<Either<DataError, Boolean>>,
private val isSortedByBalance: Flow<Either<DataError, Boolean>>, private val isSortedByBalance: Flow<Either<DataError, Boolean>>,
) : TokensRepository { ) : CurrenciesRepository {
var tokensIdsAfterSortingApply: Set<CryptoCurrency>? = null var tokensIdsAfterSortingApply: Set<CryptoCurrency>? = null
private set private set
@ -38,7 +38,7 @@ internal class MockTokensRepository(
isTokensSortedByBalanceAfterSortingApply = isSortedByBalance isTokensSortedByBalanceAfterSortingApply = isSortedByBalance
} }
override suspend fun getPrimaryCurrency(userWalletId: UserWalletId): CryptoCurrency { override suspend fun getSingleCurrencyWalletPrimaryCurrency(userWalletId: UserWalletId): CryptoCurrency {
return token.getOrElse { e -> throw e } return token.getOrElse { e -> throw e }
} }
@ -49,6 +49,17 @@ internal class MockTokensRepository(
return tokens.map { it.getOrElse { e -> throw e } } return tokens.map { it.getOrElse { e -> throw e } }
} }
override suspend fun getMultiCurrencyWalletCurrency(
userWalletId: UserWalletId,
id: CryptoCurrency.ID,
): CryptoCurrency {
val token = token.getOrElse { e -> throw e }
require(token.id == id)
return token
}
override fun isTokensGrouped(userWalletId: UserWalletId): Flow<Boolean> { override fun isTokensGrouped(userWalletId: UserWalletId): Flow<Boolean> {
return isGrouped.map { it.getOrElse { e -> throw e } } return isGrouped.map { it.getOrElse { e -> throw e } }
} }