From 4089fe43ac58b106dbcbf8c30fb34da30f5fd98f Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 30 Jul 2025 12:33:41 +0500 Subject: [PATCH] Updated on 2026-08-14 --- .../converter/WcSendTransactionUMConverter.kt | 81 +++++++++++-------- .../entity/send/WcSendTransactionUM.kt | 2 + .../model/WcSendTransactionModel.kt | 12 ++- .../ui/common/WcSendTransactionItems.kt | 4 +- .../send/WcSendTransactionModalBottomSheet.kt | 30 ++++++- .../utils/WcNotificationsFactory.kt | 39 +++++++++ 6 files changed, 127 insertions(+), 41 deletions(-) create mode 100644 features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/utils/WcNotificationsFactory.kt diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcSendTransactionUMConverter.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcSendTransactionUMConverter.kt index c5f6ec3ed8..ccd8f0a447 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcSendTransactionUMConverter.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcSendTransactionUMConverter.kt @@ -1,5 +1,6 @@ package com.tangem.features.walletconnect.transaction.converter +import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.walletconnect.model.WcEthMethod import com.tangem.domain.walletconnect.model.WcSolanaMethod import com.tangem.domain.walletconnect.usecase.method.WcMethodContext @@ -12,6 +13,7 @@ import com.tangem.features.walletconnect.transaction.entity.common.WcTransaction import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionRequestInfoUM import com.tangem.features.walletconnect.transaction.entity.send.WcSendTransactionItemUM import com.tangem.features.walletconnect.transaction.entity.send.WcSendTransactionUM +import com.tangem.features.walletconnect.utils.WcNotificationsFactory import com.tangem.utils.converter.Converter import kotlinx.collections.immutable.toImmutableList import javax.inject.Inject @@ -20,44 +22,52 @@ internal class WcSendTransactionUMConverter @Inject constructor( private val appInfoContentUMConverter: WcTransactionAppInfoContentUMConverter, private val networkInfoUMConverter: WcNetworkInfoUMConverter, private val requestBlockUMConverter: WcTransactionRequestBlockUMConverter, + private val notificationsFactory: WcNotificationsFactory, ) : Converter { - override fun convert(value: Input): WcSendTransactionUM? = when (value.context.method) { - is WcEthMethod.SendTransaction, - is WcEthMethod.SignTransaction, - is WcSolanaMethod.SignAllTransaction, - is WcSolanaMethod.SignTransaction, - -> WcSendTransactionUM( - transaction = WcSendTransactionItemUM( - onDismiss = value.actions.onDismiss, - onSend = value.actions.onSign, - appInfo = appInfoContentUMConverter.convert( - WcTransactionAppInfoContentUMConverter.Input( - session = value.context.session, - onShowVerifiedAlert = value.actions.onShowVerifiedAlert, - ), - ), - feeState = value.feeState, - walletName = value.context.session.wallet.name.takeIf { value.context.session.showWalletInfo }, - networkInfo = networkInfoUMConverter.convert(value.context.network), - estimatedWalletChanges = WcSendReceiveTransactionCheckResultsUM(), - isLoading = value.signState.domainStep == WcSignStep.Signing, - address = WcAddressConverter.convert(value.context.derivationState), - sendEnabled = value.feeSelectorUM is FeeSelectorUM.Content, - ), - feeSelectorUM = value.feeSelectorUM ?: FeeSelectorUM.Loading, - transactionRequestInfo = WcTransactionRequestInfoUM( - blocks = buildList { - addAll( - requestBlockUMConverter.convert( - WcTransactionRequestBlockUMConverter.Input(value.context.rawSdkRequest), - ), - ) - }.toImmutableList(), - onCopy = value.actions.onCopy, - ), + override fun convert(value: Input): WcSendTransactionUM? { + val feeExceedsBalance = notificationsFactory.createFeeExceedsBalance( + cryptoCurrencyStatus = value.cryptoCurrencyStatus, + feeSelectorUM = value.feeSelectorUM, ) - else -> null + return when (value.context.method) { + is WcEthMethod.SendTransaction, + is WcEthMethod.SignTransaction, + is WcSolanaMethod.SignAllTransaction, + is WcSolanaMethod.SignTransaction, + -> WcSendTransactionUM( + transaction = WcSendTransactionItemUM( + onDismiss = value.actions.onDismiss, + onSend = value.actions.onSign, + appInfo = appInfoContentUMConverter.convert( + WcTransactionAppInfoContentUMConverter.Input( + session = value.context.session, + onShowVerifiedAlert = value.actions.onShowVerifiedAlert, + ), + ), + feeState = value.feeState, + walletName = value.context.session.wallet.name.takeIf { value.context.session.showWalletInfo }, + networkInfo = networkInfoUMConverter.convert(value.context.network), + estimatedWalletChanges = WcSendReceiveTransactionCheckResultsUM(), + isLoading = value.signState.domainStep == WcSignStep.Signing, + address = WcAddressConverter.convert(value.context.derivationState), + sendEnabled = value.feeSelectorUM is FeeSelectorUM.Content && feeExceedsBalance == null, + feeExceedsBalanceNotification = feeExceedsBalance, + ), + feeSelectorUM = value.feeSelectorUM ?: FeeSelectorUM.Loading, + transactionRequestInfo = WcTransactionRequestInfoUM( + blocks = buildList { + addAll( + requestBlockUMConverter.convert( + WcTransactionRequestBlockUMConverter.Input(value.context.rawSdkRequest), + ), + ) + }.toImmutableList(), + onCopy = value.actions.onCopy, + ), + ) + else -> null + } } data class Input( @@ -66,5 +76,6 @@ internal class WcSendTransactionUMConverter @Inject constructor( val signState: WcSignState<*>, val actions: WcTransactionActionsUM, val feeSelectorUM: FeeSelectorUM?, + val cryptoCurrencyStatus: CryptoCurrencyStatus, ) } \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/entity/send/WcSendTransactionUM.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/entity/send/WcSendTransactionUM.kt index 271f0cd292..b6ace6d0ba 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/entity/send/WcSendTransactionUM.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/entity/send/WcSendTransactionUM.kt @@ -1,5 +1,6 @@ package com.tangem.features.walletconnect.transaction.entity.send +import com.tangem.common.ui.notifications.NotificationUM import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent import com.tangem.features.send.v2.api.entity.FeeSelectorUM import com.tangem.features.walletconnect.transaction.entity.approve.WcSpendAllowanceUM @@ -27,5 +28,6 @@ internal data class WcSendTransactionItemUM( val networkInfo: WcNetworkInfoUM, val address: String?, val sendEnabled: Boolean, + val feeExceedsBalanceNotification: NotificationUM.Info?, val isLoading: Boolean = false, ) : TangemBottomSheetConfigContent \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSendTransactionModel.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSendTransactionModel.kt index a5e0db4044..ed77d9578a 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSendTransactionModel.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSendTransactionModel.kt @@ -47,6 +47,7 @@ import com.tangem.features.walletconnect.transaction.entity.common.WcTransaction import com.tangem.features.walletconnect.transaction.entity.send.WcSendTransactionUM import com.tangem.features.walletconnect.transaction.routes.WcTransactionRoutes import com.tangem.features.walletconnect.transaction.ui.blockaid.WcSendAndReceiveBlockAidUiConverter +import com.tangem.features.walletconnect.utils.WcNotificationsFactory import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch @@ -69,6 +70,7 @@ internal class WcSendTransactionModel @Inject constructor( private val blockAidUiConverter: WcSendAndReceiveBlockAidUiConverter, private val getFeeUseCase: GetFeeUseCase, private val getNetworkCoinUseCase: GetNetworkCoinStatusUseCase, + private val notificationsFactory: WcNotificationsFactory, private val analytics: AnalyticsEventHandler, ) : Model(), WcCommonTransactionModel, FeeSelectorModelCallback { @@ -159,10 +161,17 @@ internal class WcSendTransactionModel @Inject constructor( * Also handles fee results from FeeSelectorBlockComponent */ fun updateFee(feeSelectorUM: FeeSelectorUM) { + val feeExceedsBalance = notificationsFactory.createFeeExceedsBalance( + cryptoCurrencyStatus = cryptoCurrencyStatus, + feeSelectorUM = feeSelectorUM, + ) _uiState.update { it?.copy( feeSelectorUM = feeSelectorUM, - transaction = it.transaction.copy(sendEnabled = feeSelectorUM is FeeSelectorUM.Content), + transaction = it.transaction.copy( + sendEnabled = feeSelectorUM is FeeSelectorUM.Content && feeExceedsBalance == null, + feeExceedsBalanceNotification = feeExceedsBalance, + ), ) } val fee = (feeSelectorUM as? FeeSelectorUM.Content)?.selectedFeeItem?.fee ?: return @@ -227,6 +236,7 @@ internal class WcSendTransactionModel @Inject constructor( signState = signState, actions = actions, feeSelectorUM = uiState.value?.feeSelectorUM, + cryptoCurrencyStatus = cryptoCurrencyStatus, ), ) transactionUM = transactionUM?.copy( diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/common/WcSendTransactionItems.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/common/WcSendTransactionItems.kt index b8cd6c5c85..50f7f9e106 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/common/WcSendTransactionItems.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/common/WcSendTransactionItems.kt @@ -18,12 +18,14 @@ import com.tangem.features.send.v2.api.FeeSelectorBlockComponent import com.tangem.features.walletconnect.transaction.entity.common.WcNetworkInfoUM import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionFeeState +@Suppress("LongParameterList") @Composable internal fun WcSendTransactionItems( walletName: String?, networkInfo: WcNetworkInfoUM, feeState: WcTransactionFeeState, feeSelectorBlockComponent: FeeSelectorBlockComponent?, + feeExceedsBalance: Boolean, address: String?, modifier: Modifier = Modifier, ) { @@ -62,7 +64,7 @@ internal fun WcSendTransactionItems( address = address, ) } - if (feeState != WcTransactionFeeState.None) { + if (feeState != WcTransactionFeeState.None && !feeExceedsBalance) { DividerWithPadding(start = 40.dp, end = 12.dp) feeSelectorBlockComponent?.Content( modifier = if (onFeeBlockClicked != null) { diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/send/WcSendTransactionModalBottomSheet.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/send/WcSendTransactionModalBottomSheet.kt index 4fd316dde9..1a28add4aa 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/send/WcSendTransactionModalBottomSheet.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/send/WcSendTransactionModalBottomSheet.kt @@ -4,7 +4,9 @@ import android.content.res.Configuration import androidx.compose.animation.animateContentSize import androidx.compose.foundation.background import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -14,14 +16,17 @@ 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 com.tangem.common.ui.notifications.NotificationUM import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheet import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetTitle import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetWithFooter import com.tangem.core.ui.components.divider.DividerWithPadding +import com.tangem.core.ui.components.notifications.Notification import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.features.send.v2.api.FeeSelectorBlockComponent @@ -101,14 +106,22 @@ internal fun WcSendTransactionModalBottomSheet( if (state.estimatedWalletChanges != null) { TransactionCheckResultsItem(state.estimatedWalletChanges, onClickAllowToSpend) } - Spacer(Modifier.height(16.dp)) WcSendTransactionItems( + modifier = Modifier.padding(top = 16.dp), walletName = state.walletName, networkInfo = state.networkInfo, feeState = state.feeState, feeSelectorBlockComponent = feeSelectorBlockComponent, + feeExceedsBalance = state.feeExceedsBalanceNotification != null, address = state.address, ) + if (state.feeExceedsBalanceNotification != null) { + Notification( + modifier = Modifier.padding(top = 14.dp), + config = state.feeExceedsBalanceNotification.config, + iconTint = TangemTheme.colors.icon.warning, + ) + } } } }, @@ -199,9 +212,10 @@ private class WcSendTransactionStateProvider : CollectionPreviewParameterProvide ), walletName = "Tangem 2.0 Tangem 2.0 Tangem 2", networkInfo = WcNetworkInfoUM(name = "Optimistic Ethereum Network", iconRes = R.drawable.img_eth_22), - feeState = WcTransactionFeeState.Success(null, {}), + feeState = WcTransactionFeeState.Success(dAppFee = null, onClick = {}), address = null, sendEnabled = true, + feeExceedsBalanceNotification = null, ), WcSendTransactionItemUM( onDismiss = {}, @@ -237,9 +251,13 @@ private class WcSendTransactionStateProvider : CollectionPreviewParameterProvide ), walletName = "Tangem 2.0", networkInfo = WcNetworkInfoUM(name = "Ethereum", iconRes = R.drawable.img_eth_22), - feeState = WcTransactionFeeState.Success(null, {}), + feeState = WcTransactionFeeState.Success(dAppFee = null, onClick = {}), address = "0xdac17f958d2ee523a2206206994597c13d831ec7", sendEnabled = true, + feeExceedsBalanceNotification = NotificationUM.Info( + title = stringReference("Insufficient Ethereum"), + subtitle = stringReference("Top up your balance to cover the network fee"), + ), ), WcSendTransactionItemUM( onDismiss = {}, @@ -278,6 +296,10 @@ private class WcSendTransactionStateProvider : CollectionPreviewParameterProvide feeState = WcTransactionFeeState.None, address = null, sendEnabled = false, + feeExceedsBalanceNotification = NotificationUM.Info( + title = stringReference("Insufficient Ethereum"), + subtitle = stringReference("Top up your balance to cover the network fee"), + ), ), ), ) \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/utils/WcNotificationsFactory.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/utils/WcNotificationsFactory.kt new file mode 100644 index 0000000000..db5e1c5ed9 --- /dev/null +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/utils/WcNotificationsFactory.kt @@ -0,0 +1,39 @@ +package com.tangem.features.walletconnect.utils + +import com.tangem.blockchain.common.transaction.TransactionFee +import com.tangem.common.ui.notifications.NotificationUM +import com.tangem.core.ui.extensions.stringReference +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.features.send.v2.api.entity.FeeSelectorUM +import com.tangem.features.send.v2.api.subcomponents.feeSelector.utils.FeeCalculationUtils +import javax.inject.Inject + +internal class WcNotificationsFactory @Inject constructor() { + + fun createFeeExceedsBalance( + cryptoCurrencyStatus: CryptoCurrencyStatus, + feeSelectorUM: FeeSelectorUM?, + ): NotificationUM.Info? { + // TODO: [REDACTED_TASK_KEY] localization + return NotificationUM.Info( + title = stringReference("Insufficient ${cryptoCurrencyStatus.currency.name}"), + subtitle = stringReference("Top up your balance to cover the network fee"), + ).takeIf { isFeeExceedsBalance(cryptoCurrencyStatus = cryptoCurrencyStatus, feeSelectorUM = feeSelectorUM) } + } + + private fun isFeeExceedsBalance( + cryptoCurrencyStatus: CryptoCurrencyStatus, + feeSelectorUM: FeeSelectorUM?, + ): Boolean { + val feeSelectorContent = feeSelectorUM as? FeeSelectorUM.Content ?: return false + val lowestFee = when (val fees = feeSelectorContent.fees) { + is TransactionFee.Choosable -> fees.minimum + is TransactionFee.Single -> fees.normal + } + + return FeeCalculationUtils.checkExceedBalance( + feeBalance = cryptoCurrencyStatus.value.amount, + feeAmount = lowestFee.amount.value, + ) + } +} \ No newline at end of file