Updated on 2026-08-14

This commit is contained in:
Tangem 2025-07-30 12:33:41 +05:00
parent c8c61bb9d6
commit 4089fe43ac
6 changed files with 127 additions and 41 deletions

View file

@ -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<WcSendTransactionUMConverter.Input, WcSendTransactionUM?> {
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,
)
}

View file

@ -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

View file

@ -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(

View file

@ -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) {

View file

@ -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"),
),
),
),
)

View file

@ -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,
)
}
}