Updated on 2026-08-14

This commit is contained in:
Tangem 2025-07-01 13:39:08 +05:00
parent 566c06095d
commit 8aa6ce0100
43 changed files with 274 additions and 146 deletions

View file

@ -15,6 +15,7 @@ import com.tangem.core.decompose.context.childByContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.features.send.v2.api.FeeSelectorBlockComponent
import com.tangem.features.send.v2.api.FeeSelectorComponent
import com.tangem.features.walletconnect.components.WcRoutingComponent
import com.tangem.features.walletconnect.connections.components.AlertsComponent
import com.tangem.features.walletconnect.connections.components.WcPairComponent
@ -31,6 +32,7 @@ internal class DefaultWcRoutingComponent @AssistedInject constructor(
@Assisted private val appComponentContext: AppComponentContext,
@Assisted params: Unit,
private val feeSelectorBlockComponentFactory: FeeSelectorBlockComponent.Factory,
private val feeSelectorComponentFactory: FeeSelectorComponent.Factory,
) : AppComponentContext by appComponentContext, WcRoutingComponent {
private val model: WcRoutingModel = getOrCreateModel()
@ -73,6 +75,7 @@ internal class DefaultWcRoutingComponent @AssistedInject constructor(
appComponentContext = childContext,
params = WcTransactionModelParams(config.rawRequest),
feeSelectorBlockComponentFactory = feeSelectorBlockComponentFactory,
feeSelectorComponentFactory = feeSelectorComponentFactory,
)
is WcInnerRoute.Pair -> WcPairComponent(
appComponentContext = childContext,

View file

@ -15,9 +15,14 @@ import androidx.compose.ui.unit.dp
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.send.v2.api.FeeSelectorBlockComponent
import com.tangem.features.send.v2.api.entity.FeeSelectorUM
import com.tangem.features.walletconnect.impl.R
internal class PreviewFeeSelectorBlockComponent : FeeSelectorBlockComponent {
override fun updateState(feeSelectorUM: FeeSelectorUM) {
/** No-op */
}
@Composable
override fun Content(modifier: Modifier) {
Row(

View file

@ -61,8 +61,9 @@ internal abstract class WcCommonTransactionComponentDelegate(
is WcTransactionRoutes.TransactionRequestInfo,
is WcTransactionRoutes.Alert,
is WcTransactionRoutes.CustomAllowance,
is WcTransactionRoutes.SelectFee,
-> stackNavigation?.pop()
else -> Unit
null -> Unit
}
}
}

View file

@ -2,6 +2,8 @@ package com.tangem.features.walletconnect.transaction.components.common
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.features.send.v2.api.FeeSelectorComponent
import com.tangem.features.send.v2.api.params.FeeSelectorParams
import com.tangem.features.walletconnect.connections.components.AlertsComponentV2
import com.tangem.features.walletconnect.connections.utils.WcAlertsFactory.createCommonTransactionAppInfoAlertUM
import com.tangem.features.walletconnect.transaction.components.send.WcCustomAllowanceComponent
@ -14,6 +16,7 @@ internal fun getWcCommonScreen(
appComponentContext: AppComponentContext,
transactionScreenConverter: () -> ComposableBottomSheetComponent,
model: WcCommonTransactionModel,
feeSelectorComponentFactory: FeeSelectorComponent.Factory? = null,
): ComposableBottomSheetComponent {
return when (config) {
is WcTransactionRoutes.Transaction -> transactionScreenConverter()
@ -32,5 +35,20 @@ internal fun getWcCommonScreen(
(model as? WcSendTransactionModel)?.onClickDoneCustomAllowance(value, isUnlimited)
},
)
is WcTransactionRoutes.SelectFee -> {
requireNotNull(feeSelectorComponentFactory) { "feeSelectorComponentFactory must not be null" }
val model = model as? WcSendTransactionModel ?: error("model must be WcSendTransactionModel")
val state = model.uiState.value ?: error("in this step state should be not null")
feeSelectorComponentFactory.create(
context = appComponentContext,
params = FeeSelectorParams.FeeSelectorDetailsParams(
state = state.feeSelectorUM,
onLoadFee = model::loadFee,
cryptoCurrencyStatus = model.cryptoCurrencyStatus,
callback = model,
suggestedFeeState = model.suggestedFeeState,
),
)
}
}
}

View file

@ -3,10 +3,12 @@ package com.tangem.features.walletconnect.transaction.components.send
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.arkivanov.essenty.lifecycle.doOnResume
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.features.send.v2.api.FeeSelectorBlockComponent
import com.tangem.features.send.v2.api.params.FeeSelectorParams
import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionFeeState
import com.tangem.features.walletconnect.transaction.model.WcSendTransactionModel
import com.tangem.features.walletconnect.transaction.ui.send.WcSendTransactionModalBottomSheet
@ -17,17 +19,28 @@ internal class WcSendTransactionComponent(
) : AppComponentContext by appComponentContext, ComposableBottomSheetComponent {
private val feeSelectorBlockComponent by lazy {
val state = requireNotNull(model.uiState.value) { "in this step state should be not null" }
feeSelectorBlockComponentFactory.create(
context = appComponentContext,
params = FeeSelectorParams.FeeSelectorBlockParams(
state = state.feeSelectorUM,
onLoadFee = model::loadFee,
network = model.useCase.network,
cryptoCurrencyStatus = model.cryptoCurrencyStatus,
callback = model,
suggestedFeeState = model.suggestedFeeState,
),
)
}
init {
lifecycle.doOnResume {
val state = model.uiState.value
if (state?.transaction?.feeState is WcTransactionFeeState.Success) {
feeSelectorBlockComponent.updateState(state.feeSelectorUM)
}
}
}
override fun dismiss() {
model.dismiss()
}

View file

@ -6,6 +6,7 @@ import com.tangem.core.decompose.context.childByContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.features.send.v2.api.FeeSelectorBlockComponent
import com.tangem.features.send.v2.api.FeeSelectorComponent
import com.tangem.features.walletconnect.transaction.components.common.WcCommonTransactionComponentDelegate
import com.tangem.features.walletconnect.transaction.components.common.WcTransactionModelParams
import com.tangem.features.walletconnect.transaction.components.common.getWcCommonScreen
@ -16,6 +17,7 @@ internal class WcSendTransactionContainerComponent(
appComponentContext: AppComponentContext,
params: WcTransactionModelParams,
private val feeSelectorBlockComponentFactory: FeeSelectorBlockComponent.Factory,
private val feeSelectorComponentFactory: FeeSelectorComponent.Factory,
) : WcCommonTransactionComponentDelegate(appComponentContext) {
private val model: WcSendTransactionModel = getOrCreateModel(params = params)
@ -38,6 +40,7 @@ internal class WcSendTransactionContainerComponent(
appComponentContext = appComponentContext,
transactionScreenConverter = { createTransactionComponent(appComponentContext) },
model = model,
feeSelectorComponentFactory = feeSelectorComponentFactory,
)
}

View file

@ -6,6 +6,7 @@ import com.tangem.domain.walletconnect.usecase.method.WcMessageSignUseCase
import com.tangem.domain.walletconnect.usecase.method.WcSignState
import com.tangem.domain.walletconnect.usecase.method.WcSignUseCase
import com.tangem.domain.walletconnect.usecase.method.WcTransactionUseCase
import com.tangem.features.send.v2.api.entity.FeeSelectorUM
import com.tangem.features.walletconnect.transaction.entity.common.WcCommonTransactionUM
import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionActionsUM
import com.tangem.utils.converter.Converter
@ -45,6 +46,7 @@ internal class WcCommonTransactionUMConverter @Inject constructor(
useCase = value.useCase,
signState = value.signState,
actions = value.actions,
feeSelectorUM = value.feeSelectorUM,
),
)
else -> null
@ -55,5 +57,6 @@ internal class WcCommonTransactionUMConverter @Inject constructor(
val useCase: WcSignUseCase<*>,
val signState: WcSignState<*>,
val actions: WcTransactionActionsUM,
val feeSelectorUM: FeeSelectorUM? = null,
)
}

View file

@ -7,6 +7,7 @@ import com.tangem.domain.walletconnect.usecase.method.WcMutableFee
import com.tangem.domain.walletconnect.usecase.method.WcSignState
import com.tangem.domain.walletconnect.usecase.method.WcSignStep
import com.tangem.domain.walletconnect.usecase.method.WcTransactionUseCase
import com.tangem.features.send.v2.api.entity.FeeSelectorUM
import com.tangem.features.walletconnect.impl.R
import com.tangem.features.walletconnect.transaction.entity.blockaid.WcSendReceiveTransactionCheckResultsUM
import com.tangem.features.walletconnect.transaction.entity.common.*
@ -37,13 +38,14 @@ internal class WcSendTransactionUMConverter @Inject constructor(
onShowVerifiedAlert = value.actions.onShowVerifiedAlert,
),
),
feeState = constructFeeState(useCase = value.useCase),
feeState = constructFeeState(useCase = value.useCase, actions = value.actions),
walletName = value.useCase.session.wallet.name,
networkInfo = networkInfoUMConverter.convert(value.useCase.network),
address = value.useCase.walletAddress.toShortAddressText(),
estimatedWalletChanges = WcSendReceiveTransactionCheckResultsUM(),
isLoading = value.signState.domainStep == WcSignStep.Signing,
),
feeSelectorUM = value.feeSelectorUM ?: FeeSelectorUM.Loading,
transactionRequestInfo = WcTransactionRequestInfoUM(
blocks = buildList {
add(
@ -80,15 +82,19 @@ internal class WcSendTransactionUMConverter @Inject constructor(
else -> null
}
private fun constructFeeState(useCase: WcTransactionUseCase): WcTransactionFeeState {
private fun constructFeeState(
useCase: WcTransactionUseCase,
actions: WcTransactionActionsUM,
): WcTransactionFeeState {
val mutableFee = useCase as? WcMutableFee ?: return WcTransactionFeeState.None
val dAppFee = mutableFee.dAppFee()
return if (dAppFee != null) WcTransactionFeeState.Success(dAppFee) else WcTransactionFeeState.Loading
return WcTransactionFeeState.Success(dAppFee = dAppFee, onClick = actions.onShowFeeBottomSheet)
}
data class Input(
val useCase: WcTransactionUseCase,
val signState: WcSignState<*>,
val actions: WcTransactionActionsUM,
val feeSelectorUM: FeeSelectorUM?,
)
}

View file

@ -8,4 +8,5 @@ internal data class WcTransactionActionsUM(
val onDismiss: () -> Unit,
val onSign: () -> Unit,
val onCopy: () -> Unit,
val onShowFeeBottomSheet: () -> Unit = {},
)

View file

@ -3,7 +3,6 @@ package com.tangem.features.walletconnect.transaction.entity.common
import com.tangem.blockchain.common.transaction.Fee
internal sealed class WcTransactionFeeState {
object Loading : WcTransactionFeeState()
data class Success(val fee: Fee) : WcTransactionFeeState()
data class Success(val dAppFee: Fee?, val onClick: () -> Unit) : WcTransactionFeeState()
data object None : WcTransactionFeeState()
}

View file

@ -1,6 +1,7 @@
package com.tangem.features.walletconnect.transaction.entity.send
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
import com.tangem.features.walletconnect.transaction.entity.blockaid.WcSendReceiveTransactionCheckResultsUM
import com.tangem.features.walletconnect.transaction.entity.common.WcCommonTransactionUM
@ -13,6 +14,7 @@ internal data class WcSendTransactionUM(
val transaction: WcSendTransactionItemUM,
override val transactionRequestInfo: WcTransactionRequestInfoUM,
val spendAllowance: WcSpendAllowanceUM? = null,
val feeSelectorUM: FeeSelectorUM,
) : WcCommonTransactionUM
internal data class WcSendTransactionItemUM(

View file

@ -24,6 +24,8 @@ import com.tangem.domain.transaction.usecase.GetFeeUseCase
import com.tangem.domain.walletconnect.WcRequestUseCaseFactory
import com.tangem.domain.walletconnect.usecase.method.*
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.features.send.v2.api.callbacks.FeeSelectorModelCallback
import com.tangem.features.send.v2.api.entity.FeeSelectorUM
import com.tangem.features.send.v2.api.params.FeeSelectorParams
import com.tangem.features.walletconnect.connections.routing.WcInnerRoute
import com.tangem.features.walletconnect.transaction.components.common.WcTransactionModelParams
@ -54,18 +56,18 @@ internal class WcSendTransactionModel @Inject constructor(
private val blockAidUiConverter: WcSendAndReceiveBlockAidUiConverter,
private val getFeeUseCase: GetFeeUseCase,
private val getNetworkCoinUseCase: GetNetworkCoinStatusUseCase,
) : Model(), WcCommonTransactionModel {
) : Model(), WcCommonTransactionModel, FeeSelectorModelCallback {
private val params = paramsContainer.require<WcTransactionModelParams>()
private val _uiState = MutableStateFlow<WcSendTransactionUM?>(null)
override val uiState: StateFlow<WcSendTransactionUM?> = _uiState
override val uiState: StateFlow<WcSendTransactionUM?> = _uiState.asStateFlow()
val stackNavigation = StackNavigation<WcTransactionRoutes>()
internal var useCase: WcSignUseCase<*> by Delegates.notNull()
internal var cryptoCurrencyStatus: CryptoCurrencyStatus by Delegates.notNull()
internal var suggestedFeeState: FeeSelectorParams.SuggestedFeeState = FeeSelectorParams.SuggestedFeeState.None
private var useCase: WcSignUseCase<*> by Delegates.notNull()
private var signState: WcSignState<*> by Delegates.notNull()
private var wcApproval: WcApproval? = null
private var sign: () -> Unit = {}
@ -116,6 +118,12 @@ internal class WcSendTransactionModel @Inject constructor(
}
}
override fun onFeeResult(feeSelectorUM: FeeSelectorUM) {
_uiState.update { it?.copy(feeSelectorUM = feeSelectorUM) }
val fee = (feeSelectorUM as? FeeSelectorUM.Content)?.selectedFeeItem?.fee ?: return
(useCase as? WcMutableFee)?.updateFee(fee)
}
suspend fun loadFee(): Either<GetFeeError, TransactionFee> {
val signModel = signState.signModel
val transactionData = signModel as? TransactionData.Uncompiled ?: error("TransactionData must be Uncompiled")
@ -162,7 +170,9 @@ internal class WcSendTransactionModel @Inject constructor(
onDismiss = { cancel(useCase) },
onSign = { onSign(securityCheck.getOrNull()) },
onCopy = { copyData(useCase.rawSdkRequest.request.params) },
onShowFeeBottomSheet = ::onShowFeeBottomSheet,
),
feeSelectorUM = uiState.value?.feeSelectorUM,
),
) as? WcSendTransactionUM
transactionUM = transactionUM?.copy(
@ -180,6 +190,10 @@ internal class WcSendTransactionModel @Inject constructor(
stackNavigation.pushNew(WcTransactionRoutes.TransactionRequestInfo)
}
private fun onShowFeeBottomSheet() {
stackNavigation.pushNew(WcTransactionRoutes.SelectFee)
}
private fun onSign(securityCheck: BlockAidTransactionCheck.Result?) {
if (securityCheck?.result?.validation == ValidationResult.UNSAFE) {
showMaliciousAlert(securityCheck.result.description)

View file

@ -17,6 +17,9 @@ internal sealed class WcTransactionRoutes : TangemBottomSheetConfigContent, Rout
@Serializable
data object CustomAllowance : WcTransactionRoutes()
@Serializable
data object SelectFee : WcTransactionRoutes()
@Serializable
data class Alert(val type: Type) : WcTransactionRoutes() {
@Serializable

View file

@ -7,10 +7,12 @@ 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.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.divider.DividerWithPadding
import com.tangem.core.ui.extensions.clickableSingle
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.send.v2.api.FeeSelectorBlockComponent
import com.tangem.features.walletconnect.transaction.entity.common.WcNetworkInfoUM
@ -25,6 +27,12 @@ internal fun WcSendTransactionItems(
modifier: Modifier = Modifier,
address: String? = null,
) {
val onFeeBlockClicked = remember(feeState) {
when (feeState) {
WcTransactionFeeState.None -> null
is WcTransactionFeeState.Success -> feeState.onClick
}
}
Column(
modifier = modifier
.clip(RoundedCornerShape(TangemTheme.dimens.radius14))
@ -55,7 +63,13 @@ internal fun WcSendTransactionItems(
}
DividerWithPadding(start = 40.dp, end = 12.dp)
if (feeState != WcTransactionFeeState.None) {
feeSelectorBlockComponent.Content(Modifier)
feeSelectorBlockComponent.Content(
modifier = if (onFeeBlockClicked != null) {
Modifier.clickableSingle(onClick = onFeeBlockClicked)
} else {
Modifier
},
)
}
}
}

View file

@ -194,7 +194,7 @@ private class WcSendTransactionStateProvider : CollectionPreviewParameterProvide
),
walletName = "Tangem 2.0",
networkInfo = WcNetworkInfoUM(name = "Ethereum", iconRes = R.drawable.img_eth_22),
feeState = WcTransactionFeeState.Loading,
feeState = WcTransactionFeeState.None,
address = "0x345FF...34FA",
),
),