diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 90a9ac6c99..02d8bd7799 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -1292,6 +1292,7 @@ Network Networks Wallet + Connected networks View your wallet balance and activity Sign transactions without your notice Request approval for transactions diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/di/WalletConnectDataModule.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/di/WalletConnectDataModule.kt index 77752b86b6..16f2cde7ad 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/di/WalletConnectDataModule.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/di/WalletConnectDataModule.kt @@ -7,11 +7,6 @@ import com.tangem.data.walletconnect.DefaultWalletConnectRepository import com.tangem.data.walletconnect.initialize.DefaultWcInitializeUseCase import com.tangem.data.walletconnect.network.ethereum.WcEthNetwork import com.tangem.data.walletconnect.network.solana.WcSolanaNetwork -import com.tangem.data.walletconnect.pair.AssociateNetworksDelegate -import com.tangem.data.walletconnect.pair.CaipNamespaceDelegate -import com.tangem.data.walletconnect.pair.DefaultWcPairService -import com.tangem.data.walletconnect.pair.DefaultWcPairUseCase -import com.tangem.data.walletconnect.pair.WcPairSdkDelegate import com.tangem.data.walletconnect.pair.* import com.tangem.data.walletconnect.request.DefaultWcRequestService import com.tangem.data.walletconnect.request.DefaultWcRequestUseCaseFactory @@ -31,6 +26,7 @@ import com.tangem.domain.walletconnect.WcRequestUseCaseFactory import com.tangem.domain.walletconnect.model.legacy.WalletConnectSessionsRepository import com.tangem.domain.walletconnect.repository.WalletConnectRepository import com.tangem.domain.walletconnect.repository.WcSessionsManager +import com.tangem.domain.walletconnect.usecase.disconnect.WcDisconnectUseCase import com.tangem.domain.walletconnect.usecase.initialize.WcInitializeUseCase import com.tangem.domain.walletconnect.usecase.pair.WcPairUseCase import com.tangem.domain.walletmanager.WalletManagersFacade @@ -197,6 +193,12 @@ internal object WalletConnectDataModule { return DefaultWcRequestUseCaseFactory(diHelperBox.handlers) } + @Provides + @Singleton + fun providesWcDisconnectUseCase(sessionsManager: WcSessionsManager): WcDisconnectUseCase { + return WcDisconnectUseCase(sessionsManager) + } + internal class DiHelperBox( val converters: Set, val handlers: Set, diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/CaipNamespaceDelegate.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/CaipNamespaceDelegate.kt index 597bdecefb..cdc248d8b7 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/CaipNamespaceDelegate.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/CaipNamespaceDelegate.kt @@ -34,9 +34,14 @@ internal class CaipNamespaceDelegate constructor( requiredNamespaces[namespaceKey]?.methods?.let { addAll(it) } optionalNamespaces[namespaceKey]?.methods?.let { addAll(it) } } + val events = buildSet { + requiredNamespaces[namespaceKey]?.events?.let { addAll(it) } + optionalNamespaces[namespaceKey]?.events?.let { addAll(it) } + } session.chains.add(account.chainId.raw) session.accounts.add(account.raw) session.methods.addAll(methods) + session.events.addAll(events) } return result.mapValues { (namespaceKey, session) -> Wallet.Model.Namespace.Session( diff --git a/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/WcSessionsUseCase.kt b/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/WcSessionsUseCase.kt index 138c4a419f..d15047f8e9 100644 --- a/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/WcSessionsUseCase.kt +++ b/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/WcSessionsUseCase.kt @@ -14,4 +14,8 @@ class WcSessionsUseCase(private val sessionsManager: WcSessionsManager) { suspend fun invokeSync(): Map> { return sessionsManager.sessions.first() } + + suspend fun findByTopic(topic: String): WcSession? { + return sessionsManager.findSessionByTopic(topic) + } } \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/components/AlertsComponent.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/components/AlertsComponent.kt index a03ae3a26d..5ba2668002 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/components/AlertsComponent.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/components/AlertsComponent.kt @@ -7,6 +7,7 @@ import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent import com.tangem.core.ui.decompose.ComposableBottomSheetComponent import com.tangem.features.walletconnect.connections.ui.AlertsModalBottomSheet +import kotlinx.serialization.Serializable internal class AlertsComponent( appComponentContext: AppComponentContext, @@ -31,17 +32,35 @@ internal class AlertsComponent( internal data class Params(val alertType: AlertType) @Immutable + @Serializable internal sealed class AlertType : TangemBottomSheetConfigContent { abstract val onDismiss: () -> Unit + @Serializable data class VerifiedDomain(val appName: String, override val onDismiss: () -> Unit) : AlertType() + + @Serializable data class UnknownDomain(val onConnect: () -> Unit, override val onDismiss: () -> Unit) : AlertType() + + @Serializable data class UnsafeDomain(val onConnect: () -> Unit, override val onDismiss: () -> Unit) : AlertType() + + @Serializable data class UnsupportedNetworks(val appName: String, override val onDismiss: () -> Unit) : AlertType() + + @Serializable data class WcDisconnected(override val onDismiss: () -> Unit) : AlertType() + + @Serializable data class UnknownError(val errorCode: Int, override val onDismiss: () -> Unit) : AlertType() + + @Serializable data class WrongCardSelected(override val onDismiss: () -> Unit) : AlertType() + + @Serializable data class ConnectionTimeout(val onTryAgain: () -> Unit, override val onDismiss: () -> Unit) : AlertType() + + @Serializable data class MaliciousTransaction( val descriptionMessage: String, val onConnect: () -> Unit, diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/components/ConnectionsComponent.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/components/ConnectionsComponent.kt index 0c311f6f4a..0fd5c6d2d5 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/components/ConnectionsComponent.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/components/ConnectionsComponent.kt @@ -15,7 +15,7 @@ import com.tangem.core.ui.decompose.ComposableBottomSheetComponent import com.tangem.core.ui.decompose.ComposableContentComponent import com.tangem.domain.walletconnect.model.WcPairRequest import com.tangem.features.walletconnect.connections.model.WcConnectionsModel -import com.tangem.features.walletconnect.connections.routes.WcConnectionsBottomSheetRoutes +import com.tangem.features.walletconnect.connections.routes.WcConnectionsBottomSheetConfig import com.tangem.features.walletconnect.connections.ui.WcConnectionsContent internal class ConnectionsComponent( @@ -40,11 +40,11 @@ internal class ConnectionsComponent( } private fun bottomSheetChild( - config: WcConnectionsBottomSheetRoutes, + config: WcConnectionsBottomSheetConfig, componentContext: ComponentContext, ): ComposableBottomSheetComponent { return when (config) { - is WcConnectionsBottomSheetRoutes.AppInfo -> WcAppInfoContainerComponent( + is WcConnectionsBottomSheetConfig.AppInfo -> WcAppInfoContainerComponent( appComponentContext = childByContext(componentContext), params = WcAppInfoContainerComponent.Params( wcUrl = config.wcUrl, @@ -52,6 +52,13 @@ internal class ConnectionsComponent( onDismiss = model.bottomSheetNavigation::dismiss, ), ) + is WcConnectionsBottomSheetConfig.ConnectedApp -> WcConnectedAppInfoComponent( + appComponentContext = childByContext(componentContext), + params = WcConnectedAppInfoComponent.Params( + topic = config.topic, + onDismiss = model.bottomSheetNavigation::dismiss, + ), + ) } } } \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/components/WcConnectedAppInfoComponent.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/components/WcConnectedAppInfoComponent.kt new file mode 100644 index 0000000000..cfdaa99d34 --- /dev/null +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/components/WcConnectedAppInfoComponent.kt @@ -0,0 +1,30 @@ +package com.tangem.features.walletconnect.connections.components + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.model.getOrCreateModel +import com.tangem.core.ui.decompose.ComposableBottomSheetComponent +import com.tangem.features.walletconnect.connections.model.WcConnectedAppInfoModel +import com.tangem.features.walletconnect.connections.ui.WcConnectedAppInfoBS + +internal class WcConnectedAppInfoComponent( + params: Params, + appComponentContext: AppComponentContext, +) : AppComponentContext by appComponentContext, ComposableBottomSheetComponent { + + private val model: WcConnectedAppInfoModel = getOrCreateModel(params = params) + + override fun dismiss() { + model.dismiss() + } + + @Composable + override fun BottomSheet() { + val state by model.uiState.collectAsStateWithLifecycle() + state?.let { WcConnectedAppInfoBS(it) } + } + + data class Params(val topic: String, val onDismiss: () -> Unit) +} \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/entity/WcAppInfoUM.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/entity/WcAppInfoUM.kt index 413724be60..f7d17a2af5 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/entity/WcAppInfoUM.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/entity/WcAppInfoUM.kt @@ -50,5 +50,5 @@ sealed class WcAppInfoSecurityNotification( @Immutable internal sealed class WcNetworksInfo { data class MissingRequiredNetworkInfo(val networks: String) : WcNetworksInfo() - data class ContainsAllRequiredNetworks(val icons: ImmutableList) : WcNetworksInfo() + data class ContainsAllRequiredNetworks(val items: ImmutableList) : WcNetworksInfo() } \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/entity/WcConnectedAppInfoUM.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/entity/WcConnectedAppInfoUM.kt new file mode 100644 index 0000000000..5fe46765ca --- /dev/null +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/entity/WcConnectedAppInfoUM.kt @@ -0,0 +1,15 @@ +package com.tangem.features.walletconnect.connections.entity + +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent +import kotlinx.collections.immutable.ImmutableList + +data class WcConnectedAppInfoUM( + val appName: String, + val appIcon: String, + val isVerified: Boolean, + val appSubtitle: String, + val walletName: String, + val networks: ImmutableList, + val disconnectButtonConfig: WcPrimaryButtonConfig, + val onDismiss: () -> Unit, +) : TangemBottomSheetConfigContent \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/entity/WcNetworkInfoItem.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/entity/WcNetworkInfoItem.kt new file mode 100644 index 0000000000..498b1b759c --- /dev/null +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/entity/WcNetworkInfoItem.kt @@ -0,0 +1,10 @@ +package com.tangem.features.walletconnect.connections.entity + +import androidx.annotation.DrawableRes + +data class WcNetworkInfoItem( + val id: String, + @DrawableRes val icon: Int, + val name: String, + val symbol: String, +) \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/WcAppInfoModel.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/WcAppInfoModel.kt index d2c9722fbc..1d36aed1f4 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/WcAppInfoModel.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/WcAppInfoModel.kt @@ -53,7 +53,7 @@ internal class WcAppInfoModel @Inject constructor( appInfoUiState.transformerUpdate( WcConnectButtonProgressTransformer(showProgress = false), ) - // TODO: wc dismiss and reload sessions + params.onDismiss() } is WcPairState.Error -> { // TODO: wc show toast/snackbar/alert? @@ -78,6 +78,7 @@ internal class WcAppInfoModel @Inject constructor( } fun dismiss() { + wcPairUseCase.reject() params.onDismiss() } diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/WcConnectedAppInfoModel.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/WcConnectedAppInfoModel.kt new file mode 100644 index 0000000000..6126b5f404 --- /dev/null +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/WcConnectedAppInfoModel.kt @@ -0,0 +1,88 @@ +package com.tangem.features.walletconnect.connections.model + +import androidx.compose.runtime.Stable +import com.domain.blockaid.models.dapp.CheckDAppResult +import com.tangem.core.decompose.di.ModelScoped +import com.tangem.core.decompose.model.Model +import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.core.decompose.ui.UiMessageSender +import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.message.SnackbarMessage +import com.tangem.domain.walletconnect.model.WcSession +import com.tangem.domain.walletconnect.usecase.WcSessionsUseCase +import com.tangem.domain.walletconnect.usecase.disconnect.WcDisconnectUseCase +import com.tangem.features.walletconnect.connections.components.WcConnectedAppInfoComponent +import com.tangem.features.walletconnect.connections.entity.WcConnectedAppInfoUM +import com.tangem.features.walletconnect.connections.entity.WcPrimaryButtonConfig +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.collections.immutable.persistentListOf +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import timber.log.Timber +import javax.inject.Inject + +@Stable +@ModelScoped +internal class WcConnectedAppInfoModel @Inject constructor( + private val wcSessionsUseCase: WcSessionsUseCase, + private val messageSender: UiMessageSender, + private val disconnectUseCase: WcDisconnectUseCase, + override val dispatchers: CoroutineDispatcherProvider, + paramsContainer: ParamsContainer, +) : Model() { + + private val params = paramsContainer.require() + val uiState: StateFlow + field = MutableStateFlow(null) + + init { + loadDAppInfo(params.topic) + } + + private fun loadDAppInfo(topic: String) { + modelScope.launch { + val session = wcSessionsUseCase.findByTopic(topic = topic) + if (session == null) { + Timber.e("Can not find WcSession by topic: $topic") + messageSender.send(SnackbarMessage(stringReference("Can not find WcSession by topic: $topic"))) + dismiss() + } else { + uiState.update { state -> + WcConnectedAppInfoUM( + appName = session.sdkModel.appMetaData.name, + appIcon = session.sdkModel.appMetaData.icons.firstOrNull().orEmpty(), + isVerified = session.securityStatus == CheckDAppResult.SAFE, + appSubtitle = session.sdkModel.appMetaData.description, + walletName = session.wallet.name, + networks = persistentListOf(), // TODO(wc): Nikolai & Doston: Where to find networks??? + disconnectButtonConfig = WcPrimaryButtonConfig( + showProgress = false, + enabled = true, + onClick = { disconnect(session) }, + ), + onDismiss = ::dismiss, + ) + } + } + } + } + + private fun disconnect(session: WcSession) { + uiState.update { state -> + state?.copy( + disconnectButtonConfig = state.disconnectButtonConfig.copy(showProgress = true), + ) + } + modelScope.launch { + disconnectUseCase.disconnect(session) + messageSender.send(SnackbarMessage(message = stringReference("dApp disconnected"))) + dismiss() + } + } + + fun dismiss() { + params.onDismiss() + } +} \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/WcConnectionsModel.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/WcConnectionsModel.kt index 69216f06c3..11a6d2a74c 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/WcConnectionsModel.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/WcConnectionsModel.kt @@ -16,16 +16,19 @@ import com.tangem.core.ui.message.EventMessageAction import com.tangem.core.ui.res.TangemTheme import com.tangem.domain.qrscanning.models.SourceType import com.tangem.domain.qrscanning.usecases.ListenToQrScanningUseCase +import com.tangem.domain.walletconnect.model.WcSession import com.tangem.domain.walletconnect.usecase.WcSessionsUseCase +import com.tangem.domain.walletconnect.usecase.disconnect.WcDisconnectUseCase import com.tangem.features.walletconnect.connections.entity.WcConnectionsState import com.tangem.features.walletconnect.connections.entity.WcConnectionsTopAppBarConfig import com.tangem.features.walletconnect.connections.model.transformers.WcSessionsTransformer -import com.tangem.features.walletconnect.connections.routes.WcConnectionsBottomSheetRoutes +import com.tangem.features.walletconnect.connections.routes.WcConnectionsBottomSheetConfig import com.tangem.features.walletconnect.impl.R import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.transformer.update import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.flow.* +import kotlinx.coroutines.launch import timber.log.Timber import javax.inject.Inject @@ -35,12 +38,13 @@ internal class WcConnectionsModel @Inject constructor( private val uiMessageSender: UiMessageSender, private val listenToQrScanningUseCase: ListenToQrScanningUseCase, private val wcSessionsUseCase: WcSessionsUseCase, + private val wcDisconnectUseCase: WcDisconnectUseCase, override val dispatchers: CoroutineDispatcherProvider, ) : Model() { val uiState: StateFlow field = MutableStateFlow(getInitialState()) - val bottomSheetNavigation: SlotNavigation = SlotNavigation() + val bottomSheetNavigation: SlotNavigation = SlotNavigation() init { listenQrUpdates() @@ -50,39 +54,43 @@ internal class WcConnectionsModel @Inject constructor( private fun listenQrUpdates() { listenToQrScanningUseCase(SourceType.WALLET_CONNECT) .getOrElse { emptyFlow() } - .onEach { wcUrl -> bottomSheetNavigation.activate(WcConnectionsBottomSheetRoutes.AppInfo(wcUrl)) } + .onEach { wcUrl -> bottomSheetNavigation.activate(WcConnectionsBottomSheetConfig.AppInfo(wcUrl)) } .launchIn(modelScope) } private fun listenWcSessions() { wcSessionsUseCase.invoke() .conflate() + .onEach { Timber.tag("ddk9499").d("listenWcSessions before: ${it.size}") } .distinctUntilChanged() .onEach { sessionsMap -> + Timber.tag("ddk9499").d("listenWcSessions after: ${sessionsMap.size}") uiState.update( WcSessionsTransformer( sessionsMap = sessionsMap, - openAppInfoModal = { Timber.d("Open app info for session: $it") }, + openAppInfoModal = ::openAppInfoModal, ), ) } .launchIn(modelScope) } + private fun openAppInfoModal(session: WcSession) { + bottomSheetNavigation.activate(WcConnectionsBottomSheetConfig.ConnectedApp(session.sdkModel.topic)) + } + private fun openQrScanScreen() { router.push(AppRoute.QrScanning(source = AppRoute.QrScanning.Source.WalletConnect)) } - private fun disconnectAll() { + private fun showDisconnectAllDialog() { val message = DialogMessage( title = resourceReference(R.string.wc_disconnect_all_alert_title), message = resourceReference(R.string.wc_disconnect_all_alert_desc), firstActionBuilder = { EventMessageAction( title = resourceReference(R.string.common_disconnect), - onClick = { - // TODO(wc): disconnect all - }, + onClick = ::disconnectAllSessions, ) }, secondActionBuilder = { cancelAction() }, @@ -90,6 +98,10 @@ internal class WcConnectionsModel @Inject constructor( uiMessageSender.send(message) } + private fun disconnectAllSessions() { + modelScope.launch { wcDisconnectUseCase.disconnectAll() } + } + private fun getInitialState(): WcConnectionsState { return WcConnectionsState( topAppBarConfig = WcConnectionsTopAppBarConfig( @@ -101,7 +113,7 @@ internal class WcConnectionsModel @Inject constructor( disconnectAllItem = TangemDropdownMenuItem( title = resourceReference(R.string.wc_disconnect_all), textColorProvider = { TangemTheme.colors.text.warning }, - onClick = ::disconnectAll, + onClick = ::showDisconnectAllDialog, ), ), connections = persistentListOf(), diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/transformers/WcAppInfoTransformer.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/transformers/WcAppInfoTransformer.kt index d64ddbd43b..134338c410 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/transformers/WcAppInfoTransformer.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/transformers/WcAppInfoTransformer.kt @@ -4,10 +4,7 @@ import com.domain.blockaid.models.dapp.CheckDAppResult import com.tangem.core.ui.extensions.iconResId import com.tangem.domain.walletconnect.model.WcSessionProposal import com.tangem.domain.wallets.models.UserWallet -import com.tangem.features.walletconnect.connections.entity.WcAppInfoSecurityNotification -import com.tangem.features.walletconnect.connections.entity.WcAppInfoUM -import com.tangem.features.walletconnect.connections.entity.WcNetworksInfo -import com.tangem.features.walletconnect.connections.entity.WcPrimaryButtonConfig +import com.tangem.features.walletconnect.connections.entity.* import com.tangem.utils.transformer.Transformer import kotlinx.collections.immutable.toImmutableList @@ -53,8 +50,15 @@ internal class WcAppInfoTransformer( ) } else { WcNetworksInfo.ContainsAllRequiredNetworks( - icons = (proposalNetwork.required + proposalNetwork.available) - .map { it.iconResId } + items = (proposalNetwork.required + proposalNetwork.available) + .map { + WcNetworkInfoItem( + id = it.id.value, + icon = it.iconResId, + name = it.name, + symbol = it.currencySymbol, + ) + } .toImmutableList(), ) } diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routes/WcAppInfoRoutes.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routes/WcAppInfoRoutes.kt index 8c0d1fcc2c..74a5c349f2 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routes/WcAppInfoRoutes.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routes/WcAppInfoRoutes.kt @@ -8,8 +8,15 @@ import kotlinx.serialization.Serializable @Serializable @Immutable internal sealed class WcAppInfoRoutes : TangemBottomSheetConfigContent { + @Serializable data object AppInfo : WcAppInfoRoutes() + + @Serializable data object SelectWallet : WcAppInfoRoutes() + + @Serializable data object SelectNetworks : WcAppInfoRoutes() + + @Serializable data class Alert(val alertType: AlertsComponent.AlertType) : WcAppInfoRoutes() } \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routes/WcConnectionsBottomSheetRoutes.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routes/WcConnectionsBottomSheetConfig.kt similarity index 54% rename from features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routes/WcConnectionsBottomSheetRoutes.kt rename to features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routes/WcConnectionsBottomSheetConfig.kt index 66f2dc4f1e..b83e610fd8 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routes/WcConnectionsBottomSheetRoutes.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routes/WcConnectionsBottomSheetConfig.kt @@ -3,7 +3,10 @@ package com.tangem.features.walletconnect.connections.routes import kotlinx.serialization.Serializable @Serializable -internal sealed class WcConnectionsBottomSheetRoutes { +internal sealed class WcConnectionsBottomSheetConfig { @Serializable - data class AppInfo(val wcUrl: String) : WcConnectionsBottomSheetRoutes() + data class AppInfo(val wcUrl: String) : WcConnectionsBottomSheetConfig() + + @Serializable + data class ConnectedApp(val topic: String) : WcConnectionsBottomSheetConfig() } \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/ui/InternalComponents.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/ui/InternalComponents.kt index 2f583ee287..960e4b2e74 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/ui/InternalComponents.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/ui/InternalComponents.kt @@ -1,5 +1,6 @@ package com.tangem.features.walletconnect.connections.ui +import androidx.annotation.DrawableRes import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Icon @@ -10,6 +11,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import coil.compose.AsyncImage import com.tangem.core.ui.res.TangemTheme @@ -64,4 +66,33 @@ internal fun WcAppInfoItem( ) } } +} + +@Composable +internal fun WcNetworkInfoItem(@DrawableRes icon: Int, name: String, symbol: String, modifier: Modifier = Modifier) { + Row( + modifier = modifier.padding(vertical = 14.dp, horizontal = 12.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + modifier = Modifier.size(24.dp), + painter = painterResource(icon), + contentDescription = null, + tint = Color.Unspecified, + ) + Text( + modifier = Modifier.padding(start = 12.dp).weight(1f, fill = false), + text = name, + style = TangemTheme.typography.body1, + color = TangemTheme.colors.text.primary1, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + Text( + modifier = Modifier.padding(start = 4.dp), + text = symbol, + style = TangemTheme.typography.body1, + color = TangemTheme.colors.text.tertiary, + ) + } } \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/ui/WcAppInfoContent.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/ui/WcAppInfoContent.kt index eef9435ca0..f248d6323c 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/ui/WcAppInfoContent.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/ui/WcAppInfoContent.kt @@ -37,10 +37,7 @@ import com.tangem.core.ui.extensions.wrappedList import com.tangem.core.ui.res.TangemColorPalette import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview -import com.tangem.features.walletconnect.connections.entity.WcAppInfoSecurityNotification -import com.tangem.features.walletconnect.connections.entity.WcAppInfoUM -import com.tangem.features.walletconnect.connections.entity.WcNetworksInfo -import com.tangem.features.walletconnect.connections.entity.WcPrimaryButtonConfig +import com.tangem.features.walletconnect.connections.entity.* import com.tangem.features.walletconnect.impl.R import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf @@ -308,7 +305,7 @@ private fun SelectNetworksBlock(networksInfo: WcNetworksInfo, modifier: Modifier color = TangemTheme.colors.text.primary1, ) when (networksInfo) { - is WcNetworksInfo.ContainsAllRequiredNetworks -> NetworkIcons(icons = networksInfo.icons) + is WcNetworksInfo.ContainsAllRequiredNetworks -> NetworkIcons(items = networksInfo.items) is WcNetworksInfo.MissingRequiredNetworkInfo -> Unit } Icon( @@ -327,17 +324,17 @@ private fun SelectNetworksBlock(networksInfo: WcNetworksInfo, modifier: Modifier @Suppress("MagicNumber") @Composable -private fun NetworkIcons(icons: ImmutableList, modifier: Modifier = Modifier) { - val (iconsToShow, remainingCount) = remember(icons) { +private fun NetworkIcons(items: ImmutableList, modifier: Modifier = Modifier) { + val (iconsToShow, remainingCount) = remember(items) { when { - icons.size <= 4 -> icons to 0 - else -> icons.take(3) to icons.size - 3 + items.size <= 4 -> items to 0 + else -> items.take(3) to items.size - 3 } } Box(modifier = modifier.wrapContentWidth()) { - iconsToShow.forEachIndexed { index, iconRes -> + iconsToShow.forEachIndexed { index, item -> Image( - painter = painterResource(id = iconRes), + painter = painterResource(id = item.icon), contentDescription = null, contentScale = ContentScale.Crop, modifier = Modifier @@ -569,12 +566,32 @@ private class WcAppInfoStateProvider : CollectionPreviewParameterProvider( + config = TangemBottomSheetConfig( + isShown = true, + onDismissRequest = state.onDismiss, + content = state, + ), + containerColor = TangemTheme.colors.background.tertiary, + title = { + TangemModalBottomSheetTitle( + title = resourceReference(R.string.wc_wallet_connect), + endIconRes = R.drawable.ic_close_24, + onEndClick = state.onDismiss, + ) + }, + content = { + WcConnectedAppInfoBSContent( + state, + modifier = Modifier + .padding(horizontal = 16.dp) + .fillMaxSize(), + ) + }, + ) +} + +@Composable +private fun WcConnectedAppInfoBSContent(state: WcConnectedAppInfoUM, modifier: Modifier = Modifier) { + Column(modifier = modifier) { + val blocksModifier = Modifier + .clip(RoundedCornerShape(TangemTheme.dimens.radius14)) + .background(color = TangemTheme.colors.background.action) + .fillMaxWidth() + AppInfoFirstBlock( + state = state, + modifier = Modifier + .padding(top = 8.dp) + .then(blocksModifier), + ) + NetworksBlock( + networks = state.networks, + modifier = Modifier + .padding(top = 14.dp) + .then(blocksModifier), + ) + SecondaryButton( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 16.dp), + text = stringResourceSafe(R.string.common_disconnect), + enabled = state.disconnectButtonConfig.enabled, + showProgress = state.disconnectButtonConfig.showProgress, + onClick = state.disconnectButtonConfig.onClick, + ) + } +} + +@Composable +private fun AppInfoFirstBlock(state: WcConnectedAppInfoUM, modifier: Modifier = Modifier) { + Column(modifier = modifier) { + WcAppInfoItem( + iconUrl = state.appIcon, + title = state.appName, + subtitle = state.appSubtitle, + isVerified = state.isVerified, + ) + HorizontalDivider(thickness = 1.dp, color = TangemTheme.colors.stroke.primary) + Row( + modifier = Modifier + .fillMaxWidth() + .padding(TangemTheme.dimens.spacing12), + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + modifier = Modifier.size(24.dp), + painter = painterResource(R.drawable.ic_wallet_new_24), + contentDescription = null, + tint = TangemTheme.colors.icon.accent, + ) + Text( + modifier = Modifier + .padding(start = TangemTheme.dimens.spacing4) + .weight(1f), + text = stringResourceSafe(R.string.manage_tokens_network_selector_wallet), + style = TangemTheme.typography.body1, + color = TangemTheme.colors.text.primary1, + ) + Text( + modifier = Modifier + .padding(start = TangemTheme.dimens.spacing16) + .weight(1f), + text = state.walletName, + textAlign = TextAlign.End, + style = TangemTheme.typography.body1, + color = TangemTheme.colors.text.tertiary, + ) + } + } +} + +@Composable +private fun NetworksBlock(networks: ImmutableList, modifier: Modifier = Modifier) { + Column(modifier = modifier) { + Text( + modifier = Modifier + .padding(top = 12.dp, bottom = 4.dp) + .padding(horizontal = 12.dp), + text = stringResourceSafe(R.string.wc_connected_networks), + style = TangemTheme.typography.subtitle2, + color = TangemTheme.colors.text.tertiary, + ) + networks.fastForEach { network -> + key(network.id) { WcNetworkInfoItem(icon = network.icon, name = network.name, symbol = network.symbol) } + } + } +} + +@Preview(showBackground = true) +@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun WcConnectedAppInfoBS_Preview() { + TangemThemePreview { + WcConnectedAppInfoBS( + state = WcConnectedAppInfoUM( + appName = "React App", + appIcon = "", + isVerified = true, + appSubtitle = "react-app.walletconnect.com", + walletName = "Tangem 2.0", + networks = persistentListOf( + WcNetworkInfoItem( + id = "1", + icon = R.drawable.img_optimism_22, + name = "img_optimism_22img_optimism_22", + symbol = "optimism", + ), + WcNetworkInfoItem(id = "2", icon = R.drawable.img_bsc_22, name = "img_bsc_22", symbol = "bsc"), + WcNetworkInfoItem( + id = "3", + icon = R.drawable.img_solana_22, + name = "img_solana_22", + symbol = "solana", + ), + ), + disconnectButtonConfig = WcPrimaryButtonConfig(showProgress = false, enabled = true, onClick = {}), + onDismiss = {}, + ), + ) + } +} \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/di/WalletConnectModelModule.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/di/WalletConnectModelModule.kt index bb589438d6..00039ca420 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/di/WalletConnectModelModule.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/di/WalletConnectModelModule.kt @@ -3,6 +3,7 @@ package com.tangem.features.walletconnect.di import com.tangem.core.decompose.di.ModelComponent import com.tangem.core.decompose.model.Model import com.tangem.features.walletconnect.connections.model.WcAppInfoModel +import com.tangem.features.walletconnect.connections.model.WcConnectedAppInfoModel import com.tangem.features.walletconnect.connections.model.WcConnectionsModel import dagger.Binds import dagger.Module @@ -23,4 +24,9 @@ internal interface WalletConnectModelModule { @IntoMap @ClassKey(WcAppInfoModel::class) fun bindWcAppInfoModel(model: WcAppInfoModel): Model + + @Binds + @IntoMap + @ClassKey(WcConnectedAppInfoModel::class) + fun bindWcConnectedAppInfoModel(model: WcConnectedAppInfoModel): Model } \ No newline at end of file