Updated on 2026-08-14

This commit is contained in:
Tangem 2025-04-28 14:02:26 +05:00
parent c1c3b52715
commit b280cd9e5e
20 changed files with 491 additions and 44 deletions

View file

@ -1292,6 +1292,7 @@
<string name="wc_common_network">Network</string>
<string name="wc_common_networks">Networks</string>
<string name="wc_common_wallet">Wallet</string>
<string name="wc_connected_networks">Connected networks</string>
<string name="wc_connection_reqeust_can_view_balance">View your wallet balance and activity</string>
<string name="wc_connection_reqeust_cant_sign">Sign transactions without your notice</string>
<string name="wc_connection_reqeust_request_approval">Request approval for transactions</string>

View file

@ -7,11 +7,6 @@ import com.tangem.data.walletconnect.DefaultWalletConnectRepository
import com.tangem.data.walletconnect.initialize.DefaultWcInitializeUseCase
import com.tangem.data.walletconnect.network.ethereum.WcEthNetwork
import com.tangem.data.walletconnect.network.solana.WcSolanaNetwork
import com.tangem.data.walletconnect.pair.AssociateNetworksDelegate
import com.tangem.data.walletconnect.pair.CaipNamespaceDelegate
import com.tangem.data.walletconnect.pair.DefaultWcPairService
import com.tangem.data.walletconnect.pair.DefaultWcPairUseCase
import com.tangem.data.walletconnect.pair.WcPairSdkDelegate
import com.tangem.data.walletconnect.pair.*
import com.tangem.data.walletconnect.request.DefaultWcRequestService
import com.tangem.data.walletconnect.request.DefaultWcRequestUseCaseFactory
@ -31,6 +26,7 @@ import com.tangem.domain.walletconnect.WcRequestUseCaseFactory
import com.tangem.domain.walletconnect.model.legacy.WalletConnectSessionsRepository
import com.tangem.domain.walletconnect.repository.WalletConnectRepository
import com.tangem.domain.walletconnect.repository.WcSessionsManager
import com.tangem.domain.walletconnect.usecase.disconnect.WcDisconnectUseCase
import com.tangem.domain.walletconnect.usecase.initialize.WcInitializeUseCase
import com.tangem.domain.walletconnect.usecase.pair.WcPairUseCase
import com.tangem.domain.walletmanager.WalletManagersFacade
@ -197,6 +193,12 @@ internal object WalletConnectDataModule {
return DefaultWcRequestUseCaseFactory(diHelperBox.handlers)
}
@Provides
@Singleton
fun providesWcDisconnectUseCase(sessionsManager: WcSessionsManager): WcDisconnectUseCase {
return WcDisconnectUseCase(sessionsManager)
}
internal class DiHelperBox(
val converters: Set<WcNamespaceConverter>,
val handlers: Set<WcRequestToUseCaseConverter>,

View file

@ -34,9 +34,14 @@ internal class CaipNamespaceDelegate constructor(
requiredNamespaces[namespaceKey]?.methods?.let { addAll(it) }
optionalNamespaces[namespaceKey]?.methods?.let { addAll(it) }
}
val events = buildSet {
requiredNamespaces[namespaceKey]?.events?.let { addAll(it) }
optionalNamespaces[namespaceKey]?.events?.let { addAll(it) }
}
session.chains.add(account.chainId.raw)
session.accounts.add(account.raw)
session.methods.addAll(methods)
session.events.addAll(events)
}
return result.mapValues { (namespaceKey, session) ->
Wallet.Model.Namespace.Session(

View file

@ -14,4 +14,8 @@ class WcSessionsUseCase(private val sessionsManager: WcSessionsManager) {
suspend fun invokeSync(): Map<UserWallet, List<WcSession>> {
return sessionsManager.sessions.first()
}
suspend fun findByTopic(topic: String): WcSession? {
return sessionsManager.findSessionByTopic(topic)
}
}

View file

@ -7,6 +7,7 @@ import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.features.walletconnect.connections.ui.AlertsModalBottomSheet
import kotlinx.serialization.Serializable
internal class AlertsComponent(
appComponentContext: AppComponentContext,
@ -31,17 +32,35 @@ internal class AlertsComponent(
internal data class Params(val alertType: AlertType)
@Immutable
@Serializable
internal sealed class AlertType : TangemBottomSheetConfigContent {
abstract val onDismiss: () -> Unit
@Serializable
data class VerifiedDomain(val appName: String, override val onDismiss: () -> Unit) : AlertType()
@Serializable
data class UnknownDomain(val onConnect: () -> Unit, override val onDismiss: () -> Unit) : AlertType()
@Serializable
data class UnsafeDomain(val onConnect: () -> Unit, override val onDismiss: () -> Unit) : AlertType()
@Serializable
data class UnsupportedNetworks(val appName: String, override val onDismiss: () -> Unit) : AlertType()
@Serializable
data class WcDisconnected(override val onDismiss: () -> Unit) : AlertType()
@Serializable
data class UnknownError(val errorCode: Int, override val onDismiss: () -> Unit) : AlertType()
@Serializable
data class WrongCardSelected(override val onDismiss: () -> Unit) : AlertType()
@Serializable
data class ConnectionTimeout(val onTryAgain: () -> Unit, override val onDismiss: () -> Unit) : AlertType()
@Serializable
data class MaliciousTransaction(
val descriptionMessage: String,
val onConnect: () -> Unit,

View file

@ -15,7 +15,7 @@ import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.domain.walletconnect.model.WcPairRequest
import com.tangem.features.walletconnect.connections.model.WcConnectionsModel
import com.tangem.features.walletconnect.connections.routes.WcConnectionsBottomSheetRoutes
import com.tangem.features.walletconnect.connections.routes.WcConnectionsBottomSheetConfig
import com.tangem.features.walletconnect.connections.ui.WcConnectionsContent
internal class ConnectionsComponent(
@ -40,11 +40,11 @@ internal class ConnectionsComponent(
}
private fun bottomSheetChild(
config: WcConnectionsBottomSheetRoutes,
config: WcConnectionsBottomSheetConfig,
componentContext: ComponentContext,
): ComposableBottomSheetComponent {
return when (config) {
is WcConnectionsBottomSheetRoutes.AppInfo -> WcAppInfoContainerComponent(
is WcConnectionsBottomSheetConfig.AppInfo -> WcAppInfoContainerComponent(
appComponentContext = childByContext(componentContext),
params = WcAppInfoContainerComponent.Params(
wcUrl = config.wcUrl,
@ -52,6 +52,13 @@ internal class ConnectionsComponent(
onDismiss = model.bottomSheetNavigation::dismiss,
),
)
is WcConnectionsBottomSheetConfig.ConnectedApp -> WcConnectedAppInfoComponent(
appComponentContext = childByContext(componentContext),
params = WcConnectedAppInfoComponent.Params(
topic = config.topic,
onDismiss = model.bottomSheetNavigation::dismiss,
),
)
}
}
}

View file

@ -0,0 +1,30 @@
package com.tangem.features.walletconnect.connections.components
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.features.walletconnect.connections.model.WcConnectedAppInfoModel
import com.tangem.features.walletconnect.connections.ui.WcConnectedAppInfoBS
internal class WcConnectedAppInfoComponent(
params: Params,
appComponentContext: AppComponentContext,
) : AppComponentContext by appComponentContext, ComposableBottomSheetComponent {
private val model: WcConnectedAppInfoModel = getOrCreateModel(params = params)
override fun dismiss() {
model.dismiss()
}
@Composable
override fun BottomSheet() {
val state by model.uiState.collectAsStateWithLifecycle()
state?.let { WcConnectedAppInfoBS(it) }
}
data class Params(val topic: String, val onDismiss: () -> Unit)
}

View file

@ -50,5 +50,5 @@ sealed class WcAppInfoSecurityNotification(
@Immutable
internal sealed class WcNetworksInfo {
data class MissingRequiredNetworkInfo(val networks: String) : WcNetworksInfo()
data class ContainsAllRequiredNetworks(val icons: ImmutableList<Int>) : WcNetworksInfo()
data class ContainsAllRequiredNetworks(val items: ImmutableList<WcNetworkInfoItem>) : WcNetworksInfo()
}

View file

@ -0,0 +1,15 @@
package com.tangem.features.walletconnect.connections.entity
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import kotlinx.collections.immutable.ImmutableList
data class WcConnectedAppInfoUM(
val appName: String,
val appIcon: String,
val isVerified: Boolean,
val appSubtitle: String,
val walletName: String,
val networks: ImmutableList<WcNetworkInfoItem>,
val disconnectButtonConfig: WcPrimaryButtonConfig,
val onDismiss: () -> Unit,
) : TangemBottomSheetConfigContent

View file

@ -0,0 +1,10 @@
package com.tangem.features.walletconnect.connections.entity
import androidx.annotation.DrawableRes
data class WcNetworkInfoItem(
val id: String,
@DrawableRes val icon: Int,
val name: String,
val symbol: String,
)

View file

@ -53,7 +53,7 @@ internal class WcAppInfoModel @Inject constructor(
appInfoUiState.transformerUpdate(
WcConnectButtonProgressTransformer(showProgress = false),
)
// TODO: wc dismiss and reload sessions
params.onDismiss()
}
is WcPairState.Error -> {
// TODO: wc show toast/snackbar/alert?
@ -78,6 +78,7 @@ internal class WcAppInfoModel @Inject constructor(
}
fun dismiss() {
wcPairUseCase.reject()
params.onDismiss()
}

View file

@ -0,0 +1,88 @@
package com.tangem.features.walletconnect.connections.model
import androidx.compose.runtime.Stable
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.ui.UiMessageSender
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.message.SnackbarMessage
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.WcConnectedAppInfoUM
import com.tangem.features.walletconnect.connections.entity.WcPrimaryButtonConfig
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
@Stable
@ModelScoped
internal class WcConnectedAppInfoModel @Inject constructor(
private val wcSessionsUseCase: WcSessionsUseCase,
private val messageSender: UiMessageSender,
private val disconnectUseCase: WcDisconnectUseCase,
override val dispatchers: CoroutineDispatcherProvider,
paramsContainer: ParamsContainer,
) : Model() {
private val params = paramsContainer.require<WcConnectedAppInfoComponent.Params>()
val uiState: StateFlow<WcConnectedAppInfoUM?>
field = MutableStateFlow<WcConnectedAppInfoUM?>(null)
init {
loadDAppInfo(params.topic)
}
private fun loadDAppInfo(topic: String) {
modelScope.launch {
val session = wcSessionsUseCase.findByTopic(topic = topic)
if (session == null) {
Timber.e("Can not find WcSession by topic: $topic")
messageSender.send(SnackbarMessage(stringReference("Can not find WcSession by topic: $topic")))
dismiss()
} else {
uiState.update { state ->
WcConnectedAppInfoUM(
appName = session.sdkModel.appMetaData.name,
appIcon = session.sdkModel.appMetaData.icons.firstOrNull().orEmpty(),
isVerified = session.securityStatus == CheckDAppResult.SAFE,
appSubtitle = session.sdkModel.appMetaData.description,
walletName = session.wallet.name,
networks = persistentListOf(), // TODO(wc): Nikolai & Doston: Where to find networks???
disconnectButtonConfig = WcPrimaryButtonConfig(
showProgress = false,
enabled = true,
onClick = { disconnect(session) },
),
onDismiss = ::dismiss,
)
}
}
}
}
private fun disconnect(session: WcSession) {
uiState.update { state ->
state?.copy(
disconnectButtonConfig = state.disconnectButtonConfig.copy(showProgress = true),
)
}
modelScope.launch {
disconnectUseCase.disconnect(session)
messageSender.send(SnackbarMessage(message = stringReference("dApp disconnected")))
dismiss()
}
}
fun dismiss() {
params.onDismiss()
}
}

View file

@ -16,16 +16,19 @@ import com.tangem.core.ui.message.EventMessageAction
import com.tangem.core.ui.res.TangemTheme
import com.tangem.domain.qrscanning.models.SourceType
import com.tangem.domain.qrscanning.usecases.ListenToQrScanningUseCase
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.entity.WcConnectionsState
import com.tangem.features.walletconnect.connections.entity.WcConnectionsTopAppBarConfig
import com.tangem.features.walletconnect.connections.model.transformers.WcSessionsTransformer
import com.tangem.features.walletconnect.connections.routes.WcConnectionsBottomSheetRoutes
import com.tangem.features.walletconnect.connections.routes.WcConnectionsBottomSheetConfig
import com.tangem.features.walletconnect.impl.R
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.transformer.update
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
@ -35,12 +38,13 @@ internal class WcConnectionsModel @Inject constructor(
private val uiMessageSender: UiMessageSender,
private val listenToQrScanningUseCase: ListenToQrScanningUseCase,
private val wcSessionsUseCase: WcSessionsUseCase,
private val wcDisconnectUseCase: WcDisconnectUseCase,
override val dispatchers: CoroutineDispatcherProvider,
) : Model() {
val uiState: StateFlow<WcConnectionsState>
field = MutableStateFlow<WcConnectionsState>(getInitialState())
val bottomSheetNavigation: SlotNavigation<WcConnectionsBottomSheetRoutes> = SlotNavigation()
val bottomSheetNavigation: SlotNavigation<WcConnectionsBottomSheetConfig> = SlotNavigation()
init {
listenQrUpdates()
@ -50,39 +54,43 @@ internal class WcConnectionsModel @Inject constructor(
private fun listenQrUpdates() {
listenToQrScanningUseCase(SourceType.WALLET_CONNECT)
.getOrElse { emptyFlow() }
.onEach { wcUrl -> bottomSheetNavigation.activate(WcConnectionsBottomSheetRoutes.AppInfo(wcUrl)) }
.onEach { wcUrl -> bottomSheetNavigation.activate(WcConnectionsBottomSheetConfig.AppInfo(wcUrl)) }
.launchIn(modelScope)
}
private fun listenWcSessions() {
wcSessionsUseCase.invoke()
.conflate()
.onEach { Timber.tag("ddk9499").d("listenWcSessions before: ${it.size}") }
.distinctUntilChanged()
.onEach { sessionsMap ->
Timber.tag("ddk9499").d("listenWcSessions after: ${sessionsMap.size}")
uiState.update(
WcSessionsTransformer(
sessionsMap = sessionsMap,
openAppInfoModal = { Timber.d("Open app info for session: $it") },
openAppInfoModal = ::openAppInfoModal,
),
)
}
.launchIn(modelScope)
}
private fun openAppInfoModal(session: WcSession) {
bottomSheetNavigation.activate(WcConnectionsBottomSheetConfig.ConnectedApp(session.sdkModel.topic))
}
private fun openQrScanScreen() {
router.push(AppRoute.QrScanning(source = AppRoute.QrScanning.Source.WalletConnect))
}
private fun disconnectAll() {
private fun showDisconnectAllDialog() {
val message = DialogMessage(
title = resourceReference(R.string.wc_disconnect_all_alert_title),
message = resourceReference(R.string.wc_disconnect_all_alert_desc),
firstActionBuilder = {
EventMessageAction(
title = resourceReference(R.string.common_disconnect),
onClick = {
// TODO(wc): disconnect all
},
onClick = ::disconnectAllSessions,
)
},
secondActionBuilder = { cancelAction() },
@ -90,6 +98,10 @@ internal class WcConnectionsModel @Inject constructor(
uiMessageSender.send(message)
}
private fun disconnectAllSessions() {
modelScope.launch { wcDisconnectUseCase.disconnectAll() }
}
private fun getInitialState(): WcConnectionsState {
return WcConnectionsState(
topAppBarConfig = WcConnectionsTopAppBarConfig(
@ -101,7 +113,7 @@ internal class WcConnectionsModel @Inject constructor(
disconnectAllItem = TangemDropdownMenuItem(
title = resourceReference(R.string.wc_disconnect_all),
textColorProvider = { TangemTheme.colors.text.warning },
onClick = ::disconnectAll,
onClick = ::showDisconnectAllDialog,
),
),
connections = persistentListOf(),

View file

@ -4,10 +4,7 @@ import com.domain.blockaid.models.dapp.CheckDAppResult
import com.tangem.core.ui.extensions.iconResId
import com.tangem.domain.walletconnect.model.WcSessionProposal
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.features.walletconnect.connections.entity.WcAppInfoSecurityNotification
import com.tangem.features.walletconnect.connections.entity.WcAppInfoUM
import com.tangem.features.walletconnect.connections.entity.WcNetworksInfo
import com.tangem.features.walletconnect.connections.entity.WcPrimaryButtonConfig
import com.tangem.features.walletconnect.connections.entity.*
import com.tangem.utils.transformer.Transformer
import kotlinx.collections.immutable.toImmutableList
@ -53,8 +50,15 @@ internal class WcAppInfoTransformer(
)
} else {
WcNetworksInfo.ContainsAllRequiredNetworks(
icons = (proposalNetwork.required + proposalNetwork.available)
.map { it.iconResId }
items = (proposalNetwork.required + proposalNetwork.available)
.map {
WcNetworkInfoItem(
id = it.id.value,
icon = it.iconResId,
name = it.name,
symbol = it.currencySymbol,
)
}
.toImmutableList(),
)
}

View file

@ -8,8 +8,15 @@ import kotlinx.serialization.Serializable
@Serializable
@Immutable
internal sealed class WcAppInfoRoutes : TangemBottomSheetConfigContent {
@Serializable
data object AppInfo : WcAppInfoRoutes()
@Serializable
data object SelectWallet : WcAppInfoRoutes()
@Serializable
data object SelectNetworks : WcAppInfoRoutes()
@Serializable
data class Alert(val alertType: AlertsComponent.AlertType) : WcAppInfoRoutes()
}

View file

@ -3,7 +3,10 @@ package com.tangem.features.walletconnect.connections.routes
import kotlinx.serialization.Serializable
@Serializable
internal sealed class WcConnectionsBottomSheetRoutes {
internal sealed class WcConnectionsBottomSheetConfig {
@Serializable
data class AppInfo(val wcUrl: String) : WcConnectionsBottomSheetRoutes()
data class AppInfo(val wcUrl: String) : WcConnectionsBottomSheetConfig()
@Serializable
data class ConnectedApp(val topic: String) : WcConnectionsBottomSheetConfig()
}

View file

@ -1,5 +1,6 @@
package com.tangem.features.walletconnect.connections.ui
import androidx.annotation.DrawableRes
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
@ -10,6 +11,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
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.res.TangemTheme
@ -64,4 +66,33 @@ internal fun WcAppInfoItem(
)
}
}
}
@Composable
internal fun WcNetworkInfoItem(@DrawableRes icon: Int, name: String, symbol: String, modifier: Modifier = Modifier) {
Row(
modifier = modifier.padding(vertical = 14.dp, horizontal = 12.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
modifier = Modifier.size(24.dp),
painter = painterResource(icon),
contentDescription = null,
tint = Color.Unspecified,
)
Text(
modifier = Modifier.padding(start = 12.dp).weight(1f, fill = false),
text = name,
style = TangemTheme.typography.body1,
color = TangemTheme.colors.text.primary1,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
Text(
modifier = Modifier.padding(start = 4.dp),
text = symbol,
style = TangemTheme.typography.body1,
color = TangemTheme.colors.text.tertiary,
)
}
}

View file

@ -37,10 +37,7 @@ import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.res.TangemColorPalette
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.features.walletconnect.connections.entity.WcAppInfoSecurityNotification
import com.tangem.features.walletconnect.connections.entity.WcAppInfoUM
import com.tangem.features.walletconnect.connections.entity.WcNetworksInfo
import com.tangem.features.walletconnect.connections.entity.WcPrimaryButtonConfig
import com.tangem.features.walletconnect.connections.entity.*
import com.tangem.features.walletconnect.impl.R
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
@ -308,7 +305,7 @@ private fun SelectNetworksBlock(networksInfo: WcNetworksInfo, modifier: Modifier
color = TangemTheme.colors.text.primary1,
)
when (networksInfo) {
is WcNetworksInfo.ContainsAllRequiredNetworks -> NetworkIcons(icons = networksInfo.icons)
is WcNetworksInfo.ContainsAllRequiredNetworks -> NetworkIcons(items = networksInfo.items)
is WcNetworksInfo.MissingRequiredNetworkInfo -> Unit
}
Icon(
@ -327,17 +324,17 @@ private fun SelectNetworksBlock(networksInfo: WcNetworksInfo, modifier: Modifier
@Suppress("MagicNumber")
@Composable
private fun NetworkIcons(icons: ImmutableList<Int>, modifier: Modifier = Modifier) {
val (iconsToShow, remainingCount) = remember(icons) {
private fun NetworkIcons(items: ImmutableList<WcNetworkInfoItem>, modifier: Modifier = Modifier) {
val (iconsToShow, remainingCount) = remember(items) {
when {
icons.size <= 4 -> icons to 0
else -> icons.take(3) to icons.size - 3
items.size <= 4 -> items to 0
else -> items.take(3) to items.size - 3
}
}
Box(modifier = modifier.wrapContentWidth()) {
iconsToShow.forEachIndexed { index, iconRes ->
iconsToShow.forEachIndexed { index, item ->
Image(
painter = painterResource(id = iconRes),
painter = painterResource(id = item.icon),
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier
@ -569,12 +566,32 @@ private class WcAppInfoStateProvider : CollectionPreviewParameterProvider<WcAppI
notification = WcAppInfoSecurityNotification.SecurityRisk,
walletName = "Tangem 2.0",
networksInfo = WcNetworksInfo.ContainsAllRequiredNetworks(
icons = persistentListOf(
R.drawable.img_optimism_22,
R.drawable.img_bsc_22,
R.drawable.img_avalanche_22,
R.drawable.img_solana_22,
R.drawable.img_avalanche_22,
items = persistentListOf(
WcNetworkInfoItem(
id = "1",
icon = R.drawable.img_optimism_22,
name = "img_optimism_22",
symbol = "optimism",
),
WcNetworkInfoItem(id = "2", icon = R.drawable.img_bsc_22, name = "img_bsc_22", symbol = "bsc"),
WcNetworkInfoItem(
id = "3",
icon = R.drawable.img_avalanche_22,
name = "img_avalanche_22",
symbol = "avalanche",
),
WcNetworkInfoItem(
id = "4",
icon = R.drawable.img_solana_22,
name = "img_solana_22",
symbol = "solana",
),
WcNetworkInfoItem(
id = "5",
icon = R.drawable.img_avalanche_22,
name = "img_avalanche_22",
symbol = "avalanche",
),
),
),
onDismiss = {},

View file

@ -0,0 +1,185 @@
package com.tangem.features.walletconnect.connections.ui
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.key
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEach
import com.tangem.core.ui.components.SecondaryButton
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheet
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetTitle
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.WcConnectedAppInfoUM
import com.tangem.features.walletconnect.connections.entity.WcNetworkInfoItem
import com.tangem.features.walletconnect.connections.entity.WcPrimaryButtonConfig
import com.tangem.features.walletconnect.impl.R
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
@Composable
internal fun WcConnectedAppInfoBS(state: WcConnectedAppInfoUM) {
TangemModalBottomSheet<WcConnectedAppInfoUM>(
config = TangemBottomSheetConfig(
isShown = true,
onDismissRequest = state.onDismiss,
content = state,
),
containerColor = TangemTheme.colors.background.tertiary,
title = {
TangemModalBottomSheetTitle(
title = resourceReference(R.string.wc_wallet_connect),
endIconRes = R.drawable.ic_close_24,
onEndClick = state.onDismiss,
)
},
content = {
WcConnectedAppInfoBSContent(
state,
modifier = Modifier
.padding(horizontal = 16.dp)
.fillMaxSize(),
)
},
)
}
@Composable
private fun WcConnectedAppInfoBSContent(state: WcConnectedAppInfoUM, modifier: Modifier = Modifier) {
Column(modifier = modifier) {
val blocksModifier = Modifier
.clip(RoundedCornerShape(TangemTheme.dimens.radius14))
.background(color = TangemTheme.colors.background.action)
.fillMaxWidth()
AppInfoFirstBlock(
state = state,
modifier = Modifier
.padding(top = 8.dp)
.then(blocksModifier),
)
NetworksBlock(
networks = state.networks,
modifier = Modifier
.padding(top = 14.dp)
.then(blocksModifier),
)
SecondaryButton(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 16.dp),
text = stringResourceSafe(R.string.common_disconnect),
enabled = state.disconnectButtonConfig.enabled,
showProgress = state.disconnectButtonConfig.showProgress,
onClick = state.disconnectButtonConfig.onClick,
)
}
}
@Composable
private fun AppInfoFirstBlock(state: WcConnectedAppInfoUM, modifier: Modifier = Modifier) {
Column(modifier = modifier) {
WcAppInfoItem(
iconUrl = state.appIcon,
title = state.appName,
subtitle = state.appSubtitle,
isVerified = state.isVerified,
)
HorizontalDivider(thickness = 1.dp, color = TangemTheme.colors.stroke.primary)
Row(
modifier = Modifier
.fillMaxWidth()
.padding(TangemTheme.dimens.spacing12),
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
modifier = Modifier.size(24.dp),
painter = painterResource(R.drawable.ic_wallet_new_24),
contentDescription = null,
tint = TangemTheme.colors.icon.accent,
)
Text(
modifier = Modifier
.padding(start = TangemTheme.dimens.spacing4)
.weight(1f),
text = stringResourceSafe(R.string.manage_tokens_network_selector_wallet),
style = TangemTheme.typography.body1,
color = TangemTheme.colors.text.primary1,
)
Text(
modifier = Modifier
.padding(start = TangemTheme.dimens.spacing16)
.weight(1f),
text = state.walletName,
textAlign = TextAlign.End,
style = TangemTheme.typography.body1,
color = TangemTheme.colors.text.tertiary,
)
}
}
}
@Composable
private fun NetworksBlock(networks: ImmutableList<WcNetworkInfoItem>, modifier: Modifier = Modifier) {
Column(modifier = modifier) {
Text(
modifier = Modifier
.padding(top = 12.dp, bottom = 4.dp)
.padding(horizontal = 12.dp),
text = stringResourceSafe(R.string.wc_connected_networks),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.tertiary,
)
networks.fastForEach { network ->
key(network.id) { WcNetworkInfoItem(icon = network.icon, name = network.name, symbol = network.symbol) }
}
}
}
@Preview(showBackground = true)
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun WcConnectedAppInfoBS_Preview() {
TangemThemePreview {
WcConnectedAppInfoBS(
state = WcConnectedAppInfoUM(
appName = "React App",
appIcon = "",
isVerified = true,
appSubtitle = "react-app.walletconnect.com",
walletName = "Tangem 2.0",
networks = persistentListOf(
WcNetworkInfoItem(
id = "1",
icon = R.drawable.img_optimism_22,
name = "img_optimism_22img_optimism_22",
symbol = "optimism",
),
WcNetworkInfoItem(id = "2", icon = R.drawable.img_bsc_22, name = "img_bsc_22", symbol = "bsc"),
WcNetworkInfoItem(
id = "3",
icon = R.drawable.img_solana_22,
name = "img_solana_22",
symbol = "solana",
),
),
disconnectButtonConfig = WcPrimaryButtonConfig(showProgress = false, enabled = true, onClick = {}),
onDismiss = {},
),
)
}
}

View file

@ -3,6 +3,7 @@ package com.tangem.features.walletconnect.di
import com.tangem.core.decompose.di.ModelComponent
import com.tangem.core.decompose.model.Model
import com.tangem.features.walletconnect.connections.model.WcAppInfoModel
import com.tangem.features.walletconnect.connections.model.WcConnectedAppInfoModel
import com.tangem.features.walletconnect.connections.model.WcConnectionsModel
import dagger.Binds
import dagger.Module
@ -23,4 +24,9 @@ internal interface WalletConnectModelModule {
@IntoMap
@ClassKey(WcAppInfoModel::class)
fun bindWcAppInfoModel(model: WcAppInfoModel): Model
@Binds
@IntoMap
@ClassKey(WcConnectedAppInfoModel::class)
fun bindWcConnectedAppInfoModel(model: WcConnectedAppInfoModel): Model
}