diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index 41c4f24cdb..8f4bc4a262 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -313,6 +313,7 @@ %dStunden her Importieren + in In Arbeit Unzureichende Mittel Später @@ -398,6 +399,7 @@ An Zu %s Heute + Token Zu sendendes Token %d Token diff --git a/core/res/src/main/res/values-es/strings.xml b/core/res/src/main/res/values-es/strings.xml index f7bc36a8d3..b385282be7 100644 --- a/core/res/src/main/res/values-es/strings.xml +++ b/core/res/src/main/res/values-es/strings.xml @@ -309,6 +309,7 @@ Hace %dh Importe + en En progreso Fondos insuficientes Más tarde @@ -394,6 +395,7 @@ A A %s Hoy + Token Token para enviar %d token diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml index 8faa21a926..2835248f7c 100644 --- a/core/res/src/main/res/values-fr/strings.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -309,6 +309,7 @@ Il y a %dh Importez + dans En cours Plus tard En savoir plus @@ -392,6 +393,7 @@ À À %s Aujourd\'hui + Token %d token %d tokens diff --git a/core/res/src/main/res/values-it/strings.xml b/core/res/src/main/res/values-it/strings.xml index 1b7dd158fb..45d98693f9 100644 --- a/core/res/src/main/res/values-it/strings.xml +++ b/core/res/src/main/res/values-it/strings.xml @@ -16,12 +16,14 @@ Fatto Errore Impossibile ottenere la commissione + in Costi della rete OK Mantieni le modifiche Invia Impossibile inviare la transazione Con successo + Token %d gettone %d gettoni diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml index b500909414..a9db096537 100644 --- a/core/res/src/main/res/values-ja/strings.xml +++ b/core/res/src/main/res/values-ja/strings.xml @@ -214,6 +214,7 @@ アクセスが拒否されました アカウント アカウント + %s に失敗しました 有効化 追加 資金を追加 @@ -230,6 +231,8 @@ 適用する 承認 承認 + 承認済み + 承認中 注意 利用可能なネットワーク バックアップ @@ -309,10 +312,14 @@ 非表示 %sまで長押し 時間 + + %d 時間 + %d時間前 インポート + 進行中 残高不足 後で @@ -355,6 +362,8 @@ %1$s — %2$s 続きを読む 受け取る + 受け取り済み + 受け取り中 おすすめ 拒否 リロード @@ -373,6 +382,8 @@ 送る 送金: 取引の送信に失敗しました + 送金中 + 送金済み サーバーが利用できません。しばらくしてからもう一度お試しください。 共有 リンクを共有 @@ -383,6 +394,7 @@ スキップ 問題が発生しました ステーキング + ステーキング済み ステーキング 始める 送信 @@ -390,6 +402,8 @@ サポート 対応ネットワーク スワップ + スワップ済み + スワップ中 Tangem Tangem Wallet タップして長押し @@ -398,6 +412,7 @@ 宛先 %sへ 今日 + トークン 送信するトークン %d トークン @@ -406,6 +421,7 @@ 取引状況 取引 送金 + 送金済み データを読み込めません… わかりました 理解して続行 @@ -415,10 +431,12 @@ ステーキング解除 %1$sの制限により、1つのトランザクションに収まるUTXOは%2$d個のみです。つまり、 %3$s以下しか送信できません。量を減らす必要があります。 値がコピーされました + 投票 ウォレット 警告 + 引き出し中 はい 利息モード コントラクトアドレスをコピーしました! @@ -580,12 +598,14 @@ ベストレート FCA警告リスト 固定レートは利用できません + スワッププロバイダー お得なレート FCA警告リストに掲載されたプロバイダー 最大 %s まで使用可能 %s 以上で利用可能 このペアは利用できません 許可が必要です + 権限が必要です 推奨 %sを買い付けました %sを買い付けています @@ -633,6 +653,7 @@ 承認機能は、別のアドレスに特定の量のトークンを使用する許可を与えるために必要です。設計上スマートコントラクトは、承認しない限りトークンにアクセスできません。トークンを「ロック解除」すると、StakeKitスマート コントラクトがトークンを使用する権限が与えられます。ネットワークのマイナーは、このアクションをブロックチェーンに記録するためにガス料金(あなたが支払う)を受け取ります。承認後、トークンをステーキングできます。 続行するには、Polygonスマートコントラクトが%sを使用することを許可する必要があります 続行するには、%1sスマートコントラクトに%2sを使用する権限を付与してください + 分散型取引所がウォレットと連携するには、許可が必要です。%1s 許可を与える 無制限 アドレスはTangemハードウェアウォレット上で直接生成され、そのまま安全に使用できます。 @@ -660,6 +681,8 @@ 暗号資産をオフラインで厳重保管。カードサイズで、金庫以上の安心を。 実行すると、最初からやり直す必要があります。 Googleドライブのバックアップから既存のウォレットを復元する + ウォレットの復元をさらに簡単にするため、Googleドライブバックアップ機能を準備しています。 + Googleドライブバックアップは近日対応予定です Googleドライブのバックアップ さらに強固なセキュリティのために、新しいウォレットを作成して資産を移動しましょう。 新しいウォレットを作成 @@ -1103,6 +1126,8 @@ 別の方法を選択してください。 現地の規制要件に準拠するため、%@の利用には本人確認が必要です。 決済プロバイダーによる本人確認が必要です。 + 認証する + 重要事項 オンランプ機能を使用することにより、プロバイダの%1$sおよび%2$sに同意するものとします サービスは外部プロバイダーによって提供されます。 \nTangemは責任を負いません。 買付金額は%s以下にしてください @@ -1166,7 +1191,11 @@ 不明なパラメータ クレジットカードまたは銀行口座 アドレスまたはQRコードを共有してください + 暗号資産を安全に売却 + 別のウォレットに送信 ポートフォリオ間で + その他 + クイック入金 メモ不要 %3$sネットワーク上の%1$s ( %2$s ) %2$sネットワーク上の%1$s @@ -1522,6 +1551,7 @@ 承認すると、このスマートコントラクトが今後の取引でトークンを利用できるようになります。 固定レート ネットワークは、あなたがトークンのスワップを承認していることを確認するために、トークン承認手数料を請求します。 + スワップ中 より多くのトークンをより良いレートで、ウォレット内にて直接交換します。 新しいスワッププロバイダーが利用可能になりました! 他のものをお探しですか?\n検索してみるか、別の暗号資産をチェックしてみましょう! @@ -1530,11 +1560,11 @@ 24時間体制のサポートであらゆる問題に対応します。 いつもここに 複数の信頼できるプロバイダーが一箇所に集結。ウォレット内で様々な暗号資産を簡単に交換できます。 - Tangem内で暗号資産を直接交換\n追加の送金は不要\n取引所に資産を移す必要なし + Tangem内で暗号資産を直接交換できます\n追加の送金は不要です\n取引所に資金を移す必要もありません ぜひスワップしてください ウォレット内でスワップ 失敗も死角もありません。取引は常に保護されます。 - スワップは信頼できるプロバイダーを通じて実行されます。秘密鍵は常にTangemウォレット内に保持されます。シンプル。透明。自己管理。 + スワップは信頼できるプロバイダーを通じて実行されます。秘密鍵は常にTangemウォレット内に保持されます。明確で、透明性が高く、自己管理型です。 難攻不落の防御 主導権はあなたの手にあります 幅広いネットワークの中から、常に最適なプロバイダーと料金レートを選択します @@ -1555,7 +1585,9 @@ すべての分散型取引所は、スマートコントラクトがあなたの許可なくウォレットにアクセスするのを防ぐために承認を必要とします。設計上、スマートコントラクトは承認なしでトークンにアクセスできません。トークンを「ロック解除」することで、あなたは1-inchのスマートコントラクトがトークンを使うことを承認します。ネットワークのマイナーは、このアクションをブロックチェーンに記録するためのガス料金(あなたが支払う)を受け取ります。承認後、トークンを交換することができます。 承認 手数料見積りエラーです。サポートにフィードバックをお送りください。 + 送信元 スワップする + 送信 選択したトークンをこの量を交換すると、価格に大きな影響が生じ、結果が減少します。 流動性が低いため、受取額が大幅に少なくなる可能性があります。金額を減らすか、別のプロバイダーをお試しください。 価格への影響が甚大です @@ -1564,11 +1596,14 @@ 許可を与える スワップ スワップ中… + 受け取り先 受け取る トークンを選択 利用不可 この取引に十分な流動性がありません。\n金額を減らすか、別のプロバイダーを選択してください。 取引額が大きすぎます + 送金 + 送金... 皆様からのフィードバックをお待ちしております Tangem Payのベータ版を公開しました カード名を変更できません @@ -1657,6 +1692,7 @@ 現在、出金ができません 現在の処理が完了するまでは、スワップや新しい出金を開始することはできません。 出金処理中です + カード名 %s以上の金額を設定してください 限度額を設定できませんでした。もう一度お試しください 変更 @@ -1708,6 +1744,8 @@ 余計な費用なしで、表示金額のみを支払う あなたの住所や資産を開示することなく、個別の支払い用アカウントが作成されます。 他に類を見ないプライバシー + そして支払いカードを連携します + ウォレットを設定します 無料のTangem Payカードを数分でゲットしましょう Payサポート 支払いアカウント @@ -1757,7 +1795,7 @@ 承認が取り消されましたが、あなたの資金は引き続き利息モードです。操作を行うには、利息モードに移動し、再度承認を付与してください。 利用可能残高 合計残高 - 年間で最大%sを獲得 + 年利最大%s XPUBを生成する 非表示 このトークンをメイン画面から非表示にします。トークンの管理ページからいつでも再度追加できます。 @@ -1783,12 +1821,21 @@ 売却できません %sからのスワップは利用できません スワップはできません + 報酬を請求中 コントラクト: %s + 利息モードを無効化中 + ステーキングで獲得した金額 まだ取引はありません 取引履歴の読み込みに失敗しました。\n情報を更新するには、リロードボタンをクリックしてください。 + %%image%% %s から 複数のアドレス 現在、このブロックチェーンでは取引履歴はサポートされていません。しかしご心配なく!弊社で対応中です。その間、エクスプローラーで確認することができます。 オペレーション + 保留中 + 報酬を再ステーキングしました + 報酬の再ステーキング + ステーキング報酬 + %%image%% %s へ 対象:%s 送金元: %s 送金先: %s @@ -2064,7 +2111,7 @@ MATICはPOLに移行中です。ただし、期限は設定されておらず、MATICはまだ廃止されていません。MATICトークンを引き続き安全に使用することも、POLに交換することもできます。 MATICからPOLへの移行 - %d ネットワークのアドレスを取得するために、カードまたはリングを利用してください + カードまたはリングを使って、[%i}ネットワークのアドレスを取得します 一部のアドレスが見つかりません 現在、ネットワークにアクセスできません。しばらくしてからもう一度お試しください。 diff --git a/core/res/src/main/res/values-pt-rBR/strings.xml b/core/res/src/main/res/values-pt-rBR/strings.xml index 4cd264c962..cc86f5186c 100644 --- a/core/res/src/main/res/values-pt-rBR/strings.xml +++ b/core/res/src/main/res/values-pt-rBR/strings.xml @@ -320,6 +320,7 @@ OUTRO Importar + em Em andamento Saldo insuficiente Mais tarde @@ -407,6 +408,7 @@ Para Para %s Hoje + Token Token a ser enviado token diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 5f7b583b7d..39a182aab0 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -339,6 +339,7 @@ %dч назад Импортировать + в В процессе Недостаточный баланс Позже @@ -429,6 +430,7 @@ На На %s Сегодня + Токен Токен к отправке %d токен 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 0e1fecd93d..ed08a77c16 100644 --- a/core/res/src/main/res/values-uk-rUA/strings.xml +++ b/core/res/src/main/res/values-uk-rUA/strings.xml @@ -321,6 +321,7 @@ %d годин тому Імпортувати + у В процесі Пізніше Дізнатися більше @@ -408,6 +409,7 @@ До На %s Сьогодні + Токен %d токен %d токени diff --git a/core/res/src/main/res/values-zh-rCN/strings.xml b/core/res/src/main/res/values-zh-rCN/strings.xml index 336648fd49..21d712ae21 100644 --- a/core/res/src/main/res/values-zh-rCN/strings.xml +++ b/core/res/src/main/res/values-zh-rCN/strings.xml @@ -312,6 +312,7 @@ 小时之前 导入 + 进行中 余额不足 稍后 @@ -397,6 +398,7 @@ 到 %s 今天 + 代币 要发送的代币 %d代币 diff --git a/core/res/src/main/res/values-zh-rTW/strings.xml b/core/res/src/main/res/values-zh-rTW/strings.xml index 69ae0df956..bd779ef436 100644 --- a/core/res/src/main/res/values-zh-rTW/strings.xml +++ b/core/res/src/main/res/values-zh-rTW/strings.xml @@ -68,6 +68,7 @@ 錯誤 獲取費用失敗 導入 + 進行中 網路費 @@ -92,6 +93,7 @@ 成功 交換 條款和條件 + 代幣 %d 代幣 diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index a986955bd5..b85f6fa201 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -327,6 +327,7 @@ %dh ago Import + in In progress Insufficient balance Later @@ -421,6 +422,7 @@ To To %s Today + Token Token to send %d token @@ -1578,8 +1580,10 @@ The network will charge a token approval fee to verify that you are authorizing the use of your token for the swap. Swap in progress Exchange more tokens at better rates directly in your wallet. + Detailed mode New Swap Provider Available! Looking for something else?\nTry searching or explore another crypto! + Simple mode Search for any token, even if it’s not in your list yet. Use search to find what you need Feel confident with round-the-clock support to help with any issues diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapRepository.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapRepository.kt index 2f5e08ccf7..f30d3ef92f 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapRepository.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapRepository.kt @@ -21,6 +21,7 @@ import com.tangem.datasource.exchangeservice.swap.ExpressUtils import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.datasource.local.preferences.PreferencesKeys import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull +import com.tangem.datasource.local.preferences.utils.storeObject import com.tangem.domain.exchange.RampStateManager import com.tangem.domain.express.models.ExpressOperationType import com.tangem.domain.models.currency.CryptoCurrency @@ -419,4 +420,8 @@ internal class DefaultSwapRepository( override suspend fun getStoredSwapUiMode(): SwapUIMode? { return appPreferencesStore.getObjectSyncOrNull(PreferencesKeys.SWAP_UI_MODE_KEY) } + + override suspend fun storeSwapUiMode(mode: SwapUIMode) { + appPreferencesStore.storeObject(PreferencesKeys.SWAP_UI_MODE_KEY, mode) + } } \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SetSwapUiModeUseCase.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SetSwapUiModeUseCase.kt new file mode 100644 index 0000000000..90055925d2 --- /dev/null +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SetSwapUiModeUseCase.kt @@ -0,0 +1,13 @@ +package com.tangem.feature.swap.domain + +import com.tangem.feature.swap.domain.api.SwapRepository +import com.tangem.feature.swap.domain.models.domain.SwapUIMode + +class SetSwapUiModeUseCase( + private val swapRepository: SwapRepository, +) { + + suspend operator fun invoke(mode: SwapUIMode) { + swapRepository.storeSwapUiMode(mode) + } +} \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/api/SwapRepository.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/api/SwapRepository.kt index fbc9f2828a..fb7b0b78ab 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/api/SwapRepository.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/api/SwapRepository.kt @@ -77,4 +77,6 @@ interface SwapRepository { ): Either suspend fun getStoredSwapUiMode(): SwapUIMode? + + suspend fun storeSwapUiMode(mode: SwapUIMode) } \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt index 963d0ab92d..8a60dec354 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt @@ -3,6 +3,7 @@ package com.tangem.feature.swap.domain.di import com.tangem.feature.swap.domain.AllowPermissionsHandler import com.tangem.feature.swap.domain.AllowPermissionsHandlerImpl import com.tangem.feature.swap.domain.GetSwapUiModeUseCase +import com.tangem.feature.swap.domain.SetSwapUiModeUseCase import com.tangem.feature.swap.domain.SwapInteractor import com.tangem.feature.swap.domain.SwapInteractorImpl import com.tangem.feature.swap.domain.api.SwapRepository @@ -35,6 +36,11 @@ internal class SwapDomainModule { swapFeatureToggles = swapFeatureToggles, swapRepository = swapRepository, ) + + @Provides + @Singleton + fun provideSetSwapUiModeUseCase(swapRepository: SwapRepository): SetSwapUiModeUseCase = + SetSwapUiModeUseCase(swapRepository = swapRepository) } @Module diff --git a/features/swap/domain/src/test/kotlin/com/tangem/feature/swap/domain/SetSwapUiModeUseCaseTest.kt b/features/swap/domain/src/test/kotlin/com/tangem/feature/swap/domain/SetSwapUiModeUseCaseTest.kt new file mode 100644 index 0000000000..97cca8f4c1 --- /dev/null +++ b/features/swap/domain/src/test/kotlin/com/tangem/feature/swap/domain/SetSwapUiModeUseCaseTest.kt @@ -0,0 +1,29 @@ +package com.tangem.feature.swap.domain + +import com.tangem.feature.swap.domain.api.SwapRepository +import com.tangem.feature.swap.domain.models.domain.SwapUIMode +import io.mockk.coVerify +import io.mockk.mockk +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.Test + +internal class SetSwapUiModeUseCaseTest { + + private val swapRepository: SwapRepository = mockk(relaxUnitFun = true) + + private val useCase = SetSwapUiModeUseCase(swapRepository = swapRepository) + + @Test + fun `GIVEN Simple mode WHEN invoke THEN delegates to repository`() = runTest { + useCase.invoke(SwapUIMode.Simple) + + coVerify(exactly = 1) { swapRepository.storeSwapUiMode(SwapUIMode.Simple) } + } + + @Test + fun `GIVEN Detailed mode WHEN invoke THEN delegates to repository`() = runTest { + useCase.invoke(SwapUIMode.Detailed) + + coVerify(exactly = 1) { swapRepository.storeSwapUiMode(SwapUIMode.Detailed) } + } +} \ No newline at end of file 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 8f249d3085..d8f79e8f0d 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 @@ -74,6 +74,7 @@ import com.tangem.feature.swap.component.SwapFeeSelectorBlockComponent import com.tangem.feature.swap.converters.SwapTransactionErrorStateConverter import com.tangem.feature.swap.domain.AllowPermissionsHandler import com.tangem.feature.swap.domain.GetSwapUiModeUseCase +import com.tangem.feature.swap.domain.SetSwapUiModeUseCase import com.tangem.feature.swap.domain.SwapInteractor import com.tangem.feature.swap.domain.TransactionFeeResult import com.tangem.feature.swap.domain.TxFeeSealedState @@ -83,6 +84,7 @@ import com.tangem.feature.swap.domain.models.domain.ExchangeProviderType import com.tangem.feature.swap.domain.models.domain.SwapDataModel import com.tangem.feature.swap.domain.models.domain.SwapPairLeast import com.tangem.feature.swap.domain.models.domain.SwapProvider +import com.tangem.feature.swap.domain.models.domain.SwapUIMode import com.tangem.feature.swap.domain.models.ui.* import com.tangem.feature.swap.domain.transfer.SwapTransferInteractor import com.tangem.feature.swap.models.* @@ -157,6 +159,7 @@ internal class SwapModel @Inject constructor( private val allowPermissionsHandler: AllowPermissionsHandler, private val swapFeatureToggles: SwapFeatureToggles, private val getSwapUiModeUseCase: GetSwapUiModeUseCase, + private val setSwapUiModeUseCase: SetSwapUiModeUseCase, ) : Model() { private val params = paramsContainer.require() @@ -192,6 +195,7 @@ internal class SwapModel @Inject constructor( appCurrencyProvider = Provider(selectedAppCurrencyFlow::value), isAccountsModeProvider = Provider { isAccountsMode }, isGaslessFeeSupportedForNetwork = isGaslessFeeSupportedForNetwork, + shouldShowAbMenu = swapFeatureToggles.isSwapAbEnabled, ) private val inputNumberFormatter = InputNumberFormatter( @@ -1621,9 +1625,16 @@ internal class SwapModel @Inject constructor( onSuccess = { router.replaceAll(SwapRoute.Success) }, + onSwapUIModeChange = ::onSwapUIModeChange, ) } + private fun onSwapUIModeChange(mode: SwapUIMode) { + if (uiState.swapUIMode == mode) return + uiState = uiState.copy(swapUIMode = mode) + modelScope.launch { setSwapUiModeUseCase(mode) } + } + private fun selectWalletInSelector( fromSwapCurrencyStatus: SwapCurrencyStatus?, toSwapCurrencyStatus: SwapCurrencyStatus?, diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt index d96f1afc23..aba41ecb33 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt @@ -33,6 +33,7 @@ internal data class SwapStateHolder( val shouldShowMaxAmount: Boolean, val tosState: TosState? = null, val swapUIMode: SwapUIMode = SwapUIMode.Detailed, + val shouldShowAbMenu: Boolean = false, val onRefresh: () -> Unit, val onBackClicked: () -> Unit, @@ -41,6 +42,7 @@ internal data class SwapStateHolder( val onSuccess: (() -> Unit), val onMaxAmountSelected: (() -> Unit)? = null, val onShowPermissionBottomSheet: () -> Unit = {}, + val onSwapUIModeChange: (SwapUIMode) -> Unit = {}, ) @Immutable diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/models/UiActions.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/models/UiActions.kt index f9f2353886..31ccca8739 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/models/UiActions.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/models/UiActions.kt @@ -1,6 +1,7 @@ package com.tangem.feature.swap.models import com.tangem.feature.swap.domain.models.SwapAmount +import com.tangem.feature.swap.domain.models.domain.SwapUIMode import com.tangem.feature.swap.domain.models.ui.TxFee import java.math.BigDecimal @@ -26,4 +27,5 @@ internal data class UiActions( val onSuccess: () -> Unit, val onLinkClick: (String) -> Unit, val onReceiveCardWarningClick: () -> Unit, + val onSwapUIModeChange: (SwapUIMode) -> Unit, ) \ No newline at end of file diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/ProviderItemSimple.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/ProviderItemSimple.kt new file mode 100644 index 0000000000..71cd53b67e --- /dev/null +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/ProviderItemSimple.kt @@ -0,0 +1,156 @@ +package com.tangem.feature.swap.ui + +import android.content.res.Configuration +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import androidx.compose.ui.unit.dp +import coil.compose.SubcomposeAsyncImage +import coil.request.ImageRequest +import com.tangem.core.ui.R +import com.tangem.core.ui.components.RectangleShimmer +import com.tangem.core.ui.components.SpacerW8 +import com.tangem.core.ui.components.SpacerWMax +import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.extensions.stringResourceSafe +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.feature.swap.models.states.PercentDifference +import com.tangem.feature.swap.models.states.ProviderState + +// TODO: [REDACTED_TASK_KEY] — remove this UI after swap migrates to swap-v2. +// Layout copied from V2 `SwapChooseProviderContent`: +// features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/ui/SwapChooseProviderContent.kt +@Composable +internal fun ProviderItemBlockSimple(state: ProviderState, modifier: Modifier = Modifier) { + if (state is ProviderState.Empty) return + + Row( + modifier = modifier + .clip(RoundedCornerShape(TangemTheme.dimens.radius16)) + .background(TangemTheme.colors.background.primary) + .clickable( + enabled = state.onProviderClick != null, + onClick = { state.onProviderClick?.invoke(state.id) }, + ) + .fillMaxWidth() + .padding(TangemTheme.dimens.spacing12), + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + painter = painterResource(id = R.drawable.ic_stack_new_24), + tint = TangemTheme.colors.icon.accent, + contentDescription = null, + modifier = Modifier.size(TangemTheme.dimens.size24), + ) + SpacerW8() + Text( + text = stringResourceSafe(R.string.express_provider), + style = TangemTheme.typography.body2, + color = TangemTheme.colors.text.primary1, + ) + SpacerWMax() + SimpleProviderTrailing(state = state) + } +} + +@Composable +private fun SimpleProviderTrailing(state: ProviderState) { + when (state) { + is ProviderState.Content -> { + SubcomposeAsyncImage( + model = ImageRequest.Builder(context = LocalContext.current) + .data(state.iconUrl) + .crossfade(enable = true) + .allowHardware(false) + .build(), + loading = { RectangleShimmer(radius = 4.dp) }, + error = { RectangleShimmer(radius = 4.dp) }, + contentDescription = null, + modifier = Modifier + .size(TangemTheme.dimens.size20) + .clip(RoundedCornerShape(TangemTheme.dimens.radius4)), + ) + Text( + text = state.name, + style = TangemTheme.typography.body2, + color = TangemTheme.colors.text.tertiary, + modifier = Modifier.padding(start = TangemTheme.dimens.spacing6), + ) + Icon( + painter = painterResource(id = R.drawable.ic_chevron_24), + tint = TangemTheme.colors.icon.secondary, + contentDescription = null, + modifier = Modifier.size(TangemTheme.dimens.size24), + ) + } + is ProviderState.Loading -> { + RectangleShimmer( + modifier = Modifier + .size(width = TangemTheme.dimens.size80, height = TangemTheme.dimens.size20), + radius = TangemTheme.dimens.radius4, + ) + } + is ProviderState.Unavailable -> { + Text( + text = state.alertText.resolveReference(), + style = TangemTheme.typography.body2, + color = TangemTheme.colors.text.warning, + ) + } + is ProviderState.Empty -> Unit + } +} + +// region Preview +@Preview(showBackground = true, widthDp = 360) +@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun ProviderItemBlockSimple_Preview(@PreviewParameter(SimpleProviderPreview::class) state: ProviderState) { + TangemThemePreview { + ProviderItemBlockSimple(state = state) + } +} + +private class SimpleProviderPreview : PreviewParameterProvider { + override val values: Sequence = sequenceOf( + ProviderState.Content( + id = "1", + name = "Changelly", + type = "CEX", + iconUrl = "", + subtitle = stringReference("1 SOL ≈ 0.0011337 BTC"), + selectionType = ProviderState.SelectionType.CLICK, + additionalBadge = ProviderState.AdditionalBadge.Empty, + percentLowerThenBest = PercentDifference.Empty, + namePrefix = ProviderState.PrefixType.NONE, + onProviderClick = {}, + ), + ProviderState.Loading(), + ProviderState.Unavailable( + id = "2", + name = "1inch", + type = "DEX", + iconUrl = "", + alertText = stringReference("Unavailable"), + selectionType = ProviderState.SelectionType.NONE, + ), + ) +} +// endregion \ No newline at end of file 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 fe6ade5424..c2684c811a 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 @@ -59,6 +59,7 @@ internal class StateBuilder( private val appCurrencyProvider: Provider, private val isAccountsModeProvider: Provider, private val isGaslessFeeSupportedForNetwork: IsGaslessFeeSupportedForNetwork, + private val shouldShowAbMenu: Boolean, ) { private val iconStateConverter by lazy(::CryptoCurrencyToIconStateConverter) @@ -97,6 +98,8 @@ internal class StateBuilder( priceImpact = PriceImpact.Empty, isInsufficientFunds = false, swapUIMode = swapUIMode, + onSwapUIModeChange = actions.onSwapUIModeChange, + shouldShowAbMenu = shouldShowAbMenu, ) } diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/SwapScreen.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/SwapScreen.kt index 593a6e4109..4cc5d10cd9 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/SwapScreen.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/SwapScreen.kt @@ -2,19 +2,39 @@ package com.tangem.feature.swap.ui import androidx.activity.compose.BackHandler import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.systemBarsPadding +import androidx.compose.foundation.layout.width +import androidx.compose.material3.Icon import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.testTag -import com.tangem.core.ui.components.appbar.AppBarWithBackButton +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.DpOffset +import androidx.compose.ui.unit.dp +import com.tangem.core.ui.components.appbar.AppBarWithBackButtonAndIcon +import com.tangem.core.ui.components.dropdownmenu.TangemDropdownMenu import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.test.SwapTokenScreenTestTags import com.tangem.core.ui.utils.WindowInsetsZero import com.tangem.feature.swap.component.SwapFeeSelectorBlockComponent +import com.tangem.feature.swap.domain.models.domain.SwapUIMode import com.tangem.feature.swap.models.SwapStateHolder import com.tangem.feature.swap.models.states.ChooseFeeBottomSheetConfig import com.tangem.feature.swap.models.states.ChooseProviderBottomSheetConfig @@ -26,13 +46,7 @@ internal fun SwapScreen(stateHolder: SwapStateHolder, feeSelectorBlockComponent: Scaffold( modifier = Modifier.systemBarsPadding(), - topBar = { - AppBarWithBackButton( - text = stringResourceSafe(R.string.common_swap), - onBackClick = stateHolder.onBackClicked, - iconRes = R.drawable.ic_close_24, - ) - }, + topBar = { SwapTopBar(stateHolder = stateHolder) }, contentWindowInsets = WindowInsetsZero, containerColor = TangemTheme.colors.background.secondary, ) { scaffoldPaddings -> @@ -64,4 +78,79 @@ internal fun SwapScreen(stateHolder: SwapStateHolder, feeSelectorBlockComponent: } } } +} + +@Composable +private fun SwapTopBar(stateHolder: SwapStateHolder) { + var shouldShowModeMenu by rememberSaveable { mutableStateOf(false) } + Box(modifier = Modifier.fillMaxWidth()) { + AppBarWithBackButtonAndIcon( + text = stringResourceSafe(R.string.common_swap), + backIconRes = R.drawable.ic_close_24, + iconRes = if (stateHolder.shouldShowAbMenu) R.drawable.ic_more_vertical_24 else null, + onIconClick = if (stateHolder.shouldShowAbMenu) { + { shouldShowModeMenu = true } + } else { + null + }, + onBackClick = stateHolder.onBackClicked, + ) + if (stateHolder.shouldShowAbMenu) { + Box(modifier = Modifier.align(Alignment.TopEnd)) { + TangemDropdownMenu( + expanded = shouldShowModeMenu, + modifier = Modifier.background(TangemTheme.colors.background.primary), + offset = DpOffset(x = TangemTheme.dimens.spacing20, y = 44.dp), + onDismissRequest = { shouldShowModeMenu = false }, + content = { + SwapUiModeMenuItem( + title = stringResourceSafe(R.string.swap_simple_mode), + isSelected = stateHolder.swapUIMode == SwapUIMode.Simple, + onClick = { + shouldShowModeMenu = false + stateHolder.onSwapUIModeChange(SwapUIMode.Simple) + }, + ) + SwapUiModeMenuItem( + title = stringResourceSafe(R.string.swap_detailed_mode), + isSelected = stateHolder.swapUIMode == SwapUIMode.Detailed, + onClick = { + shouldShowModeMenu = false + stateHolder.onSwapUIModeChange(SwapUIMode.Detailed) + }, + ) + }, + ) + } + } + } +} + +@Composable +private fun SwapUiModeMenuItem(title: String, isSelected: Boolean, onClick: () -> Unit) { + Row( + modifier = Modifier + .fillMaxWidth() + .clickable(onClick = onClick) + .padding(horizontal = 16.dp, vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(16.dp), + ) { + Text( + text = title, + style = TangemTheme.typography.button, + color = TangemTheme.colors.text.primary1, + modifier = Modifier.weight(1f), + ) + if (isSelected) { + Icon( + painter = painterResource(id = R.drawable.ic_check_24), + tint = TangemTheme.colors.icon.primary1, + contentDescription = null, + modifier = Modifier.size(16.dp), + ) + } else { + Spacer(modifier = Modifier.width(16.dp)) + } + } } \ No newline at end of file diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt index b8006f40bb..fa11d144da 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt @@ -39,6 +39,7 @@ import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.test.SwapTokenScreenTestTags +import com.tangem.feature.swap.domain.models.domain.SwapUIMode import com.tangem.feature.swap.domain.models.ui.FeeType import com.tangem.feature.swap.domain.models.ui.PriceImpact import com.tangem.feature.swap.models.* @@ -79,7 +80,11 @@ internal fun SwapScreenContent( ) { MainInfo(state) - ProviderItemBlock(state = state.providerState) + if (state.swapUIMode == SwapUIMode.Simple) { + ProviderItemBlockSimple(state = state.providerState) + } else { + ProviderItemBlock(state = state.providerState) + } if (feeBlock != null) { feeBlock(Modifier.fillMaxWidth()) @@ -139,14 +144,25 @@ private fun MainInfo(state: SwapStateHolder) { onSelectTokenClick = { state.onSelectTokenClick(TokenSelectionDirection.FROM) }, ) val marginCard = TangemTheme.dimens.spacing12 - TransactionCard( - priceImpact = priceImpact, - swapCardState = state.receiveCardData, - modifier = Modifier.constrainAs(bottomCard) { - top.linkTo(topCard.bottom, margin = marginCard) - }, - onSelectTokenClick = { state.onSelectTokenClick(TokenSelectionDirection.TO) }, - ) + if (state.swapUIMode == SwapUIMode.Simple) { + TransactionCardSimple( + priceImpact = priceImpact, + swapCardState = state.receiveCardData, + modifier = Modifier.constrainAs(bottomCard) { + top.linkTo(topCard.bottom, margin = marginCard) + }, + onSelectTokenClick = { state.onSelectTokenClick(TokenSelectionDirection.TO) }, + ) + } else { + TransactionCard( + priceImpact = priceImpact, + swapCardState = state.receiveCardData, + modifier = Modifier.constrainAs(bottomCard) { + top.linkTo(topCard.bottom, margin = marginCard) + }, + onSelectTokenClick = { state.onSelectTokenClick(TokenSelectionDirection.TO) }, + ) + } val marginButton = TangemTheme.dimens.spacing30 SwapButton( state, diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/TransactionCardSimple.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/TransactionCardSimple.kt new file mode 100644 index 0000000000..a807f078a3 --- /dev/null +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/TransactionCardSimple.kt @@ -0,0 +1,418 @@ +package com.tangem.feature.swap.ui + +import android.content.res.Configuration +import androidx.compose.animation.AnimatedContent +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.TextAutoSize +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Text +import androidx.compose.material3.ripple +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.tangem.common.ui.account.AccountTitle +import com.tangem.core.ui.R +import com.tangem.core.ui.components.RectangleShimmer +import com.tangem.core.ui.components.SpacerH4 +import com.tangem.core.ui.components.SpacerW16 +import com.tangem.core.ui.components.TextShimmer +import com.tangem.core.ui.components.buttons.SecondarySmallButton +import com.tangem.core.ui.components.buttons.SmallButtonConfig +import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition +import com.tangem.core.ui.extensions.orMaskWithStars +import com.tangem.core.ui.extensions.resolveAnnotatedReference +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.stringResourceSafe +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.core.ui.test.SwapTokenScreenTestTags +import com.tangem.feature.swap.domain.models.ui.PriceImpact +import com.tangem.feature.swap.models.SwapCardState +import com.tangem.feature.swap.models.TransactionCardType +import com.tangem.feature.swap.ui.preview.SwapTransactionCardPreview + +@Composable +internal fun TransactionCardSimple( + priceImpact: PriceImpact, + swapCardState: SwapCardState, + onSelectTokenClick: () -> Unit, + modifier: Modifier = Modifier, +) { + val cardTag = when (swapCardState.type) { + is TransactionCardType.Inputtable -> SwapTokenScreenTestTags.SWAP_CARD + is TransactionCardType.ReadOnly -> SwapTokenScreenTestTags.RECEIVE_CARD + } + + when (swapCardState) { + is SwapCardState.Empty -> SimpleTransactionCardEmpty( + cardState = swapCardState, + onChangeTokenClick = onSelectTokenClick, + modifier = modifier.testTag(cardTag), + ) + is SwapCardState.SwapCardData -> SimpleTransactionCardData( + cardState = swapCardState, + priceImpact = priceImpact, + onChangeTokenClick = onSelectTokenClick, + modifier = modifier.testTag(cardTag), + ) + is SwapCardState.Loading -> SimpleTransactionCardLoading(modifier = modifier.testTag(cardTag)) + } +} + +@Composable +private fun SimpleTransactionCardData( + cardState: SwapCardState.SwapCardData, + priceImpact: PriceImpact, + modifier: Modifier = Modifier, + onChangeTokenClick: (() -> Unit)? = null, +) { + Box( + modifier = modifier + .background( + shape = RoundedCornerShape(TangemTheme.dimens.radius16), + color = TangemTheme.colors.background.primary, + ) + .fillMaxWidth(), + ) { + Column( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.Top, + horizontalAlignment = Alignment.Start, + ) { + SimpleHeader( + balance = stringResourceSafe( + R.string.common_balance, + cardState.balance, + ).orMaskWithStars(cardState.isBalanceHidden), + type = cardState.type, + ) + + SimpleContent( + type = cardState.type, + textFieldValue = cardState.amountTextFieldValue, + priceImpact = priceImpact, + ) + } + + Box(modifier = Modifier.align(Alignment.BottomEnd)) { + Token( + currencyIconState = cardState.currencyIconState, + tokenSymbol = cardState.tokenSymbol, + ) + } + + if (onChangeTokenClick != null) { + Box(modifier = Modifier.align(Alignment.CenterEnd)) { + ChangeTokenSelector() + } + Box( + Modifier + .align(Alignment.CenterEnd) + .height(TangemTheme.dimens.size116) + .width(TangemTheme.dimens.size102) + .clickable( + indication = ripple(bounded = false), + interactionSource = remember { MutableInteractionSource() }, + ) { onChangeTokenClick() }, + ) + } + } +} + +@Composable +private fun SimpleTransactionCardEmpty( + cardState: SwapCardState.Empty, + modifier: Modifier = Modifier, + onChangeTokenClick: () -> Unit, +) { + Column( + modifier = modifier + .background( + shape = RoundedCornerShape(TangemTheme.dimens.radius12), + color = TangemTheme.colors.background.primary, + ) + .padding( + top = 12.dp, + start = 12.dp, + end = 12.dp, + bottom = 16.dp, + ) + .fillMaxWidth(), + horizontalAlignment = Alignment.Start, + verticalArrangement = Arrangement.spacedBy(6.dp), + ) { + AccountTitle( + accountTitleUM = cardState.type.accountTitleUM, + modifier = Modifier.fillMaxWidth(), + ) + Row( + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Column( + modifier = Modifier.weight(1f), + verticalArrangement = Arrangement.spacedBy(4.dp), + ) { + Text( + text = cardState.amountTextFieldValue?.text.orEmpty(), + color = TangemTheme.colors.text.disabled, + style = TangemTheme.typography.h2, + autoSize = TextAutoSize.StepBased( + minFontSize = 16.sp, + maxFontSize = TangemTheme.typography.h2.fontSize, + ), + maxLines = 1, + modifier = Modifier.testTag(SwapTokenScreenTestTags.SWAP_TEXT_FIELD), + ) + Text( + text = cardState.amountEquivalent.resolveAnnotatedReference(), + color = TangemTheme.colors.text.tertiary, + style = TangemTheme.typography.body2, + modifier = Modifier + .defaultMinSize(minHeight = TangemTheme.dimens.size20) + .testTag(SwapTokenScreenTestTags.SWAP_FIAT_AMOUNT), + ) + } + SecondarySmallButton( + config = SmallButtonConfig( + text = resourceReference(R.string.common_choose_token), + icon = TangemButtonIconPosition.End(R.drawable.ic_chevron_24), + onClick = onChangeTokenClick, + ), + ) + } + } +} + +@Composable +private fun SimpleTransactionCardLoading(modifier: Modifier = Modifier) { + Column( + modifier = modifier + .background( + shape = RoundedCornerShape(TangemTheme.dimens.radius12), + color = TangemTheme.colors.background.primary, + ) + .padding( + top = 12.dp, + start = 12.dp, + end = 12.dp, + bottom = 16.dp, + ) + .fillMaxWidth(), + horizontalAlignment = Alignment.Start, + verticalArrangement = Arrangement.spacedBy(6.dp), + ) { + Column(modifier = Modifier.fillMaxWidth()) { + TextShimmer( + text = stringResourceSafe(R.string.swapping_to_title), + style = TangemTheme.typography.subtitle2, + ) + TextShimmer( + style = TangemTheme.typography.body2, + modifier = Modifier + .testTag(SwapTokenScreenTestTags.BALANCE) + .width(60.dp), + ) + } + Row( + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Column( + modifier = Modifier.weight(1f), + verticalArrangement = Arrangement.spacedBy(4.dp), + ) { + TextShimmer( + style = TangemTheme.typography.h2, + modifier = Modifier + .width(100.dp) + .testTag(SwapTokenScreenTestTags.SWAP_TEXT_FIELD), + ) + TextShimmer( + style = TangemTheme.typography.body2, + modifier = Modifier + .defaultMinSize(minHeight = 20.dp, minWidth = 40.dp) + .testTag(SwapTokenScreenTestTags.SWAP_FIAT_AMOUNT), + ) + } + SecondarySmallButton( + config = SmallButtonConfig( + text = resourceReference(R.string.common_choose_token), + icon = TangemButtonIconPosition.End(R.drawable.ic_chevron_24), + isEnabled = false, + onClick = {}, + ), + ) + } + } +} + +@Composable +private fun SimpleHeader(type: TransactionCardType, balance: String, modifier: Modifier = Modifier) { + Column( + modifier = modifier + .fillMaxWidth() + .padding( + bottom = TangemTheme.dimens.spacing8, + top = TangemTheme.dimens.spacing14, + start = TangemTheme.dimens.spacing12, + end = TangemTheme.dimens.spacing12, + ) + .testTag(SwapTokenScreenTestTags.SWAP_BLOCK_HEADER), + ) { + val titleColor = if (type.inputError is TransactionCardType.InputError.Empty) { + TangemTheme.colors.text.tertiary + } else { + TangemTheme.colors.text.warning + } + AccountTitle( + accountTitleUM = type.accountTitleUM, + textColor = titleColor, + ) + SpacerW16() + if (balance.isNotBlank()) { + AnimatedContent(targetState = balance, label = "") { balanceText -> + Text( + text = balanceText, + color = TangemTheme.colors.text.tertiary, + style = TangemTheme.typography.body2, + modifier = Modifier.testTag(SwapTokenScreenTestTags.BALANCE), + ) + } + } else { + RectangleShimmer( + modifier = Modifier + .width(TangemTheme.dimens.size80) + .height(TangemTheme.dimens.size12), + radius = TangemTheme.dimens.radius3, + ) + } + } +} + +@Suppress("LongMethod") +@Composable +private fun SimpleContent(type: TransactionCardType, priceImpact: PriceImpact, textFieldValue: TextFieldValue?) { + Row( + modifier = Modifier + .padding( + start = TangemTheme.dimens.spacing12, + bottom = TangemTheme.dimens.spacing16, + ), + horizontalArrangement = Arrangement.Start, + verticalAlignment = Alignment.Top, + ) { + Column( + modifier = Modifier.padding(end = TangemTheme.dimens.spacing92), + verticalArrangement = Arrangement.Top, + horizontalAlignment = Alignment.Start, + ) { + val sumTextModifier = Modifier.defaultMinSize(minHeight = TangemTheme.dimens.size32) + when (type) { + is TransactionCardType.ReadOnly -> { + if (textFieldValue != null) { + Text( + text = textFieldValue.text, + color = TangemTheme.colors.text.primary1, + style = TangemTheme.typography.h2, + autoSize = TextAutoSize.StepBased( + minFontSize = 16.sp, + maxFontSize = TangemTheme.typography.h2.fontSize, + ), + maxLines = 1, + modifier = sumTextModifier.testTag(SwapTokenScreenTestTags.RECEIVE_TEXT_FIELD), + ) + } else { + RectangleShimmer( + modifier = Modifier + .padding(vertical = TangemTheme.dimens.spacing4) + .width(TangemTheme.dimens.size102) + .height(TangemTheme.dimens.size24), + ) + } + } + is TransactionCardType.Inputtable -> { + val focusRequester = remember { FocusRequester() } + AutoSizeTextField( + modifier = sumTextModifier.testTag(SwapTokenScreenTestTags.SWAP_TEXT_FIELD), + focusRequester = focusRequester, + textFieldValue = textFieldValue ?: TextFieldValue(), + isEnabled = type.isEnabled, + onAmountChange = { type.onAmountChanged(it) }, + onFocusChange = type.onFocusChanged, + ) + LaunchedEffect(Unit) { focusRequester.requestFocus() } + } + } + SpacerH4() + // Keep the same 20dp slot as Detailed (where fiat/shimmer lives) + // so that Token (BottomEnd) does not shift when switching modes. + Box(modifier = Modifier.defaultMinSize(minHeight = TangemTheme.dimens.size20)) { + if (type is TransactionCardType.ReadOnly && type.shouldShowWarning) { + WarningIcon(priceImpact = priceImpact, onClick = type.onWarningClick) + } + } + } + } +} + +@Composable +private fun WarningIcon(priceImpact: PriceImpact, onClick: (() -> Unit)?) { + IconButton( + onClick = { onClick?.invoke() }, + modifier = Modifier.size(TangemTheme.dimens.size20), + ) { + Icon( + painter = painterResource(id = R.drawable.ic_information_24), + contentDescription = null, + tint = when (priceImpact.type) { + PriceImpact.Type.HIGH -> TangemTheme.colors.text.warning + PriceImpact.Type.MEDIUM -> TangemTheme.colors.text.attention + else -> TangemTheme.colors.text.tertiary + }, + modifier = Modifier.testTag(SwapTokenScreenTestTags.RECEIVE_FIAT_AMOUNT_INFORMATION_ICON), + ) + } +} + +// region Preview +@Preview(showBackground = true, widthDp = 360) +@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun TransactionCardSimple_Preview(@PreviewParameter(SimplePreviewProvider::class) params: SwapCardState) { + TangemThemePreview { + TransactionCardSimple( + priceImpact = PriceImpact.Empty, + swapCardState = params, + onSelectTokenClick = {}, + modifier = Modifier, + ) + } +} + +private class SimplePreviewProvider : PreviewParameterProvider { + override val values: Sequence = sequenceOf( + SwapTransactionCardPreview.sendCard, + SwapTransactionCardPreview.receiveCard, + SwapTransactionCardPreview.emptyReadOnlyCard, + SwapTransactionCardPreview.emptyInputtableCard, + SwapTransactionCardPreview.loadingCard, + ) +} +// endregion \ No newline at end of file diff --git a/features/swap/impl/src/test/java/com/tangem/feature/swap/StateBuilderInitialStateTest.kt b/features/swap/impl/src/test/java/com/tangem/feature/swap/StateBuilderInitialStateTest.kt index 74d5f2bf6b..cd31b0db91 100644 --- a/features/swap/impl/src/test/java/com/tangem/feature/swap/StateBuilderInitialStateTest.kt +++ b/features/swap/impl/src/test/java/com/tangem/feature/swap/StateBuilderInitialStateTest.kt @@ -48,6 +48,7 @@ internal class StateBuilderInitialStateTest { appCurrencyProvider = appCurrencyProvider, isAccountsModeProvider = isAccountsModeProvider, isGaslessFeeSupportedForNetwork = isGaslessFeeSupportedForNetwork, + shouldShowAbMenu = false, ) } diff --git a/features/swap/impl/src/test/java/com/tangem/feature/swap/StateBuilderPairsTest.kt b/features/swap/impl/src/test/java/com/tangem/feature/swap/StateBuilderPairsTest.kt index ef0ac3d90e..3045de86cd 100644 --- a/features/swap/impl/src/test/java/com/tangem/feature/swap/StateBuilderPairsTest.kt +++ b/features/swap/impl/src/test/java/com/tangem/feature/swap/StateBuilderPairsTest.kt @@ -53,6 +53,7 @@ internal class StateBuilderPairsTest { appCurrencyProvider = appCurrencyProvider, isAccountsModeProvider = isAccountsModeProvider, isGaslessFeeSupportedForNetwork = isGaslessFeeSupportedForNetwork, + shouldShowAbMenu = false, ) } diff --git a/features/swap/impl/src/test/java/com/tangem/feature/swap/StateBuilderQuotesTest.kt b/features/swap/impl/src/test/java/com/tangem/feature/swap/StateBuilderQuotesTest.kt index 0302eebdfc..e03feca1b0 100644 --- a/features/swap/impl/src/test/java/com/tangem/feature/swap/StateBuilderQuotesTest.kt +++ b/features/swap/impl/src/test/java/com/tangem/feature/swap/StateBuilderQuotesTest.kt @@ -57,6 +57,7 @@ internal class StateBuilderQuotesTest { appCurrencyProvider = appCurrencyProvider, isAccountsModeProvider = isAccountsModeProvider, isGaslessFeeSupportedForNetwork = isGaslessFeeSupportedForNetwork, + shouldShowAbMenu = false, ) } diff --git a/features/swap/impl/src/test/java/com/tangem/feature/swap/StateBuilderSwapDataTest.kt b/features/swap/impl/src/test/java/com/tangem/feature/swap/StateBuilderSwapDataTest.kt index 2d15fe9295..7d74bddb02 100644 --- a/features/swap/impl/src/test/java/com/tangem/feature/swap/StateBuilderSwapDataTest.kt +++ b/features/swap/impl/src/test/java/com/tangem/feature/swap/StateBuilderSwapDataTest.kt @@ -59,6 +59,7 @@ internal class StateBuilderSwapDataTest { appCurrencyProvider = appCurrencyProvider, isAccountsModeProvider = isAccountsModeProvider, isGaslessFeeSupportedForNetwork = isGaslessFeeSupportedForNetwork, + shouldShowAbMenu = false, ) }