From feef15fc31a0e4e38297becc5deeeaab5c6e7821 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 20 Jun 2025 18:24:46 +0300 Subject: [PATCH 01/19] Updated on 2026-08-14 --- core/res/src/main/res/values-de/strings.xml | 33 +++++++++-- core/res/src/main/res/values-es/strings.xml | 56 ++++++++++++++++++ core/res/src/main/res/values-fr/strings.xml | 42 +++++++++++++ core/res/src/main/res/values-ja/strings.xml | 25 ++++++-- core/res/src/main/res/values-ru/strings.xml | 24 ++++++-- .../src/main/res/values-uk-rUA/strings.xml | 59 +++++++++++++++++++ core/res/src/main/res/values/strings.xml | 24 +++++--- 7 files changed, 239 insertions(+), 24 deletions(-) diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index 7dccd4ed94..24c3cc5827 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -289,6 +289,7 @@ Kann nicht gegen %s ausgetauscht werden Bereitgestellt von Status + Anbieter auswählen Anbieter erleichtern Transaktionen Anbieter Es ist ein Fehler aufgetreten. Code: %s @@ -349,6 +350,8 @@ Diese Transaktion ausblenden Sobald der Transaktionsstatus ausgeblendet ist, kann er nicht mehr angezeigt werden. Stattdessen kannst Du ihn durch einfaches Wischen schließen. Diese Transaktion ausblenden + Dieses Token wird nicht unterstützt. Bitte wähle zum Tauschen ein anderes Token. + %s wird nicht unterstützt Tauschen mit Keine Token gefunden. Bitte versuche eine andere Anfrage ID: %s @@ -377,6 +380,8 @@ Um fortzufahren, erteile %1s Smart Contracts die Berechtigung, dein zu %2s verwenden. Erlaubnis erteilen Unbegrenzt + Vorhandene Wallet hinzufügen + Neues Wallet erstellen Karte oder Ring bestellen Karte oder Ring scannen An %s @@ -568,6 +573,8 @@ Du hast dieses Netzwerk noch nicht hinzugefügt. Um NFTs zu erhalten, füge es Deinem Portfolio hinzu. Netzwerk nicht hinzugefügt Nicht hinzugefügt + Nicht unterstützte NFT-Arten + cNFTs und pNFTs werden noch nicht unterstützt NFT senden Eigenschaften Unbetitelte Sammlung @@ -823,6 +830,9 @@ Gebühr bis zu Ungültiges Memo Abdeckung der Netzgebühren + Nonce + Eindeutige Nummer für jede Transaktion. Verwende diese Nummer, um eine ausstehende Transaktion erneut zu senden oder abzubrechen. + Nonce eingeben… Unzureichende Mittel für die Überweisung, da die Summe aus Gebühr und Überweisungsbetrag das bestehende Guthaben übersteigt Gesamtbetrag übersteigt den Saldo Ein Guthaben von mindestens %s ist erforderlich, um dein Konto in der Blockchain aktiv zu halten und Sicherheitsrisiken zu vermeiden. Dieser Betrag verbleibt auf deinem Guthaben und kann nicht abgehoben werden. @@ -850,8 +860,12 @@ Empfänger Keine gültige Adresse Vergewisser dich, dass die empfangende Wallet-Adresse im %s Netzwerk ist, damit du deine Token nicht verlierst. + senden an %s + Stelle sicher, dass Du %s Netzwerkadresse hast, da Fehler zu verlorenen Übertragungen führen können Senden an Ein Memo/Destination Tag ist eine eindeutige ID zur Unterscheidung von Transaktionen, die an denselben Empfänger im selben Netzwerk gesendet werden. Vorsicht! Das Weglassen eines Vermerks kann dazu führen, dass Gelder für immer verloren sind! + Memo / Destination Tag ist ein Code, der Transaktionen an einen gemeinsamen Empfänger im Kryptonetzwerk voneinander trennt. + Achtung: Das Fehlen eines Memos kann zum Verlust von Geldern führen. Meine Wallet Eine Möglichkeit, Bitcoin-Transaktionsgebühren zu messen. Du gibst die Anzahl der kleinsten Bitcoin-Einheiten (Satoshi) für jedes virtuelle Byte in einer Transaktion an. Je höher die Zahl, desto schneller wird die Transaktion von den Minern verarbeitet. Satoshi / vByte @@ -872,11 +886,14 @@ Ungültiger Betrag Die Gebühr geht über die Bilanz hinaus Der Gesamtbetrag geht über die Bilanz hinaus + Sende uns ein Token, und wir konvertieren es unterwegs. Dein Empfänger erhält genau das, was er braucht – nahtlos. + Senden mit Swap Transaktion gesendet Bereite das Scannen der Karte oder Ring vor, die du einrichten möchtest. Entferne diese Wallet Hiermit wird die Wallet aus der Anwendung entfernt. Die Wallet selbst kann wieder hinzugefügt werden. Name + Lass deinen Token für dich arbeiten Die Anzahl der zu stakenden Krypros muss mindesten %s betragen Der Stakingbetrag wird aufgrund der Netzwerkregeln auf %1$s TRX aufgerundet. Der Betrag der unstaked wird, wird aufgrund der Netzwerkregeln auf %1$s TRX gerundet. @@ -1011,6 +1028,7 @@ Web 3.0-kompatibel Zum Fortfahren ist eine eingehende Transaktion von mindestens %1$s erforderlich Unzureichende Mittel + Fester Zinssatz Das Netzwerk erhebt eine Token-Genehmigungsgebühr, um zu überprüfen, ob Sie die Verwendung Ihres Tokens für den Swap genehmigen. Tausche mehr Token zu besseren Kursen direkt in deiner Brieftasche. Neuer Swap-Anbieter verfügbar! @@ -1260,7 +1278,7 @@ Deine Bewertung motiviert uns, die Tangem Wallet noch besser zu machen. Gefällt dir Tangem? Du musst deinen Token zuordnen, bevor du Token erhalten kannst - Du musst die trustline öffnen, bevor Du Token erhältst + Du musst die Trustline für Deinen Token öffnen, bevor Du diesen erhalten kannst. Netzmietgebühr erforderlich Handlungsbedarf Hast Du innerhalb von 7 Tagen nach der Erstellung Ihres Wallets über die App Kontakt zum Support aufgenommen? Wenn ja oder wenn Du Dir nicht sicher bist, klicke auf „Ja“ und folge den Anweisungen. @@ -1276,13 +1294,13 @@ Wischen Sie nach unten, um zu aktualisieren, oder versuchen Sie es später erneut. Einige Netzwerke sind nicht erreichbar Einige Token-Guthaben konnten nicht aktualisiert werden - Erforderliche Mindest-XLM-Reserve %1$s - Trustline öffnen - Trustline-Untermenü öffnen - Trustline-Titel öffnen Dies ist eine Testnet-Karte. Sie kann keine Transaktionen verarbeiten und sollte nur zu Test- und Entwicklungszwecken verwendet werden. Nur für Testzwecke Der Kontostand ist möglicherweise veraltet. Aktualisiere bittre die Seite. + Nicht genug %1$s. Lade Dein XLM-Konto auf, um dieses Token zu verknüpfen + Trustline aktivieren + Um dieses Token zu erhalten, muss eine Trustline aktiviert sein. Das Netzwerk benötigt eine Reserve von %1$s %2$s. + Trustline erforderlich Unbekannte Domäne Trotzdem verbinden Zeitüberschreitungsfehler. Bitte versuche es später erneut. @@ -1290,7 +1308,9 @@ Diese Domäne kann nicht verifiziert werden. Überprüfe die Anfrage sorgfältig und bestätige diese dann. Bitte kehre zu Deinem Browser zurück und stellen die Verbindung über WalletConnect erneut her. WalletConnect-Sitzung wurde getrennt + Trotzdem unterschreiben Fehlercode: %s. Wenn das Problem weiterhin besteht, wende Dich bitte an unseren Support. + Wenn das Problem weiterhin besteht, wende Dich bitte an unseren Support. Wir haben einen unbekannten Fehler festgestellt. Fehlercode: 8 005. Wenn das Problem weiterhin besteht, wende Dich bitte an unseren Support. Wir haben einen unbekannten Fehler festgestellt. @@ -1303,8 +1323,10 @@ Erlaubnis auszugeben Vertragsadresse Verbinden + Laden Netzwerk Netzwerke + Unbegrenzt Wallet Verbundene Netzwerke Anzeige des Kontostands und der Aktivitäten in Deiner Wallet @@ -1321,6 +1343,7 @@ Text über die Trennung aller dApps Alle dApps trennen Geschätzte Wallet-Änderungen + Die Transaktion konnte nicht simuliert werden. Bitte sei vorsichtig. Böswillige/ gefährliche Transaktion Füge Deinem Profil für dieses Wallet das Netzwerk %s hinzu Die Wallet verfügt über keine erforderlichen Netzwerke diff --git a/core/res/src/main/res/values-es/strings.xml b/core/res/src/main/res/values-es/strings.xml index 0421d01d9e..bc9fd8a0e8 100644 --- a/core/res/src/main/res/values-es/strings.xml +++ b/core/res/src/main/res/values-es/strings.xml @@ -162,6 +162,7 @@ Tarifa de la red La cantidad enviada se reducirá en %1$s (%2$s) para cubrir el nivel de tarifa seleccionado Siguiente + NFT No Ninguna dirección Ahora @@ -184,6 +185,7 @@ Buscar Buscar tokens sec + Ver todo Seed phrase Seleccione una acción Vender @@ -514,6 +516,56 @@ Tire hacia arriba o toque la barra de búsqueda para agregar tokens directamente desde el mercado Agregar tokens NFC no está disponible en su dispositivo + Acerca de NFT + Activo NFT + + %d elemento + %d elementos + + Los NFTs enviados a la dirección de su billetera aparecerán aquí. + No hay colecciones todavía + Recibir NFT + Colecciones de NFT + Es posible que algunos datos no se carguen + Problemas temporales de carga + Información de base + Cadena + Dirección del contrato + Cadena es la blockchain en la que está el NFT. + La dirección del contrato es un identificador único para el contrato inteligente que gobierna los tokens en la blockchain + Una etiqueta que describe lo raro que es el NFT. Cuanto menor sea el valor, más único será el NFT. + La posición de un NFT en el ranking de rareza entre otros tokens. Cuanto más alto sea el ranking, más raro y valioso será el NFT. + La dirección del contrato es un identificador único para el contrato inteligente que gobierna los tokens en la blockchain + El ID del token es un identificador único asignado a cada token, que lo distingue de otros en la colección. + El estándar de token define qué tipo de token es y cómo funciona con diferentes billeteras y plataformas + Último precio de venta + Etiqueta de rareza + Ranking de rareza + Dirección del token + Token ID + Estándar de token + Rasgos + Sin resultados. Por favor, intente otra solicitud. + Sin colección + Disponible + Elegir red + Recibir NFT + Todavía no ha agregado esta red. Para recibir NFTs, añádala a su portafolio. + Red no agregada + No aregada + Tipos de NFT no compatibles + cNFT y pNFT aún no son compatibles. Por favor, no los envíes a tu billetera. + Enviar NFT + Rasgos + Colección sin título + %1$d NFT en %2$d colecciones + + + %1$d NFTs en %2$d colecciones + + Pulse aquí para recibir el primer NFT + Colecciones de NFT + No se pueden cargar los datos Para utilizar la red %1$s, debe pagar la reserva de cuenta (%2$s%3$s), que bloquea y oculta ese monto indefinidamente La cuenta de destino no está activa. Envíe %s o más para activar la cuenta. Para crear una cuenta, envíe fondos a esta dirección @@ -652,6 +704,7 @@ Acceso a la cámara denegado No se requiere nota %1$s (%2$s) en la red %3$s + %1$s en la red %2$s El envío de cualquier otra moneda supondrá una pérdida irreversible. Envíe solo %s a esta dirección. Enviar cualquier otra moneda resultará en su pérdida irreversible. Envíe solo %1$s en la red %2$s @@ -779,6 +832,8 @@ Asegúrese de que la dirección de la billetera receptora esté en la red %s para evitar perder sus tokens Destinatario Un Memo/ID de destino es un ID único para diferenciar transacciones enviadas al mismo destinatario en la misma red. Precaución: Omitir un memo puede llevar a la pérdida de fondos + Memo / Destination Tag es un código que separa las transacciones dirigidas a un destinatario compartido en una red de criptomonedas. + Atención: si falta el Memo, можно потерять средства. Mis billeteras Una forma de medir las tarifas de transacción de Bitcoin. Indique el número de la unidad más pequeña de Bitcoin (Satoshi) por cada byte virtual en una transacción. Cuanto más alto sea el número, más rápido procesarán los mineros la transacción. Satoshi / vByte @@ -804,6 +859,7 @@ Olvidar la billetera Esto eliminará la wallet de la aplicación. La wallet en sí puede\nañadirse de nuevo. Nombre + Haz que tu token trabaje por ti El monto del staking debe ser al menos %s El monto del staking se redondeará a %1$s TRX debido a las reglas de la red. El monto de cancelación del staking se redondeará a %1$s TRX debido a las reglas de la red. diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml index 6e8c8d6142..5bd4e39551 100644 --- a/core/res/src/main/res/values-fr/strings.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -162,6 +162,7 @@ Commissions du réseau Le montant envoyé sera réduit de %1$s(%2$s) pour couvrir le niveau de frais sélectionné Suivant + NFT Non Aucune adresse Maintenant @@ -184,6 +185,7 @@ Rechercher Rechercher des jetons sec + Voir tout Seed phrase Sélectionnez une action Vendre @@ -515,12 +517,48 @@ Tirez vers le haut ou appuyez sur la barre de recherche pour ajouter des jetons directement depuis le marché Ajouter des jetons NFC n\'est pas disponible sur votre appareil + À propos des NFT + Actif NFT + + %d article + %d articles + Les NFT envoyés à l\'adresse de votre portefeuille s\'afficheront ici. Aucune collection pour le moment Recevoir des NFT Collections NFT Certaines données peuvent ne pas se charger Problèmes de chargement temporaires + Informations de base + Chaîne + Adresse du contrat + Chain est la blockchain où existe le NFT. + L\'adresse du contrat est un identifiant unique pour le contrat intelligent qui régit les jetons sur la blockchain + Une étiquette décrivant la rareté du NFT. Plus la valeur est faible, plus le NFT est unique. + Position d\'un NFT dans le classement de rareté des autres jetons. Plus le rang est élevé, plus le NFT est rare et précieux. + L\'adresse du jeton est un identifiant unique pour le jeton sur la blockchain, permettant le suivi des transactions et de la propriété + L\'ID de jeton est un identifiant unique attribué à chaque jeton, le distinguant des autres dans la collection + La norme de jeton définit de quel type de jeton il s\'agit et comment il fonctionne avec différents portefeuilles et plateformes + Dernier prix de vente + Étiquette de rareté + Rang de rareté + Adresse du jeton + ID du jeton + Norme de jeton + Caractéristiques + Aucun résultat. Veuillez réessayer. + Aucune collection + Disponible + Choisir le réseau + Recevoir des NFT + Vous n\'avez pas encore ajouté ce réseau. Pour recevoir des NFT, ajoutez-le à votre portefeuille. + Réseau non ajouté + Non ajouté + Types de NFT non pris en charge + Les cNFT et les pNFT ne sont pas encore pris en charge + Envoyer un NFT + Caractéristiques + Collection sans titre %1$d NFT dans la collection %2$d @@ -667,6 +705,7 @@ Accès à la caméra refusé Aucun mémo requis %1$s (%2$s) sur le réseau %3$s + %1$s sur le réseau %2$s L’envoi de toute autre devise entraînera sa perte irréversible. Envoyez uniquement %s à cette adresse. L\'envoi de toute autre devise entraînera sa perte irréversible. Envoyez uniquement %1$s sur le réseau %2$s @@ -794,6 +833,8 @@ Assurez-vous que l\'adresse du portefeuille de réception est sur le réseau %s pour éviter de perdre vos jetons 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. + Attention : l\'absence de Memo peut entraîner une perte de fonds. Mes portefeuilles Un moyen de mesurer les frais de transaction Bitcoin. Il indique le nombre de la plus petite unité de Bitcoin (Satoshi) pour chaque octet virtuel dans une transaction. Plus le nombre est élevé, plus la transaction sera traitée rapidement par les mineurs. Satoshi / vByte @@ -819,6 +860,7 @@ Oublier le portefeuille Cela supprimera le portefeuille de l\'application. Le portefeuille lui-même peut être ajouté à nouveau. Nom + Faites travailler votre jeton pour vous Le montant à staker doit être au moins %s Le montant du staking sera arrondi à %1$s TRX en raison des règles du réseau. Le montant d\'annulation du staking sera arrondi à %1$s TRX en raison des règles du réseau. diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml index f3c8df4619..722f628412 100644 --- a/core/res/src/main/res/values-ja/strings.xml +++ b/core/res/src/main/res/values-ja/strings.xml @@ -285,6 +285,7 @@ %sとの交換はできません 提供元 ステータス + プロバイダーを選択 プロバイダーが取引を促進します プロバイダー エラーが発生しました。コード: %s @@ -375,6 +376,7 @@ 続行するには、%1sスマートコントラクトに%2sを使用する権限を付与してください 許可を与える 無制限 + 既存のウォレットを追加 Tangemを注文 Tangemをスキャン %sへ @@ -562,6 +564,8 @@ このネットワークはまだ追加されていません。NFTを受け取るには、ポートフォリオに追加してください。 ネットワークが追加されていません 未追加 + サポートされていないNFTタイプ + cNFTとpNFTはまだサポートされていません NFTを送信する 特徴 無題のコレクション @@ -812,6 +816,9 @@ 最大手数料 無効なメモ ネットワーク手数料のカバー + ナンス + 各取引に固有の番号です。保留中の取引を再送信またはキャンセルするために使用します。 + ナンスを入力してください... 手数料と送金額の合計が残高を超えているため、送金に必要な資金が不足しています。 合計が残高を超えています セキュリティリスクを防ぐため、ブロックチェーン上にアカウントを維持するには、少なくとも%s の残高が必要です。この金額は残高に残り、引き出すことはできません。 @@ -839,8 +846,12 @@ 受取人 有効なアドレスではありません トークンを失わないように、受信ウォレットアドレスが%sネットワーク上にあることを確認してください。 + %s に送信 + %sネットワークアドレスを確認してください。エラーが発生すると転送が失われる可能性があります。 送金先 メモ/宛先タグは、同じネットワーク上の同じ受信者に送信された取引を区別するためのユニークなIDです。注意:メモを省略すると、資金の行き違いにつながる可能性があります。 + メモ / 宛先タグは、暗号資産ネットワーク内の共有受信者への取引を分離するコードです。 + 注意:メモを忘れると資金が失われる可能性があります。 マイウォレット Bitcoinの取引手数料を測定する方法。取引における各仮想バイトの最小Bitcoin単位(Satoshi)の数を示します。数値が高いほど、マイナーによる取引の処理速度が速くなります。 Satoshi / vByte @@ -868,7 +879,7 @@ ウォレット削除 これにより、ウォレットがアプリから削除されます。ウォレットは再度追加できます。 名前 - このトークンのステーキングに参加 + トークンを活用しよう ステーキング金額は %s 以上である必要があります ネットワークルールにより、ステーキング金額は%1$s TRX に切り上げられます。 ネットワークルールにより、ステーキング解除の量は%1$s TRX に切り上げられます。 @@ -1247,7 +1258,7 @@ あなたのレビューは、Tangemウォレットをさらに良くするためのモチベーションになります Tangemを楽しんでいますか? トークンを受け取る前に、トークンを関連付ける必要があります。 - トークンを受け取る前に、トラストラインを開く必要があります + トークンを受け取る前に、トラストラインを開く必要があります ネットワーク使用料が必要です 対応が必要です ウォレットの作成後、7日以内にアプリ経由でサポートに連絡しましたか?連絡した場合、あるいは連絡したかわからない場合は、「はい」をクリックして指示に従ってください。 @@ -1263,13 +1274,13 @@ 下にスワイプして更新するか、後でもう一度お試しください。 一部のネットワークにアクセスできません 一部のトークン残高を更新できませんでした - %1$sが足りません。このトークンを関連付けるには、XLMアカウントにチャージしてください。 - トラストラインを有効にする - ネットワークルールに従い、このトークンを入金する前にトラストラインを開設する必要があります。これにより、ウォレットが資産を受け入れ、保管できるようになります。 - このトークンのトラストラインを有効にする これはテストネットカードです。取引処理はできませんので、テストおよび開発目的でのみご利用ください。 テスト目的のみ 残高が古い可能性があります。ページを更新してください。 + %1$sが足りません。このトークンを関連付けるには、XLMアカウントにチャージしてください。 + トラストラインを有効にする + このトークンを受け取るには、トラストラインを有効にする必要があります。ネットワークには%1$s %2$s予備金が必要です。 + トラストラインが必要 不明なドメイン とにかく接続する タイムアウトエラーが発生しました。しばらくしてからもう一度お試しください。 @@ -1277,7 +1288,9 @@ このドメインは検証できません。承認前にリクエスト内容をよく確認してください。 ブラウザに戻り、WalletConnect経由で再接続してください。 WalletConnectセッションが接続解除されました + とにかくサインする エラーコード: %s 。問題が解決しない場合は、お気軽にサポートまでお問い合わせください。 + 問題が解決しない場合は、お気軽にサポートまでお問い合わせください。 不明なエラーが発生しました エラーコード: 8 005。問題が解決しない場合は、お気軽にサポートまでお問い合わせください。 不明なエラーが発生しました diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 132232d93f..6be2e1fb65 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -370,6 +370,8 @@ Чтобы продолжить, вам нужно разрешить смарт-контракту %1s использовать ваш %2s Дать разрешение Безлимитно + Добавить существующий кошелек + Создать новый кошелек Купить Сканировать в %s @@ -568,14 +570,17 @@ Вы ещё не добавили эту сеть. Чтобы получать NFT, добавьте её в ваш портфель. Сеть не добавлена Не добавлено + Неподдерживаемый тип NFT + cNFT и pNFT не поддерживаются. Пожалуйста, не отправляйте их на свой кошелёк. Отправить NFT Черты + Безымянная коллекция %1$d NFTs в %2$d коллекциях - - + %1$d NFT в %2$d коллекции + %1$d NFT в %2$d коллекциях - %1$d NFTs в %2$d коллекциях + %1$d NFT в %2$d коллекциях Нажмите здесь, чтобы получить первую NFT NFT коллекции @@ -724,6 +729,7 @@ Доступ к камере запрещен Memo не требуется %1$s (%2$s) в сети %3$s + %1$s в %2$s сети Использование другой сети может привести к утрате средств. Отправляйте только %s на этот адрес. Использование другой сети может привести к утрате средств. Отправляйте только %1$s в сети %2$s @@ -826,6 +832,9 @@ Комиссия не превысит Недопустимый Memo Покрытие сетевой комиссии + Nonce + Уникальный номер транзакции. Измените, чтобы ускорить отправку или отменить зависшую операцию. + Введите nonce… Недостаточно средств для перевода, так как сумма комиссии и сумма перевода в совокупности больше имеющегося баланса Недостаточно средств Для сохранения вашего аккаунта в блокчейне и защиты от возможных рисков необходим баланс не менее %s. Эта сумма останется на вашем счете и не может быть снята. @@ -855,6 +864,8 @@ Убедитесь, что вы отправляете средства на адрес кошелька %s. Ошибки могут привести к потере ваших токенов. Отправить Мемо/ Код назначения - это код, разделяющий транзакции к общему получателю в сети криптовалют. Внимание: отсутствие мемо может привести к потере средств. + Memo / Destination Tag — это код, разделяющий транзакции, отправляемые одному и тому же получателю в криптовалютной сети. + Внимание: отсутствие Memo может привести к потере средств. Мои кошельки Способ измерения комиссии за биткоин-транзакцию. Он указывает на количество самой маленькой единицы биткоина (сатоши) за каждый виртуальный байт в транзакции. Чем выше число, тем быстрее будет обработана транзакция майнерами. Сатоши / вбайт @@ -879,6 +890,7 @@ Забыть кошелек Это приведет к удалению кошелька из приложения. Сам кошелек можно добавить снова. Имя + Заставьте ваш токен работать Сумма для стейкинга должна быть не менее %s Согласно правилам сети, сумма стейкинга будет округлена до %1$sTRX. Сумма для вывода из стейкинга будет округлена до %1$s TRX ввиду особенностей сети. @@ -1247,12 +1259,12 @@ Свайпните вниз для обновления или попробуйте позже. Некоторые сети недоступны Некоторые балансы токенов не удалось обновить - Открыть Trustline - Согласно правилам сети, чтобы пополнить этот токен, необходимо сначала открыть Trustline — это позволит вашему кошельку принимать и хранить этот актив. - Откройте Trustline Это Testnet карта. Он не может обрабатывать транзакции и используется только в целях тестирования и разработки. Только для целей тестирования Возможно, баланс устарел. Обновите страницу. + Открыть Trustline + Согласно правилам сети, чтобы пополнить этот токен, необходимо сначала открыть Trustline — это позволит вашему кошельку принимать и хранить этот актив. + Откройте Trustline Пожалуйста, вернитесь в браузер и выполните повторное подключение через WalletConnect. Выбрана не верная карта или кольцо Адрес 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 a6ec71a7c8..314b79ecef 100644 --- a/core/res/src/main/res/values-uk-rUA/strings.xml +++ b/core/res/src/main/res/values-uk-rUA/strings.xml @@ -168,6 +168,7 @@ Комісія мережі Сума відправлення буде зменшена на %1$s (%2$s) для покриття обраного рівня комісії Далі + NFT Ні Немає адреси Зараз @@ -190,6 +191,7 @@ Пошук Пошук токенів сек + Переглянути все Seed-фраза Оберіть дію Продати @@ -527,6 +529,59 @@ Потягніть вгору або торкніться панелі пошуку, щоб додати токени безпосередньо з маркету Додати токени Функція NFC недоступна на вашому пристрої + Про NFT + NFT + + %d NFT + %d NFTs + %d NFTs + %d NFTs + + Тут відображатимуться NFT, отримані на ваш гаманець + У вас ще немає колекцій + Отримати NFT + NFT колекції + Частина даних може не відобразитися + Не вдалося завантажити дані + Базова інформація + Мережа + Адреса контракту + Мережа — це блокчейн, на якому існує NFT. + Адреса контракту – це унікальний код, який ідентифікує смарт-контракт, керуючий токенами в блокчейні + Мітка, яка описує, наскільки рідкісним є NFT. Чим нижче значення, тим унікальніше + Позиція NFT у рейтингу рідкісності серед інших токенів. Чим вище ранг, тим рідкісніше і цінніше NFT. + Адреса токена — це унікальний ідентифікатор токена в блокчейні, що дозволяє відстежувати транзакції та володіння. + ID токена – це унікальний ідентифікатор, який присвоюється кожному токену, що відрізняє його від інших у колекції + Стандарт токена визначає, який це тип токена і як він працює з різними гаманцями та платформами + Остання ціна продажу + Мітка унікальності + Ранг рідкості + Адреса токену + ID токену + Стандарт токена + Риси + Немає результатів. Будь ласка, спробуйте інший запит. + Без колекції + Доступно + Оберіть мережу + Отримати NFT + Ви ще не додали цю мережу. Щоб отримувати NFT, додайте її до свого портфоліо. + Мережу не додано + Не додано + Непідтримувані типи NFT + cNFT і pNFT наразі не підтримуються. Будь ласка, не надсилайте їх на свій гаманець. + Надіслати NFT + Риси + NFTs в %2$d колекціях%1$d + + + + + %1$d NFTs в %2$d колекціях + + Натисніть тут, щоб отримати першу NFT + NFT колекції + Неможливо завантажити дані Щоб користуватися мережею %1$s, ви маєте оплатити резерв акаунта (%2$s %3$s), який блокується і не використовується у вашому балансі Обліковий запис одержувача не активовано. Надішліть %s або більше, щоб активувати обліковий запис. Для створення акаунту надішліть кошти на цю адресу @@ -671,6 +726,7 @@ Доступ до камери заборонено Memo не вимагається %1$s (%2$s) у мережі %3$s + %1$s у мережі %2$s Використання іншої мережі може призвести до втрати коштів. Надсилайте лише %s на цю адресу. Надсилання будь-якої іншої валюти призведе до її незворотної втрати. Надсилайте лише %1$s у мережі %2$s @@ -802,6 +858,8 @@ Переконайтеся, що адреса гаманця одержувача знаходиться в мережі %s, щоб уникнути втрати ваших токенів Надіслати до Memo/ Тег призначення — це унікальний ідентифікатор для розрізнення транзакцій, надісланих тому самому одержувачу в тій самій мережі. Застереження: відсутність тегу може призвести до втрати коштів. + Мемо / Destination Tag — це код, що розділяє транзакції до спільного одержувача в криптовалютній мережі. + Увага: відсутність Memo може призвести до втрати коштів. Мої гаманці Спосіб виміру комісії за біткоїн-транзакцію. Він свідчить про кількість найменшої одиниці біткоїна (сатоші) за кожен віртуальний байт у транзакції. Чим вище число, тим швидше буде оброблено транзакцію майнерами. Сатоші / вбайт @@ -827,6 +885,7 @@ Забути гаманець Це призведе до видалення гаманця з застосунку. Сам гаманець можна додати знову. Ім\'я + Змусьте свій токен працювати Сума для стейкінгу має бути не менше %s Сума стейкінгу буде округлена до %1$s TRX відповідно до правил мережі. Сума зняття зі стейкінгу буде округлена до %1$s TRX через мережеві правила. diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 11d55684c1..59973b51ac 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -381,6 +381,8 @@ To continue, grant %1s smart contracts permission to use your %2s Give Permission Unlimited + Add Existing Wallet + Create New Wallet Order Tangem Scan Tangem to %s @@ -573,7 +575,7 @@ Network not added Not Added Unsupported NFT types - cNFTs and pNFTs are not supported yet. Please don’t send them to your wallet. + cNFTs and pNFTs are not supported yet. Please don\'t send them to your wallet. Send NFT Traits Untitled collection @@ -829,6 +831,9 @@ Fee up to Invalid Memo Network fee coverage + Nonce + Unique number for each transaction. Use it to resend or cancel a pending transaction. + Enter nonce… Insufficient funds for the transfer, as the total of the fee and transfer amount exceeds the existing balance Total exceeds balance A balance of at least %s is required to keep your account on the blockchain to prevent security risks. This amount will remain in your balance and cannot be withdrawn. @@ -856,8 +861,12 @@ Recipient Not a valid address Ensure the receiving wallet address is on the %s network to avoid losing your tokens + send to %s + Ensure you %s network address, as errors may result in lost transfers Send to A Memo/Destination Tag is a unique ID for differentiating transactions sent to the same recipient on the same network. Caution: Omitting a memo may lead to misplaced funds + Memo / Destination Tag is a code separating transactions to a shared recipient in a crypto network. + Caution: Missing a memo may lead to fund loss. My wallets A way of measuring Bitcoin transaction fees. It indicates the number of the smallest Bitcoin unit (Satoshi) for each virtual byte in a transaction. The higher the number, the faster the transaction will be processed by miners. Satoshi / vByte @@ -885,7 +894,7 @@ Forget wallet This will remove the wallet from the application. The wallet itself can be added again. Name - Join staking with this token + Put your token to work The amount to stake must be at least %s Staking amount will be rounded to %1$s TRX due to network rules. Unstaking amount will be rounded to %1$s TRX due to network rules. @@ -1314,7 +1323,7 @@ Your review keeps us motivated to make Tangem Wallet even better Enjoying Tangem? You must associate your token before receiving tokens - You must open trustline for your token before receiving it + You must open trustline for your token before receiving it Network rent fee required Action required Did you contact support via the app within 7 days of creating your wallet? If yes, or if you\'re unsure, click \'Yes\' and follow the instructions. @@ -1330,13 +1339,13 @@ Swipe down to refresh or try again later. Some networks are unreachable Some token balances could not be updated - Not enough %1$s. Top up your XLM account to associate this token - Enable Trustline - According to network rules, you need to open a Trustline before depositing this token. It allows your wallet to accept and hold the asset. - Enable Trustline for this token 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. + Not enough %1$s. Top up your XLM account to associate this token + Enable Trustline + A Trustline must be enabled to receive this token. The network requires a %1$s %2$s reserve. + Trustline Required Unknown domain Connect anyway Timeout error. Please, try again later. @@ -1346,6 +1355,7 @@ WalletConnect session was disconnected Sign anyway Error code: %s. If the problem persists — feel free to contact our support. + If the problem persists — feel free to contact our support. We\'ve encountered unknown error Error code: 8 005. If the problem persists — feel free to contact our support. We\'ve encountered unknown error From fc8c6a8fb83732d7a641e899a647996e22512e39 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 23 Jun 2025 19:16:07 +0400 Subject: [PATCH 02/19] Updated on 2026-08-14 --- gradle/tangem_dependencies.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/tangem_dependencies.toml b/gradle/tangem_dependencies.toml index 8cd2d641c9..00fa6c50e3 100644 --- a/gradle/tangem_dependencies.toml +++ b/gradle/tangem_dependencies.toml @@ -7,7 +7,7 @@ tangemBlockchainSdk = "releases-5.25.0-1083" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds -tangemCardSdk = "releases-5.25.0-483" +tangemCardSdk = "releases-5.25.0-486" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ tangemVico = "2.0.0-alpha.25-tangem12" #tangemVico = "0.0.1" # Keep it! - used for local builds ^ From c1469cb09bb05c221e0e91b36cced1a8d3a44489 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 20 Jun 2025 14:07:58 +0300 Subject: [PATCH 03/19] Updated on 2026-08-14 --- .../staking/impl/presentation/model/StakingModel.kt | 9 +++++++-- .../presentation/state/helpers/StakingBalanceUpdater.kt | 8 +------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingModel.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingModel.kt index 82db22b95a..2454768e9c 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingModel.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingModel.kt @@ -165,6 +165,7 @@ internal class StakingModel @Inject constructor( } private var isInitialInfoAnalyticSent: Boolean = false + private var isBalanceUpdatedAfterStart: Boolean = false private val balanceUpdater by lazy(LazyThreadSafetyMode.NONE) { stakingBalanceUpdater.create( @@ -421,7 +422,7 @@ internal class StakingModel @Inject constructor( override fun onRefreshSwipe(isRefreshing: Boolean) { stateController.update(SetInitialLoadingStateTransformer(isRefreshing)) coroutineScope.launch { - balanceUpdater.updatePullToRefresh() + balanceUpdater.partialUpdate() }.invokeOnCompletion { stateController.update(SetInitialLoadingStateTransformer(false)) } @@ -1011,7 +1012,11 @@ internal class StakingModel @Inject constructor( when { isInitState() -> { updateInitialData(status) - balanceUpdater.updateAfterNavigationToInitial() + + if (!isBalanceUpdatedAfterStart) { + isBalanceUpdatedAfterStart = true + balanceUpdater.partialUpdate() + } } isAssentState() -> { getFee() diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingBalanceUpdater.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingBalanceUpdater.kt index 22e12b38e9..0bb0d545b7 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingBalanceUpdater.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingBalanceUpdater.kt @@ -51,7 +51,7 @@ internal class StakingBalanceUpdater @AssistedInject constructor( } } - suspend fun updatePullToRefresh() { + suspend fun partialUpdate() { coroutineScope { listOf( async { @@ -64,12 +64,6 @@ internal class StakingBalanceUpdater @AssistedInject constructor( } } - suspend fun updateAfterNavigationToInitial() { - coroutineScope { - async { updateStakingActions() }.await() - } - } - private suspend fun fetchCurrencyStatus(delayMillis: Long = 0L) { delay(delayMillis) fetchCurrencyStatusUseCase( From c694e7430af83ee65cded1fad95e974c1521eed8 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 25 Jun 2025 17:50:02 +0900 Subject: [PATCH 04/19] Updated on 2026-08-14 --- gradle/tangem_dependencies.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/tangem_dependencies.toml b/gradle/tangem_dependencies.toml index 00fa6c50e3..2bf98649b5 100644 --- a/gradle/tangem_dependencies.toml +++ b/gradle/tangem_dependencies.toml @@ -5,7 +5,7 @@ # https://github.com/tangem/tangem-sdk-android/ # https://github.com/tangem/vico -tangemBlockchainSdk = "releases-5.25.0-1083" +tangemBlockchainSdk = "releases-5.25.0-1089" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "releases-5.25.0-486" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From d309c0130446c67a74db4cacd9c243bfdf1bda36 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 25 Jun 2025 14:24:14 +0300 Subject: [PATCH 05/19] Updated on 2026-08-14 --- gradle/tangem_dependencies.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/tangem_dependencies.toml b/gradle/tangem_dependencies.toml index 2bf98649b5..f462f6d6d4 100644 --- a/gradle/tangem_dependencies.toml +++ b/gradle/tangem_dependencies.toml @@ -5,7 +5,7 @@ # https://github.com/tangem/tangem-sdk-android/ # https://github.com/tangem/vico -tangemBlockchainSdk = "releases-5.25.0-1089" +tangemBlockchainSdk = "releases-5.25.0-1091" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "releases-5.25.0-486" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From 73832d3e8e0dcded4b5c6dc20ca349b90c576820 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 25 Jun 2025 21:46:03 +0300 Subject: [PATCH 06/19] Updated on 2026-08-14 --- gradle/tangem_dependencies.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/tangem_dependencies.toml b/gradle/tangem_dependencies.toml index f462f6d6d4..793a9a0c14 100644 --- a/gradle/tangem_dependencies.toml +++ b/gradle/tangem_dependencies.toml @@ -5,7 +5,7 @@ # https://github.com/tangem/tangem-sdk-android/ # https://github.com/tangem/vico -tangemBlockchainSdk = "releases-5.25.0-1091" +tangemBlockchainSdk = "releases-5.25.0-1092" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "releases-5.25.0-486" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From 867d9fd73526294bc5d1bc5457facc703136bbb8 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 26 Jun 2025 13:22:19 +0400 Subject: [PATCH 07/19] Updated on 2026-08-14 --- .../exchangeServices/DefaultRampManager.kt | 10 ++-- .../fetcher/CommonNetworkStatusFetcher.kt | 2 + .../domain/exchange/RampStateManager.kt | 7 ++- .../tokens/FetchCurrencyStatusUseCase.kt | 60 +++++++++---------- .../tokens/GetCryptoCurrencyActionsUseCase.kt | 5 +- .../tokens/GetNetworkCoinStatusUseCase.kt | 16 +++-- .../BaseCurrencyStatusOperations.kt | 22 ++++--- .../CachedCurrenciesStatusesOperations.kt | 12 ---- .../tokenlist/model/OnrampTokenListModel.kt | 11 ++-- 9 files changed, 66 insertions(+), 79 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt index 32256ffa87..ce11ee65f0 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt @@ -18,6 +18,7 @@ import com.tangem.domain.tokens.GetNetworkCoinStatusUseCase import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason import com.tangem.domain.tokens.repository.CurrenciesRepository +import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId import com.tangem.features.onramp.OnrampFeatureToggles import com.tangem.utils.Provider @@ -55,7 +56,7 @@ internal class DefaultRampManager( } override suspend fun availableForSell( - userWalletId: UserWalletId, + userWallet: UserWallet, status: CryptoCurrencyStatus, ): Either { return either { @@ -68,7 +69,7 @@ internal class DefaultRampManager( catch = { raise(ScenarioUnavailabilityReason.NotSupportedBySellService(status.currency.name)) }, ) - val reason = getSendUnavailabilityReason(userWalletId, status) + val reason = getSendUnavailabilityReason(userWallet = userWallet, cryptoCurrencyStatus = status) ensure(condition = reason is ScenarioUnavailabilityReason.None) { when (reason) { @@ -205,14 +206,13 @@ internal class DefaultRampManager( } private suspend fun getSendUnavailabilityReason( - userWalletId: UserWalletId, + userWallet: UserWallet, cryptoCurrencyStatus: CryptoCurrencyStatus, ): ScenarioUnavailabilityReason { val coinStatus = getNetworkCoinStatusUseCase.invokeSync( - userWalletId = userWalletId, + userWallet = userWallet, networkId = cryptoCurrencyStatus.currency.network.id, derivationPath = cryptoCurrencyStatus.currency.network.derivationPath, - isSingleWalletWithTokens = false, ).getOrNull() return when { diff --git a/data/networks/src/main/java/com/tangem/data/networks/fetcher/CommonNetworkStatusFetcher.kt b/data/networks/src/main/java/com/tangem/data/networks/fetcher/CommonNetworkStatusFetcher.kt index deb556fc61..59ee027076 100644 --- a/data/networks/src/main/java/com/tangem/data/networks/fetcher/CommonNetworkStatusFetcher.kt +++ b/data/networks/src/main/java/com/tangem/data/networks/fetcher/CommonNetworkStatusFetcher.kt @@ -14,6 +14,7 @@ import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.withContext import timber.log.Timber import javax.inject.Inject +import javax.inject.Singleton /** * Common implementation of network status fetcher @@ -24,6 +25,7 @@ import javax.inject.Inject * [REDACTED_AUTHOR] */ +@Singleton internal class CommonNetworkStatusFetcher @Inject constructor( private val walletManagersFacade: WalletManagersFacade, private val networksStatusesStore: NetworksStatusesStore, diff --git a/domain/legacy/src/main/java/com/tangem/domain/exchange/RampStateManager.kt b/domain/legacy/src/main/java/com/tangem/domain/exchange/RampStateManager.kt index 0f10ddbc84..ece1e97c5b 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/exchange/RampStateManager.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/exchange/RampStateManager.kt @@ -6,6 +6,7 @@ import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason +import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId import kotlinx.coroutines.flow.Flow @@ -23,11 +24,11 @@ interface RampStateManager { /** * Check if [CryptoCurrency] is available for sell * - * @param userWalletId id of multi-currency wallet - * @param status crypto currency status + * @param userWallet user wallet + * @param status crypto currency status */ suspend fun availableForSell( - userWalletId: UserWalletId, + userWallet: UserWallet, status: CryptoCurrencyStatus, ): Either diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/FetchCurrencyStatusUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/FetchCurrencyStatusUseCase.kt index 5a6a9fa889..8fea6820d1 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/FetchCurrencyStatusUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/FetchCurrencyStatusUseCase.kt @@ -4,6 +4,7 @@ import arrow.core.Either import arrow.core.raise.Raise import arrow.core.raise.catch import arrow.core.raise.either +import arrow.core.right import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.network.Network import com.tangem.domain.networks.single.SingleNetworkStatusFetcher @@ -51,17 +52,18 @@ class FetchCurrencyStatusUseCase( return either { val currency = getCurrency(userWalletId, id) - coroutineScope { + return@either coroutineScope { val fetchStatus = async { - fetchNetworkStatus(userWalletId, currency.network) - } - val fetchQuote = async { - fetchQuote(currency.id) + fetchNetworkStatus(userWalletId = userWalletId, network = currency.network) } + + val fetchQuote = async { fetchQuote(currencyId = currency.id) } + val fetchStakingBalance = async { - fetchStakingBalance(userWalletId, currency, refresh) + fetchStakingBalance(userWalletId = userWalletId, cryptoCurrency = currency, refresh = refresh) } - awaitAll(fetchStatus, fetchQuote, fetchStakingBalance) + + awaitAll(fetchStatus, fetchQuote, fetchStakingBalance).summarizeResult() } } } @@ -80,14 +82,14 @@ class FetchCurrencyStatusUseCase( return either { val currency = getPrimaryCurrency(userWalletId, refresh) - coroutineScope { + return@either coroutineScope { val fetchStatus = async { - fetchNetworkStatus(userWalletId, currency.network) + fetchNetworkStatus(userWalletId = userWalletId, network = currency.network) } - val fetchQuote = async { - fetchQuote(currency.id) - } - awaitAll(fetchStatus, fetchQuote) + + val fetchQuote = async { fetchQuote(currencyId = currency.id) } + + awaitAll(fetchStatus, fetchQuote).summarizeResult() } } } @@ -97,9 +99,7 @@ class FetchCurrencyStatusUseCase( id: CryptoCurrency.ID, ): CryptoCurrency { return catch( - block = { - currenciesRepository.getMultiCurrencyWalletCurrency(userWalletId, id) - }, + block = { currenciesRepository.getMultiCurrencyWalletCurrency(userWalletId = userWalletId, id = id) }, ) { raise(CurrencyStatusError.DataError(it)) } @@ -114,31 +114,27 @@ class FetchCurrencyStatusUseCase( } } - private suspend fun Raise.fetchNetworkStatus(userWalletId: UserWalletId, network: Network) { - singleNetworkStatusFetcher( + private suspend fun fetchNetworkStatus(userWalletId: UserWalletId, network: Network): Either { + return singleNetworkStatusFetcher( params = SingleNetworkStatusFetcher.Params(userWalletId = userWalletId, network = network), ) - .mapLeft { CurrencyStatusError.DataError(it) } - .bind() } - private suspend fun Raise.fetchQuote(currencyId: CryptoCurrency.ID) { - multiQuoteFetcher( + private suspend fun fetchQuote(currencyId: CryptoCurrency.ID): Either { + return multiQuoteFetcher( params = MultiQuoteFetcher.Params( currenciesIds = setOfNotNull(currencyId.rawCurrencyId), appCurrencyId = null, ), ) - .mapLeft { CurrencyStatusError.DataError(it) } - .bind() } - private suspend fun Raise.fetchStakingBalance( + private suspend fun fetchStakingBalance( userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency, refresh: Boolean, - ) { - if (tokensFeatureToggles.isStakingLoadingRefactoringEnabled) { + ): Either { + return if (tokensFeatureToggles.isStakingLoadingRefactoringEnabled) { singleYieldBalanceFetcher( params = SingleYieldBalanceFetcher.Params( userWalletId = userWalletId, @@ -147,11 +143,11 @@ class FetchCurrencyStatusUseCase( ), ) } else { - catch( - block = { stakingRepository.fetchSingleYieldBalance(userWalletId, cryptoCurrency, refresh) }, - ) { - raise(CurrencyStatusError.DataError(it)) - } + Either.catch { stakingRepository.fetchSingleYieldBalance(userWalletId, cryptoCurrency, refresh) } } } + + private fun List>.summarizeResult(): Either { + return firstOrNull { it.isLeft() } ?: Unit.right() + } } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt index 77f16153ff..a6d2f178bb 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt @@ -194,10 +194,7 @@ class GetCryptoCurrencyActionsUseCase( } // region sell - rampManager.availableForSell( - userWalletId = userWallet.walletId, - status = cryptoCurrencyStatus, - ) + rampManager.availableForSell(userWallet = userWallet, status = cryptoCurrencyStatus) .onRight { activeList.add(TokenActionsState.ActionState.Sell(ScenarioUnavailabilityReason.None)) } diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetNetworkCoinStatusUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetNetworkCoinStatusUseCase.kt index 6a1fab3d6b..4ecbcaebcb 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetNetworkCoinStatusUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetNetworkCoinStatusUseCase.kt @@ -1,13 +1,16 @@ package com.tangem.domain.tokens import arrow.core.Either +import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.models.network.Network import com.tangem.domain.tokens.error.CurrencyStatusError import com.tangem.domain.tokens.error.mapper.mapToCurrencyError import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.operations.BaseCurrencyStatusOperations import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations +import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.domain.wallets.models.requireColdWallet import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.flow.* @@ -36,16 +39,21 @@ class GetNetworkCoinStatusUseCase( } suspend fun invokeSync( - userWalletId: UserWalletId, + userWallet: UserWallet, networkId: Network.ID, derivationPath: Network.DerivationPath, - isSingleWalletWithTokens: Boolean, ): Either { - val maybeCurrency = if (isSingleWalletWithTokens) { + val userWalletId = userWallet.walletId + val cardTypesResolver = userWallet.requireColdWallet().cardTypesResolver // TODO [REDACTED_TASK_KEY] + + val maybeCurrency = if (userWallet.isMultiCurrency) { + currencyStatusOperations.getNetworkCoinSync(userWalletId, networkId, derivationPath) + } else if (cardTypesResolver.isSingleWalletWithToken()) { currencyStatusOperations.getNetworkCoinForSingleWalletWithTokenSync(userWalletId, networkId) } else { - currencyStatusOperations.getNetworkCoinSync(userWalletId, networkId, derivationPath) + currencyStatusOperations.getPrimaryCurrencyStatusSync(userWalletId) } + return maybeCurrency.mapLeft(CurrenciesStatusesOperations.Error::mapToCurrencyError) } diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/BaseCurrencyStatusOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/BaseCurrencyStatusOperations.kt index 6e8d810651..e24d551194 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/BaseCurrencyStatusOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/BaseCurrencyStatusOperations.kt @@ -54,11 +54,6 @@ abstract class BaseCurrencyStatusOperations( protected abstract fun getQuotes(id: CryptoCurrency.RawID): Flow>> - protected abstract fun getNetworksStatuses( - userWalletId: UserWalletId, - network: Network, - ): EitherFlow> - protected abstract suspend fun fetchComponents( userWalletId: UserWalletId, networks: Set, @@ -106,13 +101,7 @@ abstract class BaseCurrencyStatusOperations( flow { emit(Error.EmptyQuotes.left()) } } - val statusFlow = getNetworksStatuses(userWalletId = userWalletId, network = currency.network) - .map { maybeStatuses -> - maybeStatuses.flatMap { statuses -> - statuses.singleOrNull { it.network == currency.network }?.right() - ?: Error.EmptyNetworksStatuses.left() - } - } + val statusFlow = getNetworkStatus(userWalletId = userWalletId, network = currency.network) val yieldBalanceFlow = getYieldBalance(userWalletId = userWalletId, cryptoCurrency = currency) @@ -349,6 +338,15 @@ abstract class BaseCurrencyStatusOperations( .onEmpty { emit(Error.EmptyYieldBalances.left()) } } + private fun getNetworkStatus(userWalletId: UserWalletId, network: Network): EitherFlow { + return singleNetworkStatusSupplier( + params = SingleNetworkStatusProducer.Params(userWalletId = userWalletId, network = network), + ) + .map>(NetworkStatus::right) + .distinctUntilChanged() + .onEmpty { emit(Error.EmptyNetworksStatuses.left()) } + } + private suspend fun Raise.getNetworkCoin( userWalletId: UserWalletId, networkId: Network.ID, diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CachedCurrenciesStatusesOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CachedCurrenciesStatusesOperations.kt index d29b49e8be..bf69424d0c 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CachedCurrenciesStatusesOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CachedCurrenciesStatusesOperations.kt @@ -303,18 +303,6 @@ class CachedCurrenciesStatusesOperations( .distinctUntilChanged() } - override fun getNetworksStatuses( - userWalletId: UserWalletId, - network: Network, - ): EitherFlow> { - return singleNetworkStatusSupplier( - params = SingleNetworkStatusProducer.Params(userWalletId = userWalletId, network = network), - ) - .map>> { setOf(it).right() } - .distinctUntilChanged() - .onEmpty { emit(Error.EmptyNetworksStatuses.left()) } - } - private fun getYieldBalances( userWalletId: UserWalletId, cryptoCurrencies: List, diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt index 7086e3f10c..2fb53e1132 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt @@ -59,9 +59,9 @@ internal class OnrampTokenListModel @Inject constructor( val state: StateFlow = tokenListUMController.state private val params: OnrampTokenListComponent.Params = paramsContainer.require() - private val scanResponse by lazy { + private val userWallet by lazy { getWalletsUseCase.invokeSync().first { it.walletId == params.userWalletId } - .requireColdWallet().scanResponse // TODO [REDACTED_TASK_KEY] + .requireColdWallet() // TODO [REDACTED_TASK_KEY] } init { @@ -229,16 +229,13 @@ internal class OnrampTokenListModel @Inject constructor( return when (params.filterOperation) { OnrampOperation.BUY -> { rampStateManager.availableForBuy( - scanResponse = scanResponse, + scanResponse = userWallet.scanResponse, userWalletId = params.userWalletId, cryptoCurrency = status.currency, ).isAvailable() } OnrampOperation.SELL -> { - rampStateManager.availableForSell( - userWalletId = params.userWalletId, - status = status, - ).isRight() + rampStateManager.availableForSell(userWallet = userWallet, status = status).isRight() } OnrampOperation.SWAP -> { val isAvailable = rampStateManager.availableForSwap( From c72294500d56030c3287b2de01f1f61ea06965d7 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 26 Jun 2025 15:41:52 +0300 Subject: [PATCH 08/19] Updated on 2026-08-14 --- .../features/send/v2/send/model/SendModel.kt | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/model/SendModel.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/model/SendModel.kt index 9e82a35a76..6c4430e910 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/model/SendModel.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/model/SendModel.kt @@ -275,14 +275,18 @@ internal class SendModel @Inject constructor( isSingleWalletWithToken: Boolean, isMultiCurrency: Boolean, ): Flow> { - return if (isMultiCurrency) { - getSingleCryptoCurrencyStatusUseCase.invokeMultiWallet( + return when { + isSingleWalletWithToken -> getSingleCryptoCurrencyStatusUseCase.invokeMultiWallet( userWalletId = userWalletId, currencyId = cryptoCurrency.id, - isSingleWalletWithTokens = isSingleWalletWithToken, + isSingleWalletWithTokens = true, ) - } else { - getSingleCryptoCurrencyStatusUseCase.invokeSingleWallet(userWalletId = userWalletId) + isMultiCurrency -> getSingleCryptoCurrencyStatusUseCase.invokeMultiWallet( + userWalletId = userWalletId, + currencyId = cryptoCurrency.id, + isSingleWalletWithTokens = false, + ) + else -> getSingleCryptoCurrencyStatusUseCase.invokeSingleWallet(userWalletId = userWalletId) } } From 2bbded1c2909a507df8d16cf41adb23325644487 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 26 Jun 2025 15:55:57 +0300 Subject: [PATCH 09/19] Updated on 2026-08-14 --- gradle/tangem_dependencies.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/tangem_dependencies.toml b/gradle/tangem_dependencies.toml index 793a9a0c14..800aec4569 100644 --- a/gradle/tangem_dependencies.toml +++ b/gradle/tangem_dependencies.toml @@ -5,7 +5,7 @@ # https://github.com/tangem/tangem-sdk-android/ # https://github.com/tangem/vico -tangemBlockchainSdk = "releases-5.25.0-1092" +tangemBlockchainSdk = "releases-5.25.0-1094" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "releases-5.25.0-486" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From e2a6af1ccb508491d9c526e2e92d026ba75fc79e Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 26 Jun 2025 18:11:51 +0400 Subject: [PATCH 10/19] Updated on 2026-08-14 --- .../staking/multi/DefaultMultiYieldBalanceFetcher.kt | 2 +- .../presentation/state/helpers/StakingBalanceUpdater.kt | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/data/staking/src/main/java/com/tangem/data/staking/multi/DefaultMultiYieldBalanceFetcher.kt b/data/staking/src/main/java/com/tangem/data/staking/multi/DefaultMultiYieldBalanceFetcher.kt index 8019b9fcc8..8c21c90072 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/multi/DefaultMultiYieldBalanceFetcher.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/multi/DefaultMultiYieldBalanceFetcher.kt @@ -165,7 +165,7 @@ internal class DefaultMultiYieldBalanceFetcher @Inject constructor( val yieldBalances = coroutineScope { requests // TODO: in the future, consider optimizing this part - .chunked(size = 16) // StakeKitApi limitation: no more than 16 requests at the same time + .chunked(size = 15) // StakeKitApi limitation: no more than 15 requests at the same time .map { async(dispatchers.io) { stakeKitApi.getMultipleYieldBalances(it).bind() } } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingBalanceUpdater.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingBalanceUpdater.kt index 0bb0d545b7..e6c32aa82c 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingBalanceUpdater.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingBalanceUpdater.kt @@ -1,6 +1,7 @@ package com.tangem.features.staking.impl.presentation.state.helpers import com.tangem.domain.staking.FetchActionsUseCase +import com.tangem.domain.staking.FetchStakingYieldBalanceUseCase import com.tangem.domain.staking.model.stakekit.Yield import com.tangem.domain.staking.model.stakekit.action.StakingActionStatus import com.tangem.domain.tokens.FetchCurrencyStatusUseCase @@ -22,6 +23,7 @@ internal class StakingBalanceUpdater @AssistedInject constructor( private val fetchActionsUseCase: FetchActionsUseCase, private val txHistoryContentUpdateEmitter: TxHistoryContentUpdateEmitter, private val fetchCurrencyStatusUseCase: FetchCurrencyStatusUseCase, + private val fetchStakingYieldBalanceUseCase: FetchStakingYieldBalanceUseCase, @DelayedWork private val coroutineScope: CoroutineScope, @Assisted private val userWallet: UserWallet, @Assisted private val cryptoCurrencyStatus: CryptoCurrencyStatus, @@ -37,6 +39,13 @@ internal class StakingBalanceUpdater @AssistedInject constructor( network = cryptoCurrencyStatus.currency.network, ) }, + async { + fetchStakingYieldBalanceUseCase( + userWalletId = userWallet.walletId, + cryptoCurrency = cryptoCurrencyStatus.currency, + isRefactoringEnabled = true, + ) + }, // we should update tx history and network for new balances async { fetchCurrencyStatus(delayMillis = BALANCE_UPDATE_DELAY) From 70ee6205074b8ac09b2e6a48da0d9837346ed766 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 26 Jun 2025 22:33:31 +0400 Subject: [PATCH 11/19] Updated on 2026-08-14 --- .../java/com/tangem/tap/features/main/MainViewModel.kt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt b/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt index 29b1eb08c8..404a978379 100644 --- a/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt @@ -31,6 +31,7 @@ import com.tangem.domain.onboarding.repository.OnboardingRepository import com.tangem.domain.onramp.FetchHotCryptoUseCase import com.tangem.domain.promo.GetStoryContentUseCase import com.tangem.domain.promo.models.StoryContentIds +import com.tangem.domain.quotes.multi.MultiQuoteUpdater import com.tangem.domain.settings.DeleteDeprecatedLogsUseCase import com.tangem.domain.settings.IncrementAppLaunchCounterUseCase import com.tangem.domain.settings.usercountry.FetchUserCountryUseCase @@ -86,6 +87,7 @@ internal class MainViewModel @Inject constructor( private val updateRemoteWalletsInfoUseCase: UpdateRemoteWalletsInfoUseCase, private val sendPushTokenUseCase: SendPushTokenUseCase, private val apiConfigsManager: ApiConfigsManager, + private val multiQuoteUpdater: MultiQuoteUpdater, routingFeatureToggle: RoutingFeatureToggle, getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, ) : ViewModel() { @@ -116,6 +118,8 @@ internal class MainViewModel @Inject constructor( viewModelScope.launch { incrementAppLaunchCounterUseCase() } + multiQuoteUpdater.subscribe() + observeFlips() displayBalancesHidingStatusToast() displayHiddenBalancesModalNotification() @@ -131,6 +135,12 @@ internal class MainViewModel @Inject constructor( } } + override fun onCleared() { + super.onCleared() + + multiQuoteUpdater.unsubscribe() + } + fun checkForUnfinishedBackup() { viewModelScope.launch(dispatchers.main) { val onboardingScanResponse = onboardingRepository.getUnfinishedFinalizeOnboarding() ?: return@launch From 51efb0e7814887471cf9e27c077e1c3e9b1da7df Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 27 Jun 2025 12:47:02 +0400 Subject: [PATCH 12/19] Updated on 2026-08-14 --- .../common/ui/userwallet/UserWalletItem.kt | 64 +++++++++++-------- .../converter/ArtworkUMConverter.kt | 23 +++++++ .../converter/UserWalletItemUMConverter.kt | 5 +- .../core/ui/components/artwork/ArtworkUM.kt | 13 +--- .../onboarding/v2/common/ui/WalletCard.kt | 36 +++++++---- .../impl/model/OnboardingMultiWalletModel.kt | 11 ++-- .../topup/ui/OnboardingNoteTopUpHeader.kt | 3 +- .../v2/note/impl/model/OnboardingNoteModel.kt | 5 +- 8 files changed, 101 insertions(+), 59 deletions(-) create mode 100644 common/ui/src/main/java/com/tangem/common/ui/userwallet/converter/ArtworkUMConverter.kt diff --git a/common/ui/src/main/java/com/tangem/common/ui/userwallet/UserWalletItem.kt b/common/ui/src/main/java/com/tangem/common/ui/userwallet/UserWalletItem.kt index 5d2c226351..bc60544cb1 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/userwallet/UserWalletItem.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/userwallet/UserWalletItem.kt @@ -12,7 +12,9 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.rotate +import androidx.compose.ui.graphics.painter.BitmapPainter import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.vectorResource @@ -178,32 +180,42 @@ private fun CardImage(imageState: UserWalletItemUM.ImageState, modifier: Modifie ) } is UserWalletItemUM.ImageState.Image -> { - SubcomposeAsyncImage( - modifier = imageModifier, - model = ImageRequest.Builder(LocalContext.current) - .size( - width = with(LocalDensity.current) { TangemTheme.dimens.size36.roundToPx() }, - height = with(LocalDensity.current) { TangemTheme.dimens.size24.roundToPx() }, - ) - .data(imageState.artwork.verifiedArtwork?.toByteArray() ?: imageState.artwork.defaultUrl) - .crossfade(enable = true) - .allowHardware(enable = false) - .build(), - loading = { - RectangleShimmer( - modifier = imageModifier, - radius = TangemTheme.dimens.size2, - ) - }, - error = { - Image( - modifier = imageModifier.then(Modifier.rotate(90F)), - imageVector = ImageVector.vectorResource(R.drawable.img_card_wallet_2_gray_22_36), - contentDescription = null, - ) - }, - contentDescription = null, - ) + val verifiedArtwork = imageState.artwork.verifiedArtwork + if (verifiedArtwork != null) { + Image( + modifier = imageModifier, + painter = BitmapPainter(verifiedArtwork), + contentScale = ContentScale.Fit, + contentDescription = null, + ) + } else { + SubcomposeAsyncImage( + modifier = imageModifier, + model = ImageRequest.Builder(LocalContext.current) + .size( + width = with(LocalDensity.current) { TangemTheme.dimens.size36.roundToPx() }, + height = with(LocalDensity.current) { TangemTheme.dimens.size24.roundToPx() }, + ) + .data(imageState.artwork.defaultUrl) + .crossfade(enable = true) + .allowHardware(enable = false) + .build(), + loading = { + RectangleShimmer( + modifier = imageModifier, + radius = TangemTheme.dimens.size2, + ) + }, + error = { + Image( + modifier = imageModifier.then(Modifier.rotate(90F)), + imageVector = ImageVector.vectorResource(R.drawable.img_card_wallet_2_gray_22_36), + contentDescription = null, + ) + }, + contentDescription = null, + ) + } } } } diff --git a/common/ui/src/main/java/com/tangem/common/ui/userwallet/converter/ArtworkUMConverter.kt b/common/ui/src/main/java/com/tangem/common/ui/userwallet/converter/ArtworkUMConverter.kt new file mode 100644 index 0000000000..3278017126 --- /dev/null +++ b/common/ui/src/main/java/com/tangem/common/ui/userwallet/converter/ArtworkUMConverter.kt @@ -0,0 +1,23 @@ +package com.tangem.common.ui.userwallet.converter + +import android.graphics.BitmapFactory +import androidx.compose.ui.graphics.asImageBitmap +import com.tangem.core.ui.components.artwork.ArtworkUM +import com.tangem.domain.models.ArtworkModel +import com.tangem.utils.converter.Converter +import javax.inject.Inject + +class ArtworkUMConverter @Inject constructor() : Converter { + + override fun convert(value: ArtworkModel): ArtworkUM { + val bimap = try { + value.verifiedArtwork?.let { BitmapFactory.decodeByteArray(it, 0, it.size) } + } catch (ignore: Exception) { + null + } + return ArtworkUM( + verifiedArtwork = bimap?.asImageBitmap(), + defaultUrl = value.defaultUrl, + ) + } +} \ No newline at end of file diff --git a/common/ui/src/main/java/com/tangem/common/ui/userwallet/converter/UserWalletItemUMConverter.kt b/common/ui/src/main/java/com/tangem/common/ui/userwallet/converter/UserWalletItemUMConverter.kt index 875f1044f8..534a8de836 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/userwallet/converter/UserWalletItemUMConverter.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/userwallet/converter/UserWalletItemUMConverter.kt @@ -2,7 +2,6 @@ package com.tangem.common.ui.userwallet.converter import com.tangem.common.ui.R import com.tangem.common.ui.userwallet.state.UserWalletItemUM -import com.tangem.core.ui.components.artwork.ArtworkUM import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.extensions.wrappedList @@ -38,6 +37,8 @@ class UserWalletItemUMConverter( private val artwork: ArtworkModel? = null, ) : Converter { + private val artworkUMConverter = ArtworkUMConverter() + override fun convert(value: UserWallet): UserWalletItemUM { return with(value) { UserWalletItemUM( @@ -49,7 +50,7 @@ class UserWalletItemUMConverter( endIcon = endIcon, onClick = { onClick(value.walletId) }, imageState = artwork?.let { - UserWalletItemUM.ImageState.Image(ArtworkUM(it.verifiedArtwork, it.defaultUrl)) + UserWalletItemUM.ImageState.Image(artworkUMConverter.convert(it)) } ?: UserWalletItemUM.ImageState.Loading, ) } diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/artwork/ArtworkUM.kt b/core/ui/src/main/java/com/tangem/core/ui/components/artwork/ArtworkUM.kt index 5c1f568b02..a0e9d2e0f7 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/artwork/ArtworkUM.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/artwork/ArtworkUM.kt @@ -1,17 +1,10 @@ package com.tangem.core.ui.components.artwork import androidx.compose.runtime.Immutable -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.toImmutableList +import androidx.compose.ui.graphics.ImageBitmap @Immutable data class ArtworkUM( - val verifiedArtwork: ImmutableList? = null, + val verifiedArtwork: ImageBitmap? = null, val defaultUrl: String, -) { - - constructor(bytes: ByteArray?, defaultUrl: String) : this( - verifiedArtwork = bytes?.toList()?.toImmutableList(), - defaultUrl = defaultUrl, - ) -} \ No newline at end of file +) \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/common/ui/WalletCard.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/common/ui/WalletCard.kt index bd7b6d1a54..1fd4c123e5 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/common/ui/WalletCard.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/common/ui/WalletCard.kt @@ -1,7 +1,9 @@ package com.tangem.features.onboarding.v2.common.ui +import androidx.compose.foundation.Image import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.painter.BitmapPainter import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource @@ -12,16 +14,26 @@ import com.tangem.features.onboarding.v2.impl.R @Composable internal fun WalletCard(artwork: ArtworkUM?, modifier: Modifier = Modifier) { - AsyncImage( - modifier = modifier, - model = ImageRequest.Builder(LocalContext.current) - .data(artwork?.verifiedArtwork?.toByteArray() ?: artwork?.defaultUrl) - .crossfade(true) - .build(), - placeholder = painterResource(R.drawable.card_placeholder_black), - error = painterResource(R.drawable.card_placeholder_black), - fallback = painterResource(R.drawable.card_placeholder_black), - contentScale = ContentScale.Fit, - contentDescription = null, - ) + val verifiedArtwork = artwork?.verifiedArtwork + if (verifiedArtwork != null) { + Image( + modifier = modifier, + painter = BitmapPainter(verifiedArtwork), + contentScale = ContentScale.Fit, + contentDescription = null, + ) + } else { + AsyncImage( + modifier = modifier, + model = ImageRequest.Builder(LocalContext.current) + .data(artwork?.defaultUrl) + .crossfade(true) + .build(), + placeholder = painterResource(R.drawable.card_placeholder_black), + error = painterResource(R.drawable.card_placeholder_black), + fallback = painterResource(R.drawable.card_placeholder_black), + contentScale = ContentScale.Fit, + contentDescription = null, + ) + } } \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/OnboardingMultiWalletModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/OnboardingMultiWalletModel.kt index 6d5a640d3d..bf9980e12d 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/OnboardingMultiWalletModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/OnboardingMultiWalletModel.kt @@ -1,19 +1,19 @@ package com.tangem.features.onboarding.v2.multiwallet.impl.model +import com.tangem.common.ui.userwallet.converter.ArtworkUMConverter import com.tangem.core.analytics.api.AnalyticsEventHandler 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.decompose.ui.UiMessageSender -import com.tangem.core.ui.components.artwork.ArtworkUM import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.ProductType import com.tangem.domain.onboarding.repository.OnboardingRepository import com.tangem.domain.wallets.usecase.GetCardImageUseCase -import com.tangem.features.onboarding.v2.multiwallet.api.OnboardingMultiWalletComponent import com.tangem.features.onboarding.v2.common.analytics.OnboardingEvent import com.tangem.features.onboarding.v2.common.ui.interruptBackupDialog +import com.tangem.features.onboarding.v2.multiwallet.api.OnboardingMultiWalletComponent import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChildParams import com.tangem.features.onboarding.v2.multiwallet.impl.model.OnboardingMultiWalletState.FinalizeStage import com.tangem.features.onboarding.v2.multiwallet.impl.ui.state.OnboardingMultiWalletUM @@ -39,6 +39,7 @@ internal class OnboardingMultiWalletModel @Inject constructor( private val onboardingRepository: OnboardingRepository, private val getCardImageUseCase: GetCardImageUseCase, private val uiMessageSender: UiMessageSender, + private val artworkUMConverter: ArtworkUMConverter, ) : Model() { private val params = paramsContainer.require() private val _uiState = MutableStateFlow(OnboardingMultiWalletUM()) @@ -90,7 +91,7 @@ internal class OnboardingMultiWalletModel @Inject constructor( firmwareVersion = backup.card2.firmwareVersion, ) _uiState.update { - it.copy(artwork2 = ArtworkUM(artwork.verifiedArtwork, artwork.defaultUrl)) + it.copy(artwork2 = artworkUMConverter.convert(artwork)) } } _uiState.value.artwork3 == null && backup.card3 != null -> { @@ -103,7 +104,7 @@ internal class OnboardingMultiWalletModel @Inject constructor( firmwareVersion = backup.card3.firmwareVersion, ) _uiState.update { - it.copy(artwork3 = ArtworkUM(artwork.verifiedArtwork, artwork.defaultUrl)) + it.copy(artwork3 = artworkUMConverter.convert(artwork)) } } } @@ -179,7 +180,7 @@ internal class OnboardingMultiWalletModel @Inject constructor( ) _uiState.update { - it.copy(artwork1 = ArtworkUM(artwork.verifiedArtwork, artwork.defaultUrl)) + it.copy(artwork1 = artworkUMConverter.convert(artwork)) } } } diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/child/topup/ui/OnboardingNoteTopUpHeader.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/child/topup/ui/OnboardingNoteTopUpHeader.kt index 9830905a93..672d5e838b 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/child/topup/ui/OnboardingNoteTopUpHeader.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/child/topup/ui/OnboardingNoteTopUpHeader.kt @@ -22,7 +22,6 @@ import com.tangem.features.onboarding.v2.common.ui.RefreshButton import com.tangem.features.onboarding.v2.common.ui.WalletCard import com.tangem.features.onboarding.v2.impl.R import com.valentinilk.shimmer.shimmer -import kotlinx.collections.immutable.ImmutableList @Composable fun OnboardingNoteTopUpHeader( @@ -95,7 +94,7 @@ private fun OnboardinNoteTopUpHeaderPreview() { TangemThemePreview { OnboardingNoteTopUpHeader( balance = "0.00000001 BTC", - cardArtwork = ArtworkUM(null as ImmutableList?, ""), + cardArtwork = ArtworkUM(null, ""), onRefreshBalanceClick = {}, isRefreshing = false, ) diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/model/OnboardingNoteModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/model/OnboardingNoteModel.kt index 43d12d119e..27e57382fd 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/model/OnboardingNoteModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/model/OnboardingNoteModel.kt @@ -1,12 +1,12 @@ package com.tangem.features.onboarding.v2.note.impl.model import com.arkivanov.decompose.router.stack.StackNavigation +import com.tangem.common.ui.userwallet.converter.ArtworkUMConverter 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.decompose.ui.UiMessageSender -import com.tangem.core.ui.components.artwork.ArtworkUM import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.usecase.GetCardImageUseCase import com.tangem.features.onboarding.v2.common.ui.exitOnboardingDialog @@ -29,6 +29,7 @@ internal class OnboardingNoteModel @Inject constructor( private val router: Router, private val messageSender: UiMessageSender, private val getCardImageUseCase: GetCardImageUseCase, + private val artworkUMConverter: ArtworkUMConverter, ) : Model() { @Suppress("UnusedPrivateMember") @@ -98,7 +99,7 @@ internal class OnboardingNoteModel @Inject constructor( firmwareVersion = cardInfo.firmwareVersion.toSdkFirmwareVersion(), ) _uiState.update { - it.copy(cardArtwork = ArtworkUM(artwork.verifiedArtwork, artwork.defaultUrl)) + it.copy(cardArtwork = artworkUMConverter.convert(artwork)) } } } From b51b57cf8993eb7943aedb97745a98fe2d460d67 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 27 Jun 2025 14:21:36 +0500 Subject: [PATCH 13/19] Updated on 2026-08-14 --- .../features/nft/details/entity/NFTAssetUM.kt | 3 +- .../entity/factory/NFTDetailsUMFactory.kt | 48 ++++++++++------- .../nft/details/model/NFTDetailsModel.kt | 17 ++++++- .../nft/details/ui/NFTDetailsBlocksGroup.kt | 51 ++++++++++++------- 4 files changed, 80 insertions(+), 39 deletions(-) diff --git a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/entity/NFTAssetUM.kt b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/entity/NFTAssetUM.kt index 4b2b63d11a..cedf40aae1 100644 --- a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/entity/NFTAssetUM.kt +++ b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/entity/NFTAssetUM.kt @@ -62,6 +62,7 @@ data class NFTAssetUM( val value: String, val valueTextEllipsis: TextEllipsis = TextEllipsis.End, val showInfoButton: Boolean, - val onClick: () -> Unit = { }, + val onBlockClick: (() -> Unit)? = null, + val onValueClick: (() -> Unit)? = null, ) } \ No newline at end of file diff --git a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/entity/factory/NFTDetailsUMFactory.kt b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/entity/factory/NFTDetailsUMFactory.kt index 66f7a6cae1..ae5bfb547a 100644 --- a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/entity/factory/NFTDetailsUMFactory.kt +++ b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/entity/factory/NFTDetailsUMFactory.kt @@ -27,7 +27,8 @@ internal class NFTDetailsUMFactory( private val onExploreClick: () -> Unit, private val onSendClick: () -> Unit, private val onRefresh: () -> Unit, - private val onInfoBlockClick: (title: TextReference, text: TextReference) -> Unit, + private val onRegularInfoBlockClick: (title: TextReference, text: TextReference) -> Unit, + private val onTokenAddressBlockClick: () -> Unit, ) { fun getInitialState(nftAsset: NFTAsset, nftCollection: NFTCollection): NFTDetailsUM = NFTDetailsUM( @@ -70,13 +71,13 @@ internal class NFTDetailsUMFactory( label = rarity.label, showDivider = hasSalePrice || hasDescription, onLabelClick = { - onInfoBlockClick( + onRegularInfoBlockClick( resourceReference(R.string.nft_details_rarity_label), resourceReference(R.string.nft_details_info_rarity_label), ) }, onRankClick = { - onInfoBlockClick( + onRegularInfoBlockClick( resourceReference(R.string.nft_details_rarity_rank), resourceReference(R.string.nft_details_info_rarity_rank), ) @@ -135,8 +136,8 @@ internal class NFTDetailsUMFactory( title = resourceReference(R.string.nft_details_token_standard), value = contractType, showInfoButton = true, - onClick = { - onInfoBlockClick( + onBlockClick = { + onRegularInfoBlockClick( resourceReference(R.string.nft_details_token_standard), resourceReference(R.string.nft_details_info_token_standard), ) @@ -147,20 +148,23 @@ internal class NFTDetailsUMFactory( value = id.tokenAddress, valueTextEllipsis = TextEllipsis.Middle, showInfoButton = true, - onClick = { - onInfoBlockClick( + onBlockClick = { + onRegularInfoBlockClick( resourceReference(R.string.nft_details_contract_address), resourceReference(R.string.nft_details_info_contract_address), ) }, + onValueClick = { + onTokenAddressBlockClick() + }, ), NFTAssetUM.BlockItem( title = resourceReference(R.string.nft_details_token_id), value = id.tokenId.toString(), valueTextEllipsis = TextEllipsis.Middle, showInfoButton = true, - onClick = { - onInfoBlockClick( + onBlockClick = { + onRegularInfoBlockClick( resourceReference(R.string.nft_details_token_id), resourceReference(R.string.nft_details_info_token_id), ) @@ -170,8 +174,8 @@ internal class NFTDetailsUMFactory( title = resourceReference(R.string.nft_details_chain), value = network.name, showInfoButton = true, - onClick = { - onInfoBlockClick( + onBlockClick = { + onRegularInfoBlockClick( resourceReference(R.string.nft_details_chain), resourceReference(R.string.nft_details_info_chain), ) @@ -184,19 +188,22 @@ internal class NFTDetailsUMFactory( value = id.tokenAddress, valueTextEllipsis = TextEllipsis.Middle, showInfoButton = true, - onClick = { - onInfoBlockClick( + onBlockClick = { + onRegularInfoBlockClick( resourceReference(R.string.nft_details_token_address), resourceReference(R.string.nft_details_info_token_address), ) }, + onValueClick = { + onTokenAddressBlockClick() + }, ), NFTAssetUM.BlockItem( title = resourceReference(R.string.nft_details_chain), value = network.name, showInfoButton = true, - onClick = { - onInfoBlockClick( + onBlockClick = { + onRegularInfoBlockClick( resourceReference(R.string.nft_details_chain), resourceReference(R.string.nft_details_info_chain), ) @@ -209,19 +216,22 @@ internal class NFTDetailsUMFactory( value = id.tokenAddress, valueTextEllipsis = TextEllipsis.Middle, showInfoButton = true, - onClick = { - onInfoBlockClick( + onBlockClick = { + onRegularInfoBlockClick( resourceReference(R.string.nft_details_token_address), resourceReference(R.string.nft_details_info_token_address), ) }, + onValueClick = { + onTokenAddressBlockClick() + }, ), NFTAssetUM.BlockItem( title = resourceReference(R.string.nft_details_chain), value = network.name, showInfoButton = true, - onClick = { - onInfoBlockClick( + onBlockClick = { + onRegularInfoBlockClick( resourceReference(R.string.nft_details_chain), resourceReference(R.string.nft_details_info_chain), ) diff --git a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/model/NFTDetailsModel.kt b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/model/NFTDetailsModel.kt index 852e5d2e40..85bb073209 100644 --- a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/model/NFTDetailsModel.kt +++ b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/model/NFTDetailsModel.kt @@ -10,6 +10,7 @@ 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.navigation.url.UrlOpener +import com.tangem.core.ui.clipboard.ClipboardManager import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.stringReference @@ -20,6 +21,7 @@ import com.tangem.domain.nft.FetchNFTPriceUseCase import com.tangem.domain.nft.GetNFTExploreUrlUseCase import com.tangem.domain.nft.GetNFTPriceUseCase import com.tangem.domain.nft.analytics.NFTAnalyticsEvent +import com.tangem.domain.nft.models.NFTAsset import com.tangem.features.nft.details.NFTDetailsComponent import com.tangem.features.nft.details.entity.NFTAssetUM import com.tangem.features.nft.details.entity.NFTDetailsBottomSheetConfig @@ -49,6 +51,7 @@ internal class NFTDetailsModel @Inject constructor( private val getNFTPriceUseCase: GetNFTPriceUseCase, private val fetchNFTCollectionAssetsUseCase: FetchNFTCollectionAssetsUseCase, private val fetchNFTPriceUseCase: FetchNFTPriceUseCase, + private val clipboardManager: ClipboardManager, paramsContainer: ParamsContainer, ) : Model() { @@ -68,8 +71,9 @@ internal class NFTDetailsModel @Inject constructor( }, onExploreClick = ::onExploreClick, onSendClick = ::onSendClick, - onInfoBlockClick = ::onInfoBlockClick, + onRegularInfoBlockClick = ::onInfoBlockClick, onRefresh = ::onRefresh, + onTokenAddressBlockClick = ::onTokenAddressBlockClick, ) private val _state by lazy { @@ -191,4 +195,15 @@ internal class NFTDetailsModel @Inject constructor( ), ) } + + private fun onTokenAddressBlockClick() { + val asset = params.nftAsset + val addressToCopy = when (val id = asset.id) { + is NFTAsset.Identifier.EVM -> id.tokenAddress + is NFTAsset.Identifier.Solana -> id.tokenAddress + is NFTAsset.Identifier.TON -> id.tokenAddress + is NFTAsset.Identifier.Unknown -> return + } + clipboardManager.setText(text = addressToCopy, isSensitive = false) + } } \ No newline at end of file diff --git a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/ui/NFTDetailsBlocksGroup.kt b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/ui/NFTDetailsBlocksGroup.kt index cc7726dfd1..df471fec36 100644 --- a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/ui/NFTDetailsBlocksGroup.kt +++ b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/ui/NFTDetailsBlocksGroup.kt @@ -69,17 +69,14 @@ internal fun NFTDetailsBlocksGroup( NFTDetailsGroupBlock( modifier = Modifier .weight(1f) - .padding(paddingValues) - .clickable( - interactionSource = remember { MutableInteractionSource() }, - indication = null, - onClick = item1.onClick, - ), + .padding(paddingValues), title = item1.title, value = stringReference(item1.value), titleEllipsis = item1.titleTextEllipsis, valueEllipsis = item1.valueTextEllipsis, showInfoButton = item1.showInfoButton, + onBlockClick = item1.onBlockClick, + onValueClick = item1.onValueClick, ) if (item2 == null) { Box( @@ -91,17 +88,14 @@ internal fun NFTDetailsBlocksGroup( NFTDetailsGroupBlock( modifier = Modifier .weight(1f) - .padding(paddingValues) - .clickable( - interactionSource = remember { MutableInteractionSource() }, - indication = null, - onClick = item2.onClick, - ), + .padding(paddingValues), title = item2.title, value = stringReference(item2.value), titleEllipsis = item2.titleTextEllipsis, valueEllipsis = item2.valueTextEllipsis, showInfoButton = item2.showInfoButton, + onBlockClick = item2.onBlockClick, + onValueClick = item2.onValueClick, ) } } @@ -128,6 +122,8 @@ internal fun NFTDetailsGroupBlock( value: TextReference, showInfoButton: Boolean, modifier: Modifier = Modifier, + onBlockClick: (() -> Unit)? = null, + onValueClick: (() -> Unit)? = null, titleEllipsis: TextEllipsis = TextEllipsis.End, valueEllipsis: TextEllipsis = TextEllipsis.End, ) { @@ -136,7 +132,14 @@ internal fun NFTDetailsGroupBlock( ) { Row( modifier = Modifier - .fillMaxWidth(), + .fillMaxWidth() + .clickable( + enabled = onBlockClick != null, + interactionSource = remember { MutableInteractionSource() }, + indication = null, + ) { + onBlockClick?.invoke() + }, ) { EllipsisText( modifier = Modifier @@ -159,6 +162,18 @@ internal fun NFTDetailsGroupBlock( } EllipsisText( modifier = Modifier + .fillMaxWidth() + .clickable( + enabled = onValueClick != null || onBlockClick != null, + interactionSource = remember { MutableInteractionSource() }, + indication = null, + ) { + if (onValueClick != null) { + onValueClick.invoke() + } else { + onBlockClick?.invoke() + } + } .padding(top = TangemTheme.dimens.spacing4), text = value.resolveReference(), style = TangemTheme.typography.body1, @@ -255,27 +270,27 @@ private class NFTAssetBlocksProvider : CollectionPreviewParameterProvider Date: Fri, 27 Jun 2025 14:41:00 +0500 Subject: [PATCH 14/19] Updated on 2026-08-14 --- .../core/analytics/models/AnalyticsParam.kt | 2 ++ .../NFTSdkAssetIdentifierConverter.kt | 4 +-- .../com/tangem/domain/nft/models/NFTAsset.kt | 2 +- .../domain/nft/analytics/NFTAnalyticsEvent.kt | 30 ++++++++++++----- .../nft/details/model/NFTDetailsModel.kt | 12 ++++++- .../analytics/WalletSettingsAnalyticEvents.kt | 17 ++++++++++ .../model/WalletSettingsModel.kt | 8 +++++ .../intents/WalletContentClickIntents.kt | 32 +++++++++++++------ .../common/preview/WalletScreenPreviewData.kt | 3 +- .../wallet/state/model/WalletNFTItemUM.kt | 3 +- .../SetNFTCollectionsTransformer.kt | 20 ++++++++++-- .../wallet/ui/components/WalletNFTItem.kt | 17 ++++++---- gradle/tangem_dependencies.toml | 2 +- 13 files changed, 119 insertions(+), 33 deletions(-) create mode 100644 features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/analytics/WalletSettingsAnalyticEvents.kt diff --git a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt index fc00d6b5bc..d02be99d34 100644 --- a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt +++ b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt @@ -219,5 +219,7 @@ sealed class AnalyticsParam { const val ACTION = "Action" const val COLLECTIONS = "Collections" const val NFT = "Nft" + const val STANDARD = "Standard" + const val NO_COLLECTION = "No collection" } } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/nft/converter/NFTSdkAssetIdentifierConverter.kt b/core/datasource/src/main/java/com/tangem/datasource/local/nft/converter/NFTSdkAssetIdentifierConverter.kt index bd93af8f19..b06cb3be83 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/nft/converter/NFTSdkAssetIdentifierConverter.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/nft/converter/NFTSdkAssetIdentifierConverter.kt @@ -16,7 +16,7 @@ object NFTSdkAssetIdentifierConverter : TwoWayConverter NFTAsset.Identifier.Solana( tokenAddress = value.tokenAddress, - cnft = value.cnft, + tokenStandard = value.tokenStandard, ) is SdkNFTAsset.Identifier.Unknown -> NFTAsset.Identifier.Unknown } @@ -32,7 +32,7 @@ object NFTSdkAssetIdentifierConverter : TwoWayConverter SdkNFTAsset.Identifier.Solana( tokenAddress = value.tokenAddress, - cnft = value.cnft, + tokenStandard = value.tokenStandard, ) is NFTAsset.Identifier.Unknown -> SdkNFTAsset.Identifier.Unknown } diff --git a/domain/nft/models/src/main/kotlin/com/tangem/domain/nft/models/NFTAsset.kt b/domain/nft/models/src/main/kotlin/com/tangem/domain/nft/models/NFTAsset.kt index f30b865df7..f83aec887a 100644 --- a/domain/nft/models/src/main/kotlin/com/tangem/domain/nft/models/NFTAsset.kt +++ b/domain/nft/models/src/main/kotlin/com/tangem/domain/nft/models/NFTAsset.kt @@ -68,7 +68,7 @@ data class NFTAsset( @Serializable data class Solana( val tokenAddress: String, - val cnft: Boolean, + val tokenStandard: Int?, ) : Identifier() { override val stringValue: String = tokenAddress } diff --git a/domain/nft/src/main/kotlin/com/tangem/domain/nft/analytics/NFTAnalyticsEvent.kt b/domain/nft/src/main/kotlin/com/tangem/domain/nft/analytics/NFTAnalyticsEvent.kt index 34bb1ab6f7..9ee3357e17 100644 --- a/domain/nft/src/main/kotlin/com/tangem/domain/nft/analytics/NFTAnalyticsEvent.kt +++ b/domain/nft/src/main/kotlin/com/tangem/domain/nft/analytics/NFTAnalyticsEvent.kt @@ -2,9 +2,12 @@ package com.tangem.domain.nft.analytics import com.tangem.core.analytics.models.AnalyticsEvent import com.tangem.core.analytics.models.AnalyticsParam.Key.BLOCKCHAIN +import com.tangem.core.analytics.models.AnalyticsParam.Key.STANDARD import com.tangem.core.analytics.models.AnalyticsParam.Key.COLLECTIONS import com.tangem.core.analytics.models.AnalyticsParam.Key.NFT +import com.tangem.core.analytics.models.AnalyticsParam.Key.NO_COLLECTION import com.tangem.core.analytics.models.AnalyticsParam.Key.STATE +import kotlin.collections.buildMap sealed class NFTAnalyticsEvent( event: String, @@ -17,19 +20,21 @@ sealed class NFTAnalyticsEvent( data class NFTListScreenOpened( val state: State, + val collectionsCount: Int, + val allAssetsCount: Int, + val noCollectionAssetsCount: Int, ) : NFTAnalyticsEvent( event = "NFT List Screen Opened", params = buildMap { put(STATE, state.value) - if (state is State.Full) { - put(COLLECTIONS, state.collectionsCount.toString()) - put(NFT, state.assetsCount.toString()) - } + put(COLLECTIONS, collectionsCount.toString()) + put(NFT, allAssetsCount.toString()) + put(NO_COLLECTION, noCollectionAssetsCount.toString()) }, ) { - sealed class State(val value: String) { - data object Empty : State("Empty") - data class Full(val assetsCount: Int, val collectionsCount: Int) : State("Full") + enum class State(val value: String) { + Empty("Empty"), + Full("Full"), } } @@ -52,7 +57,16 @@ sealed class NFTAnalyticsEvent( object Details { data class ScreenOpened( private val blockchain: String, - ) : NFTAnalyticsEvent(event = "NFT Details Screen Opened", params = mapOf(BLOCKCHAIN to blockchain)) + private val standard: String?, + ) : NFTAnalyticsEvent( + event = "NFT Details Screen Opened", + params = buildMap { + put(BLOCKCHAIN, blockchain) + standard?.let { + put(STANDARD, it) + } + }, + ) data object ButtonReadMore : NFTAnalyticsEvent(event = "Button - Read More") data object ButtonSeeAll : NFTAnalyticsEvent(event = "Button - See All") diff --git a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/model/NFTDetailsModel.kt b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/model/NFTDetailsModel.kt index 85bb073209..f20a708479 100644 --- a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/model/NFTDetailsModel.kt +++ b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/model/NFTDetailsModel.kt @@ -85,7 +85,17 @@ internal class NFTDetailsModel @Inject constructor( val bottomSheetNavigation: SlotNavigation = SlotNavigation() init { - analyticsEventHandler.send(NFTAnalyticsEvent.Details.ScreenOpened(params.nftAsset.network.name)) + analyticsEventHandler.send( + NFTAnalyticsEvent.Details.ScreenOpened( + blockchain = params.nftAsset.network.name, + standard = when (val id = params.nftAsset.id) { + is NFTAsset.Identifier.EVM -> id.contractType.name + is NFTAsset.Identifier.Solana -> id.tokenStandard?.toString() + is NFTAsset.Identifier.TON -> null + is NFTAsset.Identifier.Unknown -> null + }, + ), + ) initAppCurrency() subscribeToPriceChanges() } diff --git a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/analytics/WalletSettingsAnalyticEvents.kt b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/analytics/WalletSettingsAnalyticEvents.kt new file mode 100644 index 0000000000..6c2fbd0ee0 --- /dev/null +++ b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/analytics/WalletSettingsAnalyticEvents.kt @@ -0,0 +1,17 @@ +package com.tangem.feature.walletsettings.analytics + +import com.tangem.core.analytics.models.AnalyticsEvent +import com.tangem.core.analytics.models.AnalyticsParam +import com.tangem.core.analytics.models.AnalyticsParam.Key.STATUS + +internal sealed class WalletSettingsAnalyticEvents( + category: String = "Settings / Wallet", + event: String, + params: Map = mapOf(), +) : AnalyticsEvent(category, event, params) { + + data class NftToggleSwitch(val enabled: AnalyticsParam.OnOffState) : WalletSettingsAnalyticEvents( + event = "NFT toggle switch", + params = mapOf(STATUS to enabled.value), + ) +} \ No newline at end of file diff --git a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/model/WalletSettingsModel.kt b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/model/WalletSettingsModel.kt index ebe302c2e7..b85bb4de06 100644 --- a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/model/WalletSettingsModel.kt +++ b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/model/WalletSettingsModel.kt @@ -6,6 +6,8 @@ import com.arkivanov.decompose.router.slot.SlotNavigation import com.arkivanov.decompose.router.slot.activate import com.tangem.common.routing.AppRoute import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.core.analytics.models.AnalyticsParam.OnOffState.Off +import com.tangem.core.analytics.models.AnalyticsParam.OnOffState.On import com.tangem.core.analytics.utils.AnalyticsContextProxy import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model @@ -29,6 +31,7 @@ import com.tangem.domain.wallets.models.isMultiCurrency import com.tangem.domain.wallets.models.requireColdWallet import com.tangem.domain.wallets.usecase.* import com.tangem.feature.walletsettings.analytics.Settings +import com.tangem.feature.walletsettings.analytics.WalletSettingsAnalyticEvents import com.tangem.feature.walletsettings.component.WalletSettingsComponent import com.tangem.feature.walletsettings.entity.DialogConfig import com.tangem.feature.walletsettings.entity.NetworksAvailableForNotificationBSConfig @@ -214,6 +217,11 @@ internal class WalletSettingsModel @Inject constructor( } else { disableWalletNFTUseCase.invoke(params.userWalletId) } + analyticsEventHandler.send( + WalletSettingsAnalyticEvents.NftToggleSwitch( + enabled = if (isChecked) On else Off, + ), + ) } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletContentClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletContentClickIntents.kt index dacede164b..f125cd626d 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletContentClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletContentClickIntents.kt @@ -238,19 +238,31 @@ internal class WalletContentClickIntentsImplementor @Inject constructor( override fun onNFTClick(userWallet: UserWallet) { val selectedWallet = stateHolder.getSelectedWallet() as? WalletState.MultiCurrency.Content ?: return - val analyticsState = when (val nftState = selectedWallet.nftState) { - is WalletNFTItemUM.Content -> NFTAnalyticsEvent.NFTListScreenOpened.State.Full( - assetsCount = nftState.assetsCount, - collectionsCount = nftState.collectionsCount, - ) - is WalletNFTItemUM.Empty -> NFTAnalyticsEvent.NFTListScreenOpened.State.Empty + when (val state = selectedWallet.nftState) { + is WalletNFTItemUM.Content -> { + analyticsEventHandler.send( + NFTAnalyticsEvent.NFTListScreenOpened( + state = NFTAnalyticsEvent.NFTListScreenOpened.State.Full, + allAssetsCount = state.allAssetsCount, + collectionsCount = state.collectionsCount, + noCollectionAssetsCount = state.noCollectionAssetsCount, + ), + ) + } + is WalletNFTItemUM.Empty -> { + analyticsEventHandler.send( + NFTAnalyticsEvent.NFTListScreenOpened( + state = NFTAnalyticsEvent.NFTListScreenOpened.State.Empty, + allAssetsCount = 0, + collectionsCount = 0, + noCollectionAssetsCount = 0, + ), + ) + } is WalletNFTItemUM.Failed, is WalletNFTItemUM.Hidden, is WalletNFTItemUM.Loading, - -> null - } - analyticsState?.let { - analyticsEventHandler.send(NFTAnalyticsEvent.NFTListScreenOpened(analyticsState)) + -> Unit } router.openNFT(userWallet) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/preview/WalletScreenPreviewData.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/preview/WalletScreenPreviewData.kt index 08937391da..9a3d8d6a28 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/preview/WalletScreenPreviewData.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/preview/WalletScreenPreviewData.kt @@ -123,7 +123,8 @@ internal object WalletScreenPreviewData { nftState = WalletNFTItemUM.Content( previews = persistentListOf(WalletNFTItemUM.Content.CollectionPreview.Image("img1")), collectionsCount = 1, - assetsCount = 3, + allAssetsCount = 3, + noCollectionAssetsCount = 0, isFlickering = false, onItemClick = { }, ), diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletNFTItemUM.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletNFTItemUM.kt index f4406d5dc5..d6fb28e300 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletNFTItemUM.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletNFTItemUM.kt @@ -19,7 +19,8 @@ sealed class WalletNFTItemUM { data class Content( val previews: ImmutableList, val collectionsCount: Int, - val assetsCount: Int, + val allAssetsCount: Int, + val noCollectionAssetsCount: Int, val isFlickering: Boolean, val onItemClick: () -> Unit, ) : WalletNFTItemUM() { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetNFTCollectionsTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetNFTCollectionsTransformer.kt index b749ffccf3..7f47fb0a15 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetNFTCollectionsTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetNFTCollectionsTransformer.kt @@ -52,8 +52,24 @@ internal class SetNFTCollectionsTransformer( .toPersistentList() }, collectionsCount = collections.size, - assetsCount = collections - .sumOf { it.count }, + allAssetsCount = collections.sumOf { it.count }, + noCollectionAssetsCount = collections.sumOf { collection -> + when (val collectionId = collection.id) { + is NFTCollection.Identifier.Solana -> + collection + .count + .takeIf { collectionId.collectionAddress == null } + ?: 0 + is NFTCollection.Identifier.TON -> + collection + .count + .takeIf { collectionId.contractAddress == null } + ?: 0 + is NFTCollection.Identifier.EVM, + is NFTCollection.Identifier.Unknown, + -> 0 + } + }, isFlickering = false, onItemClick = onItemClick, ) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletNFTItem.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletNFTItem.kt index 079f2876e1..b79c1832b5 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletNFTItem.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletNFTItem.kt @@ -108,7 +108,7 @@ private fun WalletNFTItemContent(state: WalletNFTItemUM.Content, onClick: () -> Text( text = stringResourceSafe( id = R.string.nft_wallet_count, - state.assetsCount, + state.allAssetsCount, state.collectionsCount, ), style = TangemTheme.typography.caption2.applyBladeBrush( @@ -411,8 +411,9 @@ private class WalletNFTItemProvider : CollectionPreviewParameterProvider Date: Fri, 27 Jun 2025 12:54:48 +0300 Subject: [PATCH 15/19] Updated on 2026-08-14 --- gradle/tangem_dependencies.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/tangem_dependencies.toml b/gradle/tangem_dependencies.toml index ed8612fa38..292e53e595 100644 --- a/gradle/tangem_dependencies.toml +++ b/gradle/tangem_dependencies.toml @@ -5,7 +5,7 @@ # https://github.com/tangem/tangem-sdk-android/ # https://github.com/tangem/vico -tangemBlockchainSdk = "releases-5.25.0-1098" +tangemBlockchainSdk = "releases-5.25.0-1099" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "releases-5.25.0-486" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From ebebd1d3874254f03be68a3c8fec6403cc2816c6 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 27 Jun 2025 09:55:08 +0000 Subject: [PATCH 16/19] Updated on 2026-08-14 --- gradle/tangem_dependencies.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/tangem_dependencies.toml b/gradle/tangem_dependencies.toml index 292e53e595..d33eab3225 100644 --- a/gradle/tangem_dependencies.toml +++ b/gradle/tangem_dependencies.toml @@ -5,9 +5,9 @@ # https://github.com/tangem/tangem-sdk-android/ # https://github.com/tangem/vico -tangemBlockchainSdk = "releases-5.25.0-1099" +tangemBlockchainSdk = "develop-1093" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds -tangemCardSdk = "releases-5.25.0-486" +tangemCardSdk = "develop-484" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ tangemVico = "2.0.0-alpha.25-tangem12" #tangemVico = "0.0.1" # Keep it! - used for local builds ^ From 1c53e890af59579bdbf1c6441abc62979abbe1b4 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 27 Jun 2025 16:00:50 +0300 Subject: [PATCH 17/19] Updated on 2026-08-14 --- tangem-android-tools | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tangem-android-tools b/tangem-android-tools index 034ad04fdf..bc4cd43085 160000 --- a/tangem-android-tools +++ b/tangem-android-tools @@ -1 +1 @@ -Subproject commit 034ad04fdf0810e338cb8898f50554874cec96d7 +Subproject commit bc4cd430853ca794614b8d5163c9b28b9ca26112 From c1b16658405f49ea681f5e8ba35e37a6196544cd Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 27 Jun 2025 16:02:18 +0300 Subject: [PATCH 18/19] Updated on 2026-08-14 --- gradle/tangem_dependencies.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/tangem_dependencies.toml b/gradle/tangem_dependencies.toml index d33eab3225..e18a2ec5cf 100644 --- a/gradle/tangem_dependencies.toml +++ b/gradle/tangem_dependencies.toml @@ -5,7 +5,7 @@ # https://github.com/tangem/tangem-sdk-android/ # https://github.com/tangem/vico -tangemBlockchainSdk = "develop-1093" +tangemBlockchainSdk = "develop-1097" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "develop-484" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From b3bf232b1226ea74b197a146bf64342eab05e215 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 27 Jun 2025 16:13:40 +0300 Subject: [PATCH 19/19] Updated on 2026-08-14 --- .../transaction/model/WcSendTransactionModel.kt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSendTransactionModel.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSendTransactionModel.kt index 4cde43d227..20b19996a3 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSendTransactionModel.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSendTransactionModel.kt @@ -14,7 +14,6 @@ import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.navigation.Router import com.tangem.core.ui.clipboard.ClipboardManager import com.tangem.core.ui.extensions.stringReference -import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.core.lce.Lce import com.tangem.domain.models.network.Network import com.tangem.domain.tokens.GetNetworkCoinStatusUseCase @@ -132,11 +131,9 @@ internal class WcSendTransactionModel @Inject constructor( network: Network, ): Either { return getNetworkCoinUseCase.invokeSync( - userWalletId = userWallet.walletId, + userWallet = userWallet, networkId = network.id, derivationPath = network.derivationPath, - isSingleWalletWithTokens = userWallet is UserWallet.Cold && - userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken(), ) }