From 60d8a4f2a320313fd773e906f18093c96ab9c97d Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 21 Nov 2023 12:03:02 +0300 Subject: [PATCH] Updated on 2026-08-14 --- .../local/quote/DefaultQuotesStore.kt | 21 +++++---- ...> GetCryptoCurrencyStatusesSyncUseCase.kt} | 11 ++--- .../CurrenciesStatusesOperations.kt | 38 ++++++--------- .../feature/swap/domain/SwapInteractorImpl.kt | 6 +-- .../swap/domain/di/SwapDomainModule.kt | 8 ++-- .../swap/converters/TokensDataConverter.kt | 46 +++++++++++++++---- .../feature/swap/ui/SwapSelectTokenScreen.kt | 21 +++++---- 7 files changed, 83 insertions(+), 68 deletions(-) rename domain/tokens/src/main/kotlin/com/tangem/domain/tokens/{GetCryptoCurrencyStatusesUseCase.kt => GetCryptoCurrencyStatusesSyncUseCase.kt} (73%) diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/quote/DefaultQuotesStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/quote/DefaultQuotesStore.kt index e28a90bfb2..fc0d1e0e42 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/quote/DefaultQuotesStore.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/quote/DefaultQuotesStore.kt @@ -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, @@ -13,17 +13,20 @@ internal class DefaultQuotesStore( override fun get(currenciesIds: Set): Flow> { 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>(emptySet()) { acc, quote -> + acc.addOrReplace(quote) { it.rawCurrencyId == quote.rawCurrencyId } } - } - - send(quotes.toSet()) + .filter(Set::isNotEmpty) + .collect(::send) } } diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyStatusesUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyStatusesSyncUseCase.kt similarity index 73% rename from domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyStatusesUseCase.kt rename to domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyStatusesSyncUseCase.kt index 2734f1b2e3..cdf58b3080 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyStatusesUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyStatusesSyncUseCase.kt @@ -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>> { + suspend operator fun invoke(userWalletId: UserWalletId): Either> { 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() } } } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesOperations.kt index 60e3d3b4b0..3ce6112296 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesOperations.kt @@ -67,33 +67,21 @@ internal class CurrenciesStatusesOperations( } } - fun getCurrenciesStatusesMergedFlow(): Flow>> { - return getMultiCurrencyWalletCurrencies().transformLatest { maybeCurrencies -> - val nonEmptyCurrencies = maybeCurrencies.fold( - ifLeft = { error -> - emit(error.left()) - return@transformLatest + suspend fun getCurrenciesStatusesSync(): Either> { + return either { + catch( + block = { + val nonEmptyCurrencies = + currenciesRepository.getMultiCurrencyWalletCurrenciesSync(userWalletId).toNonEmptyListOrNull() + ?: return emptyList().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::toNonEmptyListOrNull, + catch = { raise(Error.DataError(it)) }, ) - - if (nonEmptyCurrencies == null) { - val emptyCurrenciesStatuses = emptyList() - - 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) } } diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt index f9d1faa5bb..ec089f6df7 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt @@ -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 { diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt index 3169f16427..7fa93f1c70 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt @@ -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, diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/converters/TokensDataConverter.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/converters/TokensDataConverter.kt index 03047e24b8..e3eff35d4c 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/converters/TokensDataConverter.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/converters/TokensDataConverter.kt @@ -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, diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSelectTokenScreen.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSelectTokenScreen.kt index 2bbeedc808..6e89f78c3a 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSelectTokenScreen.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSelectTokenScreen.kt @@ -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(