Updated on 2026-08-14

This commit is contained in:
Tangem 2025-08-04 11:40:43 +05:00
parent 0159b2f1ff
commit 0257c23494
12 changed files with 128 additions and 64 deletions

View file

@ -32,12 +32,13 @@ import com.tangem.core.ui.components.PrimaryButton
import com.tangem.core.ui.components.TangemSwitch
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.notifications.Notification
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.domain.models.network.Network
@ -145,8 +146,11 @@ private fun MissingRequiredBlock(missingNetworks: ImmutableList<WcNetworkInfoIte
val missingNetworksName = missingNetworks.joinToString { it.name }
val notificationUM = remember(missingNetworks) {
NotificationUM.Info(
title = stringReference("The wallet has no required networks"),
subtitle = stringReference("Add the $missingNetworksName network to your portfolio for this wallet."),
title = resourceReference(R.string.wc_missing_required_network_title),
subtitle = resourceReference(
R.string.wc_missing_required_network_description,
wrappedList(missingNetworksName),
),
)
}
Column(modifier = modifier) {
@ -288,7 +292,7 @@ private fun NetworkNameAndSymbol(name: String, symbol: String, modifier: Modifie
}
}
@Preview(showBackground = true, device = Devices.PIXEL_7_PRO)
@Preview(showBackground = true, device = Devices.PIXEL_7_PRO, uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(showBackground = true, device = Devices.PIXEL_7_PRO, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun WcSelectNetworksContent_Preview(
@ -296,29 +300,7 @@ private fun WcSelectNetworksContent_Preview(
state: WcSelectNetworksUM,
) {
TangemThemePreview {
TangemModalBottomSheet<TangemBottomSheetConfigContent.Empty>(
containerColor = TangemTheme.colors.background.tertiary,
config = TangemBottomSheetConfig(
isShown = true,
onDismissRequest = {},
content = TangemBottomSheetConfigContent.Empty,
),
title = {
TangemModalBottomSheetTitle(
title = stringReference("Choose wallet"),
onEndClick = {},
endIconRes = R.drawable.ic_close_24,
)
},
content = {
WcSelectNetworksContent(
modifier = Modifier
.background(TangemTheme.colors.background.tertiary)
.padding(horizontal = TangemTheme.dimens.spacing16),
state = state,
)
},
)
WcSelectNetworksBS(state = state, onBack = {}, onDismiss = {})
}
}
@ -328,7 +310,7 @@ private class WcSelectNetworksProvider : CollectionPreviewParameterProvider<WcSe
missing = persistentListOf(
WcNetworkInfoItem.Required(
id = "ethereum",
name = "EthereumEthereumEthereumEthereumEthereumEthereumEthereumEthereum",
name = "Ethereum",
symbol = "ETH",
icon = R.drawable.ic_solana_16,
),
@ -380,6 +362,7 @@ private class WcSelectNetworksProvider : CollectionPreviewParameterProvider<WcSe
),
),
onDone = {},
doneButtonEnabled = false,
),
WcSelectNetworksUM(
missing = persistentListOf(),
@ -424,6 +407,7 @@ private class WcSelectNetworksProvider : CollectionPreviewParameterProvider<WcSe
),
),
onDone = {},
doneButtonEnabled = true,
),
),
)

View file

@ -57,6 +57,7 @@ sealed class WcAppInfoSecurityNotification(
@Immutable
internal sealed class WcNetworksInfo {
data object NoneNetworksAdded : WcNetworksInfo()
data class MissingRequiredNetworkInfo(val networks: String) : WcNetworksInfo()
data class ContainsAllRequiredNetworks(val items: ImmutableList<WcNetworkInfoItem>) : WcNetworksInfo()
}

View file

@ -8,5 +8,5 @@ internal data class WcSelectNetworksUM(
val available: ImmutableList<WcNetworkInfoItem.Checkable>,
val notAdded: ImmutableList<WcNetworkInfoItem.ReadOnly>,
val onDone: () -> Unit,
val doneButtonEnabled: Boolean = true,
val doneButtonEnabled: Boolean,
)

View file

@ -1,7 +1,9 @@
package com.tangem.features.walletconnect.connections.model
import androidx.compose.runtime.Stable
import com.arkivanov.decompose.router.stack.*
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.analytics.api.AnalyticsEventHandler
import com.tangem.core.decompose.di.ModelScoped
@ -10,18 +12,16 @@ import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.decompose.navigation.Router
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.message.SnackbarMessage
import com.tangem.core.ui.message.ToastMessage
import com.tangem.domain.models.network.Network
import com.tangem.domain.walletconnect.WcAnalyticEvents
import com.tangem.domain.walletconnect.model.WcPairError
import com.tangem.domain.walletconnect.model.WcPairRequest
import com.tangem.domain.walletconnect.model.WcSessionApprove
import com.tangem.domain.walletconnect.model.WcSessionProposal
import com.tangem.domain.walletconnect.usecase.pair.WcPairState
import com.tangem.domain.walletconnect.usecase.pair.WcPairUseCase
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.models.wallet.isLocked
import com.tangem.domain.models.wallet.isMultiCurrency
import com.tangem.domain.walletconnect.WcAnalyticEvents
import com.tangem.domain.walletconnect.model.*
import com.tangem.domain.walletconnect.usecase.pair.WcPairState
import com.tangem.domain.walletconnect.usecase.pair.WcPairUseCase
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
import com.tangem.features.walletconnect.connections.components.WcPairComponent
import com.tangem.features.walletconnect.connections.components.WcSelectNetworksComponent
@ -91,7 +91,9 @@ internal class WcPairModel @Inject constructor(
appInfoUiState.transformerUpdate(
WcConnectButtonProgressTransformer(showProgress = false),
)
pairState.result.onLeft(::processError)
pairState.result
.onLeft(::processError)
.onRight(::processSuccessfullyConnected)
router.pop()
}
is WcPairState.Error -> {
@ -196,6 +198,13 @@ internal class WcPairModel @Inject constructor(
stackNavigation.pushNew(WcAppInfoRoutes.Alert(WcAppInfoRoutes.Alert.Type.Verified(appName)))
}
private fun processSuccessfullyConnected(session: WcSession) {
// TODO: [REDACTED_JIRA] localization
messageSender.send(
SnackbarMessage(message = stringReference("Connected to ${session.sdkModel.appMetaData.name}")),
)
}
private fun processError(error: WcPairError) {
val alert = when (error) {
is WcPairError.UnsupportedDApp -> {
@ -236,6 +245,8 @@ internal class WcPairModel @Inject constructor(
WcNetworksSelectedTransformer(
missingNetworks = proposalNetwork.missingRequired,
requiredNetworks = proposalNetwork.required,
availableNetworks = proposalNetwork.available,
notAddedNetworks = proposalNetwork.notAdded,
additionallyEnabledNetworks = additionallyEnabledNetworks,
),
)

View file

@ -60,6 +60,9 @@ internal class WcSelectNetworksModel @Inject constructor(
private fun getInitialState(): WcSelectNetworksUM {
val editEnabled = params.missingRequiredNetworks.isEmpty()
val doneButtonEnabled = params.missingRequiredNetworks.isEmpty() &&
(params.requiredNetworks.isNotEmpty() || params.availableNetworks.isNotEmpty())
return WcSelectNetworksUM(
missing = params.missingRequiredNetworks.map { network ->
WcNetworkInfoItem.Required(
@ -98,7 +101,7 @@ internal class WcSelectNetworksModel @Inject constructor(
)
}.toImmutableList(),
onDone = ::onDone,
doneButtonEnabled = editEnabled,
doneButtonEnabled = doneButtonEnabled,
)
}
}

View file

@ -2,8 +2,8 @@ package com.tangem.features.walletconnect.connections.model.transformers
import com.domain.blockaid.models.dapp.CheckDAppResult
import com.tangem.domain.models.network.Network
import com.tangem.domain.walletconnect.model.WcSessionProposal
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.walletconnect.model.WcSessionProposal
import com.tangem.features.walletconnect.connections.entity.WcAppInfoSecurityNotification
import com.tangem.features.walletconnect.connections.entity.WcAppInfoUM
import com.tangem.features.walletconnect.connections.entity.WcPrimaryButtonConfig
@ -37,13 +37,22 @@ internal class WcAppInfoTransformer(
value = WcNetworksInfoConverter.Input(
missingNetworks = proposalNetwork.missingRequired,
requiredNetworks = proposalNetwork.required,
availableNetworks = proposalNetwork.available,
notAddedNetworks = proposalNetwork.notAdded,
additionallyEnabledNetworks = additionallyEnabledNetworks,
),
),
onNetworksClick = onNetworksClick,
connectButtonConfig = WcPrimaryButtonConfig(
showProgress = false,
enabled = proposalNetwork.missingRequired.isEmpty(),
enabled = WcConnectButtonAvailabilityConverter.convert(
WcConnectButtonAvailabilityConverter.Input(
missingNetworks = proposalNetwork.missingRequired,
requiredNetworks = proposalNetwork.required,
availableNetworks = proposalNetwork.available,
selectedNetworks = additionallyEnabledNetworks,
),
),
onClick = { onConnect(dAppSession.securityStatus) },
),
onDismiss = onDismiss,

View file

@ -1,8 +1,8 @@
package com.tangem.features.walletconnect.connections.model.transformers
import com.tangem.domain.models.network.Network
import com.tangem.domain.walletconnect.model.WcSessionProposal
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.walletconnect.model.WcSessionProposal
import com.tangem.features.walletconnect.connections.entity.WcAppInfoUM
import com.tangem.utils.transformer.Transformer
@ -19,11 +19,20 @@ internal class WcAppInfoWalletChangedTransformer(
WcNetworksInfoConverter.Input(
missingNetworks = proposalNetwork.missingRequired,
requiredNetworks = proposalNetwork.required,
availableNetworks = proposalNetwork.available,
notAddedNetworks = proposalNetwork.notAdded,
additionallyEnabledNetworks = additionallyEnabledNetworks,
),
),
connectButtonConfig = prevState.connectButtonConfig.copy(
enabled = proposalNetwork.missingRequired.isEmpty(),
enabled = WcConnectButtonAvailabilityConverter.convert(
WcConnectButtonAvailabilityConverter.Input(
missingNetworks = proposalNetwork.missingRequired,
requiredNetworks = proposalNetwork.required,
availableNetworks = proposalNetwork.available,
selectedNetworks = additionallyEnabledNetworks,
),
),
),
)
}

View file

@ -0,0 +1,22 @@
package com.tangem.features.walletconnect.connections.model.transformers
import com.tangem.domain.models.network.Network
import com.tangem.utils.converter.Converter
@Suppress("DestructuringDeclarationWithTooManyEntries")
internal object WcConnectButtonAvailabilityConverter : Converter<WcConnectButtonAvailabilityConverter.Input, Boolean> {
override fun convert(value: Input): Boolean {
val (missingNetworks, requiredNetworks, availableNetworks, selectedNetworks) = value
return missingNetworks.isEmpty() &&
(requiredNetworks.isNotEmpty() || availableNetworks.isNotEmpty() || selectedNetworks.isNotEmpty())
}
data class Input(
val missingNetworks: Set<Network>,
val requiredNetworks: Set<Network>,
val availableNetworks: Set<Network>,
val selectedNetworks: Set<Network>,
)
}

View file

@ -7,31 +7,34 @@ import com.tangem.features.walletconnect.connections.entity.WcNetworksInfo
import com.tangem.utils.converter.Converter
import kotlinx.collections.immutable.toImmutableList
@Suppress("DestructuringDeclarationWithTooManyEntries")
internal object WcNetworksInfoConverter : Converter<WcNetworksInfoConverter.Input, WcNetworksInfo> {
override fun convert(value: Input): WcNetworksInfo {
return if (value.missingNetworks.isNotEmpty()) {
WcNetworksInfo.MissingRequiredNetworkInfo(
networks = value.missingNetworks.joinToString { it.name },
)
} else {
WcNetworksInfo.ContainsAllRequiredNetworks(
items = (value.requiredNetworks + value.additionallyEnabledNetworks)
.map {
val (missing, required, available, notAdded, additionallyEnabled) = value
return when {
missing.isNotEmpty() -> WcNetworksInfo.MissingRequiredNetworkInfo(missing.joinToString { it.name })
required.isEmpty() && available.isEmpty() && notAdded.isNotEmpty() -> WcNetworksInfo.NoneNetworksAdded
else -> {
val combinedNetworks = required + additionallyEnabled
WcNetworksInfo.ContainsAllRequiredNetworks(
items = combinedNetworks.map { network ->
WcNetworkInfoItem.Required(
id = it.rawId,
icon = it.iconResId,
name = it.name,
symbol = it.currencySymbol,
id = network.rawId,
icon = network.iconResId,
name = network.name,
symbol = network.currencySymbol,
)
}
.toImmutableList(),
)
}.toImmutableList(),
)
}
}
}
data class Input(
val missingNetworks: Set<Network>,
val requiredNetworks: Set<Network>,
val availableNetworks: Set<Network>,
val notAddedNetworks: Set<Network>,
val additionallyEnabledNetworks: Set<Network>,
)
}

View file

@ -7,19 +7,29 @@ import com.tangem.utils.transformer.Transformer
internal class WcNetworksSelectedTransformer(
private val missingNetworks: Set<Network>,
private val requiredNetworks: Set<Network>,
private val availableNetworks: Set<Network>,
private val notAddedNetworks: Set<Network>,
private val additionallyEnabledNetworks: Set<Network>,
) : Transformer<WcAppInfoUM> {
override fun transform(prevState: WcAppInfoUM): WcAppInfoUM {
val contentState = prevState as? WcAppInfoUM.Content ?: return prevState
return contentState.copy(
connectButtonConfig = contentState.connectButtonConfig.copy(
enabled = missingNetworks.isEmpty() &&
(requiredNetworks.isNotEmpty() || additionallyEnabledNetworks.isNotEmpty()),
enabled = WcConnectButtonAvailabilityConverter.convert(
WcConnectButtonAvailabilityConverter.Input(
missingNetworks = missingNetworks,
requiredNetworks = requiredNetworks,
availableNetworks = availableNetworks,
selectedNetworks = additionallyEnabledNetworks,
),
),
),
networksInfo = WcNetworksInfoConverter.convert(
value = WcNetworksInfoConverter.Input(
missingNetworks = missingNetworks,
requiredNetworks = requiredNetworks,
availableNetworks = availableNetworks,
notAddedNetworks = notAddedNetworks,
additionallyEnabledNetworks = additionallyEnabledNetworks,
),
),

View file

@ -35,10 +35,7 @@ import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetTi
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetWithFooter
import com.tangem.core.ui.components.notifications.Notification
import com.tangem.core.ui.components.notifications.NotificationConfig
import com.tangem.core.ui.extensions.clickableSingle
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.res.TangemColorPalette
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
@ -284,6 +281,19 @@ private fun WcAppInfoSecondBlock(state: WcAppInfoUM.Content, modifier: Modifier
)
when (state.networksInfo) {
is WcNetworksInfo.ContainsAllRequiredNetworks -> Unit
is WcNetworksInfo.NoneNetworksAdded -> {
HorizontalDivider(thickness = 1.dp, color = TangemTheme.colors.stroke.primary)
Notification(
config = NotificationConfig(
iconResId = R.drawable.ic_alert_circle_24,
// TODO: [REDACTED_JIRA] localization
title = stringReference("Specify selected networks"),
subtitle = stringReference("At least one network is required for dApp connection"),
),
iconTint = TangemTheme.colors.icon.attention,
containerColor = TangemTheme.colors.background.action,
)
}
is WcNetworksInfo.MissingRequiredNetworkInfo -> {
HorizontalDivider(thickness = 1.dp, color = TangemTheme.colors.stroke.primary)
Notification(
@ -370,6 +380,7 @@ private fun SelectNetworksBlock(networksInfo: WcNetworksInfo, modifier: Modifier
when (networksInfo) {
is WcNetworksInfo.ContainsAllRequiredNetworks -> NetworkIcons(items = networksInfo.items)
is WcNetworksInfo.MissingRequiredNetworkInfo -> Unit
is WcNetworksInfo.NoneNetworksAdded -> Unit
}
Icon(
modifier = Modifier.size(width = 18.dp, height = 24.dp),

View file

@ -192,6 +192,7 @@ private fun ConnectionItem(connection: WcConnectionsUM, modifier: Modifier = Mod
private fun AppInfoItem(appInfo: WcConnectedAppInfo, modifier: Modifier = Modifier) {
Row(
modifier = modifier,
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
) {
AsyncImage(