Updated on 2026-08-14

This commit is contained in:
Tangem 2026-05-13 11:43:39 +04:00
parent 822b82d1da
commit c36ba85816
28 changed files with 846 additions and 21 deletions

View file

@ -313,6 +313,7 @@
<item quantity="other">%dStunden her</item>
</plurals>
<string name="common_import">Importieren</string>
<string name="common_in">in</string>
<string name="common_in_progress">In Arbeit</string>
<string name="common_insufficient_balance">Unzureichende Mittel</string>
<string name="common_later">Später</string>
@ -398,6 +399,7 @@
<string name="common_to">An</string>
<string name="common_to_wallet_name">Zu %s</string>
<string name="common_today">Heute</string>
<string name="common_token">Token</string>
<string name="common_token_send">Zu sendendes Token</string>
<plurals name="common_tokens_count">
<item quantity="one">%d Token</item>

View file

@ -309,6 +309,7 @@
<item quantity="other">Hace %dh</item>
</plurals>
<string name="common_import">Importe</string>
<string name="common_in">en</string>
<string name="common_in_progress">En progreso</string>
<string name="common_insufficient_balance">Fondos insuficientes</string>
<string name="common_later">Más tarde</string>
@ -394,6 +395,7 @@
<string name="common_to">A</string>
<string name="common_to_wallet_name">A %s</string>
<string name="common_today">Hoy</string>
<string name="common_token">Token</string>
<string name="common_token_send">Token para enviar</string>
<plurals name="common_tokens_count">
<item quantity="one">%d token</item>

View file

@ -309,6 +309,7 @@
<item quantity="other">Il y a %dh</item>
</plurals>
<string name="common_import">Importez</string>
<string name="common_in">dans</string>
<string name="common_in_progress">En cours</string>
<string name="common_later">Plus tard</string>
<string name="common_learn_more">En savoir plus</string>
@ -392,6 +393,7 @@
<string name="common_to">À</string>
<string name="common_to_wallet_name">À %s</string>
<string name="common_today">Aujourd\'hui</string>
<string name="common_token">Token</string>
<plurals name="common_tokens_count">
<item quantity="one">%d token</item>
<item quantity="other">%d tokens</item>

View file

@ -16,12 +16,14 @@
<string name="common_done">Fatto</string>
<string name="common_error">Errore</string>
<string name="common_fee_error">Impossibile ottenere la commissione</string>
<string name="common_in">in</string>
<string name="common_network_fee_title">Costi della rete</string>
<string name="common_ok">OK</string>
<string name="common_save_changes">Mantieni le modifiche</string>
<string name="common_send">Invia</string>
<string name="common_send_tx_error">Impossibile inviare la transazione</string>
<string name="common_success">Con successo</string>
<string name="common_token">Token</string>
<plurals name="common_tokens_count">
<item quantity="one">%d gettone</item>
<item quantity="other">%d gettoni</item>

View file

@ -214,6 +214,7 @@
<string name="common_access_denied">アクセスが拒否されました</string>
<string name="common_account">アカウント</string>
<string name="common_accounts">アカウント</string>
<string name="common_action_failed">%s に失敗しました</string>
<string name="common_activate">有効化</string>
<string name="common_add">追加</string>
<string name="common_add_funds">資金を追加</string>
@ -230,6 +231,8 @@
<string name="common_apply">適用する</string>
<string name="common_approval">承認</string>
<string name="common_approve">承認</string>
<string name="common_approved">承認済み</string>
<string name="common_approving">承認中</string>
<string name="common_attention">注意</string>
<string name="common_available_networks">利用可能なネットワーク</string>
<string name="common_backup">バックアップ</string>
@ -309,10 +312,14 @@
<string name="common_hide">非表示</string>
<string name="common_hold_to">%sまで長押し</string>
<string name="common_hour">時間</string>
<plurals name="common_hours">
<item quantity="other">%d 時間</item>
</plurals>
<plurals name="common_hours_time_ago">
<item quantity="other">%d時間前</item>
</plurals>
<string name="common_import">インポート</string>
<string name="common_in"></string>
<string name="common_in_progress">進行中</string>
<string name="common_insufficient_balance">残高不足</string>
<string name="common_later">後で</string>
@ -355,6 +362,8 @@
<string name="common_range_with_space">%1$s — %2$s</string>
<string name="common_read_more">続きを読む</string>
<string name="common_receive">受け取る</string>
<string name="common_received">受け取り済み</string>
<string name="common_receiving">受け取り中</string>
<string name="common_recommended">おすすめ</string>
<string name="common_reject">拒否</string>
<string name="common_reload">リロード</string>
@ -373,6 +382,8 @@
<string name="common_send">送る</string>
<string name="common_send_colon">送金:</string>
<string name="common_send_tx_error">取引の送信に失敗しました</string>
<string name="common_sending">送金中</string>
<string name="common_sent">送金済み</string>
<string name="common_server_unavailable">サーバーが利用できません。しばらくしてからもう一度お試しください。</string>
<string name="common_share">共有</string>
<string name="common_share_link">リンクを共有</string>
@ -383,6 +394,7 @@
<string name="common_skip">スキップ</string>
<string name="common_something_went_wrong">問題が発生しました</string>
<string name="common_stake">ステーキング</string>
<string name="common_staked">ステーキング済み</string>
<string name="common_staking">ステーキング</string>
<string name="common_start">始める</string>
<string name="common_submit">送信</string>
@ -390,6 +402,8 @@
<string name="common_support">サポート</string>
<string name="common_supported_networks">対応ネットワーク</string>
<string name="common_swap">スワップ</string>
<string name="common_swapped">スワップ済み</string>
<string name="common_swapping">スワップ中</string>
<string name="common_tangem">Tangem</string>
<string name="common_tangem_wallet">Tangem Wallet</string>
<string name="common_tap_and_hold_hint">タップして長押し</string>
@ -398,6 +412,7 @@
<string name="common_to">宛先</string>
<string name="common_to_wallet_name">%sへ</string>
<string name="common_today">今日</string>
<string name="common_token">トークン</string>
<string name="common_token_send">送信するトークン</string>
<plurals name="common_tokens_count">
<item quantity="other">%d トークン</item>
@ -406,6 +421,7 @@
<string name="common_transaction_status">取引状況</string>
<string name="common_transactions">取引</string>
<string name="common_transfer">送金</string>
<string name="common_transferred">送金済み</string>
<string name="common_unable_to_load">データを読み込めません…</string>
<string name="common_understand">わかりました</string>
<string name="common_understand_continue">理解して続行</string>
@ -415,10 +431,12 @@
<string name="common_unstake">ステーキング解除</string>
<string name="common_utxo_validate_withdrawal_message_warning">%1$sの制限により、1つのトランザクションに収まるUTXOは%2$d個のみです。つまり、 %3$s以下しか送信できません。量を減らす必要があります。</string>
<string name="common_value_copied">値がコピーされました</string>
<string name="common_voting">投票</string>
<string name="common_wallets">ウォレット</string>
<string name="common_warning">警告</string>
<string name="common_week"></string>
<string name="common_with"></string>
<string name="common_withdrawing">引き出し中</string>
<string name="common_yes">はい</string>
<string name="common_yield_mode">利息モード</string>
<string name="contract_address_copied_message">コントラクトアドレスをコピーしました!</string>
@ -580,12 +598,14 @@
<string name="express_provider_best_rate">ベストレート</string>
<string name="express_provider_fca_warning_list">FCA警告リスト</string>
<string name="express_provider_fixed_rate_is_unavailable">固定レートは利用できません</string>
<string name="express_provider_for_swap">スワッププロバイダー</string>
<string name="express_provider_great_rate">お得なレート</string>
<string name="express_provider_in_fca_warning_list">FCA警告リストに掲載されたプロバイダー</string>
<string name="express_provider_max_amount">最大 %s まで使用可能</string>
<string name="express_provider_min_amount">%s 以上で利用可能</string>
<string name="express_provider_not_available">このペアは利用できません</string>
<string name="express_provider_permission_needed">許可が必要です</string>
<string name="express_provider_permission_needed_v2">権限が必要です</string>
<string name="express_provider_recommended">推奨</string>
<string name="express_status_bought">%sを買い付けました</string>
<string name="express_status_buying">%sを買い付けています</string>
@ -633,6 +653,7 @@
<string name="give_permission_staking_footer">承認機能は、別のアドレスに特定の量のトークンを使用する許可を与えるために必要です。設計上スマートコントラクトは、承認しない限りトークンにアクセスできません。トークンを「ロック解除」すると、StakeKitスマート コントラクトがトークンを使用する権限が与えられます。ネットワークのマイナーは、このアクションをブロックチェーンに記録するためにガス料金(あなたが支払う)を受け取ります。承認後、トークンをステーキングできます。</string>
<string name="give_permission_staking_subtitle">続行するには、Polygonスマートコントラクトが%sを使用することを許可する必要があります</string>
<string name="give_permission_swap_subtitle" formatted="false">続行するには、%1sスマートコントラクトに%2sを使用する権限を付与してください</string>
<string name="give_permission_swap_subtitle_v2">分散型取引所がウォレットと連携するには、許可が必要です。%1s</string>
<string name="give_permission_title">許可を与える</string>
<string name="give_permission_unlimited">無制限</string>
<string name="hardware_wallet_backup_feature_description">アドレスはTangemハードウェアウォレット上で直接生成され、そのまま安全に使用できます。</string>
@ -660,6 +681,8 @@
<string name="hw_backup_banner_description">暗号資産をオフラインで厳重保管。カードサイズで、金庫以上の安心を。</string>
<string name="hw_backup_close_description">実行すると、最初からやり直す必要があります。</string>
<string name="hw_backup_google_drive_description">Googleドライブのバックアップから既存のウォレットを復元する</string>
<string name="hw_backup_google_drive_dialog_message">ウォレットの復元をさらに簡単にするため、Googleドライブバックアップ機能を準備しています。</string>
<string name="hw_backup_google_drive_dialog_title">Googleドライブバックアップは近日対応予定です</string>
<string name="hw_backup_google_drive_title">Googleドライブのバックアップ</string>
<string name="hw_backup_hardware_create_description">さらに強固なセキュリティのために、新しいウォレットを作成して資産を移動しましょう。</string>
<string name="hw_backup_hardware_create_title">新しいウォレットを作成</string>
@ -1103,6 +1126,8 @@
<string name="onramp_kyc_verification_choose_another">別の方法を選択してください。</string>
<string name="onramp_kyc_verification_subtitle">現地の規制要件に準拠するため、%@の利用には本人確認が必要です。</string>
<string name="onramp_kyc_verification_title">決済プロバイダーによる本人確認が必要です。</string>
<string name="onramp_kyc_verification_verify_button">認証する</string>
<string name="onramp_kyc_verification_whats_important">重要事項</string>
<string name="onramp_legal">オンランプ機能を使用することにより、プロバイダの%1$sおよび%2$sに同意するものとします</string>
<string name="onramp_legal_text">サービスは外部プロバイダーによって提供されます。 \nTangemは責任を負いません。</string>
<string name="onramp_max_amount_restriction">買付金額は%s以下にしてください</string>
@ -1166,7 +1191,11 @@
<string name="qr_scanner_warning_unknown_parameters_title">不明なパラメータ</string>
<string name="quick_action_buy_description">クレジットカードまたは銀行口座</string>
<string name="quick_action_receive_description">アドレスまたはQRコードを共有してください</string>
<string name="quick_action_sell_description">暗号資産を安全に売却</string>
<string name="quick_action_send_description">別のウォレットに送信</string>
<string name="quick_action_swap_description">ポートフォリオ間で</string>
<string name="quick_top_up_chip_other">その他</string>
<string name="quick_top_up_title">クイック入金</string>
<string name="receive_bottom_sheet_no_memo_required_message">メモ不要</string>
<string name="receive_bottom_sheet_warning_message">%3$sネットワーク上の%1$s ( %2$s )</string>
<string name="receive_bottom_sheet_warning_message_compact">%2$sネットワーク上の%1$s</string>
@ -1522,6 +1551,7 @@
<string name="swap_approve_description">承認すると、このスマートコントラクトが今後の取引でトークンを利用できるようになります。</string>
<string name="swap_fixed_rate">固定レート</string>
<string name="swap_give_permission_fee_footer">ネットワークは、あなたがトークンのスワップを承認していることを確認するために、トークン承認手数料を請求します。</string>
<string name="swap_in_progress">スワップ中</string>
<string name="swap_promo_text">より多くのトークンをより良いレートで、ウォレット内にて直接交換します。</string>
<string name="swap_promo_title">新しいスワッププロバイダーが利用可能になりました!</string>
<string name="swap_search_suggestion_hint">他のものをお探しですか?\n検索してみるか、別の暗号資産をチェックしてみましょう</string>
@ -1530,11 +1560,11 @@
<string name="swap_story_fifth_subtitle">24時間体制のサポートであらゆる問題に対応します。</string>
<string name="swap_story_fifth_title">いつもここに</string>
<string name="swap_story_first_subtitle">複数の信頼できるプロバイダーが一箇所に集結。ウォレット内で様々な暗号資産を簡単に交換できます。</string>
<string name="swap_story_first_subtitle_v2">Tangem内で暗号資産を直接交換\n追加の送金は不要\n取引所に資産を移す必要なし</string>
<string name="swap_story_first_subtitle_v2">Tangem内で暗号資産を直接交換できます\n追加の送金は不要です\n取引所に資金を移す必要もありません</string>
<string name="swap_story_first_title">ぜひスワップしてください</string>
<string name="swap_story_first_title_v2">ウォレット内でスワップ</string>
<string name="swap_story_forth_subtitle">失敗も死角もありません。取引は常に保護されます。</string>
<string name="swap_story_forth_subtitle_v2">スワップは信頼できるプロバイダーを通じて実行されます。秘密鍵は常にTangemウォレット内に保持されます。シンプル。透明。自己管理</string>
<string name="swap_story_forth_subtitle_v2">スワップは信頼できるプロバイダーを通じて実行されます。秘密鍵は常にTangemウォレット内に保持されます。明確で、透明性が高く、自己管理型です</string>
<string name="swap_story_forth_title">難攻不落の防御</string>
<string name="swap_story_forth_title_v2">主導権はあなたの手にあります</string>
<string name="swap_story_second_subtitle">幅広いネットワークの中から、常に最適なプロバイダーと料金レートを選択します</string>
@ -1555,7 +1585,9 @@
<string name="swapping_approve_information_text">すべての分散型取引所は、スマートコントラクトがあなたの許可なくウォレットにアクセスするのを防ぐために承認を必要とします。設計上、スマートコントラクトは承認なしでトークンにアクセスできません。トークンを「ロック解除」することで、あなたは1-inchのスマートコントラクトがトークンを使うことを承認します。ネットワークのマイナーは、このアクションをブロックチェーンに記録するためのガス料金あなたが支払うを受け取ります。承認後、トークンを交換することができます。</string>
<string name="swapping_approve_information_title">承認</string>
<string name="swapping_fee_estimation_error_text">手数料見積りエラーです。サポートにフィードバックをお送りください。</string>
<string name="swapping_from_account_title">送信元</string>
<string name="swapping_from_title">スワップする</string>
<string name="swapping_from_title_v2">送信</string>
<string name="swapping_high_price_impact_description">選択したトークンをこの量を交換すると、価格に大きな影響が生じ、結果が減少します。</string>
<string name="swapping_high_price_impact_text">流動性が低いため、受取額が大幅に少なくなる可能性があります。金額を減らすか、別のプロバイダーをお試しください。</string>
<string name="swapping_high_price_impact_title">価格への影響が甚大です</string>
@ -1564,11 +1596,14 @@
<string name="swapping_permission_header">許可を与える</string>
<string name="swapping_swap_action">スワップ</string>
<string name="swapping_swap_action_in_progress">スワップ中…</string>
<string name="swapping_to_account_title">受け取り先</string>
<string name="swapping_to_title">受け取る</string>
<string name="swapping_token_list_title">トークンを選択</string>
<string name="swapping_token_not_available">利用不可</string>
<string name="swapping_trade_too_large_text">この取引に十分な流動性がありません。\n金額を減らすか、別のプロバイダーを選択してください。</string>
<string name="swapping_trade_too_large_title">取引額が大きすぎます</string>
<string name="swapping_transfer_action">送金</string>
<string name="swapping_transfer_action_in_progress">送金...</string>
<string name="tangem_pay_beta_notification_subtitle">皆様からのフィードバックをお待ちしております</string>
<string name="tangem_pay_beta_notification_title">Tangem Payのベータ版を公開しました</string>
<string name="tangem_pay_card_details_unable_to_rename_card_title">カード名を変更できません</string>
@ -1657,6 +1692,7 @@
<string name="tangempay_card_details_withdraw_error_title">現在、出金ができません</string>
<string name="tangempay_card_details_withdraw_in_progress_description">現在の処理が完了するまでは、スワップや新しい出金を開始することはできません。</string>
<string name="tangempay_card_details_withdraw_in_progress_title">出金処理中です</string>
<string name="tangempay_card_edit_name_placeholder">カード名</string>
<string name="tangempay_card_limit_setup_amount_subtitle">%s以上の金額を設定してください</string>
<string name="tangempay_card_limit_setup_error_message">限度額を設定できませんでした。もう一度お試しください</string>
<string name="tangempay_card_page_daily_limit_change">変更</string>
@ -1708,6 +1744,8 @@
<string name="tangempay_onboarding_purchases_title">余計な費用なしで、表示金額のみを支払う</string>
<string name="tangempay_onboarding_security_description">あなたの住所や資産を開示することなく、個別の支払い用アカウントが作成されます。</string>
<string name="tangempay_onboarding_security_title">他に類を見ないプライバシー</string>
<string name="tangempay_onboarding_setup_wallet_description">そして支払いカードを連携します</string>
<string name="tangempay_onboarding_setup_wallet_title">ウォレットを設定します</string>
<string name="tangempay_onboarding_title">無料のTangem Payカードを数分でゲットしましょう</string>
<string name="tangempay_pay_support">Payサポート</string>
<string name="tangempay_payment_account">支払いアカウント</string>
@ -1757,7 +1795,7 @@
<string name="token_button_unavailability_reason_yield_supply_approval">承認が取り消されましたが、あなたの資金は引き続き利息モードです。操作を行うには、利息モードに移動し、再度承認を付与してください。</string>
<string name="token_details_balance_available">利用可能残高</string>
<string name="token_details_balance_total">合計残高</string>
<string name="token_details_earn_staking_subtitle">間で最大%sを獲得</string>
<string name="token_details_earn_staking_subtitle">利最大%s</string>
<string name="token_details_generate_xpub">XPUBを生成する</string>
<string name="token_details_hide_alert_hide">非表示</string>
<string name="token_details_hide_alert_message">このトークンをメイン画面から非表示にします。トークンの管理ページからいつでも再度追加できます。</string>
@ -1783,12 +1821,21 @@
<string name="tokens_list_unavailable_to_sell_header">売却できません</string>
<string name="tokens_list_unavailable_to_swap_header">%sからのスワップは利用できません</string>
<string name="tokens_list_unavailable_to_swap_source_header">スワップはできません</string>
<string name="transaction_history_claiming_reward">報酬を請求中</string>
<string name="transaction_history_contract_address">コントラクト: %s</string>
<string name="transaction_history_disabling_yield_mode">利息モードを無効化中</string>
<string name="transaction_history_earned_from_stake">ステーキングで獲得した金額</string>
<string name="transaction_history_empty_transactions">まだ取引はありません</string>
<string name="transaction_history_error_failed_to_load">取引履歴の読み込みに失敗しました。\n情報を更新するには、リロードボタンをクリックしてください。</string>
<string name="transaction_history_from_inline_address">%%image%% %s から</string>
<string name="transaction_history_multiple_addresses">複数のアドレス</string>
<string name="transaction_history_not_supported_description">現在、このブロックチェーンでは取引履歴はサポートされていません。しかしご心配なく!弊社で対応中です。その間、エクスプローラーで確認することができます。</string>
<string name="transaction_history_operation">オペレーション</string>
<string name="transaction_history_pending">保留中</string>
<string name="transaction_history_rewards_restaked">報酬を再ステーキングしました</string>
<string name="transaction_history_rewards_restaking">報酬の再ステーキング</string>
<string name="transaction_history_staking_reward">ステーキング報酬</string>
<string name="transaction_history_to_inline_address">%%image%% %s へ</string>
<string name="transaction_history_transaction_for_address">対象:%s</string>
<string name="transaction_history_transaction_from_address">送金元: %s</string>
<string name="transaction_history_transaction_to_address">送金先: %s</string>
@ -2064,7 +2111,7 @@
<string name="warning_matic_migration_message">MATICはPOLに移行中です。ただし、期限は設定されておらず、MATICはまだ廃止されていません。MATICトークンを引き続き安全に使用することも、POLに交換することもできます。</string>
<string name="warning_matic_migration_title">MATICからPOLへの移行</string>
<plurals name="warning_missing_derivation_message">
<item quantity="other">%d ネットワークのアドレスを取得するために、カードまたはリングを利用してください</item>
<item quantity="other">カードまたはリングを使って、[%i}ネットワークのアドレスを取得します</item>
</plurals>
<string name="warning_missing_derivation_title">一部のアドレスが見つかりません</string>
<string name="warning_network_unreachable_message">現在、ネットワークにアクセスできません。しばらくしてからもう一度お試しください。</string>

View file

@ -320,6 +320,7 @@
<item quantity="other">OUTRO</item>
</plurals>
<string name="common_import">Importar</string>
<string name="common_in">em</string>
<string name="common_in_progress">Em andamento</string>
<string name="common_insufficient_balance">Saldo insuficiente</string>
<string name="common_later">Mais tarde</string>
@ -407,6 +408,7 @@
<string name="common_to">Para</string>
<string name="common_to_wallet_name">Para %s</string>
<string name="common_today">Hoje</string>
<string name="common_token">Token</string>
<string name="common_token_send">Token a ser enviado</string>
<plurals name="common_tokens_count">
<item quantity="one">token</item>

View file

@ -339,6 +339,7 @@
<item quantity="other">%dч назад</item>
</plurals>
<string name="common_import">Импортировать</string>
<string name="common_in">в</string>
<string name="common_in_progress">В процессе</string>
<string name="common_insufficient_balance">Недостаточный баланс</string>
<string name="common_later">Позже</string>
@ -429,6 +430,7 @@
<string name="common_to">На</string>
<string name="common_to_wallet_name">На %s</string>
<string name="common_today">Сегодня</string>
<string name="common_token">Токен</string>
<string name="common_token_send">Токен к отправке</string>
<plurals name="common_tokens_count">
<item quantity="one">%d токен</item>

View file

@ -321,6 +321,7 @@
<item quantity="other">%d годин тому</item>
</plurals>
<string name="common_import">Імпортувати</string>
<string name="common_in">у</string>
<string name="common_in_progress">В процесі</string>
<string name="common_later">Пізніше</string>
<string name="common_learn_more">Дізнатися більше</string>
@ -408,6 +409,7 @@
<string name="common_to">До</string>
<string name="common_to_wallet_name">На %s</string>
<string name="common_today">Сьогодні</string>
<string name="common_token">Токен</string>
<plurals name="common_tokens_count">
<item quantity="one">%d токен</item>
<item quantity="few">%d токени</item>

View file

@ -312,6 +312,7 @@
<item quantity="other">小时之前</item>
</plurals>
<string name="common_import">导入</string>
<string name="common_in"></string>
<string name="common_in_progress">进行中</string>
<string name="common_insufficient_balance">余额不足</string>
<string name="common_later">稍后</string>
@ -397,6 +398,7 @@
<string name="common_to"></string>
<string name="common_to_wallet_name">到 %s</string>
<string name="common_today">今天</string>
<string name="common_token">代币</string>
<string name="common_token_send">要发送的代币</string>
<plurals name="common_tokens_count">
<item quantity="other">%d代币</item>

View file

@ -68,6 +68,7 @@
<string name="common_error">錯誤</string>
<string name="common_fee_error">獲取費用失敗</string>
<string name="common_import">導入</string>
<string name="common_in"></string>
<string name="common_in_progress">進行中</string>
<string name="common_network_fee_title">網路費</string>
<string name="common_no"></string>
@ -92,6 +93,7 @@
<string name="common_success">成功</string>
<string name="common_swap">交換</string>
<string name="common_terms_and_conditions">條款和條件</string>
<string name="common_token">代幣</string>
<plurals name="common_tokens_count">
<item quantity="other">%d 代幣</item>
</plurals>

View file

@ -327,6 +327,7 @@
<item quantity="other">%dh ago</item>
</plurals>
<string name="common_import">Import</string>
<string name="common_in">in</string>
<string name="common_in_progress">In progress</string>
<string name="common_insufficient_balance">Insufficient balance</string>
<string name="common_later">Later</string>
@ -421,6 +422,7 @@
<string name="common_to">To</string>
<string name="common_to_wallet_name">To %s</string>
<string name="common_today">Today</string>
<string name="common_token">Token</string>
<string name="common_token_send">Token to send</string>
<plurals name="common_tokens_count">
<item quantity="one">%d token</item>
@ -1578,8 +1580,10 @@
<string name="swap_give_permission_fee_footer">The network will charge a token approval fee to verify that you are authorizing the use of your token for the swap.</string>
<string name="swap_in_progress">Swap in progress</string>
<string name="swap_promo_text">Exchange more tokens at better rates directly in your wallet.</string>
<string name="swap_detailed_mode">Detailed mode</string>
<string name="swap_promo_title">New Swap Provider Available!</string>
<string name="swap_search_suggestion_hint">Looking for something else?\nTry searching or explore another crypto!</string>
<string name="swap_simple_mode">Simple mode</string>
<string name="swap_search_tooltip_description">Search for any token, even if its not in your list yet.</string>
<string name="swap_search_tooltip_title">Use search to find what you need</string>
<string name="swap_story_fifth_subtitle">Feel confident with round-the-clock support to help with any issues</string>

View file

@ -21,6 +21,7 @@ import com.tangem.datasource.exchangeservice.swap.ExpressUtils
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.preferences.PreferencesKeys
import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull
import com.tangem.datasource.local.preferences.utils.storeObject
import com.tangem.domain.exchange.RampStateManager
import com.tangem.domain.express.models.ExpressOperationType
import com.tangem.domain.models.currency.CryptoCurrency
@ -419,4 +420,8 @@ internal class DefaultSwapRepository(
override suspend fun getStoredSwapUiMode(): SwapUIMode? {
return appPreferencesStore.getObjectSyncOrNull(PreferencesKeys.SWAP_UI_MODE_KEY)
}
override suspend fun storeSwapUiMode(mode: SwapUIMode) {
appPreferencesStore.storeObject(PreferencesKeys.SWAP_UI_MODE_KEY, mode)
}
}

View file

@ -0,0 +1,13 @@
package com.tangem.feature.swap.domain
import com.tangem.feature.swap.domain.api.SwapRepository
import com.tangem.feature.swap.domain.models.domain.SwapUIMode
class SetSwapUiModeUseCase(
private val swapRepository: SwapRepository,
) {
suspend operator fun invoke(mode: SwapUIMode) {
swapRepository.storeSwapUiMode(mode)
}
}

View file

@ -77,4 +77,6 @@ interface SwapRepository {
): Either<ExpressDataError, Unit>
suspend fun getStoredSwapUiMode(): SwapUIMode?
suspend fun storeSwapUiMode(mode: SwapUIMode)
}

View file

@ -3,6 +3,7 @@ package com.tangem.feature.swap.domain.di
import com.tangem.feature.swap.domain.AllowPermissionsHandler
import com.tangem.feature.swap.domain.AllowPermissionsHandlerImpl
import com.tangem.feature.swap.domain.GetSwapUiModeUseCase
import com.tangem.feature.swap.domain.SetSwapUiModeUseCase
import com.tangem.feature.swap.domain.SwapInteractor
import com.tangem.feature.swap.domain.SwapInteractorImpl
import com.tangem.feature.swap.domain.api.SwapRepository
@ -35,6 +36,11 @@ internal class SwapDomainModule {
swapFeatureToggles = swapFeatureToggles,
swapRepository = swapRepository,
)
@Provides
@Singleton
fun provideSetSwapUiModeUseCase(swapRepository: SwapRepository): SetSwapUiModeUseCase =
SetSwapUiModeUseCase(swapRepository = swapRepository)
}
@Module

View file

@ -0,0 +1,29 @@
package com.tangem.feature.swap.domain
import com.tangem.feature.swap.domain.api.SwapRepository
import com.tangem.feature.swap.domain.models.domain.SwapUIMode
import io.mockk.coVerify
import io.mockk.mockk
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
internal class SetSwapUiModeUseCaseTest {
private val swapRepository: SwapRepository = mockk(relaxUnitFun = true)
private val useCase = SetSwapUiModeUseCase(swapRepository = swapRepository)
@Test
fun `GIVEN Simple mode WHEN invoke THEN delegates to repository`() = runTest {
useCase.invoke(SwapUIMode.Simple)
coVerify(exactly = 1) { swapRepository.storeSwapUiMode(SwapUIMode.Simple) }
}
@Test
fun `GIVEN Detailed mode WHEN invoke THEN delegates to repository`() = runTest {
useCase.invoke(SwapUIMode.Detailed)
coVerify(exactly = 1) { swapRepository.storeSwapUiMode(SwapUIMode.Detailed) }
}
}

View file

@ -74,6 +74,7 @@ import com.tangem.feature.swap.component.SwapFeeSelectorBlockComponent
import com.tangem.feature.swap.converters.SwapTransactionErrorStateConverter
import com.tangem.feature.swap.domain.AllowPermissionsHandler
import com.tangem.feature.swap.domain.GetSwapUiModeUseCase
import com.tangem.feature.swap.domain.SetSwapUiModeUseCase
import com.tangem.feature.swap.domain.SwapInteractor
import com.tangem.feature.swap.domain.TransactionFeeResult
import com.tangem.feature.swap.domain.TxFeeSealedState
@ -83,6 +84,7 @@ import com.tangem.feature.swap.domain.models.domain.ExchangeProviderType
import com.tangem.feature.swap.domain.models.domain.SwapDataModel
import com.tangem.feature.swap.domain.models.domain.SwapPairLeast
import com.tangem.feature.swap.domain.models.domain.SwapProvider
import com.tangem.feature.swap.domain.models.domain.SwapUIMode
import com.tangem.feature.swap.domain.models.ui.*
import com.tangem.feature.swap.domain.transfer.SwapTransferInteractor
import com.tangem.feature.swap.models.*
@ -157,6 +159,7 @@ internal class SwapModel @Inject constructor(
private val allowPermissionsHandler: AllowPermissionsHandler,
private val swapFeatureToggles: SwapFeatureToggles,
private val getSwapUiModeUseCase: GetSwapUiModeUseCase,
private val setSwapUiModeUseCase: SetSwapUiModeUseCase,
) : Model() {
private val params = paramsContainer.require<SwapComponent.Params>()
@ -192,6 +195,7 @@ internal class SwapModel @Inject constructor(
appCurrencyProvider = Provider(selectedAppCurrencyFlow::value),
isAccountsModeProvider = Provider { isAccountsMode },
isGaslessFeeSupportedForNetwork = isGaslessFeeSupportedForNetwork,
shouldShowAbMenu = swapFeatureToggles.isSwapAbEnabled,
)
private val inputNumberFormatter = InputNumberFormatter(
@ -1621,9 +1625,16 @@ internal class SwapModel @Inject constructor(
onSuccess = {
router.replaceAll(SwapRoute.Success)
},
onSwapUIModeChange = ::onSwapUIModeChange,
)
}
private fun onSwapUIModeChange(mode: SwapUIMode) {
if (uiState.swapUIMode == mode) return
uiState = uiState.copy(swapUIMode = mode)
modelScope.launch { setSwapUiModeUseCase(mode) }
}
private fun selectWalletInSelector(
fromSwapCurrencyStatus: SwapCurrencyStatus?,
toSwapCurrencyStatus: SwapCurrencyStatus?,

View file

@ -33,6 +33,7 @@ internal data class SwapStateHolder(
val shouldShowMaxAmount: Boolean,
val tosState: TosState? = null,
val swapUIMode: SwapUIMode = SwapUIMode.Detailed,
val shouldShowAbMenu: Boolean = false,
val onRefresh: () -> Unit,
val onBackClicked: () -> Unit,
@ -41,6 +42,7 @@ internal data class SwapStateHolder(
val onSuccess: (() -> Unit),
val onMaxAmountSelected: (() -> Unit)? = null,
val onShowPermissionBottomSheet: () -> Unit = {},
val onSwapUIModeChange: (SwapUIMode) -> Unit = {},
)
@Immutable

View file

@ -1,6 +1,7 @@
package com.tangem.feature.swap.models
import com.tangem.feature.swap.domain.models.SwapAmount
import com.tangem.feature.swap.domain.models.domain.SwapUIMode
import com.tangem.feature.swap.domain.models.ui.TxFee
import java.math.BigDecimal
@ -26,4 +27,5 @@ internal data class UiActions(
val onSuccess: () -> Unit,
val onLinkClick: (String) -> Unit,
val onReceiveCardWarningClick: () -> Unit,
val onSwapUIModeChange: (SwapUIMode) -> Unit,
)

View file

@ -0,0 +1,156 @@
package com.tangem.feature.swap.ui
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.dp
import coil.compose.SubcomposeAsyncImage
import coil.request.ImageRequest
import com.tangem.core.ui.R
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.SpacerW8
import com.tangem.core.ui.components.SpacerWMax
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.feature.swap.models.states.PercentDifference
import com.tangem.feature.swap.models.states.ProviderState
// TODO: [REDACTED_TASK_KEY] — remove this UI after swap migrates to swap-v2.
// Layout copied from V2 `SwapChooseProviderContent`:
// features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/ui/SwapChooseProviderContent.kt
@Composable
internal fun ProviderItemBlockSimple(state: ProviderState, modifier: Modifier = Modifier) {
if (state is ProviderState.Empty) return
Row(
modifier = modifier
.clip(RoundedCornerShape(TangemTheme.dimens.radius16))
.background(TangemTheme.colors.background.primary)
.clickable(
enabled = state.onProviderClick != null,
onClick = { state.onProviderClick?.invoke(state.id) },
)
.fillMaxWidth()
.padding(TangemTheme.dimens.spacing12),
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
painter = painterResource(id = R.drawable.ic_stack_new_24),
tint = TangemTheme.colors.icon.accent,
contentDescription = null,
modifier = Modifier.size(TangemTheme.dimens.size24),
)
SpacerW8()
Text(
text = stringResourceSafe(R.string.express_provider),
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.primary1,
)
SpacerWMax()
SimpleProviderTrailing(state = state)
}
}
@Composable
private fun SimpleProviderTrailing(state: ProviderState) {
when (state) {
is ProviderState.Content -> {
SubcomposeAsyncImage(
model = ImageRequest.Builder(context = LocalContext.current)
.data(state.iconUrl)
.crossfade(enable = true)
.allowHardware(false)
.build(),
loading = { RectangleShimmer(radius = 4.dp) },
error = { RectangleShimmer(radius = 4.dp) },
contentDescription = null,
modifier = Modifier
.size(TangemTheme.dimens.size20)
.clip(RoundedCornerShape(TangemTheme.dimens.radius4)),
)
Text(
text = state.name,
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.tertiary,
modifier = Modifier.padding(start = TangemTheme.dimens.spacing6),
)
Icon(
painter = painterResource(id = R.drawable.ic_chevron_24),
tint = TangemTheme.colors.icon.secondary,
contentDescription = null,
modifier = Modifier.size(TangemTheme.dimens.size24),
)
}
is ProviderState.Loading -> {
RectangleShimmer(
modifier = Modifier
.size(width = TangemTheme.dimens.size80, height = TangemTheme.dimens.size20),
radius = TangemTheme.dimens.radius4,
)
}
is ProviderState.Unavailable -> {
Text(
text = state.alertText.resolveReference(),
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.warning,
)
}
is ProviderState.Empty -> Unit
}
}
// region Preview
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun ProviderItemBlockSimple_Preview(@PreviewParameter(SimpleProviderPreview::class) state: ProviderState) {
TangemThemePreview {
ProviderItemBlockSimple(state = state)
}
}
private class SimpleProviderPreview : PreviewParameterProvider<ProviderState> {
override val values: Sequence<ProviderState> = sequenceOf(
ProviderState.Content(
id = "1",
name = "Changelly",
type = "CEX",
iconUrl = "",
subtitle = stringReference("1 SOL ≈ 0.0011337 BTC"),
selectionType = ProviderState.SelectionType.CLICK,
additionalBadge = ProviderState.AdditionalBadge.Empty,
percentLowerThenBest = PercentDifference.Empty,
namePrefix = ProviderState.PrefixType.NONE,
onProviderClick = {},
),
ProviderState.Loading(),
ProviderState.Unavailable(
id = "2",
name = "1inch",
type = "DEX",
iconUrl = "",
alertText = stringReference("Unavailable"),
selectionType = ProviderState.SelectionType.NONE,
),
)
}
// endregion

View file

@ -59,6 +59,7 @@ internal class StateBuilder(
private val appCurrencyProvider: Provider<AppCurrency>,
private val isAccountsModeProvider: Provider<Boolean>,
private val isGaslessFeeSupportedForNetwork: IsGaslessFeeSupportedForNetwork,
private val shouldShowAbMenu: Boolean,
) {
private val iconStateConverter by lazy(::CryptoCurrencyToIconStateConverter)
@ -97,6 +98,8 @@ internal class StateBuilder(
priceImpact = PriceImpact.Empty,
isInsufficientFunds = false,
swapUIMode = swapUIMode,
onSwapUIModeChange = actions.onSwapUIModeChange,
shouldShowAbMenu = shouldShowAbMenu,
)
}

View file

@ -2,19 +2,39 @@ package com.tangem.feature.swap.ui
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Icon
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.testTag
import com.tangem.core.ui.components.appbar.AppBarWithBackButton
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.appbar.AppBarWithBackButtonAndIcon
import com.tangem.core.ui.components.dropdownmenu.TangemDropdownMenu
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.test.SwapTokenScreenTestTags
import com.tangem.core.ui.utils.WindowInsetsZero
import com.tangem.feature.swap.component.SwapFeeSelectorBlockComponent
import com.tangem.feature.swap.domain.models.domain.SwapUIMode
import com.tangem.feature.swap.models.SwapStateHolder
import com.tangem.feature.swap.models.states.ChooseFeeBottomSheetConfig
import com.tangem.feature.swap.models.states.ChooseProviderBottomSheetConfig
@ -26,13 +46,7 @@ internal fun SwapScreen(stateHolder: SwapStateHolder, feeSelectorBlockComponent:
Scaffold(
modifier = Modifier.systemBarsPadding(),
topBar = {
AppBarWithBackButton(
text = stringResourceSafe(R.string.common_swap),
onBackClick = stateHolder.onBackClicked,
iconRes = R.drawable.ic_close_24,
)
},
topBar = { SwapTopBar(stateHolder = stateHolder) },
contentWindowInsets = WindowInsetsZero,
containerColor = TangemTheme.colors.background.secondary,
) { scaffoldPaddings ->
@ -64,4 +78,79 @@ internal fun SwapScreen(stateHolder: SwapStateHolder, feeSelectorBlockComponent:
}
}
}
}
@Composable
private fun SwapTopBar(stateHolder: SwapStateHolder) {
var shouldShowModeMenu by rememberSaveable { mutableStateOf(false) }
Box(modifier = Modifier.fillMaxWidth()) {
AppBarWithBackButtonAndIcon(
text = stringResourceSafe(R.string.common_swap),
backIconRes = R.drawable.ic_close_24,
iconRes = if (stateHolder.shouldShowAbMenu) R.drawable.ic_more_vertical_24 else null,
onIconClick = if (stateHolder.shouldShowAbMenu) {
{ shouldShowModeMenu = true }
} else {
null
},
onBackClick = stateHolder.onBackClicked,
)
if (stateHolder.shouldShowAbMenu) {
Box(modifier = Modifier.align(Alignment.TopEnd)) {
TangemDropdownMenu(
expanded = shouldShowModeMenu,
modifier = Modifier.background(TangemTheme.colors.background.primary),
offset = DpOffset(x = TangemTheme.dimens.spacing20, y = 44.dp),
onDismissRequest = { shouldShowModeMenu = false },
content = {
SwapUiModeMenuItem(
title = stringResourceSafe(R.string.swap_simple_mode),
isSelected = stateHolder.swapUIMode == SwapUIMode.Simple,
onClick = {
shouldShowModeMenu = false
stateHolder.onSwapUIModeChange(SwapUIMode.Simple)
},
)
SwapUiModeMenuItem(
title = stringResourceSafe(R.string.swap_detailed_mode),
isSelected = stateHolder.swapUIMode == SwapUIMode.Detailed,
onClick = {
shouldShowModeMenu = false
stateHolder.onSwapUIModeChange(SwapUIMode.Detailed)
},
)
},
)
}
}
}
}
@Composable
private fun SwapUiModeMenuItem(title: String, isSelected: Boolean, onClick: () -> Unit) {
Row(
modifier = Modifier
.fillMaxWidth()
.clickable(onClick = onClick)
.padding(horizontal = 16.dp, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(16.dp),
) {
Text(
text = title,
style = TangemTheme.typography.button,
color = TangemTheme.colors.text.primary1,
modifier = Modifier.weight(1f),
)
if (isSelected) {
Icon(
painter = painterResource(id = R.drawable.ic_check_24),
tint = TangemTheme.colors.icon.primary1,
contentDescription = null,
modifier = Modifier.size(16.dp),
)
} else {
Spacer(modifier = Modifier.width(16.dp))
}
}
}

View file

@ -39,6 +39,7 @@ import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.SwapTokenScreenTestTags
import com.tangem.feature.swap.domain.models.domain.SwapUIMode
import com.tangem.feature.swap.domain.models.ui.FeeType
import com.tangem.feature.swap.domain.models.ui.PriceImpact
import com.tangem.feature.swap.models.*
@ -79,7 +80,11 @@ internal fun SwapScreenContent(
) {
MainInfo(state)
ProviderItemBlock(state = state.providerState)
if (state.swapUIMode == SwapUIMode.Simple) {
ProviderItemBlockSimple(state = state.providerState)
} else {
ProviderItemBlock(state = state.providerState)
}
if (feeBlock != null) {
feeBlock(Modifier.fillMaxWidth())
@ -139,14 +144,25 @@ private fun MainInfo(state: SwapStateHolder) {
onSelectTokenClick = { state.onSelectTokenClick(TokenSelectionDirection.FROM) },
)
val marginCard = TangemTheme.dimens.spacing12
TransactionCard(
priceImpact = priceImpact,
swapCardState = state.receiveCardData,
modifier = Modifier.constrainAs(bottomCard) {
top.linkTo(topCard.bottom, margin = marginCard)
},
onSelectTokenClick = { state.onSelectTokenClick(TokenSelectionDirection.TO) },
)
if (state.swapUIMode == SwapUIMode.Simple) {
TransactionCardSimple(
priceImpact = priceImpact,
swapCardState = state.receiveCardData,
modifier = Modifier.constrainAs(bottomCard) {
top.linkTo(topCard.bottom, margin = marginCard)
},
onSelectTokenClick = { state.onSelectTokenClick(TokenSelectionDirection.TO) },
)
} else {
TransactionCard(
priceImpact = priceImpact,
swapCardState = state.receiveCardData,
modifier = Modifier.constrainAs(bottomCard) {
top.linkTo(topCard.bottom, margin = marginCard)
},
onSelectTokenClick = { state.onSelectTokenClick(TokenSelectionDirection.TO) },
)
}
val marginButton = TangemTheme.dimens.spacing30
SwapButton(
state,

View file

@ -0,0 +1,418 @@
package com.tangem.feature.swap.ui
import android.content.res.Configuration
import androidx.compose.animation.AnimatedContent
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.TextAutoSize
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.material3.ripple
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.tangem.common.ui.account.AccountTitle
import com.tangem.core.ui.R
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.SpacerH4
import com.tangem.core.ui.components.SpacerW16
import com.tangem.core.ui.components.TextShimmer
import com.tangem.core.ui.components.buttons.SecondarySmallButton
import com.tangem.core.ui.components.buttons.SmallButtonConfig
import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition
import com.tangem.core.ui.extensions.orMaskWithStars
import com.tangem.core.ui.extensions.resolveAnnotatedReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.SwapTokenScreenTestTags
import com.tangem.feature.swap.domain.models.ui.PriceImpact
import com.tangem.feature.swap.models.SwapCardState
import com.tangem.feature.swap.models.TransactionCardType
import com.tangem.feature.swap.ui.preview.SwapTransactionCardPreview
@Composable
internal fun TransactionCardSimple(
priceImpact: PriceImpact,
swapCardState: SwapCardState,
onSelectTokenClick: () -> Unit,
modifier: Modifier = Modifier,
) {
val cardTag = when (swapCardState.type) {
is TransactionCardType.Inputtable -> SwapTokenScreenTestTags.SWAP_CARD
is TransactionCardType.ReadOnly -> SwapTokenScreenTestTags.RECEIVE_CARD
}
when (swapCardState) {
is SwapCardState.Empty -> SimpleTransactionCardEmpty(
cardState = swapCardState,
onChangeTokenClick = onSelectTokenClick,
modifier = modifier.testTag(cardTag),
)
is SwapCardState.SwapCardData -> SimpleTransactionCardData(
cardState = swapCardState,
priceImpact = priceImpact,
onChangeTokenClick = onSelectTokenClick,
modifier = modifier.testTag(cardTag),
)
is SwapCardState.Loading -> SimpleTransactionCardLoading(modifier = modifier.testTag(cardTag))
}
}
@Composable
private fun SimpleTransactionCardData(
cardState: SwapCardState.SwapCardData,
priceImpact: PriceImpact,
modifier: Modifier = Modifier,
onChangeTokenClick: (() -> Unit)? = null,
) {
Box(
modifier = modifier
.background(
shape = RoundedCornerShape(TangemTheme.dimens.radius16),
color = TangemTheme.colors.background.primary,
)
.fillMaxWidth(),
) {
Column(
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.Start,
) {
SimpleHeader(
balance = stringResourceSafe(
R.string.common_balance,
cardState.balance,
).orMaskWithStars(cardState.isBalanceHidden),
type = cardState.type,
)
SimpleContent(
type = cardState.type,
textFieldValue = cardState.amountTextFieldValue,
priceImpact = priceImpact,
)
}
Box(modifier = Modifier.align(Alignment.BottomEnd)) {
Token(
currencyIconState = cardState.currencyIconState,
tokenSymbol = cardState.tokenSymbol,
)
}
if (onChangeTokenClick != null) {
Box(modifier = Modifier.align(Alignment.CenterEnd)) {
ChangeTokenSelector()
}
Box(
Modifier
.align(Alignment.CenterEnd)
.height(TangemTheme.dimens.size116)
.width(TangemTheme.dimens.size102)
.clickable(
indication = ripple(bounded = false),
interactionSource = remember { MutableInteractionSource() },
) { onChangeTokenClick() },
)
}
}
}
@Composable
private fun SimpleTransactionCardEmpty(
cardState: SwapCardState.Empty,
modifier: Modifier = Modifier,
onChangeTokenClick: () -> Unit,
) {
Column(
modifier = modifier
.background(
shape = RoundedCornerShape(TangemTheme.dimens.radius12),
color = TangemTheme.colors.background.primary,
)
.padding(
top = 12.dp,
start = 12.dp,
end = 12.dp,
bottom = 16.dp,
)
.fillMaxWidth(),
horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.spacedBy(6.dp),
) {
AccountTitle(
accountTitleUM = cardState.type.accountTitleUM,
modifier = Modifier.fillMaxWidth(),
)
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
Column(
modifier = Modifier.weight(1f),
verticalArrangement = Arrangement.spacedBy(4.dp),
) {
Text(
text = cardState.amountTextFieldValue?.text.orEmpty(),
color = TangemTheme.colors.text.disabled,
style = TangemTheme.typography.h2,
autoSize = TextAutoSize.StepBased(
minFontSize = 16.sp,
maxFontSize = TangemTheme.typography.h2.fontSize,
),
maxLines = 1,
modifier = Modifier.testTag(SwapTokenScreenTestTags.SWAP_TEXT_FIELD),
)
Text(
text = cardState.amountEquivalent.resolveAnnotatedReference(),
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.body2,
modifier = Modifier
.defaultMinSize(minHeight = TangemTheme.dimens.size20)
.testTag(SwapTokenScreenTestTags.SWAP_FIAT_AMOUNT),
)
}
SecondarySmallButton(
config = SmallButtonConfig(
text = resourceReference(R.string.common_choose_token),
icon = TangemButtonIconPosition.End(R.drawable.ic_chevron_24),
onClick = onChangeTokenClick,
),
)
}
}
}
@Composable
private fun SimpleTransactionCardLoading(modifier: Modifier = Modifier) {
Column(
modifier = modifier
.background(
shape = RoundedCornerShape(TangemTheme.dimens.radius12),
color = TangemTheme.colors.background.primary,
)
.padding(
top = 12.dp,
start = 12.dp,
end = 12.dp,
bottom = 16.dp,
)
.fillMaxWidth(),
horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.spacedBy(6.dp),
) {
Column(modifier = Modifier.fillMaxWidth()) {
TextShimmer(
text = stringResourceSafe(R.string.swapping_to_title),
style = TangemTheme.typography.subtitle2,
)
TextShimmer(
style = TangemTheme.typography.body2,
modifier = Modifier
.testTag(SwapTokenScreenTestTags.BALANCE)
.width(60.dp),
)
}
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
Column(
modifier = Modifier.weight(1f),
verticalArrangement = Arrangement.spacedBy(4.dp),
) {
TextShimmer(
style = TangemTheme.typography.h2,
modifier = Modifier
.width(100.dp)
.testTag(SwapTokenScreenTestTags.SWAP_TEXT_FIELD),
)
TextShimmer(
style = TangemTheme.typography.body2,
modifier = Modifier
.defaultMinSize(minHeight = 20.dp, minWidth = 40.dp)
.testTag(SwapTokenScreenTestTags.SWAP_FIAT_AMOUNT),
)
}
SecondarySmallButton(
config = SmallButtonConfig(
text = resourceReference(R.string.common_choose_token),
icon = TangemButtonIconPosition.End(R.drawable.ic_chevron_24),
isEnabled = false,
onClick = {},
),
)
}
}
}
@Composable
private fun SimpleHeader(type: TransactionCardType, balance: String, modifier: Modifier = Modifier) {
Column(
modifier = modifier
.fillMaxWidth()
.padding(
bottom = TangemTheme.dimens.spacing8,
top = TangemTheme.dimens.spacing14,
start = TangemTheme.dimens.spacing12,
end = TangemTheme.dimens.spacing12,
)
.testTag(SwapTokenScreenTestTags.SWAP_BLOCK_HEADER),
) {
val titleColor = if (type.inputError is TransactionCardType.InputError.Empty) {
TangemTheme.colors.text.tertiary
} else {
TangemTheme.colors.text.warning
}
AccountTitle(
accountTitleUM = type.accountTitleUM,
textColor = titleColor,
)
SpacerW16()
if (balance.isNotBlank()) {
AnimatedContent(targetState = balance, label = "") { balanceText ->
Text(
text = balanceText,
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.body2,
modifier = Modifier.testTag(SwapTokenScreenTestTags.BALANCE),
)
}
} else {
RectangleShimmer(
modifier = Modifier
.width(TangemTheme.dimens.size80)
.height(TangemTheme.dimens.size12),
radius = TangemTheme.dimens.radius3,
)
}
}
}
@Suppress("LongMethod")
@Composable
private fun SimpleContent(type: TransactionCardType, priceImpact: PriceImpact, textFieldValue: TextFieldValue?) {
Row(
modifier = Modifier
.padding(
start = TangemTheme.dimens.spacing12,
bottom = TangemTheme.dimens.spacing16,
),
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.Top,
) {
Column(
modifier = Modifier.padding(end = TangemTheme.dimens.spacing92),
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.Start,
) {
val sumTextModifier = Modifier.defaultMinSize(minHeight = TangemTheme.dimens.size32)
when (type) {
is TransactionCardType.ReadOnly -> {
if (textFieldValue != null) {
Text(
text = textFieldValue.text,
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography.h2,
autoSize = TextAutoSize.StepBased(
minFontSize = 16.sp,
maxFontSize = TangemTheme.typography.h2.fontSize,
),
maxLines = 1,
modifier = sumTextModifier.testTag(SwapTokenScreenTestTags.RECEIVE_TEXT_FIELD),
)
} else {
RectangleShimmer(
modifier = Modifier
.padding(vertical = TangemTheme.dimens.spacing4)
.width(TangemTheme.dimens.size102)
.height(TangemTheme.dimens.size24),
)
}
}
is TransactionCardType.Inputtable -> {
val focusRequester = remember { FocusRequester() }
AutoSizeTextField(
modifier = sumTextModifier.testTag(SwapTokenScreenTestTags.SWAP_TEXT_FIELD),
focusRequester = focusRequester,
textFieldValue = textFieldValue ?: TextFieldValue(),
isEnabled = type.isEnabled,
onAmountChange = { type.onAmountChanged(it) },
onFocusChange = type.onFocusChanged,
)
LaunchedEffect(Unit) { focusRequester.requestFocus() }
}
}
SpacerH4()
// Keep the same 20dp slot as Detailed (where fiat/shimmer lives)
// so that Token (BottomEnd) does not shift when switching modes.
Box(modifier = Modifier.defaultMinSize(minHeight = TangemTheme.dimens.size20)) {
if (type is TransactionCardType.ReadOnly && type.shouldShowWarning) {
WarningIcon(priceImpact = priceImpact, onClick = type.onWarningClick)
}
}
}
}
}
@Composable
private fun WarningIcon(priceImpact: PriceImpact, onClick: (() -> Unit)?) {
IconButton(
onClick = { onClick?.invoke() },
modifier = Modifier.size(TangemTheme.dimens.size20),
) {
Icon(
painter = painterResource(id = R.drawable.ic_information_24),
contentDescription = null,
tint = when (priceImpact.type) {
PriceImpact.Type.HIGH -> TangemTheme.colors.text.warning
PriceImpact.Type.MEDIUM -> TangemTheme.colors.text.attention
else -> TangemTheme.colors.text.tertiary
},
modifier = Modifier.testTag(SwapTokenScreenTestTags.RECEIVE_FIAT_AMOUNT_INFORMATION_ICON),
)
}
}
// region Preview
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun TransactionCardSimple_Preview(@PreviewParameter(SimplePreviewProvider::class) params: SwapCardState) {
TangemThemePreview {
TransactionCardSimple(
priceImpact = PriceImpact.Empty,
swapCardState = params,
onSelectTokenClick = {},
modifier = Modifier,
)
}
}
private class SimplePreviewProvider : PreviewParameterProvider<SwapCardState> {
override val values: Sequence<SwapCardState> = sequenceOf(
SwapTransactionCardPreview.sendCard,
SwapTransactionCardPreview.receiveCard,
SwapTransactionCardPreview.emptyReadOnlyCard,
SwapTransactionCardPreview.emptyInputtableCard,
SwapTransactionCardPreview.loadingCard,
)
}
// endregion

View file

@ -48,6 +48,7 @@ internal class StateBuilderInitialStateTest {
appCurrencyProvider = appCurrencyProvider,
isAccountsModeProvider = isAccountsModeProvider,
isGaslessFeeSupportedForNetwork = isGaslessFeeSupportedForNetwork,
shouldShowAbMenu = false,
)
}

View file

@ -53,6 +53,7 @@ internal class StateBuilderPairsTest {
appCurrencyProvider = appCurrencyProvider,
isAccountsModeProvider = isAccountsModeProvider,
isGaslessFeeSupportedForNetwork = isGaslessFeeSupportedForNetwork,
shouldShowAbMenu = false,
)
}

View file

@ -57,6 +57,7 @@ internal class StateBuilderQuotesTest {
appCurrencyProvider = appCurrencyProvider,
isAccountsModeProvider = isAccountsModeProvider,
isGaslessFeeSupportedForNetwork = isGaslessFeeSupportedForNetwork,
shouldShowAbMenu = false,
)
}

View file

@ -59,6 +59,7 @@ internal class StateBuilderSwapDataTest {
appCurrencyProvider = appCurrencyProvider,
isAccountsModeProvider = isAccountsModeProvider,
isGaslessFeeSupportedForNetwork = isGaslessFeeSupportedForNetwork,
shouldShowAbMenu = false,
)
}