diff --git a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt index e7c231fd1f..6a6d1f4c01 100644 --- a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt +++ b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt @@ -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, ) diff --git a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt index aab2ba7f89..8358462b1e 100644 --- a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt +++ b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt @@ -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") diff --git a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt index 5879b1920e..e784eb24fe 100644 --- a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt +++ b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt @@ -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) { diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index 6960013a5c..656c9a0baa 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -760,6 +760,8 @@ Krypto kaufen Profitiere von **0 % Gebühren** beim Kauf von Kryptowährungen per SEPA-Überweisung. Krypto mit SEPA kaufen + Trage dich in die Warteliste ein und erhalte eine Zahlungskarte, die es so noch nie gab. + Tangem Visa Card Du musst einen einzigen Zugangscode einrichten, um alle deine Geräte zu schützen Schützen Du kannst später auf jeder Karte oder Ring einen individuellen Zugangscode einrichten @@ -1711,7 +1713,7 @@ Tangem erhebt außerdem eine Servicegebühr von 15% auf den erzielten Ertrag. Deine Gelder werden automatisch an Aave überwiesen, sobald die Netzwerkgebühren niedriger sind oder Dein Guthaben den erforderlichen Mindestbetrag erreicht. Historische Renditen - Die Freigabe für Deine Token im Yield-Modus wurde widerrufen. Öffne den Token, um die Berechtigung erneut zu erteilen. + Die Genehmigung für Deine Token im Yield-Modus wurde widerrufen. Öffne den Token, um die Berechtigung erneut zu erteilen. Token-Genehmigung erforderlich Prüfe Deine Netzwerkverbindung Informationen zu den Netzwerkgebühren nicht erreichbar @@ -1743,6 +1745,7 @@ Die Netzwerkgebühr wird von dem Betrag, den Du abhebst, abgezogen. Effektiver Jahreszins für Versorgung Effektiver Jahreszins + Zinsen fallen automatisch an Zinsen fallen automatisch an Ertragsmodus Bearbeitung Deiner Einzahlung diff --git a/core/res/src/main/res/values-es/strings.xml b/core/res/src/main/res/values-es/strings.xml index 1a93e0147b..fc32c4eedf 100644 --- a/core/res/src/main/res/values-es/strings.xml +++ b/core/res/src/main/res/values-es/strings.xml @@ -653,6 +653,8 @@ Comprar cripto Disfrute de unas comisiones del **0%** al comprar criptomonedas mediante transferencias SEPA. Comprar criptomonedas con SEPA + Únete a la lista de espera y consigue una tarjeta de pago como ninguna otra. + Tangem Visa Card Configure un código de acceso único para proteger todos sus dispositivos. Proteger Puede configurar un código de acceso individual en cada tarjeta más adelante @@ -1544,9 +1546,11 @@ La comisión de red se deducirá del importe que retire. Suministro APY APY + Los intereses se devengan automáticamente Los intereses se devengan automáticamente Modo de rendimiento Procesando su depósito + Modo Rendimiento Automático Deposite algo de %1$s %2$s para cubrir la tarifa de red para las transacciones No se puede cubrir la tarifa %s diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml index c1aa707042..ef9f1f5500 100644 --- a/core/res/src/main/res/values-fr/strings.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -626,6 +626,8 @@ Acheter crypto Profitez de **0% de frais** lors de l\'achat de cryptomonnaies via des virements SEPA. Acheter des cryptomonnaies avec SEPA + Rejoignez la liste d’attente et obtenez une carte de paiement pas comme les autres. + Tangem Visa Card Vous devez définir un seul code d\'accès pour protéger tous vos appareils. Protéger Vous pourrez définir un code d\'accès individuel sur chaque carte plus tard diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml index 2d8b7ca984..d7457f6c77 100644 --- a/core/res/src/main/res/values-ja/strings.xml +++ b/core/res/src/main/res/values-ja/strings.xml @@ -30,6 +30,7 @@ アカウントをアーカイブする アーカイブ このアカウントをアーカイブしますが、いつでも復元できます。 + アーカイブ中・・・ アカウント アカウントが保存されました 報酬用のアカウント @@ -156,6 +157,13 @@ %1$dトークン + 続行するには次のデバイスをリセットしてください。 + ウォレットのリセット + すべてのTangemデバイスがリセットされました。ウォレットのアップグレードを続行できます。 + もう一度アップグレードしてください + リセット完了 + すべてのTangemデバイスをリセットしていません + すべてのTangemデバイスをリセットしていません このカードを使用して、このウォレット内の他のカードまたはリングのアクセスコードをリセットしたくない場合は、このオプションを無効にしてください。これにより、このカードのアクセスコードもリセットできなくなりますのでご注意ください。 このカードを使用して、このウォレット内の他のカードのアクセスコードをリセットできます。 アクセスコードの復元 @@ -307,6 +315,7 @@ リロード 名前を変更 必須 + リセット 保存 変更内容を保存 検索 @@ -423,6 +432,7 @@ WalletConnectエラー 別のウォレットのカードまたはリングを使用しました。このウォレットにリンクしているカードまたはリングをタップしてください。 取引に必要な資金が不足しています。アカウントに入金してください。 + ポートフォリオ内のアセットをこのトークンと交換します マイトークン まだトークンを追加していません。マーケットからトークンを追加して交換してください。 %sとの交換はできません @@ -779,6 +789,8 @@ 暗号資産を買い付ける SEPA送金で暗号資産を買い付けると、**手数料 0% ** になります。 SEPAで暗号資産を購入 + 事前申し込み受付中。他にない特別なカードを、いち早く体験しよう。 + Tangem Visaカード すべてのデバイスを保護するには、単一のアクセスコードを設定してください。 保護する 後で各カードおよびリングに個別のアクセスコードを設定できます。 @@ -1303,6 +1315,7 @@ 取引を表示 サービス手数料 手数料 + サポートを受ける 完了 拒否 保留中 @@ -1315,6 +1328,15 @@ データの読み込みに失敗しました。しばらくしてからもう一度お試しください。 カードの一時停止 非表示 + Googleウォレットを開く + 数回タップするだけでTangem Payの設定完了。Google Payですぐに支払いを始めましょう。 + Google Payにカードを追加する + Googleウォレットを開く + 「カードを追加」をタップします + カードの詳細を手動で入力してください + お使いの端末に送信されたワンタイムパスワード(OTP)でカードを認証してください。 + 準備完了!カードはすぐに使用できます。 + Google Payにカードを追加する 技術的な問題が検出されました。しばらくしてからもう一度お試しいただくか、サポートにお問い合わせください。 現在、受け取りは利用できません 表示 diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 481e539277..8d29bcc1a7 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -701,6 +701,8 @@ Купить криптовалюту Наслаждайтесь **нулевой комиссией** при покупке криптовалюты через SEPA-переводы. Покупайте крипту через SEPA + Присоединяйтесь к списку ожидания и получите уникальную платёжную карту, не похожую ни на одну другую. + Tangem Visa Card Установите единый код доступа для защиты всех ваших карт или колец Защита Установите индивидуальный код доступа для каждой карты или кольца позже. @@ -1631,7 +1633,7 @@ Следующие пополнения вашего счёта автоматически поступят в Aave. Активен На паузе - Завершить заработок + Завершение режима доходности Выключив эту функцию, вы выведёте средства из Aave, получите их обратно в %s в своем кошельке и перестанете зарабатывать награды. Комиссия сети будет вычтена из суммы вашего вывода. Годовая доходность (APY) diff --git a/core/res/src/main/res/values-uk-rUA/strings.xml b/core/res/src/main/res/values-uk-rUA/strings.xml index 6812790144..936f193cce 100644 --- a/core/res/src/main/res/values-uk-rUA/strings.xml +++ b/core/res/src/main/res/values-uk-rUA/strings.xml @@ -629,6 +629,8 @@ Купити криптовалюту Насолоджуйтесь **нульовою комісією** при купівлі криптовалюти через перекази SEPA. Купуйте криптовалюту через SEPA + Приєднуйтесь до списку очікування та отримайте унікальну платіжну картку, якої ще не було. + Tangem Visa Card Налаштуйте єдиний код доступу для захисту всіх ваших карток або кілець Захист Встановіть індивідуальний код доступу для кожної картки або кільця пізніше. diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 15e0b5cfc4..51d8c6a65b 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -30,6 +30,7 @@ Archive account Archive You are archiving this account, but you can always get it back. + Archiving... Account Account saved Account for rewards @@ -159,6 +160,13 @@ %d token %d tokens + Please reset the next device to continue. + Wallet reset + All Tangem devices have been reset. You can now continue upgrading your wallet. + Upgrade again + Reset complete + You haven’t reset all your Tangem devices + You haven’t reset all your Tangem devices 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. Allows you to use this card to reset access code on other cards in this wallet Access code recovery @@ -313,6 +321,7 @@ Reload Rename Required + Reset Save Save changes Search @@ -431,6 +440,7 @@ WalletConnect error You have used a card or ring from another wallet. Tap the card or ring associated with this wallet Not enough funds for the transaction. Please top up your account. + Swap any asset in your portfolio for this token My tokens You haven\'t added any tokens yet. Add tokens via Market to swap Cannot be swapped for %s @@ -795,6 +805,8 @@ Buy crypto Enjoy **0% fees** when purchasing crypto via SEPA transfers. Buy Crypto with SEPA + Join the waitlist and get a payment card unlike any other + Tangem Visa Card Set up a single access code to protect all your devices. Protect Set an individual access code for each card or ring later. @@ -1326,6 +1338,9 @@ Explore transaction Service fees Fee + Keep your money safe if your card is lost or stolen. You can unfreeze anytime. + Freeze your card? + Freeze Get Help Completed Declined @@ -1333,6 +1348,8 @@ The bank rejected this transaction request. This fee goes to cover the cost of handling your transfer. Withdrawal + Add funds + Top-up options Change PIN The card is fully ready for payments. PIN code created @@ -1348,9 +1365,11 @@ Verify card using the OTP sent to your device. All set! Your card is ready to use. Add card to Google Pay + Share your address or show QR-code Technical issues detected. Please try again later or contact support. Receive unavailable now Reveal + Swap any asset in your portfolio for card Card details Create account and issue a card Tangem Pay @@ -1869,7 +1888,7 @@ Send, swap, or sell your funds instantly, anytime you want. Access your funds anytime How it works? - Aave is a decentralized protocol managing over $61  billion in total value. + Aave is a decentralized protocol managing over $61 billion in total value. Decentralized and self-custodial By using this service, you agree with provider\n%1$s and %2$s Connect Aave diff --git a/data/visa/build.gradle.kts b/data/visa/build.gradle.kts index 75c79ae815..0e59eaa831 100644 --- a/data/visa/build.gradle.kts +++ b/data/visa/build.gradle.kts @@ -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) diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/DefaultDataForReceiveFactory.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/DefaultTangemPayTopUpDataFactory.kt similarity index 59% rename from data/visa/src/main/kotlin/com/tangem/data/pay/DefaultDataForReceiveFactory.kt rename to data/visa/src/main/kotlin/com/tangem/data/pay/DefaultTangemPayTopUpDataFactory.kt index 7ac7421c08..fafac4cce8 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/DefaultDataForReceiveFactory.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/DefaultTangemPayTopUpDataFactory.kt @@ -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 { + 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 { 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)), ) diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/di/TangemPayDataModule.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/di/TangemPayDataModule.kt index 40c402edf8..43a2e87cc9 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/di/TangemPayDataModule.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/di/TangemPayDataModule.kt @@ -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 diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/usecase/DefaultGetTangemPayCurrencyStatusUseCase.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/usecase/DefaultGetTangemPayCurrencyStatusUseCase.kt new file mode 100644 index 0000000000..53ca506151 --- /dev/null +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/usecase/DefaultGetTangemPayCurrencyStatusUseCase.kt @@ -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, + ), + ) + } +} \ No newline at end of file diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/pay/DataForReceiveFactory.kt b/domain/visa/src/main/kotlin/com/tangem/domain/pay/DataForReceiveFactory.kt deleted file mode 100644 index 1b78f76580..0000000000 --- a/domain/visa/src/main/kotlin/com/tangem/domain/pay/DataForReceiveFactory.kt +++ /dev/null @@ -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 -} - -data class DataForReceive( - val walletId: UserWalletId, - val currency: CryptoCurrency, - val receiveAddress: List, -) \ No newline at end of file diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/pay/TangemPayTopUpDataFactory.kt b/domain/visa/src/main/kotlin/com/tangem/domain/pay/TangemPayTopUpDataFactory.kt new file mode 100644 index 0000000000..887df24ef0 --- /dev/null +++ b/domain/visa/src/main/kotlin/com/tangem/domain/pay/TangemPayTopUpDataFactory.kt @@ -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 +} + +data class TangemPayTopUpData( + val walletId: UserWalletId, + val currency: CryptoCurrency, + val cryptoBalance: BigDecimal, + val fiatBalance: BigDecimal, + val depositAddress: String, + val receiveAddress: List, +) \ No newline at end of file diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/tangempay/GetTangemPayCurrencyStatusUseCase.kt b/domain/visa/src/main/kotlin/com/tangem/domain/tangempay/GetTangemPayCurrencyStatusUseCase.kt new file mode 100644 index 0000000000..a6f4b0baf1 --- /dev/null +++ b/domain/visa/src/main/kotlin/com/tangem/domain/tangempay/GetTangemPayCurrencyStatusUseCase.kt @@ -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? +} \ No newline at end of file diff --git a/features/swap/api/src/main/kotlin/com/tangem/features/swap/SwapComponent.kt b/features/swap/api/src/main/kotlin/com/tangem/features/swap/SwapComponent.kt index 368cdeec5b..3f33c58822 100644 --- a/features/swap/api/src/main/kotlin/com/tangem/features/swap/SwapComponent.kt +++ b/features/swap/api/src/main/kotlin/com/tangem/features/swap/SwapComponent.kt @@ -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 } \ No newline at end of file diff --git a/features/swap/impl/build.gradle.kts b/features/swap/impl/build.gradle.kts index 4d5d733b5e..b1c706c900 100644 --- a/features/swap/impl/build.gradle.kts +++ b/features/swap/impl/build.gradle.kts @@ -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) diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt index ce0227385f..d3091f9242 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt @@ -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() @@ -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) } diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt index 8387c1b9e1..81772e801c 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt @@ -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 diff --git a/features/tangempay/details/impl/build.gradle.kts b/features/tangempay/details/impl/build.gradle.kts index f026e5a44f..6a0e6c5358 100644 --- a/features/tangempay/details/impl/build.gradle.kts +++ b/features/tangempay/details/impl/build.gradle.kts @@ -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) diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/TangemPayAddFundsComponent.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/TangemPayAddFundsComponent.kt new file mode 100644 index 0000000000..db43708e08 --- /dev/null +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/TangemPayAddFundsComponent.kt @@ -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() +} \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/TangemPayDetailsComponent.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/TangemPayDetailsComponent.kt index f5e03302ab..6f3d6490e8 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/TangemPayDetailsComponent.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/TangemPayDetailsComponent.kt @@ -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, + ), + ) } } } \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/di/TangemPayModelModule.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/di/TangemPayModelModule.kt index 912654abc7..2d4297613c 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/di/TangemPayModelModule.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/di/TangemPayModelModule.kt @@ -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 } \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayAddFundsUM.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayAddFundsUM.kt new file mode 100644 index 0000000000..701f12b092 --- /dev/null +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayAddFundsUM.kt @@ -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, + val dismiss: () -> Unit, + val errorMessage: MessageBottomSheetUMV2?, +) + +internal data class TangemPayAddFundsItemUM( + @DrawableRes val iconRes: Int, + val title: TextReference, + val description: TextReference, + val onClick: () -> Unit, +) \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayDetailsNavigation.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayDetailsNavigation.kt index 4bf153edca..9e4c9f8854 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayDetailsNavigation.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayDetailsNavigation.kt @@ -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() } \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayDetailsStateFactory.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayDetailsStateFactory.kt index 16de26202e..102e05307a 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayDetailsStateFactory.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayDetailsStateFactory.kt @@ -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, ) } \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayDetailsUM.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayDetailsUM.kt index 9ab8efa762..2e59edf389 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayDetailsUM.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayDetailsUM.kt @@ -11,6 +11,7 @@ internal data class TangemPayDetailsUM( val balanceBlockState: TangemPayDetailsBalanceBlockState, val addToWalletBlockState: AddToWalletBlockState?, val isBalanceHidden: Boolean, + val addFundsEnabled: Boolean, ) internal data class TangemPayCardDetailsUM( diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayAddFundsModel.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayAddFundsModel.kt new file mode 100644 index 0000000000..4caef18f17 --- /dev/null +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayAddFundsModel.kt @@ -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() + + 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() + } +} \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayDetailsModel.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayDetailsModel.kt index 3b4c752852..c77a21d442 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayDetailsModel.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayDetailsModel.kt @@ -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 = 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)) } diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/transformers/TangemPayAddFundsUMConverter.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/transformers/TangemPayAddFundsUMConverter.kt new file mode 100644 index 0000000000..1f2c4478f2 --- /dev/null +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/transformers/TangemPayAddFundsUMConverter.kt @@ -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 { + + 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, + ) + } + } +} \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayAddFundsContent.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayAddFundsContent.kt new file mode 100644 index 0000000000..badb84e945 --- /dev/null +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayAddFundsContent.kt @@ -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( + 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, + ), + ) + } +} \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayDetailsScreen.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayDetailsScreen.kt index 3cf7c2e7a6..e3b08338ea 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayDetailsScreen.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayDetailsScreen.kt @@ -313,6 +313,7 @@ private class TangemPayDetailsUMProvider : CollectionPreviewParameterProvider