Updated on 2026-08-14

This commit is contained in:
Tangem 2025-11-04 14:09:34 +04:00
parent 507b608807
commit 768422ea7c
34 changed files with 661 additions and 80 deletions

View file

@ -320,6 +320,13 @@ internal class ChildFactory @Inject constructor(
userWalletId = route.userWalletId,
isInitialReverseOrder = route.isInitialReverseOrder,
screenSource = route.screenSource,
tangemPayInput = route.tangemPayInput?.let { tangemPayInput ->
SwapComponent.Params.TangemPayInput(
cryptoAmount = tangemPayInput.cryptoAmount,
fiatAmount = tangemPayInput.fiatAmount,
depositAddress = tangemPayInput.depositAddress,
)
},
),
componentFactory = swapComponentFactory,
)

View file

@ -15,6 +15,7 @@ import com.tangem.domain.models.PortfolioId
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.models.serialization.SerializedBigDecimal
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.nft.models.NFTAsset
import com.tangem.domain.onramp.model.OnrampSource
@ -181,13 +182,21 @@ sealed class AppRoute(val path: String) : Route {
val userWalletId: UserWalletId,
val isInitialReverseOrder: Boolean = false,
val screenSource: String,
val tangemPayInput: TangemPayInput? = null,
) : AppRoute(
path = "/swap" +
"/${currencyFrom.id.value}" +
"/${currencyTo?.id?.value}" +
"/${userWalletId.stringValue}" +
"/$isInitialReverseOrder",
)
) {
@Serializable
data class TangemPayInput(
val cryptoAmount: SerializedBigDecimal,
val fiatAmount: SerializedBigDecimal,
val depositAddress: String,
)
}
@Serializable
data object AppCurrencySelector : AppRoute(path = "/app_currency_selector")

View file

@ -84,6 +84,7 @@ sealed class AnalyticsParam {
data object LongTap : ScreensSources("Long Tap")
data object Markets : ScreensSources("Markets")
data object HotWallet : ScreensSources("Hot Wallet")
data object TangemPay : ScreensSources("Tangem Pay")
}
sealed class TxSentFrom(val value: String) {

View file

@ -760,6 +760,8 @@
<string name="notification_sepa_button">Krypto kaufen</string>
<string name="notification_sepa_text">Profitiere von **0 % Gebühren** beim Kauf von Kryptowährungen per SEPA-Überweisung.</string>
<string name="notification_sepa_title">Krypto mit SEPA kaufen</string>
<string name="notification_visa_waitlist_promo_text">Trage dich in die Warteliste ein und erhalte eine Zahlungskarte, die es so noch nie gab.</string>
<string name="notification_visa_waitlist_promo_title">Tangem Visa Card</string>
<string name="onboarding_access_code_feature_1_description">Du musst einen einzigen Zugangscode einrichten, um alle deine Geräte zu schützen</string>
<string name="onboarding_access_code_feature_1_title">Schützen</string>
<string name="onboarding_access_code_feature_2_description">Du kannst später auf jeder Karte oder Ring einen individuellen Zugangscode einrichten</string>
@ -1711,7 +1713,7 @@
<string name="yield_module_fee_policy_tangem_service_fee_title">Tangem erhebt außerdem eine Servicegebühr von 15% auf den erzielten Ertrag.</string>
<string name="yield_module_high_fee_error">Deine Gelder werden automatisch an Aave überwiesen, sobald die Netzwerkgebühren niedriger sind oder Dein Guthaben den erforderlichen Mindestbetrag erreicht.</string>
<string name="yield_module_historical_returns">Historische Renditen</string>
<string name="yield_module_main_view_approve_notification_description">Die Freigabe für Deine Token im Yield-Modus wurde widerrufen. Öffne den Token, um die Berechtigung erneut zu erteilen.</string>
<string name="yield_module_main_view_approve_notification_description">Die Genehmigung für Deine Token im Yield-Modus wurde widerrufen. Öffne den Token, um die Berechtigung erneut zu erteilen.</string>
<string name="yield_module_main_view_approve_notification_title">Token-Genehmigung erforderlich</string>
<string name="yield_module_network_fee_unreachable_notification_description">Prüfe Deine Netzwerkverbindung</string>
<string name="yield_module_network_fee_unreachable_notification_title">Informationen zu den Netzwerkgebühren nicht erreichbar</string>
@ -1743,6 +1745,7 @@
<string name="yield_module_stop_earning_sheet_fee_note">Die Netzwerkgebühr wird von dem Betrag, den Du abhebst, abgezogen.</string>
<string name="yield_module_supply_apr">Effektiver Jahreszins für Versorgung</string>
<string name="yield_module_token_details_earn_notification_apy">Effektiver Jahreszins</string>
<string name="yield_module_token_details_earn_notification_description">Zinsen fallen automatisch an</string>
<string name="yield_module_token_details_earn_notification_earning_on_your_balance_subtitle">Zinsen fallen automatisch an</string>
<string name="yield_module_token_details_earn_notification_earning_on_your_balance_title">Ertragsmodus</string>
<string name="yield_module_token_details_earn_notification_processing">Bearbeitung Deiner Einzahlung</string>

View file

@ -653,6 +653,8 @@
<string name="notification_sepa_button">Comprar cripto</string>
<string name="notification_sepa_text">Disfrute de unas comisiones del **0%** al comprar criptomonedas mediante transferencias SEPA.</string>
<string name="notification_sepa_title">Comprar criptomonedas con SEPA</string>
<string name="notification_visa_waitlist_promo_text">Únete a la lista de espera y consigue una tarjeta de pago como ninguna otra.</string>
<string name="notification_visa_waitlist_promo_title">Tangem Visa Card</string>
<string name="onboarding_access_code_feature_1_description">Configure un código de acceso único para proteger todos sus dispositivos.</string>
<string name="onboarding_access_code_feature_1_title">Proteger</string>
<string name="onboarding_access_code_feature_2_description">Puede configurar un código de acceso individual en cada tarjeta más adelante</string>
@ -1544,9 +1546,11 @@
<string name="yield_module_stop_earning_sheet_fee_note">La comisión de red se deducirá del importe que retire.</string>
<string name="yield_module_supply_apr">Suministro APY</string>
<string name="yield_module_token_details_earn_notification_apy">APY</string>
<string name="yield_module_token_details_earn_notification_description">Los intereses se devengan automáticamente</string>
<string name="yield_module_token_details_earn_notification_earning_on_your_balance_subtitle">Los intereses se devengan automáticamente</string>
<string name="yield_module_token_details_earn_notification_earning_on_your_balance_title">Modo de rendimiento</string>
<string name="yield_module_token_details_earn_notification_processing">Procesando su depósito</string>
<string name="yield_module_token_details_earn_notification_title">Modo Rendimiento</string>
<string name="yield_module_transfer_mode_automatic">Automático</string>
<string name="yield_module_unable_to_cover_fee_description">Deposite algo de %1$s %2$s para cubrir la tarifa de red para las transacciones</string>
<string name="yield_module_unable_to_cover_fee_title">No se puede cubrir la tarifa %s</string>

View file

@ -626,6 +626,8 @@
<string name="notification_sepa_button">Acheter crypto</string>
<string name="notification_sepa_text">Profitez de **0% de frais** lors de l\'achat de cryptomonnaies via des virements SEPA.</string>
<string name="notification_sepa_title">Acheter des cryptomonnaies avec SEPA</string>
<string name="notification_visa_waitlist_promo_text">Rejoignez la liste dattente et obtenez une carte de paiement pas comme les autres.</string>
<string name="notification_visa_waitlist_promo_title">Tangem Visa Card</string>
<string name="onboarding_access_code_feature_1_description">Vous devez définir un seul code d\'accès pour protéger tous vos appareils.</string>
<string name="onboarding_access_code_feature_1_title">Protéger</string>
<string name="onboarding_access_code_feature_2_description">Vous pourrez définir un code d\'accès individuel sur chaque carte plus tard</string>

View file

@ -30,6 +30,7 @@
<string name="account_details_archive">アカウントをアーカイブする</string>
<string name="account_details_archive_action">アーカイブ</string>
<string name="account_details_archive_description">このアカウントをアーカイブしますが、いつでも復元できます。</string>
<string name="account_details_archiving">アーカイブ中・・・</string>
<string name="account_details_title">アカウント</string>
<string name="account_edit_success_message">アカウントが保存されました</string>
<string name="account_for_rewards">報酬用のアカウント</string>
@ -156,6 +157,13 @@
<plurals name="card_label_token_count">
<item quantity="other">%1$dトークン</item>
</plurals>
<string name="card_reset_alert_continue_message">続行するには次のデバイスをリセットしてください。</string>
<string name="card_reset_alert_continue_title">ウォレットのリセット</string>
<string name="card_reset_alert_finish_message">すべてのTangemデバイスがリセットされました。ウォレットのアップグレードを続行できます。</string>
<string name="card_reset_alert_finish_ok_button">もう一度アップグレードしてください</string>
<string name="card_reset_alert_finish_title">リセット完了</string>
<string name="card_reset_alert_incomplete_message">すべてのTangemデバイスをリセットしていません</string>
<string name="card_reset_alert_incomplete_title">すべてのTangemデバイスをリセットしていません</string>
<string name="card_settings_access_code_recovery_disabled_description">このカードを使用して、このウォレット内の他のカードまたはリングのアクセスコードをリセットしたくない場合は、このオプションを無効にしてください。これにより、このカードのアクセスコードもリセットできなくなりますのでご注意ください。</string>
<string name="card_settings_access_code_recovery_enabled_description">このカードを使用して、このウォレット内の他のカードのアクセスコードをリセットできます。</string>
<string name="card_settings_access_code_recovery_title">アクセスコードの復元</string>
@ -307,6 +315,7 @@
<string name="common_reload">リロード</string>
<string name="common_rename">名前を変更</string>
<string name="common_required">必須</string>
<string name="common_reset">リセット</string>
<string name="common_save">保存</string>
<string name="common_save_changes">変更内容を保存</string>
<string name="common_search">検索</string>
@ -423,6 +432,7 @@
<string name="email_subject_wc_error">WalletConnectエラー</string>
<string name="error_wrong_wallet_tapped">別のウォレットのカードまたはリングを使用しました。このウォレットにリンクしているカードまたはリングをタップしてください。</string>
<string name="eth_gas_required_exceeds_allowance">取引に必要な資金が不足しています。アカウントに入金してください。</string>
<string name="exchange_token_description">ポートフォリオ内のアセットをこのトークンと交換します</string>
<string name="exchange_tokens_available_tokens_header">マイトークン</string>
<string name="exchange_tokens_empty_tokens">まだトークンを追加していません。マーケットからトークンを追加して交換してください。</string>
<string name="exchange_tokens_unavailable_tokens_header">%sとの交換はできません</string>
@ -779,6 +789,8 @@
<string name="notification_sepa_button">暗号資産を買い付ける</string>
<string name="notification_sepa_text">SEPA送金で暗号資産を買い付けると、**手数料 0% ** になります。</string>
<string name="notification_sepa_title">SEPAで暗号資産を購入</string>
<string name="notification_visa_waitlist_promo_text">事前申し込み受付中。他にない特別なカードを、いち早く体験しよう。</string>
<string name="notification_visa_waitlist_promo_title">Tangem Visaカード</string>
<string name="onboarding_access_code_feature_1_description">すべてのデバイスを保護するには、単一のアクセスコードを設定してください。</string>
<string name="onboarding_access_code_feature_1_title">保護する</string>
<string name="onboarding_access_code_feature_2_description">後で各カードおよびリングに個別のアクセスコードを設定できます。</string>
@ -1303,6 +1315,7 @@
<string name="tangem_pay_explore_transaction">取引を表示</string>
<string name="tangem_pay_fee_subtitle">サービス手数料</string>
<string name="tangem_pay_fee_title">手数料</string>
<string name="tangem_pay_get_help">サポートを受ける</string>
<string name="tangem_pay_status_completed">完了</string>
<string name="tangem_pay_status_declined">拒否</string>
<string name="tangem_pay_status_pending">保留中</string>
@ -1315,6 +1328,15 @@
<string name="tangempay_card_details_error_text">データの読み込みに失敗しました。しばらくしてからもう一度お試しください。</string>
<string name="tangempay_card_details_freeze_card">カードの一時停止</string>
<string name="tangempay_card_details_hide_text">非表示</string>
<string name="tangempay_card_details_open_wallet_button">Googleウォレットを開く</string>
<string name="tangempay_card_details_open_wallet_notification_subtitle">数回タップするだけでTangem Payの設定完了。Google Payですぐに支払いを始めましょう。</string>
<string name="tangempay_card_details_open_wallet_notification_title">Google Payにカードを追加する</string>
<string name="tangempay_card_details_open_wallet_step_1">Googleウォレットを開く</string>
<string name="tangempay_card_details_open_wallet_step_2">「カードを追加」をタップします</string>
<string name="tangempay_card_details_open_wallet_step_3">カードの詳細を手動で入力してください</string>
<string name="tangempay_card_details_open_wallet_step_4">お使いの端末に送信されたワンタイムパスワードOTPでカードを認証してください。</string>
<string name="tangempay_card_details_open_wallet_step_5">準備完了!カードはすぐに使用できます。</string>
<string name="tangempay_card_details_open_wallet_title">Google Payにカードを追加する</string>
<string name="tangempay_card_details_receive_error_description">技術的な問題が検出されました。しばらくしてからもう一度お試しいただくか、サポートにお問い合わせください。</string>
<string name="tangempay_card_details_receive_error_title">現在、受け取りは利用できません</string>
<string name="tangempay_card_details_reveal_text">表示</string>

View file

@ -701,6 +701,8 @@
<string name="notification_sepa_button">Купить криптовалюту</string>
<string name="notification_sepa_text">Наслаждайтесь **нулевой комиссией** при покупке криптовалюты через SEPA-переводы.</string>
<string name="notification_sepa_title">Покупайте крипту через SEPA</string>
<string name="notification_visa_waitlist_promo_text">Присоединяйтесь к списку ожидания и получите уникальную платёжную карту, не похожую ни на одну другую.</string>
<string name="notification_visa_waitlist_promo_title">Tangem Visa Card</string>
<string name="onboarding_access_code_feature_1_description">Установите единый код доступа для защиты всех ваших карт или колец</string>
<string name="onboarding_access_code_feature_1_title">Защита</string>
<string name="onboarding_access_code_feature_2_description">Установите индивидуальный код доступа для каждой карты или кольца позже.</string>
@ -1631,7 +1633,7 @@
<string name="yield_module_start_earning_sheet_next_deposits">Следующие пополнения вашего счёта автоматически поступят в Aave.</string>
<string name="yield_module_status_active">Активен</string>
<string name="yield_module_status_paused">На паузе</string>
<string name="yield_module_stop_earning">Завершить заработок</string>
<string name="yield_module_stop_earning">Завершение режима доходности</string>
<string name="yield_module_stop_earning_sheet_description">Выключив эту функцию, вы выведёте средства из Aave, получите их обратно в %s в своем кошельке и перестанете зарабатывать награды.</string>
<string name="yield_module_stop_earning_sheet_fee_note">Комиссия сети будет вычтена из суммы вашего вывода.</string>
<string name="yield_module_supply_apr">Годовая доходность (APY)</string>

View file

@ -629,6 +629,8 @@
<string name="notification_sepa_button">Купити криптовалюту</string>
<string name="notification_sepa_text">Насолоджуйтесь **нульовою комісією** при купівлі криптовалюти через перекази SEPA.</string>
<string name="notification_sepa_title">Купуйте криптовалюту через SEPA</string>
<string name="notification_visa_waitlist_promo_text">Приєднуйтесь до списку очікування та отримайте унікальну платіжну картку, якої ще не було.</string>
<string name="notification_visa_waitlist_promo_title">Tangem Visa Card</string>
<string name="onboarding_access_code_feature_1_description">Налаштуйте єдиний код доступу для захисту всіх ваших карток або кілець</string>
<string name="onboarding_access_code_feature_1_title">Захист</string>
<string name="onboarding_access_code_feature_2_description">Встановіть індивідуальний код доступу для кожної картки або кільця пізніше.</string>

View file

@ -30,6 +30,7 @@
<string name="account_details_archive">Archive account</string>
<string name="account_details_archive_action">Archive</string>
<string name="account_details_archive_description">You are archiving this account, but you can always get it back.</string>
<string name="account_details_archiving">Archiving...</string>
<string name="account_details_title">Account</string>
<string name="account_edit_success_message">Account saved</string>
<string name="account_for_rewards">Account for rewards</string>
@ -159,6 +160,13 @@
<item quantity="one">%d token</item>
<item quantity="other">%d tokens</item>
</plurals>
<string name="card_reset_alert_continue_message">Please reset the next device to continue.</string>
<string name="card_reset_alert_continue_title">Wallet reset</string>
<string name="card_reset_alert_finish_message">All Tangem devices have been reset. You can now continue upgrading your wallet.</string>
<string name="card_reset_alert_finish_ok_button">Upgrade again</string>
<string name="card_reset_alert_finish_title">Reset complete</string>
<string name="card_reset_alert_incomplete_message">You havent reset all your Tangem devices</string>
<string name="card_reset_alert_incomplete_title">You havent reset all your Tangem devices</string>
<string name="card_settings_access_code_recovery_disabled_description">Disable this option if you don\'t want this card to be used to reset access codes on other cards or rings in this wallet. Please note that this will also prevent you from resetting the access code on this card.</string>
<string name="card_settings_access_code_recovery_enabled_description">Allows you to use this card to reset access code on other cards in this wallet</string>
<string name="card_settings_access_code_recovery_title">Access code recovery</string>
@ -313,6 +321,7 @@
<string name="common_reload">Reload</string>
<string name="common_rename">Rename</string>
<string name="common_required">Required</string>
<string name="common_reset">Reset</string>
<string name="common_save">Save</string>
<string name="common_save_changes">Save changes</string>
<string name="common_search">Search</string>
@ -431,6 +440,7 @@
<string name="email_subject_wc_error">WalletConnect error</string>
<string name="error_wrong_wallet_tapped">You have used a card or ring from another wallet. Tap the card or ring associated with this wallet</string>
<string name="eth_gas_required_exceeds_allowance">Not enough funds for the transaction. Please top up your account.</string>
<string name="exchange_token_description">Swap any asset in your portfolio for this token</string>
<string name="exchange_tokens_available_tokens_header">My tokens</string>
<string name="exchange_tokens_empty_tokens">You haven\'t added any tokens yet. Add tokens via Market to swap</string>
<string name="exchange_tokens_unavailable_tokens_header">Cannot be swapped for %s</string>
@ -795,6 +805,8 @@
<string name="notification_sepa_button">Buy crypto</string>
<string name="notification_sepa_text">Enjoy **0% fees** when purchasing crypto via SEPA transfers.</string>
<string name="notification_sepa_title">Buy Crypto with SEPA</string>
<string name="notification_visa_waitlist_promo_text">Join the waitlist and get a payment card unlike any other</string>
<string name="notification_visa_waitlist_promo_title">Tangem Visa Card</string>
<string name="onboarding_access_code_feature_1_description">Set up a single access code to protect all your devices.</string>
<string name="onboarding_access_code_feature_1_title">Protect</string>
<string name="onboarding_access_code_feature_2_description">Set an individual access code for each card or ring later.</string>
@ -1326,6 +1338,9 @@
<string name="tangem_pay_explore_transaction">Explore transaction</string>
<string name="tangem_pay_fee_subtitle">Service fees</string>
<string name="tangem_pay_fee_title">Fee</string>
<string name="tangem_pay_freeze_card_alert_body">Keep your money safe if your card is lost or stolen. You can unfreeze anytime.</string>
<string name="tangem_pay_freeze_card_alert_title">Freeze your card?</string>
<string name="tangem_pay_freeze_card_freeze">Freeze</string>
<string name="tangem_pay_get_help">Get Help</string>
<string name="tangem_pay_status_completed">Completed</string>
<string name="tangem_pay_status_declined">Declined</string>
@ -1333,6 +1348,8 @@
<string name="tangem_pay_transaction_declined_notification_text">The bank rejected this transaction request.</string>
<string name="tangem_pay_transaction_fee_notification_text">This fee goes to cover the cost of handling your transfer.</string>
<string name="tangem_pay_withdrawal">Withdrawal</string>
<string name="tangempay_card_details_add_funds">Add funds</string>
<string name="tangempay_card_details_add_funds_subtitle">Top-up options</string>
<string name="tangempay_card_details_change_pin">Change PIN</string>
<string name="tangempay_card_details_change_pin_success_description">The card is fully ready for payments.</string>
<string name="tangempay_card_details_change_pin_success_title">PIN code created</string>
@ -1348,9 +1365,11 @@
<string name="tangempay_card_details_open_wallet_step_4">Verify card using the OTP sent to your device.</string>
<string name="tangempay_card_details_open_wallet_step_5">All set! Your card is ready to use.</string>
<string name="tangempay_card_details_open_wallet_title">Add card to Google Pay</string>
<string name="tangempay_card_details_receive_description">Share your address or show QR-code</string>
<string name="tangempay_card_details_receive_error_description">Technical issues detected. Please try again later or contact support.</string>
<string name="tangempay_card_details_receive_error_title">Receive unavailable now</string>
<string name="tangempay_card_details_reveal_text">Reveal</string>
<string name="tangempay_card_details_swap_description">Swap any asset in your portfolio for card</string>
<string name="tangempay_card_details_title">Card details</string>
<string name="tangempay_issue_card_notification_title">Create account and issue a card</string>
<string name="tangempay_kyc_card_ready_notification_title">Tangem Pay</string>
@ -1869,7 +1888,7 @@
<string name="yield_module_promo_screen_cash_out_subtitle">Send, swap, or sell your funds instantly, anytime you want.</string>
<string name="yield_module_promo_screen_cash_out_title">Access your funds anytime</string>
<string name="yield_module_promo_screen_how_it_works_button_title">How it works?</string>
<string name="yield_module_promo_screen_self_custodial_subtitle">Aave is a decentralized protocol managing over $61 billion in total value.</string>
<string name="yield_module_promo_screen_self_custodial_subtitle">Aave is a decentralized protocol managing over $61 billion in total value.</string>
<string name="yield_module_promo_screen_self_custodial_title">Decentralized and self-custodial</string>
<string name="yield_module_promo_screen_terms_disclaimer">By using this service, you agree with provider\n%1$s and %2$s</string>
<string name="yield_module_promo_screen_title">Connect Aave</string>

View file

@ -30,6 +30,7 @@ dependencies {
implementation(projects.domain.tokens)
implementation(projects.domain.networks)
implementation(projects.domain.walletManager)
implementation(projects.domain.quotes)
/** Feature API - remove after removing [HotWalletFeatureToggles] */
implementation(projects.features.hotWallet.api)

View file

@ -3,7 +3,6 @@ package com.tangem.data.pay
import arrow.core.Either
import com.squareup.moshi.Moshi
import com.tangem.blockchain.blockchains.ethereum.Chain
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
import com.tangem.core.error.UniversalError
import com.tangem.data.common.currency.CryptoCurrencyFactory
@ -14,13 +13,15 @@ import com.tangem.datasource.di.NetworkMoshi
import com.tangem.domain.models.ReceiveAddressModel
import com.tangem.domain.models.ReceiveAddressModel.NameService
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.pay.DataForReceive
import com.tangem.domain.pay.DataForReceiveFactory
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.pay.TangemPayTopUpData
import com.tangem.domain.pay.TangemPayTopUpDataFactory
import kotlinx.coroutines.CancellationException
import timber.log.Timber
import java.math.BigDecimal
import javax.inject.Inject
private const val TAG = "TangemPay: TokenReceiveConfigFactory"
private const val TAG = "TangemPay: DefaultDataForTopUpFactory"
/**
* Custom token parameters. Will be used only for F&F.
*/
@ -29,11 +30,11 @@ private const val TOKEN_NAME = "USDC"
private const val TOKEN_CONTRACT_ADDRESS = "0x3c499c542cef5e3811e1192ce70d8cc03d5c3359"
private const val TOKEN_DECIMALS = 6
internal class DefaultDataForReceiveFactory @Inject constructor(
internal class DefaultTangemPayTopUpDataFactory @Inject constructor(
@NetworkMoshi moshi: Moshi,
private val tangemPayWalletsManager: TangemPayWalletsManager,
excludedBlockchains: ExcludedBlockchains,
) : DataForReceiveFactory {
) : TangemPayTopUpDataFactory {
private val cryptoCurrencyFactory by lazy(mode = LazyThreadSafetyMode.NONE) {
CryptoCurrencyFactory(excludedBlockchains)
@ -43,26 +44,39 @@ internal class DefaultDataForReceiveFactory @Inject constructor(
}
private val errorConverter by lazy(mode = LazyThreadSafetyMode.NONE) { TangemPayErrorConverter(moshi) }
override fun getDataForReceive(depositAddress: String, chainId: Int): Either<UniversalError, DataForReceive> {
private fun getCurrency(userWallet: UserWallet, chainId: Int): CryptoCurrency {
val chain = requireNotNull(Chain.entries.find { it.id == chainId }) { "Can not find chain with $chainId" }
val blockchain = requireNotNull(chain.blockchain)
val network = networkFactory.create(
blockchain = blockchain,
extraDerivationPath = null,
userWallet = userWallet,
)
return cryptoCurrencyFactory.createToken(
network = requireNotNull(network),
rawId = CryptoCurrency.RawID(TOKEN_ID),
name = TOKEN_NAME,
symbol = TOKEN_NAME,
contractAddress = TOKEN_CONTRACT_ADDRESS,
decimals = TOKEN_DECIMALS,
)
}
override fun create(
depositAddress: String,
chainId: Int,
cryptoBalance: BigDecimal,
fiatBalance: BigDecimal,
): Either<UniversalError, TangemPayTopUpData> {
return try {
val wallet = tangemPayWalletsManager.getDefaultWalletForTangemPayBlocking()
val blockchain: Blockchain? = Chain.entries.find { it.id == chainId }?.blockchain
val network = networkFactory.create(
blockchain = requireNotNull(blockchain),
extraDerivationPath = null,
userWallet = wallet,
)
val currency = cryptoCurrencyFactory.createToken(
network = requireNotNull(network),
rawId = CryptoCurrency.RawID(TOKEN_ID),
name = TOKEN_NAME,
symbol = TOKEN_NAME,
contractAddress = TOKEN_CONTRACT_ADDRESS,
decimals = TOKEN_DECIMALS,
)
val result = DataForReceive(
val currency = getCurrency(wallet, chainId)
val result = TangemPayTopUpData(
currency = currency,
walletId = wallet.walletId,
cryptoBalance = cryptoBalance,
fiatBalance = fiatBalance,
depositAddress = depositAddress,
receiveAddress = listOf(ReceiveAddressModel(nameService = NameService.Default, value = depositAddress)),
)

View file

@ -1,17 +1,19 @@
package com.tangem.data.pay.di
import com.tangem.data.pay.DefaultDataForReceiveFactory
import com.tangem.data.pay.DefaultTangemPayTopUpDataFactory
import com.tangem.data.pay.repository.DefaultCardDetailsRepository
import com.tangem.data.pay.repository.DefaultKycRepository
import com.tangem.data.pay.repository.DefaultTangemPayTxHistoryRepository
import com.tangem.data.pay.repository.DefaultOnboardingRepository
import com.tangem.domain.pay.DataForReceiveFactory
import com.tangem.data.pay.usecase.DefaultGetTangemPayCurrencyStatusUseCase
import com.tangem.domain.pay.TangemPayTopUpDataFactory
import com.tangem.domain.pay.repository.CardDetailsRepository
import com.tangem.domain.pay.repository.KycRepository
import com.tangem.domain.pay.repository.OnboardingRepository
import com.tangem.domain.pay.usecase.ProduceTangemPayInitialDataUseCase
import com.tangem.domain.pay.usecase.TangemPayIssueOrderUseCase
import com.tangem.domain.pay.usecase.TangemPayMainScreenCustomerInfoUseCase
import com.tangem.domain.tangempay.GetTangemPayCurrencyStatusUseCase
import com.tangem.domain.tangempay.repository.TangemPayTxHistoryRepository
import dagger.Binds
import dagger.Module
@ -42,7 +44,13 @@ internal interface TangemPayDataModule {
@Binds
@Singleton
fun bindDataForReceiveFactory(factory: DefaultDataForReceiveFactory): DataForReceiveFactory
fun bindDataForReceiveFactory(factory: DefaultTangemPayTopUpDataFactory): TangemPayTopUpDataFactory
@Binds
@Singleton
fun bindGetTangemPayCurrencyStatusUseCase(
impl: DefaultGetTangemPayCurrencyStatusUseCase,
): GetTangemPayCurrencyStatusUseCase
companion object {
@Provides

View file

@ -0,0 +1,62 @@
package com.tangem.data.pay.usecase
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.network.NetworkAddress
import com.tangem.domain.models.quote.QuoteStatus
import com.tangem.domain.quotes.single.SingleQuoteStatusFetcher
import com.tangem.domain.quotes.single.SingleQuoteStatusProducer
import com.tangem.domain.quotes.single.SingleQuoteStatusSupplier
import com.tangem.domain.tangempay.GetTangemPayCurrencyStatusUseCase
import java.math.BigDecimal
import javax.inject.Inject
internal class DefaultGetTangemPayCurrencyStatusUseCase @Inject constructor(
private val singleQuoteStatusSupplier: SingleQuoteStatusSupplier,
private val singleQuoteStatusFetcher: SingleQuoteStatusFetcher,
) : GetTangemPayCurrencyStatusUseCase {
override suspend fun invoke(
currency: CryptoCurrency,
cryptoAmount: BigDecimal,
fiatAmount: BigDecimal,
depositAddress: String,
): CryptoCurrencyStatus? {
val rawCurrencyId = currency.id.rawCurrencyId ?: return null
val quoteStatus = singleQuoteStatusSupplier.getSyncOrNull(
params = SingleQuoteStatusProducer.Params(rawCurrencyId = rawCurrencyId),
)?.value
val quoteStatusData: QuoteStatus.Data = when (quoteStatus) {
is QuoteStatus.Data -> quoteStatus
else -> {
singleQuoteStatusFetcher.invoke(
params = SingleQuoteStatusFetcher.Params(rawCurrencyId = rawCurrencyId, appCurrencyId = null),
)
singleQuoteStatusSupplier.getSyncOrNull(
params = SingleQuoteStatusProducer.Params(rawCurrencyId = rawCurrencyId),
)?.value as? QuoteStatus.Data
}
} ?: return null
return CryptoCurrencyStatus(
currency = currency,
value = CryptoCurrencyStatus.Loaded(
amount = cryptoAmount,
fiatRate = quoteStatusData.fiatRate,
priceChange = quoteStatusData.priceChange,
fiatAmount = fiatAmount,
networkAddress = NetworkAddress.Single(
defaultAddress = NetworkAddress.Address(
type = NetworkAddress.Address.Type.Primary,
value = depositAddress,
),
),
sources = CryptoCurrencyStatus.Sources(),
pendingTransactions = emptySet(),
yieldBalance = null,
yieldSupplyStatus = null,
hasCurrentNetworkTransactions = false,
),
)
}
}

View file

@ -1,18 +0,0 @@
package com.tangem.domain.pay
import arrow.core.Either
import com.tangem.core.error.UniversalError
import com.tangem.domain.models.ReceiveAddressModel
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.wallet.UserWalletId
interface DataForReceiveFactory {
fun getDataForReceive(depositAddress: String, chainId: Int): Either<UniversalError, DataForReceive>
}
data class DataForReceive(
val walletId: UserWalletId,
val currency: CryptoCurrency,
val receiveAddress: List<ReceiveAddressModel>,
)

View file

@ -0,0 +1,27 @@
package com.tangem.domain.pay
import arrow.core.Either
import com.tangem.core.error.UniversalError
import com.tangem.domain.models.ReceiveAddressModel
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.wallet.UserWalletId
import java.math.BigDecimal
interface TangemPayTopUpDataFactory {
fun create(
depositAddress: String,
chainId: Int,
cryptoBalance: BigDecimal,
fiatBalance: BigDecimal,
): Either<UniversalError, TangemPayTopUpData>
}
data class TangemPayTopUpData(
val walletId: UserWalletId,
val currency: CryptoCurrency,
val cryptoBalance: BigDecimal,
val fiatBalance: BigDecimal,
val depositAddress: String,
val receiveAddress: List<ReceiveAddressModel>,
)

View file

@ -0,0 +1,15 @@
package com.tangem.domain.tangempay
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import java.math.BigDecimal
interface GetTangemPayCurrencyStatusUseCase {
suspend operator fun invoke(
currency: CryptoCurrency,
cryptoAmount: BigDecimal,
fiatAmount: BigDecimal,
depositAddress: String,
): CryptoCurrencyStatus?
}

View file

@ -4,6 +4,7 @@ import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.wallet.UserWalletId
import java.math.BigDecimal
interface SwapComponent : ComposableContentComponent {
@ -13,7 +14,14 @@ interface SwapComponent : ComposableContentComponent {
val userWalletId: UserWalletId,
val isInitialReverseOrder: Boolean = false,
val screenSource: String,
)
val tangemPayInput: TangemPayInput? = null,
) {
data class TangemPayInput(
val cryptoAmount: BigDecimal,
val fiatAmount: BigDecimal,
val depositAddress: String,
)
}
interface Factory : ComponentFactory<Params, SwapComponent>
}

View file

@ -46,6 +46,7 @@ dependencies {
implementation(projects.domain.express.models)
implementation(projects.domain.account)
implementation(projects.domain.account.status)
implementation(projects.domain.visa)
/** Feature modules */
implementation(projects.features.swap.domain)

View file

@ -44,6 +44,7 @@ import com.tangem.domain.promo.models.StoryContentIds
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
import com.tangem.domain.settings.usercountry.models.UserCountry
import com.tangem.domain.settings.usercountry.models.needApplyFCARestrictions
import com.tangem.domain.tangempay.GetTangemPayCurrencyStatusUseCase
import com.tangem.domain.tokens.GetFeePaidCryptoCurrencyStatusSyncUseCase
import com.tangem.domain.tokens.GetMinimumTransactionAmountSyncUseCase
import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase
@ -113,6 +114,7 @@ internal class SwapModel @Inject constructor(
private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase,
private val getAccountCurrencyStatusUseCase: GetAccountCurrencyStatusUseCase,
private val accountsFeatureToggles: AccountsFeatureToggles,
private val getTangemPayCurrencyStatusUseCase: GetTangemPayCurrencyStatusUseCase,
) : Model() {
private val params = paramsContainer.require<SwapComponent.Params>()
@ -121,6 +123,7 @@ internal class SwapModel @Inject constructor(
private val initialCurrencyTo = params.currencyTo
private val userWalletId = params.userWalletId
private val isInitiallyReversed = params.isInitialReverseOrder
private val tangemPayInput = params.tangemPayInput
private val userWallet by lazy {
requireNotNull(
@ -196,7 +199,7 @@ internal class SwapModel @Inject constructor(
}
modelScope.launch(dispatchers.io) {
if (accountsFeatureToggles.isFeatureEnabled) {
if (accountsFeatureToggles.isFeatureEnabled && tangemPayInput == null) {
isAccountsMode = isAccountsModeEnabledUseCase.invokeSync()
val fromAccountStatus = getAccountCurrencyStatusUseCase.invokeSync(
@ -220,10 +223,19 @@ internal class SwapModel @Inject constructor(
initTokens(isInitiallyReversed)
}
} else {
val fromStatus = getSingleCryptoCurrencyStatusUseCase.invokeMultiWalletSync(
userWalletId = userWalletId,
cryptoCurrencyId = initialCurrencyFrom.id,
).getOrNull()
val fromStatus = if (tangemPayInput != null) {
getTangemPayCurrencyStatusUseCase(
currency = initialCurrencyFrom,
cryptoAmount = tangemPayInput.cryptoAmount,
fiatAmount = tangemPayInput.fiatAmount,
depositAddress = tangemPayInput.depositAddress,
)
} else {
getSingleCryptoCurrencyStatusUseCase.invokeMultiWalletSync(
userWalletId = userWalletId,
cryptoCurrencyId = initialCurrencyFrom.id,
).getOrNull()
}
val toStatus = initialCurrencyTo?.let {
getSingleCryptoCurrencyStatusUseCase.invokeMultiWalletSync(
userWalletId = userWalletId,
@ -555,6 +567,7 @@ internal class SwapModel @Inject constructor(
selectedFeeType = dataState.selectedFee?.feeType ?: FeeType.NORMAL,
isReverseSwapPossible = isReverseSwapPossible(),
needApplyFCARestrictions = userCountry.needApplyFCARestrictions(),
isForTangemPaySwap = tangemPayInput != null,
)
if (uiState.notifications.any { it is SwapNotificationUM.Error.UnableToCoverFeeWarning }) {
analyticsEventHandler.send(
@ -574,6 +587,7 @@ internal class SwapModel @Inject constructor(
toTokenStatus = toTokenStatus,
isReverseSwapPossible = isReverseSwapPossible(),
toAccount = dataState.toAccount,
isForTangemPaySwap = tangemPayInput != null,
)
}
is SwapState.SwapError -> {
@ -588,6 +602,7 @@ internal class SwapModel @Inject constructor(
isReverseSwapPossible = isReverseSwapPossible(),
needApplyFCARestrictions = userCountry.needApplyFCARestrictions(),
toAccount = dataState.toAccount,
isForTangemPaySwap = tangemPayInput != null,
)
sendErrorAnalyticsEvent(state.error, provider)
}

View file

@ -277,6 +277,7 @@ internal class StateBuilder(
selectedFeeType: FeeType,
isReverseSwapPossible: Boolean,
needApplyFCARestrictions: Boolean,
isForTangemPaySwap: Boolean,
): SwapStateHolder {
if (uiStateHolder.sendCardData !is SwapCardState.SwapCardData) return uiStateHolder
if (uiStateHolder.receiveCardData !is SwapCardState.SwapCardData) return uiStateHolder
@ -369,7 +370,7 @@ internal class StateBuilder(
enabled = getSwapButtonEnabled(notifications),
onClick = actions.onSwapClick,
),
changeCardsButtonState = if (isReverseSwapPossible) {
changeCardsButtonState = if (isReverseSwapPossible && !isForTangemPaySwap) {
ChangeCardsButtonState.ENABLED
} else {
ChangeCardsButtonState.DISABLED
@ -442,6 +443,7 @@ internal class StateBuilder(
expressDataError: ExpressDataError,
isReverseSwapPossible: Boolean,
needApplyFCARestrictions: Boolean,
isForTangemPaySwap: Boolean,
): SwapStateHolder {
if (uiStateHolder.sendCardData !is SwapCardState.SwapCardData) return uiStateHolder
if (uiStateHolder.receiveCardData !is SwapCardState.SwapCardData) return uiStateHolder
@ -500,7 +502,7 @@ internal class StateBuilder(
enabled = false,
onClick = actions.onSwapClick,
),
changeCardsButtonState = if (isReverseSwapPossible) {
changeCardsButtonState = if (isReverseSwapPossible && !isForTangemPaySwap) {
ChangeCardsButtonState.ENABLED
} else {
ChangeCardsButtonState.DISABLED
@ -559,6 +561,7 @@ internal class StateBuilder(
toTokenStatus: CryptoCurrencyStatus?,
toAccount: Account.CryptoPortfolio?,
isReverseSwapPossible: Boolean,
isForTangemPaySwap: Boolean,
): SwapStateHolder {
if (uiStateHolder.sendCardData !is SwapCardState.SwapCardData) return uiStateHolder
if (uiStateHolder.receiveCardData !is SwapCardState.SwapCardData) return uiStateHolder
@ -601,7 +604,7 @@ internal class StateBuilder(
enabled = false,
onClick = { },
),
changeCardsButtonState = if (isReverseSwapPossible) {
changeCardsButtonState = if (isReverseSwapPossible && !isForTangemPaySwap) {
ChangeCardsButtonState.ENABLED
} else {
ChangeCardsButtonState.DISABLED

View file

@ -18,9 +18,11 @@ dependencies {
implementation(projects.core.error)
implementation(projects.core.navigation)
implementation(projects.core.ui)
implementation(projects.core.analytics.models)
/** Common */
implementation(projects.common.ui)
implementation(projects.common.routing)
/** Features api */
implementation(projects.features.tangempay.details.api)

View file

@ -0,0 +1,43 @@
package com.tangem.features.tangempay.components
import androidx.compose.runtime.Composable
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.pay.TangemPayTopUpData
import com.tangem.features.tangempay.model.TangemPayAddFundsModel
import com.tangem.features.tangempay.ui.TangemPayAddFundsContent
import java.math.BigDecimal
internal class TangemPayAddFundsComponent(
appComponentContext: AppComponentContext,
params: Params,
) : ComposableBottomSheetComponent, AppComponentContext by appComponentContext {
private val model: TangemPayAddFundsModel = getOrCreateModel(params = params)
override fun dismiss() {
model.onDismiss()
}
@Composable
override fun BottomSheet() {
TangemPayAddFundsContent(state = model.uiState)
}
data class Params(
val listener: AddFundsListener,
val walletId: UserWalletId,
val cryptoBalance: BigDecimal,
val fiatBalance: BigDecimal,
val depositAddress: String,
val chainId: Int,
)
}
internal interface AddFundsListener {
fun onClickReceive(data: TangemPayTopUpData)
fun onClickSwap(data: TangemPayTopUpData)
fun onDismissAddFunds()
}

View file

@ -87,6 +87,17 @@ internal class TangemPayDetailsComponent(
onDismiss = model.bottomSheetNavigation::dismiss,
),
)
is TangemPayDetailsNavigation.AddFunds -> TangemPayAddFundsComponent(
appComponentContext = context,
params = TangemPayAddFundsComponent.Params(
walletId = navigation.walletId,
cryptoBalance = navigation.cryptoBalance,
fiatBalance = navigation.fiatBalance,
depositAddress = navigation.depositAddress,
chainId = navigation.chainId,
listener = model,
),
)
}
}
}

View file

@ -2,6 +2,7 @@ package com.tangem.features.tangempay.di
import com.tangem.core.decompose.di.ModelComponent
import com.tangem.core.decompose.model.Model
import com.tangem.features.tangempay.model.TangemPayAddFundsModel
import com.tangem.features.tangempay.model.TangemPayAddToWalletModel
import com.tangem.features.tangempay.model.TangemPayCardDetailsBlockModel
import com.tangem.features.tangempay.model.TangemPayChangePinModel
@ -47,4 +48,9 @@ internal interface TangemPayModelModule {
@IntoMap
@ClassKey(TangemPayAddToWalletModel::class)
fun bindTangemPayAddToWalletModel(model: TangemPayAddToWalletModel): Model
@Binds
@IntoMap
@ClassKey(TangemPayAddFundsModel::class)
fun bindTangemPayAddFundsModel(model: TangemPayAddFundsModel): Model
}

View file

@ -0,0 +1,19 @@
package com.tangem.features.tangempay.entity
import androidx.annotation.DrawableRes
import com.tangem.core.ui.components.bottomsheets.message.MessageBottomSheetUMV2
import com.tangem.core.ui.extensions.TextReference
import kotlinx.collections.immutable.ImmutableList
internal data class TangemPayAddFundsUM(
val items: ImmutableList<TangemPayAddFundsItemUM>,
val dismiss: () -> Unit,
val errorMessage: MessageBottomSheetUMV2?,
)
internal data class TangemPayAddFundsItemUM(
@DrawableRes val iconRes: Int,
val title: TextReference,
val description: TextReference,
val onClick: () -> Unit,
)

View file

@ -1,6 +1,8 @@
package com.tangem.features.tangempay.entity
import com.tangem.domain.models.TokenReceiveConfig
import com.tangem.domain.models.serialization.SerializedBigDecimal
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.visa.model.TangemPayTxHistoryItem
import kotlinx.serialization.Serializable
@ -10,6 +12,15 @@ internal sealed class TangemPayDetailsNavigation {
@Serializable
data class Receive(val config: TokenReceiveConfig) : TangemPayDetailsNavigation()
@Serializable
data class AddFunds(
val walletId: UserWalletId,
val cryptoBalance: SerializedBigDecimal,
val fiatBalance: SerializedBigDecimal,
val depositAddress: String,
val chainId: Int,
) : TangemPayDetailsNavigation()
@Serializable
data class TransactionDetails(val transaction: TangemPayTxHistoryItem) : TangemPayDetailsNavigation()
}

View file

@ -14,7 +14,7 @@ import kotlinx.collections.immutable.persistentListOf
internal class TangemPayDetailsStateFactory(
private val onBack: () -> Unit,
private val onRefresh: (ShowRefreshState) -> Unit,
private val onReceive: () -> Unit,
private val onAddFunds: () -> Unit,
private val onClickChangePin: () -> Unit,
private val onClickFreezeCard: () -> Unit,
) {
@ -39,13 +39,14 @@ internal class TangemPayDetailsStateFactory(
balanceBlockState = TangemPayDetailsBalanceBlockState.Loading(
actionButtons = persistentListOf(
ActionButtonConfig(
text = resourceReference(id = R.string.common_receive),
text = resourceReference(id = R.string.tangempay_card_details_add_funds),
iconResId = R.drawable.ic_arrow_down_24,
onClick = onReceive,
onClick = onAddFunds,
),
),
),
addToWalletBlockState = null,
isBalanceHidden = false,
addFundsEnabled = true,
)
}

View file

@ -11,6 +11,7 @@ internal data class TangemPayDetailsUM(
val balanceBlockState: TangemPayDetailsBalanceBlockState,
val addToWalletBlockState: AddToWalletBlockState?,
val isBalanceHidden: Boolean,
val addFundsEnabled: Boolean,
)
internal data class TangemPayCardDetailsUM(

View file

@ -0,0 +1,40 @@
package com.tangem.features.tangempay.model
import androidx.compose.runtime.Stable
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.domain.pay.TangemPayTopUpDataFactory
import com.tangem.features.tangempay.components.TangemPayAddFundsComponent
import com.tangem.features.tangempay.entity.TangemPayAddFundsUM
import com.tangem.features.tangempay.model.transformers.TangemPayAddFundsUMConverter
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import javax.inject.Inject
@Stable
@ModelScoped
internal class TangemPayAddFundsModel @Inject constructor(
paramsContainer: ParamsContainer,
override val dispatchers: CoroutineDispatcherProvider,
private val tangemPayTopUpDataFactory: TangemPayTopUpDataFactory,
) : Model() {
private val params = paramsContainer.require<TangemPayAddFundsComponent.Params>()
val uiState: TangemPayAddFundsUM = getInitialState()
private fun getInitialState(): TangemPayAddFundsUM {
val data = tangemPayTopUpDataFactory.create(
depositAddress = params.depositAddress,
chainId = params.chainId,
cryptoBalance = params.cryptoBalance,
fiatBalance = params.fiatBalance,
).getOrNull()
return TangemPayAddFundsUMConverter(listener = params.listener).convert(data)
}
fun onDismiss() {
params.listener.onDismissAddFunds()
}
}

View file

@ -3,6 +3,9 @@ package com.tangem.features.tangempay.model
import androidx.compose.runtime.Stable
import com.arkivanov.decompose.router.slot.SlotNavigation
import com.arkivanov.decompose.router.slot.activate
import com.arkivanov.decompose.router.slot.dismiss
import com.tangem.common.routing.AppRoute
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
@ -10,9 +13,11 @@ import com.tangem.core.decompose.navigation.Router
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig.ShowRefreshState
import com.tangem.domain.models.TokenReceiveConfig
import com.tangem.domain.pay.DataForReceiveFactory
import com.tangem.domain.pay.TangemPayTopUpData
import com.tangem.domain.pay.model.TangemPayCardBalance
import com.tangem.domain.pay.repository.CardDetailsRepository
import com.tangem.domain.visa.model.TangemPayTxHistoryItem
import com.tangem.features.tangempay.components.AddFundsListener
import com.tangem.features.tangempay.components.TangemPayDetailsContainerComponent
import com.tangem.features.tangempay.entity.TangemPayDetailsErrorType
import com.tangem.features.tangempay.entity.TangemPayDetailsNavigation
@ -44,17 +49,16 @@ internal class TangemPayDetailsModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider,
private val router: Router,
private val cardDetailsRepository: CardDetailsRepository,
private val dataForReceiveFactory: DataForReceiveFactory,
private val uiMessageSender: UiMessageSender,
private val cardDetailsEventListener: CardDetailsEventListener,
) : Model(), TangemPayTxHistoryUiActions {
) : Model(), TangemPayTxHistoryUiActions, AddFundsListener {
private val params: TangemPayDetailsContainerComponent.Params = paramsContainer.require()
private val stateFactory = TangemPayDetailsStateFactory(
onBack = router::pop,
onRefresh = ::onRefreshSwipe,
onReceive = ::onClickReceive,
onAddFunds = ::onClickAddFunds,
onClickChangePin = ::onClickChangePin,
onClickFreezeCard = ::onClickFreezeCard,
)
@ -66,6 +70,8 @@ internal class TangemPayDetailsModel @Inject constructor(
private val fetchBalanceJobHolder = JobHolder()
private val addToWalletBannerJobHolder = JobHolder()
private var balance: TangemPayCardBalance? = null
val bottomSheetNavigation: SlotNavigation<TangemPayDetailsNavigation> = SlotNavigation()
init {
@ -81,30 +87,27 @@ internal class TangemPayDetailsModel @Inject constructor(
// TODO [REDACTED_JIRA]
}
private fun onClickReceive() {
val depositAddress = params.config.depositAddress
if (depositAddress == null) {
private fun onClickAddFunds() {
if (params.config.depositAddress == null || balance == null) {
showBottomSheetError(TangemPayDetailsErrorType.Receive)
} else {
dataForReceiveFactory.getDataForReceive(depositAddress = depositAddress, chainId = params.config.chainId)
.onRight {
val config = TokenReceiveConfig(
shouldShowWarning = false,
cryptoCurrency = it.currency,
userWalletId = it.walletId,
showMemoDisclaimer = false,
receiveAddress = it.receiveAddress,
)
bottomSheetNavigation.activate(TangemPayDetailsNavigation.Receive(config))
}
.onLeft { showBottomSheetError(TangemPayDetailsErrorType.Receive) }
bottomSheetNavigation.activate(
TangemPayDetailsNavigation.AddFunds(
walletId = params.userWalletId,
fiatBalance = requireNotNull(balance).balance,
// Using the fiat balance as crypto balance. This will be changed when back returns crypto balance
cryptoBalance = requireNotNull(balance).balance,
depositAddress = requireNotNull(params.config.depositAddress),
chainId = params.config.chainId,
),
)
}
}
private fun fetchBalance(): Job {
return modelScope.launch {
val result = cardDetailsRepository.getCardBalance()
uiState.update(DetailsBalanceTransformer(result))
val result = cardDetailsRepository.getCardBalance().onRight { balance = it }
uiState.update(DetailsBalanceTransformer(balance = result))
}.saveIn(fetchBalanceJobHolder)
}
@ -147,6 +150,39 @@ internal class TangemPayDetailsModel @Inject constructor(
}.saveIn(addToWalletBannerJobHolder)
}
override fun onClickSwap(data: TangemPayTopUpData) {
bottomSheetNavigation.dismiss()
router.push(
AppRoute.Swap(
currencyFrom = data.currency,
userWalletId = data.walletId,
isInitialReverseOrder = true,
screenSource = AnalyticsParam.ScreensSources.TangemPay.value,
tangemPayInput = AppRoute.Swap.TangemPayInput(
cryptoAmount = data.cryptoBalance,
fiatAmount = data.fiatBalance,
depositAddress = data.depositAddress,
),
),
)
}
override fun onClickReceive(data: TangemPayTopUpData) {
bottomSheetNavigation.dismiss()
val config = TokenReceiveConfig(
shouldShowWarning = false,
cryptoCurrency = data.currency,
userWalletId = data.walletId,
showMemoDisclaimer = false,
receiveAddress = data.receiveAddress,
)
bottomSheetNavigation.activate(TangemPayDetailsNavigation.Receive(config))
}
override fun onDismissAddFunds() {
bottomSheetNavigation.dismiss()
}
override fun onTransactionClick(item: TangemPayTxHistoryItem) {
bottomSheetNavigation.activate(TangemPayDetailsNavigation.TransactionDetails(item))
}

View file

@ -0,0 +1,48 @@
package com.tangem.features.tangempay.model.transformers
import com.tangem.core.ui.extensions.TextReference
import com.tangem.domain.pay.TangemPayTopUpData
import com.tangem.features.tangempay.components.AddFundsListener
import com.tangem.features.tangempay.details.impl.R
import com.tangem.features.tangempay.entity.TangemPayAddFundsItemUM
import com.tangem.features.tangempay.entity.TangemPayAddFundsUM
import com.tangem.features.tangempay.entity.TangemPayDetailsErrorType
import com.tangem.features.tangempay.utils.TangemPayErrorMessageFactory
import com.tangem.utils.converter.Converter
import kotlinx.collections.immutable.persistentListOf
internal class TangemPayAddFundsUMConverter(
val listener: AddFundsListener,
) : Converter<TangemPayTopUpData?, TangemPayAddFundsUM> {
override fun convert(value: TangemPayTopUpData?): TangemPayAddFundsUM {
return if (value == null) {
TangemPayAddFundsUM(
items = persistentListOf(),
dismiss = listener::onDismissAddFunds,
errorMessage = TangemPayErrorMessageFactory.createErrorMessage(
type = TangemPayDetailsErrorType.Receive,
).messageBottomSheetUMV2,
)
} else {
TangemPayAddFundsUM(
items = persistentListOf(
TangemPayAddFundsItemUM(
iconRes = R.drawable.ic_exchange_vertical_24,
title = TextReference.Res(R.string.common_exchange),
description = TextReference.Res(R.string.tangempay_card_details_swap_description),
onClick = { listener.onClickSwap(value) },
),
TangemPayAddFundsItemUM(
iconRes = R.drawable.ic_arrow_down_24,
title = TextReference.Res(R.string.common_receive),
description = TextReference.Res(R.string.tangempay_card_details_receive_description),
onClick = { listener.onClickReceive(value) },
),
),
dismiss = listener::onDismissAddFunds,
errorMessage = null,
)
}
}
}

View file

@ -0,0 +1,154 @@
package com.tangem.features.tangempay.ui
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Icon
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.key
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.SpacerH16
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import com.tangem.core.ui.components.bottomsheets.message.MessageBottomSheetV2Content
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheet
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetTitle
import com.tangem.core.ui.extensions.TextReference.Res
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.features.tangempay.details.impl.R
import com.tangem.features.tangempay.entity.TangemPayAddFundsItemUM
import com.tangem.features.tangempay.entity.TangemPayAddFundsUM
import kotlinx.collections.immutable.persistentListOf
@Composable
internal fun TangemPayAddFundsContent(state: TangemPayAddFundsUM) {
TangemModalBottomSheet<TangemBottomSheetConfigContent.Empty>(
config = TangemBottomSheetConfig(
isShown = true,
onDismissRequest = state.dismiss,
content = TangemBottomSheetConfigContent.Empty,
),
onBack = state.dismiss,
containerColor = TangemTheme.colors.background.tertiary,
title = {
TangemModalBottomSheetTitle(
title = resourceReference(R.string.tangempay_card_details_add_funds),
endIconRes = R.drawable.ic_close_24,
onEndClick = state.dismiss,
)
},
) {
if (state.errorMessage != null) {
MessageBottomSheetV2Content(state.errorMessage)
} else {
Surface(
modifier = Modifier
.padding(horizontal = 16.dp)
.fillMaxWidth(),
shape = TangemTheme.shapes.roundedCornersXMedium,
color = TangemTheme.colors.background.primary,
) {
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.Start,
) {
Text(
modifier = Modifier.padding(top = 12.dp, start = 16.dp, end = 16.dp),
text = resourceReference(R.string.tangempay_card_details_add_funds_subtitle).resolveReference(),
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.subtitle2,
)
state.items.forEach {
key(it.title) {
TangemPayTopUpItem(state = it)
}
}
}
}
SpacerH16()
}
}
}
@Composable
private fun TangemPayTopUpItem(state: TangemPayAddFundsItemUM, modifier: Modifier = Modifier) {
Row(
modifier = modifier
.fillMaxWidth()
.clickable(onClick = state.onClick)
.padding(horizontal = 12.dp, vertical = 16.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(12.dp),
) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.background(
color = TangemTheme.colors.icon.accent.copy(alpha = 0.1f),
shape = CircleShape,
)
.size(36.dp),
) {
Icon(
modifier = Modifier.size(16.dp),
imageVector = ImageVector.vectorResource(id = state.iconRes),
contentDescription = null,
tint = TangemTheme.colors.icon.accent,
)
}
Column(
verticalArrangement = Arrangement.spacedBy(2.dp),
) {
Text(
text = state.title.resolveReference(),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.primary1,
)
Text(
text = state.description.resolveReference(),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
)
}
}
}
@Preview(showBackground = true)
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun TangemPayAddFundsContentPreview() {
TangemThemePreview {
TangemPayAddFundsContent(
state = TangemPayAddFundsUM(
items = persistentListOf(
TangemPayAddFundsItemUM(
iconRes = R.drawable.ic_exchange_vertical_24,
title = Res(R.string.common_exchange),
description = Res(R.string.exсhange_token_description),
onClick = {},
),
TangemPayAddFundsItemUM(
iconRes = R.drawable.ic_arrow_down_24,
title = Res(R.string.common_receive),
description = Res(R.string.receive_token_description),
onClick = {},
),
),
dismiss = {},
errorMessage = null,
),
)
}
}

View file

@ -313,6 +313,7 @@ private class TangemPayDetailsUMProvider : CollectionPreviewParameterProvider<Ta
),
addToWalletBlockState = AddToWalletBlockState({}, {}),
isBalanceHidden = false,
addFundsEnabled = true,
),
TangemPayDetailsUM(
topBarConfig = TangemPayDetailsTopBarConfig(onBackClick = {}, items = null),
@ -320,6 +321,7 @@ private class TangemPayDetailsUMProvider : CollectionPreviewParameterProvider<Ta
balanceBlockState = TangemPayDetailsBalanceBlockState.Loading(actionButtons = persistentListOf()),
addToWalletBlockState = AddToWalletBlockState({}, {}),
isBalanceHidden = false,
addFundsEnabled = true,
),
),
)