Updated on 2026-08-14
This commit is contained in:
parent
616c0b2c05
commit
60d8a4f2a3
7 changed files with 83 additions and 68 deletions
|
|
@ -4,8 +4,8 @@ import com.tangem.datasource.api.tangemTech.models.QuotesResponse
|
|||
import com.tangem.datasource.local.datastore.core.StringKeyDataStore
|
||||
import com.tangem.datasource.local.quote.model.StoredQuote
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.channelFlow
|
||||
import com.tangem.utils.extensions.addOrReplace
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
internal class DefaultQuotesStore(
|
||||
private val dataStore: StringKeyDataStore<StoredQuote>,
|
||||
|
|
@ -13,17 +13,20 @@ internal class DefaultQuotesStore(
|
|||
|
||||
override fun get(currenciesIds: Set<CryptoCurrency.ID>): Flow<Set<StoredQuote>> {
|
||||
return channelFlow {
|
||||
if (dataStore.isEmpty()) {
|
||||
val flows = currenciesIds.mapNotNull { currencyId ->
|
||||
currencyId.rawCurrencyId?.let(dataStore::get)
|
||||
}
|
||||
|
||||
if (dataStore.isEmpty() || flows.isEmpty()) {
|
||||
send(emptySet())
|
||||
}
|
||||
|
||||
val quotes = currenciesIds.mapNotNull { currencyId ->
|
||||
currencyId.rawCurrencyId?.let {
|
||||
dataStore.getSyncOrNull(it)
|
||||
merge(*flows.toTypedArray())
|
||||
.scan<StoredQuote, Set<StoredQuote>>(emptySet()) { acc, quote ->
|
||||
acc.addOrReplace(quote) { it.rawCurrencyId == quote.rawCurrencyId }
|
||||
}
|
||||
}
|
||||
|
||||
send(quotes.toSet())
|
||||
.filter(Set<StoredQuote>::isNotEmpty)
|
||||
.collect(::send)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,16 +10,15 @@ import com.tangem.domain.tokens.repository.NetworksRepository
|
|||
import com.tangem.domain.tokens.repository.QuotesRepository
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
class GetCryptoCurrencyStatusesUseCase(
|
||||
class GetCryptoCurrencyStatusesSyncUseCase(
|
||||
internal val currenciesRepository: CurrenciesRepository,
|
||||
internal val quotesRepository: QuotesRepository,
|
||||
internal val networksRepository: NetworksRepository,
|
||||
internal val dispatchers: CoroutineDispatcherProvider,
|
||||
) {
|
||||
|
||||
operator fun invoke(userWalletId: UserWalletId): Flow<Either<TokenListError, List<CryptoCurrencyStatus>>> {
|
||||
suspend operator fun invoke(userWalletId: UserWalletId): Either<TokenListError, List<CryptoCurrencyStatus>> {
|
||||
val operations = CurrenciesStatusesOperations(
|
||||
userWalletId = userWalletId,
|
||||
currenciesRepository = currenciesRepository,
|
||||
|
|
@ -27,9 +26,7 @@ class GetCryptoCurrencyStatusesUseCase(
|
|||
networksRepository = networksRepository,
|
||||
)
|
||||
|
||||
return operations.getCurrenciesStatusesMergedFlow()
|
||||
.map { maybeCurrenciesStatuses ->
|
||||
maybeCurrenciesStatuses.mapLeft(CurrenciesStatusesOperations.Error::mapToTokenListError)
|
||||
}
|
||||
return operations.getCurrenciesStatusesSync()
|
||||
.mapLeft { error -> error.mapToTokenListError() }
|
||||
}
|
||||
}
|
||||
|
|
@ -67,33 +67,21 @@ internal class CurrenciesStatusesOperations(
|
|||
}
|
||||
}
|
||||
|
||||
fun getCurrenciesStatusesMergedFlow(): Flow<Either<Error, List<CryptoCurrencyStatus>>> {
|
||||
return getMultiCurrencyWalletCurrencies().transformLatest { maybeCurrencies ->
|
||||
val nonEmptyCurrencies = maybeCurrencies.fold(
|
||||
ifLeft = { error ->
|
||||
emit(error.left())
|
||||
return@transformLatest
|
||||
suspend fun getCurrenciesStatusesSync(): Either<Error, List<CryptoCurrencyStatus>> {
|
||||
return either {
|
||||
catch(
|
||||
block = {
|
||||
val nonEmptyCurrencies =
|
||||
currenciesRepository.getMultiCurrencyWalletCurrenciesSync(userWalletId).toNonEmptyListOrNull()
|
||||
?: return emptyList<CryptoCurrencyStatus>().right()
|
||||
val (networks, currenciesIds) = getIds(nonEmptyCurrencies)
|
||||
val quotes = quotesRepository.getQuotesSync(currenciesIds, false).right()
|
||||
val networkStatuses =
|
||||
networksRepository.getNetworkStatusesSync(userWalletId, networks, false).right()
|
||||
return createCurrenciesStatuses(nonEmptyCurrencies, quotes, networkStatuses)
|
||||
},
|
||||
ifRight = List<CryptoCurrency>::toNonEmptyListOrNull,
|
||||
catch = { raise(Error.DataError(it)) },
|
||||
)
|
||||
|
||||
if (nonEmptyCurrencies == null) {
|
||||
val emptyCurrenciesStatuses = emptyList<CryptoCurrencyStatus>()
|
||||
|
||||
emit(emptyCurrenciesStatuses.right())
|
||||
return@transformLatest
|
||||
}
|
||||
|
||||
val (networks, currenciesIds) = getIds(nonEmptyCurrencies)
|
||||
|
||||
val currenciesFlow = combine(
|
||||
getQuotes(currenciesIds),
|
||||
getNetworksStatuses(networks),
|
||||
) { maybeQuotes, maybeNetworksStatuses ->
|
||||
createCurrenciesStatuses(nonEmptyCurrencies, maybeQuotes, maybeNetworksStatuses)
|
||||
}
|
||||
|
||||
emitAll(currenciesFlow)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package com.tangem.feature.swap.domain
|
|||
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase
|
||||
import com.tangem.domain.tokens.GetCryptoCurrencyStatusesUseCase
|
||||
import com.tangem.domain.tokens.GetCryptoCurrencyStatusesSyncUseCase
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
|
|
@ -22,7 +22,6 @@ import com.tangem.lib.crypto.UserWalletManager
|
|||
import com.tangem.lib.crypto.models.*
|
||||
import com.tangem.lib.crypto.models.transactions.SendTxResult
|
||||
import com.tangem.utils.toFiatString
|
||||
import kotlinx.coroutines.flow.first
|
||||
import timber.log.Timber
|
||||
import java.math.BigDecimal
|
||||
import java.math.RoundingMode
|
||||
|
|
@ -39,7 +38,7 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
private val networksRepository: NetworksRepository,
|
||||
private val walletFeatureToggles: WalletFeatureToggles,
|
||||
private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
|
||||
private val getMultiCryptoCurrencyStatusUseCase: GetCryptoCurrencyStatusesUseCase,
|
||||
private val getMultiCryptoCurrencyStatusUseCase: GetCryptoCurrencyStatusesSyncUseCase,
|
||||
) : SwapInteractor {
|
||||
|
||||
// TODO: Move to DI
|
||||
|
|
@ -61,7 +60,6 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
requireNotNull(selectedWallet) { "No selected wallet" }
|
||||
|
||||
val walletCurrencyStatuses = getMultiCryptoCurrencyStatusUseCase(selectedWallet.walletId)
|
||||
.first()
|
||||
.getOrElse { emptyList() }
|
||||
|
||||
val walletCurrencyStatusesExceptInitial = walletCurrencyStatuses.filter {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package com.tangem.feature.swap.domain.di
|
|||
|
||||
import com.tangem.domain.tokens.GetCardTokensListUseCase
|
||||
import com.tangem.domain.tokens.GetCryptoCurrenciesUseCase
|
||||
import com.tangem.domain.tokens.GetCryptoCurrencyStatusesUseCase
|
||||
import com.tangem.domain.tokens.GetCryptoCurrencyStatusesSyncUseCase
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.tokens.repository.NetworksRepository
|
||||
import com.tangem.domain.tokens.repository.QuotesRepository
|
||||
|
|
@ -35,7 +35,7 @@ class SwapDomainModule {
|
|||
networksRepository: NetworksRepository,
|
||||
walletFeatureToggles: WalletFeatureToggles,
|
||||
@SwapScope getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
|
||||
@SwapScope getCryptoCurrencyStatusUseCase: GetCryptoCurrencyStatusesUseCase,
|
||||
@SwapScope getCryptoCurrencyStatusUseCase: GetCryptoCurrencyStatusesSyncUseCase,
|
||||
): SwapInteractor {
|
||||
return SwapInteractorImpl(
|
||||
transactionManager = transactionManager,
|
||||
|
|
@ -81,8 +81,8 @@ class SwapDomainModule {
|
|||
quotesRepository: QuotesRepository,
|
||||
networksRepository: NetworksRepository,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): GetCryptoCurrencyStatusesUseCase {
|
||||
return GetCryptoCurrencyStatusesUseCase(
|
||||
): GetCryptoCurrencyStatusesSyncUseCase {
|
||||
return GetCryptoCurrencyStatusesSyncUseCase(
|
||||
currenciesRepository = currenciesRepository,
|
||||
quotesRepository = quotesRepository,
|
||||
networksRepository = networksRepository,
|
||||
|
|
|
|||
|
|
@ -2,10 +2,13 @@ package com.tangem.feature.swap.converters
|
|||
|
||||
import com.tangem.common.Provider
|
||||
import com.tangem.core.ui.components.currency.tokenicon.TokenIconState
|
||||
import com.tangem.core.ui.extensions.getTintForTokenIcon
|
||||
import com.tangem.core.ui.extensions.networkIconResId
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.extensions.tryGetBackgroundForTokenIcon
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.feature.swap.domain.models.domain.CryptoCurrencySwapInfo
|
||||
import com.tangem.feature.swap.domain.models.ui.CurrenciesGroup
|
||||
|
|
@ -26,13 +29,13 @@ class TokensDataConverter(
|
|||
val availableTitle = TokenToSelectState.Title(stringReference("My tokens")) // todo replace with resource
|
||||
val unavailableTitle = TokenToSelectState.Title(stringReference("My tokens")) // todo replace with resource
|
||||
return SwapSelectTokenStateHolder(
|
||||
availableTokens = value.available.map { tokenWithBalanceToTokenToSelect(it) }
|
||||
availableTokens = value.available.map { tokenWithBalanceToTokenToSelect(it, true) }
|
||||
.toMutableList()
|
||||
.apply {
|
||||
this.add(0, availableTitle)
|
||||
}
|
||||
.toImmutableList(),
|
||||
unavailableTokens = value.unavailable.map { tokenWithBalanceToTokenToSelect(it) }
|
||||
unavailableTokens = value.unavailable.map { tokenWithBalanceToTokenToSelect(it, false) }
|
||||
.toMutableList()
|
||||
.apply {
|
||||
this.add(0, unavailableTitle)
|
||||
|
|
@ -43,18 +46,17 @@ class TokensDataConverter(
|
|||
)
|
||||
}
|
||||
|
||||
private fun tokenWithBalanceToTokenToSelect(cryptoCurrencySwapInfo: CryptoCurrencySwapInfo): TokenToSelectState {
|
||||
private fun tokenWithBalanceToTokenToSelect(
|
||||
cryptoCurrencySwapInfo: CryptoCurrencySwapInfo,
|
||||
isAvailable: Boolean,
|
||||
): TokenToSelectState {
|
||||
val cryptoCurrencyStatus = cryptoCurrencySwapInfo.currencyStatus
|
||||
return TokenToSelectState.TokenToSelect(
|
||||
id = cryptoCurrencyStatus.currency.id.value,
|
||||
name = cryptoCurrencyStatus.currency.name,
|
||||
symbol = cryptoCurrencyStatus.currency.symbol,
|
||||
tokenIcon = TokenIconState.CoinIcon(
|
||||
url = cryptoCurrencyStatus.currency.iconUrl,
|
||||
fallbackResId = cryptoCurrencyStatus.currency.networkIconResId,
|
||||
isGrayscale = false,
|
||||
showCustomBadge = cryptoCurrencyStatus.currency.isCustom,
|
||||
),
|
||||
available = isAvailable,
|
||||
tokenIcon = convertIcon(cryptoCurrencyStatus.currency, isAvailable),
|
||||
addedTokenBalanceData = TokenBalanceData(
|
||||
amount = formatCryptoAmount(cryptoCurrencyStatus),
|
||||
amountEquivalent = formatFiatAmount(cryptoCurrencyStatus, appCurrencyProvider.invoke()),
|
||||
|
|
@ -63,6 +65,32 @@ class TokensDataConverter(
|
|||
)
|
||||
}
|
||||
|
||||
private fun convertIcon(currency: CryptoCurrency, isAvailable: Boolean): TokenIconState {
|
||||
return when (currency) {
|
||||
is CryptoCurrency.Coin -> {
|
||||
TokenIconState.CoinIcon(
|
||||
url = currency.iconUrl,
|
||||
fallbackResId = currency.networkIconResId,
|
||||
isGrayscale = !isAvailable,
|
||||
showCustomBadge = currency.isCustom,
|
||||
)
|
||||
}
|
||||
is CryptoCurrency.Token -> {
|
||||
val isGrayscale = currency.network.isTestnet
|
||||
val background = currency.tryGetBackgroundForTokenIcon(isGrayscale)
|
||||
val tint = getTintForTokenIcon(background)
|
||||
TokenIconState.TokenIcon(
|
||||
url = currency.iconUrl,
|
||||
isGrayscale = !isAvailable,
|
||||
showCustomBadge = currency.isCustom,
|
||||
networkBadgeIconResId = currency.networkIconResId,
|
||||
fallbackTint = tint,
|
||||
fallbackBackground = background,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun formatCryptoAmount(cryptoCurrencyStatus: CryptoCurrencyStatus): String {
|
||||
return BigDecimalFormatter.formatCryptoAmount(
|
||||
cryptoCurrencyStatus.value.amount,
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@ private fun TitleHeader(item: TokenToSelectState.Title, modifier: Modifier = Mod
|
|||
.background(TangemTheme.colors.background.action),
|
||||
) {
|
||||
Text(
|
||||
text = item.title.resolveReference(),
|
||||
text = item.title.resolveReference().uppercase(),
|
||||
style = TangemTheme.typography.overline,
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
|
|
@ -133,7 +133,10 @@ private fun TokenItem(
|
|||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.height(TangemTheme.dimens.size72)
|
||||
.clickable(onClick = onTokenClick)
|
||||
.clickable(
|
||||
enabled = token.available,
|
||||
onClick = onTokenClick,
|
||||
)
|
||||
.padding(
|
||||
vertical = TangemTheme.dimens.spacing14,
|
||||
horizontal = TangemTheme.dimens.spacing16,
|
||||
|
|
@ -169,13 +172,7 @@ private fun TokenItem(
|
|||
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
|
||||
if (!token.available) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.swapping_token_not_available),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
} else if (token.addedTokenBalanceData != null) {
|
||||
if (token.addedTokenBalanceData != null) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.End,
|
||||
verticalArrangement = Arrangement.Center,
|
||||
|
|
@ -190,7 +187,11 @@ private fun TokenItem(
|
|||
token.addedTokenBalanceData.amountEquivalent.orEmpty()
|
||||
},
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
color = if (token.available) {
|
||||
TangemTheme.colors.text.primary1
|
||||
} else {
|
||||
TangemTheme.colors.text.tertiary
|
||||
},
|
||||
)
|
||||
SpacerW2()
|
||||
Text(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue