From ef6918ce4ae3f2d435c5e6dd67c8daf2fa301077 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 8 Dec 2025 14:49:51 +0500 Subject: [PATCH 1/7] Updated on 2026-08-14 --- core/res/src/main/res/values-de/strings.xml | 1 - core/res/src/main/res/values-ja/strings.xml | 20 ++- core/res/src/main/res/values-ru/strings.xml | 11 +- core/res/src/main/res/values/strings.xml | 23 +++- .../ui/components/transactions/Transaction.kt | 7 +- .../src/main/res/drawable/ic_connect_24.xml | 9 ++ .../main/res/drawable/ic_disconnect_24.xml | 15 +++ core/ui/src/main/res/drawable/ic_gear_24.xml | 14 +++ .../SdkTransactionHistoryItemConverter.kt | 2 +- .../utils/SdkTransactionTypeConverter.kt | 53 ++++++-- ...TransactionDataToTxHistoryItemConverter.kt | 29 +++-- .../supply/DefaultYieldSupplyRepository.kt | 8 +- .../src/main/assets/contract_methods.json | 12 +- .../tangem/domain/models/network/TxInfo.kt | 26 +++- ...xHistoryItemToTransactionStateConverter.kt | 110 +++++++++++++---- .../SetTxHistoryCountErrorTransformer.kt | 3 + .../converter/TxHistoryItemStateConverter.kt | 114 ++++++++++++++---- .../wallet/subscribers/TxHistorySubscriber.kt | 15 ++- .../subscribers/TxHistorySubscriberV2.kt | 15 ++- gradle/tangem_dependencies.toml | 2 +- 20 files changed, 374 insertions(+), 115 deletions(-) create mode 100644 core/ui/src/main/res/drawable/ic_connect_24.xml create mode 100644 core/ui/src/main/res/drawable/ic_disconnect_24.xml create mode 100644 core/ui/src/main/res/drawable/ic_gear_24.xml diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index 95bfe4d9e6..58da791e4e 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -583,7 +583,6 @@ Zuerst die Sicherung abschließen Unvollständig Andere Methoden - Physische Geräte, die Deine privaten Schlüssel sicher offline speichern. Wiederherstellungs-Phrase Um Deine Wallet auf Hardware umzustellen, erstelle vorher ein Backup. Deine privaten Schlüssel sind sicher verschlüsselt und auf Deinem Telefon gespeichert. diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml index b15a7f50b0..3979991fb5 100644 --- a/core/res/src/main/res/values-ja/strings.xml +++ b/core/res/src/main/res/values-ja/strings.xml @@ -3,6 +3,10 @@ アクセスコードがないとウォレットは安全ではありません。 とにかくスキップ アクセスコードが設定されていません + コードを変更 + アクセスコードは、ウォレットのロック解除・資産へのアクセス保護に使用されます + このまま使用 + このアクセスコードは簡単に推測される可能性があります アクセスコードを入力 アクセスコードが間違っています。あと%s回間違えると、モバイルウォレットが削除されます。 アクセスコードが間違っています。あと%s回失敗すると、アプリはロックされます。 @@ -586,7 +590,6 @@ まずバックアップを完了する まずバックアップを完了する その他の方法 - 秘密鍵をオフラインで安全に保存する物理デバイス。 リカバリーフレーズ アクセスコードを使用してウォレットを保護するには、まずバックアップを完了してください。 ウォレットをハードウェアにアップグレードするには、まずバックアップしてください。 @@ -773,6 +776,10 @@ 取引量 これをドラッグするか、検索窓をタップして、マーケットから直接トークンを追加します トークンを追加 + 資産を即時アクセス可能な状態に保ったまま、パワーアップさせよう。%s + 利息モードを有効にする + モバイルウォレットを作成するには、%1$sにアップデートする必要があります + モバイルウォレットを使用するには、%1$s以降が必要です すべてのニュース 最新情報を入手 お使いのデバイスではNFCが使用できません @@ -1897,6 +1904,7 @@ Tangemは、生成された利息に対して15%サービス手数料も徴収します。 ネットワーク手数料が下がるか、残高が最低必要額に達すると、資金は自動的にAaveに供給されます。 過去のリターン + 保有資産に年利%1$s%%を適用 利息モードでのトークンの承認が取り消されました。トークンを開いて再度許可してください。 トークンの承認が必要です ネットワーク接続を確認してください @@ -1911,7 +1919,9 @@ 分散型・自己管理型 このサービスを利用することにより、プロバイダー\n%1$sおよび%2$sに同意するものとします。 Aaveに接続 + 残高に %1$s%% の年利(APY)を適用 Aave %1$s%% • 変動金利 + 変動金利 Aave 平均%s 昨年のリターン @@ -1938,12 +1948,16 @@ 利息モード 利息モードの有効化 利息モード - 利息モードが有効になりました + 利息モードコントラクトのデプロイ + 利息モードを有効にする %1$sがAaveに供給されました - 利息モードが無効になりました + 利息モードを無効にする %1$sがAaveから引き出されました + 利息モードをセットアップ + 利息モードの再有効化 Aaveへの供給 %1$sがAaveに供給されました + Aaveから引き出す 自動 取引のネットワーク手数料をカバーするために、 %1$s %2$sを追加してください。 %s手数料を支払えません diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index db0fefa23f..62446f8321 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -274,7 +274,7 @@ Из Из %s Синхронизировать адреса - Начать зарабатывать + Начать Получить токен К провайдеру Перейти в токен @@ -576,7 +576,7 @@ Сначала завершите создание резервной копии Не завершено Другие способы - Физические устройства, которые надёжно хранят ваш приватный ключ офлайн. + Сохраните свою recovery-фразу вручную в надёжном месте. Держите её в секрете, чтобы защитить свои средства. Фраза восстановления Чтобы защитить ваш кошелёк с помощью кода доступа, сначала завершите резервное копирование. Чтобы улучшить кошелёк до аппаратного, сначала создайте резервную копию. @@ -1075,7 +1075,7 @@ Обратите внимание, что для совершения транзакции с вашими средствами по-прежнему потребуется ваша карта или кольцо Сканировать Отсканируйте карту или кольцо, чтобы изменить ее настройки. Изменения затронут только ту карту или кольцо, которые вы отсканировали, и не повлияют на другие устройства, привязанные к вашему кошельку. - Приготовьте свой Tangem! + Приготовьте устройство Tangem! Уведомление безопасности Нет Да @@ -1538,6 +1538,7 @@ Восстановите существующий кошелек через Google Drive. Импорт из Google Drive Добавить существующий кошелек + Физические устройства, которые безопасно хранят ваш приватный ключ офлайн. Сканировать кошелек Tangem Импортируйте существующий кошелек через вашу фразу восстановления. Имрортировать кошелек @@ -1854,9 +1855,9 @@ Режим доходности Включение режима доходности Режим доходности - Режим доходности включен + Включение режима доходности %1$s отправлено в Aave - Режим доходности выключен + Отключение режима доходности %1$s выведено из Aave Перевод средств в Aave %1$s отправлено в Aave diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index deea678229..1dd7761587 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -3,6 +3,10 @@ Without an access code, your wallet is not secure. Skip anyway Access code not set + Change code + Your access code will be used to unlock your wallet and to protect access to the assets + Use anyway + This access code can be easily guessed Enter access code Wrong access code. Your mobile wallet will be deleted after %s more incorrect attempts. Wrong access code. The app will be locked after %s more failed attempts @@ -596,7 +600,7 @@ Finalize backup first Incomplete Other methods - Physical devices that securely store your private key offline. + Manually save your recovery phrase in a secure place and keep it private to protect your funds. Recovery phrase To secure your wallet with a Access Code, complete the backup first. To upgrade your wallet to hardware, back it up first. @@ -788,6 +792,10 @@ Volume Pull this up or tap the search bar to add tokens directly from the market Add tokens + Power up your assets while supplying them with instant access. %s + Activate Yield Mode + You must update to %1$s in order to create mobile wallet + Mobile Wallet requires %1$s or later All news Stay in the loop NFC is not available on your device @@ -1450,6 +1458,8 @@ Failed to issue card A technical error has occurred, please try again by clicking the button below. A technical error has occurred, please contact support. + Use your crypto for real world spending. \nIt\'s a payment card unlike any other. + Get Tangem Pay It usually takes up to 15 minutes Setting up your Tangem Card Issuing your card @@ -1474,6 +1484,7 @@ Service temporarily unavailable The service is currently unreachable. Please try again later. Sync needed + Tangem Visa Card Tangem Pay is temporarily unavailable Tangem Pay Use your card or ring to restore access to your payment account @@ -1520,6 +1531,7 @@ Multiple addresses Transaction history is currently not supported for this blockchain. But don\'t worry, we\'re working on it! In the meantime you can check it in the explorer. Operation + for: %s from: %s to: %s validator: %s @@ -1973,6 +1985,7 @@ Tangem also takes a 15% service fee on yield generated. Your funds will be automatically supplied to Aave once network fees are lower or your balance meets the minimum required amount. Historical returns + "Enable %1$s%% APY on your balance" Approval for your token in Yield Mode has been revoked. Open the token to grant permission again. Token approval needed Check your network connection @@ -2016,12 +2029,16 @@ Yield Mode Enabling Yield Mode Yield Mode - Yield Mode enabled + Yield Mode contract deploy + Yield Mode enable %1$s supplied to Aave - Yield Mode disabled + Yield Mode disable %1$s withdrawn from Aave + Yield Mode initialize + Yield Mode reactivate Supply to Aave %1$s supplied to Aave + Withdraw from Aave Automatic Add some %1$s %2$s to cover the network fee for transactions. Unable to cover %s fee diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/Transaction.kt b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/Transaction.kt index 53ec6c23b8..884b8bfdae 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/Transaction.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/Transaction.kt @@ -19,10 +19,12 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider +import androidx.compose.ui.unit.dp import androidx.constraintlayout.compose.ChainStyle import androidx.constraintlayout.compose.ConstraintLayout import androidx.constraintlayout.compose.Dimension import androidx.constraintlayout.compose.Visibility +import androidx.constraintlayout.compose.atLeast import com.tangem.core.ui.R import com.tangem.core.ui.components.CircleShimmer import com.tangem.core.ui.components.RectangleShimmer @@ -94,7 +96,7 @@ fun Transaction(state: TransactionState, isBalanceHidden: Boolean, modifier: Mod bottom.linkTo(subtitleItem.top) start.linkTo(iconItem.end) end.linkTo(amountItem.start) - width = Dimension.fillToConstraints + width = Dimension.fillToConstraints.atLeast(50.dp) }, ) @@ -122,7 +124,6 @@ fun Transaction(state: TransactionState, isBalanceHidden: Boolean, modifier: Mod visibility = state.isGoneIf { amount.isEmpty() } top.linkTo(parent.top) bottom.linkTo(timestampItem.top) - start.linkTo(titleItem.end) end.linkTo(parent.end) width = Dimension.fillToConstraints }, @@ -436,7 +437,7 @@ private class TransactionItemStateProvider : CollectionPreviewParameterProvider< ), TransactionState.Content( txHash = UUID.randomUUID().toString(), - amount = "0.625 USDT", + amount = "0.62521313 USDT", time = "€0.50", status = Status.Confirmed, direction = Direction.OUTGOING, diff --git a/core/ui/src/main/res/drawable/ic_connect_24.xml b/core/ui/src/main/res/drawable/ic_connect_24.xml new file mode 100644 index 0000000000..ebab5fcf69 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_connect_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/core/ui/src/main/res/drawable/ic_disconnect_24.xml b/core/ui/src/main/res/drawable/ic_disconnect_24.xml new file mode 100644 index 0000000000..2baf3ca1c9 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_disconnect_24.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/core/ui/src/main/res/drawable/ic_gear_24.xml b/core/ui/src/main/res/drawable/ic_gear_24.xml new file mode 100644 index 0000000000..ea7f21abb2 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_gear_24.xml @@ -0,0 +1,14 @@ + + + + diff --git a/data/wallet-manager/src/main/java/com/tangem/data/walletmanager/utils/SdkTransactionHistoryItemConverter.kt b/data/wallet-manager/src/main/java/com/tangem/data/walletmanager/utils/SdkTransactionHistoryItemConverter.kt index 40e874e14e..1a677b9cdb 100644 --- a/data/wallet-manager/src/main/java/com/tangem/data/walletmanager/utils/SdkTransactionHistoryItemConverter.kt +++ b/data/wallet-manager/src/main/java/com/tangem/data/walletmanager/utils/SdkTransactionHistoryItemConverter.kt @@ -24,7 +24,7 @@ internal class SdkTransactionHistoryItemConverter( SdkTransactionHistoryItem.TransactionStatus.Failed -> TxInfo.TransactionStatus.Failed SdkTransactionHistoryItem.TransactionStatus.Unconfirmed -> TxInfo.TransactionStatus.Unconfirmed }, - type = typeConverter.convert(value.type), + type = typeConverter.convert(value.type to value.destinationType.toDomain()), amount = requireNotNull(value.amount.value) { "Transaction amount value must not be null" }, ) diff --git a/data/wallet-manager/src/main/java/com/tangem/data/walletmanager/utils/SdkTransactionTypeConverter.kt b/data/wallet-manager/src/main/java/com/tangem/data/walletmanager/utils/SdkTransactionTypeConverter.kt index 7e674c1f59..5e9fe1dc0b 100644 --- a/data/wallet-manager/src/main/java/com/tangem/data/walletmanager/utils/SdkTransactionTypeConverter.kt +++ b/data/wallet-manager/src/main/java/com/tangem/data/walletmanager/utils/SdkTransactionTypeConverter.kt @@ -1,21 +1,27 @@ package com.tangem.data.walletmanager.utils import com.tangem.blockchain.transactionhistory.models.TransactionHistoryItem.TransactionType +import com.tangem.blockchain.yieldsupply.providers.ethereum.yield.EthereumYieldSupplyEnterCallData +import com.tangem.blockchain.yieldsupply.providers.ethereum.yield.EthereumYieldSupplyExitCallData +import com.tangem.blockchain.yieldsupply.providers.ethereum.yield.EthereumYieldSupplyInitTokenCallData +import com.tangem.blockchain.yieldsupply.providers.ethereum.yield.EthereumYieldSupplyReactivateTokenCallData import com.tangem.domain.models.network.TxInfo import com.tangem.domain.walletmanager.model.SmartContractMethod import com.tangem.utils.converter.Converter internal class SdkTransactionTypeConverter( private val smartContractMethods: Map, -) : Converter { +) : Converter, TxInfo.TransactionType> { - override fun convert(value: TransactionType): TxInfo.TransactionType { - return when (value) { + override fun convert(value: Pair): TxInfo.TransactionType { + val (type, destination) = value + + return when (type) { is TransactionType.ContractMethod -> { - getTransactionType(methodName = smartContractMethods[value.id]?.name) + getTransactionType(methodName = smartContractMethods[type.id]?.name, type.callData, destination) } is TransactionType.ContractMethodName -> { - getTransactionType(methodName = value.name) + getTransactionType(methodName = type.name, type.callData, destination) } is TransactionType.Transfer -> { TxInfo.TransactionType.Transfer @@ -27,7 +33,7 @@ internal class SdkTransactionTypeConverter( TxInfo.TransactionType.Staking.Unstake } is TransactionType.TronStakingTransactionType.VoteWitnessContract -> { - TxInfo.TransactionType.Staking.Vote(value.validatorAddress) + TxInfo.TransactionType.Staking.Vote(type.validatorAddress) } is TransactionType.TronStakingTransactionType.WithdrawBalanceContract -> { TxInfo.TransactionType.Staking.ClaimRewards @@ -38,7 +44,12 @@ internal class SdkTransactionTypeConverter( } } - private fun getTransactionType(methodName: String?): TxInfo.TransactionType { + @Suppress("CyclomaticComplexMethod") + private fun getTransactionType( + methodName: String?, + callData: String?, + destination: TxInfo.DestinationType, + ): TxInfo.TransactionType { return when (methodName) { "transfer" -> TxInfo.TransactionType.Transfer "approve" -> TxInfo.TransactionType.Approve @@ -59,11 +70,33 @@ internal class SdkTransactionTypeConverter( "withdrawRewardsPOL", -> TxInfo.TransactionType.Staking.ClaimRewards "redelegate" -> TxInfo.TransactionType.Staking.Restake - "supplyEnter" -> TxInfo.TransactionType.YieldSupply.Enter - "supplyExit" -> TxInfo.TransactionType.YieldSupply.Exit + "yieldSend" -> TxInfo.TransactionType.YieldSupply.Send + "enterProtocolByOwner" -> callData?.let { data -> + TxInfo.TransactionType.YieldSupply.Enter( + EthereumYieldSupplyEnterCallData.decode(data)?.tokenContractAddress.orEmpty(), + ) + } + "withdrawAndDeactivate" -> callData?.let { data -> + TxInfo.TransactionType.YieldSupply.Exit( + EthereumYieldSupplyExitCallData.decode(data)?.tokenContractAddress.orEmpty(), + ) + } + "deployYieldModule" -> TxInfo.TransactionType.YieldSupply.DeployContract( + (destination as? TxInfo.DestinationType.Single)?.addressType?.address.orEmpty(), + ) + "initYieldToken" -> callData?.let { data -> + TxInfo.TransactionType.YieldSupply.InitializeToken( + EthereumYieldSupplyInitTokenCallData.decode(data)?.tokenContractAddress.orEmpty(), + ) + } + "reactivateToken" -> callData?.let { data -> + TxInfo.TransactionType.YieldSupply.ReactivateToken( + EthereumYieldSupplyReactivateTokenCallData.decode(data)?.tokenContractAddress.orEmpty(), + ) + } "supplyTopUp" -> TxInfo.TransactionType.YieldSupply.Topup null -> TxInfo.TransactionType.UnknownOperation else -> TxInfo.TransactionType.Operation(name = methodName.replaceFirstChar { it.titlecase() }) - } + } ?: TxInfo.TransactionType.Operation(name = methodName?.replaceFirstChar { it.titlecase() }.orEmpty()) } } \ No newline at end of file diff --git a/data/wallet-manager/src/main/java/com/tangem/data/walletmanager/utils/TransactionDataToTxHistoryItemConverter.kt b/data/wallet-manager/src/main/java/com/tangem/data/walletmanager/utils/TransactionDataToTxHistoryItemConverter.kt index dae65d3ec7..36016b121b 100644 --- a/data/wallet-manager/src/main/java/com/tangem/data/walletmanager/utils/TransactionDataToTxHistoryItemConverter.kt +++ b/data/wallet-manager/src/main/java/com/tangem/data/walletmanager/utils/TransactionDataToTxHistoryItemConverter.kt @@ -45,7 +45,7 @@ internal class TransactionDataToTxHistoryItemConverter( TransactionStatus.Confirmed -> TxInfo.TransactionStatus.Confirmed TransactionStatus.Unconfirmed -> TxInfo.TransactionStatus.Unconfirmed }, - type = getTransactionType(value.extras), + type = getTransactionType(value), amount = amount, ) } @@ -99,16 +99,25 @@ internal class TransactionDataToTxHistoryItemConverter( ) } - private fun getTransactionType(extras: TransactionExtras?): TxInfo.TransactionType { - return when (extras) { + private fun getTransactionType(transactionData: TransactionData.Uncompiled?): TxInfo.TransactionType { + return when (val extras = transactionData?.extras) { is EthereumTransactionExtras -> { - when (extras.callData) { - is EthereumYieldSupplyDeployCallData, - is EthereumYieldSupplyReactivateTokenCallData, - is EthereumYieldSupplyInitTokenCallData, - is EthereumYieldSupplyEnterCallData, - -> TxInfo.TransactionType.YieldSupply.Enter - is EthereumYieldSupplyExitCallData -> TxInfo.TransactionType.YieldSupply.Exit + when (val callData = extras.callData) { + is EthereumYieldSupplyDeployCallData -> TxInfo.TransactionType.YieldSupply.DeployContract( + transactionData.destinationAddress, + ) + is EthereumYieldSupplyReactivateTokenCallData -> TxInfo.TransactionType.YieldSupply.ReactivateToken( + callData.tokenContractAddress, + ) + is EthereumYieldSupplyInitTokenCallData -> TxInfo.TransactionType.YieldSupply.InitializeToken( + callData.tokenContractAddress, + ) + is EthereumYieldSupplyEnterCallData -> TxInfo.TransactionType.YieldSupply.Enter( + callData.tokenContractAddress, + ) + is EthereumYieldSupplyExitCallData -> TxInfo.TransactionType.YieldSupply.Exit( + callData.tokenContractAddress, + ) is ApprovalERC20TokenCallData -> TxInfo.TransactionType.Approve else -> TxInfo.TransactionType.Transfer } diff --git a/data/yield-supply/src/main/java/com/tangem/data/yield/supply/DefaultYieldSupplyRepository.kt b/data/yield-supply/src/main/java/com/tangem/data/yield/supply/DefaultYieldSupplyRepository.kt index e652292644..384cb82f4b 100644 --- a/data/yield-supply/src/main/java/com/tangem/data/yield/supply/DefaultYieldSupplyRepository.kt +++ b/data/yield-supply/src/main/java/com/tangem/data/yield/supply/DefaultYieldSupplyRepository.kt @@ -174,10 +174,10 @@ internal class DefaultYieldSupplyRepository( null } - private fun Set.hasYieldEnterTransactions(yieldAddress: String) = any { - it.type == TxInfo.TransactionType.YieldSupply.Enter || - it.type == TxInfo.TransactionType.Approve && - (it.interactionAddressType as? TxInfo.InteractionAddressType.Contract)?.address == yieldAddress + private fun Set.hasYieldEnterTransactions(yieldAddress: String) = any { txInfo -> + txInfo.type is TxInfo.TransactionType.YieldSupply.Enter || + txInfo.type == TxInfo.TransactionType.Approve && + (txInfo.interactionAddressType as? TxInfo.InteractionAddressType.Contract)?.address == yieldAddress } private fun Set.hasYieldExitTransactions() = any { diff --git a/domain/legacy/src/main/assets/contract_methods.json b/domain/legacy/src/main/assets/contract_methods.json index 5ca94cd491..fe538d3526 100644 --- a/domain/legacy/src/main/assets/contract_methods.json +++ b/domain/legacy/src/main/assets/contract_methods.json @@ -205,32 +205,32 @@ "0xcbeda14c": { "info": "yieldModule", "source": "https://github.com/tangem/tangem-yield-module-contracts/", - "name": "supply" + "name": "deployYieldModule" }, "0x79be55f7": { "info": "yieldModule", "source": "https://github.com/tangem/tangem-yield-module-contracts/", - "name": "supplyEnter" + "name": "enterProtocolByOwner" }, "0xc65e6dcf": { "info": "yieldModule", "source": "https://github.com/tangem/tangem-yield-module-contracts/", - "name": "supplyExit" + "name": "withdrawAndDeactivate" }, "0xebd4b81c": { "info": "yieldModule", "source": "https://github.com/tangem/tangem-yield-module-contracts/", - "name": "supply" + "name": "initYieldToken" }, "0xc478e956": { "info": "yieldModule", "source": "https://github.com/tangem/tangem-yield-module-contracts/", - "name": "supply" + "name": "reactivateToken" }, "0x0779afe6": { "info": "yieldModule", "source": "https://github.com/tangem/tangem-yield-module-contracts/", - "name": "transfer" + "name": "yieldSend" }, "0xb9de6a93": { "info": "yieldModule", diff --git a/domain/models/src/main/kotlin/com/tangem/domain/models/network/TxInfo.kt b/domain/models/src/main/kotlin/com/tangem/domain/models/network/TxInfo.kt index 65467f7460..d8fd074625 100644 --- a/domain/models/src/main/kotlin/com/tangem/domain/models/network/TxInfo.kt +++ b/domain/models/src/main/kotlin/com/tangem/domain/models/network/TxInfo.kt @@ -110,14 +110,32 @@ data class TxInfo( @Serializable sealed interface YieldSupply : TransactionType { - @Serializable - data object Enter : YieldSupply + val address: String? @Serializable - data object Exit : YieldSupply + data class Enter(override val address: String) : YieldSupply @Serializable - data object Topup : YieldSupply + data class Exit(override val address: String) : YieldSupply + + @Serializable + data object Topup : YieldSupply { + override val address: String? = null + } + + @Serializable + data object Send : YieldSupply { + override val address: String? = null + } + + @Serializable + data class DeployContract(override val address: String) : YieldSupply + + @Serializable + data class ReactivateToken(override val address: String) : YieldSupply + + @Serializable + data class InitializeToken(override val address: String) : YieldSupply } @Serializable diff --git a/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/converter/TxHistoryItemToTransactionStateConverter.kt b/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/converter/TxHistoryItemToTransactionStateConverter.kt index 0fc75e0d67..bc5f160b47 100644 --- a/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/converter/TxHistoryItemToTransactionStateConverter.kt +++ b/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/converter/TxHistoryItemToTransactionStateConverter.kt @@ -41,7 +41,9 @@ internal class TxHistoryItemToTransactionStateConverter( R.drawable.ic_close_24 } else { when (type) { - is TransactionType.Approve -> R.drawable.ic_doc_24 + is TransactionType.YieldSupply.DeployContract, + is TransactionType.Approve, + -> R.drawable.ic_doc_24 is TransactionType.Staking.Stake, is TransactionType.Staking.Vote, is TransactionType.Staking.Restake, @@ -51,11 +53,16 @@ internal class TxHistoryItemToTransactionStateConverter( is TransactionType.Staking.Unstake, is TransactionType.Staking.Withdraw, -> R.drawable.ic_transaction_history_unstaking_24 + is TransactionType.YieldSupply.Enter -> R.drawable.ic_connect_24 + is TransactionType.YieldSupply.InitializeToken -> R.drawable.ic_gear_24 + is TransactionType.YieldSupply.ReactivateToken -> R.drawable.ic_refresh_24 + is TransactionType.YieldSupply.Exit -> R.drawable.ic_disconnect_24 is TransactionType.Operation, is TransactionType.Swap, is TransactionType.Transfer, - is TransactionType.YieldSupply, is TransactionType.UnknownOperation, + TransactionType.YieldSupply.Send, + TransactionType.YieldSupply.Topup, -> if (isOutgoing) R.drawable.ic_arrow_up_24 else R.drawable.ic_arrow_down_24 } } @@ -66,32 +73,76 @@ internal class TxHistoryItemToTransactionStateConverter( is TransactionType.Operation -> stringReference(type.name) is TransactionType.Swap -> resourceReference(R.string.common_swap) is TransactionType.Transfer -> resourceReference(R.string.common_transfer) - is TransactionType.YieldSupply.Enter -> resourceReference(R.string.yield_module_transaction_enter) - is TransactionType.YieldSupply.Exit -> resourceReference(R.string.yield_module_transaction_exit) - is TransactionType.YieldSupply.Topup -> resourceReference(R.string.yield_module_transaction_topup) is TransactionType.Staking.Stake -> resourceReference(R.string.common_stake) is TransactionType.Staking.Unstake -> resourceReference(R.string.common_unstake) is TransactionType.Staking.Vote -> resourceReference(R.string.staking_vote) is TransactionType.Staking.ClaimRewards -> resourceReference(R.string.common_claim_rewards) is TransactionType.Staking.Withdraw -> resourceReference(R.string.staking_withdraw) is TransactionType.Staking.Restake -> resourceReference(R.string.staking_restake) + is TransactionType.YieldSupply -> when (type) { + is TransactionType.YieldSupply.Enter -> resourceReference(R.string.yield_module_transaction_enter) + is TransactionType.YieldSupply.Exit -> resourceReference(R.string.yield_module_transaction_exit) + TransactionType.YieldSupply.Topup -> resourceReference(R.string.yield_module_transaction_topup) + TransactionType.YieldSupply.Send -> if (isOutgoing) { + resourceReference(R.string.common_transfer) + } else { + resourceReference(R.string.yield_module_transaction_withdraw) + } + is TransactionType.YieldSupply.DeployContract -> resourceReference( + R.string + .yield_module_transaction_deploy_contract, + ) + is TransactionType.YieldSupply.InitializeToken -> resourceReference( + R.string + .yield_module_transaction_initialize, + ) + is TransactionType.YieldSupply.ReactivateToken -> resourceReference( + R.string + .yield_module_transaction_reactivate, + ) + } is TransactionType.UnknownOperation -> resourceReference(R.string.transaction_history_operation) } private fun TxInfo.extractSubtitle(): TextReference { - return when (this.type) { - is TransactionType.YieldSupply.Enter -> { - val amount = amount.format { crypto(symbol = currency.symbol, decimals = currency.decimals) } - resourceReference(R.string.yield_module_transaction_enter_subtitle, wrappedList(amount)) - } - is TransactionType.YieldSupply.Topup, - -> { - val amount = amount.format { crypto(symbol = currency.symbol, decimals = currency.decimals) } - resourceReference(R.string.yield_module_transaction_topup_subtitle, wrappedList(amount)) - } - is TransactionType.YieldSupply.Exit -> { - val amount = amount.format { crypto(symbol = currency.symbol, decimals = currency.decimals) } - resourceReference(R.string.yield_module_transaction_exit_subtitle, wrappedList(amount)) + return when (val type = this.type) { + is TransactionType.YieldSupply -> if (currency is CryptoCurrency.Coin) { + if (type == TransactionType.YieldSupply.Send) { + extractSubtitleByAddressType() + } else { + resourceReference( + R.string.transaction_history_transaction_for_address, + wrappedList(type.address?.toBriefAddressFormat().orEmpty()), + ) + } + } else { + when (type) { + is TransactionType.YieldSupply.Enter -> { + val amount = amount.format { crypto(symbol = currency.symbol, decimals = currency.decimals) } + resourceReference(R.string.yield_module_transaction_enter_subtitle, wrappedList(amount)) + } + TransactionType.YieldSupply.Topup -> { + val amount = amount.format { crypto(symbol = currency.symbol, decimals = currency.decimals) } + resourceReference(R.string.yield_module_transaction_topup_subtitle, wrappedList(amount)) + } + is TransactionType.YieldSupply.Exit -> { + val amount = amount.format { crypto(symbol = currency.symbol, decimals = currency.decimals) } + resourceReference(R.string.yield_module_transaction_exit_subtitle, wrappedList(amount)) + } + TransactionType.YieldSupply.Send -> { + if (isOutgoing) { + extractSubtitleByAddressType() + } else { + val amount = + amount.format { crypto(symbol = currency.symbol, decimals = currency.decimals) } + resourceReference( + R.string.yield_module_transaction_exit_subtitle, + wrappedList(amount), + ) + } + } + else -> extractSubtitleByAddressType() + } } else -> extractSubtitleByAddressType() } @@ -133,14 +184,21 @@ internal class TxHistoryItemToTransactionStateConverter( @Suppress("ComplexCondition") private fun TxInfo.getAmount(): String { - if (type is TransactionType.Staking.Vote || - type == TransactionType.Staking.ClaimRewards || - type == TransactionType.Staking.Withdraw || - type == TransactionType.YieldSupply.Enter || - type == TransactionType.YieldSupply.Exit || - type == TransactionType.YieldSupply.Topup - ) { - return "" + when (type) { + is TransactionType.Staking.Vote, + TransactionType.Staking.ClaimRewards, + TransactionType.Staking.Withdraw, + -> return "" + + is TransactionType.YieldSupply -> if (currency is CryptoCurrency.Token) { + when (type) { + TransactionType.YieldSupply.Send -> if (!isOutgoing) { + return "" + } + else -> return "" + } + } + else -> Unit } val prefix = when { status == TxInfo.TransactionStatus.Failed -> "" diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTxHistoryCountErrorTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTxHistoryCountErrorTransformer.kt index 3b5dfff91c..fa92bb7623 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTxHistoryCountErrorTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTxHistoryCountErrorTransformer.kt @@ -3,6 +3,7 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers import com.tangem.blockchain.common.Blockchain import com.tangem.core.ui.components.transactions.state.TxHistoryState import com.tangem.domain.card.common.util.cardTypesResolver +import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.network.TxInfo import com.tangem.domain.txhistory.models.TxHistoryStateError import com.tangem.domain.models.wallet.UserWallet @@ -17,6 +18,7 @@ internal class SetTxHistoryCountErrorTransformer( private val error: TxHistoryStateError, private val pendingTransactions: Set, private val clickIntents: WalletClickIntents, + private val currency: CryptoCurrency, ) : WalletStateTransformer(userWallet.walletId) { private val txHistoryItemConverter by lazy { @@ -26,6 +28,7 @@ internal class SetTxHistoryCountErrorTransformer( } TxHistoryItemStateConverter( + currency = currency, symbol = blockchain.currency, decimals = blockchain.decimals(), clickIntents = clickIntents, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TxHistoryItemStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TxHistoryItemStateConverter.kt index db435626e8..a30612c67e 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TxHistoryItemStateConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TxHistoryItemStateConverter.kt @@ -9,6 +9,7 @@ import com.tangem.core.ui.extensions.wrappedList import com.tangem.core.ui.format.bigdecimal.crypto import com.tangem.core.ui.format.bigdecimal.format import com.tangem.core.ui.utils.toTimeFormat +import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.network.TxInfo import com.tangem.domain.models.network.TxInfo.TransactionStatus import com.tangem.domain.models.network.TxInfo.TransactionType @@ -20,6 +21,7 @@ import com.tangem.utils.converter.Converter import com.tangem.utils.toBriefAddressFormat internal class TxHistoryItemStateConverter( + private val currency: CryptoCurrency, private val symbol: String, private val decimals: Int, private val clickIntents: WalletClickIntents, @@ -49,7 +51,9 @@ internal class TxHistoryItemStateConverter( R.drawable.ic_close_24 } else { when (type) { - is TransactionType.Approve -> R.drawable.ic_doc_24 + is TransactionType.YieldSupply.DeployContract, + is TransactionType.Approve, + -> R.drawable.ic_doc_24 is TransactionType.Staking.Stake, is TransactionType.Staking.Vote, is TransactionType.Staking.Restake, @@ -59,11 +63,16 @@ internal class TxHistoryItemStateConverter( is TransactionType.Staking.Unstake, is TransactionType.Staking.Withdraw, -> R.drawable.ic_transaction_history_unstaking_24 + is TransactionType.YieldSupply.Enter -> R.drawable.ic_connect_24 + is TransactionType.YieldSupply.InitializeToken -> R.drawable.ic_gear_24 + is TransactionType.YieldSupply.ReactivateToken -> R.drawable.ic_refresh_24 + is TransactionType.YieldSupply.Exit -> R.drawable.ic_disconnect_24 is TransactionType.Operation, is TransactionType.Swap, is TransactionType.Transfer, - is TransactionType.YieldSupply, is TransactionType.UnknownOperation, + TransactionType.YieldSupply.Send, + TransactionType.YieldSupply.Topup, -> if (isOutgoing) R.drawable.ic_arrow_up_24 else R.drawable.ic_arrow_down_24 } } @@ -74,32 +83,81 @@ internal class TxHistoryItemStateConverter( is TransactionType.Operation -> stringReference(type.name) is TransactionType.Swap -> resourceReference(R.string.common_swap) is TransactionType.Transfer -> resourceReference(R.string.common_transfer) - is TransactionType.YieldSupply.Enter -> resourceReference(R.string.yield_module_transaction_enter) - is TransactionType.YieldSupply.Exit -> resourceReference(R.string.yield_module_transaction_exit) - is TransactionType.YieldSupply.Topup -> resourceReference(R.string.yield_module_transaction_topup) is TransactionType.Staking.Stake -> resourceReference(R.string.common_stake) is TransactionType.Staking.Unstake -> resourceReference(R.string.common_unstake) is TransactionType.Staking.Vote -> resourceReference(R.string.staking_vote) is TransactionType.Staking.ClaimRewards -> resourceReference(R.string.common_claim_rewards) is TransactionType.Staking.Withdraw -> resourceReference(R.string.staking_withdraw) is TransactionType.Staking.Restake -> resourceReference(R.string.staking_restake) + is TransactionType.YieldSupply -> when (type) { + is TransactionType.YieldSupply.Enter -> resourceReference(R.string.yield_module_transaction_enter) + is TransactionType.YieldSupply.Exit -> resourceReference(R.string.yield_module_transaction_exit) + TransactionType.YieldSupply.Topup -> resourceReference(R.string.yield_module_transaction_topup) + TransactionType.YieldSupply.Send -> if (isOutgoing) { + resourceReference(R.string.common_transfer) + } else { + resourceReference(R.string.yield_module_transaction_withdraw) + } + is TransactionType.YieldSupply.DeployContract -> resourceReference( + R.string + .yield_module_transaction_deploy_contract, + ) + is TransactionType.YieldSupply.InitializeToken -> resourceReference( + R.string + .yield_module_transaction_initialize, + ) + is TransactionType.YieldSupply.ReactivateToken -> resourceReference( + R.string + .yield_module_transaction_reactivate, + ) + } is TransactionType.UnknownOperation -> resourceReference(R.string.transaction_history_operation) } private fun TxInfo.extractSubtitle(): TextReference { - return when (this.type) { - is TransactionType.YieldSupply.Enter -> { - val amount = amount.format { crypto(symbol = symbol, decimals = decimals) } - resourceReference(R.string.yield_module_transaction_enter_subtitle, wrappedList(amount)) - } - is TransactionType.YieldSupply.Topup, - -> { - val amount = amount.format { crypto(symbol = symbol, decimals = decimals) } - resourceReference(R.string.yield_module_transaction_topup_subtitle, wrappedList(amount)) - } - is TransactionType.YieldSupply.Exit -> { - val amount = amount.format { crypto(symbol = symbol, decimals = decimals) } - resourceReference(R.string.yield_module_transaction_exit_subtitle, wrappedList(amount)) + return when (val type = this.type) { + is TransactionType.YieldSupply -> if (currency is CryptoCurrency.Coin) { + resourceReference( + R.string.transaction_history_transaction_for_address, + wrappedList(type.address?.toBriefAddressFormat().orEmpty()), + ) + } else { + when (type) { + is TransactionType.YieldSupply.Enter -> { + val amount = amount.format { crypto(symbol = currency.symbol, decimals = currency.decimals) } + resourceReference( + R.string.yield_module_transaction_enter_subtitle, + wrappedList(amount), + ) + } + TransactionType.YieldSupply.Topup -> { + val amount = amount.format { crypto(symbol = currency.symbol, decimals = currency.decimals) } + resourceReference( + R.string.yield_module_transaction_topup_subtitle, + wrappedList(amount), + ) + } + TransactionType.YieldSupply.Send -> { + if (isOutgoing) { + extractSubtitleByAddressType() + } else { + val amount = + amount.format { crypto(symbol = currency.symbol, decimals = currency.decimals) } + resourceReference( + R.string.yield_module_transaction_exit_subtitle, + wrappedList(amount), + ) + } + } + is TransactionType.YieldSupply.Exit -> { + val amount = amount.format { crypto(symbol = currency.symbol, decimals = currency.decimals) } + resourceReference( + R.string.yield_module_transaction_exit_subtitle, + wrappedList(amount), + ) + } + else -> extractSubtitleByAddressType() + } } else -> extractSubtitleByAddressType() } @@ -147,14 +205,18 @@ internal class TxHistoryItemStateConverter( @Suppress("ComplexCondition") private fun TxInfo.getAmount(): String { - if (type is TransactionType.Staking.Vote || - type == TransactionType.Staking.ClaimRewards || - type == TransactionType.Staking.Withdraw || - type == TransactionType.YieldSupply.Enter || - type == TransactionType.YieldSupply.Exit || - type == TransactionType.YieldSupply.Topup - ) { - return "" + when (type) { + is TransactionType.Staking.Vote, + TransactionType.Staking.ClaimRewards, + TransactionType.Staking.Withdraw, + -> return "" + + is TransactionType.YieldSupply -> { + if (currency is CryptoCurrency.Token && type == TransactionType.YieldSupply.Send && !isOutgoing) { + return "" + } + } + else -> Unit } val prefix = when { status == TransactionStatus.Failed -> "" diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/TxHistorySubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/TxHistorySubscriber.kt index b9c65f6340..bef7b6defb 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/TxHistorySubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/TxHistorySubscriber.kt @@ -5,6 +5,7 @@ import androidx.paging.cachedIn import androidx.paging.map import arrow.core.Either import com.tangem.domain.card.common.util.cardTypesResolver +import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.network.TxInfo import com.tangem.domain.models.wallet.UserWallet @@ -58,7 +59,7 @@ internal class TxHistorySubscriber( refresh = isRefresh, ).map { it.cachedIn(coroutineScope) } - setLoadedTxHistoryState(maybeTxHistoryItems) + setLoadedTxHistoryState(maybeTxHistoryItems, status.currency) } } } @@ -67,18 +68,19 @@ internal class TxHistorySubscriber( private fun setLoadingTxHistoryState(maybeTxHistoryItemCount: MaybeTxHistoryCount, status: CryptoCurrencyStatus) { stateHolder.update( maybeTxHistoryItemCount.fold( - ifLeft = { + ifLeft = { error -> SetTxHistoryCountErrorTransformer( userWallet = userWallet, - error = it, + error = error, pendingTransactions = status.value.pendingTransactions, + currency = status.currency, clickIntents = clickIntents, ) }, - ifRight = { + ifRight = { txCount -> SetTxHistoryCountTransformer( userWalletId = userWallet.walletId, - transactionsCount = it, + transactionsCount = txCount, clickIntents = clickIntents, ) }, @@ -86,7 +88,7 @@ internal class TxHistorySubscriber( ) } - private fun setLoadedTxHistoryState(maybeTxHistoryItems: MaybeTxHistoryItems) { + private fun setLoadedTxHistoryState(maybeTxHistoryItems: MaybeTxHistoryItems, currency: CryptoCurrency) { stateHolder.update( maybeTxHistoryItems.fold( ifLeft = { @@ -102,6 +104,7 @@ internal class TxHistorySubscriber( symbol = blockchain.currency, decimals = blockchain.decimals(), clickIntents = clickIntents, + currency = currency, ) SetTxHistoryItemsTransformer( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/TxHistorySubscriberV2.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/TxHistorySubscriberV2.kt index 23e7c78681..78c388f87f 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/TxHistorySubscriberV2.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/TxHistorySubscriberV2.kt @@ -5,6 +5,7 @@ import androidx.paging.cachedIn import androidx.paging.map import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier import com.tangem.domain.card.common.util.cardTypesResolver +import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.network.TxInfo import com.tangem.domain.models.wallet.UserWallet @@ -51,7 +52,7 @@ internal class TxHistorySubscriberV2( refresh = isRefresh, ).map { it.cachedIn(coroutineScope) } - setLoadedTxHistoryState(maybeTxHistoryItems) + setLoadedTxHistoryState(maybeTxHistoryItems, currency = status.currency) } } } @@ -60,18 +61,19 @@ internal class TxHistorySubscriberV2( private fun setLoadingTxHistoryState(maybeTxHistoryItemCount: MaybeTxHistoryCount, status: CryptoCurrencyStatus) { stateController.update( maybeTxHistoryItemCount.fold( - ifLeft = { + ifLeft = { error -> SetTxHistoryCountErrorTransformer( userWallet = userWallet, - error = it, + error = error, pendingTransactions = status.value.pendingTransactions, clickIntents = clickIntents, + currency = status.currency, ) }, - ifRight = { + ifRight = { txCount -> SetTxHistoryCountTransformer( userWalletId = userWallet.walletId, - transactionsCount = it, + transactionsCount = txCount, clickIntents = clickIntents, ) }, @@ -79,7 +81,7 @@ internal class TxHistorySubscriberV2( ) } - private fun setLoadedTxHistoryState(maybeTxHistoryItems: MaybeTxHistoryItems) { + private fun setLoadedTxHistoryState(maybeTxHistoryItems: MaybeTxHistoryItems, currency: CryptoCurrency) { stateController.update( maybeTxHistoryItems.fold( ifLeft = { @@ -92,6 +94,7 @@ internal class TxHistorySubscriberV2( ifRight = { itemsFlow -> val blockchain = userWallet.scanResponse.cardTypesResolver.getBlockchain() val itemConverter = TxHistoryItemStateConverter( + currency = currency, symbol = blockchain.currency, decimals = blockchain.decimals(), clickIntents = clickIntents, diff --git a/gradle/tangem_dependencies.toml b/gradle/tangem_dependencies.toml index 07be659f64..cbabfbf53b 100644 --- a/gradle/tangem_dependencies.toml +++ b/gradle/tangem_dependencies.toml @@ -5,7 +5,7 @@ # https://github.com/tangem/tangem-sdk-android/ # https://github.com/tangem/vico -tangemBlockchainSdk = "releases-5.31-1314" +tangemBlockchainSdk = "releases-5.31.1-1316" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "releases-5.31-569" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From 76c129f20552e8523f41913c63ef2bbb093e5a19 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 8 Dec 2025 15:12:38 +0500 Subject: [PATCH 2/7] Updated on 2026-08-14 --- .../usecase/YieldSupplyGetRewardsBalanceUseCase.kt | 4 ++-- .../usecase/YieldSupplyGetRewardsBalanceUseCaseTest.kt | 4 ++-- .../tokendetails/model/TokenDetailsModel.kt | 10 ++-------- .../yield/supply/impl/main/model/YieldSupplyModel.kt | 6 ++++++ 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetRewardsBalanceUseCase.kt b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetRewardsBalanceUseCase.kt index 5e1b8676a9..52c1434981 100644 --- a/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetRewardsBalanceUseCase.kt +++ b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetRewardsBalanceUseCase.kt @@ -92,8 +92,8 @@ class YieldSupplyGetRewardsBalanceUseCase( } private companion object { - const val TICK_MILLIS: Long = 300 - private val TICK_SECONDS_BD = BigDecimal("0.3") + const val TICK_MILLIS: Long = 800 + private val TICK_SECONDS_BD = BigDecimal("0.8") private val SECONDS_PER_YEAR_BD = BigDecimal("31536000") // 365 * 24 * 60 * 60 private val HUNDRED_BD = BigDecimal("100") private const val SCALE = 18 diff --git a/domain/yield-supply/src/test/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetRewardsBalanceUseCaseTest.kt b/domain/yield-supply/src/test/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetRewardsBalanceUseCaseTest.kt index 254c8c6d4a..3bdd2f446e 100644 --- a/domain/yield-supply/src/test/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetRewardsBalanceUseCaseTest.kt +++ b/domain/yield-supply/src/test/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetRewardsBalanceUseCaseTest.kt @@ -288,12 +288,12 @@ class YieldSupplyGetRewardsBalanceUseCaseTest { private companion object { private const val SCALE = 18 - private val TICK_SECONDS_BD = BigDecimal("0.3") + private val TICK_SECONDS_BD = BigDecimal("0.8") private val SECONDS_PER_YEAR_BD = BigDecimal("31536000") private val HUNDRED_BD = BigDecimal("100") private const val MIN_DECIMALS = 3 - private const val MAX_DECIMALS = 8 + private const val MAX_DECIMALS = 12 private val LN_10 = ln(10.0) private const val EPSILON = 1e-18 diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt index 809fa4ea3f..3ef6e97629 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt @@ -1251,14 +1251,8 @@ internal class TokenDetailsModel @Inject constructor( } private fun handleNavigationParam() { - when (val action = params.navigationAction) { - is NavigationAction.Staking -> openStaking() - is NavigationAction.YieldSupply -> if (action.isActive) { - modelScope.launch(dispatchers.default) { - fetchCurrencyStatusUseCase(userWalletId = userWalletId, id = cryptoCurrency.id) - } - } - else -> Unit + if (params.navigationAction is NavigationAction.Staking) { + openStaking() } } diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/main/model/YieldSupplyModel.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/main/model/YieldSupplyModel.kt index 95f483022e..c8f25dea1a 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/main/model/YieldSupplyModel.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/main/model/YieldSupplyModel.kt @@ -89,6 +89,12 @@ internal class YieldSupplyModel @Inject constructor( val isAvailable = yieldSupplyIsAvailableUseCase(params.userWalletId, params.cryptoCurrency) if (isAvailable) { subscribeOnCurrencyStatusUpdates() + singleNetworkStatusFetcher( + params = SingleNetworkStatusFetcher.Params( + userWalletId = userWallet.walletId, + network = cryptoCurrency.network, + ), + ) } } } From a13a54accda0cff8cd488d2ecc54e583e8620244 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 10 Dec 2025 13:24:16 +0500 Subject: [PATCH 3/7] Updated on 2026-08-14 --- .../tap/di/domain/YieldSupplyDomainModule.kt | 20 ++ .../ui/tokens/TokenItemStateConverter.kt | 50 ++++- .../local/preferences/PreferencesKeys.kt | 4 + core/res/src/main/res/values-ja/strings.xml | 5 + core/res/src/main/res/values/strings.xml | 6 +- .../core/ui/components/token/TokenItem.kt | 54 ++++- .../token/internal/YieldSupplyPromoBanner.kt | 110 +++++++++ .../components/token/state/TokenItemState.kt | 19 ++ .../core/ui/test/TokenElementsTestTags.kt | 1 + .../main/res/drawable/ic_rectangle_bottom.xml | 9 + .../tangem/utils/coroutines/CoroutineExt.kt | 8 +- data/yield-supply/build.gradle.kts | 4 + .../supply/DefaultYieldSupplyRepository.kt | 21 +- .../yield/supply/di/YieldSupplyDataModule.kt | 3 + .../yield/supply/YieldSupplyRepository.kt | 4 + .../usecase/YieldSupplyApyFlowUseCase.kt | 5 +- ...ieldSupplyGetShouldShowMainPromoUseCase.kt | 13 ++ ...ieldSupplySetShouldShowMainPromoUseCase.kt | 12 + .../intents/WalletContentClickIntents.kt | 10 + .../implementors/MultiWalletContentLoader.kt | 3 + .../MultiWalletContentLoaderFactory.kt | 3 + .../SingleWalletWithTokenContentLoader.kt | 3 + ...ngleWalletWithTokenContentLoaderFactory.kt | 3 + .../transformers/SetTokenListTransformer.kt | 5 +- .../converter/TokenListStateConverter.kt | 12 +- .../YieldSupplyPromoBannerKeyConverter.kt | 48 ++++ .../subscribers/AccountListSubscriber.kt | 14 +- .../subscribers/BasicAccountListSubscriber.kt | 21 +- .../subscribers/BasicTokenListSubscriber.kt | 16 +- .../MultiWalletTokenListSubscriber.kt | 3 + .../SingleWalletWithTokenListSubscriber.kt | 3 + .../YieldSupplyPromoBannerKeyConverterTest.kt | 208 ++++++++++++++++++ 32 files changed, 665 insertions(+), 35 deletions(-) create mode 100644 core/ui/src/main/java/com/tangem/core/ui/components/token/internal/YieldSupplyPromoBanner.kt create mode 100644 core/ui/src/main/res/drawable/ic_rectangle_bottom.xml create mode 100644 domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetShouldShowMainPromoUseCase.kt create mode 100644 domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplySetShouldShowMainPromoUseCase.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/YieldSupplyPromoBannerKeyConverter.kt create mode 100644 features/wallet/impl/src/test/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/YieldSupplyPromoBannerKeyConverterTest.kt diff --git a/app/src/main/java/com/tangem/tap/di/domain/YieldSupplyDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/YieldSupplyDomainModule.kt index a151bfa469..28f00e7e98 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/YieldSupplyDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/YieldSupplyDomainModule.kt @@ -199,4 +199,24 @@ internal object YieldSupplyDomainModule { yieldSupplyRepository = yieldSupplyRepository, ) } + + @Provides + @Singleton + fun provideYieldSupplyGetShouldShowMainPromoUseCase( + yieldSupplyRepository: YieldSupplyRepository, + ): YieldSupplyGetShouldShowMainPromoUseCase { + return YieldSupplyGetShouldShowMainPromoUseCase( + yieldSupplyRepository = yieldSupplyRepository, + ) + } + + @Provides + @Singleton + fun provideYieldSupplySetShouldShowMainPromoUseCase( + yieldSupplyRepository: YieldSupplyRepository, + ): YieldSupplySetShouldShowMainPromoUseCase { + return YieldSupplySetShouldShowMainPromoUseCase( + yieldSupplyRepository = yieldSupplyRepository, + ) + } } \ No newline at end of file diff --git a/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenItemStateConverter.kt b/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenItemStateConverter.kt index 9817a2f88a..516a15539c 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenItemStateConverter.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenItemStateConverter.kt @@ -43,12 +43,14 @@ import java.math.BigDecimal */ class TokenItemStateConverter( private val appCurrency: AppCurrency, - private val yieldModuleApyMap: Map = emptyMap(), + private val yieldModuleApyMap: Map = emptyMap(), private val stakingApyMap: Map> = emptyMap(), + private val yieldSupplyPromoBannerKey: String? = null, private val iconStateProvider: (CryptoCurrencyStatus) -> CurrencyIconState = { CryptoCurrencyToIconStateConverter().convert(it) }, private val onApyLabelClick: ((CryptoCurrencyStatus, String) -> Unit)? = null, + private val onYieldPromoCloseClick: (() -> Unit)? = null, private val titleStateProvider: (CryptoCurrencyStatus) -> TokenItemState.TitleState = { currencyStatus -> createTitleState( currencyStatus = currencyStatus, @@ -66,6 +68,15 @@ class TokenItemStateConverter( private val fiatAmountStateProvider: (CryptoCurrencyStatus) -> TokenItemState.FiatAmountState? = { createFiatAmountState(status = it, appCurrency = appCurrency) }, + private val promoBannerProvider: (CryptoCurrencyStatus) -> TokenItemState.PromoBannerState = { status -> + createPromoBannerState( + status = status, + yieldModuleApyMap = yieldModuleApyMap, + yieldSupplyPromoBannerKey = yieldSupplyPromoBannerKey, + onApyLabelClick = onApyLabelClick, + onYieldPromoCloseClick = onYieldPromoCloseClick, + ) + }, private val onItemClick: ((TokenItemState, CryptoCurrencyStatus) -> Unit)? = null, private val onItemLongClick: ((TokenItemState, CryptoCurrencyStatus) -> Unit)? = null, ) : Converter { @@ -102,6 +113,7 @@ class TokenItemStateConverter( subtitleState = requireNotNull(subtitleStateProvider(this)), fiatAmountState = requireNotNull(fiatAmountStateProvider(this)), subtitle2State = requireNotNull(subtitle2StateProvider(this)), + promoBannerState = promoBannerProvider(this), onItemClick = onItemClick?.let { onItemClick -> { onItemClick(it, this) } }, @@ -164,7 +176,7 @@ class TokenItemStateConverter( private fun createTitleState( currencyStatus: CryptoCurrencyStatus, - yieldModuleApyMap: Map, + yieldModuleApyMap: Map, stakingApyMap: Map>, onApyLabelClick: ((CryptoCurrencyStatus, String) -> Unit)?, ): TokenItemState.TitleState { @@ -204,7 +216,7 @@ class TokenItemStateConverter( // polygon-pos_0xc2132d05d31c914a87c6611c10748aeb04b58e8f private fun resolveEarnApy( cryptoCurrencyStatus: CryptoCurrencyStatus, - yieldModuleApyMap: Map, + yieldModuleApyMap: Map, stakingApyMap: Map>, ): EarnApyInfo? { val token = cryptoCurrencyStatus.currency as? CryptoCurrency.Token @@ -223,7 +235,7 @@ class TokenItemStateConverter( wrappedList(yieldSupplyApy), ), isActive = isActive, - apy = yieldSupplyApy, + apy = yieldSupplyApy.toString(), ) } } @@ -379,6 +391,36 @@ class TokenItemStateConverter( } } + private fun createPromoBannerState( + status: CryptoCurrencyStatus, + yieldModuleApyMap: Map, + yieldSupplyPromoBannerKey: String?, + onApyLabelClick: ((CryptoCurrencyStatus, String) -> Unit)?, + onYieldPromoCloseClick: (() -> Unit)?, + ): TokenItemState.PromoBannerState { + val token = status.currency as? CryptoCurrency.Token ?: return TokenItemState.PromoBannerState.Empty + if (yieldSupplyPromoBannerKey == null || yieldSupplyPromoBannerKey != token.yieldSupplyKey() || + yieldModuleApyMap[token.yieldSupplyKey()] == null + ) { + return TokenItemState.PromoBannerState.Empty + } + val yieldSupplyApy = + yieldModuleApyMap[token.yieldSupplyKey()] ?: return TokenItemState.PromoBannerState.Empty + + return TokenItemState.PromoBannerState.Content( + title = resourceReference( + R.string.yield_module_main_screen_promo_banner_message, + wrappedList(yieldSupplyApy), + ), + onPromoBannerClick = { + onApyLabelClick?.invoke(status, yieldSupplyApy.toString()) + }, + onCloseClick = { + onYieldPromoCloseClick?.invoke() + }, + ) + } + private fun CryptoCurrencyStatus.getCryptoPriceState(appCurrency: AppCurrency): TokenItemState.SubtitleState { val fiatRate = value.fiatRate val priceChange = value.priceChange diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt index 2c128557a6..f3f7fbf07d 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt @@ -122,6 +122,10 @@ object PreferencesKeys { val YIELD_SUPPLY_WARNINGS_STATES_KEY by lazy { stringPreferencesKey(name = "yieldSupplyWarningsStates") } + val YIELD_SUPPLY_SHOULD_SHOW_MAIN_PROMO_KEY by lazy { + booleanPreferencesKey(name = "yieldSupplyShouldShowMainPromo") + } + val ACCESS_CODE_SKIPPED_STATES_KEY by lazy { stringPreferencesKey(name = "accessCodeSkippedStates") } // region Notifications diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml index 3979991fb5..849f728af4 100644 --- a/core/res/src/main/res/values-ja/strings.xml +++ b/core/res/src/main/res/values-ja/strings.xml @@ -590,6 +590,7 @@ まずバックアップを完了する まずバックアップを完了する その他の方法 + リカバリーフレーズは、ご自身で安全な場所に保管し、資金を守るために他人には絶対に共有しないでください。 リカバリーフレーズ アクセスコードを使用してウォレットを保護するには、まずバックアップを完了してください。 ウォレットをハードウェアにアップグレードするには、まずバックアップしてください。 @@ -1432,6 +1433,8 @@ カードの発行に失敗しました 技術的なエラーが発生しました。下のボタンをクリックして、もう一度お試しください。 技術的なエラーが発生しました。サポートへお問い合わせください。 + 暗号資産を日常の支払いに使おう。\n\nこれまでにないタイプの決済カード。 + Tangem Payを入手 通常は最大で15分ほどかかります Tangemカードのセットアップ カードを発行しています @@ -1454,6 +1457,7 @@ 技術的な問題を修正しています。後でもう一度お試しください。 サービスは一時的に利用できません 現在サービスに接続できません。後ほどもう一度お試しください。 + Tangem Visaカード Tangem Payは一時的に利用できません Tangem Pay カードまたはリングを使用して、支払いアカウントへのアクセスを復元してください。 @@ -1500,6 +1504,7 @@ 複数のアドレス 現在、このブロックチェーンでは取引履歴はサポートされていません。しかしご心配なく!弊社で対応中です。その間、エクスプローラーで確認することができます。 オペレーション + 対象:%s 送金元: %s 送金先: %s バリデーター: %s diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 1dd7761587..0bbcf90b6a 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -527,7 +527,7 @@ Buying %s Buying %s... Hide this transaction - Once hidden, the transaction status cannot be viewed again. You can simply swipe to dismiss instead. + If you hide this transaction, it won’t appear in the status screen anymore. If you just want to close the status screen and return later, simply swipe it away instead. Hide Transaction Status? This token is not supported. Please choose a different token to swap. %s is not supported @@ -697,11 +697,13 @@ APY %s My portfolio Market + Earn with Tangem To generate addresses for selected networks, you must scan your Tangem Wallet card or ring To add tokens pull this up or tap the search bar This section’s data is sourced from the following networks: %s Unable to load the data… No data + Market Pulse Quick actions Search through the market Result @@ -1985,7 +1987,7 @@ Tangem also takes a 15% service fee on yield generated. Your funds will be automatically supplied to Aave once network fees are lower or your balance meets the minimum required amount. Historical returns - "Enable %1$s%% APY on your balance" + Enable %1$s%% APY on your balance Approval for your token in Yield Mode has been revoked. Open the token to grant permission again. Token approval needed Check your network connection diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/token/TokenItem.kt b/core/ui/src/main/java/com/tangem/core/ui/components/token/TokenItem.kt index 374b6de2e3..fc56519c01 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/token/TokenItem.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/token/TokenItem.kt @@ -3,6 +3,7 @@ package com.tangem.core.ui.components.token import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -18,6 +19,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import androidx.compose.ui.unit.Constraints +import androidx.compose.ui.unit.dp import com.tangem.core.ui.R import com.tangem.core.ui.components.audits.AuditLabelUM import com.tangem.core.ui.components.currency.icon.CurrencyIcon @@ -28,6 +30,7 @@ import com.tangem.core.ui.components.token.internal.* import com.tangem.core.ui.components.token.state.TokenItemState import com.tangem.core.ui.components.token.state.TokenItemState.FiatAmountState import com.tangem.core.ui.components.token.state.TokenItemState.Subtitle2State +import com.tangem.core.ui.components.token.state.TokenItemState.PromoBannerState import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.rememberHapticFeedback import com.tangem.core.ui.extensions.stringReference @@ -44,7 +47,7 @@ private const val TITLE_MIN_WIDTH_COEFFICIENT = 0.3 private const val PRICE_MIN_WIDTH_COEFFICIENT = 0.32 private enum class LayoutId { - ICON, TITLE, FIAT_AMOUNT, CRYPTO_AMOUNT, CRYPTO_PRICE, NON_FIAT_CONTENT + ICON, TITLE, FIAT_AMOUNT, CRYPTO_AMOUNT, CRYPTO_PRICE, NON_FIAT_CONTENT, PROMO_BANNER } /** @@ -109,6 +112,14 @@ fun TokenItem( .testTag(TokenElementsTestTags.TOKEN_ICON), ) + YieldSupplyPromoBanner( + state = state.promoBannerState, + modifier = Modifier + .layoutId(layoutId = LayoutId.PROMO_BANNER) + .testTag(TokenElementsTestTags.TOKEN_YIELD_PROMO_BANNER) + .fillMaxWidth(), + ) + TokenTitle( state = state.titleState, modifier = Modifier @@ -220,6 +231,13 @@ private fun CustomContainer(state: TokenItemState, modifier: Modifier = Modifier val nonFiatContent = measurables.measure(layoutId = LayoutId.NON_FIAT_CONTENT, constraints = constraints) + val promoBanner = when (state.promoBannerState) { + is PromoBannerState.Content -> measurables.measure( + layoutId = LayoutId.PROMO_BANNER, + constraints = constraints, + ) + else -> null + } var firstRowRemainingFreeSpace: Int? = null var secondRowRemainingFreeSpace: Int? = null @@ -283,10 +301,18 @@ private fun CustomContainer(state: TokenItemState, modifier: Modifier = Modifier ) } + val promoBannerHeight = promoBanner?.height ?: 0 + val promoOffset = if (promoBannerHeight > 0) { + promoBannerHeight - 8.dp.roundToPx() + } else { + 0 + } + val layoutHeight = calculateLayoutHeight( state = state, minLayoutHeight = with(density) { dimens.size68.roundToPx() }, layoutPadding = verticalPadding, + promoOffset = promoOffset, title = title, fiatAmount = fiatAmount, cryptoAmount = cryptoAmount, @@ -294,16 +320,22 @@ private fun CustomContainer(state: TokenItemState, modifier: Modifier = Modifier ) layout(width = constraints.maxWidth, height = layoutHeight) { - icon.placeRelative(x = 0, y = (layoutHeight - icon.height).div(other = 2)) + promoBanner?.placeRelative(x = 0, y = 0) + + icon.placeRelative( + x = 0, + y = promoOffset + (layoutHeight - promoOffset - icon.height) + .div(other = 2), + ) title.placeRelative( x = icon.width, - y = when (state) { + y = promoOffset + when (state) { is TokenItemState.NoAddress, is TokenItemState.Unreachable, -> { if (state.subtitleState == null) { - (layoutHeight - title.height).div(other = 2) + (layoutHeight - promoOffset - title.height).div(other = 2) } else { verticalPadding } @@ -314,8 +346,8 @@ private fun CustomContainer(state: TokenItemState, modifier: Modifier = Modifier fiatAmount?.placeRelative( x = layoutWidth - fiatAmount.width, - y = when (state.subtitle2State) { - null -> (layoutHeight - fiatAmount.height).div(other = 2) + y = promoOffset + when (state.subtitle2State) { + null -> (layoutHeight - promoOffset - fiatAmount.height).div(other = 2) else -> verticalPadding }, ) @@ -335,7 +367,7 @@ private fun CustomContainer(state: TokenItemState, modifier: Modifier = Modifier nonFiatContent.placeRelative( x = layoutWidth - nonFiatContent.width, - y = (layoutHeight - nonFiatContent.height).div(other = 2), + y = promoOffset + (layoutHeight - promoOffset - nonFiatContent.height).div(other = 2), ) } } @@ -443,6 +475,7 @@ private fun calculateLayoutHeight( state: TokenItemState, minLayoutHeight: Int, layoutPadding: Int, + promoOffset: Int, title: Placeable, fiatAmount: Placeable?, cryptoAmount: Placeable?, @@ -468,7 +501,7 @@ private fun calculateLayoutHeight( } } - return max(firstColumnHeight, secondColumnHeight).coerceAtLeast(minLayoutHeight) + return (promoOffset + max(firstColumnHeight, secondColumnHeight)).coerceAtLeast(promoOffset + minLayoutHeight) } @Preview(widthDp = 360, showBackground = true) @@ -587,6 +620,11 @@ private class TokenItemStateProvider : CollectionPreviewParameterProvider YieldSupplyPromoBanner(state = state, modifier = modifier) + is PromoBannerState.Empty -> Unit + } +} + +@Composable +internal fun YieldSupplyPromoBanner(state: PromoBannerState.Content, modifier: Modifier = Modifier) { + val bgColor = TangemTheme.colors.control.unchecked + Column(modifier = modifier) { + Row( + modifier = Modifier + .background(color = bgColor, shape = TangemTheme.shapes.roundedCornersXMedium) + .padding(horizontal = 12.dp, vertical = 8.dp) + .clickable(onClick = state.onPromoBannerClick) + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + imageVector = ImageVector.vectorResource(id = R.drawable.ic_analytics_up_24), + contentDescription = null, + tint = TangemTheme.colors.icon.accent, + modifier = Modifier + .padding(end = TangemTheme.dimens.spacing8) + .size(TangemTheme.dimens.size16), + ) + Text( + text = state.title.resolveReference(), + style = TangemTheme.typography.caption1, + color = TangemTheme.colors.text.secondary, + modifier = Modifier + .weight(1f) + .padding(end = TangemTheme.dimens.spacing8), + ) + Icon( + painter = painterResource(id = R.drawable.ic_close_24), + contentDescription = null, + tint = TangemTheme.colors.text.secondary, + modifier = Modifier + .size(TangemTheme.dimens.size16) + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = ripple(bounded = false), + onClick = { state.onCloseClick() }, + ), + ) + } + Box( + modifier = Modifier.fillMaxWidth(), + contentAlignment = Alignment.Center, + ) { + Icon( + painter = painterResource(id = R.drawable.ic_rectangle_bottom), + contentDescription = null, + tint = bgColor, + modifier = Modifier + .size(width = 12.dp, height = 8.dp), + ) + } + } +} + +@Preview(widthDp = 360, showBackground = true) +@Preview(widthDp = 360, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun Preview_YieldSupplyPromoBanner() { + TangemThemePreview { + YieldSupplyPromoBanner( + state = PromoBannerState.Content( + title = TextReference.Str(value = "Earn up to 5% APY"), + onPromoBannerClick = {}, + onCloseClick = {}, + ), + modifier = Modifier.fillMaxWidth(), + ) + } +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/token/state/TokenItemState.kt b/core/ui/src/main/java/com/tangem/core/ui/components/token/state/TokenItemState.kt index 7f05cdb7cb..a2180bb614 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/token/state/TokenItemState.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/token/state/TokenItemState.kt @@ -34,6 +34,8 @@ sealed class TokenItemState { */ abstract val subtitle2State: Subtitle2State? + abstract val promoBannerState: PromoBannerState + /** Callback which will be called when an item is clicked */ abstract val onItemClick: ((TokenItemState) -> Unit)? @@ -59,6 +61,7 @@ sealed class TokenItemState { ) : TokenItemState() { override val fiatAmountState: FiatAmountState = FiatAmountState.Loading override val subtitle2State: Subtitle2State = Subtitle2State.Loading + override val promoBannerState: PromoBannerState = PromoBannerState.Empty override val onItemClick: ((TokenItemState) -> Unit)? = null override val onItemLongClick: ((TokenItemState) -> Unit)? = null override val onApyLabelClick: ((TokenItemState) -> Unit)? = null @@ -75,6 +78,7 @@ sealed class TokenItemState { override val subtitleState: SubtitleState = SubtitleState.Locked override val fiatAmountState: FiatAmountState = FiatAmountState.Locked override val subtitle2State: Subtitle2State = Subtitle2State.Locked + override val promoBannerState: PromoBannerState = PromoBannerState.Empty override val onItemClick: ((TokenItemState) -> Unit)? = null override val onItemLongClick: ((TokenItemState) -> Unit)? = null override val onApyLabelClick: ((TokenItemState) -> Unit)? = null @@ -99,6 +103,7 @@ sealed class TokenItemState { override val subtitleState: SubtitleState, override val fiatAmountState: FiatAmountState?, override val subtitle2State: Subtitle2State?, + override val promoBannerState: PromoBannerState = PromoBannerState.Empty, override val onItemClick: ((TokenItemState) -> Unit)?, override val onItemLongClick: ((TokenItemState) -> Unit)?, override val onApyLabelClick: ((TokenItemState) -> Unit)? = null, @@ -120,6 +125,7 @@ sealed class TokenItemState { ) : TokenItemState() { override val subtitleState: SubtitleState? = null override val fiatAmountState: FiatAmountState? = null + override val promoBannerState: PromoBannerState = PromoBannerState.Empty override val onItemClick: ((TokenItemState) -> Unit)? = null override val onItemLongClick: ((TokenItemState) -> Unit)? = null override val onApyLabelClick: ((TokenItemState) -> Unit)? = null @@ -146,6 +152,7 @@ sealed class TokenItemState { ) : TokenItemState() { override val fiatAmountState: FiatAmountState? = null override val subtitle2State: Subtitle2State? = null + override val promoBannerState: PromoBannerState = PromoBannerState.Empty } /** @@ -168,6 +175,7 @@ sealed class TokenItemState { override val subtitle2State: Subtitle2State? = null override val onItemClick: ((TokenItemState) -> Unit)? = null override val onApyLabelClick: ((TokenItemState) -> Unit)? = null + override val promoBannerState: PromoBannerState = PromoBannerState.Empty } @Immutable @@ -254,4 +262,15 @@ sealed class TokenItemState { data object Locked : Subtitle2State() } + + @Immutable + sealed class PromoBannerState { + data class Content( + val title: TextReference, + val onPromoBannerClick: () -> Unit, + val onCloseClick: () -> Unit, + ) : PromoBannerState() + + data object Empty : PromoBannerState() + } } \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/test/TokenElementsTestTags.kt b/core/ui/src/main/java/com/tangem/core/ui/test/TokenElementsTestTags.kt index a6c08721f7..d513f7791c 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/test/TokenElementsTestTags.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/test/TokenElementsTestTags.kt @@ -7,4 +7,5 @@ object TokenElementsTestTags { const val TOKEN_FIAT_AMOUNT = "TOKEN_FIAT_AMOUNT" const val TOKEN_CRYPTO_AMOUNT = "TOKEN_CRYPTO_AMOUNT" const val TOKEN_NON_FIAT_BLOCK = "TOKEN_NON_FIAT_BLOCK" + const val TOKEN_YIELD_PROMO_BANNER = "TOKEN_YIELD_PROMO_BANNER" } \ No newline at end of file diff --git a/core/ui/src/main/res/drawable/ic_rectangle_bottom.xml b/core/ui/src/main/res/drawable/ic_rectangle_bottom.xml new file mode 100644 index 0000000000..6b77c79cb9 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_rectangle_bottom.xml @@ -0,0 +1,9 @@ + + + diff --git a/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineExt.kt b/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineExt.kt index 8a2fd97b72..7d14d15efb 100644 --- a/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineExt.kt +++ b/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineExt.kt @@ -69,15 +69,16 @@ fun CoroutineScope.launchOnCancellation(block: suspend () -> Unit) { } @Suppress("LongParameterList", "MagicNumber") -inline fun combine6( +inline fun combine7( flow1: Flow, flow2: Flow, flow3: Flow, flow4: Flow, flow5: Flow, flow6: Flow, - crossinline transform: suspend (T1, T2, T3, T4, T5, T6) -> R, -): Flow = combine(flow1, flow2, flow3, flow4, flow5, flow6) { arr -> + flow7: Flow, + crossinline transform: suspend (T1, T2, T3, T4, T5, T6, T7) -> R, +): Flow = combine(flow1, flow2, flow3, flow4, flow5, flow6, flow7) { arr -> @Suppress("UNCHECKED_CAST") transform( arr[0] as T1, @@ -86,5 +87,6 @@ inline fun combine6( arr[3] as T4, arr[4] as T5, arr[5] as T6, + arr[6] as T7, ) } \ No newline at end of file diff --git a/data/yield-supply/build.gradle.kts b/data/yield-supply/build.gradle.kts index 4fbb4f314a..cc7313cd67 100644 --- a/data/yield-supply/build.gradle.kts +++ b/data/yield-supply/build.gradle.kts @@ -19,6 +19,10 @@ dependencies { /** Tangem SDKs */ implementation(tangemDeps.blockchain) + // region AndroidX libraries + implementation(deps.androidx.datastore) + // endregion + /** Core */ implementation(projects.core.datasource) implementation(projects.core.utils) diff --git a/data/yield-supply/src/main/java/com/tangem/data/yield/supply/DefaultYieldSupplyRepository.kt b/data/yield-supply/src/main/java/com/tangem/data/yield/supply/DefaultYieldSupplyRepository.kt index 384cb82f4b..a40499ba87 100644 --- a/data/yield-supply/src/main/java/com/tangem/data/yield/supply/DefaultYieldSupplyRepository.kt +++ b/data/yield-supply/src/main/java/com/tangem/data/yield/supply/DefaultYieldSupplyRepository.kt @@ -12,6 +12,10 @@ import com.tangem.data.yield.supply.converters.YieldTokenChartConverter import com.tangem.datasource.api.common.response.getOrThrow import com.tangem.datasource.api.tangemTech.YieldSupplyApi import com.tangem.datasource.api.tangemTech.models.YieldSupplyChangeTokenStatusBody +import com.tangem.datasource.local.preferences.AppPreferencesStore +import com.tangem.datasource.local.preferences.PreferencesKeys +import com.tangem.datasource.local.preferences.utils.get +import com.tangem.datasource.local.preferences.utils.store import com.tangem.datasource.local.yieldsupply.YieldMarketsStore import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus @@ -35,6 +39,7 @@ internal class DefaultYieldSupplyRepository( private val walletManagersFacade: WalletManagersFacade, private val dispatchers: CoroutineDispatcherProvider, private val analyticsExceptionHandler: AnalyticsExceptionHandler, + private val appPreferencesStore: AppPreferencesStore, ) : YieldSupplyRepository { private val statusMap: MutableMap = ConcurrentHashMap() @@ -174,10 +179,18 @@ internal class DefaultYieldSupplyRepository( null } - private fun Set.hasYieldEnterTransactions(yieldAddress: String) = any { txInfo -> - txInfo.type is TxInfo.TransactionType.YieldSupply.Enter || - txInfo.type == TxInfo.TransactionType.Approve && - (txInfo.interactionAddressType as? TxInfo.InteractionAddressType.Contract)?.address == yieldAddress + override fun getShouldShowYieldPromoBanner(): Flow { + return appPreferencesStore.get(PreferencesKeys.YIELD_SUPPLY_SHOULD_SHOW_MAIN_PROMO_KEY, true) + } + + override suspend fun setShouldShowYieldPromoBanner(shouldShow: Boolean) { + appPreferencesStore.store(PreferencesKeys.YIELD_SUPPLY_SHOULD_SHOW_MAIN_PROMO_KEY, shouldShow) + } + + private fun Set.hasYieldEnterTransactions(yieldAddress: String) = any { + it.type == TxInfo.TransactionType.YieldSupply.Enter || + it.type == TxInfo.TransactionType.Approve && + (it.interactionAddressType as? TxInfo.InteractionAddressType.Contract)?.address == yieldAddress } private fun Set.hasYieldExitTransactions() = any { diff --git a/data/yield-supply/src/main/java/com/tangem/data/yield/supply/di/YieldSupplyDataModule.kt b/data/yield-supply/src/main/java/com/tangem/data/yield/supply/di/YieldSupplyDataModule.kt index 409d1da57f..ee040a543a 100644 --- a/data/yield-supply/src/main/java/com/tangem/data/yield/supply/di/YieldSupplyDataModule.kt +++ b/data/yield-supply/src/main/java/com/tangem/data/yield/supply/di/YieldSupplyDataModule.kt @@ -5,6 +5,7 @@ import com.tangem.data.yield.supply.DefaultYieldSupplyRepository import com.tangem.data.yield.supply.DefaultYieldSupplyErrorResolver import com.tangem.data.yield.supply.DefaultYieldSupplyTransactionRepository import com.tangem.datasource.api.tangemTech.YieldSupplyApi +import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.datasource.local.yieldsupply.YieldMarketsStore import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.yield.supply.YieldSupplyRepository @@ -41,6 +42,7 @@ internal object YieldSupplyDataModule { walletManagersFacade: WalletManagersFacade, dispatchers: CoroutineDispatcherProvider, analyticsExceptionHandler: AnalyticsExceptionHandler, + appPreferencesStore: AppPreferencesStore, ): YieldSupplyRepository { return DefaultYieldSupplyRepository( yieldSupplyApi = yieldSupplyApi, @@ -48,6 +50,7 @@ internal object YieldSupplyDataModule { dispatchers = dispatchers, walletManagersFacade = walletManagersFacade, analyticsExceptionHandler = analyticsExceptionHandler, + appPreferencesStore = appPreferencesStore, ) } diff --git a/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/YieldSupplyRepository.kt b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/YieldSupplyRepository.kt index 8ab13bbac1..9a73322ca8 100644 --- a/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/YieldSupplyRepository.kt +++ b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/YieldSupplyRepository.kt @@ -108,4 +108,8 @@ interface YieldSupplyRepository { userWalletId: UserWalletId, cryptoCurrencyStatus: CryptoCurrencyStatus, ): YieldSupplyEnterStatus? + + fun getShouldShowYieldPromoBanner(): Flow + + suspend fun setShouldShowYieldPromoBanner(shouldShow: Boolean) } \ No newline at end of file diff --git a/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyApyFlowUseCase.kt b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyApyFlowUseCase.kt index d93fcca1a1..3dda098013 100644 --- a/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyApyFlowUseCase.kt +++ b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyApyFlowUseCase.kt @@ -3,6 +3,7 @@ package com.tangem.domain.yield.supply.usecase import com.tangem.domain.yield.supply.YieldSupplyRepository import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map +import java.math.BigDecimal /** * Emits a map of APY values per token. @@ -15,11 +16,11 @@ class YieldSupplyApyFlowUseCase( private val yieldSupplyRepository: YieldSupplyRepository, ) { - operator fun invoke(): Flow> { + operator fun invoke(): Flow> { return yieldSupplyRepository.getMarketsFlow() .map { yieldMarketTokenList -> yieldMarketTokenList.filter { it.isActive }.associate { token -> - token.yieldSupplyKey to token.apy.toString() + token.yieldSupplyKey to token.apy } } } diff --git a/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetShouldShowMainPromoUseCase.kt b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetShouldShowMainPromoUseCase.kt new file mode 100644 index 0000000000..42a1493d5b --- /dev/null +++ b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetShouldShowMainPromoUseCase.kt @@ -0,0 +1,13 @@ +package com.tangem.domain.yield.supply.usecase + +import com.tangem.domain.yield.supply.YieldSupplyRepository +import kotlinx.coroutines.flow.Flow + +class YieldSupplyGetShouldShowMainPromoUseCase( + private val yieldSupplyRepository: YieldSupplyRepository, +) { + + operator fun invoke(): Flow { + return yieldSupplyRepository.getShouldShowYieldPromoBanner() + } +} \ No newline at end of file diff --git a/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplySetShouldShowMainPromoUseCase.kt b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplySetShouldShowMainPromoUseCase.kt new file mode 100644 index 0000000000..dcc0a985e6 --- /dev/null +++ b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplySetShouldShowMainPromoUseCase.kt @@ -0,0 +1,12 @@ +package com.tangem.domain.yield.supply.usecase + +import com.tangem.domain.yield.supply.YieldSupplyRepository + +class YieldSupplySetShouldShowMainPromoUseCase( + private val yieldSupplyRepository: YieldSupplyRepository, +) { + + suspend operator fun invoke(shouldShow: Boolean) { + yieldSupplyRepository.setShouldShowYieldPromoBanner(shouldShow) + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletContentClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletContentClickIntents.kt index 8c1278e5b6..0cd9d5e7b1 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletContentClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletContentClickIntents.kt @@ -21,6 +21,7 @@ import com.tangem.domain.tokens.model.details.NavigationAction import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.domain.yield.supply.usecase.YieldSupplyEnterStatusUseCase +import com.tangem.domain.yield.supply.usecase.YieldSupplySetShouldShowMainPromoUseCase import com.tangem.feature.wallet.presentation.account.AccountDependencies import com.tangem.feature.wallet.presentation.wallet.domain.OnrampStatusFactory import com.tangem.feature.wallet.presentation.wallet.domain.unwrap @@ -52,6 +53,8 @@ internal interface WalletContentClickIntents { fun onApyLabelClick(userWalletId: UserWalletId, currencyStatus: CryptoCurrencyStatus, apy: String) + fun onYieldPromoCloseClick() + fun onAccountExpandClick(account: Account) fun onAccountCollapseClick(account: Account) @@ -89,6 +92,7 @@ internal class WalletContentClickIntentsImplementor @Inject constructor( private val hotWalletFeatureToggles: HotWalletFeatureToggles, private val accountDependencies: AccountDependencies, private val yieldSupplyEnterStatusUseCase: YieldSupplyEnterStatusUseCase, + private val yieldSupplySetShouldShowMainPromoUseCase: YieldSupplySetShouldShowMainPromoUseCase, ) : BaseWalletClickIntents(), WalletContentClickIntents { override fun onDetailsClick() { @@ -182,6 +186,12 @@ internal class WalletContentClickIntentsImplementor @Inject constructor( } } + override fun onYieldPromoCloseClick() { + modelScope.launch { + yieldSupplySetShouldShowMainPromoUseCase(false) + } + } + override fun onAccountExpandClick(account: Account) { val userWalletId = stateHolder.getSelectedWalletId() accountDependencies.expandedAccountsHolder.expandAccount(userWalletId, account.accountId) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoader.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoader.kt index 0ce2d80a8d..e08e469afc 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoader.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoader.kt @@ -11,6 +11,7 @@ import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.wallets.repository.WalletsRepository import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase +import com.tangem.domain.yield.supply.usecase.YieldSupplyGetShouldShowMainPromoUseCase import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender @@ -45,6 +46,7 @@ internal class MultiWalletContentLoader( private val currenciesRepository: CurrenciesRepository, private val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase, private val stakingApyFlowUseCase: StakingApyFlowUseCase, + private val yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase, private val hotWalletFeatureToggles: HotWalletFeatureToggles, private val tangemPayFeatureToggles: TangemPayFeatureToggles, private val tangemPayMainSubscriberFactory: TangemPayMainSubscriber.Factory, @@ -63,6 +65,7 @@ internal class MultiWalletContentLoader( applyTokenListSortingUseCase = applyTokenListSortingUseCase, yieldSupplyApyFlowUseCase = yieldSupplyApyFlowUseCase, stakingApyFlowUseCase = stakingApyFlowUseCase, + yieldSupplyGetShouldShowMainPromoUseCase = yieldSupplyGetShouldShowMainPromoUseCase, ).let(::add) WalletNFTListSubscriber( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt index d3fe3d60a4..1f9d0570e8 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt @@ -11,6 +11,7 @@ import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.wallets.repository.WalletsRepository import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase +import com.tangem.domain.yield.supply.usecase.YieldSupplyGetShouldShowMainPromoUseCase import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender @@ -44,6 +45,7 @@ internal class MultiWalletContentLoaderFactory @Inject constructor( private val currenciesRepository: CurrenciesRepository, private val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase, private val stakingApyFlowUseCase: StakingApyFlowUseCase, + private val yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase, private val hotWalletFeatureToggles: HotWalletFeatureToggles, private val tangemPayFeatureToggles: TangemPayFeatureToggles, private val tangemPayMainSubscriberFactory: TangemPayMainSubscriber.Factory, @@ -72,6 +74,7 @@ internal class MultiWalletContentLoaderFactory @Inject constructor( hotWalletFeatureToggles = hotWalletFeatureToggles, tangemPayFeatureToggles = tangemPayFeatureToggles, tangemPayMainSubscriberFactory = tangemPayMainSubscriberFactory, + yieldSupplyGetShouldShowMainPromoUseCase = yieldSupplyGetShouldShowMainPromoUseCase, ) } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoader.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoader.kt index 7f3e9f91b6..6d895697ed 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoader.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoader.kt @@ -6,6 +6,7 @@ import com.tangem.domain.promo.GetStoryContentUseCase import com.tangem.domain.staking.usecase.StakingApyFlowUseCase import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase +import com.tangem.domain.yield.supply.usecase.YieldSupplyGetShouldShowMainPromoUseCase import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender @@ -35,6 +36,7 @@ internal class SingleWalletWithTokenContentLoader( private val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase, private val stakingApyFlowUseCase: StakingApyFlowUseCase, private val hotWalletFeatureToggles: HotWalletFeatureToggles, + private val yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase, ) : WalletContentLoader(id = userWallet.walletId) { override fun create(): List { @@ -49,6 +51,7 @@ internal class SingleWalletWithTokenContentLoader( getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase, yieldSupplyApyFlowUseCase = yieldSupplyApyFlowUseCase, stakingApyFlowUseCase = stakingApyFlowUseCase, + yieldSupplyGetShouldShowMainPromoUseCase = yieldSupplyGetShouldShowMainPromoUseCase, ).let(::add) MultiWalletWarningsSubscriber( userWallet = userWallet, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoaderFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoaderFactory.kt index 0f6ac06ef5..4d89203c74 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoaderFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoaderFactory.kt @@ -7,6 +7,7 @@ import com.tangem.domain.promo.GetStoryContentUseCase import com.tangem.domain.staking.usecase.StakingApyFlowUseCase import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase +import com.tangem.domain.yield.supply.usecase.YieldSupplyGetShouldShowMainPromoUseCase import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender @@ -36,6 +37,7 @@ internal class SingleWalletWithTokenContentLoaderFactory @Inject constructor( private val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase, private val stakingApyFlowUseCase: StakingApyFlowUseCase, private val hotWalletFeatureToggles: HotWalletFeatureToggles, + private val yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase, ) { fun create(userWallet: UserWallet.Cold, clickIntents: WalletClickIntents): SingleWalletWithTokenContentLoader { @@ -55,6 +57,7 @@ internal class SingleWalletWithTokenContentLoaderFactory @Inject constructor( yieldSupplyApyFlowUseCase = yieldSupplyApyFlowUseCase, stakingApyFlowUseCase = stakingApyFlowUseCase, hotWalletFeatureToggles = hotWalletFeatureToggles, + yieldSupplyGetShouldShowMainPromoUseCase = yieldSupplyGetShouldShowMainPromoUseCase, ) } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTokenListTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTokenListTransformer.kt index 4d102bb5d8..0dec24b4b9 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTokenListTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTokenListTransformer.kt @@ -11,14 +11,16 @@ import com.tangem.feature.wallet.presentation.wallet.state.transformers.converte import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.TokenListStateConverter import com.tangem.feature.wallet.presentation.wallet.state.utils.enableButtons import timber.log.Timber +import java.math.BigDecimal internal class SetTokenListTransformer( private val params: TokenConverterParams, private val userWallet: UserWallet, private val appCurrency: AppCurrency, private val clickIntents: WalletClickIntents, - private val yieldSupplyApyMap: Map = emptyMap(), + private val yieldSupplyApyMap: Map = emptyMap(), private val stakingApyMap: Map> = emptyMap(), + private val shouldShowMainPromo: Boolean, ) : WalletStateTransformer(userWallet.walletId) { override fun transform(prevState: WalletState): WalletState { @@ -63,6 +65,7 @@ internal class SetTokenListTransformer( clickIntents = clickIntents, yieldModuleApyMap = yieldSupplyApyMap, stakingApyMap = stakingApyMap, + shouldShowMainPromo = shouldShowMainPromo, ).convert(value = this) } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenListStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenListStateConverter.kt index f9eefeccd6..9037cb7b57 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenListStateConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenListStateConverter.kt @@ -28,17 +28,25 @@ import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.mutate import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toPersistentList +import java.math.BigDecimal import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState.OrganizeTokensButtonConfig as WalletOrganizeTokensButtonConfig +@Suppress("LongParameterList") internal class TokenListStateConverter( private val appCurrency: AppCurrency, private val params: TokenConverterParams, private val selectedWallet: UserWallet, private val clickIntents: WalletClickIntents, - private val yieldModuleApyMap: Map, + private val yieldModuleApyMap: Map, private val stakingApyMap: Map>, + private val shouldShowMainPromo: Boolean, ) : Converter { + private val yieldSupplyPromoBannerKeyConverter = YieldSupplyPromoBannerKeyConverter( + yieldModuleApyMap, + shouldShowMainPromo, + ) + private val onTokenClick: (accountId: AccountId?, currencyStatus: CryptoCurrencyStatus) -> Unit = { accountId, currencyStatus -> clickIntents.onTokenItemClick(selectedWallet.walletId, currencyStatus) @@ -57,10 +65,12 @@ internal class TokenListStateConverter( private fun tokenStatusConverter(accountId: AccountId? = null) = TokenItemStateConverter( appCurrency = appCurrency, yieldModuleApyMap = yieldModuleApyMap, + yieldSupplyPromoBannerKey = yieldSupplyPromoBannerKeyConverter.convert(params), stakingApyMap = stakingApyMap, onItemClick = { _, status -> onTokenClick(accountId, status) }, onItemLongClick = { _, status -> onTokenLongClick(accountId, status) }, onApyLabelClick = { status, apy -> onApyLabelClick(status, apy) }, + onYieldPromoCloseClick = clickIntents::onYieldPromoCloseClick, ) override fun convert(value: WalletTokensListState): WalletTokensListState { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/YieldSupplyPromoBannerKeyConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/YieldSupplyPromoBannerKeyConverter.kt new file mode 100644 index 0000000000..dd2569e500 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/YieldSupplyPromoBannerKeyConverter.kt @@ -0,0 +1,48 @@ +package com.tangem.feature.wallet.presentation.wallet.state.transformers.converter + +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.currency.CryptoCurrencyStatus +import com.tangem.domain.models.currency.yieldSupplyKey +import com.tangem.feature.wallet.presentation.wallet.state.transformers.TokenConverterParams +import com.tangem.lib.crypto.BlockchainUtils +import com.tangem.utils.converter.Converter +import java.math.BigDecimal + +internal class YieldSupplyPromoBannerKeyConverter( + private val yieldModuleApyMap: Map, + private val shouldShowMainPromo: Boolean, +) : Converter { + + override fun convert(value: TokenConverterParams): String? { + if (!shouldShowMainPromo) return null + + val currencies = when (value) { + is TokenConverterParams.Wallet -> value.tokenList.flattenCurrencies() + is TokenConverterParams.Account -> value.accountList.flattenCurrencies() + }.filter { status -> + status.value is CryptoCurrencyStatus.Loaded || + status.value is CryptoCurrencyStatus.Custom + } + + val tokens = currencies.filter { it.currency is CryptoCurrency.Token } + + if (tokens.any { it.value.yieldSupplyStatus?.isActive == true }) return null + if (yieldModuleApyMap.isEmpty()) return null + + val max = tokens.asSequence() + .mapNotNull { status -> + val token = status.currency as? CryptoCurrency.Token ?: return@mapNotNull null + val tokenKey = token.yieldSupplyKey() + val shouldIgnoreCase = BlockchainUtils.isCaseInsensitiveContractAddress(token.network.rawId) + + val matchedKey = yieldModuleApyMap.keys.firstOrNull { mapKey -> + mapKey.equals(tokenKey, shouldIgnoreCase) + } ?: return@mapNotNull null + + status to matchedKey + } + .maxByOrNull { (status, _) -> status.value.amount ?: BigDecimal.ZERO } + + return max?.second + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/AccountListSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/AccountListSubscriber.kt index 1a01d99ec3..d1cbb38064 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/AccountListSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/AccountListSubscriber.kt @@ -5,16 +5,18 @@ import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.staking.model.stakekit.Yield import com.tangem.domain.staking.usecase.StakingApyFlowUseCase import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase +import com.tangem.domain.yield.supply.usecase.YieldSupplyGetShouldShowMainPromoUseCase import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import com.tangem.feature.wallet.presentation.account.AccountDependencies import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController -import com.tangem.utils.coroutines.combine6 +import com.tangem.utils.coroutines.combine7 import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.distinctUntilChanged +import java.math.BigDecimal /** * Subscriber that monitors account list related data and updates the wallet state accordingly. @@ -30,19 +32,21 @@ internal class AccountListSubscriber @AssistedInject constructor( override val clickIntents: WalletClickIntents, private val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase, private val stakingApyFlowUseCase: StakingApyFlowUseCase, + private val yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase, ) : BasicAccountListSubscriber() { - override fun create(coroutineScope: CoroutineScope): Flow<*> = combine6( + override fun create(coroutineScope: CoroutineScope): Flow<*> = combine7( flow1 = getAccountStatusListFlow(), flow2 = getAppCurrencyFlow(), flow3 = accountDependencies.expandedAccountsHolder.expandedAccounts(userWallet), flow4 = accountDependencies.isAccountsModeEnabledUseCase(), flow5 = yieldSupplyApyFlow(), flow6 = stakingApyFlow(), + flow7 = yieldSupplyGetShouldShowMainPromoFlow(), transform = ::updateState, ) - private fun yieldSupplyApyFlow(): Flow> { + private fun yieldSupplyApyFlow(): Flow> { return yieldSupplyApyFlowUseCase().distinctUntilChanged() } @@ -50,6 +54,10 @@ internal class AccountListSubscriber @AssistedInject constructor( return stakingApyFlowUseCase().distinctUntilChanged() } + private fun yieldSupplyGetShouldShowMainPromoFlow(): Flow { + return yieldSupplyGetShouldShowMainPromoUseCase().distinctUntilChanged() + } + @AssistedFactory interface Factory { fun create(userWallet: UserWallet): AccountListSubscriber diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicAccountListSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicAccountListSubscriber.kt index 40a9681b13..080766b9b3 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicAccountListSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicAccountListSubscriber.kt @@ -20,6 +20,7 @@ import com.tangem.feature.wallet.presentation.wallet.state.transformers.TokenCon import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.distinctUntilChanged import timber.log.Timber +import java.math.BigDecimal /** * Basic implementation of [WalletSubscriber] for wallet with accounts. @@ -46,8 +47,9 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() { appCurrency: AppCurrency, expandedAccounts: Set, isAccountMode: Boolean, - yieldSupplyApyMap: Map = emptyMap(), + yieldSupplyApyMap: Map = emptyMap(), stakingApyMap: Map> = emptyMap(), + shouldShowMainPromo: Boolean = false, ) { val accountFlattenCurrencies = accountList.flattenCurrencies() val mainAccount = accountList.mainAccount @@ -67,6 +69,7 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() { portfolioId = PortfolioId(mainAccount.accountId), yieldSupplyApyMap = yieldSupplyApyMap, stakingApyMap = stakingApyMap, + shouldShowMainPromo = shouldShowMainPromo, ) } isAccountMode -> { @@ -82,7 +85,13 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() { } else { val convertParams = TokenConverterParams.Account(accountList, expandedAccounts) - updateContent(convertParams, appCurrency, yieldSupplyApyMap, stakingApyMap) + updateContent( + params = convertParams, + appCurrency = appCurrency, + yieldSupplyApyMap = yieldSupplyApyMap, + stakingApyMap = stakingApyMap, + shouldShowMainPromo = shouldShowMainPromo, + ) } } } @@ -92,8 +101,9 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() { maybeTokenList: Lce, appCurrency: AppCurrency, portfolioId: PortfolioId, - yieldSupplyApyMap: Map = emptyMap(), + yieldSupplyApyMap: Map = emptyMap(), stakingApyMap: Map> = emptyMap(), + shouldShowMainPromo: Boolean, ) { val tokenList = maybeTokenList.getOrElse( ifLoading = { maybeContent -> @@ -123,14 +133,16 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() { appCurrency = appCurrency, yieldSupplyApyMap = yieldSupplyApyMap, stakingApyMap = stakingApyMap, + shouldShowMainPromo = shouldShowMainPromo, ) } private fun updateContent( params: TokenConverterParams, appCurrency: AppCurrency, - yieldSupplyApyMap: Map = emptyMap(), + yieldSupplyApyMap: Map = emptyMap(), stakingApyMap: Map> = emptyMap(), + shouldShowMainPromo: Boolean, ) { stateController.update( SetTokenListTransformer( @@ -140,6 +152,7 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() { clickIntents = clickIntents, yieldSupplyApyMap = yieldSupplyApyMap, stakingApyMap = stakingApyMap, + shouldShowMainPromo = shouldShowMainPromo, ), ) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt index e827068c9b..36de78ef22 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt @@ -15,6 +15,7 @@ import com.tangem.domain.staking.model.stakekit.Yield import com.tangem.domain.staking.usecase.StakingApyFlowUseCase import com.tangem.domain.tokens.error.TokenListError import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase +import com.tangem.domain.yield.supply.usecase.YieldSupplyGetShouldShowMainPromoUseCase import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker @@ -28,6 +29,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import timber.log.Timber +import java.math.BigDecimal @Deprecated("Use AccountListSubscriber instead") @Suppress("LongParameterList") @@ -40,6 +42,7 @@ internal abstract class BasicTokenListSubscriber( private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase, private val stakingApyFlowUseCase: StakingApyFlowUseCase, + private val yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase, ) : WalletSubscriber() { private val sendAnalyticsJobHolder = JobHolder() @@ -69,7 +72,8 @@ internal abstract class BasicTokenListSubscriber( flow2 = appCurrencyFlow(), flow3 = yieldSupplyApyFlow(), flow4 = stakingApyFlow(), - transform = { maybeTokenList, appCurrency, yieldSupplyApyMap, stakingApyMap -> + flow5 = yieldSupplyGetShouldShowMainPromoFlow(), + transform = { maybeTokenList, appCurrency, yieldSupplyApyMap, stakingApyMap, shouldShowMainPromo -> val tokenList = maybeTokenList.getOrElse( ifLoading = { maybeContent -> val isRefreshing = stateHolder.getWalletState(userWallet.walletId) @@ -98,6 +102,7 @@ internal abstract class BasicTokenListSubscriber( appCurrency = appCurrency, yieldSupplyApyMap = yieldSupplyApyMap, stakingApyMap = stakingApyMap, + shouldShowMainPromo = shouldShowMainPromo, ) walletWithFundsChecker.check(tokenList) @@ -122,8 +127,9 @@ internal abstract class BasicTokenListSubscriber( private fun updateContent( params: TokenConverterParams, appCurrency: AppCurrency, - yieldSupplyApyMap: Map, + yieldSupplyApyMap: Map, stakingApyMap: Map>, + shouldShowMainPromo: Boolean, ) { stateHolder.update( SetTokenListTransformer( @@ -133,6 +139,7 @@ internal abstract class BasicTokenListSubscriber( clickIntents = clickIntents, yieldSupplyApyMap = yieldSupplyApyMap, stakingApyMap = stakingApyMap, + shouldShowMainPromo = shouldShowMainPromo, ), ) } @@ -146,9 +153,12 @@ internal abstract class BasicTokenListSubscriber( } .distinctUntilChanged() - private fun yieldSupplyApyFlow(): Flow> = yieldSupplyApyFlowUseCase() + private fun yieldSupplyApyFlow(): Flow> = yieldSupplyApyFlowUseCase() .distinctUntilChanged() private fun stakingApyFlow(): Flow>> = stakingApyFlowUseCase() .distinctUntilChanged() + + private fun yieldSupplyGetShouldShowMainPromoFlow(): Flow = yieldSupplyGetShouldShowMainPromoUseCase() + .distinctUntilChanged() } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt index 36220cd6e1..d75d637dad 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt @@ -12,6 +12,7 @@ import com.tangem.domain.staking.usecase.StakingApyFlowUseCase import com.tangem.domain.tokens.ApplyTokenListSortingUseCase import com.tangem.domain.tokens.error.TokenListError import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase +import com.tangem.domain.yield.supply.usecase.YieldSupplyGetShouldShowMainPromoUseCase import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenListStore @@ -32,6 +33,7 @@ internal class MultiWalletTokenListSubscriber( getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase, stakingApyFlowUseCase: StakingApyFlowUseCase, + yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase, ) : BasicTokenListSubscriber( userWallet = userWallet, stateHolder = stateHolder, @@ -41,6 +43,7 @@ internal class MultiWalletTokenListSubscriber( getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase, yieldSupplyApyFlowUseCase = yieldSupplyApyFlowUseCase, stakingApyFlowUseCase = stakingApyFlowUseCase, + yieldSupplyGetShouldShowMainPromoUseCase = yieldSupplyGetShouldShowMainPromoUseCase, ) { override fun tokenListFlow(coroutineScope: CoroutineScope): LceFlow { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenListSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenListSubscriber.kt index e686dd4e20..9e148dcf24 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenListSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenListSubscriber.kt @@ -8,6 +8,7 @@ import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.staking.usecase.StakingApyFlowUseCase import com.tangem.domain.tokens.error.TokenListError import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase +import com.tangem.domain.yield.supply.usecase.YieldSupplyGetShouldShowMainPromoUseCase import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenListStore @@ -27,6 +28,7 @@ internal class SingleWalletWithTokenListSubscriber( getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase, stakingApyFlowUseCase: StakingApyFlowUseCase, + yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase, ) : BasicTokenListSubscriber( userWallet = userWallet, stateHolder = stateHolder, @@ -36,6 +38,7 @@ internal class SingleWalletWithTokenListSubscriber( getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase, yieldSupplyApyFlowUseCase = yieldSupplyApyFlowUseCase, stakingApyFlowUseCase = stakingApyFlowUseCase, + yieldSupplyGetShouldShowMainPromoUseCase = yieldSupplyGetShouldShowMainPromoUseCase, ) { override fun tokenListFlow(coroutineScope: CoroutineScope): LceFlow { diff --git a/features/wallet/impl/src/test/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/YieldSupplyPromoBannerKeyConverterTest.kt b/features/wallet/impl/src/test/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/YieldSupplyPromoBannerKeyConverterTest.kt new file mode 100644 index 0000000000..458cbcaef2 --- /dev/null +++ b/features/wallet/impl/src/test/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/YieldSupplyPromoBannerKeyConverterTest.kt @@ -0,0 +1,208 @@ +package com.tangem.feature.wallet.presentation.wallet.state.transformers.converter + +import com.google.common.truth.Truth.assertThat +import com.tangem.domain.models.PortfolioId +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.currency.yieldSupplyKey +import com.tangem.domain.models.network.Network +import com.tangem.domain.models.network.NetworkAddress +import com.tangem.domain.models.tokenlist.TokenList +import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.models.yield.supply.YieldSupplyStatus +import com.tangem.feature.wallet.presentation.wallet.state.transformers.TokenConverterParams +import org.junit.Test +import java.math.BigDecimal + +class YieldSupplyPromoBannerKeyConverterTest { + + @Test + fun `GIVEN promo disabled WHEN convert THEN return null`() { + val token = createToken(networkId = "ethereum", backendId = "ethereum", contract = "0xABCDEF") + val status = createStatus(token = token, amount = BigDecimal.ONE, isYieldActive = false) + val tokenList = ungroupedTokenList(status) + val params = TokenConverterParams.Wallet( + portfolioId = PortfolioId.Wallet(UserWalletId("00")), + tokenList = tokenList, + ) + val converter = YieldSupplyPromoBannerKeyConverter( + yieldModuleApyMap = mapOf(token.yieldSupplyKey() to BigDecimal("0.10")), + shouldShowMainPromo = false, + ) + + val result = converter.convert(params) + + assertThat(result).isNull() + } + + @Test + fun `GIVEN empty apy map WHEN convert THEN return null`() { + val token = createToken(networkId = "ethereum", backendId = "ethereum", contract = "0xA1") + val status = createStatus(token = token, amount = BigDecimal("2.0"), isYieldActive = false) + val params = TokenConverterParams.Wallet( + portfolioId = PortfolioId.Wallet(UserWalletId("00")), + tokenList = ungroupedTokenList(status), + ) + val converter = YieldSupplyPromoBannerKeyConverter( + yieldModuleApyMap = emptyMap(), + shouldShowMainPromo = true, + ) + + val result = converter.convert(params) + + assertThat(result).isNull() + } + + @Test + fun `GIVEN active yield token present WHEN convert THEN return null`() { + val token = createToken(networkId = "ethereum", backendId = "ethereum", contract = "0xAA") + val statusActive = createStatus(token = token, amount = BigDecimal("5"), isYieldActive = true) + val params = TokenConverterParams.Wallet( + portfolioId = PortfolioId.Wallet(UserWalletId("00")), + tokenList = ungroupedTokenList(statusActive), + ) + val converter = YieldSupplyPromoBannerKeyConverter( + yieldModuleApyMap = mapOf(token.yieldSupplyKey() to BigDecimal("0.12")), + shouldShowMainPromo = true, + ) + + val result = converter.convert(params) + + assertThat(result).isNull() + } + + @Test + fun `GIVEN multiple candidates EVM case insensitive WHEN convert THEN return key of max amount`() { + val evmNetworkId = "ETH" + val tokenSmall = createToken(networkId = evmNetworkId, backendId = evmNetworkId, contract = "0xAbCd") + val tokenBig = createToken(networkId = evmNetworkId, backendId = evmNetworkId, contract = "0xBEEF") + + val statusSmall = createStatus(token = tokenSmall, amount = BigDecimal("1.00"), isYieldActive = false) + val statusBig = createStatus(token = tokenBig, amount = BigDecimal("10.00"), isYieldActive = false) + + val apyMap = mapOf( + "${tokenSmall.network.backendId}_${tokenSmall.contractAddress.lowercase()}" to BigDecimal("0.05"), + "${tokenBig.network.backendId}_${tokenBig.contractAddress.uppercase()}" to BigDecimal("0.15"), + ) + + val params = TokenConverterParams.Wallet( + portfolioId = PortfolioId.Wallet(UserWalletId("00")), + tokenList = ungroupedTokenList(statusSmall, statusBig), + ) + val converter = YieldSupplyPromoBannerKeyConverter( + yieldModuleApyMap = apyMap, + shouldShowMainPromo = true, + ) + + val result = converter.convert(params) + + val expectedKey = "${tokenBig.network.backendId}_${tokenBig.contractAddress.uppercase()}" + assertThat(result).isEqualTo(expectedKey) + } + + @Test + fun `GIVEN non evm case sensitive mismatch WHEN convert THEN return null`() { + val nonEvmId = "xrp" + val token = createToken(networkId = nonEvmId, backendId = nonEvmId, contract = "rAbC123") + val status = createStatus(token = token, amount = BigDecimal("3"), isYieldActive = false) + + val mismatchedKey = "${token.network.backendId}_${token.contractAddress.lowercase()}" + val apyMap = mapOf(mismatchedKey to BigDecimal("0.07")) + + val params = TokenConverterParams.Wallet( + portfolioId = PortfolioId.Wallet(UserWalletId("00")), + tokenList = ungroupedTokenList(status), + ) + val converter = YieldSupplyPromoBannerKeyConverter( + yieldModuleApyMap = apyMap, + shouldShowMainPromo = true, + ) + + val result = converter.convert(params) + + assertThat(result).isNull() + } + + private fun ungroupedTokenList(vararg statuses: CryptoCurrencyStatus): TokenList.Ungrouped { + return TokenList.Ungrouped( + totalFiatBalance = com.tangem.domain.models.TotalFiatBalance.Loaded( + amount = BigDecimal.ZERO, + source = StatusSource.ACTUAL, + ), + sortedBy = com.tangem.domain.models.TokensSortType.NONE, + currencies = statuses.toList(), + ) + } + + private fun createStatus( + token: CryptoCurrency.Token, + amount: BigDecimal, + isYieldActive: Boolean, + ): CryptoCurrencyStatus { + val networkAddress = NetworkAddress.Single( + defaultAddress = NetworkAddress.Address( + value = "addr", + type = NetworkAddress.Address.Type.Primary, + ), + ) + val value = CryptoCurrencyStatus.Custom( + amount = amount, + fiatAmount = null, + fiatRate = null, + priceChange = null, + yieldBalance = null, + yieldSupplyStatus = if (isYieldActive) { + YieldSupplyStatus( + isActive = true, + isInitialized = true, + isAllowedToSpend = true, + effectiveProtocolBalance = null, + ) + } else { + null + }, + hasCurrentNetworkTransactions = false, + pendingTransactions = emptySet(), + networkAddress = networkAddress, + sources = CryptoCurrencyStatus.Sources(), + ) + return CryptoCurrencyStatus( + currency = token, + value = value, + ) + } + + private fun createToken(networkId: String, backendId: String, contract: String): CryptoCurrency.Token { + val network = Network( + id = Network.ID(value = networkId, derivationPath = Network.DerivationPath.None), + backendId = backendId, + name = backendId, + currencySymbol = "SYM", + derivationPath = Network.DerivationPath.None, + isTestnet = false, + standardType = when (backendId) { + "ethereum" -> Network.StandardType.ERC20 + else -> Network.StandardType.Unspecified("UNSPEC") + }, + hasFiatFeeRate = false, + canHandleTokens = true, + transactionExtrasType = Network.TransactionExtrasType.NONE, + nameResolvingType = Network.NameResolvingType.NONE, + ) + return CryptoCurrency.Token( + id = CryptoCurrency.ID( + prefix = CryptoCurrency.ID.Prefix.TOKEN_PREFIX, + body = CryptoCurrency.ID.Body.NetworkId(networkId), + suffix = CryptoCurrency.ID.Suffix.ContractAddress(contract), + ), + network = network, + name = "Token", + symbol = "TKN", + decimals = 18, + iconUrl = null, + isCustom = false, + contractAddress = contract, + ) + } +} \ No newline at end of file From f023fa7f2c97f44fd83240837930367a23794bdf Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 11 Dec 2025 11:42:49 +0500 Subject: [PATCH 4/7] Updated on 2026-08-14 --- .../supply/models/YieldSupplyRewardBalance.kt | 10 + .../YieldSupplyGetRewardsBalanceUseCase.kt | 98 +++++--- ...YieldSupplyGetRewardsBalanceUseCaseTest.kt | 213 ++++++++++++++---- features/tokendetails/impl/build.gradle.kts | 1 + .../tokendetails/TokenDetailsPreviewData.kt | 2 +- .../tokendetails/model/TokenDetailsModel.kt | 5 +- .../state/TokenDetailsBalanceBlockState.kt | 3 +- .../TokenDetailsLoadedBalanceConverter.kt | 4 +- .../state/factory/TokenDetailsStateFactory.kt | 10 +- .../ui/components/TokenDetailsBalanceBlock.kt | 11 +- 10 files changed, 267 insertions(+), 90 deletions(-) create mode 100644 domain/yield-supply/models/src/main/java/com/tangem/domain/yield/supply/models/YieldSupplyRewardBalance.kt diff --git a/domain/yield-supply/models/src/main/java/com/tangem/domain/yield/supply/models/YieldSupplyRewardBalance.kt b/domain/yield-supply/models/src/main/java/com/tangem/domain/yield/supply/models/YieldSupplyRewardBalance.kt new file mode 100644 index 0000000000..d3086742cb --- /dev/null +++ b/domain/yield-supply/models/src/main/java/com/tangem/domain/yield/supply/models/YieldSupplyRewardBalance.kt @@ -0,0 +1,10 @@ +package com.tangem.domain.yield.supply.models + +data class YieldSupplyRewardBalance( + val fiatBalance: String?, + val cryptoBalance: String?, +) { + companion object { + fun empty() = YieldSupplyRewardBalance(null, null) + } +} \ No newline at end of file diff --git a/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetRewardsBalanceUseCase.kt b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetRewardsBalanceUseCase.kt index 52c1434981..93e18928bf 100644 --- a/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetRewardsBalanceUseCase.kt +++ b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetRewardsBalanceUseCase.kt @@ -1,11 +1,13 @@ package com.tangem.domain.yield.supply.usecase import com.tangem.core.ui.format.bigdecimal.anyDecimals +import com.tangem.core.ui.format.bigdecimal.crypto import com.tangem.core.ui.format.bigdecimal.fiat import com.tangem.core.ui.format.bigdecimal.format import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus +import com.tangem.domain.yield.supply.models.YieldSupplyRewardBalance import com.tangem.domain.yield.supply.YieldSupplyRepository import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.delay @@ -22,7 +24,7 @@ class YieldSupplyGetRewardsBalanceUseCase( private val dispatcherProvider: CoroutineDispatcherProvider, ) { - operator fun invoke(status: CryptoCurrencyStatus, appCurrency: AppCurrency): Flow = flow { + operator fun invoke(status: CryptoCurrencyStatus, appCurrency: AppCurrency): Flow = flow { val cryptoAmount = status.value.amount val fiatRate = status.value.fiatRate @@ -30,11 +32,7 @@ class YieldSupplyGetRewardsBalanceUseCase( return@flow } - val amount = if (cryptoAmount != null && fiatRate != null) { - cryptoAmount.multiply(fiatRate) - } else { - return@flow - } + if (cryptoAmount == null) return@flow val tokenAddress = (status.currency as? CryptoCurrency.Token)?.contractAddress ?: return@flow val apy = try { @@ -50,37 +48,57 @@ class YieldSupplyGetRewardsBalanceUseCase( return@flow } - val initialPerTickDelta = amount - .multiply(apyFraction) - .multiply(TICK_SECONDS_BD) - .divide(SECONDS_PER_YEAR_BD, SCALE, RoundingMode.HALF_UP) - .abs() + val initialPerTickDeltaCrypto = perTickDelta(cryptoAmount, apyFraction).abs() - val minVisibleDecimals = calculateMinVisibleDecimals(initialPerTickDelta) + val minVisibleDecimalsCrypto = calculateMinVisibleDecimals( + perTickDeltaAbs = initialPerTickDeltaCrypto, + maxDecimals = status.currency.decimals, + ) - var currentBalance: BigDecimal = amount + val fiatAmountStart = fiatRate?.let { cryptoAmount.multiply(it) } + val minVisibleDecimalsFiat = fiatAmountStart?.let { amount -> + val initialPerTickDeltaFiat = perTickDelta(amount, apyFraction).abs() + calculateMinVisibleDecimals( + perTickDeltaAbs = initialPerTickDeltaFiat, + maxDecimals = FIAT_MAX_DECIMALS, + ) + } + + var currentCryptoBalance: BigDecimal = cryptoAmount + var currentFiatBalance: BigDecimal? = fiatAmountStart while (true) { + val fiatBalanceFormatted: String? = currentFiatBalance?.format { + fiat( + fiatCurrencyCode = appCurrency.code, + fiatCurrencySymbol = appCurrency.symbol, + ).anyDecimals(decimals = minVisibleDecimalsFiat ?: FIAT_MIN_DECIMALS) + } + + val cryptoBalanceFormatted: String = currentCryptoBalance.format { + crypto(status.currency).anyDecimals( + maxDecimals = minVisibleDecimalsCrypto, + minDecimals = minVisibleDecimalsCrypto, + ) + } + emit( - currentBalance.format { - fiat( - fiatCurrencyCode = appCurrency.code, - fiatCurrencySymbol = appCurrency.symbol, - ).anyDecimals(decimals = minVisibleDecimals) - }, + YieldSupplyRewardBalance(fiatBalance = fiatBalanceFormatted, cryptoBalance = cryptoBalanceFormatted), ) - val perTickDelta = currentBalance - .multiply(apyFraction) - .multiply(TICK_SECONDS_BD) - .divide(SECONDS_PER_YEAR_BD, SCALE, RoundingMode.HALF_UP) + val perTickDeltaCrypto = perTickDelta(currentCryptoBalance, apyFraction) - currentBalance = currentBalance.add(perTickDelta) + currentCryptoBalance = currentCryptoBalance.add(perTickDeltaCrypto) + + currentFiatBalance = currentFiatBalance?.let { current -> + val perTickDeltaFiat = perTickDelta(current, apyFraction) + current.add(perTickDeltaFiat) + } delay(TICK_MILLIS) } }.flowOn(dispatcherProvider.default) - private fun calculateMinVisibleDecimals(perTickDeltaAbs: BigDecimal): Int { + private fun calculateMinVisibleDecimals(perTickDeltaAbs: BigDecimal, maxDecimals: Int): Int { if (perTickDeltaAbs <= BigDecimal.ZERO) return MIN_DECIMALS val perTickAsDouble = perTickDeltaAbs.toDouble() @@ -88,20 +106,28 @@ class YieldSupplyGetRewardsBalanceUseCase( val safe = if (perTickAsDouble <= 0.0) EPSILON else perTickAsDouble val raw = ceil(-ln(safe) / LN_10) - return raw.toInt().coerceIn(MIN_DECIMALS, MAX_DECIMALS) + return raw.toInt().coerceIn(MIN_DECIMALS, maxDecimals) } - private companion object { - const val TICK_MILLIS: Long = 800 - private val TICK_SECONDS_BD = BigDecimal("0.8") - private val SECONDS_PER_YEAR_BD = BigDecimal("31536000") // 365 * 24 * 60 * 60 - private val HUNDRED_BD = BigDecimal("100") - private const val SCALE = 18 + private fun perTickDelta(amount: BigDecimal, apyFraction: BigDecimal): BigDecimal { + return amount + .multiply(apyFraction) + .multiply(TICK_SECONDS_BD) + .divide(SECONDS_PER_YEAR_BD, SCALE, RoundingMode.HALF_UP) + } - private const val MIN_DECIMALS = 3 - private const val MAX_DECIMALS = 12 + companion object { + internal const val TICK_MILLIS: Long = 800 + internal val TICK_SECONDS_BD: BigDecimal = BigDecimal("0.8") + internal val SECONDS_PER_YEAR_BD: BigDecimal = BigDecimal("31536000") // 365 * 24 * 60 * 60 + internal val HUNDRED_BD: BigDecimal = BigDecimal("100") + internal const val SCALE: Int = 18 - private val LN_10 = ln(10.0) - private const val EPSILON = 1e-18 + internal const val MIN_DECIMALS: Int = 3 + internal const val FIAT_MIN_DECIMALS: Int = 2 + internal const val FIAT_MAX_DECIMALS: Int = 12 + + internal val LN_10: Double = ln(10.0) + internal const val EPSILON: Double = 1e-18 } } \ No newline at end of file diff --git a/domain/yield-supply/src/test/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetRewardsBalanceUseCaseTest.kt b/domain/yield-supply/src/test/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetRewardsBalanceUseCaseTest.kt index 3bdd2f446e..c086813f73 100644 --- a/domain/yield-supply/src/test/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetRewardsBalanceUseCaseTest.kt +++ b/domain/yield-supply/src/test/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetRewardsBalanceUseCaseTest.kt @@ -4,6 +4,7 @@ import com.google.common.truth.Truth.assertThat import com.tangem.core.ui.format.bigdecimal.format import com.tangem.core.ui.format.bigdecimal.fiat import com.tangem.core.ui.format.bigdecimal.anyDecimals +import com.tangem.core.ui.format.bigdecimal.crypto import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus @@ -11,6 +12,7 @@ import com.tangem.domain.models.network.Network import com.tangem.domain.models.network.NetworkAddress import com.tangem.domain.yield.supply.YieldSupplyRepository import com.tangem.domain.yield.supply.models.YieldMarketToken +import com.tangem.domain.yield.supply.usecase.YieldSupplyGetRewardsBalanceUseCase.Companion.TICK_MILLIS import com.tangem.utils.coroutines.CoroutineDispatcherProvider import io.mockk.coEvery import io.mockk.mockk @@ -26,7 +28,6 @@ import org.junit.jupiter.api.Test import java.math.BigDecimal import java.math.RoundingMode import kotlin.math.ceil -import kotlin.math.ln class YieldSupplyGetRewardsBalanceUseCaseTest { @@ -165,9 +166,9 @@ class YieldSupplyGetRewardsBalanceUseCaseTest { val deferred = async { useCase(status, appCurrency).take(3).toList() } testScheduler.advanceUntilIdle() - advanceTimeBy(300) + advanceTimeBy(TICK_MILLIS) testScheduler.advanceUntilIdle() - advanceTimeBy(300) + advanceTimeBy(TICK_MILLIS) testScheduler.advanceUntilIdle() val collected = deferred.await() @@ -175,25 +176,147 @@ class YieldSupplyGetRewardsBalanceUseCaseTest { val expectedDecimals = calculateMinVisibleDecimalsForTest(initialPerTickDelta(amount, apy)) - val firstExpected = amount.format { fiat( - appCurrency.code, - appCurrency.symbol, - ).anyDecimals(decimals = expectedDecimals) } - assertThat(collected[0]).isEqualTo(firstExpected) + val firstExpected = amount.format { + fiat( + appCurrency.code, + appCurrency.symbol, + ).anyDecimals(decimals = expectedDecimals) + } + assertThat(collected[0].fiatBalance).isEqualTo(firstExpected) val firstNext = nextBalance(amount, apy) - val secondExpected = firstNext.format { fiat( - appCurrency.code, - appCurrency.symbol, - ).anyDecimals(decimals = expectedDecimals) } - assertThat(collected[1]).isEqualTo(secondExpected) + val secondExpected = firstNext.format { + fiat( + appCurrency.code, + appCurrency.symbol, + ).anyDecimals(decimals = expectedDecimals) + } + assertThat(collected[1].fiatBalance).isEqualTo(secondExpected) val secondNext = nextBalance(firstNext, apy) - val thirdExpected = secondNext.format { fiat( - appCurrency.code, - appCurrency.symbol, - ).anyDecimals(decimals = expectedDecimals) } - assertThat(collected[2]).isEqualTo(thirdExpected) + val thirdExpected = secondNext.format { + fiat( + appCurrency.code, + appCurrency.symbol, + ).anyDecimals(decimals = expectedDecimals) + } + assertThat(collected[2].fiatBalance).isEqualTo(thirdExpected) + } + + @Test + fun `GIVEN polygon USDT0 Loaded status WHEN invoke THEN emit formatted balances`() = runTest { + val network = Network( + id = Network.ID(Network.RawID("POLYGON"), Network.DerivationPath.Card("m/44'/60'/0'/0/0")), + backendId = "polygon-pos", + name = "Polygon", + currencySymbol = "POL", + derivationPath = Network.DerivationPath.Card("m/44'/60'/0'/0/0"), + isTestnet = false, + standardType = Network.StandardType.Unspecified("Polygon"), + hasFiatFeeRate = true, + canHandleTokens = true, + transactionExtrasType = Network.TransactionExtrasType.NONE, + nameResolvingType = Network.NameResolvingType.NONE, + ) + val tokenId = CryptoCurrency.ID( + prefix = CryptoCurrency.ID.Prefix.TOKEN_PREFIX, + body = CryptoCurrency.ID.Body.NetworkId(network.rawId), + suffix = CryptoCurrency.ID.Suffix.RawID("usdt0", "0xc2132d05d31c914a87c6611c10748aeb04b58e8f"), + ) + val currency = CryptoCurrency.Token( + id = tokenId, + network = network, + name = "USDT0", + symbol = "USDT0", + decimals = 6, + iconUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/large/usdt0.png", + isCustom = false, + contractAddress = "0xc2132d05d31c914a87c6611c10748aeb04b58e8f", + ) + + val amount = BigDecimal("9.241136") + val fiatRate = BigDecimal("0.9999761277273864") + val status = CryptoCurrencyStatus( + currency = currency, + value = CryptoCurrencyStatus.Loaded( + amount = amount, + fiatAmount = amount.multiply(fiatRate), + fiatRate = fiatRate, + priceChange = BigDecimal("-0.000058200000000008245"), + yieldBalance = null, + yieldSupplyStatus = null, + hasCurrentNetworkTransactions = false, + pendingTransactions = emptySet(), + networkAddress = NetworkAddress.Single( + NetworkAddress.Address( + value = "0xb71fa0E20ba8579B3ec51cC79aaa84Bf5982BB49", + type = NetworkAddress.Address.Type.Primary, + ), + ), + sources = CryptoCurrencyStatus.Sources(), + ), + ) + + val apy = BigDecimal("5.0") + coEvery { repository.getCachedMarkets() } returns listOf( + YieldMarketToken( + tokenAddress = currency.contractAddress, + chainId = 137, + apy = apy, + isActive = true, + maxFeeNative = BigDecimal.ZERO, + maxFeeUSD = BigDecimal.ZERO, + backendId = "polygon-pos", + ), + ) + + val dispatcherProvider = testDispatcherProvider(this) + val useCase = YieldSupplyGetRewardsBalanceUseCase(repository, dispatcherProvider) + val appCurrency = AppCurrency.Default + + val deferred = async { useCase(status, appCurrency).take(2).toList() } + + testScheduler.advanceUntilIdle() + advanceTimeBy(TICK_MILLIS) + testScheduler.advanceUntilIdle() + + val emissions = deferred.await() + assertThat(emissions).hasSize(2) + + val apyFraction = apy.divide(BigDecimal("100"), 18, RoundingMode.HALF_UP) + val perTickCrypto = amount.multiply(apyFraction) + .multiply(YieldSupplyGetRewardsBalanceUseCase.TICK_SECONDS_BD) + .divide( + YieldSupplyGetRewardsBalanceUseCase.SECONDS_PER_YEAR_BD, + YieldSupplyGetRewardsBalanceUseCase.SCALE, + RoundingMode.HALF_UP, + ) + .abs() + val minCryptoDecimals = calculateMinVisibleDecimalsForTest(perTickCrypto).coerceAtMost(currency.decimals) + + val fiatAmountStart = amount.multiply(fiatRate) + val perTickFiat = fiatAmountStart.multiply(apyFraction) + .multiply(YieldSupplyGetRewardsBalanceUseCase.TICK_SECONDS_BD) + .divide( + YieldSupplyGetRewardsBalanceUseCase.SECONDS_PER_YEAR_BD, + YieldSupplyGetRewardsBalanceUseCase.SCALE, + RoundingMode.HALF_UP, + ) + .abs() + val minFiatDecimals = calculateMinVisibleDecimalsForTest(perTickFiat) + + val expectedCrypto0 = amount.format { + crypto(currency).anyDecimals( + maxDecimals = minCryptoDecimals, + minDecimals = minCryptoDecimals, + ) + } + val expectedFiat0 = fiatAmountStart.format { + fiat(appCurrency.code, appCurrency.symbol).anyDecimals(decimals = minFiatDecimals) + } + + assertThat(emissions[0].cryptoBalance).isEqualTo(expectedCrypto0) + assertThat(emissions[0].fiatBalance).isEqualTo(expectedFiat0) } private fun testDispatcherProvider(scope: TestScope): CoroutineDispatcherProvider { @@ -260,42 +383,48 @@ class YieldSupplyGetRewardsBalanceUseCaseTest { } private fun initialPerTickDelta(amount: BigDecimal, apy: BigDecimal): BigDecimal { - val apyFraction = apy.divide(HUNDRED_BD, SCALE, RoundingMode.HALF_UP) + val apyFraction = apy.divide( + YieldSupplyGetRewardsBalanceUseCase.HUNDRED_BD, + YieldSupplyGetRewardsBalanceUseCase.SCALE, + RoundingMode.HALF_UP, + ) return amount .multiply(apyFraction) - .multiply(TICK_SECONDS_BD) - .divide(SECONDS_PER_YEAR_BD, SCALE, RoundingMode.HALF_UP) + .multiply(YieldSupplyGetRewardsBalanceUseCase.TICK_SECONDS_BD) + .divide( + YieldSupplyGetRewardsBalanceUseCase.SECONDS_PER_YEAR_BD, + YieldSupplyGetRewardsBalanceUseCase.SCALE, + RoundingMode.HALF_UP, + ) .abs() } private fun nextBalance(current: BigDecimal, apy: BigDecimal): BigDecimal { - val apyFraction = apy.divide(HUNDRED_BD, SCALE, RoundingMode.HALF_UP) + val apyFraction = apy.divide( + YieldSupplyGetRewardsBalanceUseCase.HUNDRED_BD, + YieldSupplyGetRewardsBalanceUseCase.SCALE, + RoundingMode.HALF_UP, + ) val perTickDelta = current .multiply(apyFraction) - .multiply(TICK_SECONDS_BD) - .divide(SECONDS_PER_YEAR_BD, SCALE, RoundingMode.HALF_UP) + .multiply(YieldSupplyGetRewardsBalanceUseCase.TICK_SECONDS_BD) + .divide( + YieldSupplyGetRewardsBalanceUseCase.SECONDS_PER_YEAR_BD, + YieldSupplyGetRewardsBalanceUseCase.SCALE, + RoundingMode.HALF_UP, + ) return current.add(perTickDelta) } private fun calculateMinVisibleDecimalsForTest(perTickDeltaAbs: BigDecimal): Int { - if (perTickDeltaAbs <= BigDecimal.ZERO) return MIN_DECIMALS + if (perTickDeltaAbs <= BigDecimal.ZERO) return YieldSupplyGetRewardsBalanceUseCase.MIN_DECIMALS val perTickAsDouble = perTickDeltaAbs.toDouble() - if (perTickAsDouble.isNaN() || perTickAsDouble.isInfinite()) return MIN_DECIMALS - val safe = if (perTickAsDouble <= 0.0) EPSILON else perTickAsDouble - val raw = ceil(-ln(safe) / LN_10) - return raw.toInt().coerceIn(MIN_DECIMALS, MAX_DECIMALS) - } - - private companion object { - private const val SCALE = 18 - private val TICK_SECONDS_BD = BigDecimal("0.8") - private val SECONDS_PER_YEAR_BD = BigDecimal("31536000") - private val HUNDRED_BD = BigDecimal("100") - - private const val MIN_DECIMALS = 3 - private const val MAX_DECIMALS = 12 - - private val LN_10 = ln(10.0) - private const val EPSILON = 1e-18 + if (perTickAsDouble.isNaN() || perTickAsDouble.isInfinite()) return YieldSupplyGetRewardsBalanceUseCase.MIN_DECIMALS + val safe = if (perTickAsDouble <= 0.0) YieldSupplyGetRewardsBalanceUseCase.EPSILON else perTickAsDouble + val raw = ceil(-kotlin.math.ln(safe) / YieldSupplyGetRewardsBalanceUseCase.LN_10) + return raw.toInt().coerceIn( + YieldSupplyGetRewardsBalanceUseCase.MIN_DECIMALS, + YieldSupplyGetRewardsBalanceUseCase.FIAT_MAX_DECIMALS, + ) } } \ No newline at end of file diff --git a/features/tokendetails/impl/build.gradle.kts b/features/tokendetails/impl/build.gradle.kts index 2d9665f967..05ec40ad40 100644 --- a/features/tokendetails/impl/build.gradle.kts +++ b/features/tokendetails/impl/build.gradle.kts @@ -88,6 +88,7 @@ dependencies { implementation(projects.domain.wallets) implementation(projects.domain.wallets.models) implementation(projects.domain.yieldSupply) + implementation(projects.domain.yieldSupply.models) /** Temp dependency to swap domain */ implementation(projects.features.swap.domain) diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt index 4e70a2faa4..a221f254ec 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt @@ -121,7 +121,7 @@ internal object TokenDetailsPreviewData { selectedBalanceType = BalanceType.ALL, onBalanceSelect = {}, displayCryptoBalance = "966,96 XLM", - displayYeildSupplyCryptoBalance = null, + displayYieldSupplyFiatBalance = null, displayFiatBalance = "91,50$", isBalanceSelectorEnabled = true, isBalanceFlickering = false, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt index 3ef6e97629..16ce0aecda 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt @@ -75,6 +75,7 @@ import com.tangem.domain.wallets.usecase.GetExploreUrlUseCase import com.tangem.domain.wallets.usecase.GetExtendedPublicKeyForCurrencyUseCase import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.domain.wallets.usecase.NetworkHasDerivationUseCase +import com.tangem.domain.yield.supply.models.YieldSupplyRewardBalance import com.tangem.domain.yield.supply.usecase.YieldSupplyGetRewardsBalanceUseCase import com.tangem.feature.tokendetails.deeplink.TokenDetailsDeepLinkActionListener import com.tangem.feature.tokendetails.presentation.router.InnerTokenDetailsRouter @@ -416,7 +417,9 @@ internal class TokenDetailsModel @Inject constructor( .saveIn(yieldSupplyBalanceJobHolder) } else { yieldSupplyBalanceJobHolder.cancel() - internalUiState.value = stateFactory.getStateWithUpdatedYieldSupplyDisplayBalance(null) + internalUiState.value = stateFactory.getStateWithUpdatedYieldSupplyDisplayBalance( + YieldSupplyRewardBalance.empty(), + ) } } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsBalanceBlockState.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsBalanceBlockState.kt index a74d80f6b8..9e17b95c95 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsBalanceBlockState.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsBalanceBlockState.kt @@ -28,7 +28,8 @@ internal sealed class TokenDetailsBalanceBlockState { val isBalanceSelectorEnabled: Boolean, val isBalanceFlickering: Boolean, val yieldSupplyState: TokenDetailsYieldSupplyState = TokenDetailsYieldSupplyState.Empty, - val displayYeildSupplyCryptoBalance: String? = null, + val displayYieldSupplyFiatBalance: String? = null, + val displayYieldSupplyCryptoBalance: String? = null, ) : TokenDetailsBalanceBlockState() data class Error( diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt index 3243636297..306315e766 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt @@ -98,8 +98,8 @@ internal class TokenDetailsLoadedBalanceConverter( stakingCryptoAmount, currentState.selectedBalanceType, ), - displayYeildSupplyCryptoBalance = (currentState as? TokenDetailsBalanceBlockState.Content) - ?.displayYeildSupplyCryptoBalance, + displayYieldSupplyFiatBalance = (currentState as? TokenDetailsBalanceBlockState.Content) + ?.displayYieldSupplyFiatBalance, balanceSegmentedButtonConfig = currentState.balanceSegmentedButtonConfig, onBalanceSelect = clickIntents::onBalanceSelect, selectedBalanceType = currentState.selectedBalanceType, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt index e19dda0395..da13ce39ba 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt @@ -26,6 +26,7 @@ import com.tangem.domain.tokens.model.TokenActionsState import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.domain.wallets.usecase.NetworkHasDerivationUseCase +import com.tangem.domain.yield.supply.models.YieldSupplyRewardBalance import com.tangem.feature.tokendetails.presentation.tokendetails.model.TokenDetailsClickIntents import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenBalanceSegmentedButtonConfig import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsAppBarMenuConfig @@ -315,13 +316,18 @@ internal class TokenDetailsStateFactory( return balanceSelectStateConverter.convert(buttonConfig) } - fun getStateWithUpdatedYieldSupplyDisplayBalance(displayBalance: String?): TokenDetailsState { + fun getStateWithUpdatedYieldSupplyDisplayBalance( + yieldSupplyRewardBalance: YieldSupplyRewardBalance, + ): TokenDetailsState { val state = currentStateProvider() val balanceState = state.tokenBalanceBlockState return state.copy( tokenBalanceBlockState = when (balanceState) { is TokenDetailsBalanceBlockState.Content -> - balanceState.copy(displayYeildSupplyCryptoBalance = displayBalance) + balanceState.copy( + displayYieldSupplyFiatBalance = yieldSupplyRewardBalance.fiatBalance, + displayYieldSupplyCryptoBalance = yieldSupplyRewardBalance.cryptoBalance, + ) is TokenDetailsBalanceBlockState.Error -> balanceState is TokenDetailsBalanceBlockState.Loading -> balanceState }, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenDetailsBalanceBlock.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenDetailsBalanceBlock.kt index 9eaa517709..293e54545c 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenDetailsBalanceBlock.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenDetailsBalanceBlock.kt @@ -122,12 +122,12 @@ private fun FiatBalance( height = TangemTheme.dimens.size32, ), ) - is TokenDetailsBalanceBlockState.Content -> if (state.displayYeildSupplyCryptoBalance != null && + is TokenDetailsBalanceBlockState.Content -> if (state.displayYieldSupplyFiatBalance != null && !isBalanceHidden ) { TextAnimatedCounter( modifier = modifier, - text = state.displayYeildSupplyCryptoBalance, + text = state.displayYieldSupplyFiatBalance, style = TangemTheme.typography.h2.applyBladeBrush( isEnabled = state.isBalanceFlickering, textColor = TangemTheme.colors.text.primary1, @@ -136,7 +136,7 @@ private fun FiatBalance( } else { Text( modifier = modifier, - text = (state.displayYeildSupplyCryptoBalance ?: state.displayFiatBalance).orMaskWithStars( + text = (state.displayYieldSupplyFiatBalance ?: state.displayFiatBalance).orMaskWithStars( isBalanceHidden, ), style = TangemTheme.typography.h2.applyBladeBrush( @@ -184,8 +184,9 @@ private fun CryptoBalance( tint = TangemTheme.colors.icon.inactive, contentDescription = null, ) - Text( - text = state.displayCryptoBalance.orMaskWithStars(isBalanceHidden), + TextAnimatedCounter( + text = (state.displayYieldSupplyCryptoBalance ?: state.displayCryptoBalance) + .orMaskWithStars(isBalanceHidden), style = TangemTheme.typography.caption2.applyBladeBrush( isEnabled = state.isBalanceFlickering, textColor = TangemTheme.colors.text.tertiary, From aa9956d69f613b824febfbc22682f4908938927e Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 11 Dec 2025 11:44:10 +0500 Subject: [PATCH 5/7] Updated on 2026-08-14 --- .../features/yield/supply/impl/main/model/YieldSupplyModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/main/model/YieldSupplyModel.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/main/model/YieldSupplyModel.kt index c8f25dea1a..2c16d93d51 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/main/model/YieldSupplyModel.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/main/model/YieldSupplyModel.kt @@ -91,7 +91,7 @@ internal class YieldSupplyModel @Inject constructor( subscribeOnCurrencyStatusUpdates() singleNetworkStatusFetcher( params = SingleNetworkStatusFetcher.Params( - userWalletId = userWallet.walletId, + userWalletId = params.userWalletId, network = cryptoCurrency.network, ), ) From 8cee77705fa9ce2cad080047b399c5e13a1a49ca Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 12 Dec 2025 13:16:10 +0500 Subject: [PATCH 6/7] Updated on 2026-08-14 --- .../tap/di/domain/YieldSupplyDomainModule.kt | 6 ++++ .../currency/CryptoCurrencyExtensions.kt | 4 +-- .../YieldSupplyGetDustMinAmountUseCase.kt | 20 ++++++++++++ .../YieldSupplyGetDustMinAmountUseCaseTest.kt | 31 +++++++++++++++++++ .../active/model/YieldSupplyActiveModel.kt | 3 ++ .../YieldSupplyActiveMinAmountTransformer.kt | 6 ++-- .../impl/main/model/YieldSupplyModel.kt | 16 ++++++++-- 7 files changed, 80 insertions(+), 6 deletions(-) create mode 100644 domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetDustMinAmountUseCase.kt create mode 100644 domain/yield-supply/src/test/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetDustMinAmountUseCaseTest.kt diff --git a/app/src/main/java/com/tangem/tap/di/domain/YieldSupplyDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/YieldSupplyDomainModule.kt index 28f00e7e98..b8c7e6dfc7 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/YieldSupplyDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/YieldSupplyDomainModule.kt @@ -219,4 +219,10 @@ internal object YieldSupplyDomainModule { yieldSupplyRepository = yieldSupplyRepository, ) } + + @Provides + @Singleton + fun provideYieldSupplyGetDustMinAmountUseCase(): YieldSupplyGetDustMinAmountUseCase { + return YieldSupplyGetDustMinAmountUseCase() + } } \ No newline at end of file diff --git a/domain/models/src/main/kotlin/com/tangem/domain/models/currency/CryptoCurrencyExtensions.kt b/domain/models/src/main/kotlin/com/tangem/domain/models/currency/CryptoCurrencyExtensions.kt index 4937c3c0af..09c43362de 100644 --- a/domain/models/src/main/kotlin/com/tangem/domain/models/currency/CryptoCurrencyExtensions.kt +++ b/domain/models/src/main/kotlin/com/tangem/domain/models/currency/CryptoCurrencyExtensions.kt @@ -11,9 +11,9 @@ fun CryptoCurrencyStatus.hasNotSuppliedAmount(): Boolean { return notSupplied > BigDecimal.ZERO } -fun CryptoCurrencyStatus.shouldShowNotSuppliedInfoIcon(minAmount: BigDecimal): Boolean { +fun CryptoCurrencyStatus.shouldShowNotSuppliedInfoIcon(dustAmount: BigDecimal): Boolean { val notSupplied = notSuppliedAmountOrNull() ?: return false - return notSupplied >= minAmount + return notSupplied >= dustAmount } fun CryptoCurrencyStatus.notSuppliedAmountOrNull(): BigDecimal? { diff --git a/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetDustMinAmountUseCase.kt b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetDustMinAmountUseCase.kt new file mode 100644 index 0000000000..7814d60add --- /dev/null +++ b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetDustMinAmountUseCase.kt @@ -0,0 +1,20 @@ +package com.tangem.domain.yield.supply.usecase + +import com.tangem.domain.appcurrency.model.AppCurrency +import java.math.BigDecimal + +class YieldSupplyGetDustMinAmountUseCase { + + operator fun invoke(minAmount: BigDecimal, appCurrency: AppCurrency): BigDecimal { + return if (appCurrency.code in SUPPORTED_DUST_CURRENCIES) { + DUST_MIN_AMOUNT + } else { + minAmount.stripTrailingZeros() + } + } + + companion object { + private val DUST_MIN_AMOUNT = BigDecimal("0.1") + private val SUPPORTED_DUST_CURRENCIES = setOf("EUR", "USD", "AUD", "CAD", "GBP") + } +} \ No newline at end of file diff --git a/domain/yield-supply/src/test/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetDustMinAmountUseCaseTest.kt b/domain/yield-supply/src/test/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetDustMinAmountUseCaseTest.kt new file mode 100644 index 0000000000..50a00ac69c --- /dev/null +++ b/domain/yield-supply/src/test/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetDustMinAmountUseCaseTest.kt @@ -0,0 +1,31 @@ +package com.tangem.domain.yield.supply.usecase + +import com.google.common.truth.Truth.assertThat +import com.tangem.domain.appcurrency.model.AppCurrency +import org.junit.jupiter.api.Test +import java.math.BigDecimal + +class YieldSupplyGetDustMinAmountUseCaseTest { + + private val useCase = YieldSupplyGetDustMinAmountUseCase() + + @Test + fun `GIVEN supported currency WHEN invoke THEN return dust min amount`() { + val minAmount = BigDecimal("123.456") + val appCurrency = AppCurrency(code = "EUR", name = "Euro", symbol = "€") + + val result = useCase(minAmount, appCurrency) + + assertThat(result).isEqualTo(BigDecimal("0.1")) + } + + @Test + fun `GIVEN unsupported currency WHEN invoke THEN return min amount stripped`() { + val minAmount = BigDecimal("1.2300") + val appCurrency = AppCurrency(code = "JPY", name = "Japanese Yen", symbol = "¥") + + val result = useCase(minAmount, appCurrency) + + assertThat(result).isEqualTo(BigDecimal("1.23")) + } +} \ No newline at end of file diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/active/model/YieldSupplyActiveModel.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/active/model/YieldSupplyActiveModel.kt index 8dbc13d8c2..d468e6d60d 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/active/model/YieldSupplyActiveModel.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/active/model/YieldSupplyActiveModel.kt @@ -58,6 +58,7 @@ internal class YieldSupplyActiveModel @Inject constructor( private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase, private val urlOpener: UrlOpener, private val appRouter: AppRouter, + private val yieldSupplyGetDustMinAmountUseCase: YieldSupplyGetDustMinAmountUseCase, ) : Model(), YieldSupplyStopEarningComponent.ModelCallback, YieldSupplyApproveComponent.ModelCallback { @@ -241,11 +242,13 @@ internal class YieldSupplyActiveModel @Inject constructor( userWalletId, cryptoCurrencyStatusFlow.value, ).onRight { minAmount -> + val dustAmount = yieldSupplyGetDustMinAmountUseCase(minAmount = minAmount, appCurrency = appCurrency) uiState.update( YieldSupplyActiveMinAmountTransformer( cryptoCurrencyStatus = cryptoCurrencyStatusFlow.value, appCurrency = appCurrency, minAmount = minAmount, + dustMinAmount = dustAmount, analyticsHandler = analyticsHandler, onApprove = ::onApprove, ), diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/active/model/transformers/YieldSupplyActiveMinAmountTransformer.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/active/model/transformers/YieldSupplyActiveMinAmountTransformer.kt index c3b1fd4763..54dd971067 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/active/model/transformers/YieldSupplyActiveMinAmountTransformer.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/active/model/transformers/YieldSupplyActiveMinAmountTransformer.kt @@ -28,11 +28,12 @@ import java.math.BigDecimal * - Builds the fee policy note text using the minimum amount. * - Adds contextual notifications: * - Approval required notification when spending is not yet allowed (emits analytics on CTA). - * - "Not all amount supplied" info when wallet balance exceeds the supplied balance by more than [minAmount]. + * - "Not all amount supplied" info when the not-supplied balance exceeds the dust threshold [dustMinAmount]. * * @property cryptoCurrencyStatus Current currency status used to calculate values and flags. * @property appCurrency Preferred fiat currency for formatting. * @property minAmount Protocol-required minimal amount to deposit/supply (in crypto units). + * @property dustMinAmount Threshold used to detect dust/not-supplied balance (in crypto units). * @property analyticsHandler Analytics reporter for user actions. * @property onApprove Action invoked when the "Approve" notification button is tapped. */ @@ -40,6 +41,7 @@ internal class YieldSupplyActiveMinAmountTransformer( private val cryptoCurrencyStatus: CryptoCurrencyStatus, private val appCurrency: AppCurrency, private val minAmount: BigDecimal, + private val dustMinAmount: BigDecimal, private val analyticsHandler: AnalyticsEventHandler, private val onApprove: () -> Unit, ) : Transformer { @@ -89,7 +91,7 @@ internal class YieldSupplyActiveMinAmountTransformer( } private fun getNotSuppliedNotification(cryptoCurrencyStatus: CryptoCurrencyStatus): NotificationUM? { - return if (cryptoCurrencyStatus.shouldShowNotSuppliedInfoIcon(minAmount)) { + return if (cryptoCurrencyStatus.shouldShowNotSuppliedInfoIcon(dustMinAmount)) { val cryptoCurrency = cryptoCurrencyStatus.currency val notDepositedAmount = cryptoCurrencyStatus.notSuppliedAmountOrNull() val formattedAmount = diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/main/model/YieldSupplyModel.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/main/model/YieldSupplyModel.kt index 2c16d93d51..266b8f2df4 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/main/model/YieldSupplyModel.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/main/model/YieldSupplyModel.kt @@ -1,5 +1,6 @@ package com.tangem.features.yield.supply.impl.main.model +import arrow.core.getOrElse import com.tangem.common.routing.AppRoute import android.os.SystemClock import com.tangem.common.routing.AppRoute.YieldSupplyPromo @@ -12,6 +13,8 @@ import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.combinedReference import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.stringReference +import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase +import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.models.StatusSource import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus @@ -28,6 +31,7 @@ import com.tangem.domain.yield.supply.usecase.YieldSupplyActivateUseCase import com.tangem.domain.yield.supply.usecase.YieldSupplyDeactivateUseCase import com.tangem.domain.yield.supply.usecase.YieldSupplyGetTokenStatusUseCase import com.tangem.domain.yield.supply.usecase.YieldSupplyIsAvailableUseCase +import com.tangem.domain.yield.supply.usecase.YieldSupplyGetDustMinAmountUseCase import com.tangem.domain.yield.supply.usecase.YieldSupplyMinAmountUseCase import com.tangem.features.yield.supply.api.YieldSupplyComponent import com.tangem.features.yield.supply.api.analytics.YieldSupplyAnalytics @@ -55,6 +59,7 @@ internal class YieldSupplyModel @Inject constructor( override val dispatchers: CoroutineDispatcherProvider, private val analyticsEventsHandler: AnalyticsEventHandler, private val appRouter: AppRouter, + private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val getUserWalletUseCase: GetUserWalletUseCase, private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase, private val singleNetworkStatusFetcher: SingleNetworkStatusFetcher, @@ -65,6 +70,7 @@ internal class YieldSupplyModel @Inject constructor( private val yieldSupplyDeactivateUseCase: YieldSupplyDeactivateUseCase, private val yieldSupplyRepository: YieldSupplyRepository, private val yieldSupplyMinAmountUseCase: YieldSupplyMinAmountUseCase, + private val yieldSupplyGetDustMinAmountUseCase: YieldSupplyGetDustMinAmountUseCase, ) : Model(), YieldSupplyClickIntents { private val params = paramsContainer.require() @@ -73,6 +79,7 @@ internal class YieldSupplyModel @Inject constructor( field = MutableStateFlow(YieldSupplyUM.Initial) private val cryptoCurrency = params.cryptoCurrency + private var appCurrency: AppCurrency = AppCurrency.Default var userWallet: UserWallet by Delegates.notNull() private val fetchCurrencyJobHolder = JobHolder() @@ -85,7 +92,8 @@ internal class YieldSupplyModel @Inject constructor( } private fun checkIfYieldSupplyIsAvailable() { - modelScope.launch(dispatchers.io) { + modelScope.launch(dispatchers.default) { + appCurrency = getSelectedAppCurrencyUseCase.invokeSync().getOrElse { AppCurrency.Default } val isAvailable = yieldSupplyIsAvailableUseCase(params.userWalletId, params.cryptoCurrency) if (isAvailable) { subscribeOnCurrencyStatusUpdates() @@ -345,7 +353,11 @@ internal class YieldSupplyModel @Inject constructor( val minAmount = yieldSupplyMinAmountUseCase(userWalletId = userWallet.walletId, cryptoCurrencyStatus) .getOrNull() if (minAmount != null) { - cryptoCurrencyStatus.shouldShowNotSuppliedInfoIcon(minAmount) + val dustAmount = yieldSupplyGetDustMinAmountUseCase( + minAmount = minAmount, + appCurrency = appCurrency, + ) + cryptoCurrencyStatus.shouldShowNotSuppliedInfoIcon(dustAmount) } else { false } From afa13d836e7aca0cca44dfb10a375449b45a07c6 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 12 Dec 2025 19:07:37 +0700 Subject: [PATCH 7/7] Updated on 2026-08-14 --- .../di/WalletConnectDataModule.kt | 6 ----- .../network/ethereum/WcEthNetwork.kt | 4 +--- .../network/solana/WcSolanaNetwork.kt | 4 +--- .../pair/CaipNamespaceDelegate.kt | 4 +--- .../utils/WcNetworksConverter.kt | 22 +++++++++++++++---- .../entity/common/WcCommonTransactionModel.kt | 4 ++++ .../transaction/model/WcAddNetworkModel.kt | 2 +- 7 files changed, 26 insertions(+), 20 deletions(-) diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/di/WalletConnectDataModule.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/di/WalletConnectDataModule.kt index b0e9c01c6e..9b04683948 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/di/WalletConnectDataModule.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/di/WalletConnectDataModule.kt @@ -148,14 +148,12 @@ internal object WalletConnectDataModule { @SdkMoshi moshi: Moshi, sessionsManager: WcSessionsManager, factories: WcEthNetwork.Factories, - walletManagersFacade: WalletManagersFacade, wcNetworksConverter: WcNetworksConverter, ): WcEthNetwork = WcEthNetwork( moshi = moshi, networksConverter = wcNetworksConverter, sessionsManager = sessionsManager, factories = factories, - walletManagersFacade = walletManagersFacade, ) @Provides @@ -165,24 +163,20 @@ internal object WalletConnectDataModule { wcNetworksConverter: WcNetworksConverter, sessionsManager: WcSessionsManager, factories: WcSolanaNetwork.Factories, - walletManagersFacade: WalletManagersFacade, ): WcSolanaNetwork = WcSolanaNetwork( moshi = moshi, sessionsManager = sessionsManager, factories = factories, networksConverter = wcNetworksConverter, - walletManagersFacade = walletManagersFacade, ) @Provides @Singleton fun caipNamespaceDelegate( namespaceConverters: Set<@JvmSuppressWildcards WcNamespaceConverter>, - walletManagersFacade: WalletManagersFacade, wcNetworksConverter: WcNetworksConverter, ): CaipNamespaceDelegate = CaipNamespaceDelegate( namespaceConverters = namespaceConverters, - walletManagersFacade = walletManagersFacade, wcNetworksConverter = wcNetworksConverter, ) diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthNetwork.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthNetwork.kt index a14460a566..b713e9f18b 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthNetwork.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthNetwork.kt @@ -18,7 +18,6 @@ import com.tangem.domain.walletconnect.model.* import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest import com.tangem.domain.walletconnect.repository.WcSessionsManager import com.tangem.domain.walletconnect.usecase.method.WcMethodUseCase -import com.tangem.domain.walletmanager.WalletManagersFacade import jakarta.inject.Inject internal class WcEthNetwork( @@ -26,7 +25,6 @@ internal class WcEthNetwork( private val sessionsManager: WcSessionsManager, private val factories: Factories, private val networksConverter: WcNetworksConverter, - private val walletManagersFacade: WalletManagersFacade, ) : WcRequestToUseCaseConverter { override fun toWcMethodName(request: WcSdkSessionRequest): WcEthMethodName? { @@ -57,7 +55,7 @@ internal class WcEthNetwork( is WcEthMethod.SwitchEthereumChain, -> anyExistNetwork() - ?.let { network -> walletManagersFacade.getDefaultAddress(wallet.walletId, network).orEmpty() } + ?.let { network -> networksConverter.getAddressForWC(wallet.walletId, network).orEmpty() } .orEmpty() } val walletNetwork = when (method) { diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/solana/WcSolanaNetwork.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/solana/WcSolanaNetwork.kt index ebaf668ba0..db6f83849d 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/solana/WcSolanaNetwork.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/solana/WcSolanaNetwork.kt @@ -22,7 +22,6 @@ import com.tangem.domain.walletconnect.model.WcSolanaMethodName import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest import com.tangem.domain.walletconnect.repository.WcSessionsManager import com.tangem.domain.walletconnect.usecase.method.WcMethodUseCase -import com.tangem.domain.walletmanager.WalletManagersFacade import jakarta.inject.Inject internal class WcSolanaNetwork( @@ -30,7 +29,6 @@ internal class WcSolanaNetwork( private val sessionsManager: WcSessionsManager, private val factories: Factories, private val networksConverter: WcNetworksConverter, - private val walletManagersFacade: WalletManagersFacade, ) : WcRequestToUseCaseConverter { override fun toWcMethodName(request: WcSdkSessionRequest): WcSolanaMethodName? { @@ -52,7 +50,7 @@ internal class WcSolanaNetwork( val chainId = request.chainId.orEmpty() suspend fun anyExistNetwork() = networksConverter.mainOrAnyWalletNetworkForRequest(chainId, wallet) suspend fun anyAddress() = anyExistNetwork() - ?.let { network -> walletManagersFacade.getDefaultAddress(wallet.walletId, network).orEmpty() } + ?.let { network -> networksConverter.getAddressForWC(wallet.walletId, network).orEmpty() } .orEmpty() val accountAddress = when (method) { diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/CaipNamespaceDelegate.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/CaipNamespaceDelegate.kt index 4b5d2c336a..3013709c9f 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/CaipNamespaceDelegate.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/CaipNamespaceDelegate.kt @@ -9,11 +9,9 @@ import com.tangem.data.walletconnect.utils.WcNetworksConverter import com.tangem.domain.models.network.Network import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.walletconnect.model.WcSessionApprove -import com.tangem.domain.walletmanager.WalletManagersFacade internal class CaipNamespaceDelegate( private val namespaceConverters: Set, - private val walletManagersFacade: WalletManagersFacade, private val wcNetworksConverter: WcNetworksConverter, ) { @@ -36,7 +34,7 @@ internal class CaipNamespaceDelegate( } suspend fun createCAIP10(userWalletId: UserWalletId, network: Network): CAIP10? { - val address = walletManagersFacade.getDefaultAddress(userWalletId, network) + val address = wcNetworksConverter.getAddressForWC(userWalletId, network) val chainId = allWcNetworks .find { (wcNetwork, _) -> network.rawId == wcNetwork.rawId } ?.second diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/WcNetworksConverter.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/WcNetworksConverter.kt index 880b142dbd..a494eb772e 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/WcNetworksConverter.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/WcNetworksConverter.kt @@ -1,6 +1,9 @@ package com.tangem.data.walletconnect.utils import com.reown.walletkit.client.Wallet +import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchain.common.address.AddressType +import com.tangem.blockchainsdk.utils.toBlockchain import com.tangem.data.common.currency.isCustomCoin import com.tangem.data.walletconnect.model.CAIP10 import com.tangem.domain.account.producer.SingleAccountProducer @@ -44,7 +47,7 @@ internal class WcNetworksConverter @Inject constructor( val allCoinNetwork = filterWalletNetworkForRequest(request.chainId.orEmpty(), session.wallet) val requestNetwork = allCoinNetwork.find { network -> - val address = walletManagersFacade.getDefaultAddress(wallet.walletId, network) + val address = getAddressForWC(wallet.walletId, network) requestAddress.lowercase() == address?.lowercase() } return requestNetwork @@ -60,7 +63,18 @@ internal class WcNetworksConverter @Inject constructor( suspend fun allAddressForChain(rawChainId: String, wallet: UserWallet): List { return filterWalletNetworkForRequest(rawChainId, wallet) - .mapNotNull { walletManagersFacade.getDefaultAddress(wallet.walletId, it)?.lowercase() } + .mapNotNull { getAddressForWC(wallet.walletId, it)?.lowercase() } + } + + suspend fun getAddressForWC(userWalletId: UserWalletId, network: Network): String? { + return when (network.toBlockchain()) { + Blockchain.XDC, + Blockchain.XDCTestnet, + -> walletManagersFacade.getAddresses(userWalletId, network) + .find { address -> address.type == AddressType.Legacy } + ?.value + else -> walletManagersFacade.getDefaultAddress(userWalletId, network) + } } /** @@ -94,8 +108,8 @@ internal class WcNetworksConverter @Inject constructor( // find all derivation .filter { it.rawId == blockchain.id } // find equal address - .firstOrNull { - val walletAddress = walletManagersFacade.getDefaultAddress(wallet.walletId, it) + .firstOrNull { network -> + val walletAddress = getAddressForWC(wallet.walletId, network) walletAddress?.lowercase() == caip10.accountAddress.lowercase() } } diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/entity/common/WcCommonTransactionModel.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/entity/common/WcCommonTransactionModel.kt index 5acb41c803..038b9106de 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/entity/common/WcCommonTransactionModel.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/entity/common/WcCommonTransactionModel.kt @@ -20,4 +20,8 @@ internal interface WcCommonTransactionModel { fun showSuccessSignMessage(message: TextReference = resourceReference(R.string.wc_successfully_signed)) { messageSender.send(ToastMessage(message = message)) } + + fun showSuccessAddedMessage(message: TextReference = resourceReference(R.string.common_added)) { + messageSender.send(ToastMessage(message = message)) + } } \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcAddNetworkModel.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcAddNetworkModel.kt index 08176516ce..8ed61a1b5d 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcAddNetworkModel.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcAddNetworkModel.kt @@ -111,7 +111,7 @@ internal class WcAddNetworkModel @Inject constructor( modelScope.launch { _uiState.update { it?.copy(transaction = it.transaction.copy(isLoading = true)) } useCase.approve().getOrNull()?.let { - showSuccessSignMessage() + showSuccessAddedMessage() router.pop() } ?: run { _uiState.update { it?.copy(transaction = it.transaction.copy(isLoading = false)) }