From 4febcb6c9371e566b2101bcf3d0bd9e1fc2a08a2 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 30 Jun 2025 17:51:33 +0500 Subject: [PATCH] Updated on 2026-08-14 --- core/res/src/main/res/values-de/strings.xml | 6 +- core/res/src/main/res/values-ja/strings.xml | 76 ++++++- core/res/src/main/res/values-ru/strings.xml | 5 + .../impl/amount/SwapAmountBlockComponent.kt | 104 +++++++++ .../v2/impl/amount/model/SwapAlertFactory.kt | 64 ++++++ .../amount/model/SwapAmountClickIntents.kt | 1 + .../v2/impl/amount/model/SwapAmountModel.kt | 17 ++ .../impl/amount/ui/SwapAmountBlockContent.kt | 201 ++++++++++++++++++ .../ui/preview/SwapAmountClickIntentsStub.kt | 2 + 9 files changed, 473 insertions(+), 3 deletions(-) create mode 100644 features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/SwapAmountBlockComponent.kt create mode 100644 features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAlertFactory.kt create mode 100644 features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/ui/SwapAmountBlockContent.kt diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index e90c8e89e2..1432eaaaa2 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -574,7 +574,7 @@ Netzwerk nicht hinzugefügt Nicht hinzugefügt Nicht unterstützte NFT-Arten - cNFTs und pNFTs werden noch nicht unterstützt + cNFTs und pNFTs werden derzeit nicht unterstützt. Bitte sende diese nicht an Deine Wallet. NFT senden Eigenschaften Unbetitelte Sammlung @@ -821,6 +821,7 @@ Überprüfe deine Netzwerkverbindung Informationen zur Netzwerkgebühr nicht erreichbar Von + Von %s Grenzwert Gasgebühr Dies ist die maximale Gasgebühr, die für den Abschluss einer Transaktion oder eines Vertrags ausgegeben wird. Ein Gaslimit verhindert unerwartete oder unbegrenzte Gebühren bei der Ausführung einer Transaktion. Gaspreis @@ -828,6 +829,7 @@ Max Höchstbetrag Gebühr bis zu + Memo: %s Ungültiges Memo Abdeckung der Netzgebühren Nonce @@ -878,6 +880,7 @@ Die Netzwerkgebühr wird durch die Nutzung von %1$s Energieträgern gedeckt. Die Netzwerkgebühr wird durch den Verbrauch von %1$s Energie reduziert inklusive einer Netzgebühr von %1$s + Zur Adresse Die Transaktion wurde erfolgreich signiert und an den Blockchain-Knoten gesendet. Die Walletbilanz wird aktualisiert %1$s ist ein Vermögenswert im Tron-Netzwerk. Um die Gebühr zu berechnen und eine Transaktion durchzuführen, musst du etwas Tron (TRX) auf deinem Konto einzahlen. Der Betrag geht über die Bilanz hinaus @@ -887,6 +890,7 @@ Die Gebühr geht über die Bilanz hinaus Der Gesamtbetrag geht über die Bilanz hinaus Sende uns ein Token, und wir konvertieren es unterwegs. Dein Empfänger erhält genau das, was er braucht – nahtlos. + Wird an den Empfänger gesendet Senden mit Swap Transaktion gesendet Bereite das Scannen der Karte oder Ring vor, die du einrichten möchtest. diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml index 11cb1fb068..06b321ef1a 100644 --- a/core/res/src/main/res/values-ja/strings.xml +++ b/core/res/src/main/res/values-ja/strings.xml @@ -1,5 +1,14 @@ + アクセスコードがないとウォレットは保護されません。 + とにかくスキップ + アクセスコードが設定されていません + アクセスコードを入力 + 続行するには、以前に入力したコードを確認してください + アクセスコードを再入力 + ウォレットのロックを解除するには、 %s桁のアクセスコードを設定します。 + アクセスコードの作成 + アクセスコード トークンが見つかりませんか?メインページのマーケットセクションに移動し、ポートフォリオに追加して買付できるようにします。 トークンが見つかりませんか?メインページのマーケットセクションに移動し、ポートフォリオに追加して売却できるようにします。 売却 @@ -49,6 +58,17 @@ システムのデフォルト テーマ アプリ設定 + ウォレットのバックアップが正常に完了しました。 + バックアップが完了しました + シークレットリカバリーフレーズは、ウォレットへのアクセスと復元のために使用される%sのランダムな単語のセットです。 + これらの単語は紛失した場合、復元できません。必ず安全な場所に保管してください。 + 安全に保管してください + これらの%s個の単語をパスワードマネージャーなどの安全な場所に保存し、決して他の人と共有しないでください。 + 復元は不可能です + リカバリーフレーズ + これらの%s語を順番に書き留めて、安全かつプライベートに保管してください。 + ウォレットと、リカバリーフレーズのセキュリティとバックアップの全責任は、Tangemではなくユーザーにあります。 + リカバリーフレーズ 残高を表示または非表示にするには、デバイスの画面を下向きにするか、設定でオフにしてください。 今後表示しない わかりました @@ -101,6 +121,7 @@ 承認 承認 注意 + バックアップ 残高: %s 残高 生体認証 @@ -117,10 +138,12 @@ 請求 報酬を受け取る 閉じる + 近日公開 確認 Tangemサポートへ問い合わせる Visaサポートへ問い合わせる 続ける + 変換する コピー アドレスをコピー 作成 @@ -150,6 +173,7 @@ 遅い 速度と料金 終了 + 無料 アドレスを同期する プロバイダーへ移動 トークンへ移動 @@ -202,6 +226,7 @@ もっと見る 署名 署名して送信 + スキップ ステーキング ステーキング 始める @@ -222,6 +247,7 @@ アクセスできません ステーキング解除 %1$sの制限により、1つのトランザクションに収まるUTXOは%2$d個のみです。つまり、 %3$s以下しか送信できません。量を減らす必要があります。 + 値がコピーされました はい @@ -377,10 +403,19 @@ 許可を与える 無制限 既存のウォレットを追加 + 新しいウォレットを作成する Tangemを注文 Tangemをスキャン %sへ %sネットワーク + バックアップへ移動 + アクセスコードを使用してウォレットを保護するには、まずバックアップを完了してください。 + まずバックアップを完了する + 受信取引の通知を受け取る + 鍵はアプリに保存されます + 最新の機能とニュースをお届けします + シードフレーズのバックアップ + モバイルウォレットを作成する この情報はAIで生成されました。 \nエラーが見つかった場合は、ここをタップしてください。 アクセスコードを変更するには、上図のようにカードまたはリングをタップし、操作が終了するまで取り外さないでください。 パスコードを変更するには、上記のようにカードをタップし、操作が終了するまで取り外さないでください。 @@ -565,7 +600,7 @@ ネットワークが追加されていません 未追加 サポートされていないNFTタイプ - cNFTとpNFTはまだサポートされていません + cNFTとpNFTはまだサポートされていません。ウォレットに送信しないでください。 NFTを送信する 特徴 無題のコレクション @@ -621,6 +656,7 @@ 秘密鍵を非公開で生成する カードは有効化され、使用可能になりました 成功! + ウォレットの設定が完了し、使用できるようになりました。 この場合は、最初からやり直す必要があります。 アクティベーション処理を途中で終了しますか? スタート @@ -783,7 +819,9 @@ 推奨手数料を下回る手数料が指定されたため、取引に遅れが生じる可能性があります。続行しますか? 理由: %1$s \nコード: %2$s 取引は完了していません + 別のトークンに変換する 金額 + 受取人に送信されます 取引手数料は、vByteフィールドのSatoshiの値を調整して設定できます。 取引にかかる手数料です。自由に設定できます。 最大手数料 @@ -807,6 +845,7 @@ ネットワーク接続を確認してください ネットワーク手数料についての情報にアクセスできません より + %sから ガス上限 これは、取引または契約を完了するために使用されるガスの最大額です。ガス上限を設定することで、取引実行時に予期せぬ請求や無制限の請求を防ぐことができます。 ガス代 @@ -814,6 +853,7 @@ 最大 最大金額 最大手数料 + メモ: %s 無効なメモ ネットワーク手数料のカバー ナンス @@ -827,6 +867,7 @@ カスタム手数料が高くなっています %1$sネットワークの特殊性により、残高全体を転送する場合の手数料は高くなります。手数料を削減するには、 %2$sを残します。 手数料が高くなっています + 受取人の口座が有効化されていません。最低送金額はレント免除残高%1$s以上である必要があります。 口座残高はレンタル料金より低くすることはできません。残高を少なくとも%1$sに維持するか、すべての資金を引き出してください。 手数料が送金金額を超えており、マイナスの値になってしまいます。 無効な金額 @@ -845,7 +886,7 @@ 最近の取引 受取人 有効なアドレスではありません - トークンを失わないように、受信ウォレットアドレスが%sネットワーク上にあることを確認してください。 + トークンの損失を避けるために、受信ウォレットアドレスが** %sネットワーク**上にあることを確認してください %s に送信 %sネットワークアドレスを確認してください。エラーが発生すると転送が失われる可能性があります。 送金先 @@ -864,6 +905,7 @@ ネットワーク手数料は%1$sエネルギーを使用してカバーされます %1$sエネルギーを使用するとネットワーク手数料が減額されます ネットワーク手数料%1$sを含む + アドレスへ 取引は正常に署名され、ブロックチェーンノードに送信されました。ウォレットの残高はしばらくして更新されます。 %1$sはTronネットワークのアセットです。手数料を計算して取引を行うには、アカウントにTron(TRX)を入金する必要があります。 金額が残高を超えています @@ -873,6 +915,8 @@ 手数料が残高を超えています 合計金額が残高を超えています トークンを送信すれば、送信中に変換されます。受信者は必要なものをシームレスに受け取ります。 + 受信者に送信されます + 受取金額 スワップして送信 取引が送信されました 設定したいカードまたはリングをスキャンするために準備してください。 @@ -1028,6 +1072,7 @@ 破格のレート 手間がかからず直感的に操作でき、数回タップするだけでトークンを交換できます。 とにかく便利 + プロバイダー経由のスワップ この金額には以下が含まれます:\n- サービスプロバイダーの手数料\n- 取引所からユーザーのアドレスに%s を送り返すためのネットワーク手数料。 金額には以下が含まれます: \n • サービス プロバイダーの手数料\n • 取引所からユーザーのアドレスに%1$sを送金するためのネットワーク手数料。 \n\nプロバイダーのスリッページは最大%2$sです この金額には、サービスプロバイダーの手数料が含まれています。 @@ -1178,6 +1223,27 @@ dAppsに接続する WalletConnect 接続には数秒かかる場合があります + %sから + Tangemウォレットを購入 — 暗号資産をオフラインで安全に保管できる物理カードです。 + ハードウェアウォレット + 数秒でスマートフォンに安全なウォレットが作成されます。 + モバイルウォレット + 何を選ぶべき? + すでにTangemウォレットをお持ちですか? + 今すぐスキャン + ウォレットの作成方法を選択してください + Tangemウォレットを購入しますか? + 今すぐ購入 + 既存のウォレットを追加 + 暗号資産をオフラインで安全に保管する物理カード。 + Tangemウォレットをスキャンする + ウォレットを復元するためのシードフレーズ + ウォレットをインポート + リカバリーフレーズをインポートする + ウォレットのバックアップが正常に完了しました。 + ウォレットをインポート + インポート完了 + ウォレットをインポート %s市場価格 直近24時間 %sネットワーク @@ -1186,6 +1252,8 @@ 今すぐ10 % オフで購入 1.3万種類以上の暗号資産にアクセス。ワンタップで買付、売却、スワップ、ステーキングが可能です。\nバックアップ用に最大3枚のカードを連携できます。 Tangemウォレットを見る + このウォレットを保護するためのシークレットコードです。ログインと署名に使用されます。 + アクセスコードの設定 / 変更 ウォレットの受信取引とTangemの更新について通知を受け取る。 取引通知 ウォレット設定 @@ -1274,6 +1342,10 @@ 下にスワイプして更新するか、後でもう一度お試しください。 一部のネットワークにアクセスできません 一部のトークン残高を更新できませんでした + トラストラインを有効にする + %sが足りません。このトークンを関連付けるには、XLMアカウントに資金を追加してください。 + このトークンを受け取るには、トラストラインを有効にする必要があります。ネットワークには 0.5 XLMの準備金が必要です。 + トラストラインが必要 これはテストネットカードです。取引処理はできませんので、テストおよび開発目的でのみご利用ください。 テスト目的のみ 残高が古い可能性があります。ページを更新してください。 diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 8ac257b580..4c4ce1336a 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -223,6 +223,7 @@ Недоступно Завершить стейкинг Из-за ограничений %1$s в одну транзакцию может поместиться только %2$d UTXO. Это означает, что вы можете отправить только %3$s или меньше. Вам нужно уменьшить сумму. + Значение скопировано неделю с Да @@ -843,6 +844,7 @@ Установлена высокая комиссия Ввиду особенности сети %1$s комиссия при переводе всего баланса выше. Для того, чтобы снизить комиссию Вы можете оставить %2$s. Комиссия повышена + Аккаунт получателя не активирован. Минимальная сумма перевода должна быть не меньше баланса, необходимого для покрытия арендной платы: %1$s. Баланс вашего счета не может быть ниже арендной платы. Пожалуйста, оставьте на счете не менее %1$s или выведите все средства. Включенная комиссия превышает сумму перевода, что приводит к отрицательному значению Недопустимая сумма @@ -1259,6 +1261,9 @@ Свайпните вниз для обновления или попробуйте позже. Некоторые сети недоступны Некоторые балансы токенов не удалось обновить + Открыть Trustline + Чтобы получить этот токен, необходимо открыть Trustline. Сеть удержит резерв в размере 0.5 XLM. + Откройте Trustline Это Testnet карта. Он не может обрабатывать транзакции и используется только в целях тестирования и разработки. Только для целей тестирования Возможно, баланс устарел. Обновите страницу. diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/SwapAmountBlockComponent.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/SwapAmountBlockComponent.kt new file mode 100644 index 0000000000..f0f3fc46a4 --- /dev/null +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/SwapAmountBlockComponent.kt @@ -0,0 +1,104 @@ +package com.tangem.features.swap.v2.impl.amount + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.arkivanov.decompose.ComponentContext +import com.arkivanov.decompose.extensions.compose.subscribeAsState +import com.arkivanov.decompose.router.slot.activate +import com.arkivanov.decompose.router.slot.childSlot +import com.arkivanov.decompose.router.slot.dismiss +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.context.childByContext +import com.tangem.core.decompose.model.getOrCreateModel +import com.tangem.core.ui.decompose.ComposableBottomSheetComponent +import com.tangem.core.ui.decompose.ComposableContentComponent +import com.tangem.domain.express.models.ExpressProvider +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.features.swap.v2.impl.amount.SwapAmountComponentParams.AmountBlockParams +import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM +import com.tangem.features.swap.v2.impl.amount.model.SwapAmountModel +import com.tangem.features.swap.v2.impl.amount.ui.SwapAmountBlockContent +import com.tangem.features.swap.v2.impl.chooseprovider.SwapChooseProviderComponent +import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM +import kotlinx.collections.immutable.ImmutableList +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach + +internal class SwapAmountBlockComponent( + private val appComponentContext: AppComponentContext, + private val params: AmountBlockParams, + private val onResult: (SwapAmountUM) -> Unit, + val onClick: () -> Unit, +) : ComposableContentComponent, AppComponentContext by appComponentContext { + + private val model: SwapAmountModel = getOrCreateModel(params = params) + + private val bottomSheetSlot = childSlot( + source = model.bottomSheetNavigation, + serializer = null, + handleBackButton = true, + childFactory = ::bottomSheetChild, + ) + + init { + model.uiState.onEach { + onResult(it) + }.launchIn(componentScope) + } + + fun updateState(amountUM: SwapAmountUM) = model.updateState(amountUM) + + @Composable + override fun Content(modifier: Modifier) { + val state by model.uiState.collectAsStateWithLifecycle() + val bottomSheet by bottomSheetSlot.subscribeAsState() + + val isClickEnabled by params.blockClickEnableFlow.collectAsStateWithLifecycle() + + SwapAmountBlockContent( + amountUM = state, + onInfoClick = model::onInfoClick, + isClickEnabled = isClickEnabled, + onClick = onClick, + onProviderSelectClick = { + val amountUM = model.uiState.value as? SwapAmountUM.Content ?: return@SwapAmountBlockContent + val selectedProvider = amountUM.selectedQuote.provider ?: return@SwapAmountBlockContent + val cryptoCurrency = params.secondaryCryptoCurrency ?: return@SwapAmountBlockContent + + model.bottomSheetNavigation.activate( + SwapChooseProviderConfig( + providers = amountUM.swapQuotes, + cryptoCurrency = cryptoCurrency, + selectedProvider = selectedProvider, + ), + ) + }, + ) + + bottomSheet.child?.instance?.BottomSheet() + } + + private fun bottomSheetChild( + config: SwapChooseProviderConfig, + componentContext: ComponentContext, + ): ComposableBottomSheetComponent { + return SwapChooseProviderComponent( + context = childByContext(componentContext), + params = SwapChooseProviderComponent.Params( + providers = config.providers, + cryptoCurrency = config.cryptoCurrency, + selectedProvider = config.selectedProvider, + callback = model, + onDismiss = { model.bottomSheetNavigation.dismiss() }, + ), + ) + } + + data class SwapChooseProviderConfig( + val providers: ImmutableList, + val cryptoCurrency: CryptoCurrency, + val selectedProvider: ExpressProvider, + ) +} \ No newline at end of file diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAlertFactory.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAlertFactory.kt new file mode 100644 index 0000000000..80af642d28 --- /dev/null +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAlertFactory.kt @@ -0,0 +1,64 @@ +package com.tangem.features.swap.v2.impl.amount.model + +import com.tangem.core.decompose.di.ModelScoped +import com.tangem.core.decompose.ui.UiMessageSender +import com.tangem.core.ui.extensions.* +import com.tangem.core.ui.message.DialogMessage +import com.tangem.core.ui.utils.parseBigDecimal +import com.tangem.domain.express.models.ExpressProvider +import com.tangem.domain.express.models.ExpressProviderType +import com.tangem.features.swap.v2.impl.R +import com.tangem.utils.StringsSigns.PERCENT +import javax.inject.Inject + +@ModelScoped +internal class SwapAlertFactory @Inject constructor( + private val uiMessageSender: UiMessageSender, +) { + + fun priceImpactAlert(hasPriceImpact: Boolean, currencySymbol: String, provider: ExpressProvider) { + val slippage = provider.slippage?.let { "${it.parseBigDecimal(1)}$PERCENT" } + val combinedMessage = buildList { + when (provider.type) { + ExpressProviderType.CEX -> { + if (slippage != null) { + add( + resourceReference( + id = R.string.swapping_alert_cex_description_with_slippage, + formatArgs = wrappedList(currencySymbol, slippage), + ), + ) + } else { + add(resourceReference(R.string.swapping_alert_cex_description, wrappedList(currencySymbol))) + } + } + ExpressProviderType.DEX, + ExpressProviderType.DEX_BRIDGE, + -> { + if (hasPriceImpact) { + add(resourceReference(R.string.swapping_high_price_impact_description)) + add(stringReference("\n\n")) + } + if (slippage != null) { + add( + resourceReference( + id = R.string.swapping_alert_dex_description_with_slippage, + formatArgs = wrappedList(slippage), + ), + ) + } else { + add(resourceReference(R.string.swapping_alert_dex_description, wrappedList(currencySymbol))) + } + } + else -> Unit + } + } + uiMessageSender.send( + DialogMessage( + title = resourceReference(R.string.swapping_alert_title), + message = combinedReference(combinedMessage.toWrappedList()), + firstActionBuilder = { okAction() }, + ), + ) + } +} \ No newline at end of file diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAmountClickIntents.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAmountClickIntents.kt index f9c92b77a1..34cf98f664 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAmountClickIntents.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAmountClickIntents.kt @@ -6,5 +6,6 @@ import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountType internal interface SwapAmountClickIntents : AmountScreenClickIntents { fun onExpandEditField(selectedAmountType: SwapAmountType) + fun onInfoClick() fun onSelectTokenClick() } \ No newline at end of file diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAmountModel.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAmountModel.kt index b8461d6736..97287393a0 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAmountModel.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAmountModel.kt @@ -1,6 +1,7 @@ package com.tangem.features.swap.v2.impl.amount.model import arrow.core.getOrElse +import com.arkivanov.decompose.router.slot.SlotNavigation import com.tangem.common.routing.AppRoute import com.tangem.common.routing.AppRouter import com.tangem.common.ui.amountScreen.converters.MaxEnterAmountConverter @@ -29,6 +30,7 @@ import com.tangem.domain.tokens.GetMultiCryptoCurrencyStatusUseCase import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.transaction.usecase.GetAllowanceUseCase import com.tangem.features.swap.v2.impl.R +import com.tangem.features.swap.v2.impl.amount.SwapAmountBlockComponent.SwapChooseProviderConfig import com.tangem.features.swap.v2.impl.amount.SwapAmountComponentParams import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountFieldUM import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountType @@ -64,6 +66,7 @@ internal class SwapAmountModel @Inject constructor( private val getAllowanceUseCase: GetAllowanceUseCase, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val appRouter: AppRouter, + private val swapAlertFactory: SwapAlertFactory, ) : Model(), SwapAmountClickIntents, SwapChooseProviderComponent.ModelCallback { private val params: SwapAmountComponentParams = paramsContainer.require() @@ -85,6 +88,8 @@ internal class SwapAmountModel @Inject constructor( private var primaryMinimumAmountBoundary: EnterAmountBoundary by Delegates.notNull() private var secondaryMinimumAmountBoundary: EnterAmountBoundary by Delegates.notNull() + val bottomSheetNavigation: SlotNavigation = SlotNavigation() + val uiState: StateFlow field = MutableStateFlow(params.amountUM) @@ -121,6 +126,18 @@ internal class SwapAmountModel @Inject constructor( } } + override fun onInfoClick() { + val amountUM = uiState.value as? SwapAmountUM.Content ?: return + val selectedProvider = amountUM.selectedQuote.provider ?: return + val cryptoCurrency = secondaryCryptoCurrency ?: return + + swapAlertFactory.priceImpactAlert( + hasPriceImpact = (amountUM.secondaryAmount as? SwapAmountFieldUM.Content)?.priceImpact != null, + currencySymbol = cryptoCurrency.symbol, + provider = selectedProvider, + ) + } + override fun onAmountValueChange(value: String) { uiState.transformerUpdate( SwapAmountValueChangeTransformer( diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/ui/SwapAmountBlockContent.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/ui/SwapAmountBlockContent.kt new file mode 100644 index 0000000000..291627de98 --- /dev/null +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/ui/SwapAmountBlockContent.kt @@ -0,0 +1,201 @@ +package com.tangem.features.swap.v2.impl.amount.ui + +import android.content.res.Configuration +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.material3.ripple +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.PathEffect +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.rememberVectorPainter +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.constraintlayout.compose.ConstraintLayout +import com.tangem.common.ui.amountScreen.models.AmountState +import com.tangem.common.ui.amountScreen.ui.AmountBlockV2 +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.stringResourceSafe +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.features.swap.v2.impl.R +import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountFieldUM +import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM +import com.tangem.features.swap.v2.impl.amount.ui.preview.SwapAmountContentPreview +import com.tangem.features.swap.v2.impl.chooseprovider.ui.SwapChooseProviderContent + +@Suppress("DestructuringDeclarationWithTooManyEntries") +@Composable +internal fun SwapAmountBlockContent( + amountUM: SwapAmountUM, + isClickEnabled: Boolean, + onProviderSelectClick: () -> Unit, + onInfoClick: () -> Unit, + onClick: () -> Unit, + modifier: Modifier = Modifier, +) { + if (amountUM !is SwapAmountUM.Content) return + ConstraintLayout( + modifier = modifier + .clip(RoundedCornerShape(16.dp)) + .background(TangemTheme.colors.background.action) + .clickable( + indication = ripple(), + interactionSource = remember { MutableInteractionSource() }, + enabled = isClickEnabled, + onClick = onClick, + ), + ) { + val (from, to, separator, provider) = createRefs() + AmountBlockV2( + amountState = amountUM.primaryAmount.amountField, + isClickDisabled = true, + isEditingDisabled = false, + modifier = Modifier.constrainAs(from) { + top.linkTo(parent.top) + start.linkTo(parent.start) + end.linkTo(parent.end) + }, + extraContent = { + SwapPriceImpact(amountFieldUM = amountUM.primaryAmount, onInfoClick = onInfoClick) + }, + ) + AmountBlockV2( + amountState = (amountUM.secondaryAmount.amountField as? AmountState.Data)?.copy( + title = resourceReference(R.string.send_with_swap_recipient_amount_title), + availableBalance = TextReference.EMPTY, + ) ?: amountUM.secondaryAmount.amountField, + isClickDisabled = true, + isEditingDisabled = false, + modifier = Modifier.constrainAs(to) { + top.linkTo(from.bottom, 8.dp) + start.linkTo(parent.start) + end.linkTo(parent.end) + }, + extraContent = { + SwapPriceImpact(amountFieldUM = amountUM.secondaryAmount, onInfoClick = onInfoClick) + }, + ) + SwapAmountDivider( + modifier = Modifier.constrainAs(separator) { + top.linkTo(from.bottom) + bottom.linkTo(to.top) + start.linkTo(parent.start) + end.linkTo(parent.end) + }, + ) + SwapChooseProviderContent( + expressProvider = amountUM.selectedQuote.provider, + onClick = onProviderSelectClick, + modifier = Modifier.constrainAs(provider) { + top.linkTo(to.bottom) + bottom.linkTo(parent.bottom) + start.linkTo(parent.start) + end.linkTo(parent.end) + }, + ) + } +} + +@Composable +private fun SwapPriceImpact(amountFieldUM: SwapAmountFieldUM, onInfoClick: () -> Unit) { + val priceImpact = (amountFieldUM as? SwapAmountFieldUM.Content)?.priceImpact + if (priceImpact != null) { + Text( + text = priceImpact.resolveReference(), + style = TangemTheme.typography.body2, + color = TangemTheme.colors.text.attention, + ) + Icon( + painter = rememberVectorPainter( + ImageVector.vectorResource(R.drawable.ic_information_24), + ), + tint = TangemTheme.colors.icon.attention, + contentDescription = null, + modifier = Modifier + .size(20.dp) + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = ripple(bounded = false), + onClick = onInfoClick, + ), + ) + } +} + +@Composable +private fun SwapAmountDivider(modifier: Modifier = Modifier) { + Box(modifier = modifier) { + SwapDivider() + Text( + text = stringResourceSafe(R.string.swap_via_provider), + style = TangemTheme.typography.caption1, + color = TangemTheme.colors.text.tertiary, + modifier = Modifier + .background(TangemTheme.colors.stroke.primary, RoundedCornerShape(32.dp)) + .padding(1.dp) + .background(TangemTheme.colors.text.primary2, RoundedCornerShape(32.dp)) // workaround + .background(TangemTheme.colors.icon.informative.copy(alpha = 0.1f), RoundedCornerShape(32.dp)) + .padding(horizontal = 11.dp, vertical = 5.dp) + .align(Alignment.Center), + ) + } +} + +@Suppress("MagicNumber") +@Composable +private fun BoxScope.SwapDivider() { + val color = TangemTheme.colors.background.tertiary + Canvas( + Modifier + .fillMaxWidth() + .align(Alignment.Center), + ) { + val gapWidth = 2.dp.toPx() + val radius = 2.dp.toPx() + + val pathEffect = PathEffect.dashPathEffect( + intervals = floatArrayOf(gapWidth, gapWidth * 3), + phase = 0f, + ) + drawLine( + color = color, + start = Offset(0f, 0f), + end = Offset(size.width, 0f), + pathEffect = pathEffect, + cap = StrokeCap.Round, + strokeWidth = radius, + ) + } +} + +// region Preview +@Composable +@Preview(showBackground = true, widthDp = 360) +@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) +private fun SwapAmountBlockContent_Preview() { + TangemThemePreview { + SwapAmountBlockContent( + amountUM = SwapAmountContentPreview.defaultState, + isClickEnabled = true, + onProviderSelectClick = {}, + onInfoClick = {}, + onClick = {}, + ) + } +} +// endregion \ No newline at end of file diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/ui/preview/SwapAmountClickIntentsStub.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/ui/preview/SwapAmountClickIntentsStub.kt index 98bcb53665..194e61c3d3 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/ui/preview/SwapAmountClickIntentsStub.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/ui/preview/SwapAmountClickIntentsStub.kt @@ -6,6 +6,8 @@ import com.tangem.features.swap.v2.impl.amount.model.SwapAmountClickIntents internal object SwapAmountClickIntentsStub : SwapAmountClickIntents { override fun onExpandEditField(selectedAmountType: SwapAmountType) {} + override fun onInfoClick() {} + override fun onSelectTokenClick() {} override fun onAmountValueChange(value: String) {}