Updated on 2026-08-14
This commit is contained in:
parent
90abe449d6
commit
d06051845b
15 changed files with 37 additions and 1002 deletions
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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() },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue