Updated on 2026-08-14
This commit is contained in:
parent
add9d552b4
commit
e7ebbb931e
12 changed files with 516 additions and 76 deletions
|
|
@ -24,6 +24,7 @@ import com.tangem.datasource.local.walletconnect.WalletConnectStore
|
|||
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
|
||||
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.tokens.MultiWalletCryptoCurrenciesSupplier
|
||||
import com.tangem.domain.walletconnect.WcPairService
|
||||
import com.tangem.domain.walletconnect.WcRequestService
|
||||
|
|
@ -185,11 +186,13 @@ internal object WalletConnectDataModule {
|
|||
walletManagersFacade: WalletManagersFacade,
|
||||
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
|
||||
singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
|
||||
singleAccountSupplier: SingleAccountSupplier,
|
||||
): WcNetworksConverter = WcNetworksConverter(
|
||||
namespaceConverters = namespaceConverters,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
singleAccountStatusListSupplier = singleAccountStatusListSupplier,
|
||||
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
|
||||
singleAccountSupplier = singleAccountSupplier,
|
||||
)
|
||||
|
||||
@Provides
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import com.tangem.core.analytics.api.AnalyticsEventHandler
|
|||
import com.tangem.data.walletconnect.utils.*
|
||||
import com.tangem.datasource.local.walletconnect.WalletConnectStore
|
||||
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
|
||||
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,12 +38,15 @@ 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
|
||||
|
|
@ -51,6 +55,26 @@ internal class DefaultWcSessionsManager(
|
|||
.distinctUntilChanged()
|
||||
.flowOn(dispatchers.io)
|
||||
|
||||
private suspend fun migrateToAccountSession(inStore: Set<WcSessionDTO>): Boolean {
|
||||
if (!accountsFeatureToggles.isFeatureEnabled) return false
|
||||
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()
|
||||
|
|
|
|||
|
|
@ -3,8 +3,10 @@ package com.tangem.data.walletconnect.utils
|
|||
import com.reown.walletkit.client.Wallet
|
||||
import com.tangem.data.common.currency.isCustomCoin
|
||||
import com.tangem.data.walletconnect.model.CAIP10
|
||||
import com.tangem.domain.account.producer.SingleAccountProducer
|
||||
import com.tangem.domain.account.status.producer.SingleAccountStatusListProducer
|
||||
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
|
||||
import com.tangem.domain.account.supplier.SingleAccountSupplier
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.account.AccountId
|
||||
import com.tangem.domain.models.account.AccountStatus
|
||||
|
|
@ -24,6 +26,7 @@ internal class WcNetworksConverter @Inject constructor(
|
|||
private val namespaceConverters: Set<WcNamespaceConverter>,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
|
||||
private val singleAccountSupplier: SingleAccountSupplier,
|
||||
private val multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
|
||||
) {
|
||||
|
||||
|
|
@ -101,7 +104,7 @@ internal class WcNetworksConverter @Inject constructor(
|
|||
}
|
||||
|
||||
suspend fun getAccount(accountId: AccountId): Account? {
|
||||
return getAccountStatus(accountId)?.account
|
||||
return singleAccountSupplier.getSyncOrNull(SingleAccountProducer.Params(accountId))
|
||||
}
|
||||
|
||||
suspend fun convertNetworksForApprove(sessionForApprove: WcSessionApprove): List<Network> {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.features.walletconnect.connections.entity
|
||||
|
||||
import com.tangem.common.ui.account.AccountTitleUM
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
|
|
@ -10,7 +11,7 @@ data class WcConnectedAppInfoUM(
|
|||
val verifiedDAppState: VerifiedDAppState,
|
||||
val notification: WcAppInfoSecurityNotification? = null,
|
||||
val appSubtitle: String,
|
||||
val walletName: String,
|
||||
val portfolioName: AccountTitleUM?,
|
||||
val connectingTime: Long?,
|
||||
val networks: ImmutableList<WcNetworkInfoItem.Required>,
|
||||
val disconnectButtonConfig: WcPrimaryButtonConfig,
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
package com.tangem.features.walletconnect.connections.entity
|
||||
|
||||
import com.tangem.common.ui.account.AccountTitleUM
|
||||
import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM
|
||||
import com.tangem.core.ui.components.dropdownmenu.TangemDropdownMenuItem
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
internal data class WcConnectionsState(
|
||||
val topAppBarConfig: WcConnectionsTopAppBarConfig,
|
||||
val connections: ImmutableList<WcConnectionsUM>,
|
||||
val connections: WcConnections,
|
||||
val onNewConnectionClick: () -> Unit,
|
||||
)
|
||||
|
||||
|
|
@ -15,6 +16,39 @@ internal data class WcConnectionsTopAppBarConfig(
|
|||
val disconnectAllItem: TangemDropdownMenuItem,
|
||||
)
|
||||
|
||||
internal sealed interface WcConnections {
|
||||
data object Loading : WcConnections
|
||||
data class WalletMode(
|
||||
val connections: ImmutableList<WcConnectionsUM>,
|
||||
) : WcConnections
|
||||
|
||||
data class AccountMode(
|
||||
val items: ImmutableList<WcConnectionsItem>,
|
||||
) : WcConnections
|
||||
|
||||
fun isNotEmpty(): Boolean = when (this) {
|
||||
is AccountMode -> items.isNotEmpty()
|
||||
is WalletMode -> connections.isNotEmpty()
|
||||
Loading -> false
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed interface WcConnectionsItem {
|
||||
val id: String
|
||||
|
||||
data class WalletHeader(
|
||||
override val id: String,
|
||||
val walletName: String,
|
||||
val isLocked: Boolean,
|
||||
) : WcConnectionsItem
|
||||
|
||||
data class PortfolioConnections(
|
||||
override val id: String,
|
||||
val portfolioTitle: AccountTitleUM,
|
||||
val connectedApps: ImmutableList<WcConnectedAppInfo>,
|
||||
) : WcConnectionsItem
|
||||
}
|
||||
|
||||
internal data class WcConnectionsUM(
|
||||
val userWalletId: String,
|
||||
val walletName: String,
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import com.tangem.features.walletconnect.connections.entity.WcAppInfoSecurityNot
|
|||
import com.tangem.features.walletconnect.connections.model.transformers.WcAppSubtitleConverter
|
||||
import com.tangem.features.walletconnect.connections.routes.ConnectedAppInfoRoutes
|
||||
import com.tangem.features.walletconnect.impl.R
|
||||
import com.tangem.features.walletconnect.transaction.converter.WcPortfolioNameDelegate
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
|
@ -39,10 +40,12 @@ internal class WcConnectedAppInfoModel @Inject constructor(
|
|||
private val wcSessionsUseCase: WcSessionsUseCase,
|
||||
private val messageSender: UiMessageSender,
|
||||
private val disconnectUseCase: WcDisconnectUseCase,
|
||||
portfolioNameDelegateFactory: WcPortfolioNameDelegate.Factory,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
paramsContainer: ParamsContainer,
|
||||
) : Model() {
|
||||
|
||||
private val portfolioNameDelegate = portfolioNameDelegateFactory.create(modelScope)
|
||||
private val params = paramsContainer.require<WcConnectedAppInfoContainerComponent.Params>()
|
||||
val uiState: StateFlow<WcConnectedAppInfoUM?>
|
||||
field = MutableStateFlow<WcConnectedAppInfoUM?>(null)
|
||||
|
|
@ -68,7 +71,7 @@ internal class WcConnectedAppInfoModel @Inject constructor(
|
|||
isVerified = session.securityStatus == CheckDAppResult.SAFE,
|
||||
verifiedDAppState = extractVerifiedState(session),
|
||||
appSubtitle = WcAppSubtitleConverter.convert(session.sdkModel.appMetaData),
|
||||
walletName = session.wallet.name,
|
||||
portfolioName = portfolioNameDelegate.createAccountTitleUM(session),
|
||||
connectingTime = session.connectingTime,
|
||||
networks = session.networks
|
||||
.distinctBy { network -> network.rawId }
|
||||
|
|
|
|||
|
|
@ -18,6 +18,9 @@ import com.tangem.core.ui.message.DialogMessage
|
|||
import com.tangem.core.ui.message.EventMessageAction
|
||||
import com.tangem.core.ui.message.SnackbarMessage
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
|
||||
import com.tangem.domain.account.supplier.MultiAccountListSupplier
|
||||
import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.qrscanning.models.QrResultSource
|
||||
import com.tangem.domain.qrscanning.models.SourceType
|
||||
import com.tangem.domain.qrscanning.usecases.ListenToQrScanningUseCase
|
||||
|
|
@ -28,14 +31,16 @@ 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.WcConnectionsComponent
|
||||
import com.tangem.features.walletconnect.connections.entity.WcConnections
|
||||
import com.tangem.features.walletconnect.connections.entity.WcConnectionsState
|
||||
import com.tangem.features.walletconnect.connections.entity.WcConnectionsTopAppBarConfig
|
||||
import com.tangem.features.walletconnect.connections.model.transformers.WcSessionsAccountModeTransformer
|
||||
import com.tangem.features.walletconnect.connections.model.transformers.WcSessionsTransformer
|
||||
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.Transformer
|
||||
import com.tangem.utils.transformer.update
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
|
@ -48,6 +53,9 @@ internal class WcConnectionsModel @Inject constructor(
|
|||
private val listenToQrScanningUseCase: ListenToQrScanningUseCase,
|
||||
private val wcSessionsUseCase: WcSessionsUseCase,
|
||||
private val wcDisconnectUseCase: WcDisconnectUseCase,
|
||||
private val multiAccountListSupplier: MultiAccountListSupplier,
|
||||
private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase,
|
||||
accountsFeatureToggles: AccountsFeatureToggles,
|
||||
private val wcPairService: WcPairService,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
analytics: AnalyticsEventHandler,
|
||||
|
|
@ -62,7 +70,11 @@ internal class WcConnectionsModel @Inject constructor(
|
|||
init {
|
||||
analytics.send(WcAnalyticEvents.ScreenOpened)
|
||||
listenQrUpdates()
|
||||
listenWcSessions()
|
||||
if (accountsFeatureToggles.isFeatureEnabled) {
|
||||
listenWcSessions()
|
||||
} else {
|
||||
listenWcSessionsOld()
|
||||
}
|
||||
}
|
||||
|
||||
private fun listenQrUpdates() {
|
||||
|
|
@ -86,7 +98,7 @@ internal class WcConnectionsModel @Inject constructor(
|
|||
.launchIn(modelScope)
|
||||
}
|
||||
|
||||
private fun listenWcSessions() {
|
||||
private fun listenWcSessionsOld() {
|
||||
wcSessionsUseCase.invoke()
|
||||
.conflate()
|
||||
.distinctUntilChanged()
|
||||
|
|
@ -101,6 +113,31 @@ internal class WcConnectionsModel @Inject constructor(
|
|||
.launchIn(modelScope)
|
||||
}
|
||||
|
||||
private fun listenWcSessions() {
|
||||
combine(
|
||||
flow = wcSessionsUseCase.invoke().distinctUntilChanged(),
|
||||
flow2 = isAccountsModeEnabledUseCase(),
|
||||
flow3 = multiAccountListSupplier(Unit),
|
||||
transform = { sessionsMap, isAccountMode, accountList ->
|
||||
val transformer: Transformer<WcConnectionsState> =
|
||||
if (isAccountMode) {
|
||||
WcSessionsAccountModeTransformer(
|
||||
sessionsMap = sessionsMap,
|
||||
accountList = accountList,
|
||||
openAppInfoModal = ::openAppInfoModal,
|
||||
)
|
||||
} else {
|
||||
WcSessionsTransformer(
|
||||
sessionsMap = sessionsMap,
|
||||
openAppInfoModal = ::openAppInfoModal,
|
||||
)
|
||||
}
|
||||
uiState.update(transformer)
|
||||
},
|
||||
)
|
||||
.launchIn(modelScope)
|
||||
}
|
||||
|
||||
private fun openAppInfoModal(session: WcSession) {
|
||||
bottomSheetNavigation.activate(WcConnectionsBottomSheetConfig.ConnectedApp(session.sdkModel.topic))
|
||||
}
|
||||
|
|
@ -144,7 +181,7 @@ internal class WcConnectionsModel @Inject constructor(
|
|||
onClick = ::showDisconnectAllDialog,
|
||||
),
|
||||
),
|
||||
connections = persistentListOf(),
|
||||
connections = WcConnections.Loading,
|
||||
onNewConnectionClick = ::openQrScanScreen,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,89 @@
|
|||
package com.tangem.features.walletconnect.connections.model.transformers
|
||||
|
||||
import com.tangem.common.ui.account.AccountTitleUM
|
||||
import com.tangem.common.ui.account.toUM
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.domain.account.models.AccountList
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.isLocked
|
||||
import com.tangem.domain.walletconnect.model.WcSession
|
||||
import com.tangem.features.walletconnect.connections.entity.WcConnectedAppInfo
|
||||
import com.tangem.features.walletconnect.connections.entity.WcConnections
|
||||
import com.tangem.features.walletconnect.connections.entity.WcConnectionsItem
|
||||
import com.tangem.features.walletconnect.connections.entity.WcConnectionsState
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
|
||||
internal class WcSessionsAccountModeTransformer(
|
||||
private val sessionsMap: Map<UserWallet, List<WcSession>>,
|
||||
private val accountList: List<AccountList>,
|
||||
private val openAppInfoModal: (WcSession) -> Unit,
|
||||
) : Transformer<WcConnectionsState> {
|
||||
|
||||
override fun transform(prevState: WcConnectionsState): WcConnectionsState {
|
||||
val items = mutableListOf<WcConnectionsItem>()
|
||||
val lockedWallet = mutableListOf<WcConnectionsItem.WalletHeader>()
|
||||
|
||||
accountList.forEach { accountList ->
|
||||
val userWallet = sessionsMap.keys.find { it.walletId == accountList.userWalletId }
|
||||
?: return@forEach
|
||||
val sessions = sessionsMap[userWallet] ?: return@forEach
|
||||
|
||||
if (userWallet.isLocked) {
|
||||
val lockedItem = WcConnectionsItem.WalletHeader(
|
||||
id = userWallet.walletId.stringValue,
|
||||
walletName = userWallet.name,
|
||||
isLocked = true,
|
||||
)
|
||||
lockedWallet.add(lockedItem)
|
||||
return@forEach
|
||||
}
|
||||
|
||||
val walletHeader = WcConnectionsItem.WalletHeader(
|
||||
id = userWallet.walletId.stringValue,
|
||||
walletName = userWallet.name,
|
||||
isLocked = false,
|
||||
)
|
||||
items.add(walletHeader)
|
||||
|
||||
accountList.accounts.forEach accountsForEach@{ account ->
|
||||
val accountSessions = sessions.filter { it.account?.accountId == account.accountId }
|
||||
if (accountSessions.isEmpty()) return@accountsForEach
|
||||
val connectedApps = accountSessions.map { dappSession ->
|
||||
with(dappSession.sdkModel) {
|
||||
WcConnectedAppInfo(
|
||||
name = appMetaData.name,
|
||||
iconUrl = appMetaData.icons.firstOrNull().orEmpty(),
|
||||
subtitle = WcAppSubtitleConverter.convert(appMetaData),
|
||||
verifiedState = WcDAppVerifiedStateConverter {}
|
||||
.convert(dappSession.securityStatus to appMetaData.name),
|
||||
onClick = { openAppInfoModal(dappSession) },
|
||||
)
|
||||
}
|
||||
}
|
||||
val accountIcon = when (account) {
|
||||
is Account.CryptoPortfolio -> account.icon
|
||||
}
|
||||
|
||||
val accountTitle = AccountTitleUM.Account(
|
||||
prefixText = TextReference.EMPTY,
|
||||
name = account.accountName.toUM().value,
|
||||
icon = accountIcon.toUM(),
|
||||
)
|
||||
|
||||
val accountConnections = WcConnectionsItem.PortfolioConnections(
|
||||
id = userWallet.walletId.stringValue + account.accountId.value,
|
||||
portfolioTitle = accountTitle,
|
||||
connectedApps = connectedApps.toPersistentList(),
|
||||
)
|
||||
items.add(accountConnections)
|
||||
}
|
||||
}
|
||||
return WcConnectionsState(
|
||||
topAppBarConfig = prevState.topAppBarConfig,
|
||||
connections = WcConnections.AccountMode(items.plus(lockedWallet).toPersistentList()),
|
||||
onNewConnectionClick = prevState.onNewConnectionClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
package com.tangem.features.walletconnect.connections.model.transformers
|
||||
|
||||
import com.tangem.domain.walletconnect.model.WcSession
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.walletconnect.model.WcSession
|
||||
import com.tangem.features.walletconnect.connections.entity.WcConnectedAppInfo
|
||||
import com.tangem.features.walletconnect.connections.entity.WcConnections
|
||||
import com.tangem.features.walletconnect.connections.entity.WcConnectionsState
|
||||
import com.tangem.features.walletconnect.connections.entity.WcConnectionsUM
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
|
|
@ -14,25 +15,28 @@ internal class WcSessionsTransformer(
|
|||
) : Transformer<WcConnectionsState> {
|
||||
|
||||
override fun transform(prevState: WcConnectionsState): WcConnectionsState {
|
||||
return prevState.copy(
|
||||
connections = sessionsMap.map { (wallet, sessions) ->
|
||||
WcConnectionsUM(
|
||||
userWalletId = wallet.walletId.stringValue,
|
||||
walletName = wallet.name,
|
||||
connectedApps = sessions.map { session ->
|
||||
with(session.sdkModel) {
|
||||
WcConnectedAppInfo(
|
||||
name = appMetaData.name,
|
||||
iconUrl = appMetaData.icons.firstOrNull().orEmpty(),
|
||||
subtitle = WcAppSubtitleConverter.convert(session.sdkModel.appMetaData),
|
||||
verifiedState = WcDAppVerifiedStateConverter {}
|
||||
.convert(session.securityStatus to appMetaData.name),
|
||||
onClick = { openAppInfoModal(session) },
|
||||
)
|
||||
}
|
||||
}.toPersistentList(),
|
||||
)
|
||||
}.toPersistentList(),
|
||||
val newConnections = sessionsMap.map { (wallet, sessions) ->
|
||||
WcConnectionsUM(
|
||||
userWalletId = wallet.walletId.stringValue,
|
||||
walletName = wallet.name,
|
||||
connectedApps = sessions.map { session ->
|
||||
with(session.sdkModel) {
|
||||
WcConnectedAppInfo(
|
||||
name = appMetaData.name,
|
||||
iconUrl = appMetaData.icons.firstOrNull().orEmpty(),
|
||||
subtitle = WcAppSubtitleConverter.convert(session.sdkModel.appMetaData),
|
||||
verifiedState = WcDAppVerifiedStateConverter {}
|
||||
.convert(session.securityStatus to appMetaData.name),
|
||||
onClick = { openAppInfoModal(session) },
|
||||
)
|
||||
}
|
||||
}.toPersistentList(),
|
||||
)
|
||||
}.toPersistentList()
|
||||
return WcConnectionsState(
|
||||
topAppBarConfig = prevState.topAppBarConfig,
|
||||
connections = WcConnections.WalletMode(newConnections),
|
||||
onNewConnectionClick = prevState.onNewConnectionClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -14,11 +14,13 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.platform.testTag
|
||||
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.common.ui.account.AccountTitle
|
||||
import com.tangem.common.ui.account.AccountTitleUM
|
||||
import com.tangem.core.ui.components.SecondaryButton
|
||||
import com.tangem.core.ui.components.account.AccountIconSize
|
||||
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
|
||||
|
|
@ -35,6 +37,7 @@ import com.tangem.core.ui.utils.toDateFormatWithTodayYesterday
|
|||
import com.tangem.core.ui.utils.toTimeFormat
|
||||
import com.tangem.features.walletconnect.connections.entity.*
|
||||
import com.tangem.features.walletconnect.impl.R
|
||||
import com.tangem.features.walletconnect.transaction.ui.sign.accountPortfolioName
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
|
|
@ -137,44 +140,54 @@ private fun AppInfoFirstBlock(state: WcConnectedAppInfoUM, modifier: Modifier =
|
|||
subtitle = state.appSubtitle,
|
||||
verifiedDAppState = state.verifiedDAppState,
|
||||
)
|
||||
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)
|
||||
.testTag(WalletConnectDetailsBottomSheetTestTags.WALLET_ICON),
|
||||
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)
|
||||
.testTag(WalletConnectDetailsBottomSheetTestTags.WALLET_TITLE),
|
||||
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)
|
||||
.testTag(WalletConnectDetailsBottomSheetTestTags.WALLET_NAME),
|
||||
text = state.walletName,
|
||||
textAlign = TextAlign.End,
|
||||
style = TangemTheme.typography.body1,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
if (state.portfolioName != null) {
|
||||
HorizontalDivider(thickness = 0.5.dp, color = TangemTheme.colors.stroke.primary)
|
||||
PortfolioName(state.portfolioName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PortfolioName(portfolioName: AccountTitleUM, modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(TangemTheme.dimens.spacing12),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.size(24.dp)
|
||||
.testTag(WalletConnectDetailsBottomSheetTestTags.WALLET_ICON),
|
||||
painter = painterResource(R.drawable.ic_wallet_new_24),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.icon.accent,
|
||||
)
|
||||
val leftText: String = when (portfolioName) {
|
||||
is AccountTitleUM.Account -> stringResourceSafe(R.string.common_account)
|
||||
is AccountTitleUM.Text -> stringResourceSafe(R.string.wc_common_wallet)
|
||||
}
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.padding(start = TangemTheme.dimens.spacing4)
|
||||
.weight(1f)
|
||||
.testTag(WalletConnectDetailsBottomSheetTestTags.WALLET_TITLE),
|
||||
text = leftText,
|
||||
style = TangemTheme.typography.body1,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
AccountTitle(
|
||||
modifier = Modifier
|
||||
.padding(start = TangemTheme.dimens.spacing16)
|
||||
.testTag(WalletConnectDetailsBottomSheetTestTags.WALLET_NAME),
|
||||
accountTitleUM = portfolioName,
|
||||
textStyle = TangemTheme.typography.body1,
|
||||
textColor = TangemTheme.colors.text.tertiary,
|
||||
iconSize = AccountIconSize.Small,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun NetworksBlock(networks: ImmutableList<WcNetworkInfoItem.Required>, modifier: Modifier = Modifier) {
|
||||
Column(modifier = modifier) {
|
||||
|
|
@ -194,7 +207,6 @@ private fun NetworksBlock(networks: ImmutableList<WcNetworkInfoItem.Required>, m
|
|||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun WcConnectedAppInfoBS_Preview() {
|
||||
TangemThemePreview {
|
||||
|
|
@ -205,7 +217,48 @@ private fun WcConnectedAppInfoBS_Preview() {
|
|||
verifiedDAppState = VerifiedDAppState.Verified {},
|
||||
isVerified = true,
|
||||
appSubtitle = "react-app.walletconnect.com",
|
||||
walletName = "Tangem 2.0",
|
||||
portfolioName = accountPortfolioName,
|
||||
connectingTime = System.currentTimeMillis(),
|
||||
networks = persistentListOf(
|
||||
WcNetworkInfoItem.Required(
|
||||
id = "1",
|
||||
icon = R.drawable.img_optimism_22,
|
||||
name = "img_optimism_22img_optimism_22",
|
||||
symbol = "optimism",
|
||||
),
|
||||
WcNetworkInfoItem.Required(
|
||||
id = "2",
|
||||
icon = R.drawable.img_bsc_22,
|
||||
name = "img_bsc_22",
|
||||
symbol = "bsc",
|
||||
),
|
||||
WcNetworkInfoItem.Required(
|
||||
id = "3",
|
||||
icon = R.drawable.img_solana_22,
|
||||
name = "img_solana_22",
|
||||
symbol = "solana",
|
||||
),
|
||||
),
|
||||
disconnectButtonConfig = WcPrimaryButtonConfig(showProgress = false, enabled = true, onClick = {}),
|
||||
onDismiss = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun WcConnectedAppInfoBS_OneWallet_Preview() {
|
||||
TangemThemePreview {
|
||||
WcConnectedAppInfoBS(
|
||||
state = WcConnectedAppInfoUM(
|
||||
appName = "React App",
|
||||
appIcon = "",
|
||||
verifiedDAppState = VerifiedDAppState.Verified {},
|
||||
isVerified = true,
|
||||
appSubtitle = "react-app.walletconnect.com",
|
||||
portfolioName = null,
|
||||
connectingTime = System.currentTimeMillis(),
|
||||
networks = persistentListOf(
|
||||
WcNetworkInfoItem.Required(
|
||||
|
|
|
|||
|
|
@ -8,17 +8,22 @@ import androidx.compose.foundation.clickable
|
|||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
|
|
@ -27,12 +32,14 @@ import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameter
|
|||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.util.fastForEach
|
||||
import coil.compose.AsyncImage
|
||||
import com.tangem.common.ui.account.AccountTitle
|
||||
import com.tangem.core.ui.components.BottomFade
|
||||
import com.tangem.core.ui.components.PrimaryButton
|
||||
import com.tangem.core.ui.components.appbar.TopAppBarButton
|
||||
import com.tangem.core.ui.components.dropdownmenu.TangemDropdownItem
|
||||
import com.tangem.core.ui.components.dropdownmenu.TangemDropdownMenu
|
||||
import com.tangem.core.ui.components.snackbar.TangemSnackbarHost
|
||||
import com.tangem.core.ui.extensions.conditional
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
|
|
@ -42,6 +49,8 @@ import com.tangem.features.walletconnect.connections.ui.preview.WcConnectionsPre
|
|||
import com.tangem.features.walletconnect.impl.R
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
private const val LOCKED_WALLET_ALPHA = 0.5f
|
||||
|
||||
@Composable
|
||||
internal fun WcConnectionsContent(
|
||||
state: WcConnectionsState,
|
||||
|
|
@ -88,15 +97,21 @@ internal fun WcConnectionsContent(
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun Content(
|
||||
connections: ImmutableList<WcConnectionsUM>,
|
||||
onNewConnectionClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
if (connections.isEmpty()) {
|
||||
EmptyConnectionsBlock(modifier = modifier, onNewConnectionClick = onNewConnectionClick)
|
||||
} else {
|
||||
ConnectionsBlock(modifier = modifier, connections = connections)
|
||||
private fun Content(connections: WcConnections, onNewConnectionClick: () -> Unit, modifier: Modifier = Modifier) {
|
||||
when {
|
||||
connections is WcConnections.Loading -> return
|
||||
!connections.isNotEmpty() -> EmptyConnectionsBlock(
|
||||
modifier = modifier,
|
||||
onNewConnectionClick = onNewConnectionClick,
|
||||
)
|
||||
connections is WcConnections.AccountMode -> AccountModeContent(
|
||||
modifier = modifier,
|
||||
items = connections.items,
|
||||
)
|
||||
connections is WcConnections.WalletMode -> ConnectionsBlock(
|
||||
modifier = modifier,
|
||||
connections = connections.connections,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -169,6 +184,59 @@ private fun ConnectionsBlock(connections: ImmutableList<WcConnectionsUM>, modifi
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AccountModeContent(items: ImmutableList<WcConnectionsItem>, modifier: Modifier = Modifier) {
|
||||
Box(modifier = modifier) {
|
||||
val bottomBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getBottom(this).toDp() }
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentPadding = PaddingValues(
|
||||
start = TangemTheme.dimens.spacing16,
|
||||
end = TangemTheme.dimens.spacing16,
|
||||
bottom = TangemTheme.dimens.spacing76 + bottomBarHeight,
|
||||
),
|
||||
) {
|
||||
itemsIndexed(
|
||||
items = items,
|
||||
key = { _, item -> item.id },
|
||||
itemContent = { index, item ->
|
||||
val previousItem = items.getOrNull(index.dec())
|
||||
|
||||
val itemModifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.getOffsetModifier(item, previousItem)
|
||||
when (item) {
|
||||
is WcConnectionsItem.WalletHeader -> WalletHeader(item, itemModifier)
|
||||
is WcConnectionsItem.PortfolioConnections -> PortfolioItem(
|
||||
modifier = itemModifier
|
||||
.background(
|
||||
color = TangemTheme.colors.background.primary,
|
||||
shape = RoundedCornerShape(TangemTheme.dimens.radius14),
|
||||
)
|
||||
.clip(RoundedCornerShape(TangemTheme.dimens.radius14)),
|
||||
connection = item,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
BottomFade(modifier = Modifier.align(Alignment.BottomCenter))
|
||||
}
|
||||
}
|
||||
|
||||
private fun Modifier.getOffsetModifier(item: WcConnectionsItem, previousItem: WcConnectionsItem?): Modifier = when {
|
||||
item is WcConnectionsItem.WalletHeader && previousItem == null -> padding(top = 8.dp)
|
||||
item is WcConnectionsItem.WalletHeader && previousItem !is WcConnectionsItem.WalletHeader ->
|
||||
padding(top = 16.dp)
|
||||
item is WcConnectionsItem.WalletHeader && previousItem is WcConnectionsItem.WalletHeader ->
|
||||
padding(top = 8.dp)
|
||||
item is WcConnectionsItem.PortfolioConnections && previousItem is WcConnectionsItem.WalletHeader ->
|
||||
padding(top = 8.dp)
|
||||
item is WcConnectionsItem.PortfolioConnections && previousItem is WcConnectionsItem.PortfolioConnections ->
|
||||
padding(top = 8.dp)
|
||||
else -> this
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ConnectionItem(connection: WcConnectionsUM, modifier: Modifier = Modifier) {
|
||||
Column(modifier = modifier) {
|
||||
|
|
@ -195,6 +263,69 @@ private fun ConnectionItem(connection: WcConnectionsUM, modifier: Modifier = Mod
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun WalletHeader(connection: WcConnectionsItem.WalletHeader, modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.padding(horizontal = 8.dp, vertical = 2.dp)
|
||||
.conditional(connection.isLocked) { alpha(LOCKED_WALLET_ALPHA) },
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.weight(1f),
|
||||
text = connection.walletName,
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
|
||||
if (connection.isLocked) {
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = Modifier
|
||||
.background(
|
||||
color = TangemTheme.colors.button.secondary,
|
||||
shape = CircleShape,
|
||||
)
|
||||
.padding(4.dp),
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.size(16.dp),
|
||||
tint = TangemTheme.colors.icon.informative,
|
||||
imageVector = ImageVector.vectorResource(id = R.drawable.ic_locked_24),
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PortfolioItem(connection: WcConnectionsItem.PortfolioConnections, modifier: Modifier = Modifier) {
|
||||
Column(modifier = modifier) {
|
||||
key(connection.id) {
|
||||
AccountTitle(
|
||||
textStyle = TangemTheme.typography.caption1,
|
||||
textColor = TangemTheme.colors.text.primary1,
|
||||
accountTitleUM = connection.portfolioTitle,
|
||||
modifier = Modifier.padding(top = 12.dp, bottom = 4.dp, start = 12.dp, end = 12.dp),
|
||||
)
|
||||
}
|
||||
connection.connectedApps.fastForEach { appInfo ->
|
||||
key("${connection.id}_${appInfo.name}_${appInfo.iconUrl}") {
|
||||
AppInfoItem(
|
||||
appInfo = appInfo,
|
||||
modifier = Modifier
|
||||
.clickable(onClick = appInfo.onClick)
|
||||
.padding(all = 12.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AppInfoItem(appInfo: WcConnectedAppInfo, modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
|
|
@ -331,7 +462,9 @@ private fun WcConnectionsContentPreview(
|
|||
|
||||
private class WcConnectionsStateProvider : CollectionPreviewParameterProvider<WcConnectionsState>(
|
||||
listOf(
|
||||
WcConnectionsPreviewData.loadingState,
|
||||
WcConnectionsPreviewData.stateWithEmptyConnections,
|
||||
WcConnectionsPreviewData.fullState,
|
||||
WcConnectionsPreviewData.accountState,
|
||||
),
|
||||
)
|
||||
|
|
@ -7,8 +7,10 @@ import com.tangem.core.ui.extensions.themedColor
|
|||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.walletconnect.connections.entity.*
|
||||
import com.tangem.features.walletconnect.impl.R
|
||||
import com.tangem.features.walletconnect.transaction.ui.sign.accountPortfolioName
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import timber.log.Timber
|
||||
import java.util.UUID
|
||||
|
||||
internal object WcConnectionsPreviewData {
|
||||
private const val BASE_URL = "https://raw.githubusercontent.com/TaranVH/LOGOS/refs/heads/master/Crypto"
|
||||
|
|
@ -125,8 +127,62 @@ internal object WcConnectionsPreviewData {
|
|||
onClick = { },
|
||||
),
|
||||
),
|
||||
connections = persistentListOf(),
|
||||
connections = WcConnections.WalletMode(persistentListOf()),
|
||||
onNewConnectionClick = {},
|
||||
)
|
||||
val fullState = stateWithEmptyConnections.copy(connections = connections)
|
||||
|
||||
val connectedDapp
|
||||
get() = WcConnectedAppInfo(
|
||||
name = "React app",
|
||||
iconUrl = "$BASE_URL/LiteCoin.png",
|
||||
subtitle = "https://react-app.walletconnect.com/",
|
||||
verifiedState = VerifiedDAppState.Verified {},
|
||||
onClick = { Timber.d("React app clicked") },
|
||||
)
|
||||
|
||||
val walletHeader
|
||||
get() = WcConnectionsItem.WalletHeader(
|
||||
id = UUID.randomUUID().toString(),
|
||||
walletName = "User Wallet",
|
||||
isLocked = false,
|
||||
)
|
||||
|
||||
val lockedWallet
|
||||
get() = WcConnectionsItem.WalletHeader(
|
||||
id = UUID.randomUUID().toString(),
|
||||
walletName = "Locked Wallet",
|
||||
isLocked = true,
|
||||
)
|
||||
|
||||
val accountStateItems
|
||||
get() = persistentListOf(
|
||||
walletHeader,
|
||||
WcConnectionsItem.PortfolioConnections(
|
||||
id = UUID.randomUUID().toString(),
|
||||
portfolioTitle = accountPortfolioName,
|
||||
connectedApps = persistentListOf(connectedDapp),
|
||||
),
|
||||
walletHeader,
|
||||
WcConnectionsItem.PortfolioConnections(
|
||||
id = UUID.randomUUID().toString(),
|
||||
portfolioTitle = accountPortfolioName,
|
||||
connectedApps = persistentListOf(connectedDapp, connectedDapp),
|
||||
),
|
||||
WcConnectionsItem.PortfolioConnections(
|
||||
id = UUID.randomUUID().toString(),
|
||||
portfolioTitle = accountPortfolioName,
|
||||
connectedApps = persistentListOf(connectedDapp),
|
||||
),
|
||||
WcConnectionsItem.PortfolioConnections(
|
||||
id = UUID.randomUUID().toString(),
|
||||
portfolioTitle = accountPortfolioName,
|
||||
connectedApps = persistentListOf(connectedDapp),
|
||||
),
|
||||
lockedWallet,
|
||||
lockedWallet,
|
||||
)
|
||||
|
||||
val fullState = stateWithEmptyConnections.copy(connections = WcConnections.WalletMode(connections))
|
||||
val accountState = stateWithEmptyConnections.copy(connections = WcConnections.AccountMode(accountStateItems))
|
||||
val loadingState = stateWithEmptyConnections.copy(connections = WcConnections.Loading)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue