diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index 2ad0dfb2fe..ce1167a434 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -1316,7 +1316,7 @@ Die Genehmigung des Swaps ist im Gange und wird in Kürze abgeschlossen sein. Genehmigung in Arbeit Der Mindestbetrag für den Tausch beträgt %1$s. Bitte stelle sicher, dass der Restsaldo nach dem Swap nicht unter %2$s liegt. - Du hast keine %s austauschbaren Coins in Deiner Liste + Sie haben keine Token in Ihrem Portfolio, gegen die %s getauscht werden kann. Bitte fügen Sie einen anderen Token hinzu, um den Tausch durchzuführen. Keine Token zum Tauschen verfügbar Um eine Transaktion durchzuführen, du etwas etwas einzahlen %1$s %2$s Die Gebühr %s kann nicht gedeckt werden diff --git a/core/res/src/main/res/values-es/strings.xml b/core/res/src/main/res/values-es/strings.xml index c2084479a7..fb5d92c4e0 100644 --- a/core/res/src/main/res/values-es/strings.xml +++ b/core/res/src/main/res/values-es/strings.xml @@ -1236,7 +1236,7 @@ La aprobación del intercambio está en progreso y se completará en breve Aprobación en proceso El monto mínimo de transacción es %1$s. Asegúrese de que el saldo restante después del canje no sea inferior a %2$s. - No tiene %s monedas negociables en su lista + No tienes tokens en tu portafolio a los que puedas intercambiar %s. Por favor, añade otro token para realizar el intercambio. No hay tokens disponibles para intercambiar Para realizar una transacción necesita depositar %1$s %2$s No se pueden cubrir %s tarifa diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml index b3b307cac4..fb1cde4a04 100644 --- a/core/res/src/main/res/values-fr/strings.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -49,6 +49,8 @@ Par défaut du système 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 @@ -216,6 +218,7 @@ Inaccessible Unstakez En raison d\'une limitations sur les %1$s, seuls les %2$d UTXO peuvent s\'intégrer dans une seule transaction. Ce qui signifie vous ne pouvez envoyer que %3$s ou moins. Réduisez le montant. + Valeur copiée semaine avec Oui @@ -699,6 +702,7 @@ Par solde Organiser les jetons Dégrouper + Recevez des alertes pour les transactions entrantes sur les réseaux pris en charge. Sélectionnez dans la galerie Paramètres Vous n\'avez pas donné accès à votre caméra @@ -855,6 +859,7 @@ Montant invalide Les frais de commissions dépassent le solde Le total dépasse le solde + Échanger et envoyer Transaction envoyée Scannez la carte/ bague que vous souhaitez configurer. Oublier le portefeuille @@ -1085,6 +1090,7 @@ Utilisez %s ou scannez une carte/bague pour avoir accès à votre portefeuille Échec de la connexion : Cette dApp utilise Wallet Connect version1.0, qui n\'est pas prise en charge. Veuillez vous assurer que la dApp prend en charge Wallet Connect version2.0 pour réussir la connexion. Restez à jour avec les dernières fonctionnalités et actualités + Recevez des notifications des transactions entrantes Soyez le premier informé des nouvelles promotions Souhaitez-vous utiliser les\nnotifications push? Ajouter un nouveau portefeuille @@ -1157,6 +1163,7 @@ Obtenez-le maintenant avec 10 % de réduction 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. Paramètres du portefeuille Tangem Utilisez %s ou scannez une carte/bague pour déverrouiller l\'accès à votre portefeuille @@ -1184,7 +1191,7 @@ L\'approbation de l\'échange est en cours et sera achevée sous peu Approbation en cours Le montant minimum d\'échange est de %1$s. Veuillez vous assurer que le solde restant après l\'échange ne sera pas inférieur à %2$s. - Vous n\'avez pas de pièces échangeables %s dans votre liste + Vous n’avez aucun jeton dans votre portefeuille pouvant recevoir %s en échange. Veuillez ajouter un autre jeton pour effectuer l’échange. Aucun jeton disponible à échanger Pour effectuer une transaction, vous devez déposer %1$s %2$s Impossible de couvrir %s frais @@ -1243,9 +1250,13 @@ Certains réseaux sont actuellement inaccessibles. Veuillez réessayer plus tard. Certains réseaux sont inaccessibles Certains soldes de jetons n\'ont pas pu être mis à jour + Pas assez de %s. Rechargez votre compte XLM pour associer ce jeton Il s\'agit d\'une carte Testnet. Elle ne peut pas traiter les transactions et ne doit être utilisée qu\'à des fins de test et de développement. À des fins de test uniquement Le solde peut être obsolète. Rafraîchissez la page. + Domaine malveillant + Le portefeuille Tangem ne prend actuellement pas en charge %ss + dApp non prise en charge Connexions Déconnecter tout Texte sur la déconnexion de toutes les dApps diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml index b828454968..19a625c9ee 100644 --- a/core/res/src/main/res/values-ja/strings.xml +++ b/core/res/src/main/res/values-ja/strings.xml @@ -4,6 +4,9 @@ とにかくスキップ アクセスコードが設定されていません アクセスコードを入力 + アクセスコードが間違っています。あと%s回間違えるとホットウォレットが削除されます。 + アクセスコードが間違っています。あと%s回入力エラーが発生するとアプリがロックされます。 + アクセスコードが間違っています。 \n %s秒待ってから再試行してください。 続行するには、以前に入力したコードを確認してください アクセスコードを再入力 ウォレットのロックを解除するには、 %s桁のアクセスコードを設定します。 @@ -655,7 +658,7 @@ ウォレットへ進む バックアップを完了する 暗号資産を受け取る - プライマリカードをスキャン + メインカードまたはリングをスキャン スキップする どのように機能しますか? すべての秘密鍵をカードまたはリング内で生成し、安全なウォレットを作成しましょう @@ -706,7 +709,7 @@ バックアップデバイスなし 通知 バックアップデバイスが1つ追加されました - カードを準備してください + カードまたはリングを用意してください バックアップデバイス2つが追加されました 始めるには、ウォレットに任意の金額を入金するだけです 始めるには、ウォレットに%1$s %2$s以上入金するだけです @@ -924,6 +927,7 @@ 無効な金額 手数料が残高を超えています 合計金額が残高を超えています + スワップして送信 トークンを送信すれば、送信中に変換されます。受信者は必要なものをシームレスに受け取ります。 受信者に送信されます 受取金額 @@ -1296,8 +1300,8 @@ スワップ承認は現在進行中で、まもなく完了する予定です。 承認が進行中 最低のスワップ金額は%1$s です。スワップ後の残金が%2$s を下回らないようにしてください。 - あなたのリストには、交換可能な %s トークンがありません。 - スワップ可能なトークンがありません + ポートフォリオ内に%sとスワップ可能なトークンがありません。交換を有効にするには、別のトークンを追加してください。 + 互換性のあるトークンが追加されていません 取引を行うには、 %1$s %2$sを入金する必要があります。 %s 手数料を支払えません 受け取る金額は、 %s 以上である必要があります。 @@ -1375,6 +1379,8 @@ エラーコード: %s 。問題が解決しない場合は、お気軽にサポートまでお問い合わせください。 問題が解決しない場合は、お気軽にサポートまでお問い合わせください。 不明なエラーが発生しました + Tangemウォレットは、現在%sをサポートしていません。 + サポートされていないdApp エラーコード: 8 005。問題が解決しない場合は、お気軽にサポートまでお問い合わせください。 不明なエラーが発生しました Tangemは現在%sで必要なネットワークをサポートしていません。 diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 32178f4574..0283698666 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -620,7 +620,7 @@ Перейти к моему кошельку Завершение бэкапа Получить криптовалюту - Сканировать основную карту + Сканировать основную карту или кольцо Пропустить Как это работает? Давайте сгенерируем все ключи на вашей карте или кольце и создадим безопасный кошелек @@ -676,7 +676,7 @@ Нет резервных устройств Уведомления Добавлено одно резервное устройство - Подготовьте свою карту + Подготовьте свою карту или кольцо Добавлены два резервных девайса Пополните кошелек на любую сумму, чтобы начать пользоваться картой Пополните кошелек более чем на %1$s %2$s, чтобы начать пользоваться картой @@ -723,7 +723,12 @@ Упорядочить токены Список Подробнее + Вы можете включить нотификации в настройках + Включить позже + Настройки Подключить нотификации + Получайте уведомления о входящих транзакциях в поддерживаемых сетях. + Уведомления о транзакциях Выбрать из галереи Настройки Вы не предоставили доступ к вашей камере @@ -1119,6 +1124,7 @@ Используйте %s или отсканируйте карту/кольцо, чтобы получить доступ к своему кошельку Соединение не удалось: это dApp использует Wallet Connect версии 1.0, которая не поддерживается. Убедитесь, что dApp поддерживает Wallet Connect версии 2.0 для успешного подключения. Будьте в курсе новых функций и новостей + Получайте уведомления о входящих транзакциях Узнавайте первым о новых акциях Хотите использовать Push-уведомления? Добавить новый кошелек @@ -1174,6 +1180,7 @@ Получить с 10% скидкой Получите доступ к более чем 13 000 криптовалют. Покупайте, продавайте, обменивайте и стейкайте в один клик. Свяжите до трех карт для резервного копирования. Откройте Tangem Wallet + Получайте уведомления о входящих транзакциях в кошельке и обновлениях Tangem. Уведомления о транзакциях Настройки кошелька Tangem @@ -1202,7 +1209,7 @@ Разрешение обмена в процессе и будет скоро завершено Разрешение в процессе Минимальная сумма обмена - %1$s. Пожалуйста, убедитесь, что остаток после обмена также не будет меньше %2$s. - У вас в списке нет монет доступных для обмена с %s + У вас нет токенов в портфеле, на которые можно обменять %s. Пожалуйста, добавьте другой токен, чтобы выполнить обмен. Нет доступных для обмена токенов Чтобы совершить транзакцию, вам необходимо внести немного %1$s %2$s Невозможно покрыть комиссию %s @@ -1268,6 +1275,7 @@ Согласно правилам сети, чтобы пополнить этот токен, необходимо сначала открыть Trustline — это позволит вашему кошельку принимать и хранить этот актив. Откройте Trustline Пожалуйста, вернитесь в браузер и выполните повторное подключение через WalletConnect. + Неподдерживаемый 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 d1048dabe7..3669539d24 100644 --- a/core/res/src/main/res/values-uk-rUA/strings.xml +++ b/core/res/src/main/res/values-uk-rUA/strings.xml @@ -316,6 +316,7 @@ Очікуємо на депозит Очікуємо на депозит... Повернено + Повернення коштів Надсилаємо вам Надсилаємо вам... Надіслано @@ -370,6 +371,7 @@ Щоб продовжити, вам потрібно дозволити смарт-контракту %1s використовувати ваш %2s Надати дозвіл Необмежено + Створити новий гаманець Купити Сканувати в %s @@ -572,10 +574,11 @@ cNFT і pNFT наразі не підтримуються. Будь ласка, не надсилайте їх на свій гаманець. Надіслати NFT Риси + Безіменна колекція NFTs в %2$d колекціях%1$d %1$d NFT в %2$d колекціi - + %1$d NFTs в %2$d колекціях %1$d NFTs в %2$d колекціях @@ -720,6 +723,7 @@ Список Більше Увімкнути сповіщення + Отримуйте сповіщення про вхідні транзакції в підтримуваних мережах. Виберіть з галереї Налаштування Ви не надали доступ до своєї камери @@ -829,6 +833,9 @@ Комісія може сягати до Недопустимий Memo Покриття мережевої комісії + Nonce + Унікальний номер транзакції. Змініть його, щоб пришвидшити відправку або скасувати завислу операцію. + Введіть nonce… Недостатньо коштів для здійснення переказу, оскільки загальна сума комісії та переказу перевищує наявний баланс Сума перевищує баланс Для збереження вашого акаунту у блокчейні та захисту від можливих ризиків необхідний баланс не менше %s. Ця сума залишиться на вашому рахунку та не може бути знята. @@ -837,6 +844,7 @@ Встановлена комісія завелика Через особливості мережі %1$s комісія за переказ всього балансу вища. Щоб зменшити комісію, Ви можете залишити %2$s. Підвищена комісія + Рахунок одержувача не активований. Мінімальна сума переказу повинна бути не менша балансу, необхідного для покриття арендної плати: %1$s. Баланс вашого рахунку не може бути меншим за орендну плату. Будь ласка, залиште на рахунку не менше %1$s або виведіть всі кошти. Включена комісія перевищує суму переказу, що призводить до від’ємного значення Недопустима сума @@ -1095,6 +1103,8 @@ валідатор: %s Мінімум %s Мінімальна сума транзакції становить %1$s. + Комісії мережі Tron для популярних токенів можуть бути вищими. Стейкінг TRX може допомогти знизити транзакційні витрати. + Заощаджуйте на Tron комісіях Спробуйте знову Ви відсканували одну й ту саму картку. Для створення twin-гаманця вам потрібно відсканувати картку з номером %d Ви відсканували не ту twin-картку. Будь ласка, спробуйте відсканувати іншу @@ -1111,6 +1121,7 @@ Використовуйте %s або відскануйте картку/кільце, щоб отримати доступ до свого гаманця Не вдалося встановити з\'єднання: Цей dApp використовує Wallet Connect версії 1.0, яка не підтримується. Будь ласка, переконайтеся, що dApp підтримує Wallet Connect версії 2.0 для успішного підключення. Будьте в курсі останніх функцій та новин + Отримуйте сповіщення про вхідні транзакції Дізнавайтеся першими про нові акції Бажаєте використовувати Push-повідомлення? Додати новий гаманець @@ -1122,6 +1133,7 @@ Перейменування гаманця Розблокувати все Розблокувати все з %s + PIN-код не прийнятий. Спробуйте ще раз або введіть інший код. Блокчейн недоступний. Спробуйте пізніше Відскануйте картку або кільце Цей гаманець вже був активований раніше.\nЯкщо це було зроблено не вами, зверніться до служби підтримки.\nTangem ніколи не продає гаманці разом з попередньо згенерованим кодом доступу. @@ -1165,6 +1177,7 @@ Отримати з 10% знижкою Доступ до 13,000+ криптовалют. Купуйте, продавайте, обмінюйте та стейкайте одним дотиком.\nЗ’єднайте до трьох карток для бекапу. Відкрийте Tangem Wallet + Отримуйте сповіщення про вхідні транзакції в гаманці та оновлення Tangem. Сповіщення про транзакції Налаштування гаманця Tangem @@ -1193,7 +1206,7 @@ Дозвіл обміну триває і незабаром буде завершено Затвердження в процесі Мінімальна сума обміну становить - %1$s. Будь ласка, переконайтеся, що залишок на рахунку після обміну буде не менше за %2$s. - У вашому списку немає доступних монет для обміну %s + У вашому портфелі немає токенів, на які можна обміняти %s. Будь ласка, додайте інший токен, щоб виконати обмін. Немає доступних токенів для обміну Щоб здійснити транзакцію, вам потрібно внести трохи %1$s %2$s Неможливо покрити комісію %s @@ -1257,8 +1270,23 @@ Це картка Testnet. Вона не може обробляти транзакції і повинна використовуватися лише для тестування та розробки. Лише для цілей тестування Баланс може бути застарілим. Оновіть сторінку. + Відкрити Trustline + Відповідно до правил мережі, щоб поповнити цей токен, спочатку потрібно відкрити Trustline — це дозволить вашому гаманцю приймати та зберігати цей актив. + Відкрийте Trustline + Будь ласка, поверніться до браузеру і повторно підключіться через WalletConnect. + Обрана не вірна картка або кільце Адреса + Підключення + Переглянути баланс гаманця та активність + Запит на підключення Вміст + Копіювати дані + Запит від + Тип підпису + До + Запит транзакції + Запит транзакції + Підключення гаманця Відмовитися Ви не завершили резервне копіювання. Бажаєте продовжити? Так, поновити diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index d6919003f0..84c5ab2dc5 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -4,6 +4,9 @@ Skip anyway Access Code not set Enter Access Code + Wrong access code. Your hot wallet will be deleted after %s more incorrect attempts. + Wrong access code. App will be locked with %s more input errors + Wrong access code.\nPlease wait %s seconds and try again. Confirm your previously entered code to continue Re-enter Access Code Set a %s-digit Access Code to unlock your wallet. @@ -58,6 +61,9 @@ System default Theme App settings + Add Wallet + Select a wallet to log in + Welcome back! You successfully backed up your wallet. These words can’t be recovered if lost. Make sure to keep it somewhere secure. Backup Completed @@ -432,6 +438,7 @@ Stay up to date with the latest features and news Seed phrase backup Create Mobile Wallet + Mobile Wallet This information was generated with AI.\nTap here, if you find any errors. To change the access code tap the card or ring as shown above and do not remove until the end of the operation To change the passcode tap the card as shown above and do not remove until the end of the operation @@ -666,7 +673,7 @@ Continue to my wallet Finalize backup Receive crypto - Scan primary card + Scan primary card or ring Skip for later How does it work? Let\'s generate all the keys on your card or ring and create a secure wallet @@ -719,7 +726,7 @@ No backup devices Notifications One backup device added - Prepare your card + Prepare your card or ring Two backup devices added To get started, simply top up the wallet with any amount To get started, simply top up the wallet with more than %1$s %2$s @@ -1360,8 +1367,8 @@ Swap approval is underway and will be completed shortly Approval in progress The minimum swapping amount is %1$s. Please ensure that the remaining balance after the swap will not be less than %2$s. - You do not have any %s exchangeable coins in your list - No available tokens to swap + You don’t have any tokens in your portfolio that %s can be swapped to. Please add another token to enable the exchange. + No compatible tokens added To make a transaction you need to deposit some %1$s %2$s Unable to cover %s fee The amount to receive must be at least %s diff --git a/domain/swap/models/src/main/java/com/tangem/domain/swap/models/SwapDirection.kt b/domain/swap/models/src/main/java/com/tangem/domain/swap/models/SwapDirection.kt index 9c60cc2ec0..4f51ce02f1 100644 --- a/domain/swap/models/src/main/java/com/tangem/domain/swap/models/SwapDirection.kt +++ b/domain/swap/models/src/main/java/com/tangem/domain/swap/models/SwapDirection.kt @@ -13,4 +13,12 @@ import com.tangem.domain.swap.models.SwapDirection.Reverse enum class SwapDirection { Direct, Reverse, + ; + + companion object { + inline fun SwapDirection.withSwapDirection(onDirect: () -> T, onReverse: () -> T): T = when (this) { + Direct -> onDirect() + Reverse -> onReverse() + } + } } \ No newline at end of file diff --git a/features/swap-v2/impl/build.gradle.kts b/features/swap-v2/impl/build.gradle.kts index 817039a569..8c880b498a 100644 --- a/features/swap-v2/impl/build.gradle.kts +++ b/features/swap-v2/impl/build.gradle.kts @@ -34,6 +34,9 @@ dependencies { implementation(projects.common.ui) implementation(projects.common.routing) + /** Libs */ + implementation(projects.libs.crypto) + implementation(tangemDeps.blockchain) { exclude(module = "joda-time") } @@ -54,6 +57,9 @@ dependencies { implementation(projects.domain.transaction.models) implementation(projects.domain.transaction) implementation(projects.domain.legacy) + implementation(projects.domain.balanceHiding.models) + implementation(projects.domain.balanceHiding) + implementation(projects.domain.settings) /** Compose */ implementation(deps.compose.foundation) diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/common/SwapUtils.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/common/SwapUtils.kt new file mode 100644 index 0000000000..01da434698 --- /dev/null +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/common/SwapUtils.kt @@ -0,0 +1,6 @@ +package com.tangem.features.swap.v2.impl.common + +internal object SwapUtils { + const val INCREASE_GAS_LIMIT_FOR_DEX = 112 // 12% + const val INCREASE_GAS_LIMIT_FOR_CEX = 105 // 5% +} \ No newline at end of file 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 new file mode 100644 index 0000000000..7e3af10fa2 --- /dev/null +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/common/entity/ConfirmUM.kt @@ -0,0 +1,24 @@ +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 kotlinx.collections.immutable.ImmutableList + +@Immutable +internal sealed class ConfirmUM { + + abstract val isPrimaryButtonEnabled: Boolean + + data class Content( + override val isPrimaryButtonEnabled: Boolean = false, + val isTransactionInProcess: Boolean, + val showTapHelp: Boolean, + val sendingFooter: TextReference, + val notifications: ImmutableList, + ) : ConfirmUM() + + data object Empty : ConfirmUM() { + override val isPrimaryButtonEnabled: Boolean = false + } +} \ No newline at end of file diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/SendWithSwapRoute.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/SendWithSwapRoute.kt new file mode 100644 index 0000000000..3f0b9b70be --- /dev/null +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/SendWithSwapRoute.kt @@ -0,0 +1,31 @@ +package com.tangem.features.swap.v2.impl.sendviaswap + +import com.tangem.core.decompose.navigation.Route +import com.tangem.features.send.v2.api.subcomponents.destination.DestinationRoute +import com.tangem.features.swap.v2.impl.amount.SwapAmountRoute +import kotlinx.serialization.Serializable + +internal sealed class SendWithSwapRoute : Route { + + abstract val isEditMode: Boolean + + @Serializable + data class Amount( + override val isEditMode: Boolean, + ) : SendWithSwapRoute(), SwapAmountRoute + + @Serializable + data class Destination( + override val isEditMode: Boolean, + ) : SendWithSwapRoute(), DestinationRoute + + @Serializable + data object Confirm : SendWithSwapRoute() { + override val isEditMode: Boolean = false + } + + @Serializable + data object Success : SendWithSwapRoute() { + override val isEditMode: Boolean = false + } +} \ No newline at end of file diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/SendWithSwapConfirmComponent.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/SendWithSwapConfirmComponent.kt new file mode 100644 index 0000000000..c32952b36c --- /dev/null +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/SendWithSwapConfirmComponent.kt @@ -0,0 +1,162 @@ +package com.tangem.features.swap.v2.impl.sendviaswap.confirm + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.context.child +import com.tangem.core.decompose.context.childByContext +import com.tangem.core.decompose.model.getOrCreateModel +import com.tangem.core.ui.decompose.ComposableContentComponent +import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.swap.models.SwapDirection +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.domain.wallets.models.UserWallet +import com.tangem.features.send.v2.api.FeeSelectorBlockComponent +import com.tangem.features.send.v2.api.SendNotificationsComponent +import com.tangem.features.send.v2.api.entity.PredefinedValues +import com.tangem.features.send.v2.api.params.FeeSelectorParams +import com.tangem.features.send.v2.api.subcomponents.destination.SendDestinationBlockComponent +import com.tangem.features.send.v2.api.subcomponents.destination.SendDestinationComponentParams +import com.tangem.features.swap.v2.impl.amount.SwapAmountBlockComponent +import com.tangem.features.swap.v2.impl.amount.SwapAmountComponentParams +import com.tangem.features.swap.v2.impl.common.entity.ConfirmUM +import com.tangem.features.swap.v2.impl.sendviaswap.SendWithSwapRoute +import com.tangem.features.swap.v2.impl.sendviaswap.confirm.model.SendWithSwapConfirmModel +import com.tangem.features.swap.v2.impl.sendviaswap.confirm.ui.SendWithSwapConfirmContent +import com.tangem.features.swap.v2.impl.sendviaswap.entity.SendWithSwapUM +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.coroutines.flow.* +import java.math.BigDecimal + +internal class SendWithSwapConfirmComponent @AssistedInject constructor( + @Assisted private val appComponentContext: AppComponentContext, + @Assisted private val params: Params, + sendDestinationBlockComponent: SendDestinationBlockComponent.Factory, + feeSelectorBlockComponentFactory: FeeSelectorBlockComponent.Factory, + sendNotificationsComponentFactory: SendNotificationsComponent.Factory, +) : ComposableContentComponent, AppComponentContext by appComponentContext { + + private val model: SendWithSwapConfirmModel = getOrCreateModel(params = params) + + private val blockClickEnableFlow = MutableStateFlow(false) + + private val amountBlockComponent = SwapAmountBlockComponent( + appComponentContext = child("sendWithSwapConfirmAmountBlock"), + params = SwapAmountComponentParams.AmountBlockParams( + amountUM = model.uiState.value.amountUM, + analyticsCategoryName = params.analyticsCategoryName, + userWallet = params.userWallet, + blockClickEnableFlow = blockClickEnableFlow.asStateFlow(), + primaryCryptoCurrencyStatusFlow = params.primaryCryptoCurrencyStatusFlow, + secondaryCryptoCurrency = model.secondaryCurrency, + isBalanceHidingFlow = params.isBalanceHidingFlow, + swapDirection = params.swapDirection, + ), + onResult = model::onAmountResult, + onClick = model::showEditAmount, + ) + + private val sendDestinationBlockComponent = sendDestinationBlockComponent.create( + context = child("sendWithSwapConfirmDestinationBlock"), + params = SendDestinationComponentParams.DestinationBlockParams( + state = model.uiState.value.destinationUM, + analyticsCategoryName = params.analyticsCategoryName, + userWalletId = params.userWallet.walletId, + blockClickEnableFlow = blockClickEnableFlow.asStateFlow(), + cryptoCurrency = model.secondaryCurrency, + predefinedValues = PredefinedValues.Empty, + ), + onResult = model::onDestinationResult, + onClick = model::showEditDestination, + ) + + private val feeSelectorBlockComponent = feeSelectorBlockComponentFactory.create( + context = child("sendWithSwapConfirmFeeBlock"), + params = FeeSelectorParams.FeeSelectorBlockParams( + state = model.uiState.value.feeSelectorUM, + onLoadFee = model::loadFee, + feeCryptoCurrencyStatus = model.primaryFeePaidCurrencyStatus, + cryptoCurrencyStatus = model.primaryCurrencyStatus, + suggestedFeeState = FeeSelectorParams.SuggestedFeeState.None, + feeDisplaySource = FeeSelectorParams.FeeDisplaySource.Screen, + ), + onResult = model::onFeeResult, + ) + + private val sendNotificationsComponent = sendNotificationsComponentFactory.create( + context = appComponentContext.childByContext(child("sendWithSwapConfirmNotifications")), + params = SendNotificationsComponent.Params( + analyticsCategoryName = params.analyticsCategoryName, + userWalletId = params.userWallet.walletId, + cryptoCurrencyStatus = model.primaryCurrencyStatus, + feeCryptoCurrencyStatus = model.primaryFeePaidCurrencyStatus, + appCurrency = params.appCurrency, + notificationData = SendNotificationsComponent.Params.NotificationData( + // todo fill with data + destinationAddress = "", + memo = "", + amountValue = BigDecimal.ZERO, + reduceAmountBy = BigDecimal.ZERO, + isIgnoreReduce = false, + fee = null, + feeError = null, + ), + ), + ) + + init { + model.uiState.onEach { state -> + val confirmUM = state.confirmUM as? ConfirmUM.Content + blockClickEnableFlow.value = confirmUM?.isTransactionInProcess == false + }.launchIn(componentScope) + } + + fun updateState(sendWithSwapUM: SendWithSwapUM) { + amountBlockComponent.updateState(sendWithSwapUM.amountUM) + sendDestinationBlockComponent.updateState(sendWithSwapUM.destinationUM) + feeSelectorBlockComponent.updateState(sendWithSwapUM.feeSelectorUM) + model.updateState(sendWithSwapUM) + } + + @Composable + override fun Content(modifier: Modifier) { + val sendWithSwapUM by model.uiState.collectAsStateWithLifecycle() + val sendNotificationsUM by sendNotificationsComponent.state.collectAsStateWithLifecycle() + + SendWithSwapConfirmContent( + sendWithSwapUM = sendWithSwapUM, + amountBlockComponent = amountBlockComponent, + sendDestinationBlockComponent = sendDestinationBlockComponent, + feeSelectorBlockComponent = feeSelectorBlockComponent, + sendNotificationsComponent = sendNotificationsComponent, + sendNotificationsUM = sendNotificationsUM, + modifier = modifier, + ) + } + + data class Params( + val sendWithSwapUM: SendWithSwapUM, + val analyticsCategoryName: String, + val userWallet: UserWallet, + val appCurrency: AppCurrency, + val currentRoute: Flow, + val swapDirection: SwapDirection, + val isBalanceHidingFlow: StateFlow, + val primaryCryptoCurrencyStatusFlow: StateFlow, + val primaryFeePaidCurrencyStatusFlow: StateFlow, + val callback: ModelCallback, + ) + + @AssistedFactory + interface Factory { + fun create(appComponentContext: AppComponentContext, params: Params): SendWithSwapConfirmComponent + } + + interface ModelCallback { + fun onResult(sendWithSwapUM: SendWithSwapUM) + } +} \ No newline at end of file diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/di/SendWithSwapConfirmModule.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/di/SendWithSwapConfirmModule.kt new file mode 100644 index 0000000000..30140080dc --- /dev/null +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/di/SendWithSwapConfirmModule.kt @@ -0,0 +1,20 @@ +package com.tangem.features.swap.v2.impl.sendviaswap.confirm.di + +import com.tangem.core.decompose.di.ModelComponent +import com.tangem.core.decompose.model.Model +import com.tangem.features.swap.v2.impl.sendviaswap.confirm.model.SendWithSwapConfirmModel +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap + +@Module +@InstallIn(ModelComponent::class) +internal interface SendWithSwapConfirmModule { + + @Binds + @IntoMap + @ClassKey(SendWithSwapConfirmModel::class) + fun bindsSendWithSwapConfirmModel(impl: SendWithSwapConfirmModel): Model +} \ No newline at end of file diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/SendWithSwapConfirmModel.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/SendWithSwapConfirmModel.kt new file mode 100644 index 0000000000..d89edaf05f --- /dev/null +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/SendWithSwapConfirmModel.kt @@ -0,0 +1,239 @@ +package com.tangem.features.swap.v2.impl.sendviaswap.confirm.model + +import arrow.core.Either +import arrow.core.getOrElse +import arrow.core.left +import com.tangem.blockchain.common.transaction.TransactionFee +import com.tangem.common.ui.amountScreen.models.AmountState +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.decompose.navigation.Router +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.domain.express.models.ExpressProviderType +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.settings.IsSendTapHelpEnabledUseCase +import com.tangem.domain.swap.models.SwapDirection.Companion.withSwapDirection +import com.tangem.domain.tokens.IsAmountSubtractAvailableUseCase +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.domain.transaction.error.GetFeeError +import com.tangem.domain.transaction.usecase.EstimateFeeUseCase +import com.tangem.features.send.v2.api.SendNotificationsComponent.Params.NotificationData +import com.tangem.features.send.v2.api.callbacks.FeeSelectorModelCallback +import com.tangem.features.send.v2.api.entity.FeeSelectorUM +import com.tangem.features.send.v2.api.subcomponents.destination.entity.DestinationUM +import com.tangem.features.send.v2.api.subcomponents.notifications.SendNotificationsUpdateTrigger +import com.tangem.features.swap.v2.impl.R +import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM +import com.tangem.features.swap.v2.impl.common.SwapUtils.INCREASE_GAS_LIMIT_FOR_CEX +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.SendWithSwapRoute +import com.tangem.features.swap.v2.impl.sendviaswap.confirm.SendWithSwapConfirmComponent +import com.tangem.features.swap.v2.impl.sendviaswap.confirm.model.transformers.SendWithSwapConfirmInitialStateTransformer +import com.tangem.features.swap.v2.impl.sendviaswap.confirm.model.transformers.SendWithSwapConfirmationNotificationsTransformer +import com.tangem.features.swap.v2.impl.sendviaswap.entity.SendWithSwapUM +import com.tangem.lib.crypto.BlockchainFeeUtils.patchTransactionFeeForSwap +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import com.tangem.utils.extensions.orZero +import jakarta.inject.Inject +import kotlinx.coroutines.flow.* +import kotlinx.coroutines.launch +import com.tangem.utils.transformer.update as transformerUpdate + +@Suppress("LongParameterList") +@ModelScoped +internal class SendWithSwapConfirmModel @Inject constructor( + override val dispatchers: CoroutineDispatcherProvider, + private val router: Router, + private val isSendTapHelpEnabledUseCase: IsSendTapHelpEnabledUseCase, + private val estimateFeeUseCase: EstimateFeeUseCase, + private val isAmountSubtractAvailableUseCase: IsAmountSubtractAvailableUseCase, + private val sendNotificationsUpdateTrigger: SendNotificationsUpdateTrigger, + paramsContainer: ParamsContainer, +) : Model(), FeeSelectorModelCallback { + + private val params: SendWithSwapConfirmComponent.Params = paramsContainer.require() + + val uiState: StateFlow + field = MutableStateFlow(params.sendWithSwapUM) + + val primaryCurrencyStatus: CryptoCurrencyStatus = params.primaryCryptoCurrencyStatusFlow.value + val primaryFeePaidCurrencyStatus: CryptoCurrencyStatus = params.primaryFeePaidCurrencyStatusFlow.value + + private val amountUM = uiState.value.amountUM as? SwapAmountUM.Content + + val secondaryCurrency: CryptoCurrency = requireNotNull(amountUM?.secondaryCryptoCurrencyStatus?.currency) { + "Crypto currency must not be null" + } + + private var isAmountSubtractAvailable = false + + init { + initAmountSubtractAvailability() + configConfirmNavigation() + initialState() + } + + override fun onFeeResult(feeSelectorUM: FeeSelectorUM) { + uiState.update { it.copy(feeSelectorUM = feeSelectorUM) } + updateConfirmNotifications() + } + + fun onAmountResult(amountUM: SwapAmountUM) { + uiState.update { it.copy(amountUM = amountUM) } + updateConfirmNotifications() + } + + fun onDestinationResult(destinationUM: DestinationUM) { + uiState.update { it.copy(destinationUM = destinationUM) } + updateConfirmNotifications() + } + + fun updateState(sendWithSwapUM: SendWithSwapUM) { + uiState.value = sendWithSwapUM + } + + fun showEditAmount() { + router.push(SendWithSwapRoute.Amount(isEditMode = true)) + } + + fun showEditDestination() { + router.push(SendWithSwapRoute.Destination(isEditMode = true)) + } + + suspend fun loadFee(): Either { + val defaultError = GetFeeError.UnknownError.left() + val quote = amountUM?.selectedQuote as? SwapQuoteUM.Content ?: return defaultError + val amountUM = uiState.value.amountUM as? SwapAmountUM.Content ?: return defaultError + + val amountField = amountUM.swapDirection.withSwapDirection( + onDirect = { amountUM.primaryAmount.amountField }, + onReverse = { amountUM.secondaryAmount.amountField }, + ) as? AmountState.Data ?: return defaultError + val amountValue = amountField.amountTextField.cryptoAmount.value ?: return defaultError + + return when (val providerType = quote.provider.type) { + ExpressProviderType.CEX -> { + estimateFeeUseCase( + amount = amountValue, + userWallet = params.userWallet, + cryptoCurrency = primaryCurrencyStatus.currency, + ).map { + it.patchTransactionFeeForSwap(INCREASE_GAS_LIMIT_FOR_CEX) + } + } + ExpressProviderType.DEX, + ExpressProviderType.DEX_BRIDGE, + -> { + // todo send with swap + GetFeeError.UnknownError.left() + } + ExpressProviderType.ONRAMP, + -> GetFeeError.DataError( + cause = IllegalStateException("Provider $providerType is not supported in Send With Swap"), + ).left() + } + } + + private fun onSendClick() { + // todo swap send tx + } + + private fun initAmountSubtractAvailability() { + modelScope.launch { + isAmountSubtractAvailable = + isAmountSubtractAvailableUseCase( + params.userWallet.walletId, + primaryCurrencyStatus.currency, + ).getOrElse { false } + } + } + + private fun initialState() { + val confirmUM = uiState.value.confirmUM + + modelScope.launch { + val isShowTapHelp = isSendTapHelpEnabledUseCase().getOrElse { false } + if (confirmUM is ConfirmUM.Empty) { + uiState.update { + it.copy( + confirmUM = SendWithSwapConfirmInitialStateTransformer( + isShowTapHelp = isShowTapHelp, + ).transform(uiState.value.confirmUM), + ) + } + updateConfirmNotifications() + } + } + } + + private fun updateConfirmNotifications() { + val amountUM = uiState.value.amountUM as? SwapAmountUM.Content ?: return + val destinationUM = uiState.value.destinationUM as? DestinationUM.Content ?: return + val feeSelectorUMContent = uiState.value.feeSelectorUM as? FeeSelectorUM.Content + val feeSelectorUMError = uiState.value.feeSelectorUM as? FeeSelectorUM.Error + + val amountField = amountUM.swapDirection.withSwapDirection( + onDirect = { amountUM.primaryAmount.amountField }, + onReverse = { amountUM.secondaryAmount.amountField }, + ) as? AmountState.Data ?: return + val enteredDestination = destinationUM.addressTextField.actualAddress + + modelScope.launch { + sendNotificationsUpdateTrigger.triggerUpdate( + data = NotificationData( + destinationAddress = enteredDestination, + memo = null, + amountValue = amountField.amountTextField.cryptoAmount.value.orZero(), + reduceAmountBy = amountField.reduceAmountBy.orZero(), + isIgnoreReduce = amountField.isIgnoreReduce, + fee = feeSelectorUMContent?.selectedFeeItem?.fee, + feeError = feeSelectorUMError?.error, + ), + ) + uiState.transformerUpdate( + SendWithSwapConfirmationNotificationsTransformer(), + ) + } + } + + private fun configConfirmNavigation() { + combine( + flow = uiState, + flow2 = params.currentRoute, + transform = { state, route -> state to route }, + ).filter { + it.second is SendWithSwapRoute.Confirm + }.onEach { (state, _) -> + val confirmUM = state.confirmUM + params.callback.onResult( + state.copy( + navigationUM = NavigationUM.Content( + title = resourceReference(id = R.string.send_with_swap_confirm_title), + subtitle = null, + backIconRes = R.drawable.ic_back_24, + backIconClick = router::pop, + primaryButton = NavigationButton( + textReference = resourceReference(R.string.common_send), + iconRes = R.drawable.ic_tangem_24, + isEnabled = confirmUM.isPrimaryButtonEnabled, + onClick = { + when (confirmUM) { + is ConfirmUM.Content -> if (confirmUM.isTransactionInProcess) { + return@NavigationButton + } else { + onSendClick() + } + else -> return@NavigationButton + } + }, + ), + ), + ), + ) + }.launchIn(modelScope) + } +} \ No newline at end of file diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/transformers/SendWithSwapConfirmInitialStateTransformer.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/transformers/SendWithSwapConfirmInitialStateTransformer.kt new file mode 100644 index 0000000000..4be5c0b56a --- /dev/null +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/transformers/SendWithSwapConfirmInitialStateTransformer.kt @@ -0,0 +1,20 @@ +package com.tangem.features.swap.v2.impl.sendviaswap.confirm.model.transformers + +import com.tangem.core.ui.extensions.TextReference +import com.tangem.features.swap.v2.impl.common.entity.ConfirmUM +import com.tangem.utils.transformer.Transformer +import kotlinx.collections.immutable.persistentListOf + +internal class SendWithSwapConfirmInitialStateTransformer( + private val isShowTapHelp: Boolean, +) : Transformer { + override fun transform(prevState: ConfirmUM): ConfirmUM { + return ConfirmUM.Content( + isPrimaryButtonEnabled = false, + isTransactionInProcess = false, + showTapHelp = isShowTapHelp, + sendingFooter = TextReference.EMPTY, + notifications = persistentListOf(), + ) + } +} \ No newline at end of file diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/transformers/SendWithSwapConfirmationNotificationsTransformer.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/transformers/SendWithSwapConfirmationNotificationsTransformer.kt new file mode 100644 index 0000000000..014e57650d --- /dev/null +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/transformers/SendWithSwapConfirmationNotificationsTransformer.kt @@ -0,0 +1,106 @@ +package com.tangem.features.swap.v2.impl.sendviaswap.confirm.model.transformers + +import com.tangem.blockchain.common.transaction.Fee +import com.tangem.common.ui.amountScreen.models.AmountState +import com.tangem.common.ui.notifications.NotificationUM +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.wrappedList +import com.tangem.core.ui.format.bigdecimal.fiat +import com.tangem.core.ui.format.bigdecimal.format +import com.tangem.domain.swap.models.SwapDirection.Companion.withSwapDirection +import com.tangem.features.send.v2.api.entity.FeeSelectorUM +import com.tangem.features.send.v2.api.subcomponents.feeSelector.utils.FeeCalculationUtils.checkIfCustomFeeTooHigh +import com.tangem.features.send.v2.api.subcomponents.feeSelector.utils.FeeCalculationUtils.checkIfCustomFeeTooLow +import com.tangem.features.swap.v2.impl.R +import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM +import com.tangem.features.swap.v2.impl.common.entity.ConfirmUM +import com.tangem.features.swap.v2.impl.sendviaswap.entity.SendWithSwapUM +import com.tangem.utils.transformer.Transformer +import kotlinx.collections.immutable.toPersistentList + +internal class SendWithSwapConfirmationNotificationsTransformer : Transformer { + override fun transform(prevState: SendWithSwapUM): SendWithSwapUM { + val confirmUM = prevState.confirmUM as? ConfirmUM.Content ?: return prevState + val feeSelectorUM = prevState.feeSelectorUM as? FeeSelectorUM.Content ?: return prevState + + return prevState.copy( + confirmUM = confirmUM.copy( + sendingFooter = getSendingFooterText(feeSelectorUM, prevState.amountUM), + notifications = buildList { + addTooHighNotification(feeSelectorUM = feeSelectorUM) + addTooLowNotification(feeSelectorUM = feeSelectorUM) + }.toPersistentList(), + ), + ) + } + + private fun MutableList.addTooLowNotification(feeSelectorUM: FeeSelectorUM.Content) { + if (checkIfCustomFeeTooLow(feeSelectorUM = feeSelectorUM)) { + add(NotificationUM.Warning.FeeTooLow) + } + } + + private fun MutableList.addTooHighNotification(feeSelectorUM: FeeSelectorUM.Content) { + val (isFeeTooHigh, diff) = checkIfCustomFeeTooHigh(feeSelectorUM = feeSelectorUM) + if (isFeeTooHigh) { + add(NotificationUM.Warning.TooHigh(diff)) + } + } + + private fun getSendingFooterText(feeSelectorUM: FeeSelectorUM.Content, swapAmountUM: SwapAmountUM): TextReference { + val amountUM = swapAmountUM.swapDirection.withSwapDirection( + onDirect = { swapAmountUM.primaryAmount.amountField }, + onReverse = { swapAmountUM.secondaryAmount.amountField }, + ) as? AmountState.Data + val feeItem = feeSelectorUM.selectedFeeItem + val feeFiatRateUM = feeSelectorUM.feeFiatRateUM + + val appCurrency = feeFiatRateUM?.appCurrency + + if (amountUM == null || appCurrency == null) return TextReference.EMPTY + + val fiatAmountValue = amountUM.amountTextField.fiatAmount.value + val fiatFeeValue = feeItem.fee.amount.value?.multiply(feeFiatRateUM.rate) + + val fiatSendingValue = if (feeSelectorUM.feeExtraInfo.isFeeConvertibleToFiat) { + fiatFeeValue?.let { fiatAmountValue?.plus(it) } + } else { + fiatAmountValue + } + + val fiatSending = fiatSendingValue.format { + fiat( + fiatCurrencyCode = appCurrency.code, + fiatCurrencySymbol = appCurrency.symbol, + ) + } + val fiatFee = TextReference.EMPTY + // todo send with swap footer + // formatFooterFiatFee( + // amount = feeItem.fee.amount.copy(value = fiatFeeValue), + // isFeeConvertibleToFiat = feeSelectorUM.feeExtraInfo.isFeeConvertibleToFiat, + // isFeeApproximate = feeSelectorUM.feeExtraInfo.isFeeApproximate, + // appCurrency = appCurrency, + // ) + + return if (feeSelectorUM.feeExtraInfo.isTronToken && feeItem.fee is Fee.Tron) { + // todo send with swap footer + // getTronTokenFeeSendingText( + // fee = feeItem.fee, + // fiatFee = fiatFee, + // fiatSending = stringReference(fiatSending), + // ) + TextReference.EMPTY + } else { + resourceReference( + id = if (feeSelectorUM.feeExtraInfo.isFeeConvertibleToFiat) { + R.string.send_summary_transaction_description + } else { + R.string.send_summary_transaction_description_no_fiat_fee + }, + formatArgs = wrappedList(fiatSending, fiatFee), + ) + } + } +} \ No newline at end of file diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/ui/SendWithSwapConfirmContent.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/ui/SendWithSwapConfirmContent.kt new file mode 100644 index 0000000000..4386a93cb6 --- /dev/null +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/ui/SendWithSwapConfirmContent.kt @@ -0,0 +1,72 @@ +package com.tangem.features.swap.v2.impl.sendviaswap.confirm.ui + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.unit.dp +import com.tangem.common.ui.notifications.NotificationUM +import com.tangem.core.ui.components.SpacerHMax +import com.tangem.features.send.v2.api.FeeSelectorBlockComponent +import com.tangem.features.send.v2.api.SendNotificationsComponent +import com.tangem.features.send.v2.api.subcomponents.destination.SendDestinationBlockComponent +import com.tangem.features.swap.v2.impl.amount.SwapAmountBlockComponent +import com.tangem.features.swap.v2.impl.common.entity.ConfirmUM +import com.tangem.features.swap.v2.impl.sendviaswap.entity.SendWithSwapUM +import kotlinx.collections.immutable.ImmutableList + +@Suppress("LongParameterList") +@Composable +internal fun SendWithSwapConfirmContent( + sendWithSwapUM: SendWithSwapUM, + amountBlockComponent: SwapAmountBlockComponent, + sendDestinationBlockComponent: SendDestinationBlockComponent, + feeSelectorBlockComponent: FeeSelectorBlockComponent, + sendNotificationsComponent: SendNotificationsComponent, + sendNotificationsUM: ImmutableList, + modifier: Modifier = Modifier, +) { + val confirmUM = sendWithSwapUM.confirmUM as? ConfirmUM.Content + + Column(modifier = modifier) { + LazyColumn( + verticalArrangement = Arrangement.spacedBy(8.dp), + modifier = Modifier.padding(horizontal = 12.dp), + ) { + item(key = "SendWithSwapAmountBlock") { + amountBlockComponent.Content(Modifier) + } + item(key = "SendWithSwapDestinationBlock") { + sendDestinationBlockComponent.Content(Modifier) + } + item(key = "SendWithSwapFeeBLock") { + Box( + modifier = Modifier.clip(RoundedCornerShape(16.dp)), + ) { + feeSelectorBlockComponent.Content(Modifier) + } + } + if (confirmUM != null) { + // tapHelp(isDisplay = confirmUM.showTapHelp) // todo + with(sendNotificationsComponent) { + content( + state = sendNotificationsUM, + isClickDisabled = confirmUM.isTransactionInProcess, + ) + } + // notifications( + // notifications = confirmUM.notifications, + // isClickDisabled = confirmUM.isTransactionInProcess, + // ) + } + } + SpacerHMax() + // todo + // SendingText(footerText = confirmUM?.sendingFooter ?: TextReference.EMPTY) + } +} \ No newline at end of file diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/entity/SendWithSwapUM.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/entity/SendWithSwapUM.kt new file mode 100644 index 0000000000..1a576345a6 --- /dev/null +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/entity/SendWithSwapUM.kt @@ -0,0 +1,15 @@ +package com.tangem.features.swap.v2.impl.sendviaswap.entity + +import com.tangem.common.ui.navigationButtons.NavigationUM +import com.tangem.features.send.v2.api.entity.FeeSelectorUM +import com.tangem.features.send.v2.api.subcomponents.destination.entity.DestinationUM +import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM +import com.tangem.features.swap.v2.impl.common.entity.ConfirmUM + +internal data class SendWithSwapUM( + val amountUM: SwapAmountUM, + val destinationUM: DestinationUM, + val feeSelectorUM: FeeSelectorUM, + val confirmUM: ConfirmUM, + val navigationUM: NavigationUM, +) \ No newline at end of file diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainFeeUtils.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainFeeUtils.kt new file mode 100644 index 0000000000..30f9d06686 --- /dev/null +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainFeeUtils.kt @@ -0,0 +1,81 @@ +package com.tangem.lib.crypto + +import com.tangem.blockchain.common.transaction.Fee +import com.tangem.blockchain.common.transaction.TransactionFee +import java.math.BigInteger +import java.math.RoundingMode + +/** + * !!!IMPORTANT!!! + * Methods for tuning transaction fee of different blockchains + * + * Temporary solution for domain specific logic for Blockchain. + * Instead of creating repositories and unnecessary and overkill use cases + */ +object BlockchainFeeUtils { + + private val HUNDRED_PERCENT = BigInteger("100") + + /** + * We need to increase gasLimit for Ethereum fees for 2 cases + * + * DEX: for dex calculated gasLimit for given data might be changed when transaction processing + * for that case dex providers recommend to increase gasLimit for few percents to ensure transaction completes + * + * CEX: for that case we calculate fee for random generated address and gasLimit might be different for it + * and result address to send. That's why we should increase gasLimit a little + * + */ + fun TransactionFee.patchTransactionFeeForSwap(increaseBy: Int): TransactionFee { + return when (this) { + is TransactionFee.Choosable -> { + this.copy( + minimum = this.minimum.increaseEthGasLimitInNeeded(increaseBy), + normal = this.normal.increaseEthGasLimitInNeeded(increaseBy), + priority = this.priority.increaseEthGasLimitInNeeded(increaseBy), + ) + } + is TransactionFee.Single -> this.copy(normal = this.normal.increaseEthGasLimitInNeeded(increaseBy)) + } + } + + private fun Fee.increaseEthGasLimitInNeeded(increaseBy: Int): Fee { + return when (this) { + is Fee.Ethereum.EIP1559, + is Fee.Ethereum.Legacy, + -> this.increaseGasLimitBy(increaseBy) + is Fee.Alephium, + is Fee.Aptos, + is Fee.Bitcoin, + is Fee.CardanoToken, + is Fee.Common, + is Fee.Filecoin, + is Fee.Hedera, + is Fee.Kaspa, + is Fee.Sui, + is Fee.Tron, + is Fee.VeChain, + -> this + } + } + + /** + * Increase gasLimit for Fee.Ethereum + */ + private fun Fee.increaseGasLimitBy(percentage: Int): Fee { + if (this !is Fee.Ethereum) return this + val gasLimit = this.gasLimit + val increasedGasPrice = this.amount.value?.movePointRight(this.amount.decimals) + ?.divide(gasLimit.toBigDecimal(), RoundingMode.HALF_UP) + val increasedGasLimit = gasLimit + .multiply(percentage.toBigInteger()) + .divide(HUNDRED_PERCENT) + val increasedAmount = this.amount.copy( + value = increasedGasLimit.toBigDecimal().multiply(increasedGasPrice).movePointLeft(this.amount.decimals), + ) + return when (this) { + is Fee.Ethereum.EIP1559 -> copy(amount = increasedAmount, gasLimit = increasedGasLimit) + is Fee.Ethereum.Legacy -> copy(amount = increasedAmount, gasLimit = increasedGasLimit) + } + } +} \ No newline at end of file