Updated on 2026-08-14

This commit is contained in:
Tangem 2025-06-23 13:58:27 +04:00
parent 315d7c9d6c
commit ab2f0f58bb
4 changed files with 85 additions and 12 deletions

View file

@ -10,11 +10,16 @@ import com.tangem.features.walletconnect.transaction.routes.WcTransactionRoutes
internal object WcAlertsFactory {
fun createCommonTransactionAppInfoAlertUM(alertType: WcTransactionRoutes.Alert.Type) = when (alertType) {
is WcTransactionRoutes.Alert.Type.Verified -> createVerifiedDomainAlert(alertType.appName)
is WcTransactionRoutes.Alert.Type.UnknownDomain -> createUnknownDomainAlert()
is WcTransactionRoutes.Alert.Type.UnsafeDomain -> createUnsafeDomainAlert()
is WcTransactionRoutes.Alert.Type.MaliciousDApp ->
is WcTransactionRoutes.Alert.Type.Verified ->
createVerifiedDomainAlert(alertType.appName)
is WcTransactionRoutes.Alert.Type.UnknownDomain ->
createUnknownDomainAlert()
is WcTransactionRoutes.Alert.Type.UnsafeDomain ->
createUnsafeDomainAlert()
is WcTransactionRoutes.Alert.Type.MaliciousInfo ->
createMaliciousDAppAlert(alertType.description, alertType.onClick)
is WcTransactionRoutes.Alert.Type.UnknownError ->
createUnknownErrorAlert(alertType.errorMessage, alertType.onDismiss)
}
fun createUnknownDomainAlert(activeButtonOnClick: (() -> Unit)? = null): MessageBottomSheetUMV2 {
@ -69,8 +74,32 @@ internal object WcAlertsFactory {
}
}
private fun createUnknownErrorAlert(errorMessage: String?, onDismiss: () -> Unit): MessageBottomSheetUMV2 {
return messageBottomSheetUM {
infoBlock {
icon(R.drawable.img_attention_20) {
type = MessageBottomSheetUMV2.Icon.Type.Warning
backgroundType = MessageBottomSheetUMV2.Icon.BackgroundType.SameAsTint
}
title = resourceReference(R.string.wc_alert_unknown_error_title)
body = if (errorMessage.isNullOrEmpty()) {
resourceReference(R.string.wc_alert_unknown_error_description_no_error_code)
} else {
resourceReference(
R.string.wc_alert_unknown_error_description,
wrappedList(errorMessage),
)
}
}
secondaryButton {
text = resourceReference(R.string.balance_hidden_got_it_button)
onClick { onDismiss() }
}
}
}
private fun createMaliciousDAppAlert(
description: String,
description: String?,
activeButtonOnClick: (() -> Unit),
): MessageBottomSheetUMV2 {
return messageBottomSheetUM {
@ -80,7 +109,9 @@ internal object WcAlertsFactory {
backgroundType = MessageBottomSheetUMV2.Icon.BackgroundType.SameAsTint
}
title = resourceReference(R.string.security_alert_title)
body = TextReference.Str(description)
if (!description.isNullOrEmpty()) {
body = TextReference.Str(description)
}
}
primaryButton {
text = resourceReference(R.string.common_cancel)

View file

@ -1,8 +1,11 @@
package com.tangem.features.walletconnect.transaction.model
import androidx.compose.runtime.Stable
import arrow.core.Either
import com.arkivanov.decompose.router.stack.StackNavigation
import com.arkivanov.decompose.router.stack.pop
import com.arkivanov.decompose.router.stack.pushNew
import com.domain.blockaid.models.transaction.ValidationResult
import com.tangem.blockchain.common.TransactionData
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
@ -48,6 +51,7 @@ internal class WcSendTransactionModel @Inject constructor(
val stackNavigation = StackNavigation<WcTransactionRoutes>()
private var wcApproval: WcApproval? = null
private var sign: () -> Unit = {}
init {
@Suppress("UnusedPrivateMember")
@ -66,7 +70,7 @@ internal class WcSendTransactionModel @Inject constructor(
.distinctUntilChanged()
.collectLatest {
val (signState, securityCheck) = it
if (signingIsDone(signState)) return@collectLatest
if (signingIsDone(signState, useCase)) return@collectLatest
val signModel: Any = signState.signModel
val isMutableFee = useCase is WcMutableFee
val dAppFee = if (isMutableFee) useCase.dAppFee() else null
@ -81,6 +85,7 @@ internal class WcSendTransactionModel @Inject constructor(
var isApprovalMethod = isSecurityCheckContent &&
securityCheck.content is BlockAidTransactionCheck.Result.Approval
wcApproval = useCase as? WcApproval
sign = { useCase.sign() }
buildUiState(securityCheck, useCase, signState)
}
else -> unknownMethodRunnable()
@ -107,7 +112,7 @@ internal class WcSendTransactionModel @Inject constructor(
actions = WcTransactionActionsUM(
onShowVerifiedAlert = ::showVerifiedAlert,
onDismiss = { cancel(useCase) },
onSign = useCase::sign,
onSign = { onSign(securityCheck.getOrNull()) },
onCopy = { copyData(useCase.rawSdkRequest.request.params) },
),
),
@ -127,6 +132,14 @@ internal class WcSendTransactionModel @Inject constructor(
stackNavigation.pushNew(WcTransactionRoutes.TransactionRequestInfo)
}
private fun onSign(securityCheck: BlockAidTransactionCheck.Result?) {
if (securityCheck?.result?.validation == ValidationResult.UNSAFE) {
showMaliciousAlert(securityCheck.result.description)
} else {
sign()
}
}
fun onClickDoneCustomAllowance(value: BigDecimal, isUnlimited: Boolean) {
val maxValue = if (isUnlimited) Double.MAX_VALUE.toBigDecimal() else value
wcApproval?.getAmount()?.let { currentAmount ->
@ -142,14 +155,36 @@ internal class WcSendTransactionModel @Inject constructor(
stackNavigation.pushNew(WcTransactionRoutes.Alert(WcTransactionRoutes.Alert.Type.Verified(appName)))
}
private fun signingIsDone(signState: WcSignState<*>): Boolean {
private fun showMaliciousAlert(description: String?) {
val type = WcTransactionRoutes.Alert.Type.MaliciousInfo(description = description, onClick = ::signFromAlert)
stackNavigation.pushNew(WcTransactionRoutes.Alert(type))
}
private fun signFromAlert() {
stackNavigation.pop()
sign()
}
private fun signingIsDone(signState: WcSignState<*>, useCase: WcSignUseCase<*>): Boolean {
(signState.domainStep as? WcSignStep.Result)?.result?.let {
router.pop()
handleSigningError(it, useCase)
return true
}
return false
}
private fun handleSigningError(result: Either<Throwable, Unit>, useCase: WcSignUseCase<*>) {
if (result.isLeft()) {
val error = WcTransactionRoutes.Alert.Type.UnknownError(
errorMessage = result.leftOrNull()?.message,
onDismiss = { cancel(useCase) },
)
stackNavigation.pushNew(WcTransactionRoutes.Alert(error))
} else {
router.pop()
}
}
private fun cancel(useCase: WcSignUseCase<*>) {
useCase.cancel()
router.pop()

View file

@ -24,7 +24,8 @@ internal sealed class WcTransactionRoutes : TangemBottomSheetConfigContent, Rout
data class Verified(val appName: String) : Type()
data object UnknownDomain : Type()
data object UnsafeDomain : Type()
data class MaliciousDApp(val description: String, val onClick: () -> Unit) : Type()
data class MaliciousInfo(val description: String?, val onClick: () -> Unit) : Type()
data class UnknownError(val errorMessage: String?, val onDismiss: () -> Unit) : Type()
}
}
}

View file

@ -4,6 +4,7 @@ import android.content.res.Configuration
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
@ -68,9 +69,14 @@ internal fun WcEstimatedWalletChangesItem(item: WcEstimatedWalletChangesUM, modi
}
if (item.items.size > MAX_CHANGES_SIZE) {
val interactionSource = remember { MutableInteractionSource() }
Row(
modifier = Modifier
.clickable { isExpanded = !isExpanded }
.clickable(
interactionSource = interactionSource,
indication = null,
onClick = { isExpanded = !isExpanded },
)
.padding(bottom = 12.dp, start = 12.dp),
verticalAlignment = Alignment.CenterVertically,
) {