Updated on 2026-08-14

This commit is contained in:
Tangem 2025-05-07 21:29:03 +04:00
commit 63133bb8fb
117 changed files with 1915 additions and 1265 deletions

View file

@ -13,15 +13,17 @@ import com.tangem.core.decompose.context.childByContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.features.walletconnect.connections.model.WcConnectionsModel
import com.tangem.features.walletconnect.connections.routes.WcConnectionsBottomSheetConfig
import com.tangem.features.walletconnect.connections.ui.WcConnectionsContent
internal class ConnectionsComponent(
appComponentContext: AppComponentContext,
params: Params,
) : AppComponentContext by appComponentContext, ComposableContentComponent {
private val model: WcConnectionsModel = getOrCreateModel()
private val model: WcConnectionsModel = getOrCreateModel(params)
private val bottomSheetSlot = childSlot(
source = model.bottomSheetNavigation,
serializer = null,
@ -52,4 +54,6 @@ internal class ConnectionsComponent(
)
}
}
data class Params(val userWalletId: UserWalletId)
}

View file

@ -22,7 +22,7 @@ import dagger.assisted.AssistedInject
@Suppress("UnusedPrivateMember")
internal class DefaultWalletConnectEntryComponent @AssistedInject constructor(
@Assisted private val appComponentContext: AppComponentContext,
@Assisted params: Unit,
@Assisted private val params: WalletConnectEntryComponent.Params,
) : AppComponentContext by appComponentContext, WalletConnectEntryComponent {
private val contentNavigation = StackNavigation<ConnectionsInnerRoute>()
@ -47,12 +47,18 @@ internal class DefaultWalletConnectEntryComponent @AssistedInject constructor(
config: ConnectionsInnerRoute,
componentContext: ComponentContext,
): ComposableContentComponent = when (config) {
ConnectionsInnerRoute.Connections -> ConnectionsComponent(childByContext(componentContext))
ConnectionsInnerRoute.Connections -> ConnectionsComponent(
appComponentContext = childByContext(componentContext),
params = ConnectionsComponent.Params(params.userWalletId),
)
ConnectionsInnerRoute.QrScan -> TODO("[REDACTED_JIRA]")
}
@AssistedFactory
interface Factory : WalletConnectEntryComponent.Factory {
override fun create(context: AppComponentContext, params: Unit): DefaultWalletConnectEntryComponent
override fun create(
context: AppComponentContext,
params: WalletConnectEntryComponent.Params,
): DefaultWalletConnectEntryComponent
}
}

View file

@ -1,13 +1,12 @@
package com.tangem.features.walletconnect.connections.components
import androidx.compose.animation.animateContentSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import com.arkivanov.decompose.ComponentContext
import com.arkivanov.decompose.extensions.compose.stack.Children
import com.arkivanov.decompose.extensions.compose.stack.animation.stackAnimation
import com.arkivanov.decompose.extensions.compose.subscribeAsState
import com.arkivanov.decompose.router.stack.StackNavigation
import com.arkivanov.decompose.router.stack.childStack
import com.arkivanov.decompose.router.stack.pop
import com.tangem.core.decompose.context.AppComponentContext
@ -18,8 +17,10 @@ import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheet
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetTitle
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.domain.walletconnect.model.WcPairRequest
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.features.walletconnect.connections.model.WcAppInfoModel
import com.tangem.features.walletconnect.connections.routes.WcAppInfoRoutes
import com.tangem.features.walletconnect.impl.R
@ -30,9 +31,8 @@ internal class WcAppInfoContainerComponent(
) : AppComponentContext by appComponentContext, ComposableContentComponent {
private val model: WcAppInfoModel = getOrCreateModel(params = params)
private val contentNavigation = StackNavigation<WcAppInfoRoutes>()
private val contentStack = childStack(
source = contentNavigation,
source = model.contentNavigation,
serializer = WcAppInfoRoutes.serializer(),
initialConfiguration = WcAppInfoRoutes.AppInfo,
childFactory = ::screenChild,
@ -51,10 +51,17 @@ internal class WcAppInfoContainerComponent(
onDismissRequest = ::dismiss,
content = content.active.configuration,
),
containerColor = TangemTheme.colors.background.tertiary,
onBack = ::contentBack,
containerColor = when (content.active.configuration) {
is WcAppInfoRoutes.Alert,
is WcAppInfoRoutes.SelectNetworks,
is WcAppInfoRoutes.AppInfo,
-> TangemTheme.colors.background.tertiary
is WcAppInfoRoutes.SelectWallet -> TangemTheme.colors.background.primary
},
title = { config -> Title(route = config) },
content = {
Children(stack = content, animation = stackAnimation()) { child ->
Children(modifier = Modifier.animateContentSize(), stack = content) { child ->
child.instance.Content(modifier = Modifier)
}
},
@ -64,7 +71,12 @@ internal class WcAppInfoContainerComponent(
@Composable
private fun Title(route: WcAppInfoRoutes) {
TangemModalBottomSheetTitle(
title = resourceReference(R.string.wc_wallet_connect),
title = when (route) {
is WcAppInfoRoutes.Alert -> null
WcAppInfoRoutes.AppInfo -> resourceReference(R.string.wc_wallet_connect)
WcAppInfoRoutes.SelectNetworks -> TODO("[REDACTED_TASK_KEY]")
WcAppInfoRoutes.SelectWallet -> stringReference("Choose wallet")
},
startIconRes = when (route) {
is WcAppInfoRoutes.AppInfo, is WcAppInfoRoutes.Alert -> null
is WcAppInfoRoutes.SelectNetworks, is WcAppInfoRoutes.SelectWallet -> R.drawable.ic_back_24
@ -88,19 +100,25 @@ internal class WcAppInfoContainerComponent(
}
private fun contentBack() {
contentNavigation.pop()
if (contentStack.value.active.configuration == WcAppInfoRoutes.AppInfo) {
dismiss()
} else {
model.contentNavigation.pop()
}
}
private fun screenChild(config: WcAppInfoRoutes, componentContext: ComponentContext): ComposableContentComponent =
when (config) {
is WcAppInfoRoutes.AppInfo -> WcAppInfoComponent(
appComponentContext = childByContext(componentContext),
model = model,
)
private fun screenChild(config: WcAppInfoRoutes, componentContext: ComponentContext): ComposableContentComponent {
val appComponentContext = childByContext(componentContext)
return when (config) {
is WcAppInfoRoutes.AppInfo -> WcAppInfoComponent(appComponentContext = appComponentContext, model = model)
is WcAppInfoRoutes.Alert -> TODO()
WcAppInfoRoutes.SelectNetworks -> TODO()
WcAppInfoRoutes.SelectWallet -> TODO()
WcAppInfoRoutes.SelectWallet -> WcSelectWalletComponent(
appComponentContext = appComponentContext,
model = model,
)
}
}
data class Params(val wcUrl: String, val source: WcPairRequest.Source)
data class Params(val userWalletId: UserWalletId, val wcUrl: String, val source: WcPairRequest.Source)
}

View file

@ -0,0 +1,130 @@
package com.tangem.features.walletconnect.connections.components
import android.content.res.Configuration
import androidx.compose.foundation.background
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.ui.components.block.TangemBlockCardColors
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.features.walletconnect.connections.model.WcAppInfoModel
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
internal class WcSelectWalletComponent(
appComponentContext: AppComponentContext,
private val model: WcAppInfoModel,
) : AppComponentContext by appComponentContext, ComposableContentComponent {
@Composable
override fun Content(modifier: Modifier) {
val walletsState by model.walletsUiState.collectAsStateWithLifecycle()
WcSelectWalletContent(
modifier = modifier.padding(start = 16.dp, end = 16.dp, bottom = 16.dp),
wallets = walletsState.wallets,
selectedWalletId = walletsState.selectedUserWallet.walletId,
)
}
}
@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) {
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,
),
)
}
}
}
}
@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() {
TangemThemePreview {
WcSelectWalletContent(
modifier = Modifier
.background(TangemTheme.colors.background.tertiary)
.padding(start = 16.dp, end = 16.dp, bottom = 16.dp),
selectedWalletId = UserWalletId("user_wallet_1".encodeToByteArray()),
wallets = persistentListOf(
UserWalletItemUM(
id = UserWalletId("user_wallet_1".encodeToByteArray()),
name = stringReference("Tangem 2.0"),
information = stringReference("42 tokens"),
balance = UserWalletItemUM.Balance.Loaded("1 496,34 \$", isFlickering = false),
isEnabled = true,
onClick = {},
),
UserWalletItemUM(
id = UserWalletId("user_wallet_2".encodeToByteArray()),
name = stringReference("Tangem White"),
information = stringReference("24 tokens"),
balance = UserWalletItemUM.Balance.Loaded("1 496,34 \$", isFlickering = false),
isEnabled = true,
onClick = {},
),
UserWalletItemUM(
id = UserWalletId("user_wallet_3".encodeToByteArray()),
name = stringReference("Bitcoin"),
information = stringReference("1 token"),
balance = UserWalletItemUM.Balance.Loaded("1 496,34 \$", isFlickering = false),
isEnabled = true,
onClick = {},
),
UserWalletItemUM(
id = UserWalletId("user_wallet_4".encodeToByteArray()),
name = stringReference("Tangem 1.0"),
information = stringReference("21 tokens"),
balance = UserWalletItemUM.Balance.Loaded("1 496,34 \$", isFlickering = false),
isEnabled = true,
onClick = {},
),
),
)
}
}

View file

@ -26,6 +26,7 @@ internal sealed class WcAppInfoUM : TangemBottomSheetConfigContent {
val appSubtitle: String,
val notification: WcAppInfoSecurityNotification?,
val walletName: String,
val onWalletClick: () -> Unit,
val networksInfo: WcNetworksInfo,
override val connectButtonConfig: WcPrimaryButtonConfig,
override val onDismiss: () -> Unit,

View file

@ -0,0 +1,10 @@
package com.tangem.features.walletconnect.connections.entity
import com.tangem.common.ui.userwallet.state.UserWalletItemUM
import com.tangem.domain.wallets.models.UserWallet
import kotlinx.collections.immutable.ImmutableList
internal data class WcAppInfoWalletUM(
val wallets: ImmutableList<UserWalletItemUM>,
val selectedUserWallet: UserWallet,
)

View file

@ -1,47 +1,98 @@
package com.tangem.features.walletconnect.connections.model
import androidx.compose.runtime.Stable
import com.arkivanov.decompose.router.stack.StackNavigation
import com.arkivanov.decompose.router.stack.pop
import com.arkivanov.decompose.router.stack.pushNew
import com.tangem.common.ui.userwallet.state.UserWalletItemUM
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.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
import com.tangem.domain.tokens.GetWalletTotalBalanceUseCase
import com.tangem.domain.walletconnect.model.WcPairRequest
import com.tangem.domain.walletconnect.model.WcSessionApprove
import com.tangem.domain.walletconnect.model.WcSessionProposal
import com.tangem.domain.walletconnect.usecase.pair.WcPairState
import com.tangem.domain.walletconnect.usecase.pair.WcPairUseCase
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.usecase.GetCardImageUseCase
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
import com.tangem.features.walletconnect.connections.components.WcAppInfoContainerComponent
import com.tangem.features.walletconnect.connections.entity.WcAppInfoUM
import com.tangem.features.walletconnect.connections.entity.WcAppInfoWalletUM
import com.tangem.features.walletconnect.connections.entity.WcPrimaryButtonConfig
import com.tangem.features.walletconnect.connections.model.transformers.WcAppInfoTransformer
import com.tangem.features.walletconnect.connections.model.transformers.WcAppInfoWalletChangedTransformer
import com.tangem.features.walletconnect.connections.model.transformers.WcConnectButtonProgressTransformer
import com.tangem.features.walletconnect.connections.routes.WcAppInfoRoutes
import com.tangem.features.walletconnect.connections.utils.WcUserWalletsFetcher
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.flow.*
import javax.inject.Inject
import kotlin.properties.Delegates
import com.tangem.utils.transformer.update as transformerUpdate
@Suppress("LongParameterList")
@Stable
@ModelScoped
internal class WcAppInfoModel @Inject constructor(
private val router: Router,
override val dispatchers: CoroutineDispatcherProvider,
wcPairUseCaseFactory: WcPairUseCase.Factory,
private val router: Router,
getWalletsUseCase: GetWalletsUseCase,
getWalletTotalBalanceUseCase: GetWalletTotalBalanceUseCase,
getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
messageSender: UiMessageSender,
getCardImageUseCase: GetCardImageUseCase,
paramsContainer: ParamsContainer,
) : Model() {
private val params: WcAppInfoContainerComponent.Params = paramsContainer.require()
private val wcPairUseCase = wcPairUseCaseFactory.create(WcPairRequest(uri = params.wcUrl, source = params.source))
private val wcPairUseCase = wcPairUseCaseFactory.create(
WcPairRequest(
userWalletId = params.userWalletId,
uri = params.wcUrl,
source = params.source,
),
)
private val userWalletsFetcher = WcUserWalletsFetcher(
getWalletsUseCase = getWalletsUseCase,
getWalletTotalBalanceUseCase = getWalletTotalBalanceUseCase,
getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase,
getBalanceHidingSettingsUseCase = getBalanceHidingSettingsUseCase,
messageSender = messageSender,
getCardImageUseCase = getCardImageUseCase,
onWalletSelected = ::onWalletSelected,
)
private val selectedUserWalletFlow =
MutableStateFlow(getWalletsUseCase.invokeSync().first { it.walletId == params.userWalletId })
// TODO(wc) Doston: Temp solution, will be fixed in next PR`s
private var proposalNetwork by Delegates.notNull<WcSessionProposal.ProposalNetwork>()
internal var sessionProposal by Delegates.notNull<WcSessionProposal>()
// UI states
internal val contentNavigation = StackNavigation<WcAppInfoRoutes>()
val appInfoUiState: StateFlow<WcAppInfoUM>
field = MutableStateFlow<WcAppInfoUM>(createLoadingState())
// TODO(wc) Doston: Temp solution, will be fixed in next PR`s
private var userWallet by Delegates.notNull<UserWallet>()
private var proposalNetwork by Delegates.notNull<WcSessionProposal.ProposalNetwork>()
val walletsUiState: StateFlow<WcAppInfoWalletUM>
field = MutableStateFlow(WcAppInfoWalletUM(persistentListOf(), selectedUserWalletFlow.value))
init {
loadDAppInfo()
combine(
flow = userWalletsFetcher.userWallets,
flow2 = selectedUserWalletFlow,
transform = ::updateWalletsState,
).launchIn(modelScope)
}
private fun loadDAppInfo() {
@ -62,14 +113,15 @@ internal class WcAppInfoModel @Inject constructor(
}
is WcPairState.Loading -> appInfoUiState.update { createLoadingState() }
is WcPairState.Proposal -> {
userWallet = state.dAppSession.proposalNetwork.keys.first { !it.isLocked }
proposalNetwork = state.dAppSession.proposalNetwork.getValue(userWallet)
sessionProposal = state.dAppSession
proposalNetwork = sessionProposal.proposalNetwork.getValue(selectedUserWalletFlow.value)
appInfoUiState.transformerUpdate(
WcAppInfoTransformer(
dAppSession = state.dAppSession,
onDismiss = ::dismiss,
onConnect = ::onConnect,
userWallet = userWallet,
onWalletClick = { contentNavigation.pushNew(WcAppInfoRoutes.SelectWallet) },
userWallet = selectedUserWalletFlow.value,
proposalNetwork = proposalNetwork,
),
)
@ -87,13 +139,24 @@ internal class WcAppInfoModel @Inject constructor(
private fun onConnect() {
wcPairUseCase.approve(
WcSessionApprove(
wallet = userWallet,
network =
proposalNetwork.required.plus(proposalNetwork.available).toList(),
wallet = selectedUserWalletFlow.value,
network = proposalNetwork.required.plus(proposalNetwork.available).toList(),
),
)
}
private fun onWalletSelected(userWalletId: UserWalletId) {
val selectedUserWallet = sessionProposal.proposalNetwork.keys.first { it.walletId == userWalletId }
selectedUserWalletFlow.update { selectedUserWallet }
proposalNetwork = sessionProposal.proposalNetwork.getValue(selectedUserWallet)
appInfoUiState.transformerUpdate(WcAppInfoWalletChangedTransformer(selectedUserWallet, proposalNetwork))
contentNavigation.pop()
}
private fun updateWalletsState(items: ImmutableList<UserWalletItemUM>, userWallet: UserWallet) {
walletsUiState.update { state -> state.copy(wallets = items, selectedUserWallet = userWallet) }
}
private fun createLoadingState(): WcAppInfoUM.Loading {
return WcAppInfoUM.Loading(
onDismiss = ::dismiss,

View file

@ -6,6 +6,7 @@ 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.iconResId
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.message.SnackbarMessage
import com.tangem.domain.walletconnect.model.WcSession
@ -13,9 +14,10 @@ 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.WcNetworkInfoItem
import com.tangem.features.walletconnect.connections.entity.WcPrimaryButtonConfig
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
@ -56,7 +58,15 @@ internal class WcConnectedAppInfoModel @Inject constructor(
isVerified = session.securityStatus == CheckDAppResult.SAFE,
appSubtitle = session.sdkModel.appMetaData.description,
walletName = session.wallet.name,
networks = persistentListOf(), // TODO(wc): Nikolai & Doston: Where to find networks???
networks = session.networks
.map {
WcNetworkInfoItem(
id = it.id.value,
icon = it.iconResId,
name = it.name,
symbol = it.currencySymbol,
)
}.toImmutableList(),
disconnectButtonConfig = WcPrimaryButtonConfig(
showProgress = false,
enabled = true,

View file

@ -6,6 +6,7 @@ import com.arkivanov.decompose.router.slot.activate
import com.tangem.common.routing.AppRoute
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.core.ui.components.appbar.models.TopAppBarButtonUM
@ -16,11 +17,12 @@ 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.WcPairService
import com.tangem.domain.walletconnect.model.WcPairRequest
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.ConnectionsComponent
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
@ -44,8 +46,10 @@ internal class WcConnectionsModel @Inject constructor(
private val wcDisconnectUseCase: WcDisconnectUseCase,
private val wcPairService: WcPairService,
override val dispatchers: CoroutineDispatcherProvider,
paramsContainer: ParamsContainer,
) : Model() {
private val params = paramsContainer.require<ConnectionsComponent.Params>()
val uiState: StateFlow<WcConnectionsState>
field = MutableStateFlow<WcConnectionsState>(getInitialState())
val bottomSheetNavigation: SlotNavigation<WcConnectionsBottomSheetConfig> = SlotNavigation()
@ -58,7 +62,15 @@ internal class WcConnectionsModel @Inject constructor(
private fun listenQrUpdates() {
listenToQrScanningUseCase(SourceType.WALLET_CONNECT)
.getOrElse { emptyFlow() }
.onEach { wcUrl -> wcPairService.pair(WcPairRequest(wcUrl, WcPairRequest.Source.QR)) }
.onEach { wcUrl ->
wcPairService.pair(
WcPairRequest(
userWalletId = params.userWalletId,
uri = wcUrl,
source = WcPairRequest.Source.QR,
),
)
}
.launchIn(modelScope)
}

View file

@ -1,17 +1,16 @@
package com.tangem.features.walletconnect.connections.model.transformers
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.*
import com.tangem.utils.transformer.Transformer
import kotlinx.collections.immutable.toImmutableList
internal class WcAppInfoTransformer(
private val dAppSession: WcSessionProposal,
private val onDismiss: () -> Unit,
private val onConnect: () -> Unit,
private val onWalletClick: () -> Unit,
private val userWallet: UserWallet,
private val proposalNetwork: WcSessionProposal.ProposalNetwork,
) : Transformer<WcAppInfoUM> {
@ -24,7 +23,8 @@ internal class WcAppInfoTransformer(
appSubtitle = dAppSession.dAppMetaData.description,
notification = createNotification(dAppSession.securityStatus),
walletName = userWallet.name,
networksInfo = convertNetworksInfo(proposalNetwork),
onWalletClick = onWalletClick,
networksInfo = WcNetworksInfoConverter.convert(proposalNetwork),
connectButtonConfig = WcPrimaryButtonConfig(
showProgress = false,
enabled = proposalNetwork.missingRequired.isEmpty(),
@ -41,26 +41,4 @@ internal class WcAppInfoTransformer(
CheckDAppResult.FAILED_TO_VERIFY -> WcAppInfoSecurityNotification.UnknownDomain
}
}
private fun convertNetworksInfo(proposalNetwork: WcSessionProposal.ProposalNetwork): WcNetworksInfo {
return if (proposalNetwork.missingRequired.isNotEmpty()) {
WcNetworksInfo.MissingRequiredNetworkInfo(
networks = proposalNetwork.missingRequired
.joinToString { it.name },
)
} else {
WcNetworksInfo.ContainsAllRequiredNetworks(
items = (proposalNetwork.required + proposalNetwork.available)
.map {
WcNetworkInfoItem(
id = it.id.value,
icon = it.iconResId,
name = it.name,
symbol = it.currencySymbol,
)
}
.toImmutableList(),
)
}
}
}

View file

@ -0,0 +1,22 @@
package com.tangem.features.walletconnect.connections.model.transformers
import com.tangem.domain.walletconnect.model.WcSessionProposal
import com.tangem.domain.wallets.models.UserWallet
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,
) : 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(proposalNetwork),
connectButtonConfig = prevState.connectButtonConfig.copy(
enabled = proposalNetwork.missingRequired.isEmpty(),
),
)
}
}

View file

@ -0,0 +1,32 @@
package com.tangem.features.walletconnect.connections.model.transformers
import com.tangem.core.ui.extensions.iconResId
import com.tangem.domain.walletconnect.model.WcSessionProposal
import com.tangem.features.walletconnect.connections.entity.WcNetworkInfoItem
import com.tangem.features.walletconnect.connections.entity.WcNetworksInfo
import com.tangem.utils.converter.Converter
import kotlinx.collections.immutable.toImmutableList
internal object WcNetworksInfoConverter : Converter<WcSessionProposal.ProposalNetwork, WcNetworksInfo> {
override fun convert(value: WcSessionProposal.ProposalNetwork): WcNetworksInfo {
return if (value.missingRequired.isNotEmpty()) {
WcNetworksInfo.MissingRequiredNetworkInfo(
networks = value.missingRequired
.joinToString { it.name },
)
} else {
WcNetworksInfo.ContainsAllRequiredNetworks(
items = (value.required + value.available)
.map {
WcNetworkInfoItem(
id = it.id.value,
icon = it.iconResId,
name = it.name,
symbol = it.currencySymbol,
)
}
.toImmutableList(),
)
}
}
}

View file

@ -16,7 +16,7 @@ import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.features.walletconnect.components.WcRoutingComponent
import com.tangem.features.walletconnect.connections.components.WcAppInfoContainerComponent
import com.tangem.features.walletconnect.transaction.components.WcSignTransactionComponent
import com.tangem.features.walletconnect.transaction.components.WcSignTransactionContainerComponent
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@ -55,13 +55,17 @@ internal class DefaultWcRoutingComponent @AssistedInject constructor(
router = model.innerRouter,
)
return when (config) {
is WcInnerRoute.SignMessage -> WcSignTransactionComponent(
is WcInnerRoute.SignMessage -> WcSignTransactionContainerComponent(
childContext,
params = WcSignTransactionComponent.Params(config.rawRequest),
params = WcSignTransactionContainerComponent.Params(config.rawRequest),
)
is WcInnerRoute.Pair -> WcAppInfoContainerComponent(
childContext,
WcAppInfoContainerComponent.Params(config.request.uri, config.request.source),
WcAppInfoContainerComponent.Params(
userWalletId = config.request.userWalletId,
wcUrl = config.request.uri,
source = config.request.source,
),
)
}
}

View file

@ -220,7 +220,12 @@ private fun WcAppInfoSecondBlock(state: WcAppInfoUM.Content, modifier: Modifier
val itemsModifier = Modifier
.fillMaxWidth()
.padding(TangemTheme.dimens.spacing12)
WalletRowItem(modifier = itemsModifier, walletName = state.walletName)
WalletRowItem(
modifier = Modifier
.clickable(onClick = state.onWalletClick)
.then(itemsModifier),
walletName = state.walletName,
)
HorizontalDivider(thickness = 1.dp, color = TangemTheme.colors.stroke.primary)
SelectNetworksBlock(
networksInfo = state.networksInfo,
@ -565,6 +570,7 @@ private class WcAppInfoStateProvider : CollectionPreviewParameterProvider<WcAppI
appSubtitle = "react-app.walletconnect.com",
notification = WcAppInfoSecurityNotification.SecurityRisk,
walletName = "Tangem 2.0",
onWalletClick = {},
networksInfo = WcNetworksInfo.ContainsAllRequiredNetworks(
items = persistentListOf(
WcNetworkInfoItem(
@ -604,6 +610,7 @@ private class WcAppInfoStateProvider : CollectionPreviewParameterProvider<WcAppI
appSubtitle = "react-app.walletconnect.com",
notification = WcAppInfoSecurityNotification.UnknownDomain,
walletName = "Tangem 2.0",
onWalletClick = {},
networksInfo = WcNetworksInfo.MissingRequiredNetworkInfo(networks = "Solana"),
onDismiss = {},
connectButtonConfig = WcPrimaryButtonConfig(showProgress = false, enabled = true, onClick = {}),

View file

@ -0,0 +1,140 @@
package com.tangem.features.walletconnect.connections.utils
import arrow.core.Either
import com.tangem.common.ui.userwallet.converter.UserWalletItemUMConverter
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.resourceReference
import com.tangem.core.ui.message.SnackbarMessage
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.error.SelectedAppCurrencyError
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.balancehiding.BalanceHidingSettings
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
import com.tangem.domain.core.lce.Lce
import com.tangem.domain.core.lce.lce
import com.tangem.domain.core.utils.getOrElse
import com.tangem.domain.core.utils.toLce
import com.tangem.domain.models.ArtworkModel
import com.tangem.domain.tokens.GetWalletTotalBalanceUseCase
import com.tangem.domain.tokens.error.TokenListError
import com.tangem.domain.tokens.model.TotalFiatBalance
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.usecase.GetCardImageUseCase
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
import com.tangem.features.walletconnect.impl.R
import com.tangem.operations.attestation.ArtworkSize
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.*
@Suppress("LongParameterList")
@ModelScoped
internal class WcUserWalletsFetcher(
getWalletsUseCase: GetWalletsUseCase,
private val getWalletTotalBalanceUseCase: GetWalletTotalBalanceUseCase,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
private val messageSender: UiMessageSender,
private val getCardImageUseCase: GetCardImageUseCase,
private val onWalletSelected: (UserWalletId) -> Unit,
) {
private var loadedArtworks: HashMap<UserWalletId, ArtworkModel> = hashMapOf()
@OptIn(ExperimentalCoroutinesApi::class)
val userWallets: Flow<ImmutableList<UserWalletItemUM>> = getWalletsUseCase().transformLatest { wallets ->
val uiModels = UserWalletItemUMConverter(onClick = onWalletSelected)
.convertList(wallets)
.toImmutableList()
emit(uiModels)
combine(
flow = getSelectedAppCurrencyUseCase().distinctUntilChanged(),
flow2 = getBalanceHidingSettingsUseCase().distinctUntilChanged(),
flow3 = getWalletTotalBalanceUseCase(wallets.map(UserWallet::walletId)).distinctUntilChanged(),
flow4 = loadArtworks(wallets),
) { maybeAppCurrency, balanceHidingSettings, maybeBalances, artworks ->
val models = createUiModels(
wallets = wallets,
maybeAppCurrency = maybeAppCurrency,
maybeBalances = maybeBalances,
balanceHidingSettings = balanceHidingSettings,
artworks = artworks,
).getOrElse(
ifLoading = { return@combine },
ifError = {
val message = resourceReference(R.string.common_unknown_error)
messageSender.send(SnackbarMessage(message))
return@combine
},
)
emit(models)
}.collect()
}
private fun loadArtworks(wallets: List<UserWallet>): Flow<HashMap<UserWalletId, ArtworkModel>> {
return flow {
emit(hashMapOf()) // emits right away so the transform doesn't wait for the images' loading to finish
wallets.forEach { wallet ->
val card = wallet.scanResponse.card
val artwork = getCardImageUseCase(
cardId = wallet.cardId,
cardPublicKey = card.cardPublicKey,
size = ArtworkSize.SMALL,
manufacturerName = card.manufacturer.name,
firmwareVersion = card.firmwareVersion.toSdkFirmwareVersion(),
)
loadedArtworks[wallet.walletId] = artwork
emit(loadedArtworks)
}
}
}
private fun createUiModels(
wallets: List<UserWallet>,
maybeAppCurrency: Either<SelectedAppCurrencyError, AppCurrency>,
maybeBalances: Lce<TokenListError, Map<UserWalletId, TotalFiatBalance>>,
balanceHidingSettings: BalanceHidingSettings,
artworks: HashMap<UserWalletId, ArtworkModel>,
): Lce<Error, ImmutableList<UserWalletItemUM>> = lce {
val balances = withError(
transform = { Error.UnableToGetBalances },
block = {
maybeBalances.bindOrNull().orEmpty()
.filterKeys { userWalletId -> wallets.any { it.walletId == userWalletId } }
.mapKeys { entry -> wallets.first { it.walletId == entry.key } }
},
)
val appCurrency = withError(
transform = { Error.UnableToGetAppCurrency },
block = { maybeAppCurrency.toLce().bind() },
)
balances
.map { (userWallet, balance) ->
UserWalletItemUMConverter(
onClick = onWalletSelected,
appCurrency = appCurrency,
balance = balance,
isBalanceHidden = balanceHidingSettings.isBalanceHidden,
artwork = artworks[userWallet.walletId],
)
.convert(userWallet)
}
.toImmutableList()
}
sealed class Error {
data object UnableToGetAppCurrency : Error()
data object UnableToGetBalances : Error()
}
}

View file

@ -1,37 +1,28 @@
package com.tangem.features.walletconnect.transaction.components
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest
import com.tangem.features.walletconnect.transaction.model.WcSignTransactionModel
import com.tangem.features.walletconnect.transaction.ui.sign.WcSignTransactionModalBottomSheet
import com.tangem.features.walletconnect.transaction.ui.sign.WcSignTransactionModalBottomSheetContent
internal class WcSignTransactionComponent(
appComponentContext: AppComponentContext,
params: Params,
private val appComponentContext: AppComponentContext,
private val model: WcSignTransactionModel,
private val transactionInfoOnClick: () -> Unit,
) : AppComponentContext by appComponentContext, ComposableContentComponent {
private val model: WcSignTransactionModel = getOrCreateModel(params)
@Composable
override fun Content(modifier: Modifier) {
val content by model.uiState.collectAsStateWithLifecycle()
content?.let {
WcSignTransactionModalBottomSheet(
config = TangemBottomSheetConfig(
isShown = true,
onDismissRequest = it.actions.onDismiss,
content = it,
),
val content = model.uiState.collectAsStateWithLifecycle().value
if (content != null) {
WcSignTransactionModalBottomSheetContent(
state = content,
onClickTransactionRequest = transactionInfoOnClick,
)
}
}
data class Params(val rawRequest: WcSdkSessionRequest)
}

View file

@ -0,0 +1,123 @@
package com.tangem.features.walletconnect.transaction.components
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import com.arkivanov.decompose.ComponentContext
import com.arkivanov.decompose.extensions.compose.stack.Children
import com.arkivanov.decompose.extensions.compose.subscribeAsState
import com.arkivanov.decompose.router.stack.StackNavigation
import com.arkivanov.decompose.router.stack.childStack
import com.arkivanov.decompose.router.stack.navigate
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.context.childByContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheet
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetTitle
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest
import com.tangem.features.walletconnect.impl.R
import com.tangem.features.walletconnect.transaction.model.WcSignTransactionModel
import com.tangem.features.walletconnect.transaction.routes.WcSignTransactionRoutes
internal class WcSignTransactionContainerComponent(
private val appComponentContext: AppComponentContext,
params: Params,
) : AppComponentContext by appComponentContext, ComposableContentComponent {
private val model: WcSignTransactionModel = getOrCreateModel(params = params)
private val contentNavigation = StackNavigation<WcSignTransactionRoutes>()
private val contentStack = childStack(
source = contentNavigation,
serializer = WcSignTransactionRoutes.serializer(),
initialConfiguration = WcSignTransactionRoutes.Transaction,
childFactory = ::screenChild,
)
private fun dismiss() {
model.dismiss()
}
@Composable
override fun Content(modifier: Modifier) {
val content by contentStack.subscribeAsState()
TangemModalBottomSheet<WcSignTransactionRoutes>(
config = TangemBottomSheetConfig(
isShown = true,
onDismissRequest = ::dismiss,
content = content.active.configuration,
),
containerColor = TangemTheme.colors.background.tertiary,
title = { config -> Title(route = config) },
content = {
Box(
modifier = Modifier
.fillMaxWidth()
.animateContentSize(),
) {
Children(stack = content) { child ->
child.instance.Content(modifier = Modifier)
}
}
},
)
}
@Composable
private fun Title(route: WcSignTransactionRoutes) {
TangemModalBottomSheetTitle(
title = resourceReference(R.string.wc_wallet_connect),
startIconRes = when (route) {
is WcSignTransactionRoutes.Transaction -> null
else -> R.drawable.ic_back_24
},
onStartClick = when (route) {
is WcSignTransactionRoutes.Transaction -> null
else -> ::toTransaction
},
endIconRes = when (route) {
is WcSignTransactionRoutes.Transaction -> R.drawable.ic_close_24
else -> null
},
onEndClick = when (route) {
is WcSignTransactionRoutes.Transaction -> ::dismiss
else -> null
},
)
}
private fun toTransactionRequestInfo() {
contentNavigation.navigate {
listOf(WcSignTransactionRoutes.TransactionRequestInfo)
}
}
private fun toTransaction() {
contentNavigation.navigate {
listOf(WcSignTransactionRoutes.Transaction)
}
}
private fun screenChild(
config: WcSignTransactionRoutes,
componentContext: ComponentContext,
): ComposableContentComponent = when (config) {
is WcSignTransactionRoutes.Transaction -> WcSignTransactionComponent(
appComponentContext = childByContext(componentContext),
model = model,
transactionInfoOnClick = ::toTransactionRequestInfo,
)
is WcSignTransactionRoutes.TransactionRequestInfo -> WcSignTransactionRequestInfoComponent(
appComponentContext = childByContext(componentContext),
model = model,
)
}
data class Params(val rawRequest: WcSdkSessionRequest)
}

View file

@ -0,0 +1,24 @@
package com.tangem.features.walletconnect.transaction.components
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.features.walletconnect.transaction.model.WcSignTransactionModel
import com.tangem.features.walletconnect.transaction.ui.common.TransactionRequestInfoContent
internal class WcSignTransactionRequestInfoComponent(
private val appComponentContext: AppComponentContext,
private val model: WcSignTransactionModel,
) : AppComponentContext by appComponentContext, ComposableContentComponent {
@Composable
override fun Content(modifier: Modifier) {
val content = model.uiState.collectAsStateWithLifecycle().value
if (content != null) {
TransactionRequestInfoContent(content)
}
}
}

View file

@ -0,0 +1,56 @@
package com.tangem.features.walletconnect.transaction.entity
import androidx.annotation.DrawableRes
import androidx.compose.runtime.Immutable
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import com.tangem.core.ui.extensions.TextReference
import kotlinx.collections.immutable.ImmutableList
@Immutable
internal data class WcSignTransactionUM(
val actions: WcTransactionActionsUM,
val transaction: WcTransactionUM,
val transactionRequestInfo: WcTransactionRequestInfoUM,
) : TangemBottomSheetConfigContent
@Immutable
internal data class WcTransactionUM(
val appName: String,
val appIcon: String,
val isVerified: Boolean,
val appSubtitle: String,
val walletName: String,
val networkInfo: WcNetworkInfoUM,
val addressText: String? = null,
val networkFee: String? = null,
val isLoading: Boolean = false,
)
@Immutable
internal data class WcTransactionRequestInfoUM(
val blocks: ImmutableList<WcTransactionRequestBlockUM>,
)
@Immutable
internal data class WcTransactionRequestBlockUM(
val info: ImmutableList<WcTransactionRequestInfoItemUM>,
)
@Immutable
internal data class WcTransactionRequestInfoItemUM(
val title: TextReference,
val description: String = "",
)
@Immutable
internal data class WcTransactionActionsUM(
val onDismiss: () -> Unit,
val onSign: () -> Unit,
val onCopy: () -> Unit,
)
@Immutable
internal data class WcNetworkInfoUM(
val name: String,
@DrawableRes val iconRes: Int,
)

View file

@ -10,9 +10,9 @@ import com.tangem.domain.walletconnect.WcRequestUseCaseFactory
import com.tangem.domain.walletconnect.usecase.method.WcSignState
import com.tangem.domain.walletconnect.usecase.method.WcSignStep
import com.tangem.domain.walletconnect.usecase.method.WcSignUseCase
import com.tangem.features.walletconnect.transaction.components.WcSignTransactionComponent
import com.tangem.features.walletconnect.transaction.entity.sign.WcSignTransactionUM
import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionActionsUM
import com.tangem.features.walletconnect.transaction.components.WcSignTransactionContainerComponent
import com.tangem.features.walletconnect.transaction.entity.WcSignTransactionUM
import com.tangem.features.walletconnect.transaction.entity.WcTransactionActionsUM
import com.tangem.features.walletconnect.transaction.utils.toUM
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.MutableStateFlow
@ -35,7 +35,7 @@ internal class WcSignTransactionModel @Inject constructor(
private val _uiState = MutableStateFlow<WcSignTransactionUM?>(null)
val uiState: StateFlow<WcSignTransactionUM?> = _uiState
private val params = paramsContainer.require<WcSignTransactionComponent.Params>()
private val params = paramsContainer.require<WcSignTransactionContainerComponent.Params>()
init {
modelScope.launch {
@ -47,10 +47,8 @@ internal class WcSignTransactionModel @Inject constructor(
signState = signState,
actions = WcTransactionActionsUM(
onDismiss = { cancel(useCase) },
onBack = ::showTransactionState,
activeButtonOnClick = useCase::sign,
onSign = useCase::sign,
onCopy = { copyData(useCase.rawSdkRequest.request.params) },
transactionRequestOnClick = ::showTransactionRequestState,
),
)
_uiState.emit(signTransactionUM)
@ -59,6 +57,10 @@ internal class WcSignTransactionModel @Inject constructor(
}
}
fun dismiss() {
_uiState.value?.actions?.onDismiss?.invoke() ?: router.pop()
}
private fun signingIsDone(signState: WcSignState<*>): Boolean {
(signState.domainStep as? WcSignStep.Result)?.result?.let {
router.pop()
@ -75,12 +77,4 @@ internal class WcSignTransactionModel @Inject constructor(
private fun copyData(text: String) {
clipboardManager.setText(text = text, isSensitive = true)
}
private fun showTransactionRequestState() {
_uiState.value = _uiState.value?.copy(state = WcSignTransactionUM.State.TRANSACTION_REQUEST_INFO)
}
private fun showTransactionState() {
_uiState.value = _uiState.value?.copy(state = WcSignTransactionUM.State.TRANSACTION)
}
}

View file

@ -0,0 +1,15 @@
package com.tangem.features.walletconnect.transaction.routes
import androidx.compose.runtime.Immutable
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import kotlinx.serialization.Serializable
@Serializable
@Immutable
internal sealed class WcSignTransactionRoutes : TangemBottomSheetConfigContent {
@Serializable
data object Transaction : WcSignTransactionRoutes()
@Serializable
data object TransactionRequestInfo : WcSignTransactionRoutes()
}

View file

@ -0,0 +1,219 @@
package com.tangem.features.walletconnect.transaction.ui.common
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.tooling.preview.Devices
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.SecondaryButtonIconEnd
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheet
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetTitle
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.features.walletconnect.impl.R
import com.tangem.features.walletconnect.transaction.entity.*
import com.tangem.features.walletconnect.transaction.entity.WcNetworkInfoUM
import com.tangem.features.walletconnect.transaction.entity.WcSignTransactionUM
import com.tangem.features.walletconnect.transaction.entity.WcTransactionActionsUM
import com.tangem.features.walletconnect.transaction.entity.WcTransactionRequestInfoUM
import com.tangem.features.walletconnect.transaction.entity.WcTransactionUM
import kotlinx.collections.immutable.persistentListOf
private const val MIN_HEIGHT_SCREEN_PERCENT = 0.35f
private const val MAX_HEIGHT_SCREEN_PERCENT = 0.75f
@Composable
internal fun TransactionRequestInfoContent(state: WcSignTransactionUM) {
val screenHeight = LocalConfiguration.current.screenHeightDp
val minHeight = (screenHeight * MIN_HEIGHT_SCREEN_PERCENT).dp
val maxHeight = (screenHeight * MAX_HEIGHT_SCREEN_PERCENT).dp
Box(
modifier = Modifier
.fillMaxWidth()
.heightIn(min = minHeight, max = maxHeight)
.padding(horizontal = TangemTheme.dimens.spacing16),
) {
LazyColumn(
modifier = Modifier
.fillMaxWidth(),
contentPadding = PaddingValues(top = TangemTheme.dimens.spacing8, bottom = TangemTheme.dimens.spacing70),
) {
items(state.transactionRequestInfo.blocks) { block ->
Column(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(TangemTheme.dimens.radius16))
.background(TangemTheme.colors.background.action)
.padding(
start = TangemTheme.dimens.spacing16,
end = TangemTheme.dimens.spacing16,
top = TangemTheme.dimens.spacing20,
),
) {
block.info.forEach { item ->
Text(
text = item.title.resolveReference(),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.tertiary,
)
if (item.description.isNotEmpty()) {
Text(
text = item.description,
modifier = Modifier.padding(
top = TangemTheme.dimens.spacing4,
bottom = TangemTheme.dimens.spacing20,
),
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.primary1,
)
} else {
Spacer(modifier = Modifier.size(TangemTheme.dimens.size12))
}
}
}
Spacer(modifier = Modifier.size(TangemTheme.dimens.size20))
}
}
SecondaryButtonIconEnd(
modifier = Modifier
.align(Alignment.BottomCenter)
.padding(bottom = TangemTheme.dimens.spacing20)
.fillMaxWidth(),
text = stringResourceSafe(R.string.wc_copy_data_button_text),
onClick = state.actions.onCopy,
iconResId = R.drawable.ic_copy_24,
)
}
}
@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 WcSignTransactionRequestInfoBottomSheetPreview(
@PreviewParameter(WcSignTransactionStateProvider::class) state: WcSignTransactionUM,
) {
TangemThemePreview {
TangemModalBottomSheet<WcSignTransactionUM>(
config = TangemBottomSheetConfig(
isShown = true,
onDismissRequest = {},
content = state,
),
containerColor = TangemTheme.colors.background.tertiary,
title = {
TangemModalBottomSheetTitle(
title = resourceReference(R.string.wc_transaction_request_title),
endIconRes = null,
onEndClick = {},
startIconRes = R.drawable.ic_back_24,
onStartClick = {},
)
},
content = {
TransactionRequestInfoContent(state = state)
},
)
}
}
private class WcSignTransactionStateProvider : CollectionPreviewParameterProvider<WcSignTransactionUM>(
listOf(
WcSignTransactionUM(
transaction = WcTransactionUM(
appName = "React App",
appIcon = "",
isVerified = true,
appSubtitle = "react-app.walletconnect.com",
walletName = "Tangem 2.0",
networkInfo = WcNetworkInfoUM(name = "Ethereum", iconRes = R.drawable.img_eth_22),
),
transactionRequestInfo = WcTransactionRequestInfoUM(
blocks = persistentListOf(
WcTransactionRequestBlockUM(
persistentListOf(
WcTransactionRequestInfoItemUM(
title = resourceReference(R.string.wc_signature_type),
description = "personal_sign",
),
WcTransactionRequestInfoItemUM(
title = resourceReference(R.string.wc_contents),
description = "Hello! My name is John Dow. test@tange.com",
),
),
),
),
),
actions = WcTransactionActionsUM(
onDismiss = {},
onSign = {},
onCopy = {},
),
),
WcSignTransactionUM(
transaction = WcTransactionUM(
appName = "React App",
appIcon = "",
isVerified = true,
appSubtitle = "react-app.walletconnect.com",
walletName = "Tangem 2.0",
networkInfo = WcNetworkInfoUM(name = "Ethereum", iconRes = R.drawable.img_eth_22),
addressText = "0x345FF...34FA",
networkFee = "~ 0.22 $",
),
transactionRequestInfo = WcTransactionRequestInfoUM(
blocks = persistentListOf(
WcTransactionRequestBlockUM(
persistentListOf(
WcTransactionRequestInfoItemUM(
title = resourceReference(R.string.wc_signature_type),
description = "personal_sign",
),
WcTransactionRequestInfoItemUM(
title = resourceReference(R.string.wc_contents),
description = "Hello! My name is John Dow. test@tange.com",
),
),
),
WcTransactionRequestBlockUM(
persistentListOf(
WcTransactionRequestInfoItemUM(
title = resourceReference(R.string.wc_transaction_info_to_title),
),
WcTransactionRequestInfoItemUM(
title = resourceReference(R.string.settings_wallet_name_title),
description = "Bob",
),
WcTransactionRequestInfoItemUM(
title = resourceReference(R.string.wc_common_wallet),
description = "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826",
),
),
),
),
),
actions = WcTransactionActionsUM(
onDismiss = {},
onSign = {},
onCopy = {},
),
),
),
)

View file

@ -0,0 +1,44 @@
package com.tangem.features.walletconnect.transaction.ui.common
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.walletconnect.impl.R
@Composable
internal fun WcAddressItem(addressText: String, modifier: Modifier = Modifier) {
Row(modifier = modifier, verticalAlignment = Alignment.CenterVertically) {
Icon(
modifier = Modifier.size(TangemTheme.dimens.size24),
painter = painterResource(R.drawable.ic_user_square_24),
contentDescription = null,
tint = TangemTheme.colors.icon.accent,
)
Text(
modifier = Modifier
.padding(start = TangemTheme.dimens.spacing8)
.weight(1f),
text = stringResourceSafe(R.string.wc_common_address),
style = TangemTheme.typography.body1,
color = TangemTheme.colors.text.primary1,
)
Text(
modifier = Modifier
.padding(start = TangemTheme.dimens.spacing16)
.weight(1f),
text = addressText,
textAlign = TextAlign.End,
style = TangemTheme.typography.body1,
color = TangemTheme.colors.text.tertiary,
)
}
}

View file

@ -0,0 +1,67 @@
package com.tangem.features.walletconnect.transaction.ui.common
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.painterResource
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.walletconnect.impl.R
@Composable
internal fun WcNetworkFeeItem(networkFeeText: String, modifier: Modifier = Modifier) {
Row(
modifier = modifier,
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
modifier = Modifier.size(TangemTheme.dimens.size24),
painter = painterResource(R.drawable.ic_fee_new_24),
contentDescription = null,
tint = TangemTheme.colors.icon.accent,
)
Text(
modifier = Modifier.padding(start = TangemTheme.dimens.spacing8),
text = stringResourceSafe(R.string.common_network_fee_title),
style = TangemTheme.typography.body1,
color = TangemTheme.colors.text.primary1,
)
Icon(
modifier = Modifier
.padding(start = TangemTheme.dimens.spacing6)
.size(TangemTheme.dimens.size16),
painter = painterResource(id = R.drawable.ic_information_24),
tint = TangemTheme.colors.icon.informative,
contentDescription = null,
)
Spacer(modifier = Modifier.weight(1f))
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.End,
) {
Text(
text = networkFeeText,
style = TangemTheme.typography.body1,
color = TangemTheme.colors.text.tertiary,
)
Spacer(modifier = Modifier.width(TangemTheme.dimens.spacing6))
Icon(
modifier = Modifier
.clip(CircleShape)
.size(width = TangemTheme.dimens.size18, height = TangemTheme.dimens.size24),
painter = painterResource(id = R.drawable.ic_select_18_24),
tint = TangemTheme.colors.icon.informative,
contentDescription = null,
)
}
}
}

View file

@ -30,7 +30,7 @@ internal fun WcNetworkItem(networkInfo: WcNetworkInfoUM, modifier: Modifier = Mo
)
Text(
modifier = Modifier
.padding(start = TangemTheme.dimens.spacing4)
.padding(start = TangemTheme.dimens.spacing8)
.weight(1f),
text = stringResourceSafe(R.string.wc_common_network),
style = TangemTheme.typography.body1,

View file

@ -25,7 +25,7 @@ internal fun WcWalletItem(walletName: String, modifier: Modifier = Modifier) {
)
Text(
modifier = Modifier
.padding(start = TangemTheme.dimens.spacing4)
.padding(start = TangemTheme.dimens.spacing8)
.weight(1f),
text = stringResourceSafe(R.string.manage_tokens_network_selector_wallet),
style = TangemTheme.typography.body1,

View file

@ -1,268 +0,0 @@
package com.tangem.features.walletconnect.transaction.ui.sign
import android.content.res.Configuration
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Devices
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheet
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetTitle
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.features.walletconnect.impl.R
import com.tangem.features.walletconnect.transaction.entity.common.WcNetworkInfoUM
import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionActionsUM
import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionRequestBlockUM
import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionRequestInfoItemUM
import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionRequestInfoUM
import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionUM
import com.tangem.features.walletconnect.transaction.entity.sign.WcSignTransactionUM
import com.tangem.features.walletconnect.transaction.ui.common.WcTransactionRequestInfoContent
import kotlinx.collections.immutable.persistentListOf
@Composable
internal fun WcSignTransactionModalBottomSheet(config: TangemBottomSheetConfig) {
TangemModalBottomSheet<WcSignTransactionUM>(
config = config,
containerColor = TangemTheme.colors.background.tertiary,
title = { state ->
TangemModalBottomSheetTitle(
title = when (state.state) {
WcSignTransactionUM.State.TRANSACTION -> {
resourceReference(R.string.wallet_connect_title)
}
WcSignTransactionUM.State.TRANSACTION_REQUEST_INFO -> {
resourceReference(R.string.wc_transaction_request_title)
}
},
endIconRes = R.drawable.ic_close_24.takeIf {
state.state == WcSignTransactionUM.State.TRANSACTION
},
onEndClick = state.actions.onDismiss,
startIconRes = R.drawable.ic_back_24.takeIf {
state.state == WcSignTransactionUM.State.TRANSACTION_REQUEST_INFO
},
onStartClick = state.actions.onBack,
)
},
content = { state ->
Box(
modifier = Modifier
.fillMaxWidth()
.animateContentSize(),
) {
when (state.state) {
WcSignTransactionUM.State.TRANSACTION -> {
WcTransactionModalBottomSheetContent(state.transaction, state.actions)
}
WcSignTransactionUM.State.TRANSACTION_REQUEST_INFO -> {
WcTransactionRequestInfoContent(state.transactionRequestInfo, state.actions)
}
}
}
},
)
}
@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 WcSignTransactionBottomSheetPreview(
@PreviewParameter(WcSignTransactionStateProvider::class) state: WcSignTransactionUM,
) {
TangemThemePreview {
WcSignTransactionModalBottomSheet(
config = TangemBottomSheetConfig(
isShown = true,
onDismissRequest = {},
content = state,
),
)
}
}
private class WcSignTransactionStateProvider : CollectionPreviewParameterProvider<WcSignTransactionUM>(
listOf(
WcSignTransactionUM(
state = WcSignTransactionUM.State.TRANSACTION,
transaction = WcTransactionUM(
appName = "React App",
appIcon = "",
isVerified = true,
appSubtitle = "react-app.walletconnect.com",
walletName = "Tangem 2.0",
activeButtonText = resourceReference(R.string.common_sign),
networkInfo = WcNetworkInfoUM(name = "Ethereum", iconRes = R.drawable.img_eth_22),
),
transactionRequestInfo = WcTransactionRequestInfoUM(
blocks = persistentListOf(
WcTransactionRequestBlockUM(
persistentListOf(
WcTransactionRequestInfoItemUM(
title = resourceReference(R.string.wc_signature_type),
description = "personal_sign",
),
WcTransactionRequestInfoItemUM(
title = resourceReference(R.string.wc_contents),
description = "Hello! My name is John Dow. test@tange.com",
),
),
),
),
),
actions = WcTransactionActionsUM(
onDismiss = {},
onBack = {},
activeButtonOnClick = {},
onCopy = {},
transactionRequestOnClick = {},
),
),
WcSignTransactionUM(
state = WcSignTransactionUM.State.TRANSACTION_REQUEST_INFO,
transaction = WcTransactionUM(
appName = "React App",
appIcon = "",
isVerified = true,
appSubtitle = "react-app.walletconnect.com",
walletName = "Tangem 2.0",
activeButtonText = resourceReference(R.string.common_sign),
networkInfo = WcNetworkInfoUM(name = "Ethereum", iconRes = R.drawable.img_eth_22),
),
transactionRequestInfo = WcTransactionRequestInfoUM(
blocks = persistentListOf(
WcTransactionRequestBlockUM(
persistentListOf(
WcTransactionRequestInfoItemUM(
title = resourceReference(R.string.wc_signature_type),
description = "personal_sign",
),
WcTransactionRequestInfoItemUM(
title = resourceReference(R.string.wc_contents),
description = "Hello! My name is John Dow. test@tange.com",
),
),
),
),
),
actions = WcTransactionActionsUM(
onDismiss = {},
onBack = {},
activeButtonOnClick = {},
onCopy = {},
transactionRequestOnClick = {},
),
),
WcSignTransactionUM(
state = WcSignTransactionUM.State.TRANSACTION,
transaction = WcTransactionUM(
appName = "React App",
appIcon = "",
isVerified = true,
appSubtitle = "react-app.walletconnect.com",
walletName = "Tangem 2.0",
activeButtonText = resourceReference(R.string.common_sign),
networkInfo = WcNetworkInfoUM(name = "Ethereum", iconRes = R.drawable.img_eth_22),
addressText = "0x345FF...34FA",
networkFee = "~ 0.22 $",
),
transactionRequestInfo = WcTransactionRequestInfoUM(
blocks = persistentListOf(
WcTransactionRequestBlockUM(
persistentListOf(
WcTransactionRequestInfoItemUM(
title = resourceReference(R.string.wc_signature_type),
description = "personal_sign",
),
WcTransactionRequestInfoItemUM(
title = resourceReference(R.string.wc_contents),
description = "Hello! My name is John Dow. test@tange.com",
),
),
),
WcTransactionRequestBlockUM(
persistentListOf(
WcTransactionRequestInfoItemUM(
title = resourceReference(R.string.wc_transaction_info_to_title),
),
WcTransactionRequestInfoItemUM(
title = resourceReference(R.string.settings_wallet_name_title),
description = "Bob",
),
WcTransactionRequestInfoItemUM(
title = resourceReference(R.string.wc_common_wallet),
description = "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826",
),
),
),
),
),
actions = WcTransactionActionsUM(
onDismiss = {},
onBack = {},
activeButtonOnClick = {},
onCopy = {},
transactionRequestOnClick = {},
),
),
WcSignTransactionUM(
state = WcSignTransactionUM.State.TRANSACTION_REQUEST_INFO,
transaction = WcTransactionUM(
appName = "React App",
appIcon = "",
isVerified = true,
appSubtitle = "react-app.walletconnect.com",
walletName = "Tangem 2.0",
activeButtonText = resourceReference(R.string.common_sign),
networkInfo = WcNetworkInfoUM(name = "Ethereum", iconRes = R.drawable.img_eth_22),
addressText = "0x345FF...34FA",
networkFee = "~ 0.22 $",
),
transactionRequestInfo = WcTransactionRequestInfoUM(
blocks = persistentListOf(
WcTransactionRequestBlockUM(
persistentListOf(
WcTransactionRequestInfoItemUM(
title = resourceReference(R.string.wc_signature_type),
description = "personal_sign",
),
WcTransactionRequestInfoItemUM(
title = resourceReference(R.string.wc_contents),
description = "Hello! My name is John Dow. test@tange.com",
),
),
),
WcTransactionRequestBlockUM(
persistentListOf(
WcTransactionRequestInfoItemUM(
title = resourceReference(R.string.wc_transaction_info_to_title),
),
WcTransactionRequestInfoItemUM(
title = resourceReference(R.string.settings_wallet_name_title),
description = "Bob",
),
WcTransactionRequestInfoItemUM(
title = resourceReference(R.string.wc_common_wallet),
description = "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826",
),
),
),
),
),
actions = WcTransactionActionsUM(
onDismiss = {},
onBack = {},
activeButtonOnClick = {},
onCopy = {},
transactionRequestOnClick = {},
),
),
),
)

View file

@ -0,0 +1,196 @@
package com.tangem.features.walletconnect.transaction.ui.sign
import android.content.res.Configuration
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.HorizontalDivider
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.tooling.preview.Devices
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheet
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetTitle
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.features.walletconnect.connections.ui.WcAppInfoItem
import com.tangem.features.walletconnect.impl.R
import com.tangem.features.walletconnect.transaction.entity.*
import com.tangem.features.walletconnect.transaction.ui.common.*
import kotlinx.collections.immutable.persistentListOf
@Composable
internal fun WcSignTransactionModalBottomSheetContent(
state: WcSignTransactionUM,
onClickTransactionRequest: () -> Unit,
) {
Column(
modifier = Modifier
.padding(horizontal = TangemTheme.dimens.spacing16),
) {
Column(
modifier = Modifier
.clip(RoundedCornerShape(TangemTheme.dimens.radius14))
.background(color = TangemTheme.colors.background.action)
.fillMaxWidth()
.animateContentSize(),
) {
RequestFromItem()
WcAppInfoItem(
iconUrl = state.transaction.appIcon,
title = state.transaction.appName,
subtitle = state.transaction.appSubtitle,
isVerified = state.transaction.isVerified,
)
DividerWithPadding(start = 0.dp, end = 0.dp)
WcTransactionRequestItem(
iconRes = R.drawable.ic_doc_new_24,
modifier = Modifier
.fillMaxWidth()
.clickable { onClickTransactionRequest() }
.padding(TangemTheme.dimens.spacing12),
)
}
Column(modifier = Modifier.padding(top = TangemTheme.dimens.spacing16)) {
WcSignTransactionItems(state)
WcTransactionRequestButtons(
modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing16),
onDismiss = state.actions.onDismiss,
onSign = state.actions.onSign,
isLoading = state.transaction.isLoading,
)
}
}
}
@Composable
private fun WcSignTransactionItems(state: WcSignTransactionUM) {
Column(
modifier = Modifier
.clip(RoundedCornerShape(TangemTheme.dimens.radius14))
.background(color = TangemTheme.colors.background.action)
.fillMaxWidth()
.animateContentSize(),
) {
val itemsModifier = Modifier
.fillMaxWidth()
.padding(TangemTheme.dimens.spacing12)
DividerWithPadding(start = 0.dp, end = 0.dp)
WcWalletItem(
modifier = itemsModifier,
walletName = state.transaction.walletName,
)
DividerWithPadding(start = TangemTheme.dimens.spacing40, end = TangemTheme.dimens.spacing12)
WcNetworkItem(
modifier = itemsModifier,
networkInfo = state.transaction.networkInfo,
)
if (!state.transaction.addressText.isNullOrEmpty()) {
DividerWithPadding(start = TangemTheme.dimens.spacing40, end = TangemTheme.dimens.spacing12)
WcAddressItem(
modifier = itemsModifier,
addressText = state.transaction.addressText,
)
}
if (!state.transaction.networkFee.isNullOrEmpty()) {
DividerWithPadding(start = TangemTheme.dimens.spacing40, end = TangemTheme.dimens.spacing12)
WcNetworkFeeItem(
modifier = itemsModifier,
networkFeeText = state.transaction.networkFee,
)
}
}
}
@Composable
private fun DividerWithPadding(start: Dp, end: Dp) {
HorizontalDivider(
modifier = Modifier.padding(
start = start,
end = end,
),
thickness = TangemTheme.dimens.size1,
color = TangemTheme.colors.stroke.primary,
)
}
@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 WcSignTransactionBottomSheetPreview(
@PreviewParameter(WcSignTransactionStateProvider::class) state: WcSignTransactionUM,
) {
TangemThemePreview {
TangemModalBottomSheet<WcSignTransactionUM>(
config = TangemBottomSheetConfig(
isShown = true,
onDismissRequest = {},
content = state,
),
containerColor = TangemTheme.colors.background.tertiary,
title = {
TangemModalBottomSheetTitle(
title = resourceReference(R.string.wallet_connect_title),
endIconRes = R.drawable.ic_close_24,
onEndClick = {},
startIconRes = null,
onStartClick = {},
)
},
content = {
WcSignTransactionModalBottomSheetContent(state = state, onClickTransactionRequest = {})
},
)
}
}
private class WcSignTransactionStateProvider : CollectionPreviewParameterProvider<WcSignTransactionUM>(
listOf(
WcSignTransactionUM(
transaction = WcTransactionUM(
appName = "React App",
appIcon = "",
isVerified = true,
appSubtitle = "react-app.walletconnect.com",
walletName = "Tangem 2.0",
networkInfo = WcNetworkInfoUM(name = "Ethereum", iconRes = R.drawable.img_eth_22),
),
transactionRequestInfo = WcTransactionRequestInfoUM(persistentListOf()),
actions = WcTransactionActionsUM(
onDismiss = {},
onSign = {},
onCopy = {},
),
),
WcSignTransactionUM(
transaction = WcTransactionUM(
appName = "React App",
appIcon = "",
isVerified = true,
appSubtitle = "react-app.walletconnect.com",
walletName = "Tangem 2.0",
networkInfo = WcNetworkInfoUM(name = "Ethereum", iconRes = R.drawable.img_eth_22),
addressText = "0x345FF...34FA",
networkFee = "~ 0.22 $",
),
transactionRequestInfo = WcTransactionRequestInfoUM(persistentListOf()),
actions = WcTransactionActionsUM(
onDismiss = {},
onSign = {},
onCopy = {},
),
),
),
)

View file

@ -0,0 +1,7 @@
package com.tangem.features.walletconnect.transaction.utils
const val ADDRESS_FIRST_PART_LENGTH = 7
const val ADDRESS_SECOND_PART_LENGTH = 4
internal fun String.toShortAddressText() =
"${take(ADDRESS_FIRST_PART_LENGTH)}...${takeLast(ADDRESS_SECOND_PART_LENGTH)}"

View file

@ -11,13 +11,13 @@ import com.tangem.domain.walletconnect.usecase.method.WcSignState
import com.tangem.domain.walletconnect.usecase.method.WcSignStep
import com.tangem.domain.walletconnect.usecase.method.WcSignUseCase
import com.tangem.features.walletconnect.impl.R
import com.tangem.features.walletconnect.transaction.entity.common.WcNetworkInfoUM
import com.tangem.features.walletconnect.transaction.entity.sign.WcSignTransactionUM
import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionActionsUM
import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionRequestBlockUM
import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionRequestInfoItemUM
import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionRequestInfoUM
import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionUM
import com.tangem.features.walletconnect.transaction.entity.WcNetworkInfoUM
import com.tangem.features.walletconnect.transaction.entity.WcSignTransactionUM
import com.tangem.features.walletconnect.transaction.entity.WcTransactionActionsUM
import com.tangem.features.walletconnect.transaction.entity.WcTransactionRequestBlockUM
import com.tangem.features.walletconnect.transaction.entity.WcTransactionRequestInfoItemUM
import com.tangem.features.walletconnect.transaction.entity.WcTransactionRequestInfoUM
import com.tangem.features.walletconnect.transaction.entity.WcTransactionUM
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
@ -47,7 +47,6 @@ private fun WcMessageSignUseCase.signTypedDataToUM(
signModel: WcMessageSignUseCase.SignModel,
actions: WcTransactionActionsUM,
) = WcSignTransactionUM(
state = WcSignTransactionUM.State.TRANSACTION,
actions = actions,
transaction = WcTransactionUM(
appName = session.sdkModel.appMetaData.name,
@ -60,7 +59,6 @@ private fun WcMessageSignUseCase.signTypedDataToUM(
iconRes = getActiveIconRes(network.id.value),
),
addressText = walletAddress.toShortAddressText(),
activeButtonText = resourceReference(R.string.common_sign),
isLoading = signState.domainStep == WcSignStep.Signing,
),
transactionRequestInfo = WcTransactionRequestInfoUM(
@ -94,7 +92,6 @@ private fun WcMessageSignUseCase.messageSignToUM(
signModel: WcMessageSignUseCase.SignModel,
actions: WcTransactionActionsUM,
) = WcSignTransactionUM(
state = WcSignTransactionUM.State.TRANSACTION,
actions = actions,
transaction = WcTransactionUM(
appName = session.sdkModel.appMetaData.name,
@ -106,7 +103,6 @@ private fun WcMessageSignUseCase.messageSignToUM(
name = network.name,
iconRes = getActiveIconRes(network.id.value),
),
activeButtonText = resourceReference(R.string.common_sign),
isLoading = signState.domainStep == WcSignStep.Signing,
),
transactionRequestInfo = WcTransactionRequestInfoUM(