Updated on 2026-08-14

This commit is contained in:
Tangem 2026-03-04 16:52:14 +04:00
parent 90abe449d6
commit d06051845b
15 changed files with 37 additions and 1002 deletions

View file

@ -20,7 +20,6 @@ import com.tangem.domain.staking.single.SingleStakingBalanceFetcher
import com.tangem.domain.staking.single.SingleStakingBalanceSupplier
import com.tangem.domain.tokens.*
import com.tangem.domain.tokens.operations.BaseCurrencyStatusOperations
import com.tangem.domain.tokens.operations.CachedCurrenciesStatusesOperations
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.CurrencyChecksRepository
import com.tangem.domain.tokens.repository.TokenReceiveWarningsViewedRepository
@ -54,18 +53,6 @@ internal object TokensDomainModule {
return DefaultTokensFeatureToggles(featureTogglesManager = featureTogglesManager)
}
@Provides
@Singleton
fun provideGetTokenListUseCase(
currenciesRepository: CurrenciesRepository,
currenciesStatusesOperations: BaseCurrencyStatusOperations,
): GetTokenListUseCase {
return GetTokenListUseCase(
currenciesRepository = currenciesRepository,
currenciesStatusesOperations = currenciesStatusesOperations,
)
}
@Provides
@Singleton
fun provideGetCurrencyUseCase(
@ -78,20 +65,6 @@ internal object TokensDomainModule {
)
}
@Provides
@Singleton
fun provideGetAllWalletsCryptoCurrencyStatusesUseCase(
currenciesRepository: CurrenciesRepository,
currencyStatusOperations: BaseCurrencyStatusOperations,
dispatchers: CoroutineDispatcherProvider,
): GetAllWalletsCryptoCurrencyStatusesUseCase {
return GetAllWalletsCryptoCurrencyStatusesUseCase(
currenciesRepository = currenciesRepository,
currencyStatusOperations = currencyStatusOperations,
dispatchers = dispatchers,
)
}
@Provides
@Singleton
fun provideGetCurrencyWarningsUseCase(
@ -245,14 +218,6 @@ internal object TokensDomainModule {
)
}
@Provides
@Singleton
fun provideGetWalletTotalBalanceUseCase(
currenciesStatusesOperations: BaseCurrencyStatusOperations,
): GetWalletTotalBalanceUseCase {
return GetWalletTotalBalanceUseCase(currenciesStatusesOperations)
}
@Provides
@Singleton
fun provideRefreshMultiCurrencyWalletQuotesUseCase(
@ -287,7 +252,7 @@ internal object TokensDomainModule {
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
stakingIdFactory: StakingIdFactory,
): BaseCurrencyStatusOperations {
return CachedCurrenciesStatusesOperations(
return BaseCurrencyStatusOperations(
currenciesRepository = currenciesRepository,
quotesRepository = quotesRepository,
singleNetworkStatusSupplier = singleNetworkStatusSupplier,
@ -300,12 +265,6 @@ internal object TokensDomainModule {
)
}
@Provides
@Singleton
fun provideGetCryptoCurrenciesUseCase(currenciesRepository: CurrenciesRepository): GetCryptoCurrenciesUseCase {
return GetCryptoCurrenciesUseCase(currenciesRepository)
}
@Provides
@Singleton
fun provideWalletBalanceFetcher(

View file

@ -4,7 +4,6 @@ import com.tangem.blockchainsdk.utils.ExcludedBlockchains
import com.tangem.data.common.cache.CacheRegistry
import com.tangem.data.common.currency.CardCryptoCurrencyFactory
import com.tangem.data.common.currency.ResponseCryptoCurrenciesFactory
import com.tangem.data.common.currency.UserTokensSaver
import com.tangem.data.tokens.repository.DefaultCurrenciesRepository
import com.tangem.data.tokens.repository.DefaultCurrencyChecksRepository
import com.tangem.data.tokens.repository.DefaultTokenReceiveWarningsViewedRepository
@ -44,7 +43,6 @@ internal object TokensDataModule {
expressServiceFetcher: ExpressServiceFetcher,
excludedBlockchains: ExcludedBlockchains,
cardCryptoCurrencyFactory: CardCryptoCurrencyFactory,
tokensSaver: UserTokensSaver,
responseCryptoCurrenciesFactory: ResponseCryptoCurrenciesFactory,
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
): CurrenciesRepository {
@ -58,7 +56,6 @@ internal object TokensDataModule {
dispatchers = dispatchers,
excludedBlockchains = excludedBlockchains,
cardCryptoCurrencyFactory = cardCryptoCurrencyFactory,
userTokensSaver = tokensSaver,
responseCryptoCurrenciesFactory = responseCryptoCurrenciesFactory,
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
)

View file

@ -2,15 +2,14 @@ package com.tangem.data.tokens.repository
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.TransactionStatus
import com.tangem.blockchainsdk.compatibility.getL2CompatibilityTokenComparison
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
import com.tangem.blockchainsdk.utils.fromNetworkId
import com.tangem.blockchainsdk.utils.toBlockchain
import com.tangem.data.common.api.safeApiCall
import com.tangem.data.common.cache.CacheRegistry
import com.tangem.data.common.currency.*
import com.tangem.data.tokens.utils.CustomTokensMerger
import com.tangem.datasource.api.common.response.ApiResponseError
import com.tangem.data.common.currency.CardCryptoCurrencyFactory
import com.tangem.data.common.currency.CryptoCurrencyFactory
import com.tangem.data.common.currency.ResponseCryptoCurrenciesFactory
import com.tangem.data.common.currency.getTokenId
import com.tangem.datasource.api.common.response.getOrThrow
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
@ -19,24 +18,28 @@ import com.tangem.domain.card.CardTypesResolver
import com.tangem.domain.card.common.util.cardTypesResolver
import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.common.wallets.getSyncStrict
import com.tangem.domain.common.wallets.loadAndGet
import com.tangem.domain.core.error.DataError
import com.tangem.domain.demo.models.DemoConfig
import com.tangem.domain.express.ExpressServiceFetcher
import com.tangem.domain.express.models.ExpressAsset
import com.tangem.domain.models.account.DerivationIndex
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.wallet.*
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.models.wallet.isMultiCurrency
import com.tangem.domain.models.wallet.requireColdWallet
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesProducer
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesSupplier
import com.tangem.domain.tokens.model.FeePaidCurrency
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.*
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import kotlinx.coroutines.plus
import kotlinx.coroutines.withContext
import timber.log.Timber
import com.tangem.blockchain.common.FeePaidCurrency as FeePaidSdkCurrency
@ -49,21 +52,13 @@ internal class DefaultCurrenciesRepository(
private val expressServiceFetcher: ExpressServiceFetcher,
private val dispatchers: CoroutineDispatcherProvider,
private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory,
private val userTokensSaver: UserTokensSaver,
private val userTokensResponseStore: UserTokensResponseStore,
private val responseCryptoCurrenciesFactory: ResponseCryptoCurrenciesFactory,
private val multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
excludedBlockchains: ExcludedBlockchains,
) : CurrenciesRepository {
private val demoConfig = DemoConfig
private val cryptoCurrencyFactory = CryptoCurrencyFactory(excludedBlockchains)
private val userTokensResponseFactory = UserTokensResponseFactory()
private val customTokensMerger = CustomTokensMerger(
tangemTechApi = tangemTechApi,
dispatchers = dispatchers,
userTokensSaver = userTokensSaver,
)
override fun getWalletCurrenciesUpdates(userWalletId: UserWalletId): Flow<List<CryptoCurrency>> {
return channelFlow {
@ -159,29 +154,6 @@ internal class DefaultCurrenciesRepository(
}
}
override suspend fun getMultiCurrencyWalletCurrenciesSync(
userWalletId: UserWalletId,
refresh: Boolean,
): List<CryptoCurrency> = withContext(dispatchers.io) {
val userWallet = userWalletsListRepository.getSyncStrict(userWalletId)
ensureIsCorrectUserWallet(userWallet, isMultiCurrencyWalletExpected = true)
fetchTokensIfCacheExpired(userWallet, refresh)
val storedTokens = requireNotNull(
value = getSavedUserTokensResponseSync(key = userWallet.walletId),
lazyMessage = {
"Unable to find tokens response for user wallet with provided ID: $userWalletId"
},
)
responseCryptoCurrenciesFactory.createCurrencies(
response = storedTokens,
userWallet = userWallet,
accountIndex = DerivationIndex.Main,
)
}
override suspend fun getNetworkCoin(
userWalletId: UserWalletId,
networkId: Network.ID,
@ -199,38 +171,6 @@ internal class DefaultCurrenciesRepository(
?: error("Unable to find coin for network ID: $networkId")
}
override fun isTokensGrouped(userWalletId: UserWalletId): Flow<Boolean> {
return channelFlow {
val userWallet = userWalletsListRepository.getSyncStrict(userWalletId)
if (userWallet.isMultiCurrency) {
getSavedUserTokensResponse(userWalletId)
.map { response -> response.group == UserTokensResponse.GroupType.NETWORK }
.distinctUntilChanged()
.onEach { isGrouped -> send(isGrouped) }
.launchIn(scope = this + dispatchers.io)
} else {
send(element = false)
}
}
}
override fun isTokensSortedByBalance(userWalletId: UserWalletId): Flow<Boolean> {
return channelFlow {
val userWallet = userWalletsListRepository.getSyncStrict(userWalletId)
if (userWallet.isMultiCurrency) {
getSavedUserTokensResponse(userWalletId)
.map { response -> response.sort == UserTokensResponse.SortType.BALANCE }
.distinctUntilChanged()
.onEach { isSorted -> send(isSorted) }
.launchIn(scope = this + dispatchers.io)
} else {
send(element = false)
}
}
}
override suspend fun isSendBlockedByPendingTransactions(
userWalletId: UserWalletId,
cryptoCurrencyStatus: CryptoCurrencyStatus,
@ -332,67 +272,6 @@ internal class DefaultCurrenciesRepository(
) ?: error("Unable to create token")
}
@OptIn(ExperimentalCoroutinesApi::class)
override fun getAllWalletsCryptoCurrencies(
currencyRawId: CryptoCurrency.RawID,
): Flow<Map<UserWallet, List<CryptoCurrency>>> {
return userWalletsListRepository.loadAndGet().flatMapLatest { userWallets ->
userWallets.filter { it.isMultiCurrency }
.forEach { fetchTokensIfCacheExpired(userWallet = it, refresh = false) }
val userWalletsWithCurrencies = userWallets
.filterNot(UserWallet::isLocked)
.map { userWallet ->
getCurrenciesForWallet(userWallet, currencyRawId).map { userWallet to it }
}
combine(userWalletsWithCurrencies) { it.toMap() }
.onEmpty { emit(value = emptyMap()) }
}
}
@Suppress("SuspendFunWithFlowReturnType")
private suspend fun getCurrenciesForWallet(
userWallet: UserWallet,
currencyRawId: CryptoCurrency.RawID,
): Flow<List<CryptoCurrency>> {
return when {
userWallet.isMultiCurrency -> {
getSavedUserTokensResponse(userWallet.walletId).map { storedTokens ->
val filterResponse = storedTokens.tokens.filter {
getL2CompatibilityTokenComparison(it, currencyRawId.value)
}
responseCryptoCurrenciesFactory.createCurrencies(
response = storedTokens.copy(tokens = filterResponse),
userWallet = userWallet,
accountIndex = DerivationIndex.Main,
)
}
}
else -> {
val currencies =
if (userWallet.requireColdWallet().scanResponse.cardTypesResolver.isSingleWalletWithToken()) {
getSingleCurrencyWalletWithCardCurrencies(userWallet.walletId)
} else {
val currency =
getSingleCurrencyWalletPrimaryCurrency(userWalletId = userWallet.walletId)
if (currency.id.rawCurrencyId == currencyRawId) {
listOf(currency)
} else {
emptyList()
}
}
flow {
emit(currencies)
}
}
}
}
override fun isNetworkFeeZero(userWalletId: UserWalletId, network: Network): Boolean {
val blockchain = Blockchain.fromNetworkId(network.backendId)
return blockchain?.isNetworkFeeZero() == true
@ -412,53 +291,6 @@ internal class DefaultCurrenciesRepository(
}
}
private suspend fun fetchTokensIfCacheExpired(userWallet: UserWallet, refresh: Boolean) {
cacheRegistry.invokeOnExpire(
key = getTokensCacheKey(userWallet.walletId),
skipCache = refresh,
block = { fetchTokens(userWallet) },
)
}
private suspend fun fetchTokens(userWallet: UserWallet) {
val userWalletId = userWallet.walletId
val response = if (userWallet is UserWallet.Cold && checkIsEmptyDemoWallet(userWallet)) {
createDefaultUserTokensResponse(userWallet)
} else {
safeApiCall({ tangemTechApi.getUserTokens(userWalletId.stringValue).bind() }) {
handleFetchTokensError(userWallet, it)
}
}
val compatibleUserTokensResponse = response
.let { it.copy(tokens = it.tokens.distinct()) }
.let { customTokensMerger.mergeIfPresented(userWalletId, it) }
userTokensSaver.store(userWalletId, compatibleUserTokensResponse)
fetchExpressAssetsByNetworkIds(userWallet, compatibleUserTokensResponse)
}
private suspend fun checkIsEmptyDemoWallet(userWallet: UserWallet.Cold): Boolean {
val response = getSavedUserTokensResponseSync(key = userWallet.walletId)
return demoConfig.isDemoCardId(userWallet.cardId) && response == null
}
private suspend fun fetchExpressAssetsByNetworkIds(userWallet: UserWallet, userTokens: UserTokensResponse) {
val tokens = userTokens.tokens.mapTo(hashSetOf()) { token ->
ExpressAsset.ID(
networkId = token.networkId,
contractAddress = token.contractAddress,
)
}
coroutineScope {
launch { expressServiceFetcher.fetch(userWallet, tokens) }
}
}
private suspend fun fetchExpressAssetsByNetworkIds(
userWallet: UserWallet,
cryptoCurrencies: List<CryptoCurrency>,
@ -484,35 +316,6 @@ internal class DefaultCurrenciesRepository(
private fun getAssetsCacheKey(userWalletId: UserWalletId): String = "assets_cache_key_${userWalletId.stringValue}"
private suspend fun handleFetchTokensError(userWallet: UserWallet, e: ApiResponseError): UserTokensResponse {
val userWalletId = userWallet.walletId
val response = userTokensResponseStore.getSyncOrNull(userWalletId = userWalletId)
?: createDefaultUserTokensResponse(userWallet = userWallet)
if (e is ApiResponseError.HttpException && e.code == ApiResponseError.HttpException.Code.NOT_FOUND) {
Timber.w(
e,
"Requested currencies could not be found in the remote store for: $userWalletId",
)
userTokensSaver.push(userWalletId, response)
} else {
cacheRegistry.invalidate(getTokensCacheKey(userWalletId))
}
return response
}
private fun createDefaultUserTokensResponse(userWallet: UserWallet) =
userTokensResponseFactory.createUserTokensResponse(
currencies = cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyWallet(
userWallet = userWallet,
),
isGroupedByNetwork = false,
isSortedByBalance = false,
accountId = null,
)
private fun ensureIsCorrectUserWallet(userWallet: UserWallet, isMultiCurrencyWalletExpected: Boolean) {
val userWalletId = userWallet.walletId
@ -536,13 +339,7 @@ internal class DefaultCurrenciesRepository(
}
}
private fun getTokensCacheKey(userWalletId: UserWalletId): String = "tokens_cache_key_${userWalletId.stringValue}"
private fun getSavedUserTokensResponse(key: UserWalletId): Flow<UserTokensResponse> {
return userTokensResponseStore.get(userWalletId = key).filterNotNull()
}
private suspend fun getSavedUserTokensResponseSync(key: UserWalletId): UserTokensResponse? {
return userTokensResponseStore.getSyncOrNull(userWalletId = key)
}
}

View file

@ -1,56 +0,0 @@
package com.tangem.domain.tokens
import arrow.core.Either
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.tokens.error.CurrencyStatusError
import com.tangem.domain.tokens.error.mapper.mapToCurrencyError
import com.tangem.domain.tokens.operations.BaseCurrencyStatusOperations
import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.*
/**
* Get crypto currency statuses by raw ID for all wallets
*
* @property currenciesRepository currencies repository
* @property dispatchers dispatchers
*
[REDACTED_AUTHOR]
*/
class GetAllWalletsCryptoCurrencyStatusesUseCase(
private val currenciesRepository: CurrenciesRepository,
private val dispatchers: CoroutineDispatcherProvider,
private val currencyStatusOperations: BaseCurrencyStatusOperations,
) {
/**
* Get crypto currency statuses by [currencyRawId] for all wallets
*
* @param currencyRawId currency raw ID
*/
@OptIn(ExperimentalCoroutinesApi::class)
operator fun invoke(
currencyRawId: CryptoCurrency.RawID,
): Flow<Map<UserWallet, List<Either<CurrencyStatusError, CryptoCurrencyStatus>>>> {
return currenciesRepository.getAllWalletsCryptoCurrencies(currencyRawId)
.flatMapLatest { userWalletsWithCurrencies: Map<UserWallet, List<CryptoCurrency>> ->
val walletStatusFlows = userWalletsWithCurrencies.map { (userWallet, cryptoCurrencies) ->
val currencyStatusFlows = cryptoCurrencies.map { cryptoCurrency ->
currencyStatusOperations.getCurrencyStatusFlow(userWallet.walletId, cryptoCurrency)
.map { it.mapLeft(CurrenciesStatusesOperations.Error::mapToCurrencyError) }
}
combine(currencyStatusFlows) { statuses -> userWallet to statuses.toList() }
.onEmpty { emit(userWallet to emptyList()) }
}
combine(walletStatusFlows) { it.toMap() }
.onEmpty { emit(emptyMap()) }
}
.flowOn(dispatchers.io)
}
}

View file

@ -1,26 +0,0 @@
package com.tangem.domain.tokens
import arrow.core.Either
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.tokens.error.CurrencyStatusError
import com.tangem.domain.tokens.repository.CurrenciesRepository
@Deprecated("Use MultiWalletCryptoCurrenciesSupplier")
class GetCryptoCurrenciesUseCase(
private val currenciesRepository: CurrenciesRepository,
) {
/**
* Retrieves the list of cryptocurrencies within a multi-currency wallet.
*
* @param userWalletId The unique identifier of the user wallet.
*
* @return An [Either] representing success (Right) or an error (Left) in fetching the status.
*/
suspend operator fun invoke(userWalletId: UserWalletId): Either<CurrencyStatusError, List<CryptoCurrency>> {
return Either.catch {
currenciesRepository.getMultiCurrencyWalletCurrenciesSync(userWalletId)
}.mapLeft(CurrencyStatusError::DataError)
}
}

View file

@ -1,62 +0,0 @@
package com.tangem.domain.tokens
import com.tangem.domain.core.lce.LceFlow
import com.tangem.domain.core.utils.lceError
import com.tangem.domain.core.utils.lceLoading
import com.tangem.domain.core.utils.toLce
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.tokenlist.TokenList
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.tokens.error.TokenListError
import com.tangem.domain.tokens.error.mapper.mapToTokenListError
import com.tangem.domain.tokens.operations.BaseCurrencyStatusOperations
import com.tangem.domain.tokens.operations.TokenListOperations
import com.tangem.domain.tokens.repository.CurrenciesRepository
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.transformLatest
class GetTokenListUseCase(
private val currenciesRepository: CurrenciesRepository,
private val currenciesStatusesOperations: BaseCurrencyStatusOperations,
) {
@OptIn(ExperimentalCoroutinesApi::class)
fun launch(userWalletId: UserWalletId): LceFlow<TokenListError, TokenList> {
return currenciesStatusesOperations.getCurrenciesStatuses(userWalletId)
.transformLatest { maybeCurrencies ->
maybeCurrencies.fold(
ifLoading = { maybeContent ->
if (maybeContent != null) {
emitAll(createTokenListLce(userWalletId, maybeContent, isCurrenciesLoading = true))
} else {
emit(lceLoading())
}
},
ifContent = { content ->
emitAll(createTokenListLce(userWalletId, content, isCurrenciesLoading = false))
},
ifError = { error -> emit(error.lceError()) },
)
}
}
private fun createTokenListLce(
userWalletId: UserWalletId,
currencies: List<CryptoCurrencyStatus>,
isCurrenciesLoading: Boolean,
): LceFlow<TokenListError, TokenList> {
val operations = TokenListOperations(
userWalletId = userWalletId,
tokens = currencies,
currenciesRepository = currenciesRepository,
)
return operations.getTokenListFlow().map { maybeTokenList ->
maybeTokenList
.mapLeft(TokenListOperations.Error::mapToTokenListError)
.toLce(isCurrenciesLoading)
}
}
}

View file

@ -1,119 +0,0 @@
package com.tangem.domain.tokens
import arrow.atomic.update
import arrow.core.raise.ensureNotNull
import arrow.core.toNonEmptyListOrNull
import com.tangem.domain.core.lce.Lce
import com.tangem.domain.core.lce.LceFlow
import com.tangem.domain.core.lce.lce
import com.tangem.domain.core.utils.lceContent
import com.tangem.domain.core.utils.lceLoading
import com.tangem.domain.models.TotalFiatBalance
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.tokens.error.TokenListError
import com.tangem.domain.tokens.operations.BaseCurrencyStatusOperations
import com.tangem.domain.tokens.operations.TotalFiatBalanceCalculator
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.*
import timber.log.Timber
import java.util.concurrent.ConcurrentHashMap
class GetWalletTotalBalanceUseCase(
private val currenciesStatusesOperations: BaseCurrencyStatusOperations,
) {
private val walletBalanceCache = ConcurrentHashMap<UserWalletId, TotalFiatBalance.Loaded>()
operator fun invoke(
userWalletsIds: Collection<UserWalletId>,
): LceFlow<TokenListError, Map<UserWalletId, TotalFiatBalance>> {
val flows = userWalletsIds.distinct()
.map { userWalletId ->
invoke(userWalletId).map { maybeBalance ->
userWalletId to maybeBalance
}
}
return combine(flows) { balances ->
lce {
balances.fold(mutableMapOf()) { acc, (userWalletId, maybeBalance) ->
val balance = maybeBalance.fold(
ifLoading = { TotalFiatBalance.Loading },
ifContent = { it },
ifError = {
Timber.e("failed to load balances with error: $it")
TotalFiatBalance.Failed
},
)
isLoading.update { it || balance is TotalFiatBalance.Loading }
acc[userWalletId] = balance
acc
}
}
}
}
@OptIn(ExperimentalCoroutinesApi::class)
operator fun invoke(userWalletId: UserWalletId): LceFlow<TokenListError, TotalFiatBalance> {
return currenciesStatusesOperations.getCurrenciesStatuses(userWalletId).map(::createBalance)
.distinctUntilChanged()
.onStart {
val cachedBalance = walletBalanceCache[userWalletId]
if (cachedBalance != null) {
emit(cachedBalance.lceContent())
}
}
.mapLatest { lceBalance ->
val cachedBalance = walletBalanceCache[userWalletId]
if (cachedBalance == null) {
lceBalance.onContent { content ->
if (content is TotalFiatBalance.Loaded) {
walletBalanceCache.put(userWalletId, content)
}
}
lceBalance
} else {
val content = lceBalance.getOrNull(isPartialContentAccepted = false)
if (content is TotalFiatBalance.Loaded && content != cachedBalance) {
walletBalanceCache.put(userWalletId, content)
lceBalance
} else {
cachedBalance.lceContent()
}
}
}
.distinctUntilChanged()
}
private fun createBalance(
maybeStatuses: Lce<TokenListError, List<CryptoCurrencyStatus>>,
): Lce<TokenListError, TotalFiatBalance> = lce {
val statuses = when (maybeStatuses) {
is Lce.Content -> maybeStatuses.content
is Lce.Error -> raise(maybeStatuses)
is Lce.Loading -> {
val content = maybeStatuses.partialContent
if (content == null) {
isLoading.set(true)
raise(lceLoading())
} else {
content
}
}
}
TotalFiatBalanceCalculator.calculate(
statuses = ensureNotNull(statuses.toNonEmptyListOrNull()) { lceLoading() },
)
}
}

View file

@ -2,7 +2,6 @@ package com.tangem.domain.tokens.error.mapper
import com.tangem.domain.tokens.error.TokenListError
import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations
import com.tangem.domain.tokens.operations.TokenListOperations
internal fun CurrenciesStatusesOperations.Error.mapToTokenListError(): TokenListError {
return when (this) {
@ -15,12 +14,4 @@ internal fun CurrenciesStatusesOperations.Error.mapToTokenListError(): TokenList
is CurrenciesStatusesOperations.Error.EmptyStakingBalances,
-> 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)
}
}

View file

@ -3,7 +3,6 @@ package com.tangem.domain.tokens.operations
import arrow.core.*
import arrow.core.raise.*
import com.tangem.blockchainsdk.utils.toBlockchain
import com.tangem.domain.core.lce.LceFlow
import com.tangem.domain.core.utils.EitherFlow
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
@ -28,7 +27,6 @@ import com.tangem.domain.staking.single.SingleStakingBalanceProducer
import com.tangem.domain.staking.single.SingleStakingBalanceSupplier
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesProducer
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesSupplier
import com.tangem.domain.tokens.error.TokenListError
import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations.Error
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.utils.CurrencyStatusProxyCreator
@ -42,7 +40,7 @@ import kotlinx.coroutines.flow.*
[REDACTED_AUTHOR]
*/
@Suppress("LargeClass", "LongParameterList")
abstract class BaseCurrencyStatusOperations(
class BaseCurrencyStatusOperations(
private val currenciesRepository: CurrenciesRepository,
private val quotesRepository: QuotesRepository,
private val multiNetworkStatusSupplier: MultiNetworkStatusSupplier,
@ -56,10 +54,6 @@ abstract class BaseCurrencyStatusOperations(
private val currencyStatusProxyCreator = CurrencyStatusProxyCreator()
abstract fun getCurrenciesStatuses(userWalletId: UserWalletId): LceFlow<TokenListError, List<CryptoCurrencyStatus>>
protected abstract fun getQuotes(id: CryptoCurrency.RawID): Flow<Either<Error, Set<QuoteStatus>>>
suspend fun getCurrencyStatusFlow(
userWalletId: UserWalletId,
currencyId: CryptoCurrency.ID,
@ -389,6 +383,14 @@ abstract class BaseCurrencyStatusOperations(
.bind()
}
private fun getQuotes(id: CryptoCurrency.RawID): Flow<Either<Error, Set<QuoteStatus>>> {
return singleQuoteStatusSupplier(
params = SingleQuoteStatusProducer.Params(rawCurrencyId = id),
)
.map<QuoteStatus, Either<Error, Set<QuoteStatus>>> { setOf(it).right() }
.distinctUntilChanged()
}
private suspend fun getStakingBalancesSync(
userWalletId: UserWalletId,
cryptoCurrencies: List<CryptoCurrency>,
@ -439,7 +441,7 @@ abstract class BaseCurrencyStatusOperations(
)
}
protected fun getIds(currencies: List<CryptoCurrency>): Pair<NonEmptySet<Network>, NonEmptySet<CryptoCurrency.ID>> {
private fun getIds(currencies: List<CryptoCurrency>): Pair<NonEmptySet<Network>, NonEmptySet<CryptoCurrency.ID>> {
val currencyIdToNetworkId = currencies.associate { currency ->
currency.id to currency.network
}

View file

@ -1,340 +0,0 @@
package com.tangem.domain.tokens.operations
import arrow.core.*
import arrow.core.raise.recover
import com.tangem.domain.core.lce.Lce
import com.tangem.domain.core.lce.LceFlow
import com.tangem.domain.core.lce.lce
import com.tangem.domain.core.lce.lceFlow
import com.tangem.domain.core.utils.EitherFlow
import com.tangem.domain.core.utils.lceContent
import com.tangem.domain.core.utils.lceError
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.network.NetworkStatus
import com.tangem.domain.models.network.getAddress
import com.tangem.domain.models.quote.QuoteStatus
import com.tangem.domain.models.staking.StakingID
import com.tangem.domain.models.staking.StakingBalance
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.networks.multi.MultiNetworkStatusSupplier
import com.tangem.domain.networks.single.SingleNetworkStatusProducer
import com.tangem.domain.networks.single.SingleNetworkStatusSupplier
import com.tangem.domain.quotes.QuotesRepository
import com.tangem.domain.quotes.single.SingleQuoteStatusProducer
import com.tangem.domain.quotes.single.SingleQuoteStatusSupplier
import com.tangem.domain.staking.StakingIdFactory
import com.tangem.domain.staking.model.StakingIntegrationID
import com.tangem.domain.staking.multi.MultiStakingBalanceSupplier
import com.tangem.domain.staking.single.SingleStakingBalanceProducer
import com.tangem.domain.staking.single.SingleStakingBalanceSupplier
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesSupplier
import com.tangem.domain.tokens.error.TokenListError
import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations.Error
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.utils.extensions.addOrReplace
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
@Suppress("LongParameterList", "LargeClass")
class CachedCurrenciesStatusesOperations(
private val currenciesRepository: CurrenciesRepository,
quotesRepository: QuotesRepository,
private val singleNetworkStatusSupplier: SingleNetworkStatusSupplier,
multiNetworkStatusSupplier: MultiNetworkStatusSupplier,
private val singleQuoteStatusSupplier: SingleQuoteStatusSupplier,
private val singleStakingBalanceSupplier: SingleStakingBalanceSupplier,
multiStakingBalanceSupplier: MultiStakingBalanceSupplier,
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
private val stakingIdFactory: StakingIdFactory,
) : BaseCurrencyStatusOperations(
currenciesRepository = currenciesRepository,
quotesRepository = quotesRepository,
multiNetworkStatusSupplier = multiNetworkStatusSupplier,
singleNetworkStatusSupplier = singleNetworkStatusSupplier,
singleQuoteStatusSupplier = singleQuoteStatusSupplier,
singleStakingBalanceSupplier = singleStakingBalanceSupplier,
multiStakingBalanceSupplier = multiStakingBalanceSupplier,
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
stakingIdFactory = stakingIdFactory,
) {
override fun getCurrenciesStatuses(
userWalletId: UserWalletId,
): LceFlow<TokenListError, List<CryptoCurrencyStatus>> {
return transformToCurrenciesStatuses(
userWalletId = userWalletId,
currenciesFlow = getCurrencies(userWalletId),
)
}
@Suppress("LongMethod")
@OptIn(ExperimentalCoroutinesApi::class)
private fun transformToCurrenciesStatuses(
userWalletId: UserWalletId,
currenciesFlow: EitherFlow<TokenListError, List<CryptoCurrency>>,
): LceFlow<TokenListError, List<CryptoCurrencyStatus>> = lceFlow {
val prevStatuses = MutableStateFlow(value = emptyList<CryptoCurrencyStatus>())
currenciesFlow.flatMapLatest { maybeCurrencies ->
val currencies = maybeCurrencies
.getOrElse { return@flatMapLatest flowOf(it.lceError()) }
.toNonEmptyListOrNull()
if (currencies.isNullOrEmpty()) {
prevStatuses.value = emptyList()
return@flatMapLatest flowOf(TokenListError.EmptyTokens.lceError())
}
// This is only 'true' when the flow here is empty, such as during initial loading
if (isLoading.get()) {
val loadingCurrencies = createCurrenciesStatuses(
currencies = currencies,
maybeNetworkStatuses = null,
maybeQuotes = null,
maybeStakingBalances = null,
isUpdating = true,
)
loadingCurrencies.getOrNull()?.let { prevStatuses.value = it }
send(loadingCurrencies)
}
val (networks, currenciesIds) = getIds(currencies)
fun createCurrenciesStatuses(
maybeQuotes: Either<TokenListError, Set<QuoteStatus>>,
maybeNetworkStatuses: Either<TokenListError, Set<NetworkStatus>>,
maybeStakingBalances: Either<TokenListError, List<StakingBalance>>,
isUpdating: Boolean,
) = createCurrenciesStatuses(
currencies = currencies,
maybeQuotes = maybeQuotes,
maybeNetworkStatuses = maybeNetworkStatuses,
maybeStakingBalances = maybeStakingBalances,
isUpdating = isUpdating,
)
// removing token
val prevStatusesValue = prevStatuses.value
if (prevStatusesValue.size - currencies.size == 1) {
val removed = prevStatusesValue.map { it.currency } - currencies
return@flatMapLatest flowOf(
prevStatusesValue.filter { it.currency !in removed }.lceContent(),
)
}
val networksStatusesUpdates = getNetworkStatusesUpdates(userWalletId, networks)
combine(
flow = getQuotes(currenciesIds),
flow2 = networksStatusesUpdates,
flow3 = networksStatusesUpdates.flatMapLatest { maybeNetworksStatuses ->
val networksStatuses = maybeNetworksStatuses.getOrNull()
val currenciesAddresses = if (networksStatuses == null) {
emptyMap()
} else {
currencies.associate { currency ->
val networkStatus = networksStatuses.firstOrNull { it.network == currency.network }
currency.id to networkStatus.getAddress()
}
}
getYieldsBalancesUpdates(userWalletId, currenciesAddresses)
},
flow4 = flowOf(value = false),
transform = ::createCurrenciesStatuses,
)
.distinctUntilChanged()
}
.onEach { statusesLce ->
statusesLce.getOrNull()?.let { prevStatuses.value = it }
send(statusesLce)
}
.launchIn(scope = this)
}
private fun createCurrenciesStatuses(
currencies: NonEmptyList<CryptoCurrency>,
maybeQuotes: Either<TokenListError, Set<QuoteStatus>>?,
maybeNetworkStatuses: Either<TokenListError, Set<NetworkStatus>>?,
maybeStakingBalances: Either<TokenListError, List<StakingBalance>>?,
isUpdating: Boolean,
): Lce<TokenListError, List<CryptoCurrencyStatus>> = lce {
isLoading.set(isUpdating)
val networksStatuses = maybeNetworkStatuses?.bindEither()?.toNonEmptySetOrNull()
val stakingBalances = maybeStakingBalances?.bindEither()
val quotes = recover({ maybeQuotes?.bind()?.toNonEmptySetOrNull() }) {
null
}
currencies.map { currency ->
val quote = quotes?.firstOrNull { it.rawCurrencyId == currency.id.rawCurrencyId }
val networkStatus = networksStatuses?.firstOrNull { it.network == currency.network }
val stakingBalance = findStakingBalanceOrNull(stakingBalances, currency, networkStatus)
val currencyStatus = CryptoCurrencyStatusFactory.create(
currency = currency,
maybeNetworkStatus = networkStatus.toOption(),
maybeQuoteStatus = quote.toOption(),
maybeStakingBalance = stakingBalance.toOption(),
)
currencyStatus
}
}
private fun findStakingBalanceOrNull(
stakingBalances: List<StakingBalance>?,
currency: CryptoCurrency,
networkStatus: NetworkStatus?,
): StakingBalance? {
if (stakingBalances.isNullOrEmpty()) return null
val supportedIntegration = StakingIntegrationID.create(currencyId = currency.id)?.value
val address = networkStatus.getAddress()
return if (supportedIntegration != null && address != null) {
val stakingId = StakingID(integrationId = supportedIntegration, address = address)
stakingBalances.firstOrNull { it.stakingId == stakingId }
?: StakingBalance.Error(stakingId = stakingId)
} else {
null
}
}
private fun getCurrencies(userWalletId: UserWalletId): EitherFlow<TokenListError, List<CryptoCurrency>> {
return currenciesRepository.getWalletCurrenciesUpdates(userWalletId)
.map<List<CryptoCurrency>, Either<TokenListError, List<CryptoCurrency>>> { it.right() }
.catch { emit(TokenListError.DataError(it).left()) }
.distinctUntilChanged()
}
private fun getQuotes(tokensIds: NonEmptySet<CryptoCurrency.ID>): Flow<Either<TokenListError, Set<QuoteStatus>>> {
return getQuotesUpdates(
rawCurrencyIds = tokensIds.mapNotNullTo(
destination = hashSetOf(),
transform = CryptoCurrency.ID::rawCurrencyId,
),
)
}
override fun getQuotes(id: CryptoCurrency.RawID): Flow<Either<Error, Set<QuoteStatus>>> {
return singleQuoteStatusSupplier(
params = SingleQuoteStatusProducer.Params(rawCurrencyId = id),
)
.map<QuoteStatus, Either<Error, Set<QuoteStatus>>> { setOf(it).right() }
.distinctUntilChanged()
}
// temporary code because token list is built using networks list
@OptIn(FlowPreview::class)
private fun getNetworkStatusesUpdates(
userWalletId: UserWalletId,
networks: NonEmptySet<Network>,
): EitherFlow<TokenListError, Set<NetworkStatus>> {
return channelFlow {
val state = MutableStateFlow(emptySet<NetworkStatus>())
networks.onEach {
launch {
singleNetworkStatusSupplier(
params = SingleNetworkStatusProducer.Params(userWalletId = userWalletId, network = it),
)
.onEach { status ->
state.update { loadedStatuses ->
loadedStatuses.addOrReplace(status) { it.network == status.network }
}
}
.launchIn(scope = this)
}
}
state
.onEach(::send)
.launchIn(scope = this)
}
.debounce(timeoutMillis = 500)
.map<Set<NetworkStatus>, Either<TokenListError, Set<NetworkStatus>>> { it.right() }
.distinctUntilChanged()
}
// temporary code because token list is built using networks list
private fun getQuotesUpdates(
rawCurrencyIds: Set<CryptoCurrency.RawID>,
): EitherFlow<TokenListError, Set<QuoteStatus>> {
return channelFlow {
val state = MutableStateFlow(emptySet<QuoteStatus>())
rawCurrencyIds.onEach {
launch {
singleQuoteStatusSupplier(
params = SingleQuoteStatusProducer.Params(rawCurrencyId = it),
)
.onEach { quote ->
state.update { loadedStatuses ->
loadedStatuses.addOrReplace(quote) { it.rawCurrencyId == quote.rawCurrencyId }
}
}
.launchIn(scope = this)
}
}
state
.onEach(::send)
.launchIn(scope = this)
}
.map<Set<QuoteStatus>, Either<TokenListError, Set<QuoteStatus>>> { it.right() }
.distinctUntilChanged()
}
// temporary code because token list is built using networks list
private fun getYieldsBalancesUpdates(
userWalletId: UserWalletId,
cryptoCurrencies: Map<CryptoCurrency.ID, String?>,
): EitherFlow<TokenListError, List<StakingBalance>> {
return channelFlow {
val state = MutableStateFlow(emptyList<StakingBalance>())
val stakingIds = cryptoCurrencies.mapNotNullTo(hashSetOf()) { currencyWithAddress ->
stakingIdFactory.create(
currencyId = currencyWithAddress.key,
defaultAddress = currencyWithAddress.value,
)
.getOrNull()
}
stakingIds.onEach { stakingId ->
launch {
singleStakingBalanceSupplier(
params = SingleStakingBalanceProducer.Params(
userWalletId = userWalletId,
stakingId = stakingId,
),
)
.onEach { balance ->
state.update { loadedBalances ->
loadedBalances.addOrReplace(balance) { balance.stakingId == it.stakingId }
}
}
.launchIn(scope = this)
}
}
state
.onEach { send(it.right()) }
.launchIn(scope = this)
}
.distinctUntilChanged()
}
}

View file

@ -1,66 +0,0 @@
package com.tangem.domain.tokens.operations
import arrow.core.Either
import arrow.core.left
import arrow.core.raise.either
import arrow.core.right
import arrow.core.toNonEmptyListOrNull
import com.tangem.domain.models.TokensGroupType
import com.tangem.domain.models.TokensSortType
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.tokenlist.TokenList
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.tokens.repository.CurrenciesRepository
import kotlinx.coroutines.flow.*
@Suppress("LongParameterList")
internal class TokenListOperations(
private val currenciesRepository: CurrenciesRepository,
private val userWalletId: UserWalletId,
private val tokens: List<CryptoCurrencyStatus>,
) {
fun getTokenListFlow(): Flow<Either<Error, TokenList>> {
return combine(
flow = getIsGrouped(),
flow2 = getIsSortedByBalance(),
) { isGrouped, isSortedByBalance ->
either {
createTokenList(isGrouped = isGrouped.bind(), isSortedByBalance = isSortedByBalance.bind())
}
}
}
private fun createTokenList(isGrouped: Boolean, isSortedByBalance: Boolean): TokenList {
val nonEmptyCurrencies = tokens.toNonEmptyListOrNull() ?: return TokenList.Empty
return TokenListFactory.create(
statuses = nonEmptyCurrencies,
groupType = if (isGrouped) TokensGroupType.NETWORK else TokensGroupType.NONE,
sortType = if (isSortedByBalance) TokensSortType.BALANCE else TokensSortType.NONE,
)
}
private fun getIsGrouped(): Flow<Either<Error, Boolean>> {
return currenciesRepository.isTokensGrouped(userWalletId)
.map<Boolean, Either<Error, Boolean>> { it.right() }
.catch { emit(Error.DataError(it).left()) }
.onEmpty { emit(value = false.right()) }
.cancellable()
}
private fun getIsSortedByBalance(): Flow<Either<Error, Boolean>> {
return currenciesRepository.isTokensSortedByBalance(userWalletId)
.map<Boolean, Either<Error, Boolean>> { it.right() }
.catch { emit(Error.DataError(it).left()) }
.onEmpty { emit(value = false.right()) }
.cancellable()
}
sealed class Error {
data class UnableToSortTokenList(val unsortedTokenList: TokenList.Ungrouped) : Error()
data class DataError(val cause: Throwable) : Error()
}
}

View file

@ -5,7 +5,6 @@ import com.tangem.domain.core.error.DataError
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.tokens.model.FeePaidCurrency
import kotlinx.coroutines.flow.Flow
@ -71,23 +70,6 @@ interface CurrenciesRepository {
id: CryptoCurrency.ID,
): CryptoCurrency
/**
* Retrieves the list of cryptocurrencies within a multi-currency wallet.
*
* Loads cryptocurrencies if they have expired or if [refresh] is `true`.
*
* @param userWalletId The unique identifier of the user wallet.
* @param refresh A boolean flag indicating whether the data should be refreshed.
* @return A list of [CryptoCurrency].
* @throws DataError.UserWalletError.WrongUserWallet If single-currency user wallet
* ID provided.
*/
@Deprecated("Use MultiWalletCryptoCurrenciesSupplier")
suspend fun getMultiCurrencyWalletCurrenciesSync(
userWalletId: UserWalletId,
refresh: Boolean = false,
): List<CryptoCurrency>
/**
* Get the coin for a specific network.
*
@ -101,28 +83,6 @@ interface CurrenciesRepository {
derivationPath: Network.DerivationPath,
): CryptoCurrency.Coin
/**
* Determines whether the tokens within a specific multi-currency user wallet are grouped.
*
* @param userWalletId The unique identifier of the user wallet.
* @return A [Flow] emitting a boolean value indicating whether the tokens are grouped.
* @throws DataError.UserWalletError.WrongUserWallet If single-currency user wallet
* ID provided.
*/
@Deprecated("Use SingleAccountListSupplier instead")
fun isTokensGrouped(userWalletId: UserWalletId): Flow<Boolean>
/**
* Determines whether the tokens within a specific multi-currency user wallet are sorted by balance.
*
* @param userWalletId The unique identifier of the user wallet.
* @return A [Flow] emitting a boolean value indicating whether the tokens are sorted by balance.
* @throws DataError.UserWalletError.WrongUserWallet If single-currency user wallet
* ID provided.
*/
@Deprecated("Use SingleAccountListSupplier instead")
fun isTokensSortedByBalance(userWalletId: UserWalletId): Flow<Boolean>
/**
* Determines whether the currency sending is blocked by network pending transaction
*
@ -153,10 +113,6 @@ interface CurrenciesRepository {
networkId: String,
): CryptoCurrency.Token
/** Get crypto currencies by [currencyRawId] from all user wallets */
@Deprecated("Use MultiAccountListSupplier instead")
fun getAllWalletsCryptoCurrencies(currencyRawId: CryptoCurrency.RawID): Flow<Map<UserWallet, List<CryptoCurrency>>>
fun isNetworkFeeZero(userWalletId: UserWalletId, network: Network): Boolean
@Throws

View file

@ -30,6 +30,7 @@ dependencies {
/** Domain models */
implementation(projects.domain.account)
implementation(projects.domain.account.status)
implementation(projects.domain.appCurrency.models)
implementation(projects.domain.balanceHiding.models)
implementation(projects.domain.blockaid.models)

View file

@ -6,7 +6,7 @@ import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.decompose.navigation.Router
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.domain.tokens.GetTokenListUseCase
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.features.wallet.utils.UserWalletsFetcher
import com.tangem.features.walletconnect.connections.components.WcSelectWalletComponent.WcSelectWalletParams
@ -24,7 +24,7 @@ internal class WcSelectWalletModel @Inject constructor(
paramsContainer: ParamsContainer,
messageSender: UiMessageSender,
userWalletsFetcherFactory: UserWalletsFetcher.Factory,
getTokenListUseCase: GetTokenListUseCase,
private val singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
private val router: Router,
override val dispatchers: CoroutineDispatcherProvider,
) : Model() {
@ -41,7 +41,7 @@ internal class WcSelectWalletModel @Inject constructor(
private val userWalletsFetcher = WcUserWalletsFetcher(
userWalletsFetcherFactory = userWalletsFetcherFactory,
getTokenListUseCase = getTokenListUseCase,
singleAccountStatusListSupplier = singleAccountStatusListSupplier,
messageSender = messageSender,
onWalletSelected = ::onWalletSelected,
)

View file

@ -6,8 +6,9 @@ import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
import com.tangem.domain.models.TotalFiatBalance
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.tokens.GetTokenListUseCase
import com.tangem.features.wallet.utils.UserWalletsFetcher
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
@ -17,12 +18,11 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
@Suppress("LongParameterList")
@ModelScoped
internal class WcUserWalletsFetcher(
userWalletsFetcherFactory: UserWalletsFetcher.Factory,
messageSender: UiMessageSender,
private val getTokenListUseCase: GetTokenListUseCase,
private val singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
private val onWalletSelected: (UserWalletId) -> Unit,
) {
@ -42,12 +42,13 @@ internal class WcUserWalletsFetcher(
}
private fun getTokenListFlow(walletItem: UserWalletItemUM): Flow<UserWalletItemUM> {
return getTokenListUseCase.launch(UserWalletId(walletItem.id)).map { lce ->
val information = lce.fold(
ifLoading = { UserWalletItemUM.Information.Loading },
ifError = { UserWalletItemUM.Information.Failed },
ifContent = { tokenList -> tokenCountInfo(tokenList.flattenCurrencies().size) },
)
return singleAccountStatusListSupplier(userWalletId = UserWalletId(walletItem.id)).map { accountStatusList ->
val information = when (accountStatusList.totalFiatBalance) {
is TotalFiatBalance.Failed -> UserWalletItemUM.Information.Failed
is TotalFiatBalance.Loading -> UserWalletItemUM.Information.Loading
is TotalFiatBalance.Loaded -> tokenCountInfo(accountStatusList.flattenCurrencies().size)
}
walletItem.copy(information = information)
}
}