Updated on 2026-08-14
This commit is contained in:
commit
3c36f08490
15 changed files with 222 additions and 127 deletions
|
|
@ -694,7 +694,7 @@
|
|||
<string name="initial_message_tap_header">Lege die Karte oder Ring an</string>
|
||||
<string name="initial_wallet_sync_banner_description">Deine Wallet ist eingerichtet und einsatzbereit!</string>
|
||||
<string name="initial_wallet_sync_banner_title">Wallet erfolgreich importiert</string>
|
||||
<string name="initial_wallet_sync_restore_progress">Wiederherstellung %d%</string>
|
||||
<string name="initial_wallet_sync_restore_progress">Wiederherstellung %d%%</string>
|
||||
<string name="key_invalidated_warning_description">Du hast deine biometrischen Daten aktualisiert, scanne deine Karte oder Ring, um einzutreten</string>
|
||||
<string name="koinos_insufficient_balance_to_send_koin_description">Dein Guthaben sollte höher sein als der Gebührenwert, um eine Überweisung zu tätigen</string>
|
||||
<string name="koinos_insufficient_balance_to_send_koin_title">Unzureichende Mittel</string>
|
||||
|
|
|
|||
|
|
@ -689,7 +689,7 @@
|
|||
<string name="initial_message_tap_header">カードまたはリングをタップ</string>
|
||||
<string name="initial_wallet_sync_banner_description">ウォレットは同期され、準備ができています。\n一部のトークンが見つかりませんか?</string>
|
||||
<string name="initial_wallet_sync_banner_title">ウォレットのインポートが完了しました</string>
|
||||
<string name="initial_wallet_sync_restore_progress">%d%を復元中</string>
|
||||
<string name="initial_wallet_sync_restore_progress">%d%%を復元中</string>
|
||||
<string name="key_invalidated_warning_description">生体認証を更新しました。カードまたはリングをスキャンして入ってください。</string>
|
||||
<string name="koinos_insufficient_balance_to_send_koin_description">送金するには、残高が手数料額より高くなければなりません。</string>
|
||||
<string name="koinos_insufficient_balance_to_send_koin_title">残高不足</string>
|
||||
|
|
|
|||
|
|
@ -306,6 +306,7 @@
|
|||
<string name="common_hour">hora</string>
|
||||
<string name="common_import">Importar</string>
|
||||
<string name="common_in_progress">Em andamento</string>
|
||||
<string name="common_insufficient_balance">Saldo insuficiente</string>
|
||||
<string name="common_later">Mais tarde</string>
|
||||
<string name="common_learn_more">Saber mais</string>
|
||||
<string name="common_left">%1$s falta</string>
|
||||
|
|
@ -692,6 +693,9 @@
|
|||
<string name="initial_message_scan_header">Toque para digitalizar</string>
|
||||
<string name="initial_message_sign_header">Toque para assinar</string>
|
||||
<string name="initial_message_tap_header">Toque no Cartão ou no Anel</string>
|
||||
<string name="initial_wallet_sync_banner_description">Sua carteira está sincronizada e pronta. \n Faltam alguns tokens?</string>
|
||||
<string name="initial_wallet_sync_banner_title">Carteira importada com sucesso.</string>
|
||||
<string name="initial_wallet_sync_restore_progress">Restaurando %d%%</string>
|
||||
<string name="key_invalidated_warning_description">Você atualizou seus dados biométricos. Escaneie seu cartão ou toque a campainha para entrar.</string>
|
||||
<string name="koinos_insufficient_balance_to_send_koin_description">Seu saldo deve ser superior ao valor da taxa para efetuar uma transferência.</string>
|
||||
<string name="koinos_insufficient_balance_to_send_koin_title">Saldo insuficiente</string>
|
||||
|
|
@ -752,6 +756,7 @@
|
|||
<string name="markets_search_result_title">Resultado</string>
|
||||
<string name="markets_search_see_tokens_under_100k">Veja tokens com capitalização de mercado inferior a 100 mil dólares.</string>
|
||||
<string name="markets_search_show_tokens">Mostrar tokens</string>
|
||||
<string name="markets_search_title_placeholder">Criptomoedas, notícias e muito mais</string>
|
||||
<string name="markets_search_token_no_result_title">Nenhum resultado</string>
|
||||
<string name="markets_select_network">Selecione a rede</string>
|
||||
<string name="markets_select_wallet">Selecione a carteira</string>
|
||||
|
|
@ -824,9 +829,11 @@
|
|||
<string name="markets_token_details_market_rating_description">Posição no ranking de criptomoedas entre todas as moedas com base na capitalização de mercado.</string>
|
||||
<string name="markets_token_details_market_rating_full">Posicionamento de mercado</string>
|
||||
<string name="markets_token_details_max_supply">Fornecimento máximo</string>
|
||||
<string name="markets_token_details_max_supply_and_circulation_full">Circulação e Fornecimento Máximo</string>
|
||||
<string name="markets_token_details_max_supply_description">O número máximo de moedas ou tokens que podem existir para uma determinada criptomoeda.</string>
|
||||
<string name="markets_token_details_max_supply_full">Fornecimento máximo</string>
|
||||
<string name="markets_token_details_metrics">Métricas</string>
|
||||
<string name="markets_token_details_metrics_no_limited">Sem limite</string>
|
||||
<string name="markets_token_details_official_links">Links oficiais</string>
|
||||
<string name="markets_token_details_price_performance">Desempenho de preço</string>
|
||||
<string name="markets_token_details_repository">Repositório</string>
|
||||
|
|
@ -1101,6 +1108,12 @@
|
|||
<string name="qr_scanner_camera_denied_settings_button">Configurações</string>
|
||||
<string name="qr_scanner_camera_denied_text">Você não concedeu acesso à sua câmera.</string>
|
||||
<string name="qr_scanner_camera_denied_title">Acesso à câmera negado</string>
|
||||
<string name="qr_scanner_error_token_not_added_message">O token solicitado não foi adicionado à sua carteira. Adicione-o e tente novamente.</string>
|
||||
<string name="qr_scanner_error_token_not_added_title">Token não adicionado</string>
|
||||
<string name="qr_scanner_error_unrecognized_message">Desculpe, este código QR não pôde ser reconhecido.</string>
|
||||
<string name="qr_scanner_error_unrecognized_title">Código QR não reconhecido</string>
|
||||
<string name="qr_scanner_error_unsupported_network_message">Esta rede não é compatível com nenhum dos tokens adicionados. Adicione um token compatível para enviar criptomoedas.</string>
|
||||
<string name="qr_scanner_error_unsupported_network_title">Nenhum token compatível encontrado</string>
|
||||
<string name="receive_bottom_sheet_no_memo_required_message">Não é necessário memorando</string>
|
||||
<string name="receive_bottom_sheet_warning_message">%1$s (%2$s) sobre %3$s rede</string>
|
||||
<string name="receive_bottom_sheet_warning_message_compact">%1$s sobre %2$s rede</string>
|
||||
|
|
@ -1246,6 +1259,11 @@
|
|||
<string name="send_notification_transaction_limit_title">Limitação de transação</string>
|
||||
<string name="send_optional_field">Opcional</string>
|
||||
<string name="send_qrcode_scan_info">Alinhe o seu código QR com o quadrado para escaneá-lo. Certifique-se de escanear corretamente. %s endereço de rede.</string>
|
||||
<string name="send_rate_fixed_info_description">Ao utilizar uma taxa fixa, o valor que você recebe fica garantido no momento da troca. Isso protege você de variações de preço durante a transação.</string>
|
||||
<string name="send_rate_fixed_info_title">Taxa fixa</string>
|
||||
<string name="send_rate_floating_info_description">Uma taxa flutuante significa que o valor final que você recebe pode variar ligeiramente com base nas condições de mercado entre o momento em que você inicia e conclui a operação de swap.</string>
|
||||
<string name="send_rate_floating_info_title">Taxa flutuante</string>
|
||||
<string name="send_rate_is_fixed">A taxa é fixa.</string>
|
||||
<string name="send_recent_transactions">Recente</string>
|
||||
<string name="send_recipient">Destinatário</string>
|
||||
<string name="send_recipient_address_error">Endereço inválido</string>
|
||||
|
|
@ -1289,6 +1307,7 @@
|
|||
<string name="send_with_swap_recipient_get_amount">O destinatário recebe %s</string>
|
||||
<string name="send_with_swap_remove_convert_alert_message">Tem certeza de que deseja cancelar a conversão? Seus dados anteriores serão apagados.</string>
|
||||
<string name="send_with_swap_remove_convert_alert_title">Remover conversão</string>
|
||||
<string name="send_with_swap_something_went_wrong">Algo deu errado. Tente novamente.</string>
|
||||
<string name="send_with_swap_title">Enviar com troca</string>
|
||||
<string name="sent_transaction_sent_title">Transação enviada</string>
|
||||
<string name="settings_card_settings_footer">Prepare-se para escanear o Cartão ou Anel que deseja configurar.</string>
|
||||
|
|
@ -1482,6 +1501,7 @@
|
|||
<string name="swapping_high_price_impact_description">Trocar essa quantidade de tokens selecionados causará um impacto significativo no preço e reduzirá seu resultado.</string>
|
||||
<string name="swapping_high_price_impact_title">Alto impacto nos preços</string>
|
||||
<string name="swapping_insufficient_funds">Fundos insuficientes</string>
|
||||
<string name="swapping_insufficient_funds_description">Não há fundos suficientes para concluir esta transação. Reduza o valor a receber ou adicione mais fundos.</string>
|
||||
<string name="swapping_permission_header">Conceder permissão</string>
|
||||
<string name="swapping_swap_action">Trocar</string>
|
||||
<string name="swapping_swap_action_in_progress">Trocar...</string>
|
||||
|
|
@ -1648,6 +1668,7 @@
|
|||
<string name="token_details_token_type_subtitle_no_standard">Token em %%imagem%% %1$s rede</string>
|
||||
<string name="token_details_unable_hide_alert_message">O %1$s (%2$sO token ) é a principal moeda da plataforma. %3$s rede e não pode ser ocultada enquanto você tiver outros tokens dessa rede na lista</string>
|
||||
<string name="token_details_unable_hide_alert_title">Não foi possível ocultar %s</string>
|
||||
<string name="token_market_metrics_no_data">N/A</string>
|
||||
<string name="token_receive_show_qr_code_title">Mostrar código QR</string>
|
||||
<string name="token_swap_changelly_promotion_message">Troque este token por outro em %1$s taxas de serviço a partir de fevereiro %2$s-%3$s.</string>
|
||||
<string name="token_swap_changelly_promotion_title">Trocar com Changelly, %s tarifas</string>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
<string name="common_unlock">解鎖</string>
|
||||
<string name="initial_wallet_sync_banner_description">您的錢包已同步並準備就緒。\n缺少某些代幣?</string>
|
||||
<string name="initial_wallet_sync_banner_title">錢包導入成功</string>
|
||||
<string name="initial_wallet_sync_restore_progress">恢復%d%</string>
|
||||
<string name="initial_wallet_sync_restore_progress">恢復%d%%</string>
|
||||
<string name="markets_token_details_insight_day_timeline">天</string>
|
||||
<string name="markets_token_details_insight_month_timeline">月</string>
|
||||
<string name="markets_token_details_insight_week_timeline">週</string>
|
||||
|
|
|
|||
|
|
@ -696,7 +696,7 @@
|
|||
<string name="initial_message_tap_header">Tap the card or ring</string>
|
||||
<string name="initial_wallet_sync_banner_description">Your wallet is synced and ready.\nSome tokens missing?</string>
|
||||
<string name="initial_wallet_sync_banner_title">Wallet successfully imported</string>
|
||||
<string name="initial_wallet_sync_restore_progress">Restoring %d%</string>
|
||||
<string name="initial_wallet_sync_restore_progress">Restoring %d%%</string>
|
||||
<string name="key_invalidated_warning_description">You have updated biometrics, scan your card or ring to enter</string>
|
||||
<string name="koinos_insufficient_balance_to_send_koin_description">Your balance should be higher than the fee value to make a transfer</string>
|
||||
<string name="koinos_insufficient_balance_to_send_koin_title">Not enough balance</string>
|
||||
|
|
@ -1500,6 +1500,7 @@
|
|||
<string name="swapping_fee_estimation_error_text">Fee estimation error. Please send feedback to support.</string>
|
||||
<string name="swapping_from_title">You swap</string>
|
||||
<string name="swapping_high_price_impact_description">Swapping this amount of selected tokens will cause a significant price impact and reduce your outcome.</string>
|
||||
<string name="swapping_high_price_impact_text">You may receive significantly less due to low liquidity. Try a smaller amount or another provider.</string>
|
||||
<string name="swapping_high_price_impact_title">High price impact</string>
|
||||
<string name="swapping_insufficient_funds">Insufficient funds</string>
|
||||
<string name="swapping_insufficient_funds_description">Not enough funds to complete this transaction. Reduce the amount to receive or add more funds.</string>
|
||||
|
|
@ -1509,6 +1510,8 @@
|
|||
<string name="swapping_to_title">You receive</string>
|
||||
<string name="swapping_token_list_title">Choose token</string>
|
||||
<string name="swapping_token_not_available">not available</string>
|
||||
<string name="swapping_trade_too_large_text">Not enough liquidity for this trade.\nReduce the amount or choose another provider.</string>
|
||||
<string name="swapping_trade_too_large_title">Trade too large</string>
|
||||
<string name="tangem_pay_beta_notification_subtitle">We would be happy to receive your feedback</string>
|
||||
<string name="tangem_pay_beta_notification_title">Tangem Pay is now in beta</string>
|
||||
<string name="tangem_pay_card_frozen">Card frozen</string>
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ object SwapTokenScreenTestTags {
|
|||
const val TOKEN_ICON = "SWAP_TOKEN_SCREEN_TOKEN_ICON"
|
||||
const val SELECT_TOKEN_ICON = "SWAP_TOKEN_SCREEN_SELECT_TOKEN_ICON"
|
||||
const val RECEIVE_FIAT_AMOUNT = "SWAP_TOKEN_SCREEN_RECEIVE_FIAT_AMOUNT"
|
||||
const val RECEIVE_FIAT_AMOUNT_WITH_PRICE_IMPACT_WARNING = "SWAP_TOKEN_SCREEN_RECEIVE_FIAT_AMOUNT_WITH_PRICE_IMPACT"
|
||||
const val RECEIVE_FIAT_AMOUNT_INFORMATION_ICON = "SWAP_TOKEN_SCREEN_PRICE_IMPACT_INFORMATION_ICON"
|
||||
const val SWAP_FIAT_AMOUNT = "SWAP_TOKEN_SCREEN_SWAP_FIAT_AMOUNT"
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@ import com.tangem.blockchain.yieldsupply.providers.ethereum.yield.EthereumYieldS
|
|||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
import com.tangem.blockchainsdk.utils.toBlockchain
|
||||
import com.tangem.blockchainsdk.utils.toNetworkId
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.domain.account.status.producer.SingleAccountStatusListProducer
|
||||
|
|
@ -67,11 +68,12 @@ import com.tangem.feature.swap.domain.models.toStringWithRightOffset
|
|||
import com.tangem.feature.swap.domain.models.ui.*
|
||||
import com.tangem.lib.crypto.BlockchainUtils.SOLANA_TRANSACTION_SIZE_THRESHOLD_BYTES
|
||||
import com.tangem.utils.coroutines.runSuspendCatching
|
||||
import com.tangem.utils.extensions.orZero
|
||||
import com.tangem.utils.logging.TangemLogger
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import com.tangem.utils.logging.TangemLogger
|
||||
import java.math.BigDecimal
|
||||
import java.math.BigInteger
|
||||
import java.math.RoundingMode
|
||||
|
|
@ -1289,12 +1291,14 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
private suspend fun createEmptyAmountState(): SwapState {
|
||||
val appCurrency = getSelectedAppCurrencyUseCase.unwrap()
|
||||
return SwapState.EmptyAmountState(
|
||||
zeroAmountEquivalent = BigDecimal.ZERO.format {
|
||||
fiat(
|
||||
fiatCurrencyCode = appCurrency.code,
|
||||
fiatCurrencySymbol = appCurrency.symbol,
|
||||
)
|
||||
},
|
||||
zeroAmountEquivalent = stringReference(
|
||||
BigDecimal.ZERO.format {
|
||||
fiat(
|
||||
fiatCurrencyCode = appCurrency.code,
|
||||
fiatCurrencySymbol = appCurrency.symbol,
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -1523,7 +1527,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
val rates = getQuotes(fromToken.currency.id)
|
||||
val fromTokenSwapInfo = TokenSwapInfo(
|
||||
tokenAmount = amount,
|
||||
amountFiat = rates[fromToken.currency.id]?.multiply(amount.value)
|
||||
amountFiat = rates[fromToken.currency.id]?.fiatRate?.multiply(amount.value)
|
||||
?: BigDecimal.ZERO,
|
||||
cryptoCurrencyStatus = fromToken,
|
||||
account = fromAccount,
|
||||
|
|
@ -1687,7 +1691,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
val rates = getQuotes(feeCurrencyId)
|
||||
return rates[feeCurrencyId]?.let { rate ->
|
||||
fees.map { fee ->
|
||||
rate.multiply(fee).format {
|
||||
rate.fiatRate.multiply(fee).format {
|
||||
fiat(
|
||||
fiatCurrencyCode = appCurrency.code,
|
||||
fiatCurrencySymbol = appCurrency.symbol,
|
||||
|
|
@ -1855,7 +1859,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
val rates = getQuotes(fromToken.currency.id)
|
||||
val fromTokenSwapInfo = TokenSwapInfo(
|
||||
tokenAmount = amount,
|
||||
amountFiat = rates[fromToken.currency.id]?.multiply(amount.value)
|
||||
amountFiat = rates[fromToken.currency.id]?.fiatRate?.multiply(amount.value)
|
||||
?: BigDecimal.ZERO,
|
||||
cryptoCurrencyStatus = fromToken,
|
||||
account = fromAccount,
|
||||
|
|
@ -1963,21 +1967,21 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
tokenAmount = fromTokenAmount,
|
||||
account = fromAccount,
|
||||
cryptoCurrencyStatus = fromTokenStatus,
|
||||
amountFiat = rates[fromToken.id]?.multiply(fromTokenAmount.value)
|
||||
amountFiat = rates[fromToken.id]?.fiatRate?.multiply(fromTokenAmount.value)
|
||||
?: BigDecimal.ZERO,
|
||||
),
|
||||
toTokenInfo = TokenSwapInfo(
|
||||
tokenAmount = toTokenAmount,
|
||||
cryptoCurrencyStatus = toTokenStatus,
|
||||
account = toAccount,
|
||||
amountFiat = rates[toToken.id]?.multiply(toTokenAmount.value)
|
||||
amountFiat = rates[toToken.id]?.fiatRate?.multiply(toTokenAmount.value)
|
||||
?: BigDecimal.ZERO,
|
||||
),
|
||||
priceImpact = calculatePriceImpact(
|
||||
fromTokenAmount = fromTokenAmount.value,
|
||||
fromRate = rates[fromToken.id]?.toDouble() ?: 0.0,
|
||||
fromQuoteStatus = rates[fromToken.id],
|
||||
toTokenAmount = toTokenAmount.value,
|
||||
toRate = rates[toToken.id]?.toDouble() ?: 0.0,
|
||||
toRate = rates[toToken.id]?.fiatRate,
|
||||
),
|
||||
swapDataModel = swapData,
|
||||
swapProvider = provider,
|
||||
|
|
@ -2446,17 +2450,42 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
|
||||
private fun calculatePriceImpact(
|
||||
fromTokenAmount: BigDecimal,
|
||||
fromRate: Double,
|
||||
fromQuoteStatus: QuoteStatus.Data?,
|
||||
toTokenAmount: BigDecimal,
|
||||
toRate: Double,
|
||||
toRate: BigDecimal?,
|
||||
): PriceImpact {
|
||||
val fromTokenFiatValue = fromTokenAmount.multiply(fromRate.toBigDecimal())
|
||||
val toTokenFiatValue = toTokenAmount.multiply(toRate.toBigDecimal())
|
||||
val value = (BigDecimal.ONE - toTokenFiatValue.divide(fromTokenFiatValue, 2, RoundingMode.HALF_UP)).toFloat()
|
||||
return PriceImpact.Value(value)
|
||||
if (fromQuoteStatus == null) return PriceImpact.Empty
|
||||
|
||||
val fromTokenFiatValue = fromTokenAmount.multiply(fromQuoteStatus.fiatRate)
|
||||
val toTokenFiatValue = toTokenAmount.multiply(toRate.orZero())
|
||||
val value = BigDecimal.ONE - toTokenFiatValue.divide(fromTokenFiatValue, 2, RoundingMode.HALF_UP)
|
||||
|
||||
val fromAmountUSD = if (fromQuoteStatus.fiatRateUSD != BigDecimal.ZERO) {
|
||||
fromTokenAmount.multiply(fromQuoteStatus.fiatRateUSD)
|
||||
} else {
|
||||
PRICE_IMPACT_AMOUNT_MIN_THRESHOLD
|
||||
}
|
||||
|
||||
val amountSignificance = when {
|
||||
fromAmountUSD < PRICE_IMPACT_AMOUNT_MIN_THRESHOLD -> PriceImpact.AmountSignificance.LOW
|
||||
fromAmountUSD > PRICE_IMPACT_AMOUNT_MAX_THRESHOLD -> PriceImpact.AmountSignificance.HIGH
|
||||
else -> PriceImpact.AmountSignificance.MEDIUM
|
||||
}
|
||||
|
||||
val type = when {
|
||||
value < PRICE_IMPACT_LOW_THRESHOLD -> PriceImpact.Type.LOW
|
||||
value in PRICE_IMPACT_LOW_THRESHOLD..PRICE_IMPACT_HIGH_THRESHOLD -> PriceImpact.Type.MEDIUM
|
||||
else -> PriceImpact.Type.HIGH
|
||||
}
|
||||
|
||||
return PriceImpact(
|
||||
value = value,
|
||||
amountSignificance = amountSignificance,
|
||||
type = type,
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun getQuotes(vararg ids: CryptoCurrency.ID): Map<CryptoCurrency.ID, BigDecimal> {
|
||||
private suspend fun getQuotes(vararg ids: CryptoCurrency.ID): Map<CryptoCurrency.ID, QuoteStatus.Data> {
|
||||
val set = ids.mapNotNullTo(destination = hashSetOf(), transform = CryptoCurrency.ID::rawCurrencyId)
|
||||
.getQuotesOrEmpty()
|
||||
|
||||
|
|
@ -2465,7 +2494,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
val found = set.find { it.rawCurrencyId == id.rawCurrencyId && it.value is QuoteStatus.Data }
|
||||
?: return@mapNotNull null
|
||||
|
||||
id to (found.value as QuoteStatus.Data).fiatRate
|
||||
id to found.value as QuoteStatus.Data
|
||||
}
|
||||
.toMap()
|
||||
}
|
||||
|
|
@ -2523,6 +2552,10 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
companion object {
|
||||
private const val INCREASE_GAS_LIMIT_FOR_DEX = 112 // 12%
|
||||
private const val INCREASE_GAS_LIMIT_FOR_SEND = 105 // 5%
|
||||
private val PRICE_IMPACT_AMOUNT_MIN_THRESHOLD = 50.toBigDecimal() // in USD
|
||||
private val PRICE_IMPACT_AMOUNT_MAX_THRESHOLD = 5000.toBigDecimal() // in USD
|
||||
private val PRICE_IMPACT_LOW_THRESHOLD = 0.1.toBigDecimal() // 10%
|
||||
private val PRICE_IMPACT_HIGH_THRESHOLD = 0.5.toBigDecimal() // 50%
|
||||
private const val INFINITY_SYMBOL = "∞"
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
package com.tangem.feature.swap.domain.models.ui
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyCheck
|
||||
|
|
@ -36,7 +38,7 @@ sealed interface SwapState {
|
|||
val swapProvider: SwapProvider,
|
||||
) : SwapState
|
||||
|
||||
data class EmptyAmountState(val zeroAmountEquivalent: String) : SwapState
|
||||
data class EmptyAmountState(val zeroAmountEquivalent: TextReference) : SwapState
|
||||
|
||||
data class SwapError(
|
||||
val fromTokenInfo: TokenSwapInfo,
|
||||
|
|
@ -45,18 +47,31 @@ sealed interface SwapState {
|
|||
) : SwapState
|
||||
}
|
||||
|
||||
sealed class PriceImpact {
|
||||
@Immutable
|
||||
data class PriceImpact(
|
||||
val value: BigDecimal,
|
||||
val amountSignificance: AmountSignificance,
|
||||
val type: Type,
|
||||
) {
|
||||
|
||||
abstract val value: Float
|
||||
enum class Type {
|
||||
NONE, LOW, MEDIUM, HIGH
|
||||
}
|
||||
|
||||
data class Empty(override val value: Float = 0f) : PriceImpact()
|
||||
enum class AmountSignificance {
|
||||
LOW, MEDIUM, HIGH
|
||||
}
|
||||
|
||||
data class Value(override val value: Float) : PriceImpact()
|
||||
|
||||
fun getIntPercentValue() = (value * HUNDRED_PERCENTS).toInt()
|
||||
fun shouldDisableButton(): Boolean {
|
||||
return type == Type.HIGH && amountSignificance == AmountSignificance.HIGH
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val HUNDRED_PERCENTS = 100
|
||||
val Empty = PriceImpact(
|
||||
value = BigDecimal.ZERO,
|
||||
amountSignificance = AmountSignificance.LOW,
|
||||
type = Type.NONE,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1700,7 +1700,7 @@ internal class SwapModel @Inject constructor(
|
|||
onReceiveCardWarningClick = {
|
||||
val selectedProvider = dataState.selectedProvider ?: return@UiActions
|
||||
val currencySymbol = dataState.toCryptoCurrency?.currency?.symbol ?: return@UiActions
|
||||
val isPriceImpact = uiState.priceImpact is PriceImpact.Value
|
||||
val isPriceImpact = uiState.priceImpact.type != PriceImpact.Type.NONE
|
||||
showSwapInfoAlert(isPriceImpact, currencySymbol, selectedProvider)
|
||||
},
|
||||
onLinkClick = urlOpener::openUrl,
|
||||
|
|
|
|||
|
|
@ -118,6 +118,7 @@ internal class SwapNotificationsFactory(
|
|||
maybeAddNetworkFeeCoverageWarning(quoteModel, selectedFeeType)
|
||||
maybeAddUnableCoverFeeWarning(quoteModel, fromToken, hideFee)
|
||||
maybeAddTransactionInProgressWarning(quoteModel)
|
||||
maybeAddPriceImpactNotification(quoteModel.priceImpact)
|
||||
}
|
||||
return warnings.toPersistentList()
|
||||
}
|
||||
|
|
@ -143,6 +144,18 @@ internal class SwapNotificationsFactory(
|
|||
}
|
||||
}
|
||||
|
||||
private fun MutableList<NotificationUM>.maybeAddPriceImpactNotification(priceImpact: PriceImpact) {
|
||||
if (priceImpact.amountSignificance == PriceImpact.AmountSignificance.LOW) return
|
||||
|
||||
val notification = when (priceImpact.type) {
|
||||
PriceImpact.Type.HIGH -> SwapNotificationUM.Warning.TradeTooHigh
|
||||
PriceImpact.Type.MEDIUM -> SwapNotificationUM.Warning.HighPriceImpact
|
||||
else -> return
|
||||
}
|
||||
|
||||
add(notification)
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
private fun MutableList<NotificationUM>.maybeAddDomainWarnings(
|
||||
quoteModel: SwapState.QuotesLoadedState,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.feature.swap.models
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import com.tangem.common.ui.account.AccountTitleUM
|
||||
import com.tangem.common.ui.bottomsheet.permission.state.GiveTxPermissionState
|
||||
|
|
@ -47,7 +48,7 @@ sealed class SwapCardState {
|
|||
data class SwapCardData(
|
||||
@DrawableRes val networkIconRes: Int?,
|
||||
val type: TransactionCardType,
|
||||
val amountEquivalent: String?,
|
||||
val amountEquivalent: TextReference?,
|
||||
val token: CryptoCurrencyStatus?,
|
||||
val coinId: String?,
|
||||
val amountTextFieldValue: TextFieldValue?,
|
||||
|
|
@ -61,7 +62,7 @@ sealed class SwapCardState {
|
|||
|
||||
data class Empty(
|
||||
val type: TransactionCardType,
|
||||
val amountEquivalent: String?,
|
||||
val amountEquivalent: TextReference?,
|
||||
val amountTextFieldValue: TextFieldValue?,
|
||||
val canSelectAnotherToken: Boolean = false,
|
||||
) : SwapCardState()
|
||||
|
|
@ -75,6 +76,7 @@ data class SwapButton(
|
|||
val onClick: () -> Unit,
|
||||
)
|
||||
|
||||
@Immutable
|
||||
sealed interface TransactionCardType {
|
||||
|
||||
val accountTitleUM: AccountTitleUM?
|
||||
|
|
|
|||
|
|
@ -194,6 +194,17 @@ internal object SwapNotificationUM {
|
|||
onClick = onConfirmClick,
|
||||
),
|
||||
)
|
||||
|
||||
data object HighPriceImpact : Warning(
|
||||
title = resourceReference(R.string.swapping_high_price_impact_title),
|
||||
subtitle = resourceReference(R.string.swapping_high_price_impact_text),
|
||||
)
|
||||
|
||||
data object TradeTooHigh : Warning(
|
||||
title = resourceReference(R.string.swapping_trade_too_large_title),
|
||||
subtitle = resourceReference(R.string.swapping_trade_too_large_text),
|
||||
iconResId = R.drawable.ic_alert_circle_24,
|
||||
)
|
||||
}
|
||||
|
||||
sealed class Info(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.feature.swap.ui
|
||||
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.TextRange
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import com.tangem.common.ui.account.AccountTitleUM
|
||||
|
|
@ -12,10 +13,8 @@ import com.tangem.core.ui.HoldToConfirmButtonFeatureToggles
|
|||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
|
||||
import com.tangem.core.ui.extensions.*
|
||||
import com.tangem.core.ui.format.bigdecimal.anyDecimals
|
||||
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.core.ui.format.bigdecimal.*
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
|
|
@ -37,6 +36,7 @@ import com.tangem.feature.swap.models.states.*
|
|||
import com.tangem.feature.swap.presentation.R
|
||||
import com.tangem.feature.swap.utils.formatToUIRepresentation
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.StringsSigns
|
||||
import com.tangem.utils.StringsSigns.DASH_SIGN
|
||||
import com.tangem.utils.StringsSigns.TILDE_SIGN
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
|
@ -127,7 +127,7 @@ internal class StateBuilder(
|
|||
onSuccess = actions.onSuccess,
|
||||
providerState = ProviderState.Empty(),
|
||||
shouldShowMaxAmount = false,
|
||||
priceImpact = PriceImpact.Empty(),
|
||||
priceImpact = PriceImpact.Empty,
|
||||
isInsufficientFunds = false,
|
||||
)
|
||||
}
|
||||
|
|
@ -141,7 +141,7 @@ internal class StateBuilder(
|
|||
sendCardData = SwapCardState.SwapCardData(
|
||||
type = requireNotNull(uiStateHolder.sendCardData.type as? TransactionCardType.Inputtable),
|
||||
amountTextFieldValue = null,
|
||||
amountEquivalent = "0 ${appCurrencyProvider.invoke().symbol}",
|
||||
amountEquivalent = getFormattedFiatAmount(BigDecimal.ZERO),
|
||||
token = fromToken,
|
||||
tokenIconUrl = fromToken.currency.iconUrl,
|
||||
coinId = fromToken.currency.network.backendId,
|
||||
|
|
@ -154,7 +154,7 @@ internal class StateBuilder(
|
|||
),
|
||||
receiveCardData = SwapCardState.Empty(
|
||||
type = TransactionCardType.ReadOnly(),
|
||||
amountEquivalent = "0 ${appCurrencyProvider.invoke().symbol}",
|
||||
amountEquivalent = getFormattedFiatAmount(BigDecimal.ZERO),
|
||||
amountTextFieldValue = TextFieldValue(
|
||||
text = "0",
|
||||
),
|
||||
|
|
@ -169,7 +169,7 @@ internal class StateBuilder(
|
|||
onClick = { },
|
||||
),
|
||||
changeCardsButtonState = ChangeCardsButtonState.DISABLED,
|
||||
priceImpact = PriceImpact.Empty(),
|
||||
priceImpact = PriceImpact.Empty,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -187,7 +187,7 @@ internal class StateBuilder(
|
|||
accountTitleUM = getFromCardAccountTitle(fromAccount),
|
||||
),
|
||||
amountTextFieldValue = null,
|
||||
amountEquivalent = "0 ${appCurrencyProvider.invoke().symbol}",
|
||||
amountEquivalent = getFormattedFiatAmount(BigDecimal.ZERO),
|
||||
token = fromToken,
|
||||
tokenIconUrl = fromToken.currency.iconUrl,
|
||||
coinId = fromToken.currency.network.backendId,
|
||||
|
|
@ -205,7 +205,7 @@ internal class StateBuilder(
|
|||
amountTextFieldValue = TextFieldValue(
|
||||
text = "0",
|
||||
),
|
||||
amountEquivalent = "0 ${appCurrencyProvider.invoke().symbol}",
|
||||
amountEquivalent = getFormattedFiatAmount(BigDecimal.ZERO),
|
||||
token = toToken,
|
||||
tokenIconUrl = toToken.currency.iconUrl,
|
||||
coinId = toToken.currency.network.backendId,
|
||||
|
|
@ -226,7 +226,7 @@ internal class StateBuilder(
|
|||
),
|
||||
changeCardsButtonState = ChangeCardsButtonState.DISABLED,
|
||||
providerState = ProviderState.Empty(),
|
||||
priceImpact = PriceImpact.Empty(),
|
||||
priceImpact = PriceImpact.Empty,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -294,7 +294,7 @@ internal class StateBuilder(
|
|||
providerState = ProviderState.Loading(),
|
||||
permissionState = uiStateHolder.permissionState,
|
||||
changeCardsButtonState = ChangeCardsButtonState.UPDATE_IN_PROGRESS,
|
||||
priceImpact = PriceImpact.Empty(),
|
||||
priceImpact = PriceImpact.Empty,
|
||||
shouldShowMaxAmount = shouldShowMaxAmount(fromToken, toToken),
|
||||
)
|
||||
}
|
||||
|
|
@ -360,6 +360,7 @@ internal class StateBuilder(
|
|||
)
|
||||
}
|
||||
}
|
||||
val priceImpact = quoteModel.priceImpact
|
||||
return uiStateHolder.copy(
|
||||
sendCardData = SwapCardState.SwapCardData(
|
||||
type = sendInput,
|
||||
|
|
@ -386,7 +387,26 @@ internal class StateBuilder(
|
|||
.formatToUIRepresentation()
|
||||
.appendApproximateSign(),
|
||||
),
|
||||
amountEquivalent = getFormattedFiatAmount(quoteModel.toTokenInfo.amountFiat),
|
||||
amountEquivalent = combinedReference(
|
||||
getFormattedFiatAmount(quoteModel.toTokenInfo.amountFiat),
|
||||
stringReference(StringsSigns.WHITE_SPACE),
|
||||
styledStringReference(
|
||||
priceImpact.value.format {
|
||||
percent(withoutSign = false)
|
||||
},
|
||||
spanStyleReference = {
|
||||
SpanStyle(
|
||||
color = when (priceImpact.type) {
|
||||
PriceImpact.Type.HIGH -> TangemTheme.colors.text.warning
|
||||
PriceImpact.Type.MEDIUM -> TangemTheme.colors.text.attention
|
||||
PriceImpact.Type.LOW,
|
||||
PriceImpact.Type.NONE,
|
||||
-> TangemTheme.colors.text.tertiary
|
||||
},
|
||||
)
|
||||
},
|
||||
),
|
||||
),
|
||||
token = toCurrencyStatus,
|
||||
tokenIconUrl = uiStateHolder.receiveCardData.tokenIconUrl,
|
||||
coinId = toCurrencyStatus.currency.network.backendId,
|
||||
|
|
@ -410,7 +430,7 @@ internal class StateBuilder(
|
|||
fee = feeState,
|
||||
swapButton = SwapButton(
|
||||
walletInteractionIcon = walletInterationIcon(userWalletProvider()),
|
||||
isEnabled = getSwapButtonEnabled(notifications),
|
||||
isEnabled = getSwapButtonEnabled(notifications, priceImpact),
|
||||
isHoldToConfirm = isHoldToConfirmEnabled,
|
||||
onClick = actions.onSwapClick,
|
||||
),
|
||||
|
|
@ -424,11 +444,7 @@ internal class StateBuilder(
|
|||
onProviderClick = actions.onProviderClick,
|
||||
needApplyFCARestrictions = needApplyFCARestrictions,
|
||||
),
|
||||
priceImpact = if (quoteModel.priceImpact.value > PRICE_IMPACT_THRESHOLD) {
|
||||
quoteModel.priceImpact
|
||||
} else {
|
||||
PriceImpact.Empty()
|
||||
},
|
||||
priceImpact = quoteModel.priceImpact,
|
||||
tosState = createTosState(swapProvider),
|
||||
shouldShowMaxAmount = shouldShowMaxAmount(fromToken, toCurrencyStatus.currency),
|
||||
)
|
||||
|
|
@ -462,7 +478,7 @@ internal class StateBuilder(
|
|||
quoteModel.preparedSwapConfigState.includeFeeInAmount !is IncludeFeeInAmount.Included
|
||||
}
|
||||
|
||||
private fun getSwapButtonEnabled(notifications: ImmutableList<NotificationUM>): Boolean {
|
||||
private fun getSwapButtonEnabled(notifications: ImmutableList<NotificationUM>, priceImpact: PriceImpact): Boolean {
|
||||
return notifications.none { notification ->
|
||||
notification is SwapNotificationUM.Error || notification is NotificationUM.Error ||
|
||||
notification is SwapNotificationUM.Warning.ExpressError ||
|
||||
|
|
@ -471,7 +487,7 @@ internal class StateBuilder(
|
|||
notification is SwapNotificationUM.Warning.SwapNotSupported ||
|
||||
notification is SwapNotificationUM.Warning.NeedReserveToCreateAccount ||
|
||||
notification is SwapNotificationUM.Info.PermissionNeeded
|
||||
}
|
||||
} && !priceImpact.shouldDisableButton()
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
|
|
@ -510,7 +526,7 @@ internal class StateBuilder(
|
|||
amountTextFieldValue = TextFieldValue(
|
||||
text = "0",
|
||||
),
|
||||
amountEquivalent = "0 ${appCurrencyProvider.invoke().symbol}",
|
||||
amountEquivalent = getFormattedFiatAmount(BigDecimal.ZERO),
|
||||
token = toToken,
|
||||
tokenIconUrl = uiStateHolder.receiveCardData.tokenIconUrl,
|
||||
coinId = toToken.currency.network.backendId,
|
||||
|
|
@ -523,7 +539,7 @@ internal class StateBuilder(
|
|||
)
|
||||
} ?: SwapCardState.Empty(
|
||||
type = type,
|
||||
amountEquivalent = "0 ${appCurrencyProvider.invoke().symbol}",
|
||||
amountEquivalent = getFormattedFiatAmount(BigDecimal.ZERO),
|
||||
amountTextFieldValue = TextFieldValue(
|
||||
text = "0",
|
||||
),
|
||||
|
|
@ -546,7 +562,7 @@ internal class StateBuilder(
|
|||
),
|
||||
changeCardsButtonState = getChangeCardsButtonState(isReverseSwapPossible),
|
||||
providerState = providerState,
|
||||
priceImpact = PriceImpact.Empty(),
|
||||
priceImpact = PriceImpact.Empty,
|
||||
tosState = createTosState(swapProvider),
|
||||
)
|
||||
}
|
||||
|
|
@ -644,7 +660,7 @@ internal class StateBuilder(
|
|||
),
|
||||
changeCardsButtonState = getChangeCardsButtonState(isReverseSwapPossible),
|
||||
providerState = ProviderState.Empty(),
|
||||
priceImpact = PriceImpact.Empty(),
|
||||
priceImpact = PriceImpact.Empty,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -852,8 +868,8 @@ internal class StateBuilder(
|
|||
toTitle = getToCardAccountTitle(toAccount = dataState.toAccount),
|
||||
fromTokenAmount = stringReference(swapTransactionState.fromAmount.orEmpty()),
|
||||
toTokenAmount = stringReference(swapTransactionState.toAmount.orEmpty()),
|
||||
fromTokenFiatAmount = stringReference(fromFiatAmount),
|
||||
toTokenFiatAmount = stringReference(toFiatAmount),
|
||||
fromTokenFiatAmount = fromFiatAmount,
|
||||
toTokenFiatAmount = toFiatAmount,
|
||||
fromTokenIconState = iconStateConverter.convert(fromCryptoCurrency),
|
||||
toTokenIconState = iconStateConverter.convert(toCryptoCurrency),
|
||||
onExploreButtonClick = onExploreClick,
|
||||
|
|
@ -892,8 +908,8 @@ internal class StateBuilder(
|
|||
toTitle = getToCardAccountTitle(toAccount = dataState.toAccount),
|
||||
fromTokenAmount = stringReference(swapTransactionState.fromAmount.orEmpty()),
|
||||
toTokenAmount = stringReference(swapTransactionState.toAmount.orEmpty()),
|
||||
fromTokenFiatAmount = stringReference(fromFiatAmount),
|
||||
toTokenFiatAmount = stringReference(toFiatAmount),
|
||||
fromTokenFiatAmount = fromFiatAmount,
|
||||
toTokenFiatAmount = toFiatAmount,
|
||||
fromTokenIconState = iconStateConverter.convert(fromCryptoCurrency),
|
||||
toTokenIconState = iconStateConverter.convert(toCryptoCurrency),
|
||||
onExploreButtonClick = onExploreClick,
|
||||
|
|
@ -1267,15 +1283,17 @@ internal class StateBuilder(
|
|||
return amount.format { crypto(symbol, currency.decimals) }
|
||||
}
|
||||
|
||||
private fun getFormattedFiatAmount(amount: BigDecimal?): String {
|
||||
private fun getFormattedFiatAmount(amount: BigDecimal?): TextReference {
|
||||
val appCurrency = appCurrencyProvider()
|
||||
|
||||
return amount.format {
|
||||
fiat(
|
||||
fiatCurrencyCode = appCurrency.code,
|
||||
fiatCurrencySymbol = appCurrency.symbol,
|
||||
)
|
||||
}
|
||||
return stringReference(
|
||||
amount.format {
|
||||
fiat(
|
||||
fiatCurrencyCode = appCurrency.code,
|
||||
fiatCurrencySymbol = appCurrency.symbol,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun SwapAmount.getFormattedCryptoAmount(token: CryptoCurrency): String {
|
||||
|
|
@ -1339,7 +1357,6 @@ internal class StateBuilder(
|
|||
const val ADDRESS_MIN_LENGTH = 11
|
||||
const val ADDRESS_FIRST_PART_LENGTH = 7
|
||||
const val ADDRESS_SECOND_PART_LENGTH = 4
|
||||
private const val PRICE_IMPACT_THRESHOLD = 0.1
|
||||
private const val MAX_DECIMALS_TO_SHOW = 8
|
||||
private const val IF_ZERO_DECIMALS_TO_SHOW = 2
|
||||
private const val FEE_READ_MORE_URL_FIRST_PART = "https://tangem.com/"
|
||||
|
|
|
|||
|
|
@ -346,6 +346,7 @@ private fun SwapNotifications(notifications: List<NotificationUM>) {
|
|||
Notification(
|
||||
config = notification.config,
|
||||
iconTint = when (notification) {
|
||||
is SwapNotificationUM.Warning.TradeTooHigh -> TangemTheme.colors.icon.warning
|
||||
is SwapNotificationUM.Error.UnableToCoverFeeWarning,
|
||||
is NotificationUM.Error.TokenExceedsBalance,
|
||||
is NotificationUM.Error.ExceedsBalance,
|
||||
|
|
@ -412,7 +413,7 @@ private val sendCard = SwapCardState.SwapCardData(
|
|||
accountTitleUM = null,
|
||||
),
|
||||
amountTextFieldValue = TextFieldValue(),
|
||||
amountEquivalent = "1 000 000",
|
||||
amountEquivalent = stringReference("1 000 000"),
|
||||
tokenIconUrl = "",
|
||||
tokenCurrency = "DAI",
|
||||
isNotNativeToken = true,
|
||||
|
|
@ -427,7 +428,7 @@ private val sendCard = SwapCardState.SwapCardData(
|
|||
private val receiveCard = SwapCardState.SwapCardData(
|
||||
type = TransactionCardType.ReadOnly(),
|
||||
amountTextFieldValue = TextFieldValue(),
|
||||
amountEquivalent = "1 000 000",
|
||||
amountEquivalent = stringReference("1 000 000"),
|
||||
tokenIconUrl = "",
|
||||
tokenCurrency = "DAI",
|
||||
isNotNativeToken = true,
|
||||
|
|
@ -466,7 +467,7 @@ private val state = SwapStateHolder(
|
|||
permissionState = GiveTxPermissionState.InProgress,
|
||||
blockchainId = "POLYGON",
|
||||
providerState = ProviderState.Loading(),
|
||||
priceImpact = PriceImpact.Empty(),
|
||||
priceImpact = PriceImpact.Empty,
|
||||
shouldShowMaxAmount = true,
|
||||
isInsufficientFunds = false,
|
||||
onSuccess = {},
|
||||
|
|
|
|||
|
|
@ -26,9 +26,7 @@ import androidx.compose.ui.platform.LocalContext
|
|||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
|
|
@ -41,8 +39,7 @@ import com.tangem.common.ui.account.AccountTitleUM
|
|||
import com.tangem.common.ui.account.CryptoPortfolioIconConverter
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.*
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.extensions.*
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.test.SwapTokenScreenTestTags
|
||||
|
|
@ -59,7 +56,7 @@ fun TransactionCard(
|
|||
balance: String,
|
||||
tokenIconUrl: String,
|
||||
tokenCurrency: String,
|
||||
amountEquivalent: String?,
|
||||
amountEquivalent: TextReference?,
|
||||
priceImpact: PriceImpact,
|
||||
textFieldValue: TextFieldValue?,
|
||||
modifier: Modifier = Modifier,
|
||||
|
|
@ -129,7 +126,7 @@ fun TransactionCard(
|
|||
@Composable
|
||||
fun TransactionCardEmpty(
|
||||
type: TransactionCardType,
|
||||
amountEquivalent: String?,
|
||||
amountEquivalent: TextReference?,
|
||||
textFieldValue: TextFieldValue?,
|
||||
modifier: Modifier = Modifier,
|
||||
onChangeTokenClick: (() -> Unit)? = null,
|
||||
|
|
@ -157,7 +154,7 @@ fun TransactionCardEmpty(
|
|||
type = type,
|
||||
amountEquivalent = amountEquivalent,
|
||||
textFieldValue = textFieldValue,
|
||||
priceImpact = PriceImpact.Empty(),
|
||||
priceImpact = PriceImpact.Empty,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -250,7 +247,7 @@ private fun Header(type: TransactionCardType, balance: String, modifier: Modifie
|
|||
@Composable
|
||||
private fun Content(
|
||||
type: TransactionCardType,
|
||||
amountEquivalent: String?,
|
||||
amountEquivalent: TextReference?,
|
||||
priceImpact: PriceImpact,
|
||||
textFieldValue: TextFieldValue?,
|
||||
) {
|
||||
|
|
@ -320,27 +317,13 @@ private fun Content(
|
|||
modifier = Modifier.defaultMinSize(minHeight = TangemTheme.dimens.size20),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
if (priceImpact is PriceImpact.Value) {
|
||||
AnimatedContent(targetState = amountEquivalent, label = "") { amount ->
|
||||
Text(
|
||||
text = makePriceImpactBalanceWarning(
|
||||
amountEquivalent,
|
||||
priceImpact.getIntPercentValue(),
|
||||
),
|
||||
text = amount.resolveAnnotatedReference(),
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
style = TangemTheme.typography.body2,
|
||||
modifier = Modifier.testTag(
|
||||
SwapTokenScreenTestTags.RECEIVE_FIAT_AMOUNT_WITH_PRICE_IMPACT_WARNING,
|
||||
),
|
||||
modifier = Modifier.testTag(SwapTokenScreenTestTags.RECEIVE_FIAT_AMOUNT),
|
||||
)
|
||||
} else {
|
||||
AnimatedContent(targetState = amountEquivalent, label = "") { amount ->
|
||||
Text(
|
||||
text = amount,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
style = TangemTheme.typography.body2,
|
||||
modifier = Modifier.testTag(SwapTokenScreenTestTags.RECEIVE_FIAT_AMOUNT),
|
||||
)
|
||||
}
|
||||
}
|
||||
if (type.shouldShowWarning) {
|
||||
SpacerW4()
|
||||
|
|
@ -353,10 +336,10 @@ private fun Content(
|
|||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_information_24),
|
||||
contentDescription = null,
|
||||
tint = if (priceImpact is PriceImpact.Value) {
|
||||
TangemTheme.colors.text.attention
|
||||
} else {
|
||||
TangemTheme.colors.text.tertiary
|
||||
tint = when (priceImpact.type) {
|
||||
PriceImpact.Type.HIGH -> TangemTheme.colors.text.warning
|
||||
PriceImpact.Type.MEDIUM -> TangemTheme.colors.text.attention
|
||||
else -> TangemTheme.colors.text.tertiary
|
||||
},
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterVertically)
|
||||
|
|
@ -368,7 +351,7 @@ private fun Content(
|
|||
} else {
|
||||
AnimatedContent(targetState = amountEquivalent, label = "") { amount ->
|
||||
Text(
|
||||
text = amount,
|
||||
text = amount.resolveAnnotatedReference(),
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
style = TangemTheme.typography.body2,
|
||||
modifier = Modifier
|
||||
|
|
@ -530,19 +513,6 @@ fun ChangeTokenSelector() {
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun makePriceImpactBalanceWarning(value: String, priceImpactPercents: Int): AnnotatedString {
|
||||
val fullValue = "$value (-$priceImpactPercents%)"
|
||||
return buildAnnotatedString {
|
||||
append(fullValue)
|
||||
addStyle(
|
||||
style = SpanStyle(color = TangemTheme.colors.text.attention),
|
||||
start = value.length,
|
||||
end = fullValue.length,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// region preview
|
||||
|
||||
@Preview(widthDp = 328, heightDp = 116, showBackground = true)
|
||||
|
|
@ -602,14 +572,14 @@ private fun TransactionCardPreview() {
|
|||
inputError = TransactionCardType.InputError.Empty,
|
||||
accountTitleUM = null,
|
||||
),
|
||||
amountEquivalent = "1 000 000",
|
||||
amountEquivalent = stringReference("1 000 000"),
|
||||
tokenIconUrl = "",
|
||||
tokenCurrency = "DAI",
|
||||
networkIconRes = R.drawable.img_polygon_22,
|
||||
onChangeTokenClick = {},
|
||||
balance = "123",
|
||||
textFieldValue = TextFieldValue(),
|
||||
priceImpact = PriceImpact.Empty(),
|
||||
priceImpact = PriceImpact.Empty,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -625,14 +595,24 @@ private fun TransactionCardPreviewWithPriceImpact() {
|
|||
icon = CryptoPortfolioIconConverter.convert(CryptoPortfolioIcon.ofDefaultCustomAccount()),
|
||||
),
|
||||
),
|
||||
amountEquivalent = "1 000 000",
|
||||
amountEquivalent = combinedReference(
|
||||
stringReference("1 000 000 $"),
|
||||
styledStringReference(
|
||||
" (-15%)",
|
||||
{ SpanStyle(color = TangemTheme.colors.text.attention) },
|
||||
),
|
||||
),
|
||||
tokenIconUrl = "",
|
||||
tokenCurrency = "DAI",
|
||||
networkIconRes = R.drawable.img_polygon_22,
|
||||
onChangeTokenClick = {},
|
||||
balance = "123",
|
||||
textFieldValue = TextFieldValue("1000000.0000000000000000000000000"),
|
||||
priceImpact = PriceImpact.Value(0.15F),
|
||||
priceImpact = PriceImpact(
|
||||
value = 0.15F.toBigDecimal(),
|
||||
type = PriceImpact.Type.MEDIUM,
|
||||
amountSignificance = PriceImpact.AmountSignificance.HIGH,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -647,14 +627,14 @@ private fun TransactionCardPreviewWithoutPriceImpact() {
|
|||
icon = CryptoPortfolioIconConverter.convert(CryptoPortfolioIcon.ofDefaultCustomAccount()),
|
||||
),
|
||||
),
|
||||
amountEquivalent = "1 000 000",
|
||||
amountEquivalent = stringReference("1 000 000"),
|
||||
tokenIconUrl = "",
|
||||
tokenCurrency = "DAI",
|
||||
networkIconRes = R.drawable.img_polygon_22,
|
||||
onChangeTokenClick = {},
|
||||
balance = "123",
|
||||
textFieldValue = TextFieldValue(),
|
||||
priceImpact = PriceImpact.Empty(),
|
||||
priceImpact = PriceImpact.Empty,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue