Updated on 2026-08-14

This commit is contained in:
Tangem 2025-05-27 19:30:03 +05:00
parent dc9341bb71
commit 222b5d75d1
23 changed files with 300 additions and 48 deletions

View file

@ -1,6 +1,7 @@
plugins {
alias(deps.plugins.android.library)
alias(deps.plugins.kotlin.android)
alias(deps.plugins.kotlin.serialization)
id("configuration")
}

View file

@ -7,6 +7,7 @@ import com.tangem.core.ui.extensions.TextReference
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toPersistentList
import kotlinx.serialization.Serializable
@Immutable
data class MessageBottomSheetUMV2(
@ -24,6 +25,7 @@ data class MessageBottomSheetUMV2(
val closeScope = CloseScope()
@Immutable
@Serializable
sealed interface Element
@Immutable
@ -90,7 +92,7 @@ annotation class MessageBottomSheetV2Dsl
// region: DSL
internal fun messageBottomSheetUM(init: @MessageBottomSheetV2Dsl MessageBottomSheetUMV2.() -> Unit) =
fun messageBottomSheetUM(init: @MessageBottomSheetV2Dsl MessageBottomSheetUMV2.() -> Unit) =
MessageBottomSheetUMV2().apply(init)
fun MessageBottomSheetUMV2.onDismiss(block: () -> Unit) = MessageBottomSheetUMV2().apply { onDismissRequest = block }

View file

@ -7,6 +7,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
@ -33,23 +34,31 @@ import kotlinx.collections.immutable.toPersistentList
@Composable
fun MessageBottomSheetV2(state: MessageBottomSheetUMV2, onDismissRequest: () -> Unit) {
val stateWithOnDismiss = remember(state) {
state.copy(
onDismissRequest = {
state.onDismissRequest.invoke()
onDismissRequest()
},
)
}
val config = TangemBottomSheetConfig(
isShown = true,
content = state,
onDismissRequest = onDismissRequest,
content = stateWithOnDismiss,
onDismissRequest = stateWithOnDismiss.onDismissRequest,
)
TangemModalBottomSheet(
config,
config = config,
title = {
TangemModalBottomSheetTitle(
endIconRes = R.drawable.ic_close_24,
onEndClick = state.onDismissRequest,
onEndClick = stateWithOnDismiss.onDismissRequest,
)
},
) { content: MessageBottomSheetUMV2 ->
MessageBottomSheetV2Content(content.copy(onDismissRequest = onDismissRequest))
}
content = { content: MessageBottomSheetUMV2 -> MessageBottomSheetV2Content(content) },
)
}
@Composable

View file

@ -27,4 +27,15 @@ fun Modifier.clickableSingle(
indication = LocalIndication.current,
interactionSource = remember { MutableInteractionSource() },
)
}
/**
* Conditionally applies a modifier based on a boolean condition.
*/
fun Modifier.conditional(condition: Boolean, modifier: Modifier.() -> Modifier): Modifier {
return if (condition) {
then(modifier(Modifier))
} else {
this
}
}

View file

@ -0,0 +1,27 @@
package com.tangem.features.walletconnect.connections.components
import androidx.compose.runtime.Composable
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.ui.components.bottomsheets.message.MessageBottomSheetUMV2
import com.tangem.core.ui.components.bottomsheets.message.MessageBottomSheetV2
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import kotlinx.collections.immutable.ImmutableList
internal class AlertsComponentV2(
appComponentContext: AppComponentContext,
private val elements: ImmutableList<MessageBottomSheetUMV2.Element>,
private val onDismissRequest: () -> Unit = {},
) : AppComponentContext by appComponentContext, ComposableBottomSheetComponent {
override fun dismiss() {
router.pop()
}
@Composable
override fun BottomSheet() {
MessageBottomSheetV2(
state = MessageBottomSheetUMV2(elements = elements, onDismissRequest = onDismissRequest),
onDismissRequest = ::dismiss,
)
}
}

View file

@ -6,7 +6,6 @@ import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import com.arkivanov.decompose.ComponentContext
import com.arkivanov.decompose.extensions.compose.subscribeAsState
import com.arkivanov.decompose.router.stack.StackNavigation
import com.arkivanov.decompose.router.stack.childStack
import com.arkivanov.decompose.router.stack.pop
import com.tangem.core.decompose.context.AppComponentContext
@ -25,15 +24,14 @@ internal class WcPairComponent(
params: Params,
) : AppComponentContext by appComponentContext, ComposableContentComponent {
private val stackNavigation = StackNavigation<WcAppInfoRoutes>()
private val model: WcPairModel = getOrCreateModel(params = params)
private val innerRouter = InnerRouter<WcAppInfoRoutes>(
stackNavigation = stackNavigation,
stackNavigation = model.stackNavigation,
popCallback = { onChildBack() },
)
private val model: WcPairModel = getOrCreateModel(params = params, router = innerRouter)
private val contentStack = childStack(
key = "WcConnectionFlowStack",
source = stackNavigation,
source = model.stackNavigation,
serializer = WcAppInfoRoutes.serializer(),
initialConfiguration = WcAppInfoRoutes.AppInfo,
handleBackButton = true,
@ -59,7 +57,7 @@ internal class WcPairComponent(
is WcAppInfoRoutes.Alert,
is WcAppInfoRoutes.SelectNetworks,
is WcAppInfoRoutes.SelectWallet,
-> stackNavigation.pop()
-> model.stackNavigation.pop()
}
}
@ -77,7 +75,10 @@ internal class WcPairComponent(
onDismiss = ::dismiss,
model = model,
)
is WcAppInfoRoutes.Alert -> TODO()
is WcAppInfoRoutes.Alert -> AlertsComponentV2(
appComponentContext = appComponentContext,
elements = config.elements,
)
is WcAppInfoRoutes.SelectNetworks -> WcSelectNetworksComponent(
appComponentContext = appComponentContext,
params = WcSelectNetworksComponent.WcSelectNetworksParams(

View file

@ -22,7 +22,7 @@ internal sealed class WcAppInfoUM : TangemBottomSheetConfigContent {
data class Content(
val appName: String,
val appIcon: String,
val isVerified: Boolean,
val verifiedDAppState: VerifiedDAppState,
val appSubtitle: String,
val notification: WcAppInfoSecurityNotification?,
val walletName: String,
@ -34,6 +34,12 @@ internal sealed class WcAppInfoUM : TangemBottomSheetConfigContent {
) : WcAppInfoUM()
}
@Immutable
sealed class VerifiedDAppState {
data class Verified(val onVerifiedClick: () -> Unit) : VerifiedDAppState()
data object Unknown : VerifiedDAppState()
}
sealed class WcAppInfoSecurityNotification(
title: TextReference,
subtitle: TextReference,

View file

@ -7,6 +7,7 @@ data class WcConnectedAppInfoUM(
val appName: String,
val appIcon: String,
val isVerified: Boolean,
val verifiedDAppState: VerifiedDAppState,
val appSubtitle: String,
val walletName: String,
val networks: ImmutableList<WcNetworkInfoItem.Required>,

View file

@ -13,6 +13,7 @@ import com.tangem.domain.walletconnect.model.WcSession
import com.tangem.domain.walletconnect.usecase.WcSessionsUseCase
import com.tangem.domain.walletconnect.usecase.disconnect.WcDisconnectUseCase
import com.tangem.features.walletconnect.connections.components.WcConnectedAppInfoComponent
import com.tangem.features.walletconnect.connections.entity.VerifiedDAppState
import com.tangem.features.walletconnect.connections.entity.WcConnectedAppInfoUM
import com.tangem.features.walletconnect.connections.entity.WcNetworkInfoItem
import com.tangem.features.walletconnect.connections.entity.WcPrimaryButtonConfig
@ -56,6 +57,7 @@ internal class WcConnectedAppInfoModel @Inject constructor(
appName = session.sdkModel.appMetaData.name,
appIcon = session.sdkModel.appMetaData.icons.firstOrNull().orEmpty(),
isVerified = session.securityStatus == CheckDAppResult.SAFE,
verifiedDAppState = extractVerifiedState(session),
appSubtitle = session.sdkModel.appMetaData.description,
walletName = session.wallet.name,
networks = session.networks
@ -79,6 +81,15 @@ internal class WcConnectedAppInfoModel @Inject constructor(
}
}
private fun extractVerifiedState(session: WcSession): VerifiedDAppState {
return VerifiedDAppState.Verified(onVerifiedClick = {})
return if (session.securityStatus == CheckDAppResult.SAFE) {
VerifiedDAppState.Verified(onVerifiedClick = {})
} else {
VerifiedDAppState.Unknown
}
}
private fun disconnect(session: WcSession) {
uiState.update { state ->
state?.copy(

View file

@ -1,10 +1,18 @@
package com.tangem.features.walletconnect.connections.model
import androidx.compose.runtime.Stable
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.dapp.CheckDAppResult
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.decompose.navigation.Router
import com.tangem.core.ui.R
import com.tangem.core.ui.components.bottomsheets.message.*
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.domain.models.network.Network
import com.tangem.domain.walletconnect.model.WcPairRequest
import com.tangem.domain.walletconnect.model.WcSessionApprove
@ -21,6 +29,7 @@ import com.tangem.features.walletconnect.connections.entity.WcPrimaryButtonConfi
import com.tangem.features.walletconnect.connections.model.transformers.WcAppInfoTransformer
import com.tangem.features.walletconnect.connections.model.transformers.WcAppInfoWalletChangedTransformer
import com.tangem.features.walletconnect.connections.model.transformers.WcConnectButtonProgressTransformer
import com.tangem.features.walletconnect.connections.model.transformers.WcDAppVerifiedStateConverter
import com.tangem.features.walletconnect.connections.routes.WcAppInfoRoutes
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.*
@ -52,11 +61,14 @@ internal class WcPairModel @Inject constructor(
),
)
val stackNavigation = StackNavigation<WcAppInfoRoutes>()
private val selectedUserWalletFlow =
MutableStateFlow(getWalletsUseCase.invokeSync().first { it.walletId == params.userWalletId })
private var proposalNetwork by Delegates.notNull<WcSessionProposal.ProposalNetwork>()
internal var sessionProposal by Delegates.notNull<WcSessionProposal>()
private var sessionProposal by Delegates.notNull<WcSessionProposal>()
private val additionallyEnabledNetworks = mutableSetOf<Network.RawID>()
private val dAppVerifiedStateConverter = WcDAppVerifiedStateConverter(onVerifiedClick = ::showVerifiedAlert)
val appInfoUiState: StateFlow<WcAppInfoUM>
field = MutableStateFlow<WcAppInfoUM>(createLoadingState())
@ -87,16 +99,17 @@ internal class WcPairModel @Inject constructor(
proposalNetwork = sessionProposal.proposalNetwork.getValue(selectedUserWalletFlow.value)
appInfoUiState.transformerUpdate(
WcAppInfoTransformer(
dAppSession = pairState.dAppSession,
dAppSession = sessionProposal,
dAppVerifiedStateConverter = dAppVerifiedStateConverter,
onDismiss = ::rejectPairing,
onConnect = ::onConnect,
onWalletClick = {
router.push(
stackNavigation.pushNew(
WcAppInfoRoutes.SelectWallet(selectedUserWalletFlow.value.walletId),
)
},
onNetworksClick = {
router.push(
stackNavigation.pushNew(
WcAppInfoRoutes.SelectNetworks(
missingRequiredNetworks = proposalNetwork.missingRequired,
requiredNetworks = proposalNetwork.required,
@ -120,7 +133,15 @@ internal class WcPairModel @Inject constructor(
wcPairUseCase.reject()
}
private fun onConnect() {
private fun onConnect(securityStatus: CheckDAppResult) {
when (securityStatus) {
CheckDAppResult.SAFE -> connect()
CheckDAppResult.UNSAFE -> showSecurityRiskAlert()
CheckDAppResult.FAILED_TO_VERIFY -> showUnknownDomainAlert()
}
}
private fun connect() {
val enabledAvailableNetworks =
proposalNetwork.available.filter { network -> network.id.rawId in additionallyEnabledNetworks }
wcPairUseCase.approve(
@ -131,6 +152,79 @@ internal class WcPairModel @Inject constructor(
)
}
private fun connectFromAlert() {
stackNavigation.pop()
connect()
}
private fun showUnknownDomainAlert() {
val message = messageBottomSheetUM {
infoBlock {
icon(R.drawable.img_knight_shield_32) {
type = MessageBottomSheetUMV2.Icon.Type.Attention
backgroundType = MessageBottomSheetUMV2.Icon.BackgroundType.SameAsTint
}
title = resourceReference(R.string.security_alert_title)
body = resourceReference(R.string.wc_alert_domain_issues_description)
chip(resourceReference(R.string.wc_alert_audit_unknown_domain)) {
type = MessageBottomSheetUMV2.Chip.Type.Unspecified
}
}
primaryButton {
text = resourceReference(R.string.common_cancel)
onClick { closeBs() }
}
secondaryButton {
text = resourceReference(R.string.wc_alert_connect_anyway)
onClick { connectFromAlert() }
}
}
stackNavigation.pushNew(WcAppInfoRoutes.Alert(elements = message.elements))
}
private fun showSecurityRiskAlert() {
val message = messageBottomSheetUM {
infoBlock {
icon(R.drawable.img_knight_shield_32) {
type = MessageBottomSheetUMV2.Icon.Type.Warning
backgroundType = MessageBottomSheetUMV2.Icon.BackgroundType.SameAsTint
}
title = resourceReference(R.string.security_alert_title)
body = resourceReference(R.string.wc_alert_domain_issues_description)
chip(resourceReference(R.string.wc_alert_audit_unknown_domain)) {
type = MessageBottomSheetUMV2.Chip.Type.Warning
}
}
primaryButton {
text = resourceReference(R.string.common_cancel)
onClick { closeBs() }
}
secondaryButton {
text = resourceReference(R.string.wc_alert_connect_anyway)
onClick { connectFromAlert() }
}
}
stackNavigation.pushNew(WcAppInfoRoutes.Alert(elements = message.elements))
}
private fun showVerifiedAlert(appName: String) {
val message = messageBottomSheetUM {
infoBlock {
icon(R.drawable.img_approvale2_20) {
type = MessageBottomSheetUMV2.Icon.Type.Accent
backgroundType = MessageBottomSheetUMV2.Icon.BackgroundType.Unspecified
}
title = resourceReference(R.string.wc_alert_verified_domain_title)
body = resourceReference(R.string.wc_alert_verified_domain_description, wrappedList(appName))
}
secondaryButton {
text = resourceReference(R.string.common_done)
onClick { closeBs() }
}
}
stackNavigation.pushNew(WcAppInfoRoutes.Alert(elements = message.elements))
}
override fun onWalletSelected(userWalletId: UserWalletId) {
val selectedUserWallet = sessionProposal.proposalNetwork.keys.first { it.walletId == userWalletId }
proposalNetwork = sessionProposal.proposalNetwork.getValue(selectedUserWallet)
@ -147,7 +241,7 @@ internal class WcPairModel @Inject constructor(
private fun createLoadingState(): WcAppInfoUM.Loading {
return WcAppInfoUM.Loading(
onDismiss = ::rejectPairing,
connectButtonConfig = WcPrimaryButtonConfig(showProgress = false, enabled = false, onClick = ::onConnect),
connectButtonConfig = WcPrimaryButtonConfig(showProgress = false, enabled = false, onClick = {}),
)
}
}

View file

@ -3,14 +3,17 @@ package com.tangem.features.walletconnect.connections.model.transformers
import com.domain.blockaid.models.dapp.CheckDAppResult
import com.tangem.domain.walletconnect.model.WcSessionProposal
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.features.walletconnect.connections.entity.*
import com.tangem.features.walletconnect.connections.entity.WcAppInfoSecurityNotification
import com.tangem.features.walletconnect.connections.entity.WcAppInfoUM
import com.tangem.features.walletconnect.connections.entity.WcPrimaryButtonConfig
import com.tangem.utils.transformer.Transformer
@Suppress("LongParameterList")
internal class WcAppInfoTransformer(
private val dAppSession: WcSessionProposal,
private val dAppVerifiedStateConverter: WcDAppVerifiedStateConverter,
private val onDismiss: () -> Unit,
private val onConnect: () -> Unit,
private val onConnect: (securityStatus: CheckDAppResult) -> Unit,
private val onWalletClick: () -> Unit,
private val onNetworksClick: () -> Unit,
private val userWallet: UserWallet,
@ -21,7 +24,9 @@ internal class WcAppInfoTransformer(
return WcAppInfoUM.Content(
appName = dAppSession.dAppMetaData.name,
appIcon = dAppSession.dAppMetaData.icons.firstOrNull().orEmpty(),
isVerified = dAppSession.securityStatus == CheckDAppResult.SAFE,
verifiedDAppState = dAppVerifiedStateConverter.convert(
dAppSession.securityStatus to dAppSession.dAppMetaData.name,
),
appSubtitle = dAppSession.dAppMetaData.description,
notification = createNotification(dAppSession.securityStatus),
walletName = userWallet.name,
@ -31,7 +36,7 @@ internal class WcAppInfoTransformer(
connectButtonConfig = WcPrimaryButtonConfig(
showProgress = false,
enabled = proposalNetwork.missingRequired.isEmpty(),
onClick = onConnect,
onClick = { onConnect(dAppSession.securityStatus) },
),
onDismiss = onDismiss,
)

View file

@ -0,0 +1,21 @@
package com.tangem.features.walletconnect.connections.model.transformers
import com.domain.blockaid.models.dapp.CheckDAppResult
import com.tangem.features.walletconnect.connections.entity.VerifiedDAppState
import com.tangem.utils.converter.Converter
internal class WcDAppVerifiedStateConverter(
private val onVerifiedClick: (String) -> Unit,
) : Converter<Pair<CheckDAppResult, String>, VerifiedDAppState> {
override fun convert(value: Pair<CheckDAppResult, String>): VerifiedDAppState {
val (checkDAppResult, appName) = value
return if (checkDAppResult == CheckDAppResult.SAFE) {
VerifiedDAppState.Verified(
onVerifiedClick = { onVerifiedClick.invoke(appName) },
)
} else {
VerifiedDAppState.Unknown
}
}
}

View file

@ -3,9 +3,10 @@ package com.tangem.features.walletconnect.connections.routes
import androidx.compose.runtime.Immutable
import com.tangem.core.decompose.navigation.Route
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import com.tangem.core.ui.components.bottomsheets.message.MessageBottomSheetUMV2
import com.tangem.domain.models.network.Network
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.features.walletconnect.connections.components.AlertsComponent
import kotlinx.collections.immutable.ImmutableList
import kotlinx.serialization.Serializable
@Serializable
@ -27,5 +28,5 @@ internal sealed class WcAppInfoRoutes : TangemBottomSheetConfigContent, Route {
) : WcAppInfoRoutes()
@Serializable
data class Alert(val alertType: AlertsComponent.AlertType) : WcAppInfoRoutes()
data class Alert(val elements: ImmutableList<MessageBottomSheetUMV2.Element>) : WcAppInfoRoutes()
}

View file

@ -14,7 +14,10 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage
import com.tangem.core.ui.extensions.clickableSingle
import com.tangem.core.ui.extensions.conditional
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.walletconnect.connections.entity.VerifiedDAppState
import com.tangem.features.walletconnect.impl.R
@Composable
@ -22,11 +25,18 @@ internal fun WcAppInfoItem(
iconUrl: String,
title: String,
subtitle: String,
isVerified: Boolean,
verifiedDAppState: VerifiedDAppState,
modifier: Modifier = Modifier,
) {
Row(
modifier = modifier.padding(TangemTheme.dimens.spacing12),
modifier = modifier
.conditional(
condition = verifiedDAppState is VerifiedDAppState.Verified,
modifier = {
clickableSingle { (verifiedDAppState as VerifiedDAppState.Verified).onVerifiedClick() }
},
)
.padding(TangemTheme.dimens.spacing12),
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing16),
) {
AsyncImage(
@ -50,7 +60,7 @@ internal fun WcAppInfoItem(
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography.h3,
)
if (isVerified) {
if (verifiedDAppState is VerifiedDAppState.Verified) {
Icon(
modifier = Modifier.size(20.dp),
painter = painterResource(R.drawable.img_approvale2_20),
@ -81,7 +91,9 @@ internal fun WcNetworkInfoItem(@DrawableRes icon: Int, name: String, symbol: Str
tint = Color.Unspecified,
)
Text(
modifier = Modifier.padding(start = 12.dp).weight(1f, fill = false),
modifier = Modifier
.padding(start = 12.dp)
.weight(1f, fill = false),
text = name,
style = TangemTheme.typography.body1,
color = TangemTheme.colors.text.primary1,

View file

@ -127,11 +127,12 @@ private fun WcAppInfoModalBottomSheetContent(state: WcAppInfoUM.Content, modifie
private fun WcAppInfoFirstBlock(state: WcAppInfoUM.Content, modifier: Modifier = Modifier) {
var connectionRequestExpanded by remember { mutableStateOf(false) }
Column(modifier = modifier) {
val verifiedDAppState = state.verifiedDAppState
WcAppInfoItem(
iconUrl = state.appIcon,
title = state.appName,
subtitle = state.appSubtitle,
isVerified = state.isVerified,
verifiedDAppState = state.verifiedDAppState,
)
HorizontalDivider(thickness = 1.dp, color = TangemTheme.colors.stroke.primary)
ConnectionRequestBlock(
@ -594,7 +595,7 @@ private class WcAppInfoStateProvider : CollectionPreviewParameterProvider<WcAppI
WcAppInfoUM.Content(
appName = "React App",
appIcon = "",
isVerified = true,
verifiedDAppState = VerifiedDAppState.Verified {},
appSubtitle = "react-app.walletconnect.com",
notification = WcAppInfoSecurityNotification.SecurityRisk,
walletName = "Tangem 2.0",
@ -640,7 +641,7 @@ private class WcAppInfoStateProvider : CollectionPreviewParameterProvider<WcAppI
WcAppInfoUM.Content(
appName = "React App",
appIcon = "",
isVerified = false,
verifiedDAppState = VerifiedDAppState.Unknown,
appSubtitle = "react-app.walletconnect.com",
notification = WcAppInfoSecurityNotification.UnknownDomain,
walletName = "Tangem 2.0",

View file

@ -25,6 +25,7 @@ import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.features.walletconnect.connections.entity.VerifiedDAppState
import com.tangem.features.walletconnect.connections.entity.WcConnectedAppInfoUM
import com.tangem.features.walletconnect.connections.entity.WcNetworkInfoItem
import com.tangem.features.walletconnect.connections.entity.WcPrimaryButtonConfig
@ -97,7 +98,7 @@ private fun AppInfoFirstBlock(state: WcConnectedAppInfoUM, modifier: Modifier =
iconUrl = state.appIcon,
title = state.appName,
subtitle = state.appSubtitle,
isVerified = state.isVerified,
verifiedDAppState = state.verifiedDAppState,
)
HorizontalDivider(thickness = 1.dp, color = TangemTheme.colors.stroke.primary)
Row(
@ -159,6 +160,7 @@ private fun WcConnectedAppInfoBS_Preview() {
state = WcConnectedAppInfoUM(
appName = "React App",
appIcon = "",
verifiedDAppState = VerifiedDAppState.Verified {},
isVerified = true,
appSubtitle = "react-app.walletconnect.com",
walletName = "Tangem 2.0",

View file

@ -4,6 +4,7 @@ import androidx.compose.runtime.Immutable
@Immutable
internal data class WcTransactionActionsUM(
val onShowVerifiedAlert: (String) -> Unit,
val onDismiss: () -> Unit,
val onSign: () -> Unit,
val onCopy: () -> Unit,

View file

@ -1,8 +1,10 @@
package com.tangem.features.walletconnect.transaction.entity.common
import com.tangem.features.walletconnect.connections.entity.VerifiedDAppState
internal data class WcTransactionAppInfoContentUM(
val appName: String,
val appIcon: String,
val isVerified: Boolean,
val verifiedState: VerifiedDAppState,
val appSubtitle: String,
)

View file

@ -5,7 +5,16 @@ import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.decompose.navigation.Router
import com.tangem.core.ui.R
import com.tangem.core.ui.clipboard.ClipboardManager
import com.tangem.core.ui.components.bottomsheets.message.MessageBottomSheetUMV2
import com.tangem.core.ui.components.bottomsheets.message.icon
import com.tangem.core.ui.components.bottomsheets.message.infoBlock
import com.tangem.core.ui.components.bottomsheets.message.messageBottomSheetUM
import com.tangem.core.ui.components.bottomsheets.message.onClick
import com.tangem.core.ui.components.bottomsheets.message.secondaryButton
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.domain.walletconnect.WcRequestUseCaseFactory
import com.tangem.domain.walletconnect.usecase.method.WcSignState
import com.tangem.domain.walletconnect.usecase.method.WcSignStep
@ -46,6 +55,7 @@ internal class WcSignTransactionModel @Inject constructor(
val signTransactionUM = useCase.toUM(
signState = signState,
actions = WcTransactionActionsUM(
onShowVerifiedAlert = ::showVerifiedAlert,
onDismiss = { cancel(useCase) },
onSign = useCase::sign,
onCopy = { copyData(useCase.rawSdkRequest.request.params) },
@ -61,6 +71,25 @@ internal class WcSignTransactionModel @Inject constructor(
_uiState.value?.transaction?.onDismiss?.invoke() ?: router.pop()
}
private fun showVerifiedAlert(appName: String) {
val message = messageBottomSheetUM {
infoBlock {
icon(R.drawable.img_approvale2_20) {
type = MessageBottomSheetUMV2.Icon.Type.Accent
backgroundType = MessageBottomSheetUMV2.Icon.BackgroundType.Unspecified
}
title = resourceReference(R.string.wc_alert_verified_domain_title)
body = resourceReference(R.string.wc_alert_verified_domain_description, wrappedList(appName))
}
secondaryButton {
text = resourceReference(R.string.common_done)
onClick { closeBs() }
}
}
// TODO(wc): Nastya [REDACTED_JIRA]
// router.push(WcAppInfoRoutes.Alert(elements = message.elements))
}
private fun signingIsDone(signState: WcSignState<*>): Boolean {
(signState.domainStep as? WcSignStep.Result)?.result?.let {
router.pop()

View file

@ -21,6 +21,7 @@ import com.tangem.core.ui.components.divider.DividerWithPadding
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.features.walletconnect.connections.entity.VerifiedDAppState
import com.tangem.features.walletconnect.connections.ui.WcAppInfoItem
import com.tangem.features.walletconnect.impl.R
import com.tangem.features.walletconnect.transaction.entity.approve.WcApproveTransactionItemUM
@ -50,7 +51,7 @@ internal fun WcApproveTransactionModalBottomSheetContent(
iconUrl = state.appInfo.appIcon,
title = state.appInfo.appName,
subtitle = state.appInfo.appSubtitle,
isVerified = state.appInfo.isVerified,
verifiedDAppState = state.appInfo.verifiedState,
)
DividerWithPadding(start = 0.dp, end = 0.dp)
WcTransactionRequestItem(
@ -120,7 +121,7 @@ private class WcApproveTransactionStateProvider : CollectionPreviewParameterProv
appInfo = WcTransactionAppInfoContentUM(
appName = "React App",
appIcon = "",
isVerified = true,
verifiedState = VerifiedDAppState.Verified {},
appSubtitle = "react-app.walletconnect.com",
),
spendAllowance = WcSpendAllowanceUM(amountText = "Unlimited USDT", tokenImageUrl = ""),

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
@ -21,11 +23,15 @@ import com.tangem.core.ui.components.divider.DividerWithPadding
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.features.walletconnect.connections.entity.VerifiedDAppState
import com.tangem.features.walletconnect.connections.ui.WcAppInfoItem
import com.tangem.features.walletconnect.impl.R
import com.tangem.features.walletconnect.transaction.entity.chain.WcAddEthereumChainItemUM
import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionAppInfoContentUM
import com.tangem.features.walletconnect.transaction.ui.common.*
import com.tangem.features.walletconnect.transaction.ui.common.WcSmallTitleItem
import com.tangem.features.walletconnect.transaction.ui.common.WcTransactionRequestButtons
import com.tangem.features.walletconnect.transaction.ui.common.WcTransactionRequestItem
import com.tangem.features.walletconnect.transaction.ui.common.WcWalletItem
@Composable
internal fun WcAddEthereumChainModalBottomSheetContent(
@ -48,7 +54,7 @@ internal fun WcAddEthereumChainModalBottomSheetContent(
iconUrl = state.appInfo.appIcon,
title = state.appInfo.appName,
subtitle = state.appInfo.appSubtitle,
isVerified = state.appInfo.isVerified,
verifiedDAppState = state.appInfo.verifiedState,
)
DividerWithPadding(start = 0.dp, end = 0.dp)
WcTransactionRequestItem(
@ -130,7 +136,7 @@ private class WcAddEthereumChainStateProvider : CollectionPreviewParameterProvid
appInfo = WcTransactionAppInfoContentUM(
appName = "React App",
appIcon = "",
isVerified = true,
verifiedState = VerifiedDAppState.Verified {},
appSubtitle = "react-app.walletconnect.com",
),
walletName = "Tangem 2.0",

View file

@ -23,6 +23,7 @@ import com.tangem.core.ui.components.divider.DividerWithPadding
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.features.walletconnect.connections.entity.VerifiedDAppState
import com.tangem.features.walletconnect.connections.ui.WcAppInfoItem
import com.tangem.features.walletconnect.impl.R
import com.tangem.features.walletconnect.transaction.entity.common.WcNetworkInfoUM
@ -51,7 +52,7 @@ internal fun WcSignTransactionModalBottomSheetContent(
iconUrl = state.appInfo.appIcon,
title = state.appInfo.appName,
subtitle = state.appInfo.appSubtitle,
isVerified = state.appInfo.isVerified,
verifiedDAppState = state.appInfo.verifiedState,
)
DividerWithPadding(start = 0.dp, end = 0.dp)
WcTransactionRequestItem(
@ -146,7 +147,7 @@ private class WcSignTransactionStateProvider : CollectionPreviewParameterProvide
appInfo = WcTransactionAppInfoContentUM(
appName = "React App",
appIcon = "",
isVerified = true,
verifiedState = VerifiedDAppState.Verified {},
appSubtitle = "react-app.walletconnect.com",
),
walletName = "Tangem 2.0",
@ -158,7 +159,7 @@ private class WcSignTransactionStateProvider : CollectionPreviewParameterProvide
appInfo = WcTransactionAppInfoContentUM(
appName = "React App",
appIcon = "",
isVerified = true,
verifiedState = VerifiedDAppState.Verified {},
appSubtitle = "react-app.walletconnect.com",
),
walletName = "Tangem 2.0",

View file

@ -1,11 +1,16 @@
package com.tangem.features.walletconnect.transaction.utils
import com.domain.blockaid.models.dapp.CheckDAppResult
import com.tangem.core.ui.extensions.getActiveIconRes
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.walletconnect.model.WcEthMethod
import com.tangem.domain.walletconnect.model.WcSolanaMethod
import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest
import com.tangem.domain.walletconnect.usecase.method.*
import com.tangem.domain.walletconnect.usecase.method.WcMessageSignUseCase
import com.tangem.domain.walletconnect.usecase.method.WcSignState
import com.tangem.domain.walletconnect.usecase.method.WcSignStep
import com.tangem.domain.walletconnect.usecase.method.WcSignUseCase
import com.tangem.features.walletconnect.connections.model.transformers.WcDAppVerifiedStateConverter
import com.tangem.features.walletconnect.impl.R
import com.tangem.features.walletconnect.transaction.entity.chain.WcAddEthereumChainItemUM
import com.tangem.features.walletconnect.transaction.entity.chain.WcAddEthereumChainUM
@ -148,7 +153,9 @@ private fun WcMethodContext.basicTransactionRequestInfoBlocks() = persistentList
private fun WcMethodContext.appInfo() = WcTransactionAppInfoContentUM(
appName = session.sdkModel.appMetaData.name,
appIcon = session.sdkModel.appMetaData.url,
isVerified = session.securityStatus == com.domain.blockaid.models.dapp.CheckDAppResult.SAFE,
verifiedState = WcDAppVerifiedStateConverter(onVerifiedClick = actions.onShowVerifiedAlert).convert(
session.securityStatus to session.sdkModel.appMetaData.name,
),
appSubtitle = session.sdkModel.appMetaData.description,
)