diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml index fb1cde4a04..99020ba5c0 100644 --- a/core/res/src/main/res/values-fr/strings.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -50,7 +50,6 @@ Thème Paramètres de l\'application Ces mots ne peuvent pas être récupérés en cas de perte. Assurez-vous de les conserver en lieu sûr. - Ne partagez jamais ces mots. Quiconque les apprend peut voler toutes vos cryptomonnaies. Tangem ne vous les demandera jamais. Les %s mots ci-dessous constituent la phrase de récupération de votre portefeuille. Cette phrase vous permet de récupérer votre portefeuille en cas de perte de votre appareil. Pour masquer ou afficher vos soldes, il suffit de retourner l\'écran de votre appareil vers le bas ou de le désactiver dans les paramètres Ne plus afficher Compris @@ -118,6 +117,7 @@ Fermer Confirmez Continuer + Convertir Copier Copier l\'adresse Créer @@ -702,6 +702,11 @@ Par solde Organiser les jetons Dégrouper + Plus d\'infos + Vous pouvez activer les notifications pour Tangem dans les paramètres. + Activer plus tard + Paramètres + Activer les notifications Recevez des alertes pour les transactions entrantes sur les réseaux pris en charge. Sélectionnez dans la galerie Paramètres @@ -775,7 +780,9 @@ Vous avez spécifié une commission inférieure au montant recommandé, ce qui pourrait entraîner un retard dans votre transaction. Continuer? Raison : %1$s\nCode : %2$s La transaction est incomplète + Convertir en un autre jeton Montant + Sera envoyé au destinataire Vous pouvez définir vos frais de transaction en ajustant la valeur dans le champ Satoshi par vByte. Les frais qui seront facturés pour votre transaction. Vous pouvez définir votre propre valeur. Frais maximum @@ -799,6 +806,7 @@ Vérifiez votre connexion réseau Informations sur les frais de réseau inaccessibles De + De %s Limite de gaz Il s\'agit du montant maximal de gaz qui sera dépensé pour effectuer une transaction ou un contrat. Une limite de gaz empêche des frais imprévus ou illimités lors de l\'exécution d\'une transaction. Prix ​​du gaz @@ -806,6 +814,7 @@ Max Somme maximale Frais jusqu\'à + Mémo : %s Mémo invalide Couverture des frais de réseau Fonds insuffisants pour le transfert, car le total des frais et du montant du transfert dépasse le solde existant @@ -816,6 +825,7 @@ Les frais de douane sont élevés En raison des particularités du réseau %1$s, les frais de transfert de la totalité du solde sont plus élevés. Pour réduire la commission, vous pouvez en laisser %2$s. Les frais sont plus élevés + Le compte du bénéficiaire n\'est pas activé. Le montant minimum du transfert doit être égal ou supérieur au solde exonéré de loyer : %1$s. Le solde de votre compte ne peut pas être inférieur au montant du loyer. Veuillez conserver au moins %1$s sur votre compte ou retirer tous les fonds. La commission incluse dépasse le montant du transfert, ce qui entraîne une valeur négative Montant invalide @@ -835,6 +845,7 @@ Destinataire Adresse non valide Assurez-vous que l\'adresse du portefeuille de réception est sur le réseau %s pour éviter de perdre vos jetons + envoyer à %s Envoyer à Un Memo/Destination Tag est un identifiant unique permettant de différencier les transactions envoyées au même destinataire sur le même réseau. Attention : L\'omission d\'un mémo peut entraîner des fonds mal placés Le Memo / Destination Tag est un code qui distingue les transactions envoyées à un destinataire partagé sur un réseau crypto. @@ -851,6 +862,7 @@ les frais de réseau seront couverts en utilisant %1$s énergie les frais de réseau seront réduits en utilisant %1$s énergie y compris des frais de réseau de %1$s + À l\'adresse La transaction a été signée avec succès et envoyée au nœud de blockchain. Le solde du portefeuille sera mis à jour après un certain temps %1$s est un actif du réseau Tron. Pour calculer les frais et effectuer une transaction, déposez du Tron (TRX) sur votre compte. Le montant dépasse le solde @@ -860,6 +872,7 @@ Les frais de commissions dépassent le solde Le total dépasse le solde Échanger et envoyer + Montant à recevoir Transaction envoyée Scannez la carte/ bague que vous souhaitez configurer. Oublier le portefeuille @@ -1013,6 +1026,7 @@ Meilleurs Tarifs Simple et intuitif, vous permettant d\'échanger des jetons en quelques clics Plus Simple Que Jamais + Échange via le fournisseur Le montant comprend :\n• les frais du fournisseur de services\n• les frais de réseau pour l\'envoi de %s depuis l\'échange vers l\'adresse de l\'utilisateur. Le montant comprend :\n• les frais du fournisseur de services\n• les frais de réseau pour l\'envoi de %1$s depuis l\'échange vers l\'adresse de l\'utilisateur. \n\nLe slippage du fournisseur peut atteindre %2$s Le montant comprend les frais du fournisseur de services. @@ -1164,6 +1178,7 @@ Accédez à plus de 13 000 cryptomonnaies. Achetez, vendez, échangez et stakez en un seul clic.\nAssociez jusqu\'à trois cartes pour une sauvegarde. Découvrez le Portefeuille Tangem Recevez des notifications sur les transactions entrantes du portefeuille et les mises à jour de Tangem. + Notifications de transaction Paramètres du portefeuille Tangem Utilisez %s ou scannez une carte/bague pour déverrouiller l\'accès à votre portefeuille @@ -1255,6 +1270,7 @@ À des fins de test uniquement Le solde peut être obsolète. Rafraîchissez la page. Domaine malveillant + Si le problème persiste, n’hésitez pas à contacter notre support. Le portefeuille Tangem ne prend actuellement pas en charge %ss dApp non prise en charge Connexions diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 0283698666..3f3874186f 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -1253,6 +1253,7 @@ Ваш отзыв мотивирует нас сделать кошелек Tangem еще лучше Нравится Tangem? Вам необходимо провести ассоциацию токена для того, чтобы иметь возможность принимать его + Вы должны открыть трастлайн для своего токена, чтобы иметь возможность получить на него средства. Необходима плата за аренду сети Требуется действие Вы обращались в службу поддержки через приложение в течение 7 дней после создания кошелька? Если обращались или не уверены, нажмите «Да» и следуйте инструкциям. @@ -1268,26 +1269,71 @@ Свайпните вниз для обновления или попробуйте позже. Некоторые сети недоступны Некоторые балансы токенов не удалось обновить + Недостаточно %s. Пополните свой XLM аккаунт чтобы открыть трастлайн. Это Testnet карта. Он не может обрабатывать транзакции и используется только в целях тестирования и разработки. Только для целей тестирования Возможно, баланс устарел. Обновите страницу. Открыть Trustline Согласно правилам сети, чтобы пополнить этот токен, необходимо сначала открыть Trustline — это позволит вашему кошельку принимать и хранить этот актив. - Откройте Trustline + Требуется трастлайн + Вредоносный домен + Неизвестный домен + Подключиться всё равно + Ошибка тайм-аута. Пожалуйста, попробуйте позже. + Не удалось подключиться через Wallet Connect + Этот домен не может быть верифицирован. Внимательно проверьте запрос перед одобрением. Пожалуйста, вернитесь в браузер и выполните повторное подключение через WalletConnect. + Сессия Wallet Connect была завершена + Подписать всё равно + Код ошибки: %s. Если проблема сохраняется, обратитесь в нашу службу поддержки. + Если проблема сохраняется, обратитесь в нашу службу поддержки + Мы обнаружили неизвестную ошибку + Кошелек Tangem.в настоящий момент не поддерживает %s Неподдерживаемый dApp + Мы обнаружили неизвестную ошибку + Tangem в настоящее время не поддерживает необходимую сеть для %s + Неподдерживаемые сети + Tangem поддерживает сеть, необходимую для %s + Верифицированный домен Выбрана не верная карта или кольцо + Похоже, возникла проблема + Разрешение на использование Адрес Подключение + Загрузка + Сеть + Сети + Безлимитно + Кошелек + Подключенные сети Посмотреть баланс кошелька и его активность + Подписать транзакцию без вашего участия + Запрос разрешения на транзакцию + Не имеет возможности + Хотели бы Запрос на подключение + Подключения Вложение Копировать данные + Настраиваемый лимит + Отключить все + Отключить все dApp + Предварительные изменения + Не удалось выполнить симуляцию транзакции. Пожалуйста, действуйте с осторожностью. + Вредоносная транзакция + Добавьте сеть %s в ваш портфель для выбранного кошелька + В кошельке не добавлены необходимые сети + Новое подключение + Подключите свой кошелек к различным dApp + Нет подключений + Этот домен помечен как небезопасный несколькими поставщиками систем безопасности. Немедленно покиньте его, чтобы защитить свои активы. + Известный риск безопасности Запрос от Тип подписи На Запрос транзакции Запрос транзакции + Безлимитное количество Подключение кошелька Отказаться Вы не закончили резервное копирование. Хотите продолжить? diff --git a/core/res/src/main/res/values-uk-rUA/strings.xml b/core/res/src/main/res/values-uk-rUA/strings.xml index 3669539d24..3387633667 100644 --- a/core/res/src/main/res/values-uk-rUA/strings.xml +++ b/core/res/src/main/res/values-uk-rUA/strings.xml @@ -722,6 +722,9 @@ Сортування токенів Список Більше + Ви можете ввімкнути сповіщення у налаштуваннях + Увімкнути пізніше + Налаштування Увімкнути сповіщення Отримуйте сповіщення про вхідні транзакції в підтримуваних мережах. Виберіть з галереї diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 84c5ab2dc5..1e513517d1 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -948,6 +948,7 @@ Total amount exceeds balance Swap and send Send any token, and we’ll convert it on the way. Your recipient gets exactly what they need—seamlessly. + Recipient will receive Will be sent a recipient Amount to receive Send with swap @@ -1427,7 +1428,7 @@ Swipe down to refresh or try again later. Some networks are unreachable Some token balances could not be updated - Not enough %s. Top up your XLM account to associate this token + Not enough %s. Top up your XLM account to open trustline. This is a Testnet card. It cannot process transactions and should only be used for testing and development purposes. For testing purposes only Balance may be outdated. Refresh the page. @@ -1482,7 +1483,7 @@ Estimated wallet changes The transaction couldn\'t be simulated. Please proceed with caution. Malicious transaction - Add the %s network to your profile for this wallet + Add the %s network to your portfolio for this wallet The wallet has no required networks New connection Connect your wallet to a different dApps diff --git a/core/utils/src/main/java/com/tangem/utils/StringsSigns.kt b/core/utils/src/main/java/com/tangem/utils/StringsSigns.kt index 36945de43e..f1423fabac 100644 --- a/core/utils/src/main/java/com/tangem/utils/StringsSigns.kt +++ b/core/utils/src/main/java/com/tangem/utils/StringsSigns.kt @@ -14,4 +14,5 @@ object StringsSigns { const val PERCENT = "%" const val THREE_STARS = "\u2217\u2217\u2217" const val PASSWORD_VISUAL_CHAR = '\u2022' + const val APPROXIMATE = "≈" } \ No newline at end of file diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAmountModel.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAmountModel.kt index f722d3e1ea..1172e472b4 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAmountModel.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAmountModel.kt @@ -250,6 +250,7 @@ internal class SwapAmountModel @Inject constructor( .onEach { currency -> uiState.update { amountUM -> if (amountUM is SwapAmountUM.Content) { + initPairs(currency) amountUM.copy( isPrimaryButtonEnabled = false, secondaryAmount = SwapAmountFieldUM.Loading( @@ -260,7 +261,6 @@ internal class SwapAmountModel @Inject constructor( amountUM } } - initPairs(currency) } .launchIn(modelScope) } @@ -400,6 +400,7 @@ internal class SwapAmountModel @Inject constructor( swapDirection = swapDirection, allowanceContract = quote.allowanceContract, isApprovalNeeded = checkAllowance(state, quote), + fromAmount = fromAmountValue, ).convert( SwapQuoteUMConverter.Data( quote = quote, diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAmountQuoteUtils.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAmountQuoteUtils.kt index b2b853bf9d..c4869a1825 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAmountQuoteUtils.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAmountQuoteUtils.kt @@ -12,9 +12,13 @@ import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM import com.tangem.utils.extensions.isZero import java.math.BigDecimal import java.math.RoundingMode +import kotlin.math.min internal object SwapAmountQuoteUtils { + private const val MAX_DECIMALS = 8 + private const val MIN_DECIMALS = 2 + fun calculatePriceImpact( fromTokenAmount: BigDecimal, toTokenAmount: BigDecimal, @@ -41,6 +45,11 @@ internal object SwapAmountQuoteUtils { } } + fun calculateRate(fromAmount: BigDecimal, toAmount: BigDecimal, toAmountDecimals: Int): BigDecimal { + val rateDecimals = if (toAmountDecimals == 0) MIN_DECIMALS else toAmountDecimals + return toAmount.divide(fromAmount, min(rateDecimals, MAX_DECIMALS), RoundingMode.HALF_UP) + } + fun SwapAmountUM.updateAmount( onPrimaryAmount: SwapAmountFieldUM.Content.(CryptoCurrencyStatus) -> SwapAmountFieldUM, onSecondaryAmount: SwapAmountFieldUM.Content.(CryptoCurrencyStatus) -> SwapAmountFieldUM, diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/converter/SwapQuoteUMConverter.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/converter/SwapQuoteUMConverter.kt index 8abbd44af1..f5738cbd5e 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/converter/SwapQuoteUMConverter.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/converter/SwapQuoteUMConverter.kt @@ -1,14 +1,20 @@ package com.tangem.features.swap.v2.impl.amount.model.converter +import androidx.compose.ui.text.buildAnnotatedString +import com.tangem.core.ui.extensions.annotatedReference +import com.tangem.core.ui.extensions.appendSpace import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.format.bigdecimal.anyDecimals import com.tangem.core.ui.format.bigdecimal.crypto import com.tangem.core.ui.format.bigdecimal.format import com.tangem.domain.express.models.ExpressProvider import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.swap.models.SwapDirection import com.tangem.domain.swap.models.SwapQuoteModel +import com.tangem.features.swap.v2.impl.amount.model.SwapAmountQuoteUtils.calculateRate import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM.Content.DifferencePercent +import com.tangem.utils.StringsSigns import com.tangem.utils.converter.Converter import java.math.BigDecimal @@ -18,10 +24,24 @@ internal class SwapQuoteUMConverter( private val isApprovalNeeded: Boolean, private val primaryCurrency: CryptoCurrency, private val secondaryCurrency: CryptoCurrency, + private val fromAmount: BigDecimal, ) : Converter { override fun convert(value: Data): SwapQuoteUM { val (quote, provider) = value + val rate = calculateRate( + fromAmount = fromAmount, + toAmount = quote.toTokenAmount, + toAmountDecimals = secondaryCurrency.decimals, + ) + val rateString = buildAnnotatedString { + append(BigDecimal.ONE.format { crypto(symbol = primaryCurrency.symbol, decimals = 0).anyDecimals() }) + appendSpace() + append(StringsSigns.APPROXIMATE) + appendSpace() + append(rate.format { crypto(secondaryCurrency) }) + } + return if (allowanceContract != null) { if (isApprovalNeeded) { SwapQuoteUM.Allowance( @@ -36,6 +56,7 @@ internal class SwapQuoteUMConverter( quoteAmountValue = stringReference( quote.toTokenAmount.toQuoteValue(), ), + rate = annotatedReference(rateString), ) } } else { @@ -46,6 +67,7 @@ internal class SwapQuoteUMConverter( quoteAmountValue = stringReference( quote.toTokenAmount.toQuoteValue(), ), + rate = annotatedReference(rateString), ) } } diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/ui/preview/SwapAmountContentPreview.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/ui/preview/SwapAmountContentPreview.kt index 596cecef01..b40ecc6c80 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/ui/preview/SwapAmountContentPreview.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/ui/preview/SwapAmountContentPreview.kt @@ -5,6 +5,8 @@ import com.tangem.core.ui.components.atoms.text.TextEllipsis import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.stringReference import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.express.models.ExpressProvider +import com.tangem.domain.express.models.ExpressProviderType import com.tangem.domain.express.models.ExpressRateType import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.network.Network @@ -16,10 +18,11 @@ import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountType import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM import kotlinx.collections.immutable.persistentListOf +import java.math.BigDecimal internal data object SwapAmountContentPreview { - private val cryptoCurrencyStatus = CryptoCurrencyStatus( + val cryptoCurrencyStatus = CryptoCurrencyStatus( currency = CryptoCurrency.Coin( id = CryptoCurrency.ID.fromValue("coin⟨BITCOIN⟩bitcoin"), network = Network( @@ -41,12 +44,32 @@ internal data object SwapAmountContentPreview { name = "Bitcoin", symbol = "BTC", decimals = 8, - iconUrl = "", + iconUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/medium/bitcoin.png", isCustom = false, ), value = CryptoCurrencyStatus.Loading, ) + private val provider = ExpressProvider( + providerId = "changenow", + rateTypes = listOf(ExpressRateType.Float), + name = "ChangeNow", + type = ExpressProviderType.CEX, + imageLarge = "", + termsOfUse = "", + privacyPolicy = "", + isRecommended = true, + slippage = BigDecimal.ZERO, + ) + + private val quote = SwapQuoteUM.Content( + provider = provider, + quoteAmount = "123".toBigDecimal(), + quoteAmountValue = stringReference("123"), + rate = stringReference("1 USD ≈ 123.123 POL"), + diffPercent = SwapQuoteUM.Content.DifferencePercent.Best, + ) + val emptyState = SwapAmountUM.Content( isPrimaryButtonEnabled = false, primaryAmount = SwapAmountFieldUM.Empty( @@ -95,7 +118,7 @@ internal data object SwapAmountContentPreview { selectedAmountType = SwapAmountType.From, swapCurrencies = SwapCurrencies.EMPTY, swapQuotes = persistentListOf(), - selectedQuote = SwapQuoteUM.Empty, + selectedQuote = quote, primaryCryptoCurrencyStatus = cryptoCurrencyStatus, secondaryCryptoCurrencyStatus = cryptoCurrencyStatus, swapRateType = ExpressRateType.Float, diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/ui/preview/SwapChooseProviderContentPreview.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/ui/preview/SwapChooseProviderContentPreview.kt index 6558e68f96..b96c7acb4b 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/ui/preview/SwapChooseProviderContentPreview.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/ui/preview/SwapChooseProviderContentPreview.kt @@ -36,6 +36,7 @@ internal object SwapChooseProviderContentPreview { provider = provider1, quoteAmount = "123".toBigDecimal(), quoteAmountValue = stringReference("123"), + rate = stringReference("1 USD ≈ 123.123 POL"), diffPercent = SwapQuoteUM.Content.DifferencePercent.Best, ) @@ -43,6 +44,7 @@ internal object SwapChooseProviderContentPreview { provider = provider2, quoteAmount = "13.12".toBigDecimal(), quoteAmountValue = stringReference("13.12"), + rate = stringReference("1 USD ≈ 12.123 POL"), diffPercent = SwapQuoteUM.Content.DifferencePercent.Empty, ) diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/common/entity/ConfirmUM.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/common/entity/ConfirmUM.kt index 7e3af10fa2..701181e121 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/common/entity/ConfirmUM.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/common/entity/ConfirmUM.kt @@ -3,6 +3,8 @@ package com.tangem.features.swap.v2.impl.common.entity import androidx.compose.runtime.Immutable import com.tangem.common.ui.notifications.NotificationUM import com.tangem.core.ui.extensions.TextReference +import com.tangem.domain.express.models.ExpressProvider +import com.tangem.domain.swap.models.SwapDataModel import kotlinx.collections.immutable.ImmutableList @Immutable @@ -18,6 +20,14 @@ internal sealed class ConfirmUM { val notifications: ImmutableList, ) : ConfirmUM() + data class Success( + override val isPrimaryButtonEnabled: Boolean = true, + val transactionDate: Long, + val txUrl: String, + val swapDataModel: SwapDataModel, + val provider: ExpressProvider, + ) : ConfirmUM() + data object Empty : ConfirmUM() { override val isPrimaryButtonEnabled: Boolean = false } diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/common/entity/SwapQuoteUM.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/common/entity/SwapQuoteUM.kt index 0b4abfd3c3..ec6aaf74e7 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/common/entity/SwapQuoteUM.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/common/entity/SwapQuoteUM.kt @@ -34,6 +34,7 @@ internal sealed class SwapQuoteUM { val quoteAmount: BigDecimal, val quoteAmountValue: TextReference, val diffPercent: DifferencePercent, + val rate: TextReference, ) : SwapQuoteUM() { sealed class DifferencePercent { data object Empty : DifferencePercent() diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/success/SendWithSwapSuccessComponent.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/success/SendWithSwapSuccessComponent.kt new file mode 100644 index 0000000000..26d40108f5 --- /dev/null +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/success/SendWithSwapSuccessComponent.kt @@ -0,0 +1,37 @@ +package com.tangem.features.swap.v2.impl.sendviaswap.success + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.tangem.common.ui.navigationButtons.NavigationModelCallback +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.model.getOrCreateModel +import com.tangem.core.ui.decompose.ComposableContentComponent +import com.tangem.features.swap.v2.impl.sendviaswap.SendWithSwapRoute +import com.tangem.features.swap.v2.impl.sendviaswap.entity.SendWithSwapUM +import com.tangem.features.swap.v2.impl.sendviaswap.success.model.SendWithSwapSuccessModel +import com.tangem.features.swap.v2.impl.sendviaswap.success.ui.SendWithSwapSuccessContent +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.StateFlow + +internal class SendWithSwapSuccessComponent( + appComponentContext: AppComponentContext, + params: Params, +) : ComposableContentComponent, AppComponentContext by appComponentContext { + + private val model: SendWithSwapSuccessModel = getOrCreateModel(params = params) + + @Composable + override fun Content(modifier: Modifier) { + val state by model.uiState.collectAsStateWithLifecycle() + SendWithSwapSuccessContent(sendWithSwapUM = state) + } + + data class Params( + val sendWithSwapUMFlow: StateFlow, + val analyticsCategoryName: String, + val currentRoute: Flow, + val callback: NavigationModelCallback, + ) +} \ No newline at end of file diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/success/di/SendWithSwapSuccessModule.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/success/di/SendWithSwapSuccessModule.kt new file mode 100644 index 0000000000..f3cf9bc5a7 --- /dev/null +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/success/di/SendWithSwapSuccessModule.kt @@ -0,0 +1,20 @@ +package com.tangem.features.swap.v2.impl.sendviaswap.success.di + +import com.tangem.core.decompose.di.ModelComponent +import com.tangem.core.decompose.model.Model +import com.tangem.features.swap.v2.impl.sendviaswap.success.model.SendWithSwapSuccessModel +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap + +@Module +@InstallIn(ModelComponent::class) +internal interface SendWithSwapSuccessModule { + + @Binds + @IntoMap + @ClassKey(SendWithSwapSuccessModel::class) + fun bindSendWithSwapSuccessModel(impl: SendWithSwapSuccessModel): Model +} \ No newline at end of file diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/success/model/SendWithSwapSuccessModel.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/success/model/SendWithSwapSuccessModel.kt new file mode 100644 index 0000000000..63ad5d40da --- /dev/null +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/success/model/SendWithSwapSuccessModel.kt @@ -0,0 +1,81 @@ +package com.tangem.features.swap.v2.impl.sendviaswap.success.model + +import com.tangem.common.routing.AppRouter +import com.tangem.common.ui.navigationButtons.NavigationButton +import com.tangem.common.ui.navigationButtons.NavigationUM +import com.tangem.core.decompose.di.ModelScoped +import com.tangem.core.decompose.model.Model +import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.core.navigation.share.ShareManager +import com.tangem.core.navigation.url.UrlOpener +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.features.swap.v2.impl.R +import com.tangem.features.swap.v2.impl.common.entity.ConfirmUM +import com.tangem.features.swap.v2.impl.sendviaswap.entity.SendWithSwapUM +import com.tangem.features.swap.v2.impl.sendviaswap.success.SendWithSwapSuccessComponent +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.flow.StateFlow +import javax.inject.Inject + +@ModelScoped +internal class SendWithSwapSuccessModel @Inject constructor( + override val dispatchers: CoroutineDispatcherProvider, + private val urlOpener: UrlOpener, + private val shareManager: ShareManager, + private val appRouter: AppRouter, + paramsContainer: ParamsContainer, +) : Model() { + + private val params: SendWithSwapSuccessComponent.Params = paramsContainer.require() + + val uiState: StateFlow = params.sendWithSwapUMFlow + + val confirmUM = uiState.value.confirmUM as? ConfirmUM.Success + + init { + configConfirmSuccessNavigation() + } + + private fun configConfirmSuccessNavigation() { + params.callback.onNavigationResult( + NavigationUM.Content( + title = TextReference.EMPTY, + subtitle = null, + backIconRes = R.drawable.ic_close_24, + backIconClick = appRouter::pop, + primaryButton = NavigationButton( + textReference = resourceReference(R.string.common_close), + iconRes = null, + isEnabled = true, + isHapticClick = false, + onClick = appRouter::pop, + ), + prevButton = null, + secondaryPairButtonsUM = NavigationButton( + textReference = resourceReference(R.string.common_explore), + iconRes = R.drawable.ic_web_24, + onClick = ::onExploreClick, + ) to NavigationButton( + textReference = resourceReference(R.string.common_share), + iconRes = R.drawable.ic_share_24, + onClick = ::onShareClick, + ), + ), + ) + } + + private fun onExploreClick() { + if (confirmUM == null) return + + // analyticsEventHandler.send(CommonSendAnalyticEvents.ExploreButtonClicked(params.analyticsCategoryName)) + urlOpener.openUrl(confirmUM.txUrl) + } + + private fun onShareClick() { + if (confirmUM == null) return + + // analyticsEventHandler.send(CommonSendAnalyticEvents.ShareButtonClicked(params.analyticsCategoryName)) + shareManager.shareText(confirmUM.txUrl) + } +} \ No newline at end of file diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/success/ui/SendWithSwapSuccessContent.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/success/ui/SendWithSwapSuccessContent.kt new file mode 100644 index 0000000000..4ea2e22fea --- /dev/null +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/success/ui/SendWithSwapSuccessContent.kt @@ -0,0 +1,441 @@ +package com.tangem.features.swap.v2.impl.sendviaswap.success.ui + +import android.content.res.Configuration +import androidx.compose.animation.* +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.hapticfeedback.HapticFeedbackType +import androidx.compose.ui.platform.LocalHapticFeedback +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.tangem.blockchain.common.transaction.Fee +import com.tangem.blockchain.common.transaction.TransactionFee +import com.tangem.common.ui.amountScreen.models.AmountState +import com.tangem.common.ui.amountScreen.utils.getFiatReference +import com.tangem.common.ui.navigationButtons.NavigationButton +import com.tangem.common.ui.navigationButtons.NavigationUM +import com.tangem.core.ui.components.SecondaryButtonIconStart +import com.tangem.core.ui.components.SpacerW12 +import com.tangem.core.ui.components.currency.icon.CurrencyIcon +import com.tangem.core.ui.components.icons.identicon.IdentIcon +import com.tangem.core.ui.components.inputrow.InputRowBestRate +import com.tangem.core.ui.components.rows.SelectorRowItem +import com.tangem.core.ui.components.transactions.TransactionDoneTitle +import com.tangem.core.ui.extensions.* +import com.tangem.core.ui.format.bigdecimal.crypto +import com.tangem.core.ui.format.bigdecimal.fee +import com.tangem.core.ui.format.bigdecimal.fiat +import com.tangem.core.ui.format.bigdecimal.format +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.core.ui.utils.DateTimeFormatters +import com.tangem.core.ui.utils.singleEvent +import com.tangem.core.ui.utils.toTimeFormat +import com.tangem.domain.express.models.ExpressProvider +import com.tangem.domain.express.models.ExpressProviderType +import com.tangem.domain.swap.models.SwapDataModel +import com.tangem.domain.swap.models.SwapDataTransactionModel +import com.tangem.domain.utils.convertToSdkAmount +import com.tangem.features.send.v2.api.entity.FeeExtraInfo +import com.tangem.features.send.v2.api.entity.FeeItem +import com.tangem.features.send.v2.api.entity.FeeNonce +import com.tangem.features.send.v2.api.entity.FeeSelectorUM +import com.tangem.features.send.v2.api.subcomponents.destination.entity.DestinationTextFieldUM +import com.tangem.features.send.v2.api.subcomponents.destination.entity.DestinationUM +import com.tangem.features.swap.v2.impl.R +import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountFieldUM +import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM +import com.tangem.features.swap.v2.impl.amount.ui.preview.SwapAmountContentPreview +import com.tangem.features.swap.v2.impl.common.entity.ConfirmUM +import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM +import com.tangem.features.swap.v2.impl.sendviaswap.entity.SendWithSwapUM +import kotlinx.collections.immutable.persistentListOf +import java.math.BigDecimal + +private const val GRADIENT_ALPHA = 0.3f + +@Composable +internal fun SendWithSwapSuccessContent(sendWithSwapUM: SendWithSwapUM) { + if (sendWithSwapUM.navigationUM !is NavigationUM.Content) return + + val confirmUM = sendWithSwapUM.confirmUM as? ConfirmUM.Success ?: return + val amountUM = sendWithSwapUM.amountUM as? SwapAmountUM.Content ?: return + val quoteUM = amountUM.selectedQuote as? SwapQuoteUM.Content ?: return + val destinationUM = sendWithSwapUM.destinationUM as? DestinationUM.Content ?: return + val feeSelectorUM = sendWithSwapUM.feeSelectorUM as? FeeSelectorUM.Content ?: return + + Box( + modifier = Modifier + .fillMaxSize() + .background(TangemTheme.colors.background.tertiary), + ) { + Column( + modifier = Modifier + .padding(horizontal = 16.dp) + .verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement.spacedBy(12.dp), + ) { + TransactionDoneTitle( + title = resourceReference(R.string.sent_transaction_sent_title), + subtitle = resourceReference( + id = R.string.send_date_format, + formatArgs = wrappedList( + confirmUM.transactionDate.toTimeFormat(DateTimeFormatters.dateFormatter), + confirmUM.transactionDate.toTimeFormat(), + ), + ), + modifier = Modifier.padding(vertical = 12.dp), + ) + SwapAmountBlock(amountUM = amountUM) + InputRowBestRate( + imageUrl = confirmUM.provider.imageLarge, + title = stringReference(confirmUM.provider.name), + titleExtra = stringReference(confirmUM.provider.type.typeName), + subtitle = quoteUM.rate, + modifier = Modifier + .clip(TangemTheme.shapes.roundedCornersXMedium) + .background(TangemTheme.colors.background.action), + ) + DestinationBlock(destinationUM.addressTextField) + FeeBlock(feeSelectorUM = feeSelectorUM) + Spacer(Modifier.height(60.dp)) + } + DoneButtons( + pairButtonsUM = sendWithSwapUM.navigationUM.secondaryPairButtonsUM, + modifier = Modifier + .align(Alignment.BottomCenter) + .background( + brush = Brush.verticalGradient( + colors = listOf( + Color.Transparent, + TangemTheme.colors.background.tertiary.copy(GRADIENT_ALPHA), + TangemTheme.colors.background.tertiary, + ), + ), + ) + .padding(top = 24.dp) + .padding(horizontal = 16.dp), + + ) + } +} + +@Composable +private fun SwapAmountBlock(amountUM: SwapAmountUM.Content) { + AmountBlock( + title = resourceReference( + id = R.string.send_from_wallet_name, + formatArgs = wrappedList( + (amountUM.primaryAmount.amountField as? AmountState.Data)?.title + ?: TextReference.EMPTY, + ), + ), + amountFieldUM = amountUM.primaryAmount, + ) + AmountBlock( + title = resourceReference(R.string.send_with_swap_recipient_amount_success_title), + amountFieldUM = amountUM.secondaryAmount, + ) +} + +@Composable +private fun AmountBlock(title: TextReference, amountFieldUM: SwapAmountFieldUM, modifier: Modifier = Modifier) { + val amountFieldData = amountFieldUM.amountField as? AmountState.Data ?: return + val cryptoAmount = amountFieldData.amountTextField.cryptoAmount + val fiatAmount = amountFieldData.amountTextField.fiatAmount + + Column( + modifier = modifier + .clip(TangemTheme.shapes.roundedCornersXMedium) + .background(TangemTheme.colors.background.action) + .padding(12.dp), + ) { + Text( + text = title.resolveReference(), + style = TangemTheme.typography.subtitle2, + color = TangemTheme.colors.text.tertiary, + ) + Row( + horizontalArrangement = Arrangement.spacedBy(12.dp), + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .padding(top = 16.dp), + ) { + CurrencyIcon( + state = amountFieldData.tokenIconState, + ) + Column( + verticalArrangement = Arrangement.spacedBy(2.dp), + ) { + Text( + text = cryptoAmount.value?.format { + crypto( + symbol = cryptoAmount.currencySymbol, + decimals = cryptoAmount.decimals, + ) + }.orEmpty(), + style = TangemTheme.typography.subtitle2, + color = TangemTheme.colors.text.primary1, + ) + Text( + text = fiatAmount.value.format { + fiat( + fiatCurrencyCode = amountFieldData.appCurrency.code, + fiatCurrencySymbol = amountFieldData.appCurrency.symbol, + ) + }, + style = TangemTheme.typography.caption2, + color = TangemTheme.colors.text.tertiary, + ) + } + } + } +} + +@Composable +private fun FeeBlock(feeSelectorUM: FeeSelectorUM.Content) { + val feeExtraInfo = feeSelectorUM.feeExtraInfo + val feeFiatRateUM = feeSelectorUM.feeFiatRateUM + Column( + modifier = Modifier + .fillMaxWidth() + .clip(TangemTheme.shapes.roundedCornersXMedium) + .background(TangemTheme.colors.background.action) + .padding(TangemTheme.dimens.spacing12), + ) { + Text( + text = stringResourceSafe(com.tangem.common.ui.R.string.common_network_fee_title), + style = TangemTheme.typography.subtitle2, + color = TangemTheme.colors.text.tertiary, + ) + + Box(modifier = Modifier.padding(top = TangemTheme.dimens.spacing8)) { + val feeAmount = feeSelectorUM.selectedFeeItem.fee.amount + SelectorRowItem( + titleRes = com.tangem.common.ui.R.string.common_fee_selector_option_market, + iconRes = com.tangem.common.ui.R.drawable.ic_bird_24, + preDot = stringReference( + feeAmount.value.format { + crypto( + symbol = feeAmount.currencySymbol, + decimals = feeAmount.decimals, + ).fee(canBeLower = feeExtraInfo.isFeeApproximate) + }, + ), + postDot = if (feeExtraInfo.isFeeConvertibleToFiat && feeFiatRateUM != null) { + getFiatReference(feeAmount.value, feeFiatRateUM.rate, feeFiatRateUM.appCurrency) + } else { + null + }, + ellipsizeOffset = feeAmount.currencySymbol.length, + isSelected = true, + showDivider = false, + showSelectedAppearance = false, + paddingValues = PaddingValues(), + ) + } + } +} + +@Composable +private fun DestinationBlock(address: DestinationTextFieldUM.RecipientAddress, modifier: Modifier = Modifier) { + Column( + modifier = modifier + .fillMaxWidth() + .clip(TangemTheme.shapes.roundedCornersXMedium) + .background(TangemTheme.colors.background.action) + .padding(12.dp), + ) { + Text( + text = stringResourceSafe(R.string.send_recipient), + style = TangemTheme.typography.subtitle2, + color = TangemTheme.colors.text.tertiary, + ) + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(24.dp), + modifier = Modifier.padding(top = 8.dp), + ) { + Text( + modifier = Modifier.weight(1f), + text = address.value, + style = TangemTheme.typography.body2, + color = TangemTheme.colors.text.primary1, + ) + IdentIcon( + address = address.value, + modifier = Modifier + .size(TangemTheme.dimens.size36) + .clip(RoundedCornerShape(18.dp)) + .background(TangemTheme.colors.background.tertiary), + ) + } + } +} + +// TODO remove [REDACTED_TASK_KEY] +@Composable +private fun DoneButtons(pairButtonsUM: Pair?, modifier: Modifier = Modifier) { + val hapticFeedback = LocalHapticFeedback.current + + AnimatedVisibility( + visible = pairButtonsUM != null, + modifier = modifier, + enter = slideInVertically().plus(fadeIn()), + exit = slideOutVertically().plus(fadeOut()), + label = "Animate show sent state buttons", + ) { + val (leftButton, rightButton) = remember(this) { requireNotNull(pairButtonsUM) } + Row { + SecondaryButtonIconStart( + text = leftButton.textReference.resolveReference(), + iconResId = requireNotNull(leftButton.iconRes), + onClick = { + singleEvent { + leftButton.onClick() + } + }, + modifier = Modifier.weight(1f), + ) + SpacerW12() + SecondaryButtonIconStart( + text = rightButton.textReference.resolveReference(), + iconResId = requireNotNull(rightButton.iconRes), + onClick = { + singleEvent { + hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) + rightButton.onClick() + } + }, + modifier = Modifier.weight(1f), + ) + } + } +} + +// region Preview +@Suppress("LongMethod") +@Composable +@Preview(showBackground = true, widthDp = 360, heightDp = 720) +@Preview(showBackground = true, widthDp = 360, heightDp = 720, uiMode = Configuration.UI_MODE_NIGHT_YES) +private fun SendWithSwapSuccessContent_Preview() { + TangemThemePreview { + SendWithSwapSuccessContent( + sendWithSwapUM = SendWithSwapUM( + amountUM = SwapAmountContentPreview.defaultState, + destinationUM = DestinationUM.Content( + isPrimaryButtonEnabled = false, + addressTextField = DestinationTextFieldUM.RecipientAddress( + value = "0x391316d97a07027a0702c8A002c8A0C25d8470", + keyboardOptions = KeyboardOptions(), + placeholder = TextReference.EMPTY, + label = resourceReference(R.string.send_recipient), + isError = false, + error = null, + isValuePasted = false, + blockchainAddress = "0x391316d97a07027a0702c8A002c8A0C25d8470", + ), + memoTextField = null, + recent = persistentListOf(), + wallets = persistentListOf(), + networkName = "Polygon", + isValidating = false, + isInitialized = false, + isRedesignEnabled = false, + ), + feeSelectorUM = FeeSelectorUM.Content( + fees = TransactionFee.Single( + normal = Fee.Common( + BigDecimal.ONE.convertToSdkAmount( + SwapAmountContentPreview.cryptoCurrencyStatus.currency, + ), + ), + ), + feeItems = persistentListOf(), + selectedFeeItem = FeeItem.Market( + Fee.Common( + BigDecimal.ONE.convertToSdkAmount( + SwapAmountContentPreview.cryptoCurrencyStatus.currency, + ), + ), + ), + feeExtraInfo = FeeExtraInfo( + isFeeApproximate = false, + isFeeConvertibleToFiat = false, + isTronToken = false, + ), + feeFiatRateUM = null, + feeNonce = FeeNonce.None, + ), + confirmUM = ConfirmUM.Success( + isPrimaryButtonEnabled = true, + transactionDate = 8960, + txUrl = "https://tangem.com", + provider = ExpressProvider( + providerId = "changelly", + rateTypes = listOf(), + name = "Changelly", + type = ExpressProviderType.CEX, + imageLarge = "https://s3.eu-central-1.amazonaws.com/tangem.api/express/changelly-1024.png", + termsOfUse = "", + privacyPolicy = "", + isRecommended = false, + slippage = null, + ), + swapDataModel = SwapDataModel( + toTokenAmount = BigDecimal.TEN, + transaction = SwapDataTransactionModel.CEX( + fromAmount = BigDecimal.TEN, + toAmount = BigDecimal.TEN, + txValue = "cetero", + txId = "oporteat", + txTo = "graecis", + txExtraId = "expetendis", + externalTxId = "atomorum", + externalTxUrl = "https://tangem.com", + txExtraIdName = "Jeffry Blackwell", + ), + ), + ), + navigationUM = NavigationUM.Content( + title = TextReference.EMPTY, + subtitle = null, + backIconRes = R.drawable.ic_close_24, + backIconClick = {}, + additionalIconRes = null, + additionalIconClick = null, + primaryButton = NavigationButton( + textReference = resourceReference(R.string.common_close), + isEnabled = true, + onClick = {}, + ), + secondaryPairButtonsUM = NavigationButton( + textReference = resourceReference(R.string.common_explore), + iconRes = R.drawable.ic_web_24, + isEnabled = true, + onClick = {}, + ) to NavigationButton( + textReference = resourceReference(R.string.common_share), + iconRes = R.drawable.ic_share_24, + isEnabled = true, + onClick = {}, + ), + ), + ), + ) + } +} +// endregion \ No newline at end of file