Updated on 2026-08-14
This commit is contained in:
parent
861f650cd4
commit
59680b5305
7 changed files with 225 additions and 34 deletions
|
|
@ -1,7 +1,7 @@
|
|||
package com.tangem.tap.domain.walletconnect2.domain
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.domain.common.extensions.toNetworkId
|
||||
import com.tangem.blockchainsdk.utils.toNetworkId
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
|
|
|
|||
|
|
@ -1,11 +1,16 @@
|
|||
package com.tangem.domain.tokens
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.left
|
||||
import com.tangem.domain.core.lce.LceFlow
|
||||
import com.tangem.domain.core.utils.EitherFlow
|
||||
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.tokens.error.TokenListError
|
||||
import com.tangem.domain.tokens.error.mapper.mapToTokenListError
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.tokens.model.TokenList
|
||||
import com.tangem.domain.tokens.operations.CurrenciesStatusesLceOperations
|
||||
import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations
|
||||
import com.tangem.domain.tokens.operations.TokenListOperations
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
|
|
@ -13,7 +18,6 @@ import com.tangem.domain.tokens.repository.NetworksRepository
|
|||
import com.tangem.domain.tokens.repository.QuotesRepository
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.emitAll
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.transformLatest
|
||||
|
|
@ -25,11 +29,18 @@ class GetTokenListUseCase(
|
|||
) {
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
operator fun invoke(userWalletId: UserWalletId): Flow<Either<TokenListError, TokenList>> {
|
||||
return getTokensStatuses(userWalletId).transformLatest { maybeTokens ->
|
||||
fun launch(userWalletId: UserWalletId): EitherFlow<TokenListError, TokenList> {
|
||||
val operations = CurrenciesStatusesOperations(
|
||||
userWalletId = userWalletId,
|
||||
currenciesRepository = currenciesRepository,
|
||||
quotesRepository = quotesRepository,
|
||||
networksRepository = networksRepository,
|
||||
)
|
||||
|
||||
return operations.getCurrenciesStatusesFlow().transformLatest { maybeTokens ->
|
||||
maybeTokens.fold(
|
||||
ifLeft = { error ->
|
||||
emit(error.left())
|
||||
emit(error.mapToTokenListError().left())
|
||||
},
|
||||
ifRight = { tokens ->
|
||||
emitAll(createTokenList(userWalletId, tokens))
|
||||
|
|
@ -38,26 +49,35 @@ class GetTokenListUseCase(
|
|||
}
|
||||
}
|
||||
|
||||
private fun getTokensStatuses(
|
||||
userWalletId: UserWalletId,
|
||||
): Flow<Either<TokenListError, List<CryptoCurrencyStatus>>> {
|
||||
val operations = CurrenciesStatusesOperations(
|
||||
userWalletId = userWalletId,
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
fun launchLce(userWalletId: UserWalletId): LceFlow<TokenListError, TokenList> {
|
||||
val operations = CurrenciesStatusesLceOperations(
|
||||
currenciesRepository = currenciesRepository,
|
||||
quotesRepository = quotesRepository,
|
||||
networksRepository = networksRepository,
|
||||
)
|
||||
|
||||
return operations.getCurrenciesStatusesFlow()
|
||||
.map { maybeCurrenciesStatuses ->
|
||||
maybeCurrenciesStatuses.mapLeft(CurrenciesStatusesOperations.Error::mapToTokenListError)
|
||||
}
|
||||
return operations.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 createTokenList(
|
||||
userWalletId: UserWalletId,
|
||||
tokens: List<CryptoCurrencyStatus>,
|
||||
): Flow<Either<TokenListError, TokenList>> {
|
||||
): EitherFlow<TokenListError, TokenList> {
|
||||
val operations = TokenListOperations(
|
||||
userWalletId = userWalletId,
|
||||
tokens = tokens,
|
||||
|
|
@ -68,4 +88,22 @@ class GetTokenListUseCase(
|
|||
maybeTokenList.mapLeft(TokenListOperations.Error::mapToTokenListError)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,153 @@
|
|||
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.utils.lceError
|
||||
import com.tangem.domain.core.utils.lceLoading
|
||||
import com.tangem.domain.tokens.error.TokenListError
|
||||
import com.tangem.domain.tokens.model.*
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.tokens.repository.NetworksRepository
|
||||
import com.tangem.domain.tokens.repository.QuotesRepository
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
internal class CurrenciesStatusesLceOperations(
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val quotesRepository: QuotesRepository,
|
||||
private val networksRepository: NetworksRepository,
|
||||
) {
|
||||
|
||||
fun getCurrenciesStatuses(userWalletId: UserWalletId): LceFlow<TokenListError, List<CryptoCurrencyStatus>> {
|
||||
return getMultiCurrencyWalletCurrencies(userWalletId).transform transform@{ maybeCurrencies ->
|
||||
val nonEmptyCurrencies = maybeCurrencies.fold(
|
||||
ifLoading = { maybeContent ->
|
||||
emit(createLoadingCurrenciesStatuses(maybeContent))
|
||||
return@transform
|
||||
},
|
||||
ifContent = { content ->
|
||||
val nonEmptyCurrencies = content.toNonEmptyListOrNull()
|
||||
|
||||
if (nonEmptyCurrencies == null) {
|
||||
emit(TokenListError.EmptyTokens.lceError())
|
||||
return@transform
|
||||
} else {
|
||||
nonEmptyCurrencies
|
||||
}
|
||||
},
|
||||
ifError = { error ->
|
||||
emit(error.lceError())
|
||||
return@transform
|
||||
},
|
||||
)
|
||||
|
||||
val (networks, currenciesIds) = getIds(nonEmptyCurrencies)
|
||||
|
||||
combine(
|
||||
getQuotes(currenciesIds),
|
||||
getNetworksStatuses(userWalletId, networks),
|
||||
) { maybeQuotes, maybeNetworksStatuses ->
|
||||
val statuses = createCurrenciesStatuses(nonEmptyCurrencies, maybeQuotes, maybeNetworksStatuses)
|
||||
emit(statuses)
|
||||
}.collect()
|
||||
}
|
||||
}
|
||||
|
||||
private fun createLoadingCurrenciesStatuses(
|
||||
maybeCurrencies: List<CryptoCurrency>?,
|
||||
): Lce<TokenListError, List<CryptoCurrencyStatus>> {
|
||||
val nonEmptyCurrencies = maybeCurrencies?.toNonEmptyListOrNull()
|
||||
|
||||
val statuses = if (nonEmptyCurrencies == null) {
|
||||
lceLoading()
|
||||
} else {
|
||||
createCurrenciesStatuses(
|
||||
nonEmptyCurrencies,
|
||||
maybeNetworkStatuses = null,
|
||||
maybeQuotes = null,
|
||||
)
|
||||
}
|
||||
|
||||
return statuses
|
||||
}
|
||||
|
||||
private fun getMultiCurrencyWalletCurrencies(
|
||||
userWalletId: UserWalletId,
|
||||
): LceFlow<TokenListError, List<CryptoCurrency>> {
|
||||
return currenciesRepository.getMultiCurrencyWalletCurrenciesUpdatesLce(userWalletId)
|
||||
.map { maybeCurrencies ->
|
||||
maybeCurrencies.mapError { TokenListError.DataError(it) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun createCurrenciesStatuses(
|
||||
currencies: NonEmptyList<CryptoCurrency>,
|
||||
maybeQuotes: Either<TokenListError, Set<Quote>>?,
|
||||
maybeNetworkStatuses: Lce<TokenListError, Set<NetworkStatus>>?,
|
||||
): Lce<TokenListError, List<CryptoCurrencyStatus>> = lce {
|
||||
isLoading.set(maybeNetworkStatuses == null)
|
||||
|
||||
var quotesRetrievingFailed = false
|
||||
|
||||
val networksStatuses = maybeNetworkStatuses?.bindOrNull()?.toNonEmptySetOrNull()
|
||||
val quotes = recover({ maybeQuotes?.bind()?.toNonEmptySetOrNull() }) {
|
||||
quotesRetrievingFailed = true
|
||||
null
|
||||
}
|
||||
|
||||
currencies.map { currency ->
|
||||
val quote = quotes?.firstOrNull { it.rawCurrencyId == currency.id.rawCurrencyId }
|
||||
val networkStatus = networksStatuses?.firstOrNull { it.network == currency.network }
|
||||
|
||||
createCurrencyStatus(currency, quote, networkStatus, ignoreQuote = quotesRetrievingFailed)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createCurrencyStatus(
|
||||
currency: CryptoCurrency,
|
||||
quote: Quote?,
|
||||
networkStatus: NetworkStatus?,
|
||||
ignoreQuote: Boolean,
|
||||
): CryptoCurrencyStatus {
|
||||
val currencyStatusOperations = CurrencyStatusOperations(
|
||||
currency = currency,
|
||||
quote = quote,
|
||||
networkStatus = networkStatus,
|
||||
ignoreQuote = ignoreQuote,
|
||||
)
|
||||
|
||||
return currencyStatusOperations.createTokenStatus()
|
||||
}
|
||||
|
||||
private fun getQuotes(tokensIds: NonEmptySet<CryptoCurrency.ID>): Flow<Either<TokenListError, Set<Quote>>> {
|
||||
return quotesRepository.getQuotesUpdates(tokensIds)
|
||||
.map<Set<Quote>, Either<TokenListError, Set<Quote>>> { it.right() }
|
||||
.catch { emit(TokenListError.DataError(it).left()) }
|
||||
}
|
||||
|
||||
private fun getNetworksStatuses(
|
||||
userWalletId: UserWalletId,
|
||||
networks: NonEmptySet<Network>,
|
||||
): LceFlow<TokenListError, Set<NetworkStatus>> {
|
||||
return networksRepository.getNetworkStatusesUpdatesLce(userWalletId, networks)
|
||||
.map { maybeStatuses ->
|
||||
maybeStatuses.mapError { TokenListError.DataError(it) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun getIds(currencies: List<CryptoCurrency>): Pair<NonEmptySet<Network>, NonEmptySet<CryptoCurrency.ID>> {
|
||||
val currencyIdToNetworkId = currencies.associate { currency ->
|
||||
currency.id to currency.network
|
||||
}
|
||||
val currenciesIds = currencyIdToNetworkId.keys.toNonEmptySetOrNull()
|
||||
val networks = currencyIdToNetworkId.values.toNonEmptySetOrNull()
|
||||
|
||||
requireNotNull(currenciesIds) { "Currencies IDs cannot be empty" }
|
||||
requireNotNull(networks) { "Networks IDs cannot be empty" }
|
||||
|
||||
return networks to currenciesIds
|
||||
}
|
||||
}
|
||||
|
|
@ -43,7 +43,7 @@ internal class GetTokenListUseCaseTest {
|
|||
)
|
||||
|
||||
// When
|
||||
val result = useCase(userWalletId)
|
||||
val result = useCase.launch(userWalletId)
|
||||
.take(count = 2)
|
||||
.toList()
|
||||
|
||||
|
|
@ -59,7 +59,7 @@ internal class GetTokenListUseCaseTest {
|
|||
val useCase = getUseCase(tokens = flowOf(DataError.NetworkError.NoInternetConnection.left()))
|
||||
|
||||
// When
|
||||
val result = useCase(userWalletId).first()
|
||||
val result = useCase.launch(userWalletId).first()
|
||||
|
||||
// Then
|
||||
assertEquals(expectedResult, result)
|
||||
|
|
@ -79,7 +79,7 @@ internal class GetTokenListUseCaseTest {
|
|||
)
|
||||
|
||||
// When
|
||||
val result = useCase(userWalletId)
|
||||
val result = useCase.launch(userWalletId)
|
||||
.take(count = 2)
|
||||
.toList()
|
||||
|
||||
|
|
@ -95,7 +95,7 @@ internal class GetTokenListUseCaseTest {
|
|||
val useCase = getUseCase(isGrouped = flowOf(DataError.NetworkError.NoInternetConnection.left()))
|
||||
|
||||
// When
|
||||
val result = useCase(userWalletId).first()
|
||||
val result = useCase.launch(userWalletId).first()
|
||||
|
||||
// Then
|
||||
assertEquals(expectedResult, result)
|
||||
|
|
@ -109,7 +109,7 @@ internal class GetTokenListUseCaseTest {
|
|||
val useCase = getUseCase(isSortedByBalance = flowOf(DataError.NetworkError.NoInternetConnection.left()))
|
||||
|
||||
// When
|
||||
val result = useCase(userWalletId).first()
|
||||
val result = useCase.launch(userWalletId).first()
|
||||
|
||||
// Then
|
||||
assertEquals(expectedResult, result)
|
||||
|
|
@ -134,7 +134,7 @@ internal class GetTokenListUseCaseTest {
|
|||
)
|
||||
|
||||
// When
|
||||
val result = useCase(userWalletId)
|
||||
val result = useCase.launch(userWalletId)
|
||||
.take(count = 3)
|
||||
.toList()
|
||||
|
||||
|
|
@ -153,7 +153,7 @@ internal class GetTokenListUseCaseTest {
|
|||
val useCase = getUseCase(isGrouped = flowOf(true.right()))
|
||||
|
||||
// When
|
||||
val result = useCase(userWalletId)
|
||||
val result = useCase.launch(userWalletId)
|
||||
.take(count = 2)
|
||||
.toList()
|
||||
|
||||
|
|
@ -175,7 +175,7 @@ internal class GetTokenListUseCaseTest {
|
|||
)
|
||||
|
||||
// When
|
||||
val result = useCase(userWalletId)
|
||||
val result = useCase.launch(userWalletId)
|
||||
.take(count = 2)
|
||||
.toList()
|
||||
|
||||
|
|
@ -197,7 +197,7 @@ internal class GetTokenListUseCaseTest {
|
|||
)
|
||||
|
||||
// When
|
||||
val result = useCase(userWalletId)
|
||||
val result = useCase.launch(userWalletId)
|
||||
.take(count = 2)
|
||||
.toList()
|
||||
|
||||
|
|
@ -212,7 +212,7 @@ internal class GetTokenListUseCaseTest {
|
|||
val useCase = getUseCase(tokens = flowOf(emptyList<CryptoCurrency>().right()))
|
||||
|
||||
// When
|
||||
val result = useCase(userWalletId).first()
|
||||
val result = useCase.launch(userWalletId).first()
|
||||
|
||||
// Then
|
||||
assertEquals(expectedResult, result)
|
||||
|
|
@ -225,7 +225,7 @@ internal class GetTokenListUseCaseTest {
|
|||
val useCase = getUseCase(tokens = flowOf())
|
||||
|
||||
// When
|
||||
val result = useCase(userWalletId).first()
|
||||
val result = useCase.launch(userWalletId).first()
|
||||
|
||||
// Then
|
||||
assertEquals(expectedResult, result)
|
||||
|
|
@ -241,7 +241,7 @@ internal class GetTokenListUseCaseTest {
|
|||
val useCase = getUseCase(statuses = flowOf())
|
||||
|
||||
// When
|
||||
val result = useCase(userWalletId)
|
||||
val result = useCase.launch(userWalletId)
|
||||
.take(count = 2)
|
||||
.toList()
|
||||
|
||||
|
|
@ -256,7 +256,7 @@ internal class GetTokenListUseCaseTest {
|
|||
val useCase = getUseCase(statuses = flowOf(emptySet<NetworkStatus>().right()))
|
||||
|
||||
// When
|
||||
val result = useCase(userWalletId).first()
|
||||
val result = useCase.launch(userWalletId).first()
|
||||
|
||||
// Then
|
||||
assertEquals(expectedResult, result)
|
||||
|
|
@ -275,7 +275,7 @@ internal class GetTokenListUseCaseTest {
|
|||
)
|
||||
|
||||
// When
|
||||
val result = useCase(userWalletId)
|
||||
val result = useCase.launch(userWalletId)
|
||||
.take(count = 2)
|
||||
.toList()
|
||||
|
||||
|
|
@ -293,7 +293,7 @@ internal class GetTokenListUseCaseTest {
|
|||
)
|
||||
|
||||
// When
|
||||
val result = useCase(userWalletId).first()
|
||||
val result = useCase.launch(userWalletId).first()
|
||||
|
||||
// Then
|
||||
assertEquals(expectedResult, result)
|
||||
|
|
|
|||
|
|
@ -161,7 +161,7 @@ internal class OrganizeTokensViewModel @Inject constructor(
|
|||
|
||||
private fun bootstrapTokenList() {
|
||||
viewModelScope.launch(dispatchers.default) {
|
||||
val maybeTokenList = getTokenListUseCase(userWalletId)
|
||||
val maybeTokenList = getTokenListUseCase.launch(userWalletId)
|
||||
.first { it.getOrNull()?.totalFiatBalance !is TokenList.FiatBalance.Loading }
|
||||
|
||||
maybeTokenList.fold(
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
|
|||
|
||||
val promoFlow = flow { emit(promoRepository.getChangellyPromoBanner()) }
|
||||
return combine(
|
||||
flow = getTokenListUseCase(userWallet.walletId).conflate(),
|
||||
flow = getTokenListUseCase.launch(userWallet.walletId).conflate(),
|
||||
flow2 = isReadyToShowRateAppUseCase().conflate(),
|
||||
flow3 = isNeedToBackupUseCase(userWallet.walletId).conflate(),
|
||||
flow4 = shouldShowSwapPromoWalletUseCase().conflate(),
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ internal class MultiWalletTokenListSubscriber(
|
|||
getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase,
|
||||
) {
|
||||
|
||||
override fun tokenListFlow(): MaybeTokenListFlow = getTokenListUseCase(userWallet.walletId)
|
||||
override fun tokenListFlow(): MaybeTokenListFlow = getTokenListUseCase.launch(userWallet.walletId)
|
||||
|
||||
override suspend fun onTokenListReceived(maybeTokenList: Either<TokenListError, TokenList>) {
|
||||
// TODO disabled for 5.7.2 because of potential critical
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue