From b9b41e9d267862b8e6c7803670e4ebbfa18b1d73 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 13 Oct 2025 11:33:35 +0300 Subject: [PATCH] Updated on 2026-08-14 --- core/res/src/main/res/values-de/strings.xml | 2 +- core/res/src/main/res/values-es/strings.xml | 2 + core/res/src/main/res/values-fr/strings.xml | 2 + core/res/src/main/res/values-ja/strings.xml | 30 +- core/res/src/main/res/values-ru/strings.xml | 4 +- core/res/src/main/res/values/strings.xml | 24 +- ...faultMultiAccountStatusListProducerTest.kt | 198 ----------- ...aultSingleAccountStatusListProducerTest.kt | 244 -------------- .../CryptoCurrencyStatusesFlowFactoryTest.kt | 308 ------------------ .../presentation/model/StakingClickIntents.kt | 4 + .../impl/presentation/model/StakingModel.kt | 132 +++++++- .../presentation/state/StakingNotification.kt | 9 + .../TonInitializeAccountBottomSheetConfig.kt | 15 + .../state/events/StakingAlertUM.kt | 7 - .../state/events/StakingEventFactory.kt | 5 - .../state/helpers/StakingBalanceUpdater.kt | 5 + .../state/stub/StakingClickIntentsStub.kt | 4 + .../AddStakingNotificationsTransformer.kt | 78 +++-- ...ompleteInitializeBottomSheetTransformer.kt | 53 +++ ...orToTonInitializeBottomSheetTransformer.kt | 25 ++ ...eeToTonInitializeBottomSheetTransformer.kt | 41 +++ ...ShowTonInitializeBottomSheetTransformer.kt | 32 ++ .../impl/presentation/ui/StakingScreen.kt | 3 + .../TonInitializeAccountBottomSheet.kt | 158 +++++++++ 24 files changed, 570 insertions(+), 815 deletions(-) delete mode 100644 domain/account/status/src/test/java/com/tangem/domain/account/status/producer/DefaultMultiAccountStatusListProducerTest.kt delete mode 100644 domain/account/status/src/test/java/com/tangem/domain/account/status/producer/DefaultSingleAccountStatusListProducerTest.kt delete mode 100644 domain/account/status/src/test/java/com/tangem/domain/account/status/utils/CryptoCurrencyStatusesFlowFactoryTest.kt create mode 100644 features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/bottomsheet/TonInitializeAccountBottomSheetConfig.kt create mode 100644 features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/ton/CompleteInitializeBottomSheetTransformer.kt create mode 100644 features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/ton/SetFeeErrorToTonInitializeBottomSheetTransformer.kt create mode 100644 features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/ton/SetFeeToTonInitializeBottomSheetTransformer.kt create mode 100644 features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/ton/ShowTonInitializeBottomSheetTransformer.kt create mode 100644 features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/bottomsheet/TonInitializeAccountBottomSheet.kt diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index 522132b11b..56f11d7388 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -1362,7 +1362,7 @@ Frühzeitiger Zugriff auf neue Funktionen und exklusive Angebote. Updates zu Funktionen und Neuigkeiten Möchtest du Push-Benachrichtigungen verwenden? - Aktiviere Push-Benachrichtigungen und wir benachrichtigen Dich sofort, wenn Gelder eintreffen \n + Aktiviere Push-Benachrichtigungen und wir benachrichtigen Dich sofort, wenn Gelder eintreffen Verpasse keine Transaktion Neues Wallet hinzufügen Möchtest Du diese Wallet wirklich entfernen? diff --git a/core/res/src/main/res/values-es/strings.xml b/core/res/src/main/res/values-es/strings.xml index dcce73dd3a..6a2f5f1caf 100644 --- a/core/res/src/main/res/values-es/strings.xml +++ b/core/res/src/main/res/values-es/strings.xml @@ -1212,6 +1212,8 @@ Acceso anticipado a nuevas funciones y ofertas exclusivas. Actualizaciones de características y noticias ¿Quiere utilizar\nnotificaciones push? + Active las notificaciones push y le avisaremos al instante cuando le lleguen fondos. + No se pierda ninguna transacción Agregar una nueva billetera ¿Estás seguro de que deseas olvidar esta billetera? Ha ocurrido un error, por favor escanee su tarjeta o anillo para iniciar sesión diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml index 004c7dd622..868ad2f695 100644 --- a/core/res/src/main/res/values-fr/strings.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -1185,6 +1185,8 @@ Accès anticipé à de nouvelles fonctionnalités et à des offres exclusives. Actualités et mises à jour Souhaitez-vous utiliser les\nnotifications push? + Activez les notifications pour recevoir des alertes lorsque des fonds arrivent dans votre portefeuille. + Ne manquez aucune transaction Ajouter un nouveau portefeuille Êtes-vous sûr de vouloir supprimer ce portefeuille ? Une erreur s\'est produite, veuillez scanner votre carte ou bague pour vous connecter diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml index 4a69572db8..282551cd56 100644 --- a/core/res/src/main/res/values-ja/strings.xml +++ b/core/res/src/main/res/values-ja/strings.xml @@ -134,7 +134,7 @@ お使いの携帯電話で生体認証が無効になっているため、アプリにウォレットを保存できません。ウォレットを保存するには、携帯電話の設定で生体認証機能を有効にしてください。 プロモーションコードの処理中にエラーが発生しました。しばらくしてからもう一度お試しください。 アクティベーションエラー - プロモコードが正常に有効化されました。リワードは14日以内にあなたのアカウントに付与されます。 + プロモーションコードが正常に有効化されました。報酬は14日以内にビットコインアカウントに入金されます。 プロモーションコードが有効になりました このプロモーションコードは既に使用されているため、再度使うことはできません。 コードが利用できません @@ -180,6 +180,7 @@ 追加 ポートフォリオに追加 トークンを追加 + 追加済み アドレス すべて 許可する @@ -291,6 +292,7 @@ %1$s — %2$s 続きを読む 受け取る + おすすめ 拒否 リロード 名前を変更 @@ -347,6 +349,8 @@ はい コントラクトアドレスをコピーしました! 利用可能なネットワーク + このトークンの導出パスが%1$sの導出パスと一致しています。トークンはこのアカウントに追加されます。 + 導出パスは別のアカウントに属します。 コントラクトアドレス コントラクトアドレスが無効です ネットワークを選択してください @@ -464,6 +468,7 @@ プロバイダー ベストレート FCA警告リスト + お得なレート FCA警告リストに掲載されたプロバイダー 最大 %s まで使用可能 %s 以上で利用可能 @@ -1361,9 +1366,10 @@ 新しい機能や限定オファーへの早期アクセス。 機能およびニュースのアップデート プッシュ通知を使用しますか? - プッシュ通知を有効にすると、資金が到着するとすぐに通知されます + プッシュ通知を有効にすると、ウォレットに着金したときにアラートを受信できます。 取引を見逃さない 新しいウォレットを追加 + バックアップせずにこのウォレットを削除すると、資金に永久にアクセスできなくなります。 このウォレットを忘れてもよろしいですか? エラーが発生しました。カードまたはリングをスキャンしてログインしてください。 このウォレットはすでに保存されています。別のウォレットを追加できます。 @@ -1393,6 +1399,13 @@ ロック解除 カードをスキャンしてアクセスロックを解除する ロック解除が必要 + ウォレットの追加方法を選択してください + Tangemカードまたはリングをスキャンして復元するか、別のウォレットからインポートしてください。 + ハードウェアウォレットを作成 + Tangemウォレットを購入しますか? + シードフレーズをインポート + スマートフォン上でウォレットを復元するか、他のアプリからインポートできますが、Tangemカードと比べるとセキュリティは劣ります。 + お選びください ブロックチェーンにアクセスできません。後でもう一度お試しください。 カードまたはリングをスキャン このウォレットはすでに有効化されています。\nあなたが行ったのでない場合は、サポートに連絡してください。\nTangemは、事前に生成されたアクセスコードと一緒にウォレットを販売することはありません。 @@ -1432,6 +1445,7 @@ dAppsに接続する WalletConnect 接続には数秒かかる場合があります + Tangemウォレットを作成する %sから Tangemウォレットを購入ーこれは、あなたの秘密鍵をオフラインで安全に保管する物理デバイスです。 ハードウェアウォレット @@ -1657,13 +1671,20 @@ URIはすでに使用されています WalletConnect 不審な取引 + すでにTangemをお持ちですか? + 数千種類の資産 業界最高水準のハードウェアウォレット 迅速な配送 + ワンタップで開始 + シームレスで安全 シンプルな操作 Tangemでハードウェアウォレットを作成しましょう。銀行のカードのようにスリムで、金庫のように安全です。 ソフトウェアウォレットを作成またはインポート + スマートフォン上にソフトウェアウォレットを作成するか、インポートしてください。 モバイルウォレットから始める その他の方法 + Tangemハードウェアウォレットを使用する + 詳細を見る・購入する 破棄 バックアップが中断されました。再開しますか? はい、再開します @@ -1688,6 +1709,7 @@ 承認を確定する 資産残高に関するテキスト [プレースホルダー] あなたの%sはAaveに預けられています + チャートを読み込めません・・ 受け取った金額%1$s %2$sはAaveに入金されませんでした。 %1$s%% を獲得 利用可能 @@ -1705,6 +1727,7 @@ 最大手数料 手数料ポリシー ネットワーク手数料が現在高すぎます。設定した上限を下回るまで待機しています。 + 過去のリターン ここに説明を入力してください。1〜3行が理想的です。[プレースホルダー] トークン承認が必要 ネットワーク接続を確認してください @@ -1738,7 +1761,8 @@ 供給APR APY あなたの資産を眠らせない — 残高を運用して利息を得ましょう。 - 保有資産から報酬を得る + 利息は自動的に発生します + Aaveレンディング 入金の処理中 年間%1$s%%の収益 自動 diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 6d0accb1ee..c55cacce85 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -80,7 +80,7 @@ Вы отключили биометрическую аутентификацию на вашем телефоне и не сможете сохранять кошельки в приложении. Для сохранения кошельков, пожалуйста, включите функцию биометрической аутентификации в настройках телефона. При обработке промокода произошла ошибка. Пожалуйста, попробуйте позже. Ошибка активации - Ваш промокод был успешно активирован. Награда будет зачислена на ваш счёт в течение 14 дней. + Ваш промокод был успешно активирован. Награда будет зачислена на ваш Bitcoin адрес в течение 14 дней. Промокод активирован Этот промокод уже был использован и не может быть активирован повторно. Код недоступен @@ -1288,6 +1288,8 @@ Ранний доступ к новым функциям и эксклюзивным предложениям. Новые функции и важные новости Хотите использовать Push-уведомления? + Включите push-уведомления, и мы мгновенно сообщим вам, когда поступят средства. + Не пропустите транзакцию Добавить новый кошелек Вы уверены, что хотите забыть этот кошелек? Произошла ошибка, пожалуйста, отсканируйте свою карту или кольцо для входа diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 50216b2d4c..3e4c4ec8a8 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -135,7 +135,7 @@ You have disabled biometric authentication on your phone and will not be able to save wallets in the app. To save wallets, please enable the biometric authentication function in your phone settings. An error occurred while processing your promo code. Please try again later. Activation error - Your promo code was successfully activated. A reward will be credited to your account within 14 days. + Your promo code was successfully activated. The reward will be credited to your Bitcoin account within 14 days. Promo Code Activated This promo code has already been used and cannot be activated again. Code unavailable @@ -183,6 +183,7 @@ Add Add to portfolio Add token + Added Address All Allow @@ -297,6 +298,7 @@ %1$s — %2$s Read more Receive + Recommended Reject Reload Rename @@ -474,6 +476,7 @@ Provider Best rate FCA Warning List + Great rate Provider in FCA warning list Available up to %s Available from %s @@ -1386,9 +1389,10 @@ Early access to fresh features and exclusive offers. Feature and News Updates Would you like to use\nPush-notifications? - Enable push notifications and we’ll notify you instantly when funds arrive\n - Don’t Miss a Transaction + Enable push notifications to receive alerts when funds arrive in your wallet. + Don\'t Miss a Transaction Add new wallet + If you delete this wallet without a backup, you will permanently lose access to your funds Are you sure you want to forget this wallet? An error has occurred, please scan your card or ring to log in This wallet has already been saved, you can add another one @@ -1465,6 +1469,13 @@ Unlock Scan your card to unlock access Needed unlock + Choose how to add your wallet + Scan your Tangem card or ring to restore it or import from another wallet. + Create Hardware Wallet + Want to purchase a Tangem Wallet? + Import seed phrase + Restore your wallet on your phone or import from another app — convenient, but less secure than a Tangem card. + What to choose? Blockchain is unreachable. Try later Scan card or ring This wallet has already been activated earlier.\nIf it was not done by you, please contact support.\nTangem never sells wallets along with pre-generated access codes. @@ -1504,6 +1515,7 @@ Connect to dApps WalletConnect Connecting may take a few seconds + Create Tangem Wallet From %s Buy Tangem Wallet—a physical device that securely stores your private key offline. Hardware Wallet @@ -1785,7 +1797,10 @@ All future %s top-ups will be supplied to Aave automatically, with the transaction fee deducted. If network fees rise above maximum fee, the transaction won’t go through until they decrease. You can change this limit later. Maximum fee + The transaction fee must stay below 4% of your deposit. Tangem will transfer funds to Aave only once your balance is large enough to meet this condition. + Minimal amount Fee policy + Tangem also takes a 3% service fee on the yield earned. Network fee is too high right now. Waiting until it falls below your limit. Historical returns Write description here. In one, two or three lines will be awesome. [PLACEHOLDER] @@ -1821,7 +1836,8 @@ Supply APR APY Make your money work — earn interest on your balance. - Earning on your balance + Interest accrues automatically + Aave lending Processing your deposit Earn %1$s%% per year Automatic diff --git a/domain/account/status/src/test/java/com/tangem/domain/account/status/producer/DefaultMultiAccountStatusListProducerTest.kt b/domain/account/status/src/test/java/com/tangem/domain/account/status/producer/DefaultMultiAccountStatusListProducerTest.kt deleted file mode 100644 index a3becc8abf..0000000000 --- a/domain/account/status/src/test/java/com/tangem/domain/account/status/producer/DefaultMultiAccountStatusListProducerTest.kt +++ /dev/null @@ -1,198 +0,0 @@ -package com.tangem.domain.account.status.producer - -import com.google.common.truth.Truth -import com.tangem.common.test.utils.getEmittedValues -import com.tangem.domain.account.models.AccountStatusList -import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier -import com.tangem.domain.common.wallets.UserWalletsListRepository -import com.tangem.domain.models.wallet.UserWallet -import com.tangem.domain.models.wallet.UserWalletId -import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider -import io.mockk.* -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.AfterEach -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.TestInstance - -@Suppress("UnusedFlow") -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -class DefaultMultiAccountStatusListProducerTest { - - private val userWalletsListRepository: UserWalletsListRepository = mockk() - private val singleAccountStatusListSupplier: SingleAccountStatusListSupplier = mockk() - private val dispatchers = TestingCoroutineDispatcherProvider() - - private val userWalletId1 = UserWalletId("001") - private val userWallet1 = mockk { - every { walletId } returns userWalletId1 - } - - private val userWalletId2 = UserWalletId("002") - private val userWallet2 = mockk { - every { walletId } returns userWalletId2 - } - - private val producer = DefaultMultiAccountStatusListProducer( - params = Unit, - userWalletsListRepository = userWalletsListRepository, - singleAccountStatusListSupplier = singleAccountStatusListSupplier, - dispatchers = dispatchers, - ) - - @AfterEach - fun tearDown() { - clearMocks(userWalletsListRepository, singleAccountStatusListSupplier) - } - - @Test - fun `produce returns status lists for all user wallets`() = runTest { - // Arrange - val wallets = listOf(userWallet1, userWallet2) - val walletsFlow = MutableStateFlow(wallets) - - every { userWalletsListRepository.userWallets } returns walletsFlow - val accountStatusList1 = mockk() - val accountStatusList2 = mockk() - - every { - singleAccountStatusListSupplier( - params = SingleAccountStatusListProducer.Params(userWalletId1), - ) - } returns flowOf(accountStatusList1) - - every { - singleAccountStatusListSupplier( - params = SingleAccountStatusListProducer.Params(userWalletId2), - ) - } returns flowOf(accountStatusList2) - - // Act - val actual = producer.produce().let(::getEmittedValues) - - // Assert - val expected = listOf(accountStatusList1, accountStatusList2) - Truth.assertThat(actual).containsExactly(expected) - - coVerify(ordering = Ordering.SEQUENCE) { - userWalletsListRepository.userWallets - singleAccountStatusListSupplier( - params = SingleAccountStatusListProducer.Params(userWalletId1), - ) - singleAccountStatusListSupplier( - params = SingleAccountStatusListProducer.Params(userWalletId2), - ) - } - } - - @Test - fun `produce returns empty flow if userWallets is empty list`() = runTest { - // Arrange - val walletsFlow = MutableStateFlow>(emptyList()) - every { userWalletsListRepository.userWallets } returns walletsFlow - - // Act - val actual = producer.produce().let(::getEmittedValues) - - // Assert - Truth.assertThat(actual).isEmpty() - - coVerify(ordering = Ordering.SEQUENCE) { - userWalletsListRepository.userWallets - } - } - - @Test - fun `produce returns empty flow if userWallets is null`() = runTest { - // Arrange - val walletsFlow = MutableStateFlow?>(null) - every { userWalletsListRepository.userWallets } returns walletsFlow - - // Act - val actual = producer.produce().let(::getEmittedValues) - - // Assert - Truth.assertThat(actual).isEmpty() - - coVerify(ordering = Ordering.SEQUENCE) { - userWalletsListRepository.userWallets - } - } - - @Test - fun `flow will updated if userWallets are updated`() = runTest { - // Arrange - val userWalletId3 = UserWalletId("003") - val userWallet3 = mockk { every { walletId } returns userWalletId3 } - - val walletsFlow = MutableStateFlow(listOf(userWallet1, userWallet2)) - every { userWalletsListRepository.userWallets } returns walletsFlow - - val accountStatusList1 = mockk() - val accountStatusList2 = mockk() - val accountStatusList3 = mockk() - - every { - singleAccountStatusListSupplier( - params = SingleAccountStatusListProducer.Params(userWalletId1), - ) - } returns flowOf(accountStatusList1) - - every { - singleAccountStatusListSupplier( - params = SingleAccountStatusListProducer.Params(userWalletId2), - ) - } returns flowOf(accountStatusList2) - - every { - singleAccountStatusListSupplier( - params = SingleAccountStatusListProducer.Params(userWalletId3), - ) - } returns flowOf(accountStatusList3) - - // Act (first emission) - val actual1 = producer.produce().let(::getEmittedValues) - - // Assert (first emission) - val expected1 = listOf(accountStatusList1, accountStatusList2) - Truth.assertThat(actual1).containsExactly(expected1) - - // Act (second emission) - walletsFlow.value = listOf(userWallet1, userWallet2, userWallet3) - val actual2 = producer.produce().let(::getEmittedValues) - - // Assert (second emission) - val expected2 = listOf(accountStatusList1, accountStatusList2, accountStatusList3) - Truth.assertThat(actual2).containsExactly(expected2) - - coVerify(ordering = Ordering.SEQUENCE) { - userWalletsListRepository.userWallets - singleAccountStatusListSupplier( - params = SingleAccountStatusListProducer.Params(userWalletId1), - ) - singleAccountStatusListSupplier( - params = SingleAccountStatusListProducer.Params(userWalletId2), - ) - singleAccountStatusListSupplier( - params = SingleAccountStatusListProducer.Params(userWalletId1), - ) - singleAccountStatusListSupplier( - params = SingleAccountStatusListProducer.Params(userWalletId2), - ) - singleAccountStatusListSupplier( - params = SingleAccountStatusListProducer.Params(userWalletId3), - ) - userWalletsListRepository.userWallets - singleAccountStatusListSupplier( - params = SingleAccountStatusListProducer.Params(userWalletId1), - ) - singleAccountStatusListSupplier( - params = SingleAccountStatusListProducer.Params(userWalletId2), - ) - singleAccountStatusListSupplier( - params = SingleAccountStatusListProducer.Params(userWalletId3), - ) - } - } -} \ No newline at end of file diff --git a/domain/account/status/src/test/java/com/tangem/domain/account/status/producer/DefaultSingleAccountStatusListProducerTest.kt b/domain/account/status/src/test/java/com/tangem/domain/account/status/producer/DefaultSingleAccountStatusListProducerTest.kt deleted file mode 100644 index ca151e1478..0000000000 --- a/domain/account/status/src/test/java/com/tangem/domain/account/status/producer/DefaultSingleAccountStatusListProducerTest.kt +++ /dev/null @@ -1,244 +0,0 @@ -package com.tangem.domain.account.status.producer - -import arrow.core.nonEmptyListOf -import com.google.common.truth.Truth -import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory -import com.tangem.common.test.utils.getEmittedValues -import com.tangem.domain.account.models.AccountList -import com.tangem.domain.account.models.AccountStatusList -import com.tangem.domain.account.producer.SingleAccountListProducer -import com.tangem.domain.account.status.utils.CryptoCurrencyStatusesFlowFactory -import com.tangem.domain.account.supplier.SingleAccountListSupplier -import com.tangem.domain.core.utils.lceContent -import com.tangem.domain.core.utils.lceLoading -import com.tangem.domain.models.StatusSource -import com.tangem.domain.models.TokensSortType -import com.tangem.domain.models.TotalFiatBalance -import com.tangem.domain.models.account.AccountStatus -import com.tangem.domain.models.currency.CryptoCurrencyStatus -import com.tangem.domain.models.quote.PriceChange -import com.tangem.domain.models.tokenlist.TokenList -import com.tangem.domain.models.wallet.UserWallet -import com.tangem.domain.models.wallet.UserWalletId -import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider -import io.mockk.* -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.AfterEach -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.TestInstance -import java.math.BigDecimal - -/** -[REDACTED_AUTHOR] - */ -@Suppress("UnusedFlow") -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -class DefaultSingleAccountStatusListProducerTest { - - private val singleAccountListSupplier: SingleAccountListSupplier = mockk() - private val cryptoCurrencyStatusesFlowFactory: CryptoCurrencyStatusesFlowFactory = mockk() - - private val userWalletId = UserWalletId("011") - private val userWallet = mockk { - every { this@mockk.walletId } returns userWalletId - } - - private val producer = DefaultSingleAccountStatusListProducer( - params = SingleAccountStatusListProducer.Params(userWalletId), - singleAccountListSupplier = singleAccountListSupplier, - cryptoCurrencyStatusesFlowFactory = cryptoCurrencyStatusesFlowFactory, - dispatchers = TestingCoroutineDispatcherProvider(), - ) - - @AfterEach - fun tearDown() { - clearMocks(singleAccountListSupplier, cryptoCurrencyStatusesFlowFactory) - } - - @Test - fun `flow is mapped for user wallet id from params`() = runTest { - // Arrange - val accountList = AccountList.empty(userWallet) - - every { - singleAccountListSupplier(params = SingleAccountListProducer.Params(userWalletId)) - } returns flowOf(accountList) - - // Act - val actual = producer.produce().let(::getEmittedValues) - - // Assert - val expected = AccountStatusList( - userWallet = userWallet, - accountStatuses = setOf( - AccountStatus.CryptoPortfolio( - account = accountList.mainAccount, - tokenList = TokenList.Empty, - priceChangeLce = PriceChange(value = BigDecimal("0.00"), source = StatusSource.ACTUAL).lceContent(), - ), - ), - totalAccounts = 1, - totalFiatBalance = TotalFiatBalance.Loaded(amount = BigDecimal.ZERO, source = StatusSource.ACTUAL), - ) - Truth.assertThat(actual).containsExactly(expected) - - coVerify(ordering = Ordering.SEQUENCE) { - singleAccountListSupplier(params = SingleAccountListProducer.Params(userWalletId)) - } - } - - @Test - fun `flow will updated if balances are updated`() = runTest { - // Arrange - val accountList = AccountList.empty(userWallet) - val updatedAccountList = AccountList.empty(userWallet = userWallet, sortType = TokensSortType.BALANCE) - - val accountListFlow = MutableStateFlow(value = accountList) - - every { - singleAccountListSupplier(params = SingleAccountListProducer.Params(userWalletId)) - } returns accountListFlow - - // Act (first emission) - val actual1 = producer.produce().let(::getEmittedValues) - - // Assert (first emission) - val expected = AccountStatusList( - userWallet = userWallet, - accountStatuses = setOf( - AccountStatus.CryptoPortfolio( - account = accountList.mainAccount, - tokenList = TokenList.Empty, - priceChangeLce = PriceChange(value = BigDecimal("0.00"), source = StatusSource.ACTUAL).lceContent(), - ), - ), - totalAccounts = 1, - totalFiatBalance = TotalFiatBalance.Loaded(amount = BigDecimal.ZERO, source = StatusSource.ACTUAL), - ) - Truth.assertThat(actual1).containsExactly(expected) - - // Act (second emission) - accountListFlow.value = updatedAccountList - val actual2 = producer.produce().let(::getEmittedValues) - - // Assert (second emission) - val expected2 = AccountStatusList( - userWallet = userWallet, - accountStatuses = setOf( - AccountStatus.CryptoPortfolio( - account = updatedAccountList.mainAccount, - tokenList = TokenList.Empty, - priceChangeLce = PriceChange(value = BigDecimal("0.00"), source = StatusSource.ACTUAL).lceContent(), - ), - ), - totalAccounts = 1, - totalFiatBalance = TotalFiatBalance.Loaded(amount = BigDecimal.ZERO, source = StatusSource.ACTUAL), - ) - Truth.assertThat(actual2).containsExactly(expected2) - - coVerify(ordering = Ordering.SEQUENCE) { - singleAccountListSupplier(params = SingleAccountListProducer.Params(userWalletId)) - singleAccountListSupplier(params = SingleAccountListProducer.Params(userWalletId)) - } - } - - @Test - fun `flow is filtered the same balance`() = runTest { - // Arrange - val accountList = AccountList.empty(userWallet) - val accountListFlow = MutableStateFlow(value = accountList) - - every { - singleAccountListSupplier(params = SingleAccountListProducer.Params(userWalletId)) - } returns accountListFlow - - val expected = AccountStatusList( - userWallet = userWallet, - accountStatuses = setOf( - AccountStatus.CryptoPortfolio( - account = accountList.mainAccount, - tokenList = TokenList.Empty, - priceChangeLce = PriceChange(value = BigDecimal("0.00"), source = StatusSource.ACTUAL).lceContent(), - ), - ), - totalAccounts = 1, - totalFiatBalance = TotalFiatBalance.Loaded(amount = BigDecimal.ZERO, source = StatusSource.ACTUAL), - ) - - // Act (first emission) - val actual1 = producer.produce().let(::getEmittedValues) - - // Assert (first emission) - Truth.assertThat(actual1).containsExactly(expected) - - // Act (second emission) - accountListFlow.value = accountList - val actual2 = producer.produce().let(::getEmittedValues) - - // Assert (second emission) - Truth.assertThat(actual2).containsExactly(expected) - - coVerify(ordering = Ordering.SEQUENCE) { - singleAccountListSupplier(params = SingleAccountListProducer.Params(userWalletId)) - singleAccountListSupplier(params = SingleAccountListProducer.Params(userWalletId)) - } - } - - @Test - fun `flow is produced for account with non empty crypto currencies`() = runTest { - // Arrange - val cryptoCurrencyFactory = MockCryptoCurrencyFactory() - val accountList = AccountList.empty( - userWallet = userWallet, - cryptoCurrencies = cryptoCurrencyFactory.ethereumAndStellar.toSet(), - ) - - every { - singleAccountListSupplier(params = SingleAccountListProducer.Params(userWalletId)) - } returns flowOf(accountList) - - val ethereumStatus = CryptoCurrencyStatus( - currency = cryptoCurrencyFactory.ethereum, - value = CryptoCurrencyStatus.Loading, - ) - every { - cryptoCurrencyStatusesFlowFactory.create(userWallet = userWallet, currency = cryptoCurrencyFactory.ethereum) - } returns flowOf(ethereumStatus) - - val stellarStatus = CryptoCurrencyStatus( - currency = cryptoCurrencyFactory.stellar, - value = CryptoCurrencyStatus.MissedDerivation(priceChange = null, fiatRate = null), - ) - every { - cryptoCurrencyStatusesFlowFactory.create(userWallet = userWallet, currency = cryptoCurrencyFactory.stellar) - } returns flowOf(stellarStatus) - - // Act - val actual = producer.produce().let(::getEmittedValues) - - // Assert - val expected = AccountStatusList( - userWallet = userWallet, - accountStatuses = setOf( - AccountStatus.CryptoPortfolio( - account = accountList.mainAccount, - tokenList = TokenList.Ungrouped( - totalFiatBalance = TotalFiatBalance.Loading, - sortedBy = TokensSortType.NONE, - currencies = nonEmptyListOf(ethereumStatus, stellarStatus), - ), - priceChangeLce = lceLoading(), - ), - ), - totalAccounts = 1, - totalFiatBalance = TotalFiatBalance.Loading, - ) - Truth.assertThat(actual).containsExactly(expected) - - coVerify(ordering = Ordering.SEQUENCE) { - singleAccountListSupplier(params = SingleAccountListProducer.Params(userWalletId)) - } - } -} \ No newline at end of file diff --git a/domain/account/status/src/test/java/com/tangem/domain/account/status/utils/CryptoCurrencyStatusesFlowFactoryTest.kt b/domain/account/status/src/test/java/com/tangem/domain/account/status/utils/CryptoCurrencyStatusesFlowFactoryTest.kt deleted file mode 100644 index 0036579daf..0000000000 --- a/domain/account/status/src/test/java/com/tangem/domain/account/status/utils/CryptoCurrencyStatusesFlowFactoryTest.kt +++ /dev/null @@ -1,308 +0,0 @@ -package com.tangem.domain.account.status.utils - -import arrow.core.left -import arrow.core.right -import com.google.common.truth.Truth -import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory -import com.tangem.common.test.utils.getEmittedValues -import com.tangem.domain.models.StatusSource -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.network.NetworkStatus -import com.tangem.domain.models.quote.QuoteStatus -import com.tangem.domain.models.staking.StakingID -import com.tangem.domain.models.staking.YieldBalance -import com.tangem.domain.models.wallet.UserWallet -import com.tangem.domain.models.wallet.UserWalletId -import com.tangem.domain.models.wallet.isMultiCurrency -import com.tangem.domain.networks.single.SingleNetworkStatusProducer -import com.tangem.domain.networks.single.SingleNetworkStatusSupplier -import com.tangem.domain.quotes.single.SingleQuoteStatusProducer -import com.tangem.domain.quotes.single.SingleQuoteStatusSupplier -import com.tangem.domain.staking.StakingIdFactory -import com.tangem.domain.staking.single.SingleYieldBalanceProducer -import com.tangem.domain.staking.single.SingleYieldBalanceSupplier -import io.mockk.* -import kotlinx.coroutines.flow.emptyFlow -import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.AfterEach -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.TestInstance -import java.math.BigDecimal - -/** -[REDACTED_AUTHOR] - */ -@Suppress("UnusedFlow") -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -class CryptoCurrencyStatusesFlowFactoryTest { - - private val singleNetworkStatusSupplier: SingleNetworkStatusSupplier = mockk() - private val singleQuoteStatusSupplier: SingleQuoteStatusSupplier = mockk() - private val singleYieldBalanceSupplier: SingleYieldBalanceSupplier = mockk() - private val stakingIdFactory: StakingIdFactory = mockk() - - private val factory = CryptoCurrencyStatusesFlowFactory( - singleNetworkStatusSupplier = singleNetworkStatusSupplier, - singleQuoteStatusSupplier = singleQuoteStatusSupplier, - singleYieldBalanceSupplier = singleYieldBalanceSupplier, - stakingIdFactory = stakingIdFactory, - ) - - private val userWalletId = UserWalletId("011") - private val cryptoCurrencyFactory = MockCryptoCurrencyFactory() - - private val networkAddress = NetworkAddress.Single( - defaultAddress = NetworkAddress.Address(value = "0x1", type = NetworkAddress.Address.Type.Primary), - ) - - @AfterEach - fun tearDown() { - clearMocks( - singleNetworkStatusSupplier, - singleQuoteStatusSupplier, - singleYieldBalanceSupplier, - stakingIdFactory, - ) - } - - @Test - fun `if rawCurrencyId is null, there will be no subscription to the quote status`() = runTest { - // Arrange - val userWallet = mockk { - every { this@mockk.walletId } returns userWalletId - every { this@mockk.isMultiCurrency } returns true - } - - val currency = cryptoCurrencyFactory.ethereum.copy( - id = cryptoCurrencyFactory.ethereum.id.copy( - suffix = CryptoCurrency.ID.Suffix.ContractAddress(contractAddress = "0x12345"), - ), - ) - - val networkStatus = NetworkStatus( - network = currency.network, - value = NetworkStatus.Unreachable(address = networkAddress), - ) - val networkStatusFlow = flowOf(networkStatus) - every { - singleNetworkStatusSupplier(params = SingleNetworkStatusProducer.Params(userWalletId, currency.network)) - } returns networkStatusFlow - - val stakingId = StakingID(integrationId = "id", address = networkAddress.defaultAddress.value) - coEvery { - stakingIdFactory.create(currencyId = currency.id, defaultAddress = networkAddress.defaultAddress.value) - } returns stakingId.right() - - val yieldBalance = YieldBalance.Empty(stakingId = stakingId, source = StatusSource.ACTUAL) - val yieldBalanceFlow = flowOf(yieldBalance) - every { - singleYieldBalanceSupplier( - params = SingleYieldBalanceProducer.Params(userWalletId = userWalletId, stakingId = stakingId), - ) - } returns yieldBalanceFlow - - // Act - val actual = factory.create(userWallet = userWallet, currency = currency).let(::getEmittedValues) - - // Assert - val expected = CryptoCurrencyStatus( - currency = currency, - value = CryptoCurrencyStatus.Unreachable( - priceChange = null, - fiatRate = null, - networkAddress = networkAddress, - ), - ) - Truth.assertThat(actual).containsExactly(expected) - - coVerify(ordering = Ordering.SEQUENCE) { - singleNetworkStatusSupplier(params = SingleNetworkStatusProducer.Params(userWalletId, currency.network)) - stakingIdFactory.create(currencyId = currency.id, defaultAddress = networkAddress.defaultAddress.value) - singleYieldBalanceSupplier(params = SingleYieldBalanceProducer.Params(userWalletId, stakingId)) - } - } - - @Test - fun `if userWallet is not multi-currency, there will be no subscription to the yield balance`() = runTest { - // Arrange - val userWallet = mockk { - every { this@mockk.walletId } returns userWalletId - every { this@mockk.isMultiCurrency } returns false - } - - val currency = cryptoCurrencyFactory.ethereum - - val networkStatus = NetworkStatus( - network = currency.network, - value = NetworkStatus.Unreachable(address = networkAddress), - ) - val networkStatusFlow = flowOf(networkStatus) - every { - singleNetworkStatusSupplier(params = SingleNetworkStatusProducer.Params(userWalletId, currency.network)) - } returns networkStatusFlow - - val quoteStatus = QuoteStatus( - rawCurrencyId = currency.id.rawCurrencyId!!, - value = QuoteStatus.Data( - source = StatusSource.ACTUAL, - fiatRate = BigDecimal.ONE, - priceChange = BigDecimal.ONE, - ), - ) - val quoteStatusFlow = flowOf(quoteStatus) - every { - singleQuoteStatusSupplier(params = SingleQuoteStatusProducer.Params(currency.id.rawCurrencyId!!)) - } returns quoteStatusFlow - - // Act - val actual = factory.create(userWallet = userWallet, currency = currency).let(::getEmittedValues) - - // Assert - val expected = CryptoCurrencyStatus( - currency = currency, - value = CryptoCurrencyStatus.Unreachable( - priceChange = BigDecimal.ONE, - fiatRate = BigDecimal.ONE, - networkAddress = networkAddress, - ), - ) - Truth.assertThat(actual).containsExactly(expected) - - coVerify(ordering = Ordering.SEQUENCE) { - singleNetworkStatusSupplier(params = SingleNetworkStatusProducer.Params(userWalletId, currency.network)) - singleQuoteStatusSupplier(params = SingleQuoteStatusProducer.Params(currency.id.rawCurrencyId!!)) - } - } - - @Test - fun `no subscription to the quote status and yield balance`() = runTest { - // Arrange - val userWallet = mockk { - every { this@mockk.walletId } returns userWalletId - every { this@mockk.isMultiCurrency } returns false - } - - val currency = cryptoCurrencyFactory.ethereum.copy( - id = cryptoCurrencyFactory.ethereum.id.copy( - suffix = CryptoCurrency.ID.Suffix.ContractAddress(contractAddress = "0x12345"), - ), - ) - - val networkStatus = NetworkStatus( - network = currency.network, - value = NetworkStatus.Unreachable(address = networkAddress), - ) - val networkStatusFlow = flowOf(networkStatus) - every { - singleNetworkStatusSupplier(params = SingleNetworkStatusProducer.Params(userWalletId, currency.network)) - } returns networkStatusFlow - - // Act - val actual = factory.create(userWallet = userWallet, currency = currency).let(::getEmittedValues) - - // Assert - val expected = CryptoCurrencyStatus( - currency = currency, - value = CryptoCurrencyStatus.Unreachable( - priceChange = null, - fiatRate = null, - networkAddress = networkAddress, - ), - ) - Truth.assertThat(actual).containsExactly(expected) - - coVerify(ordering = Ordering.SEQUENCE) { - singleNetworkStatusSupplier(params = SingleNetworkStatusProducer.Params(userWalletId, currency.network)) - } - } - - @Test - fun `if stakingId is not supported, yield balance will be null`() = runTest { - // Arrange - val userWallet = mockk { - every { this@mockk.walletId } returns userWalletId - every { this@mockk.isMultiCurrency } returns true - } - - val currency = cryptoCurrencyFactory.ethereum - - val networkStatus = NetworkStatus( - network = currency.network, - value = NetworkStatus.Unreachable(address = networkAddress), - ) - val networkStatusFlow = flowOf(networkStatus) - every { - singleNetworkStatusSupplier(params = SingleNetworkStatusProducer.Params(userWalletId, currency.network)) - } returns networkStatusFlow - - val quoteStatus = QuoteStatus( - rawCurrencyId = currency.id.rawCurrencyId!!, - value = QuoteStatus.Data( - source = StatusSource.ACTUAL, - fiatRate = BigDecimal.ONE, - priceChange = BigDecimal.ONE, - ), - ) - val quoteStatusFlow = flowOf(quoteStatus) - every { - singleQuoteStatusSupplier(params = SingleQuoteStatusProducer.Params(currency.id.rawCurrencyId!!)) - } returns quoteStatusFlow - - coEvery { - stakingIdFactory.create(currencyId = currency.id, defaultAddress = networkAddress.defaultAddress.value) - } returns StakingIdFactory.Error.UnsupportedCurrency.left() - - // Act - val actual = factory.create(userWallet = userWallet, currency = currency).let(::getEmittedValues) - - // Assert - val expected = CryptoCurrencyStatus( - currency = currency, - value = CryptoCurrencyStatus.Unreachable( - priceChange = BigDecimal.ONE, - fiatRate = BigDecimal.ONE, - networkAddress = networkAddress, - ), - ) - Truth.assertThat(actual).containsExactly(expected) - - coVerify(ordering = Ordering.SEQUENCE) { - singleNetworkStatusSupplier(params = SingleNetworkStatusProducer.Params(userWalletId, currency.network)) - singleQuoteStatusSupplier(params = SingleQuoteStatusProducer.Params(currency.id.rawCurrencyId!!)) - } - } - - @Test - fun `all sources are empty`() = runTest { - // Arrange - val userWallet = mockk { - every { this@mockk.walletId } returns userWalletId - every { this@mockk.isMultiCurrency } returns true - } - - val currency = cryptoCurrencyFactory.ethereum - - every { - singleNetworkStatusSupplier(params = SingleNetworkStatusProducer.Params(userWalletId, currency.network)) - } returns emptyFlow() - - every { - singleQuoteStatusSupplier(params = SingleQuoteStatusProducer.Params(currency.id.rawCurrencyId!!)) - } returns emptyFlow() - - // Act - val actual = factory.create(userWallet = userWallet, currency = currency).let(::getEmittedValues) - - // Assert - val expected = CryptoCurrencyStatus(currency = currency, value = CryptoCurrencyStatus.Loading) - Truth.assertThat(actual).containsExactly(expected) - - coVerify(ordering = Ordering.SEQUENCE) { - singleNetworkStatusSupplier(params = SingleNetworkStatusProducer.Params(userWalletId, currency.network)) - singleQuoteStatusSupplier(params = SingleQuoteStatusProducer.Params(currency.id.rawCurrencyId!!)) - } - } -} \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingClickIntents.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingClickIntents.kt index 28507abd9f..97e447a0f5 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingClickIntents.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingClickIntents.kt @@ -69,4 +69,8 @@ internal interface StakingClickIntents : AmountScreenClickIntents { fun showPrimaryClickAlert() fun onOpenLearnMoreAboutApproveClick() + + fun onActivateTonAccountNotificationClick() + + fun onActivateTonAccountClick() } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingModel.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingModel.kt index b4c31ec116..f32aabd0e3 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingModel.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingModel.kt @@ -2,6 +2,7 @@ package com.tangem.features.staking.impl.presentation.model import androidx.compose.runtime.Stable import arrow.core.getOrElse +import com.tangem.blockchain.common.TransactionData import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.common.routing.AppRouter import com.tangem.common.ui.amountScreen.converters.AmountReduceByTransformer @@ -48,8 +49,11 @@ import com.tangem.domain.staking.utils.getValidatorsCount import com.tangem.domain.tokens.* import com.tangem.domain.transaction.error.GetFeeError import com.tangem.domain.transaction.usecase.CreateApprovalTransactionUseCase +import com.tangem.domain.transaction.usecase.CreateTransferTransactionUseCase import com.tangem.domain.transaction.usecase.GetAllowanceUseCase +import com.tangem.domain.transaction.usecase.GetFeeUseCase import com.tangem.domain.transaction.usecase.SendTransactionUseCase +import com.tangem.domain.utils.convertToSdkAmount import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.features.staking.api.StakingComponent import com.tangem.features.staking.impl.analytics.StakingParamsInterceptor @@ -69,10 +73,15 @@ import com.tangem.features.staking.impl.presentation.state.transformers.approval import com.tangem.features.staking.impl.presentation.state.transformers.confirmation.SetUpdatedAllowanceTransformer import com.tangem.features.staking.impl.presentation.state.transformers.notifications.AddStakingNotificationsTransformer import com.tangem.features.staking.impl.presentation.state.transformers.notifications.DismissStakingNotificationsStateTransformer +import com.tangem.features.staking.impl.presentation.state.transformers.ton.CompleteInitializeBottomSheetTransformer +import com.tangem.features.staking.impl.presentation.state.transformers.ton.SetFeeToTonInitializeBottomSheetTransformer +import com.tangem.features.staking.impl.presentation.state.transformers.ton.ShowTonInitializeBottomSheetTransformer +import com.tangem.features.staking.impl.presentation.state.transformers.ton.SetFeeErrorToTonInitializeBottomSheetTransformer import com.tangem.features.staking.impl.presentation.state.transformers.validator.ValidatorSelectChangeTransformer import com.tangem.features.staking.impl.presentation.state.utils.checkAndCalculateSubtractedAmount import com.tangem.features.staking.impl.presentation.state.utils.isSingleAction import com.tangem.features.staking.impl.presentation.state.utils.withStubUnstakeAction +import com.tangem.lib.crypto.BlockchainUtils.isTon import com.tangem.utils.Provider import com.tangem.utils.TangemBlogUrlBuilder.RESOURCE_TO_LEARN_ABOUT_APPROVING_IN_SWAP import com.tangem.utils.coroutines.* @@ -122,6 +131,9 @@ internal class StakingModel @Inject constructor( private val getActionsUseCase: GetActionsUseCase, private val getYieldUseCase: GetYieldUseCase, private val checkAccountInitializedUseCase: CheckAccountInitializedUseCase, + private val createTransferTransactionUseCase: CreateTransferTransactionUseCase, + private val getFeeUseCase: GetFeeUseCase, + private val getNetworkAddressesUseCase: GetNetworkAddressesUseCase, private val getActionRequirementAmountUseCase: GetActionRequirementAmountUseCase, private val paramsInterceptorHolder: ParamsInterceptorHolder, private val shareManager: ShareManager, @@ -150,11 +162,14 @@ internal class StakingModel @Inject constructor( } } + private var isAccountInitialized: Boolean = true private var cryptoCurrencyStatus: CryptoCurrencyStatus by Delegates.notNull() private var stakingActions: List = emptyList() private var feeCryptoCurrencyStatus: CryptoCurrencyStatus? = null private var minimumTransactionAmount: EnterAmountBoundary? = null + private var tonAccountInitializeTransaction: TransactionData.Uncompiled? = null + private val userWallet by lazy { requireNotNull( getUserWalletUseCase(userWalletId).getOrNull(), @@ -250,26 +265,12 @@ internal class StakingModel @Inject constructor( val isInitialInfoStep = value.currentStep == StakingStep.InitialInfo val noBalanceState = balanceState == null val noYieldBalanceData = cryptoCurrencyStatus.value.yieldBalance !is YieldBalance.Data - val isAccountInitialized = checkAccountInitializedUseCase.invoke( - userWalletId = userWalletId, - network = cryptoCurrencyStatus.currency.network, - ).getOrElse { - Timber.e(it) - false - } when { isInitialInfoStep && noBalanceState && yield.allValidatorsFull && noYieldBalanceData -> { stakingEventFactory.createStakingValidatorsUnavailableAlert() return@launch } - isInitialInfoStep && noBalanceState && !isAccountInitialized -> { - analyticsEventHandler.send(StakingAnalyticsEvent.UnitializedAddress( - token = cryptoCurrencyStatus.currency.symbol, - )) - stakingEventFactory.createInitializeAccountAlert() - return@launch - } isInitialInfoStep && noBalanceState -> { val list = buildList { SetConfirmationStateInitTransformer( @@ -763,6 +764,7 @@ internal class StakingModel @Inject constructor( AddStakingNotificationsTransformer( cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus }, appCurrencyProvider = Provider { appCurrency }, + isAccountInitializedProvider = Provider { isAccountInitialized }, feeCryptoCurrencyStatus = feeCryptoCurrencyStatus, currencyWarning = currencyWarning, currencyCheck = currencyStatus, @@ -934,6 +936,85 @@ internal class StakingModel @Inject constructor( urlOpener.openUrl(RESOURCE_TO_LEARN_ABOUT_APPROVING_IN_SWAP) } + override fun onActivateTonAccountNotificationClick() { + modelScope.launch { + stateController.update( + ShowTonInitializeBottomSheetTransformer( + onDismiss = { stateController.update(DismissBottomSheetStateTransformer) }, + ), + ) + + val tonAmount = BigDecimal(1) + val destination = getNetworkAddressesUseCase.invokeSync( + userWalletId = userWalletId, + network = cryptoCurrencyStatus.currency.network, + ).getOrNull(0)?.address ?: return@launch + + val transaction = createTransferTransactionUseCase( + amount = tonAmount.convertToSdkAmount(cryptoCurrencyStatus), + destination = destination, + userWalletId = userWalletId, + network = cryptoCurrencyStatus.currency.network, + memo = null, + ) + tonAccountInitializeTransaction = transaction.getOrElse { + stateController.update( + SetFeeErrorToTonInitializeBottomSheetTransformer(), + ) + return@launch + } + + val transactionFee = getFeeUseCase( + userWallet = userWallet, + network = cryptoCurrencyStatus.currency.network, + transactionData = tonAccountInitializeTransaction!!, + ) + + transactionFee.fold( + ifLeft = { + stateController.update( + SetFeeErrorToTonInitializeBottomSheetTransformer(), + ) + }, + ifRight = { + stateController.update( + SetFeeToTonInitializeBottomSheetTransformer( + appCurrencyProvider = Provider { appCurrency }, + feeCryptoCurrencyStatus = feeCryptoCurrencyStatus, + fee = it.normal, + isFeeApproximate = false, + ), + ) + }, + ) + } + } + + override fun onActivateTonAccountClick() { + modelScope.launch { + tonAccountInitializeTransaction?.let { transaction -> + sendTransactionUseCase.invoke( + txData = transaction, + userWallet = userWallet, + network = cryptoCurrencyStatus.currency.network, + ).fold( + ifLeft = { + stateController.update(DismissBottomSheetStateTransformer) + }, + ifRight = { + stateController.update(CompleteInitializeBottomSheetTransformer( + cryptoCurrencyStatus = cryptoCurrencyStatus, + minimumTransactionAmount = minimumTransactionAmount, + )) + + balanceUpdater.partialUpdateWithDelay() + stateController.update(DismissBottomSheetStateTransformer) + }, + ) + } + } + } + private suspend fun setupApprovalNeeded() { val approval = StakingIntegrationID.create(currencyId = cryptoCurrencyStatus.currency.id)?.approval ?: StakingApproval.Empty @@ -961,7 +1042,9 @@ internal class StakingModel @Inject constructor( ) .conflate() .distinctUntilChanged() - .filter { value.currentStep == StakingStep.InitialInfo } + .filter { + value.currentStep == StakingStep.InitialInfo || isCaseWithUnitializedTonAccount() + } .onEach { maybeStatus -> maybeStatus.fold( ifRight = { status -> @@ -989,6 +1072,19 @@ internal class StakingModel @Inject constructor( } cryptoCurrencyStatus = status + val isAccountInitializedNew = checkAccountInitializedUseCase.invoke( + userWalletId = userWalletId, + network = cryptoCurrencyStatus.currency.network, + ).getOrElse { + Timber.e(it) + false + } + if (isAccountInitializedNew && !isAccountInitialized) { + isAccountInitialized = true + updateNotifications() + } + isAccountInitialized = isAccountInitializedNew + setupApprovalNeeded() setupIsAnyTokenStaked() checkIfSubtractAvailable() @@ -1150,6 +1246,12 @@ internal class StakingModel @Inject constructor( .getOrElse { false } } + private fun isCaseWithUnitializedTonAccount(): Boolean { + return value.currentStep == StakingStep.Confirmation && + isTon(cryptoCurrencyStatus.currency.network.rawId) && + !isAccountInitialized + } + private companion object { const val WHAT_IS_STAKING_ARTICLE_URL = "https://tangem.com/en/blog/post/how-to-stake-cryptocurrency/" const val ALLOWANCE_UPDATE_DELAY = 10_000L diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingNotification.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingNotification.kt index abbe28cfb5..b5485b47ba 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingNotification.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingNotification.kt @@ -60,6 +60,15 @@ internal object StakingNotification { title = resourceReference(R.string.staking_notification_low_staked_balance_title), subtitle = resourceReference(R.string.staking_notification_low_staked_balance_text), ) + + data class InitializeTonAccount(private val onInitializeClick: () -> Unit) : Info( + title = resourceReference(R.string.staking_notification_ton_account_initialization_title), + subtitle = resourceReference(R.string.staking_notification_ton_account_initialization_message), + buttonsState = NotificationConfig.ButtonsState.PrimaryButtonConfig( + text = resourceReference(R.string.common_activate), + onClick = onInitializeClick, + ), + ) } sealed class Info( diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/bottomsheet/TonInitializeAccountBottomSheetConfig.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/bottomsheet/TonInitializeAccountBottomSheetConfig.kt new file mode 100644 index 0000000000..f941d083ec --- /dev/null +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/bottomsheet/TonInitializeAccountBottomSheetConfig.kt @@ -0,0 +1,15 @@ +package com.tangem.features.staking.impl.presentation.state.bottomsheet + +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent +import com.tangem.core.ui.extensions.TextReference +import com.tangem.features.staking.impl.presentation.state.FeeState + +internal data class TonInitializeAccountBottomSheetConfig( + val title: TextReference, + val message: TextReference, + val footer: TextReference, + val onButtonClick: () -> Unit, + val isButtonEnabled: Boolean, + val isButtonLoading: Boolean, + val feeState: FeeState, +) : TangemBottomSheetConfigContent \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/events/StakingAlertUM.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/events/StakingAlertUM.kt index d2c18a8581..af6931c29f 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/events/StakingAlertUM.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/events/StakingAlertUM.kt @@ -62,13 +62,6 @@ internal sealed class StakingAlertUM : AlertUM { override val confirmButtonText: TextReference = resourceReference(id = R.string.common_ok) } - data object InitializeAccount : StakingAlertUM() { - override val onConfirmClick: (() -> Unit)? = null - override val title: TextReference = resourceReference(id = R.string.staking_error_no_validators_title) - override val message: TextReference = resourceReference(id = R.string.staking_notification_ton_activate_account) - override val confirmButtonText: TextReference = resourceReference(id = R.string.common_ok) - } - data class RewardsMinimumRequirementsError( val cryptoCurrencyName: String, val cryptoAmountValue: String, diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/events/StakingEventFactory.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/events/StakingEventFactory.kt index bb1b446118..465bd1c024 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/events/StakingEventFactory.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/events/StakingEventFactory.kt @@ -47,11 +47,6 @@ internal class StakingEventFactory( stateController.updateEvent(alert) } - fun createInitializeAccountAlert() { - val alert = StakingEvent.ShowAlert(alert = StakingAlertUM.InitializeAccount) - stateController.updateEvent(alert) - } - fun createStakingRewardsMinimumRequirementsErrorAlert(cryptoCurrencyName: String, cryptoAmountValue: String) { stateController.updateEvent( StakingEvent.ShowAlert( diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingBalanceUpdater.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingBalanceUpdater.kt index 3defda6f74..977585131e 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingBalanceUpdater.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingBalanceUpdater.kt @@ -76,6 +76,11 @@ internal class StakingBalanceUpdater @AssistedInject constructor( } } + suspend fun partialUpdateWithDelay() { + delay(BALANCE_UPDATE_DELAY) + partialUpdate() + } + private suspend fun fetchCurrencyStatus(delayMillis: Long = 0L) { delay(delayMillis) fetchCurrencyStatusUseCase( diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/stub/StakingClickIntentsStub.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/stub/StakingClickIntentsStub.kt index d1ed5d5c98..d7fac733a7 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/stub/StakingClickIntentsStub.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/stub/StakingClickIntentsStub.kt @@ -78,4 +78,8 @@ internal object StakingClickIntentsStub : StakingClickIntents { override fun showPrimaryClickAlert() {} override fun onOpenLearnMoreAboutApproveClick() {} + + override fun onActivateTonAccountNotificationClick() {} + + override fun onActivateTonAccountClick() {} } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/AddStakingNotificationsTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/AddStakingNotificationsTransformer.kt index 0dbf76f8ad..1681808734 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/AddStakingNotificationsTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/AddStakingNotificationsTransformer.kt @@ -40,6 +40,7 @@ import java.math.BigDecimal internal class AddStakingNotificationsTransformer( private val cryptoCurrencyStatusProvider: Provider, private val appCurrencyProvider: Provider, + private val isAccountInitializedProvider: Provider, private val feeCryptoCurrencyStatus: CryptoCurrencyStatus?, private val currencyWarning: CryptoCurrencyWarning?, private val feeError: GetFeeError?, @@ -90,34 +91,40 @@ internal class AddStakingNotificationsTransformer( BigDecimal.ZERO } - val notifications = buildList { - // errors - addErrorNotifications( - prevState = prevState, - feeError = feeError, - sendingAmount = sendingAmount, - onReload = prevState.clickIntents::getFee, - feeValue = feeValue, - ) - addStakingErrorNotifications(stakingError = stakingError, onReload = prevState.clickIntents::getFee) - // warnings - addWarningNotifications( - prevState = prevState, - amountState = amountState, - feeState = feeState, - sendingAmount = sendingAmount, - isFeeCoverage = isFeeCoverage && isEnterAction && !sendingAmount.equals(minimumRequirement), - ) + val notifications = if (isAccountInitializedProvider.invoke()) { + buildList { + // errors + addErrorNotifications( + prevState = prevState, + feeError = feeError, + sendingAmount = sendingAmount, + onReload = prevState.clickIntents::getFee, + feeValue = feeValue, + ) + addStakingErrorNotifications(stakingError = stakingError, onReload = prevState.clickIntents::getFee) + // warnings + addWarningNotifications( + prevState = prevState, + amountState = amountState, + feeState = feeState, + sendingAmount = sendingAmount, + isFeeCoverage = isFeeCoverage && isEnterAction && !sendingAmount.equals(minimumRequirement), + ) - stakingInfoNotificationsFactory.addInfoNotifications( - notifications = this, - prevState = prevState, - sendingAmount = sendingAmount, - actionAmount = amountValue, - feeValue = feeValue, - tonBalanceExtraFeeThreshold = TON_BALANCE_EXTRA_FEE_THRESHOLD, - ) - }.toImmutableList() + stakingInfoNotificationsFactory.addInfoNotifications( + notifications = this, + prevState = prevState, + sendingAmount = sendingAmount, + actionAmount = amountValue, + feeValue = feeValue, + tonBalanceExtraFeeThreshold = TON_BALANCE_EXTRA_FEE_THRESHOLD, + ) + }.toImmutableList() + } else { + buildList { + addTonInitializeAccountNotification(prevState) + }.toImmutableList() + } val isActualSources = with(cryptoCurrencyStatus.value) { sources.yieldBalanceSource.isActual() && sources.networkSource.isActual() @@ -130,7 +137,8 @@ internal class AddStakingNotificationsTransformer( it is StakingNotification.Error || it is NotificationUM.Error || it is NotificationUM.Warning.NetworkFeeUnreachable || - it is StakingNotification.Warning.TransactionInProgress + it is StakingNotification.Warning.TransactionInProgress || + it is StakingNotification.Warning.InitializeTonAccount } && isActualSources, ), ) @@ -219,7 +227,6 @@ internal class AddStakingNotificationsTransformer( ) { val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() val appCurrency = appCurrencyProvider() - cryptoCurrencyStatus.currency addRentExemptionNotification( rentWarning = currencyCheck.rentWarning, @@ -282,6 +289,19 @@ internal class AddStakingNotificationsTransformer( } } + private fun MutableList.addTonInitializeAccountNotification(prevState: StakingUiState) { + val isAccountInitialized = isAccountInitializedProvider.invoke() + val cryptoCurrencyNetworkIdValue = cryptoCurrencyStatusProvider().currency.network.rawId + + if (isTon(cryptoCurrencyNetworkIdValue) && !isAccountInitialized) { + add( + StakingNotification.Warning.InitializeTonAccount( + onInitializeClick = prevState.clickIntents::onActivateTonAccountNotificationClick, + ), + ) + } + } + private companion object { val TON_BALANCE_EXTRA_FEE_THRESHOLD = BigDecimal(0.2) } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/ton/CompleteInitializeBottomSheetTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/ton/CompleteInitializeBottomSheetTransformer.kt new file mode 100644 index 0000000000..7a9bc02057 --- /dev/null +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/ton/CompleteInitializeBottomSheetTransformer.kt @@ -0,0 +1,53 @@ +package com.tangem.features.staking.impl.presentation.state.transformers.ton + +import com.tangem.common.ui.amountScreen.converters.AmountReduceByTransformer +import com.tangem.common.ui.amountScreen.converters.AmountReduceByTransformer.ReduceByData +import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary +import com.tangem.domain.models.currency.CryptoCurrencyStatus +import com.tangem.features.staking.impl.presentation.state.FeeState +import com.tangem.features.staking.impl.presentation.state.StakingStates +import com.tangem.features.staking.impl.presentation.state.StakingUiState +import com.tangem.features.staking.impl.presentation.state.bottomsheet.TonInitializeAccountBottomSheetConfig +import com.tangem.utils.transformer.Transformer +import java.math.BigDecimal + +internal class CompleteInitializeBottomSheetTransformer( + private val cryptoCurrencyStatus: CryptoCurrencyStatus, + private val minimumTransactionAmount: EnterAmountBoundary?, +) : Transformer { + + override fun transform(prevState: StakingUiState): StakingUiState { + val prevBottomSheetConfig = prevState.bottomSheetConfig ?: return prevState + val prevBottomSheetConfigContent = + prevBottomSheetConfig.content as? TonInitializeAccountBottomSheetConfig ?: return prevState + + val feeAmount = (prevBottomSheetConfigContent.feeState as? FeeState.Content) + ?.fee?.amount?.value?.multiply(BigDecimal(FEE_MULTIPLIER)) ?: return prevState + + val confirmationState = prevState.confirmationState as? StakingStates.ConfirmationState.Data ?: return prevState + + return prevState.copy( + amountState = AmountReduceByTransformer( + cryptoCurrencyStatus = cryptoCurrencyStatus, + minimumTransactionAmount = minimumTransactionAmount, + value = ReduceByData( + feeAmount, + feeAmount, + ), + ).transform(prevState.amountState), + confirmationState = confirmationState.copy( + reduceAmountBy = feeAmount, + ), + bottomSheetConfig = prevBottomSheetConfig.copy( + content = prevBottomSheetConfigContent.copy( + isButtonLoading = true, + isButtonEnabled = false, + ), + ), + ) + } + + private companion object { + const val FEE_MULTIPLIER = 1.1 + } +} \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/ton/SetFeeErrorToTonInitializeBottomSheetTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/ton/SetFeeErrorToTonInitializeBottomSheetTransformer.kt new file mode 100644 index 0000000000..644e344be8 --- /dev/null +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/ton/SetFeeErrorToTonInitializeBottomSheetTransformer.kt @@ -0,0 +1,25 @@ +package com.tangem.features.staking.impl.presentation.state.transformers.ton + +import com.tangem.features.staking.impl.presentation.state.FeeState +import com.tangem.features.staking.impl.presentation.state.StakingUiState +import com.tangem.features.staking.impl.presentation.state.bottomsheet.TonInitializeAccountBottomSheetConfig +import com.tangem.utils.transformer.Transformer + +internal class SetFeeErrorToTonInitializeBottomSheetTransformer : Transformer { + + override fun transform(prevState: StakingUiState): StakingUiState { + val prevBottomSheetConfig = prevState.bottomSheetConfig ?: return prevState + val prevBottomSheetConfigContent = + prevBottomSheetConfig.content as? TonInitializeAccountBottomSheetConfig ?: return prevState + + return prevState.copy( + bottomSheetConfig = prevBottomSheetConfig.copy( + content = prevBottomSheetConfigContent.copy( + isButtonEnabled = false, + feeState = FeeState.Error, + isButtonLoading = false, + ), + ), + ) + } +} \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/ton/SetFeeToTonInitializeBottomSheetTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/ton/SetFeeToTonInitializeBottomSheetTransformer.kt new file mode 100644 index 0000000000..659bc1e3ab --- /dev/null +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/ton/SetFeeToTonInitializeBottomSheetTransformer.kt @@ -0,0 +1,41 @@ +package com.tangem.features.staking.impl.presentation.state.transformers.ton + +import com.tangem.blockchain.common.transaction.Fee +import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.models.currency.CryptoCurrencyStatus +import com.tangem.features.staking.impl.presentation.state.FeeState +import com.tangem.features.staking.impl.presentation.state.StakingUiState +import com.tangem.features.staking.impl.presentation.state.bottomsheet.TonInitializeAccountBottomSheetConfig +import com.tangem.utils.Provider +import com.tangem.utils.transformer.Transformer + +internal class SetFeeToTonInitializeBottomSheetTransformer( + private val appCurrencyProvider: Provider, + private val feeCryptoCurrencyStatus: CryptoCurrencyStatus?, + private val fee: Fee, + private val isFeeApproximate: Boolean, +) : Transformer { + + override fun transform(prevState: StakingUiState): StakingUiState { + val prevBottomSheetConfig = prevState.bottomSheetConfig ?: return prevState + val prevBottomSheetConfigContent = + prevBottomSheetConfig.content as? TonInitializeAccountBottomSheetConfig ?: return prevState + + val isFeeConvertibleToFiat = feeCryptoCurrencyStatus?.currency?.network?.hasFiatFeeRate == true + return prevState.copy( + bottomSheetConfig = prevBottomSheetConfig.copy( + content = prevBottomSheetConfigContent.copy( + isButtonEnabled = true, + isButtonLoading = false, + feeState = FeeState.Content( + fee = fee, + rate = feeCryptoCurrencyStatus?.value?.fiatRate, + isFeeConvertibleToFiat = isFeeConvertibleToFiat, + appCurrency = appCurrencyProvider(), + isFeeApproximate = isFeeApproximate, + ), + ), + ), + ) + } +} \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/ton/ShowTonInitializeBottomSheetTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/ton/ShowTonInitializeBottomSheetTransformer.kt new file mode 100644 index 0000000000..27e17d0ca7 --- /dev/null +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/ton/ShowTonInitializeBottomSheetTransformer.kt @@ -0,0 +1,32 @@ +package com.tangem.features.staking.impl.presentation.state.transformers.ton + +import com.tangem.common.ui.R +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.features.staking.impl.presentation.state.FeeState +import com.tangem.features.staking.impl.presentation.state.StakingUiState +import com.tangem.features.staking.impl.presentation.state.bottomsheet.TonInitializeAccountBottomSheetConfig +import com.tangem.utils.transformer.Transformer + +internal class ShowTonInitializeBottomSheetTransformer( + private val onDismiss: () -> Unit, +) : Transformer { + + override fun transform(prevState: StakingUiState): StakingUiState { + return prevState.copy( + bottomSheetConfig = TangemBottomSheetConfig( + isShown = true, + onDismissRequest = onDismiss, + content = TonInitializeAccountBottomSheetConfig( + title = resourceReference(R.string.staking_account_initialization_title), + message = resourceReference(R.string.staking_account_initialization_message), + footer = resourceReference(R.string.staking_account_initialization_footer), + onButtonClick = prevState.clickIntents::onActivateTonAccountClick, + isButtonEnabled = false, + feeState = FeeState.Loading, + isButtonLoading = false, + ), + ), + ) + } +} \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingScreen.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingScreen.kt index 5170756b32..c8f15d868e 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingScreen.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingScreen.kt @@ -28,8 +28,10 @@ import com.tangem.features.staking.impl.presentation.state.StakingStep import com.tangem.features.staking.impl.presentation.state.StakingUiState import com.tangem.features.staking.impl.presentation.state.bottomsheet.StakingActionSelectionBottomSheetConfig import com.tangem.features.staking.impl.presentation.state.bottomsheet.StakingInfoBottomSheetConfig +import com.tangem.features.staking.impl.presentation.state.bottomsheet.TonInitializeAccountBottomSheetConfig import com.tangem.features.staking.impl.presentation.ui.bottomsheet.StakingActionSelectorBottomSheet import com.tangem.features.staking.impl.presentation.ui.bottomsheet.StakingInfoBottomSheet +import com.tangem.features.staking.impl.presentation.ui.bottomsheet.TonInitializeAccountBottomSheet import kotlinx.coroutines.delay import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map @@ -83,6 +85,7 @@ fun StakingBottomSheet(bottomSheetConfig: TangemBottomSheetConfig?) { is StakingInfoBottomSheetConfig -> StakingInfoBottomSheet(bottomSheetConfig) is GiveTxPermissionBottomSheetConfig -> GiveTxPermissionBottomSheet(bottomSheetConfig) is StakingActionSelectionBottomSheetConfig -> StakingActionSelectorBottomSheet(bottomSheetConfig) + is TonInitializeAccountBottomSheetConfig -> TonInitializeAccountBottomSheet(bottomSheetConfig) } } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/bottomsheet/TonInitializeAccountBottomSheet.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/bottomsheet/TonInitializeAccountBottomSheet.kt new file mode 100644 index 0000000000..21be5002e8 --- /dev/null +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/bottomsheet/TonInitializeAccountBottomSheet.kt @@ -0,0 +1,158 @@ +package com.tangem.features.staking.impl.presentation.ui.bottomsheet + +import android.content.res.Configuration +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.tangem.blockchain.common.Amount +import com.tangem.blockchain.common.AmountType +import com.tangem.blockchain.common.transaction.Fee +import com.tangem.common.ui.R +import com.tangem.core.ui.components.SpacerH +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet +import com.tangem.core.ui.components.buttons.common.TangemButton +import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition +import com.tangem.core.ui.components.buttons.common.TangemButtonSize +import com.tangem.core.ui.components.buttons.common.TangemButtonsDefaults +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.domain.appcurrency.model.AppCurrency +import com.tangem.features.staking.impl.presentation.state.FeeState +import com.tangem.features.staking.impl.presentation.state.bottomsheet.TonInitializeAccountBottomSheetConfig +import com.tangem.features.staking.impl.presentation.ui.block.StakingFeeBlock +import java.math.BigDecimal + +@Composable +internal fun TonInitializeAccountBottomSheet(config: TangemBottomSheetConfig) { + TangemBottomSheet( + config = config, + containerColor = TangemTheme.colors.background.tertiary, + ) { content -> + Column( + modifier = Modifier + .padding( + start = TangemTheme.dimens.spacing16, + end = TangemTheme.dimens.spacing16, + bottom = TangemTheme.dimens.spacing16, + ) + .fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + SpacerH(height = 16.dp) + Image( + painter = painterResource(id = R.drawable.img_ton_22), + modifier = Modifier.size(56.dp), + contentDescription = null, + ) + + SpacerH(height = 24.dp) + + Text( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + text = content.title.resolveReference(), + style = TangemTheme.typography.h3, + color = TangemTheme.colors.text.primary1, + textAlign = TextAlign.Center, + ) + + SpacerH(height = 16.dp) + + Text( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + text = content.message.resolveReference(), + style = TangemTheme.typography.body2, + color = TangemTheme.colors.text.secondary, + textAlign = TextAlign.Center, + ) + + SpacerH(height = 24.dp) + + StakingFeeBlock(content.feeState) + + SpacerH(height = 12.dp) + + Text( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + text = content.footer.resolveReference(), + style = TangemTheme.typography.caption2, + color = TangemTheme.colors.text.tertiary, + textAlign = TextAlign.Center, + ) + + SpacerH(height = 20.dp) + + TangemButton( + text = stringResourceSafe(R.string.common_activate), + icon = TangemButtonIconPosition.End(iconResId = R.drawable.ic_tangem_24), + onClick = content.onButtonClick, + colors = TangemButtonsDefaults.primaryButtonColors, + showProgress = content.feeState is FeeState.Loading || content.isButtonLoading, + enabled = content.isButtonEnabled, + size = TangemButtonSize.WideAction, + textStyle = TangemTheme.typography.subtitle1, + modifier = Modifier.fillMaxWidth(), + ) + + SpacerH(height = 8.dp) + } + } +} + +// region Preview +@Composable +@Preview(showBackground = true, widthDp = 360) +@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) +private fun Preview_TonInitializeAccountBottomSheetContent() { + TangemThemePreview { + TonInitializeAccountBottomSheet( + config = TangemBottomSheetConfig( + isShown = true, + onDismissRequest = {}, + content = TonInitializeAccountBottomSheetConfig( + title = resourceReference(R.string.staking_account_initialization_title), + message = resourceReference(R.string.staking_account_initialization_message), + footer = resourceReference(R.string.staking_account_initialization_footer), + onButtonClick = {}, + isButtonEnabled = true, + isButtonLoading = false, + feeState = FeeState.Content( + fee = Fee.Common( + amount = Amount( + currencySymbol = "MATIC", + value = BigDecimal(0.159806), + decimals = 18, + type = AmountType.Coin, + ), + ), + rate = BigDecimal.ONE, + appCurrency = AppCurrency.Default, + isFeeApproximate = false, + isFeeConvertibleToFiat = true, + ), + ), + + ), + ) + } +} +// endregion Preview \ No newline at end of file