Updated on 2026-08-14

This commit is contained in:
Tangem 2026-03-04 18:31:47 +03:00
parent d06051845b
commit 5c74278e3d
30 changed files with 83 additions and 666 deletions

View file

@ -24,7 +24,6 @@ import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
import com.tangem.domain.account.supplier.MultiAccountListSupplier
import com.tangem.domain.account.supplier.SingleAccountSupplier
import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesSupplier
import com.tangem.domain.walletconnect.WcPairService
import com.tangem.domain.walletconnect.WcRequestService
import com.tangem.domain.walletconnect.WcRequestUseCaseFactory
@ -181,14 +180,12 @@ internal object WalletConnectDataModule {
fun wcNetworksConverter(
namespaceConverters: Set<@JvmSuppressWildcards WcNamespaceConverter>,
walletManagersFacade: WalletManagersFacade,
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
singleAccountSupplier: SingleAccountSupplier,
): WcNetworksConverter = WcNetworksConverter(
namespaceConverters = namespaceConverters,
walletManagersFacade = walletManagersFacade,
singleAccountStatusListSupplier = singleAccountStatusListSupplier,
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
singleAccountSupplier = singleAccountSupplier,
)

View file

@ -131,12 +131,9 @@ internal class WcEthAddSwitchCommonDelegate @AssistedInject constructor(
val caip2 = hexChainIdToCAIP2(hexChainId)
?: return HandleMethodError.UnknownError("Failed to parse CAIP2").left()
val generalNetwork = networksConverter.createNetwork(caip2.raw, wallet)
if (generalNetwork == null) {
return HandleMethodError.TangemUnsupportedNetwork(caip2.raw).left()
}
?: return HandleMethodError.TangemUnsupportedNetwork(caip2.raw).left()
val addedNetwork = networksConverter.mainOrAnyWalletNetworkForRequest(
rawChainId = caip2.raw,
wallet = wallet,
account = context.session.account,
)
if (addedNetwork == null) {

View file

@ -47,7 +47,6 @@ internal class WcEthNetwork(
?: return error("Failed to parse $name")
suspend fun anyExistNetwork() = networksConverter.mainOrAnyWalletNetworkForRequest(
rawChainId = chainId,
wallet = wallet,
account = account,
)
@ -74,7 +73,7 @@ internal class WcEthNetwork(
-> anyExistNetwork()
} ?: return error("Failed to find walletNetwork for accountAddress $accountAddress")
val networkDerivationsCount = networksConverter.filterWalletNetworkForRequest(chainId, wallet, account).size
val networkDerivationsCount = networksConverter.filterWalletNetworkForRequest(chainId, account).size
val context = WcMethodUseCaseContext(
session = session,
rawSdkRequest = request,

View file

@ -49,7 +49,7 @@ internal class WcSolanaNetwork(
val wallet = session.wallet
val account = session.account
val chainId = request.chainId.orEmpty()
suspend fun anyExistNetwork() = networksConverter.mainOrAnyWalletNetworkForRequest(chainId, wallet, account)
suspend fun anyExistNetwork() = networksConverter.mainOrAnyWalletNetworkForRequest(chainId, account)
suspend fun anyAddress() = anyExistNetwork()
?.let { network -> networksConverter.getAddressForWC(wallet.walletId, network).orEmpty() }
.orEmpty()
@ -64,7 +64,7 @@ internal class WcSolanaNetwork(
?: anyExistNetwork()
?: return error("Failed to find walletNetwork for accountAddress $accountAddress")
val networkDerivationsCount = networksConverter.filterWalletNetworkForRequest(chainId, wallet, account).size
val networkDerivationsCount = networksConverter.filterWalletNetworkForRequest(chainId, account).size
val context = WcMethodUseCaseContext(
session = session,
rawSdkRequest = request,
@ -86,7 +86,7 @@ internal class WcSolanaNetwork(
override val namespaceKey: NamespaceKey = NamespaceKey("solana")
override fun toBlockchain(chainId: CAIP2): Blockchain? {
val isMainNet = MAINNET_CHAIN_ID.any { it.lowercase() == chainId.reference.lowercase() }
val isMainNet = MAINNET_CHAIN_ID.any { it.equals(chainId.reference, ignoreCase = true) }
if (chainId.namespace != namespaceKey.key) return null
return when {
isMainNet -> Blockchain.Solana

View file

@ -8,7 +8,6 @@ import com.tangem.domain.models.account.Account
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.wallet.UserWallet
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.model.WcPairError
@ -23,19 +22,6 @@ internal class AssociateNetworksDelegate(
private val getWallets: GetWalletsUseCase,
) {
@Throws(WcPairError.UnsupportedBlockchains::class)
suspend fun associate(sessionProposal: Wallet.Model.SessionProposal): Map<UserWallet, ProposalNetwork> {
val userWallets = getWallets.invokeSync().filter { it.isMultiCurrency }
val requiredNamespaces: Set<String> = sessionProposal.requiredNamespaces.setOfChainId()
val optionalNamespaces: Set<String> = sessionProposal.optionalNamespaces.setOfChainId()
// remove duplicates
.subtract(requiredNamespaces)
return userWallets.associateWith { wallet ->
mapNetworksForPortfolio(wallet, null, requiredNamespaces, optionalNamespaces, sessionProposal)
}
}
@Throws(WcPairError.UnsupportedBlockchains::class)
suspend fun associateAccounts(sessionProposal: Wallet.Model.SessionProposal): Map<AccountId, ProposalNetwork> {
val userWallets = getWallets.invokeSync()
@ -67,13 +53,12 @@ internal class AssociateNetworksDelegate(
@Suppress("CyclomaticComplexMethod")
private suspend fun mapNetworksForPortfolio(
wallet: UserWallet,
account: Account?,
account: Account,
requiredNamespaces: Set<String>,
optionalNamespaces: Set<String>,
sessionProposal: Wallet.Model.SessionProposal,
): ProposalNetwork {
val portfolioNetworks = account?.let { getAccountNetworks(it.accountId) }
?: getWalletNetworks(userWalletId = wallet.walletId)
val portfolioNetworks = getAccountNetworks(account.accountId)
val unknownRequired = mutableSetOf<String>()
val unknownOptional = mutableSetOf<String>()
@ -127,12 +112,6 @@ internal class AssociateNetworksDelegate(
)
}
private suspend fun getWalletNetworks(userWalletId: UserWalletId): List<Network> {
return networksConverter.getWalletNetworks(userWalletId)
// flatten all derivation
.distinctBy { it.rawId }
}
private suspend fun getAccountNetworks(accountId: AccountId): List<Network> {
return networksConverter.getAccountNetworks(accountId)
// flatten all derivation

View file

@ -111,7 +111,7 @@ internal class DefaultWcPairUseCase @AssistedInject constructor(
val sessionDTO = WcSessionDTO(
topic = "",
walletId = sessionForApprove.wallet.walletId,
accountId = sessionForApprove.account?.accountId,
accountId = sessionForApprove.account.accountId,
url = sdkVerifyContext.getDappOriginUrl(),
securityStatus = proposalState.dAppSession.securityStatus,
connectingTime = connectingTime,
@ -195,7 +195,6 @@ internal class DefaultWcPairUseCase @AssistedInject constructor(
sessionProposal: Wallet.Model.SessionProposal,
verifyContext: Wallet.Model.VerifyContext,
): Either<WcPairError, WcPairState.Proposal> = runCatching {
val proposalNetwork = associateNetworksDelegate.associate(sessionProposal)
val proposalAccountNetwork = associateNetworksDelegate.associateAccounts(sessionProposal)
val verificationInfo = when {
verifyContext.validation == Wallet.Model.Validation.INVALID -> CheckDAppResult.UNSAFE
@ -224,7 +223,6 @@ internal class DefaultWcPairUseCase @AssistedInject constructor(
)
val dAppSession = WcSessionProposal(
dAppMetaData = appMetaData,
proposalNetwork = proposalNetwork,
securityStatus = verificationInfo,
proposalAccountNetwork = proposalAccountNetwork,
)

View file

@ -9,7 +9,6 @@ import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.data.walletconnect.utils.*
import com.tangem.datasource.local.walletconnect.WalletConnectStore
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.walletconnect.WcAnalyticEvents
import com.tangem.domain.walletconnect.model.WcSession
@ -37,15 +36,12 @@ internal class DefaultWcSessionsManager(
) : WcSessionsManager, WcSdkObserver {
private val onSessionDelete = Channel<Wallet.Model.SessionDelete>(capacity = Channel.BUFFERED)
private val oneTimeMigration = MutableStateFlow(false)
override val sessions: Flow<Map<UserWallet, List<WcSession>>>
get() = combine(getWallets(), store.sessions) { wallets, inStore -> wallets to inStore }
.transform { pair ->
val (wallets, inStore) = pair
val inSdk: List<Wallet.Model.Session> = WalletKit.getListOfActiveSessions()
val someMigrate = migrateToAccountSession(inStore)
if (someMigrate) return@transform
val associatedSessions: List<WcSession> = associate(inSdk, inStore, wallets)
val someRemove = removeUnknownSessions(inStore, inSdk, associatedSessions)
if (someRemove) return@transform // ignore emit, wait next one
@ -54,25 +50,6 @@ internal class DefaultWcSessionsManager(
.distinctUntilChanged()
.flowOn(dispatchers.io)
private suspend fun migrateToAccountSession(inStore: Set<WcSessionDTO>): Boolean {
if (oneTimeMigration.value) return false
var someMigrated = false
val updatedSessions = inStore.mapTo(mutableSetOf()) { sessionDTO ->
if (sessionDTO.accountId == null) {
someMigrated = true
val mainAccountId = AccountId.forMainCryptoPortfolio(sessionDTO.walletId)
sessionDTO.copy(accountId = mainAccountId)
} else {
sessionDTO
}
}
if (someMigrated) store.saveSessions(updatedSessions)
oneTimeMigration.value = true
return someMigrated
}
override fun onWcSdkInit() {
listenOnSessionDelete()
extendSessions()
@ -114,7 +91,7 @@ internal class DefaultWcSessionsManager(
val wcSessions = savedPending.plus(inStore).mapNotNull { storeSession ->
val wallet = wallets.find { it.walletId == storeSession.walletId } ?: return@mapNotNull null
val sdkSession = inSdk.find { it.topic == storeSession.topic } ?: return@mapNotNull null
val account = storeSession.accountId?.let { wcNetworksConverter.getAccount(it) } as? Account.CryptoPortfolio
val account = wcNetworksConverter.getAccount(storeSession.accountId) as? Account.CryptoPortfolio
?: return@mapNotNull null
val networks = wcNetworksConverter.findWalletNetworks(wallet, account, sdkSession)
val originUrl = storeSession.url ?: sdkSession.metaData?.url ?: ""

View file

@ -71,7 +71,7 @@ internal class WcSignUseCaseDelegate<MiddleAction, SignModel>(
network = context.network,
errorCode = error.code(),
errorMessage = errorMessage,
accountDerivation = context.session.account?.derivationIndex?.value,
accountDerivation = context.session.account.derivationIndex.value,
)
analytics.send(event)
}

View file

@ -18,8 +18,6 @@ import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesProducer
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesSupplier
import com.tangem.domain.walletconnect.model.WcSession
import com.tangem.domain.walletconnect.model.WcSessionApprove
import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest
@ -31,7 +29,6 @@ internal class WcNetworksConverter @Inject constructor(
private val walletManagersFacade: WalletManagersFacade,
private val singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
private val singleAccountSupplier: SingleAccountSupplier,
private val multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
) {
fun createNetwork(chainId: String, wallet: UserWallet): Network? {
@ -47,13 +44,12 @@ internal class WcNetworksConverter @Inject constructor(
val wallet = session.wallet
val allCoinNetwork = filterWalletNetworkForRequest(
rawChainId = request.chainId.orEmpty(),
wallet = session.wallet,
account = session.account,
)
val requestNetwork = allCoinNetwork.find { network ->
val address = getAddressForWC(wallet.walletId, network)
requestAddress.lowercase() == address?.lowercase()
requestAddress.equals(address, ignoreCase = true)
}
return requestNetwork
}
@ -61,13 +57,13 @@ internal class WcNetworksConverter @Inject constructor(
/**
* return network with not custom derivationPath or first custom or any
*/
suspend fun mainOrAnyWalletNetworkForRequest(rawChainId: String, wallet: UserWallet, account: Account?): Network? {
val networks = filterWalletNetworkForRequest(rawChainId, wallet, account)
suspend fun mainOrAnyWalletNetworkForRequest(rawChainId: String, account: Account): Network? {
val networks = filterWalletNetworkForRequest(rawChainId, account)
return networks.firstOrNull { !isCustomCoin(it) } ?: networks.firstOrNull()
}
suspend fun allAddressForChain(rawChainId: String, wallet: UserWallet, account: Account?): List<String> {
return filterWalletNetworkForRequest(rawChainId, wallet, account)
suspend fun allAddressForChain(rawChainId: String, wallet: UserWallet, account: Account): List<String> {
return filterWalletNetworkForRequest(rawChainId, account)
.mapNotNull { getAddressForWC(wallet.walletId, it)?.lowercase() }
}
@ -85,13 +81,8 @@ internal class WcNetworksConverter @Inject constructor(
/**
* return all exist derivation networks
*/
suspend fun filterWalletNetworkForRequest(
rawChainId: String,
wallet: UserWallet,
account: Account?,
): List<Network> {
val portfolioNetworks = account?.let { getAccountNetworks(it.accountId) }
?: getWalletNetworks(wallet.walletId)
suspend fun filterWalletNetworkForRequest(rawChainId: String, account: Account): List<Network> {
val portfolioNetworks = getAccountNetworks(account.accountId)
val blockchain = namespaceConverters
.firstNotNullOfOrNull { it.toBlockchain(rawChainId) } ?: return listOf()
@ -102,11 +93,10 @@ internal class WcNetworksConverter @Inject constructor(
suspend fun findWalletNetworks(
wallet: UserWallet,
account: Account?,
account: Account,
sdkSession: Wallet.Model.Session,
): Set<Network> {
val portfolioNetworks = account?.let { getAccountNetworks(it.accountId) }
?: getWalletNetworks(wallet.walletId)
val portfolioNetworks = getAccountNetworks(account.accountId)
val existNetworks = sdkSession.namespaces.values
.map { it.accounts }.flatten().toSet()
.mapNotNull { CAIP10.fromRaw(it) }
@ -120,7 +110,7 @@ internal class WcNetworksConverter @Inject constructor(
// find equal address
.firstOrNull { network ->
val walletAddress = getAddressForWC(wallet.walletId, network)
walletAddress?.lowercase() == caip10.accountAddress.lowercase()
walletAddress.equals(caip10.accountAddress, ignoreCase = true)
}
}
@ -132,21 +122,12 @@ internal class WcNetworksConverter @Inject constructor(
}
suspend fun convertNetworksForApprove(sessionForApprove: WcSessionApprove): List<Network> {
val portfolioNetworks = sessionForApprove.account?.let { getAccountNetworks(it.accountId) }
?: getWalletNetworks(sessionForApprove.wallet.walletId)
val portfolioNetworks = getAccountNetworks(sessionForApprove.account.accountId)
return sessionForApprove.network
.map { network -> portfolioNetworks.filter { walletNetwork -> walletNetwork.rawId == network.rawId } }
.flatten()
}
suspend fun getWalletNetworks(userWalletId: UserWalletId): List<Network> {
return multiWalletCryptoCurrenciesSupplier.getSyncOrNull(
params = MultiWalletCryptoCurrenciesProducer.Params(userWalletId = userWalletId),
)
.orEmpty()
.filterIsInstance<CryptoCurrency.Coin>().map(CryptoCurrency.Coin::network)
}
private suspend fun getAccountStatus(accountId: AccountId): AccountStatus.CryptoPortfolio? {
return singleAccountStatusListSupplier.getSyncOrNull(
SingleAccountStatusListProducer.Params(accountId.userWalletId),

View file

@ -15,6 +15,7 @@ import com.tangem.data.walletconnect.pair.DefaultWcPairUseCase
import com.tangem.data.walletconnect.pair.WcPairSdkDelegate
import com.tangem.data.walletconnect.utils.WcSdkSessionConverter
import com.tangem.domain.blockaid.BlockAidVerifier
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.walletconnect.model.WcPairError
import com.tangem.domain.walletconnect.model.WcPairRequest
@ -73,7 +74,7 @@ internal class DefaultWcPairUseCaseTest {
get() = WcSessionApprove(
wallet = MockUserWalletFactory.create(),
network = listOf(),
account = null,
account = Account.CryptoPortfolio.createMainAccount(MockUserWalletFactory.create().walletId),
)
private val sdkApprove: Wallet.Params.SessionApprove
@ -106,7 +107,7 @@ internal class DefaultWcPairUseCaseTest {
networks = setOf(),
connectingTime = null,
showWalletInfo = false,
account = null,
account = Account.CryptoPortfolio.createMainAccount(MockUserWalletFactory.create().walletId),
)
private fun useCaseFactory() = DefaultWcPairUseCase(
@ -120,7 +121,6 @@ internal class DefaultWcPairUseCaseTest {
@Before
fun setup() {
coEvery { associateNetworksDelegate.associate(sdkProposal) } returns mapOf()
coEvery { associateNetworksDelegate.associateAccounts(sdkProposal) } returns mapOf()
coEvery {
caipNamespaceDelegate.associate(
@ -254,7 +254,6 @@ internal class DefaultWcPairUseCaseTest {
assertEquals(loading, awaitItem())
coVerifyOrder {
sdkDelegate.pair(url)
associateNetworksDelegate.associate(sdkProposal)
blockAidVerifier.verifyDApp(DAppData(sdkVerifyContext.origin))
}
assert(awaitItem() is WcPairState.Proposal)

View file

@ -10,6 +10,7 @@ import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.data.walletconnect.sign.*
import com.tangem.data.walletconnect.sign.SignStateConverter.toResult
import com.tangem.data.walletconnect.sign.SignStateConverter.toSigning
import com.tangem.domain.models.account.Account
import com.tangem.domain.walletconnect.model.WcRequestError
import com.tangem.domain.walletconnect.model.WcSession
import com.tangem.domain.walletconnect.model.sdkcopy.WcAppMetaData
@ -58,7 +59,7 @@ internal class WcSignUseCaseDelegateTest {
session = WcSession(
wallet = MockUserWalletFactory.create(),
networks = setOf(),
account = null,
account = Account.CryptoPortfolio.createMainAccount(MockUserWalletFactory.create().walletId),
securityStatus = CheckDAppResult.FAILED_TO_VERIFY,
connectingTime = 0L,
sdkModel = WcSdkSession(

View file

@ -8,7 +8,7 @@ import com.tangem.domain.models.wallet.UserWallet
data class WcSession(
val wallet: UserWallet,
val account: Account.CryptoPortfolio?,
val account: Account.CryptoPortfolio,
val networks: Set<Network>,
val sdkModel: WcSdkSession,
val securityStatus: CheckDAppResult,

View file

@ -6,6 +6,6 @@ import com.tangem.domain.models.wallet.UserWallet
data class WcSessionApprove(
val wallet: UserWallet,
val account: Account?,
val account: Account,
val network: List<Network>,
)

View file

@ -9,7 +9,7 @@ import com.tangem.domain.models.wallet.UserWalletId
data class WcSessionDTO(
val topic: String,
val walletId: UserWalletId,
val accountId: AccountId? = null,
val accountId: AccountId = AccountId.forMainCryptoPortfolio(walletId),
val url: String?,
val securityStatus: CheckDAppResult = CheckDAppResult.FAILED_TO_VERIFY,
val connectingTime: Long? = null,

View file

@ -9,8 +9,7 @@ import com.tangem.domain.walletconnect.model.sdkcopy.WcAppMetaData
data class WcSessionProposal(
val dAppMetaData: WcAppMetaData,
val proposalNetwork: Map<UserWallet, ProposalNetwork>,
val proposalAccountNetwork: Map<AccountId, ProposalNetwork>?,
val proposalAccountNetwork: Map<AccountId, ProposalNetwork>,
val securityStatus: CheckDAppResult,
) {

View file

@ -66,7 +66,6 @@ internal class WcPairComponent(
else -> model.stackNavigation.pop()
}
is WcAppInfoRoutes.SelectNetworks,
is WcAppInfoRoutes.SelectWallet,
is WcAppInfoRoutes.PortfolioSelector,
-> model.stackNavigation.pop()
}
@ -102,14 +101,6 @@ internal class WcPairComponent(
callback = model,
),
)
is WcAppInfoRoutes.SelectWallet -> WcSelectWalletComponent(
appComponentContext = appComponentContext,
params = WcSelectWalletComponent.WcSelectWalletParams(
selectedWalletId = config.selectedWalletId,
onDismiss = ::dismiss,
callback = model,
),
)
WcAppInfoRoutes.PortfolioSelector -> portfolioSelectorComponentFactory.create(
context = appComponentContext,
params = PortfolioSelectorComponent.Params(

View file

@ -1,216 +0,0 @@
package com.tangem.features.walletconnect.connections.components
import android.content.res.Configuration
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Devices
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEach
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.common.ui.userwallet.UserWalletItem
import com.tangem.common.ui.userwallet.state.UserWalletItemUM
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.components.block.TangemBlockCardColors
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.decompose.ComposableBottomSheetComponent
import com.tangem.core.ui.extensions.TextReference
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.wallet.UserWalletId
import com.tangem.features.walletconnect.connections.model.WcSelectWalletModel
import com.tangem.features.walletconnect.impl.R
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
internal class WcSelectWalletComponent(
appComponentContext: AppComponentContext,
private val params: WcSelectWalletParams,
) : AppComponentContext by appComponentContext, ComposableBottomSheetComponent {
private val model: WcSelectWalletModel = getOrCreateModel(params = params)
override fun dismiss() {
params.onDismiss()
}
@Composable
override fun BottomSheet() {
val state by model.state.collectAsStateWithLifecycle()
WcSelectWalletModalBS(
wallets = state.wallets,
selectedWalletId = state.selectedUserWalletId,
onBack = router::pop,
onDismiss = ::dismiss,
)
}
interface ModelCallback {
fun onWalletSelected(userWalletId: UserWalletId)
}
data class WcSelectWalletParams(
val selectedWalletId: UserWalletId,
val callback: ModelCallback,
val onDismiss: () -> Unit,
)
}
@Composable
private fun WcSelectWalletModalBS(
wallets: ImmutableList<UserWalletItemUM>,
selectedWalletId: UserWalletId,
onBack: () -> Unit,
onDismiss: () -> Unit,
modifier: Modifier = Modifier,
) {
if (wallets.isEmpty()) return
TangemModalBottomSheet<TangemBottomSheetConfigContent.Empty>(
config = TangemBottomSheetConfig(
isShown = true,
onDismissRequest = onDismiss,
content = TangemBottomSheetConfigContent.Empty,
),
onBack = onBack,
containerColor = TangemTheme.colors.background.primary,
title = {
TangemModalBottomSheetTitle(
title = resourceReference(R.string.common_choose_wallet),
startIconRes = R.drawable.ic_back_24,
onStartClick = onBack,
)
},
content = {
WcSelectWalletContent(
modifier = modifier.padding(start = 16.dp, end = 16.dp, bottom = 16.dp),
wallets = wallets,
selectedWalletId = selectedWalletId,
)
},
)
}
@Composable
private fun WcSelectWalletContent(
wallets: ImmutableList<UserWalletItemUM>,
selectedWalletId: UserWalletId,
modifier: Modifier = Modifier,
) {
Column(modifier = modifier) {
wallets.fastForEach { state ->
key(state.id) {
val baseModifier = Modifier
.clip(RoundedCornerShape(14.dp))
.clickable(onClick = state.onClick)
val itemModifier = if (state.id == selectedWalletId.stringValue) {
baseModifier.border(
width = 1.dp,
color = TangemTheme.colors.text.accent,
shape = RoundedCornerShape(14.dp),
)
} else {
baseModifier
}
UserWalletItem(
modifier = itemModifier,
state = state,
blockColors = TangemBlockCardColors.copy(
containerColor = Color.Unspecified,
disabledContainerColor = Color.Unspecified,
),
)
}
}
}
}
@Suppress("LongMethod")
@Composable
@Preview(showBackground = true, device = Devices.PIXEL_7_PRO)
@Preview(showBackground = true, device = Devices.PIXEL_7_PRO, uiMode = Configuration.UI_MODE_NIGHT_YES)
private fun WcSelectWalletContent_Preview() {
val wallets = persistentListOf(
UserWalletItemUM(
id = "user_wallet_1",
name = stringReference("Tangem 2.0"),
information = getInformation(42),
balance = UserWalletItemUM.Balance.Loaded("1 496,34 $", isFlickering = false),
isEnabled = true,
onClick = {},
),
UserWalletItemUM(
id = "user_wallet_2",
name = stringReference("Tangem White"),
information = getInformation(24),
balance = UserWalletItemUM.Balance.Loaded("1 496,34 $", isFlickering = false),
isEnabled = true,
onClick = {},
),
UserWalletItemUM(
id = "user_wallet_3",
name = stringReference("Bitcoin"),
information = getInformation(1),
balance = UserWalletItemUM.Balance.Loaded("1 496,34 $", isFlickering = false),
isEnabled = true,
onClick = {},
),
UserWalletItemUM(
id = "user_wallet_4",
name = stringReference("Tangem 1.0"),
information = getInformation(21),
balance = UserWalletItemUM.Balance.Loaded("1 496,34 $", isFlickering = false),
isEnabled = true,
onClick = {},
),
UserWalletItemUM(
id = "user_wallet_4",
name = stringReference("Tangem 1.0"),
information = UserWalletItemUM.Information.Loading,
balance = UserWalletItemUM.Balance.Loaded("1 496,34 $", isFlickering = false),
isEnabled = true,
onClick = {},
),
UserWalletItemUM(
id = "user_wallet_4",
name = stringReference("Tangem 1.0"),
information = UserWalletItemUM.Information.Failed,
balance = UserWalletItemUM.Balance.Loaded("1 496,34 $", isFlickering = false),
isEnabled = true,
onClick = {},
),
)
TangemThemePreview {
WcSelectWalletModalBS(
wallets = wallets,
selectedWalletId = UserWalletId(wallets.first().id.encodeToByteArray()),
onBack = {},
onDismiss = {},
)
}
}
private fun getInformation(tokenCount: Int): UserWalletItemUM.Information.Loaded {
val text = TextReference.PluralRes(
id = R.plurals.card_label_token_count,
count = tokenCount,
formatArgs = wrappedList(tokenCount),
)
return UserWalletItemUM.Information.Loaded(text)
}

View file

@ -27,9 +27,7 @@ internal sealed class WcAppInfoUM : TangemBottomSheetConfigContent {
val verifiedDAppState: VerifiedDAppState,
val appSubtitle: String,
val notification: WcAppInfoSecurityNotification?,
val portfolioSelectRow: PortfolioSelectUM?,
val walletName: String,
val onWalletClick: (() -> Unit)?,
val portfolioSelectRow: PortfolioSelectUM,
val networksInfo: WcNetworksInfo,
val onNetworksClick: () -> Unit,
override val connectButtonConfig: WcPrimaryButtonConfig,

View file

@ -21,16 +21,11 @@ import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.message.SnackbarMessage
import com.tangem.core.ui.message.ToastMessage
import com.tangem.domain.account.producer.SingleAccountListProducer
import com.tangem.domain.account.supplier.SingleAccountListSupplier
import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.account.AccountStatus
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.wallet.UserWallet
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.WcPairError
import com.tangem.domain.walletconnect.model.WcPairError.Unknown
@ -40,16 +35,17 @@ import com.tangem.domain.walletconnect.model.WcSessionProposal
import com.tangem.domain.walletconnect.model.sdkcopy.WcAppMetaData
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.account.PortfolioFetcher
import com.tangem.features.account.PortfolioSelectorComponent
import com.tangem.features.account.PortfolioSelectorController
import com.tangem.features.walletconnect.connections.components.WcPairComponent
import com.tangem.features.walletconnect.connections.components.WcSelectNetworksComponent
import com.tangem.features.walletconnect.connections.components.WcSelectWalletComponent
import com.tangem.features.walletconnect.connections.entity.WcAppInfoUM
import com.tangem.features.walletconnect.connections.entity.WcPrimaryButtonConfig
import com.tangem.features.walletconnect.connections.model.transformers.*
import com.tangem.features.walletconnect.connections.model.transformers.WcAppInfoTransformer
import com.tangem.features.walletconnect.connections.model.transformers.WcConnectButtonProgressTransformer
import com.tangem.features.walletconnect.connections.model.transformers.WcDAppVerifiedStateConverter
import com.tangem.features.walletconnect.connections.model.transformers.WcNetworksSelectedTransformer
import com.tangem.features.walletconnect.connections.routes.WcAppInfoRoutes
import com.tangem.features.walletconnect.impl.R
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
@ -61,11 +57,8 @@ import kotlin.properties.Delegates
import com.tangem.utils.transformer.update as transformerUpdate
internal interface WcPairComponentCallback :
WcSelectWalletComponent.ModelCallback,
WcSelectNetworksComponent.ModelCallback
private const val WC_WALLETS_SELECTOR_MIN_COUNT = 2
@Stable
@ModelScoped
@Suppress("LongParameterList", "LargeClass")
@ -75,11 +68,9 @@ internal class WcPairModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider,
private val analytics: AnalyticsEventHandler,
val selectorController: PortfolioSelectorController,
private val singleAccountListSupplier: SingleAccountListSupplier,
private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase,
portfolioFetcherFactory: PortfolioFetcher.Factory,
wcPairUseCaseFactory: WcPairUseCase.Factory,
getWalletsUseCase: GetWalletsUseCase,
paramsContainer: ParamsContainer,
) : Model(), WcPairComponentCallback {
@ -102,9 +93,6 @@ internal class WcPairModel @Inject constructor(
override val onBack: () -> Unit = { stackNavigation.pop() }
}
private val selectedUserWalletFlow: MutableStateFlow<UserWallet> by lazy {
MutableStateFlow(getWalletsUseCase.invokeSync().first { it.walletId == params.userWalletId })
}
private val selectedPortfolio = MutableSharedFlow<Pair<UserWallet, AccountStatus.CryptoPortfolio>>(
replay = 1,
onBufferOverflow = BufferOverflow.DROP_OLDEST,
@ -119,14 +107,15 @@ internal class WcPairModel @Inject constructor(
init {
modelScope.launch {
val params = SingleAccountListProducer.Params(params.userWalletId)
val accountList = singleAccountListSupplier.getSyncOrNull(params)
if (accountList == null) {
val portfolioBalance = portfolioFetcher.data.first().balances
.firstNotNullOfOrNull { (walletId, balance) ->
if (params.userWalletId == walletId) balance else null
}
if (portfolioBalance == null) {
router.pop()
return@launch
}
val firstAccount = accountList.accounts.first()
selectorController.selectAccount(firstAccount.accountId)
selectorController.selectAccount(portfolioBalance.accountsBalance.mainAccount.accountId)
combineFlows(portfolioFetcher)
}
}
@ -135,10 +124,12 @@ internal class WcPairModel @Inject constructor(
combine(
flow = portfolioFetcher.data,
flow2 = selectorController.selectedAccountWithData(portfolioFetcher)
.distinctUntilChanged()
.filterNotNull()
.onEach { selectedPortfolio.tryEmit(it) }
.onEach { stackNavigation.pop() },
.runningReduce { _, new ->
stackNavigation.pop()
new
},
flow3 = wcPairUseCase(),
flow4 = isAccountsModeEnabledUseCase(),
transform = { portfolios, selected, pairState, isAccountMode ->
@ -155,9 +146,9 @@ internal class WcPairModel @Inject constructor(
private suspend fun handlePairState(
pairState: WcPairState,
portfolios: PortfolioFetcher.Data? = null,
selected: Pair<UserWallet, AccountStatus.CryptoPortfolio>? = null,
isAccountMode: Boolean? = null,
portfolios: PortfolioFetcher.Data,
selected: Pair<UserWallet, AccountStatus.CryptoPortfolio>,
isAccountMode: Boolean,
) {
when (pairState) {
is WcPairState.Approving.Loading -> appInfoUiState.transformerUpdate(
@ -184,36 +175,32 @@ internal class WcPairModel @Inject constructor(
pairState = pairState,
portfolios = portfolios,
selected = selected,
isAccountMode = isAccountMode,
)
}
}
private suspend fun handleProposalState(
pairState: WcPairState.Proposal,
portfolios: PortfolioFetcher.Data? = null,
selected: Pair<UserWallet, AccountStatus.CryptoPortfolio>? = null,
portfolios: PortfolioFetcher.Data,
selected: Pair<UserWallet, AccountStatus.CryptoPortfolio>,
isAccountMode: Boolean,
) {
val availableWallets = pairState.dAppSession.proposalNetwork.keys
.filter { !it.isLocked && it.isMultiCurrency }
sessionProposal = pairState.dAppSession
val selectedUserWalletFlow = this.selectedUserWalletFlow
val portfolioWallet = selected?.first
val portfolioAccount = selected?.second
val portfolioAccountId = portfolioAccount?.account?.accountId
val portfolioAccount = selected.second
val portfolioAccountId = portfolioAccount.account.accountId
val proposalAccountNetwork = sessionProposal.proposalAccountNetwork
val foundNetwork = if (portfolioAccountId != null) {
requireNotNull(proposalAccountNetwork)[portfolioAccountId]
} else {
sessionProposal.proposalNetwork[selectedUserWalletFlow.value]
}
val foundNetwork = proposalAccountNetwork[portfolioAccountId]
if (foundNetwork == null) {
processError(Unknown("Selected wallet not found"))
} else {
val portfolioSelectRow = tryToCreatePortfolioSelectRow(selected, portfolios)
if (proposalAccountNetwork != null) {
selectorController.isEnabled.value = { _, account ->
proposalAccountNetwork.contains(account.account.accountId)
}
val portfolioSelectRow = createPortfolioSelectRow(
selectedPortfolio = selected,
portfolios = portfolios,
isAccountMode = isAccountMode,
)
selectorController.isEnabled.value = { _, account ->
proposalAccountNetwork.contains(account.account.accountId)
}
proposalNetwork = foundNetwork
additionallyEnabledNetworks = proposalNetwork.available
@ -224,13 +211,6 @@ internal class WcPairModel @Inject constructor(
onDismiss = ::rejectPairing,
onConnect = ::onConnect,
portfolioSelectRow = portfolioSelectRow,
onWalletClick = {
stackNavigation.pushNew(
WcAppInfoRoutes.SelectWallet(selectedUserWalletFlow.value.walletId),
)
}.takeIf {
portfolioSelectRow == null && availableWallets.size >= WC_WALLETS_SELECTOR_MIN_COUNT
},
onNetworksClick = {
stackNavigation.pushNew(
WcAppInfoRoutes.SelectNetworks(
@ -242,7 +222,6 @@ internal class WcPairModel @Inject constructor(
),
)
},
userWallet = portfolioWallet ?: selectedUserWalletFlow.value,
proposalNetwork = proposalNetwork,
additionallyEnabledNetworks = additionallyEnabledNetworks,
),
@ -250,17 +229,15 @@ internal class WcPairModel @Inject constructor(
}
}
private suspend fun tryToCreatePortfolioSelectRow(
selectedPortfolio: Pair<UserWallet, AccountStatus.CryptoPortfolio>?,
portfolios: PortfolioFetcher.Data?,
): PortfolioSelectUM? {
selectedPortfolio ?: return null
portfolios ?: return null
private suspend fun createPortfolioSelectRow(
selectedPortfolio: Pair<UserWallet, AccountStatus.CryptoPortfolio>,
portfolios: PortfolioFetcher.Data,
isAccountMode: Boolean,
): PortfolioSelectUM {
val (wallet, portfolioAccount) = selectedPortfolio
val account = when (val account = portfolioAccount.account) {
is Account.CryptoPortfolio -> account
}
val isAccountMode = selectorController.isAccountMode.first()
val icon: AccountIconUM.CryptoPortfolio?
val name: TextReference
if (isAccountMode) {
@ -303,15 +280,16 @@ internal class WcPairModel @Inject constructor(
private fun connect() {
val enabledAvailableNetworks =
proposalNetwork.available.filter { network -> network in additionallyEnabledNetworks }
val selectedPortfolio = selectedPortfolio.replayCache.firstOrNull()
val wallet = selectedPortfolio?.first ?: selectedUserWalletFlow.value
val account = selectedPortfolio?.second?.account
val selectedPortfolio = selectedPortfolio.replayCache
.firstOrNull()
?: return
val (wallet, account) = selectedPortfolio
modelScope.launch {
analytics.send(
WcAnalyticEvents.PairButtonConnect(
dAppName = sessionProposal.dAppMetaData.name,
accountDerivation = account?.derivationIndex?.value,
accountDerivation = account.account.derivationIndex.value,
),
)
}
@ -319,7 +297,7 @@ internal class WcPairModel @Inject constructor(
WcSessionApprove(
wallet = wallet,
network = enabledAvailableNetworks + proposalNetwork.required,
account = account,
account = account.account,
),
)
}
@ -375,20 +353,6 @@ internal class WcPairModel @Inject constructor(
alert?.let { stackNavigation.pushNew(it) }
}
override fun onWalletSelected(userWalletId: UserWalletId) {
val selectedUserWallet = sessionProposal.proposalNetwork.keys.first { it.walletId == userWalletId }
proposalNetwork = sessionProposal.proposalNetwork[selectedUserWallet] ?: return
selectedUserWalletFlow.update { selectedUserWallet }
additionallyEnabledNetworks = proposalNetwork.available
appInfoUiState.transformerUpdate(
WcAppInfoWalletChangedTransformer(
selectedUserWallet = selectedUserWallet,
proposalNetwork = proposalNetwork,
additionallyEnabledNetworks = additionallyEnabledNetworks,
),
)
}
override fun onNetworksSelected(selectedNetworks: Set<Network>) {
additionallyEnabledNetworks = selectedNetworks
appInfoUiState.transformerUpdate(

View file

@ -1,60 +0,0 @@
package com.tangem.features.walletconnect.connections.model
import androidx.compose.runtime.Stable
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.decompose.ui.UiMessageSender
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.features.wallet.utils.UserWalletsFetcher
import com.tangem.features.walletconnect.connections.components.WcSelectWalletComponent.WcSelectWalletParams
import com.tangem.features.walletconnect.connections.entity.WcAppInfoWalletUM
import com.tangem.features.walletconnect.connections.utils.WcUserWalletsFetcher
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.flow.*
import javax.inject.Inject
@Suppress("LongParameterList")
@Stable
@ModelScoped
internal class WcSelectWalletModel @Inject constructor(
paramsContainer: ParamsContainer,
messageSender: UiMessageSender,
userWalletsFetcherFactory: UserWalletsFetcher.Factory,
private val singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
private val router: Router,
override val dispatchers: CoroutineDispatcherProvider,
) : Model() {
private val params = paramsContainer.require<WcSelectWalletParams>()
internal val state: StateFlow<WcAppInfoWalletUM>
field = MutableStateFlow<WcAppInfoWalletUM>(
WcAppInfoWalletUM(
wallets = persistentListOf(),
selectedUserWalletId = params.selectedWalletId,
),
)
private val userWalletsFetcher = WcUserWalletsFetcher(
userWalletsFetcherFactory = userWalletsFetcherFactory,
singleAccountStatusListSupplier = singleAccountStatusListSupplier,
messageSender = messageSender,
onWalletSelected = ::onWalletSelected,
)
init {
userWalletsFetcher
.userWallets
.onEach { state.update { state -> state.copy(wallets = it) } }
.launchIn(modelScope)
}
private fun onWalletSelected(userWalletId: UserWalletId) {
params.callback.onWalletSelected(userWalletId)
router.pop()
}
}

View file

@ -3,7 +3,6 @@ package com.tangem.features.walletconnect.connections.model.transformers
import com.domain.blockaid.models.dapp.CheckDAppResult
import com.tangem.common.ui.account.PortfolioSelectUM
import com.tangem.domain.models.network.Network
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
@ -16,10 +15,8 @@ internal class WcAppInfoTransformer(
private val dAppVerifiedStateConverter: WcDAppVerifiedStateConverter,
private val onDismiss: () -> Unit,
private val onConnect: (securityStatus: CheckDAppResult) -> Unit,
private val portfolioSelectRow: PortfolioSelectUM?,
private val onWalletClick: (() -> Unit)?,
private val portfolioSelectRow: PortfolioSelectUM,
private val onNetworksClick: () -> Unit,
private val userWallet: UserWallet,
private val proposalNetwork: WcSessionProposal.ProposalNetwork,
private val additionallyEnabledNetworks: Set<Network>,
) : Transformer<WcAppInfoUM> {
@ -33,8 +30,6 @@ internal class WcAppInfoTransformer(
),
appSubtitle = WcAppSubtitleConverter.convert(dAppSession.dAppMetaData),
notification = createNotification(dAppSession.securityStatus),
walletName = userWallet.name,
onWalletClick = onWalletClick,
portfolioSelectRow = portfolioSelectRow,
networksInfo = WcNetworksInfoConverter.convert(
value = WcNetworksInfoConverter.Input(

View file

@ -1,39 +0,0 @@
package com.tangem.features.walletconnect.connections.model.transformers
import com.tangem.domain.models.network.Network
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
internal class WcAppInfoWalletChangedTransformer(
private val selectedUserWallet: UserWallet,
private val proposalNetwork: WcSessionProposal.ProposalNetwork,
private val additionallyEnabledNetworks: Set<Network>,
) : Transformer<WcAppInfoUM> {
override fun transform(prevState: WcAppInfoUM): WcAppInfoUM {
val contentState = prevState as? WcAppInfoUM.Content ?: return prevState
return contentState.copy(
walletName = selectedUserWallet.name,
networksInfo = WcNetworksInfoConverter.convert(
WcNetworksInfoConverter.Input(
missingNetworks = proposalNetwork.missingRequired,
requiredNetworks = proposalNetwork.required,
availableNetworks = proposalNetwork.available,
notAddedNetworks = proposalNetwork.notAdded,
additionallyEnabledNetworks = additionallyEnabledNetworks,
),
),
connectButtonConfig = prevState.connectButtonConfig.copy(
enabled = WcConnectButtonAvailabilityConverter.convert(
WcConnectButtonAvailabilityConverter.Input(
missingNetworks = proposalNetwork.missingRequired,
requiredNetworks = proposalNetwork.required,
availableNetworks = proposalNetwork.available,
selectedNetworks = additionallyEnabledNetworks,
),
),
),
)
}
}

View file

@ -49,7 +49,7 @@ internal class WcSessionsAccountModeTransformer(
items.add(walletHeader)
accountList.accounts.filterIsInstance<Account.CryptoPortfolio>().forEach accountsForEach@{ account ->
val accountSessions = sessions.filter { it.account?.accountId == account.accountId }
val accountSessions = sessions.filter { it.account.accountId == account.accountId }
if (accountSessions.isEmpty()) return@accountsForEach
val connectedApps = accountSessions.map { dappSession ->
with(dappSession.sdkModel) {

View file

@ -3,7 +3,6 @@ package com.tangem.features.walletconnect.connections.routes
import androidx.compose.runtime.Immutable
import com.tangem.core.decompose.navigation.Route
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.wallet.UserWalletId
import kotlinx.serialization.Serializable
@Serializable
@ -15,9 +14,6 @@ internal sealed class WcAppInfoRoutes : Route {
@Serializable
data object PortfolioSelector : WcAppInfoRoutes()
@Serializable
data class SelectWallet(val selectedWalletId: UserWalletId) : WcAppInfoRoutes()
@Serializable
data class SelectNetworks(
val missingRequiredNetworks: Set<Network>,

View file

@ -22,8 +22,6 @@ import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Devices
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
@ -273,19 +271,7 @@ private fun WcAppInfoSecondBlock(state: WcAppInfoUM.Content, modifier: Modifier
val itemsModifier = Modifier
.fillMaxWidth()
.padding(TangemTheme.dimens.spacing12)
if (state.portfolioSelectRow != null) {
PortfolioRowItem(portfolioSelectRow = state.portfolioSelectRow)
} else {
WalletRowItem(
modifier = if (state.onWalletClick != null) {
Modifier.clickableSingle(onClick = state.onWalletClick)
} else {
Modifier
}.then(itemsModifier),
walletName = state.walletName,
showEndIcon = state.onWalletClick != null,
)
}
PortfolioRowItem(portfolioSelectRow = state.portfolioSelectRow)
HorizontalDivider(thickness = 1.dp, color = TangemTheme.colors.stroke.primary)
SelectNetworksBlock(
modifier = Modifier
@ -341,56 +327,6 @@ private fun PortfolioRowItem(portfolioSelectRow: PortfolioSelectUM, modifier: Mo
}
}
@Composable
private fun WalletRowItem(walletName: String, showEndIcon: Boolean, modifier: Modifier = Modifier) {
Row(modifier = modifier, verticalAlignment = Alignment.CenterVertically) {
Icon(
modifier = Modifier
.size(24.dp)
.testTag(WalletConnectBottomSheetTestTags.WALLET_ICON),
painter = painterResource(R.drawable.ic_wallet_new_24),
contentDescription = null,
tint = TangemTheme.colors.icon.accent,
)
Row(
modifier = Modifier.weight(1f),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
Text(
modifier = Modifier
.padding(start = TangemTheme.dimens.spacing4)
.testTag(WalletConnectBottomSheetTestTags.WALLET_NAME_TITLE),
text = stringResourceSafe(R.string.manage_tokens_network_selector_wallet),
style = TangemTheme.typography.body1,
color = TangemTheme.colors.text.primary1,
maxLines = 1,
)
Text(
modifier = Modifier
.padding(start = TangemTheme.dimens.spacing16)
.testTag(WalletConnectBottomSheetTestTags.WALLET_NAME),
text = walletName,
textAlign = TextAlign.End,
style = TangemTheme.typography.body1,
color = TangemTheme.colors.text.tertiary,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
if (showEndIcon) {
Icon(
modifier = Modifier
.padding(start = TangemTheme.dimens.spacing12)
.size(width = 18.dp, height = 24.dp),
painter = painterResource(R.drawable.ic_select_18_24),
contentDescription = null,
tint = TangemTheme.colors.icon.informative,
)
}
}
}
@Composable
private fun SelectNetworksBlock(networksInfo: WcNetworksInfo, modifier: Modifier = Modifier) {
Row(
@ -673,9 +609,7 @@ private class WcAppInfoStateProvider : CollectionPreviewParameterProvider<WcAppI
verifiedDAppState = VerifiedDAppState.Verified {},
appSubtitle = "react-app.walletconnect.com",
notification = WcAppInfoSecurityNotification.SecurityRisk,
walletName = "Tangem 2.0 Tangem 2.0 Tangem 2.0 Tangem 2.0",
onWalletClick = null,
portfolioSelectRow = null,
portfolioSelectRow = PortfolioSelectRowPreviewData.account,
networksInfo = WcNetworksInfo.ContainsAllRequiredNetworks(
items = persistentListOf(
WcNetworkInfoItem.Required(
@ -720,9 +654,7 @@ private class WcAppInfoStateProvider : CollectionPreviewParameterProvider<WcAppI
verifiedDAppState = VerifiedDAppState.Unknown,
appSubtitle = "react-app.walletconnect.com",
notification = WcAppInfoSecurityNotification.UnknownDomain,
portfolioSelectRow = null,
walletName = "Tangem 2.0 Tangem 2.0 Tangem 2.0 Tangem 2.0",
onWalletClick = {},
portfolioSelectRow = PortfolioSelectRowPreviewData.wallet,
networksInfo = WcNetworksInfo.MissingRequiredNetworkInfo(networks = "Solana"),
onNetworksClick = {},
onDismiss = {},
@ -735,8 +667,6 @@ private class WcAppInfoStateProvider : CollectionPreviewParameterProvider<WcAppI
appSubtitle = "react-app.walletconnect.com",
notification = WcAppInfoSecurityNotification.UnknownDomain,
portfolioSelectRow = PortfolioSelectRowPreviewData.account,
walletName = "Tangem 2.0 Tangem 2.0 Tangem 2.0 Tangem 2.0",
onWalletClick = {},
networksInfo = WcNetworksInfo.MissingRequiredNetworkInfo(networks = "Solana"),
onNetworksClick = {},
onDismiss = {},

View file

@ -1,64 +0,0 @@
package com.tangem.features.walletconnect.connections.utils
import com.tangem.common.ui.R
import com.tangem.common.ui.userwallet.state.UserWalletItemUM
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
import com.tangem.domain.models.TotalFiatBalance
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.features.wallet.utils.UserWalletsFetcher
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
@ModelScoped
internal class WcUserWalletsFetcher(
userWalletsFetcherFactory: UserWalletsFetcher.Factory,
messageSender: UiMessageSender,
private val singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
private val onWalletSelected: (UserWalletId) -> Unit,
) {
private val userWalletsFetcher = userWalletsFetcherFactory.create(
messageSender = messageSender,
onlyMultiCurrency = true,
isAuthMode = false,
isClickableIfLocked = false,
onWalletClick = { onWalletSelected(it) },
)
@OptIn(ExperimentalCoroutinesApi::class)
val userWallets: Flow<ImmutableList<UserWalletItemUM>> = userWalletsFetcher.userWallets
.flatMapLatest { listOfWalletItem ->
val flows = listOfWalletItem.map(::getTokenListFlow)
combine(flows) { it.toList().toImmutableList() }
}
private fun getTokenListFlow(walletItem: UserWalletItemUM): Flow<UserWalletItemUM> {
return singleAccountStatusListSupplier(userWalletId = UserWalletId(walletItem.id)).map { accountStatusList ->
val information = when (accountStatusList.totalFiatBalance) {
is TotalFiatBalance.Failed -> UserWalletItemUM.Information.Failed
is TotalFiatBalance.Loading -> UserWalletItemUM.Information.Loading
is TotalFiatBalance.Loaded -> tokenCountInfo(accountStatusList.flattenCurrencies().size)
}
walletItem.copy(information = information)
}
}
private fun tokenCountInfo(count: Int): UserWalletItemUM.Information.Loaded {
val text = TextReference.PluralRes(
id = R.plurals.card_label_token_count,
count = count,
formatArgs = wrappedList(count),
)
return UserWalletItemUM.Information.Loaded(text)
}
}

View file

@ -28,11 +28,6 @@ internal interface WalletConnectModelModule {
@ClassKey(WcPairModel::class)
fun bindWcPairModel(model: WcPairModel): Model
@Binds
@IntoMap
@ClassKey(WcSelectWalletModel::class)
fun bindWcSelectWalletModel(model: WcSelectWalletModel): Model
@Binds
@IntoMap
@ClassKey(WcSelectNetworksModel::class)

View file

@ -137,7 +137,7 @@ internal class WcAddNetworkModel @Inject constructor(
network = useCase.network,
emulationStatus = null,
securityStatus = CheckDAppResult.FAILED_TO_VERIFY,
accountDerivation = useCase.session.account?.derivationIndex?.value,
accountDerivation = useCase.session.account.derivationIndex.value,
),
)

View file

@ -426,7 +426,7 @@ internal class WcSendTransactionModel @Inject constructor(
rawRequest = useCase.rawSdkRequest,
network = useCase.network,
securityStatus = securityStatusState.value.toCheckDAppResult(),
accountDerivation = useCase.session.account?.derivationIndex?.value,
accountDerivation = useCase.session.account.derivationIndex.value,
)
analytics.send(event)
showSuccessSignMessage()
@ -466,7 +466,7 @@ internal class WcSendTransactionModel @Inject constructor(
network = useCase.network,
emulationStatus = emulationStatus,
securityStatus = securityCheck.toCheckDAppResult(),
accountDerivation = useCase.session.account?.derivationIndex?.value,
accountDerivation = useCase.session.account.derivationIndex.value,
),
)

View file

@ -143,7 +143,7 @@ internal class WcSignTransactionModel @Inject constructor(
rawRequest = useCase.rawSdkRequest,
network = useCase.network,
securityStatus = CheckDAppResult.FAILED_TO_VERIFY,
accountDerivation = useCase.session.account?.derivationIndex?.value,
accountDerivation = useCase.session.account.derivationIndex.value,
)
analytics.send(event)
showSuccessSignMessage()
@ -172,7 +172,7 @@ internal class WcSignTransactionModel @Inject constructor(
network = useCase.network,
emulationStatus = null,
securityStatus = CheckDAppResult.FAILED_TO_VERIFY,
accountDerivation = useCase.session.account?.derivationIndex?.value,
accountDerivation = useCase.session.account.derivationIndex.value,
),
)