Updated on 2026-08-14

This commit is contained in:
Tangem 2025-05-19 16:51:21 +03:00
commit 4f5b134c99
16 changed files with 221 additions and 124 deletions

View file

@ -127,4 +127,13 @@ internal object NFTDomainModule {
fun provideGetWalletNFTEnabledUseCase(walletsRepository: WalletsRepository): GetWalletNFTEnabledUseCase {
return GetWalletNFTEnabledUseCase(walletsRepository)
}
@Provides
@Singleton
fun provideClearNFTCacheUseCase(
nftRepository: NFTRepository,
currenciesRepository: CurrenciesRepository,
): ObserveAndClearNFTCacheIfNeedUseCase {
return ObserveAndClearNFTCacheIfNeedUseCase(nftRepository, currenciesRepository)
}
}

View file

@ -14,12 +14,10 @@ import com.tangem.common.extensions.toHexString
import com.tangem.common.map
import com.tangem.common.timemeasure.RealtimeMonotonicTimeSource
import com.tangem.core.error.ext.tangemError
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
import com.tangem.datasource.local.visa.VisaAuthTokenStorage
import com.tangem.datasource.local.visa.VisaOTPStorage
import com.tangem.datasource.local.visa.VisaOtpData
import com.tangem.datasource.local.visa.hasSavedOTP
import com.tangem.domain.common.visa.VisaUtilities
import com.tangem.domain.common.visa.VisaWalletPublicKeyUtility
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.visa.error.VisaActivationError
@ -29,7 +27,6 @@ import com.tangem.domain.visa.repository.VisaActivationRepository
import com.tangem.domain.visa.repository.VisaAuthRepository
import com.tangem.operations.GenerateOTPCommand
import com.tangem.operations.attestation.AttestCardKeyCommand
import com.tangem.operations.derivation.DeriveWalletPublicKeyTask
import com.tangem.operations.pins.SetUserCodeCommand
import com.tangem.operations.sign.SignHashCommand
import com.tangem.operations.sign.SignHashResponse
@ -95,23 +92,7 @@ class VisaCardActivationTask @AssistedInject constructor(
context.signAuthorizationChallenge(mode.authorizationChallenge)
}
is VisaCardActivationTaskMode.SignOnly -> {
val wallet =
card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 }
?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
val derivedPublicKey = when (val deriveKeyResult = context.deriveKey(wallet.publicKey)) {
is CompletionResult.Failure -> {
return CompletionResult.Failure(deriveKeyResult.error)
}
is CompletionResult.Success -> {
deriveKeyResult.data
}
}
context.signData(
mode.dataToSignByCardWallet,
derivedPublicKey,
)
context.signData(mode.dataToSignByCardWallet)
}
}
}
@ -164,16 +145,7 @@ class VisaCardActivationTask @AssistedInject constructor(
card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 }
?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
val derivedPublicKey = when (val deriveKeyResult = deriveKey(wallet.publicKey)) {
is CompletionResult.Failure -> {
return CompletionResult.Failure(deriveKeyResult.error)
}
is CompletionResult.Success -> {
deriveKeyResult.data
}
}
val walletAddress = VisaWalletPublicKeyUtility.generateAddressOnSecp256k1(derivedPublicKey.publicKey)
val walletAddress = VisaWalletPublicKeyUtility.generateAddressOnSecp256k1(wallet.publicKey)
.getOrElse { return CompletionResult.Failure(it.tangemError) }
.value
@ -190,10 +162,7 @@ class VisaCardActivationTask @AssistedInject constructor(
otpTaskDeferred.await()
signData(
dataToSign = dataToSign,
derivedPublicKey = derivedPublicKey,
)
signData(dataToSign = dataToSign)
}
}
@ -294,7 +263,6 @@ class VisaCardActivationTask @AssistedInject constructor(
private suspend fun SessionContext.signData(
dataToSign: VisaDataToSignByCardWallet,
derivedPublicKey: ExtendedPublicKey,
): CompletionResult<VisaCardActivationResponse> {
val card =
session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
@ -306,7 +274,6 @@ class VisaCardActivationTask @AssistedInject constructor(
val task = SignHashCommand(
hash = dataToSign.hashToSign.hexToBytes(),
walletPublicKey = wallet.publicKey,
derivationPath = VisaUtilities.visaDefaultDerivationPath,
)
val timedResult = RealtimeMonotonicTimeSource.measureTimedValue {
@ -325,7 +292,7 @@ class VisaCardActivationTask @AssistedInject constructor(
handleSignedData(
dataToSign = dataToSign,
response = result.data,
derivedPublicKey = derivedPublicKey,
walletPublicKey = wallet.publicKey,
)
}
is CompletionResult.Failure -> {
@ -335,23 +302,9 @@ class VisaCardActivationTask @AssistedInject constructor(
}
}
private suspend fun SessionContext.deriveKey(publicKey: ByteArray): CompletionResult<ExtendedPublicKey> {
val derivationPath = VisaUtilities.visaDefaultDerivationPath
?: return CompletionResult.Failure(VisaActivationError.FailedToCreateAddress.tangemError)
val derivationTask = DeriveWalletPublicKeyTask(publicKey, derivationPath)
val derivationTaskResult = suspendCancellableCoroutine { continuation ->
derivationTask.run(session) { result ->
continuation.resume(result)
}
}
return derivationTaskResult
}
private suspend fun SessionContext.handleSignedData(
dataToSign: VisaDataToSignByCardWallet,
derivedPublicKey: ExtendedPublicKey,
walletPublicKey: ByteArray,
response: SignHashResponse,
): CompletionResult<VisaCardActivationResponse> {
val otp = otpStorage.getOTP(cardId) ?: run {
@ -362,7 +315,7 @@ class VisaCardActivationTask @AssistedInject constructor(
val rsvSignature = UnmarshalHelper.unmarshalSignatureExtended(
signature = response.signature,
hash = dataToSign.hashToSign.hexToBytes(),
publicKey = derivedPublicKey.publicKey.toDecompressedPublicKey(),
publicKey = walletPublicKey.toDecompressedPublicKey(),
).asRSVLegacyEVM().toHexString()
val signedActivationData = dataToSign.sign(

View file

@ -40,17 +40,12 @@ class VisaCustomerWalletApproveTask(
}
if (VisaUtilities.isVisaCard(card.firmwareVersion.doubleValue, card.batchId)) {
// TODO TVF-21
callback(CompletionResult.Failure(TangemSdkError.Underlying("Can't use Visa card for approve")))
callback(CompletionResult.Failure(VisaActivationError.VisaCardForApproval.tangemError))
return
}
if (visaDataForApprove.customerWalletCardId != null && card.cardId != visaDataForApprove.customerWalletCardId) {
callback(
CompletionResult.Failure(
TangemSdkError.Underlying("Use tangem wallet specified during visa registration"), // TODO TVF-21
),
)
callback(CompletionResult.Failure(VisaActivationError.CardIdNotMatched.tangemError))
return
}

View file

@ -2,17 +2,13 @@ package com.tangem.tap.domain.visa
import arrow.core.getOrElse
import com.tangem.common.CompletionResult
import com.tangem.common.card.CardWallet
import com.tangem.common.card.EllipticCurve
import com.tangem.common.core.CardSession
import com.tangem.common.core.TangemSdkError
import com.tangem.common.extensions.hexToBytes
import com.tangem.common.extensions.toHexString
import com.tangem.core.error.ext.tangemError
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
import com.tangem.datasource.local.visa.VisaAuthTokenStorage
import com.tangem.domain.common.visa.VisaUtilities
import com.tangem.domain.common.visa.VisaWalletPublicKeyUtility
import com.tangem.domain.visa.error.VisaActivationError
import com.tangem.domain.visa.error.VisaAuthorizationAPIError
@ -22,7 +18,6 @@ import com.tangem.domain.visa.repository.VisaActivationRepository
import com.tangem.domain.visa.repository.VisaAuthRepository
import com.tangem.operations.attestation.AttestCardKeyCommand
import com.tangem.operations.attestation.AttestCardKeyResponse
import com.tangem.operations.derivation.DeriveWalletPublicKeyTask
import com.tangem.operations.sign.SignHashCommand
import com.tangem.operations.sign.SignHashResponse
import kotlinx.coroutines.suspendCancellableCoroutine
@ -61,54 +56,19 @@ internal class VisaCardScanHandler @Inject constructor(
session = session,
)
val wallet = card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } ?: run {
card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } ?: run {
val activationInput =
VisaActivationInput(card.cardId, card.cardPublicKey.toHexString(), card.isAccessCodeSet)
val activationStatus = VisaCardActivationStatus.NotStartedActivation(activationInput)
return CompletionResult.Success(activationStatus)
}
return context.deriveKey(wallet)
}
private suspend fun SessionContext.deriveKey(wallet: CardWallet): CompletionResult<VisaCardActivationStatus> {
val derivationPath = VisaUtilities.visaDefaultDerivationPath ?: run {
Timber.e("Failed to create derivation path while first scan")
return CompletionResult.Failure(VisaCardScanError.FailedToCreateDerivationPath.tangemError)
}
val derivationTask = DeriveWalletPublicKeyTask(wallet.publicKey, derivationPath)
val derivationTaskResult = suspendCancellableCoroutine { continuation ->
derivationTask.run(session) { result ->
continuation.resume(result)
}
}
return handleDerivationResponse(derivationTaskResult)
}
private suspend fun SessionContext.handleDerivationResponse(
result: CompletionResult<ExtendedPublicKey>,
): CompletionResult<VisaCardActivationStatus> {
return when (result) {
is CompletionResult.Success -> {
Timber.i("Start task for loading challenge for Visa wallet")
handleWalletAuthorization()
}
is CompletionResult.Failure -> {
CompletionResult.Failure(result.error)
}
}
return context.handleWalletAuthorization()
}
private suspend fun SessionContext.handleWalletAuthorization(): CompletionResult<VisaCardActivationStatus> {
Timber.i("Started handling authorization using Visa wallet")
val derivationPath = VisaUtilities.visaDefaultDerivationPath ?: run {
Timber.e("Failed to create derivation path while handling wallet authorization")
return CompletionResult.Failure(VisaCardScanError.FailedToCreateDerivationPath.tangemError)
}
val card = session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
val wallet = card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } ?: run {
@ -116,12 +76,7 @@ internal class VisaCardScanHandler @Inject constructor(
return CompletionResult.Failure(VisaCardScanError.FailedToFindWallet.tangemError)
}
val extendedPublicKey = wallet.derivedKeys[derivationPath] ?: run {
Timber.e("Failed to find extended public key while handling wallet authorization")
return CompletionResult.Failure(VisaCardScanError.FailedToFindDerivedWalletKey.tangemError)
}
val walletAddress = VisaWalletPublicKeyUtility.generateAddressOnSecp256k1(extendedPublicKey.publicKey)
val walletAddress = VisaWalletPublicKeyUtility.generateAddressOnSecp256k1(wallet.publicKey)
.getOrElse {
return CompletionResult.Failure(it.tangemError)
}
@ -132,9 +87,7 @@ internal class VisaCardScanHandler @Inject constructor(
error("sign and get specific error to switch to card_id flow")
visaAuthRepository.getCardWalletAuthChallenge(cardWalletAddress = walletAddress.value)
}.getOrElse {
Timber.i(
"Failed to get Access token for Wallet public key authoziation. Authorizing using Card Pub key",
)
Timber.i("Failed to get Access token for Wallet public key authorization. Authorizing using Card Pub key")
return handleCardAuthorization(
cardWalletAddress = walletAddress.value,
)
@ -142,7 +95,6 @@ internal class VisaCardScanHandler @Inject constructor(
val signChallengeResult = signChallengeWithWallet(
publicKey = wallet.publicKey,
derivationPath = derivationPath,
nonce = challengeResponse.challenge,
)
@ -169,9 +121,7 @@ internal class VisaCardScanHandler @Inject constructor(
val authorizationTokensResponse = runCatching {
visaAuthRepository.getAccessTokens(signedChallenge = signedChallenge)
}.getOrElse {
Timber.i(
"Failed to get Access token for Wallet public key authoziation. Authorizing using Card Pub key",
)
Timber.i("Failed to get Access token for Wallet public key authorization. Authorizing using Card Pub key")
return handleCardAuthorization(
cardWalletAddress = cardWalletAddress,
)
@ -271,13 +221,11 @@ internal class VisaCardScanHandler @Inject constructor(
private suspend fun SessionContext.signChallengeWithWallet(
publicKey: ByteArray,
derivationPath: DerivationPath,
nonce: String,
): CompletionResult<SignHashResponse> {
val signHashCommand = SignHashCommand(
hash = nonce.hexToBytes(),
walletPublicKey = publicKey,
derivationPath = derivationPath,
)
return suspendCancellableCoroutine {
signHashCommand.run(session) { result ->

View file

@ -0,0 +1,38 @@
package com.tangem.domain.nft
import com.tangem.domain.nft.repository.NFTRepository
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.flow.*
class ObserveAndClearNFTCacheIfNeedUseCase(
private val nftRepository: NFTRepository,
private val currenciesRepository: CurrenciesRepository,
) {
operator fun invoke(userWalletId: UserWalletId): Flow<Set<Network>> = currenciesRepository
.getWalletCurrenciesUpdates(userWalletId)
.map { it.map(CryptoCurrency::network) }
.mapDiff { old, new ->
// calculate networks sets difference to determine which networks were removed
old.toSet() - new.toSet()
}
.distinctUntilChanged()
.onEach { removedNetworks ->
if (removedNetworks.isNotEmpty()) {
nftRepository.clearCache(userWalletId, removedNetworks.toList())
}
}
private fun <T, R> Flow<T>.mapDiff(diff: (old: T, new: T) -> R): Flow<R> = flow {
var previous: T? = null
collect { current ->
val prev = previous
if (prev != null) {
emit(diff(prev, current))
}
previous = current
}
}
}

View file

@ -45,6 +45,8 @@ enum class VisaActivationError(
AddressNotMatched(104003008),
InconsistentRemoteState(104003009),
FailedRemoteState(104003010),
VisaCardForApproval(104003011),
CardIdNotMatched(104003011),
}
object VisaAuthorizationAPIError : UniversalError {

View file

@ -55,7 +55,7 @@ internal fun NFTCollectionsEmpty(state: NFTCollectionsUM.Empty, modifier: Modifi
PrimaryButton(
modifier = Modifier
.padding(top = TangemTheme.dimens.spacing48)
.wrapContentWidth(),
.widthIn(min = TangemTheme.dimens.size158),
text = stringResourceSafe(R.string.nft_collections_receive),
onClick = state.onReceiveClick,
)

View file

@ -16,6 +16,7 @@ import com.tangem.core.deeplink.DeepLinksRegistry
import com.tangem.core.deeplink.global.ReferralDeepLink
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.nft.ObserveAndClearNFTCacheIfNeedUseCase
import com.tangem.domain.settings.*
import com.tangem.domain.tokens.FetchCurrencyStatusUseCase
import com.tangem.domain.tokens.RefreshMultiCurrencyWalletQuotesUseCase
@ -84,6 +85,7 @@ internal class WalletModel @Inject constructor(
private val fetchCurrencyStatusUseCase: FetchCurrencyStatusUseCase,
private val appRouter: AppRouter,
private val routingFeatureToggle: RoutingFeatureToggle,
private val observeAndClearNFTCacheIfNeedUseCase: ObserveAndClearNFTCacheIfNeedUseCase,
val screenLifecycleProvider: ScreenLifecycleProvider,
val innerWalletRouter: InnerWalletRouter,
) : Model() {
@ -94,6 +96,7 @@ internal class WalletModel @Inject constructor(
private val walletsUpdateJobHolder = JobHolder()
private val refreshWalletJobHolder = JobHolder()
private val expressStatusJobHolder = JobHolder()
private val clearNFTCacheJobHolder = JobHolder()
private var needToRefreshWallet = false
private var expressTxStatusTaskScheduler = SingleTaskScheduler<Unit>()
@ -224,6 +227,7 @@ internal class WalletModel @Inject constructor(
}
subscribeOnExpressTransactionsUpdates(selectedWallet)
subscribeToScreenBackgroundState(selectedWallet)
observeAndClearNFTCacheIfNeedUseCase(selectedWallet)
}
.flowOn(dispatchers.main)
.launchIn(modelScope)
@ -283,6 +287,13 @@ internal class WalletModel @Inject constructor(
)
}
private fun observeAndClearNFTCacheIfNeedUseCase(selectedWallet: UserWallet) {
observeAndClearNFTCacheIfNeedUseCase
.invoke(selectedWallet.walletId)
.launchIn(modelScope)
.saveIn(clearNFTCacheJobHolder)
}
private fun needToRefreshTimer() {
modelScope.launch {
delay(REFRESH_WALLET_BACKGROUND_TIMER_MILLIS)

View file

@ -8,6 +8,7 @@ import com.tangem.domain.nft.GetNFTCollectionsUseCase
import com.tangem.domain.promo.GetStoryContentUseCase
import com.tangem.domain.tokens.ApplyTokenListSortingUseCase
import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase
@ -47,6 +48,7 @@ internal class MultiWalletContentLoader(
private val deepLinksRegistry: DeepLinksRegistry,
private val nftFeatureToggles: NFTFeatureToggles,
private val walletsRepository: WalletsRepository,
private val currenciesRepository: CurrenciesRepository,
private val routingFeatureToggle: RoutingFeatureToggle,
) : WalletContentLoader(id = userWallet.walletId) {
@ -72,6 +74,7 @@ internal class MultiWalletContentLoader(
stateHolder = stateHolder,
walletsRepository = walletsRepository,
clickIntents = clickIntents,
currenciesRepository = currenciesRepository,
).let(::add)
}
MultiWalletWarningsSubscriber(

View file

@ -8,6 +8,7 @@ import com.tangem.domain.nft.GetNFTCollectionsUseCase
import com.tangem.domain.promo.GetStoryContentUseCase
import com.tangem.domain.tokens.ApplyTokenListSortingUseCase
import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase
@ -42,6 +43,7 @@ internal class MultiWalletContentLoaderFactory @Inject constructor(
private val walletsRepository: WalletsRepository,
private val getNFTCollectionsUseCase: GetNFTCollectionsUseCase,
private val routingFeatureToggle: RoutingFeatureToggle,
private val currenciesRepository: CurrenciesRepository,
) {
fun create(userWallet: UserWallet, clickIntents: WalletClickIntents): WalletContentLoader {
@ -65,6 +67,7 @@ internal class MultiWalletContentLoaderFactory @Inject constructor(
walletsRepository = walletsRepository,
getNFTCollectionsUseCase = getNFTCollectionsUseCase,
routingFeatureToggle = routingFeatureToggle,
currenciesRepository = currenciesRepository,
)
}
}

View file

@ -1,6 +1,7 @@
package com.tangem.feature.wallet.presentation.wallet.subscribers
import com.tangem.domain.nft.GetNFTCollectionsUseCase
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
@ -15,17 +16,21 @@ internal class WalletNFTListSubscriber(
private val userWallet: UserWallet,
private val stateHolder: WalletStateController,
private val walletsRepository: WalletsRepository,
private val currenciesRepository: CurrenciesRepository,
private val getNFTCollectionsUseCase: GetNFTCollectionsUseCase,
private val clickIntents: WalletClickIntents,
) : WalletSubscriber() {
@OptIn(ExperimentalCoroutinesApi::class)
override fun create(coroutineScope: CoroutineScope): Flow<*> = walletsRepository
.nftEnabledStatus(userWallet.walletId)
override fun create(coroutineScope: CoroutineScope): Flow<*> = combine(
walletsRepository.nftEnabledStatus(userWallet.walletId),
currenciesRepository.getWalletCurrenciesUpdates(userWallet.walletId),
) { nftEnabled, currencies -> nftEnabled to currencies }
.distinctUntilChanged()
.flatMapLatest { nftEnabled ->
// if NFT is enabled for this wallet, then start observing changes from store and apply transformer if need
if (nftEnabled) {
.flatMapLatest { (nftEnabled, currencies) ->
// if NFT is enabled for this wallet and there are currencies,
// then start observing changes from store and apply transformer if need
if (nftEnabled && currencies.isNotEmpty()) {
getNFTCollectionsUseCase(userWallet.walletId)
.shareIn(
scope = coroutineScope,

View file

@ -0,0 +1,6 @@
package com.tangem.features.walletconnect.transaction.entity.blockaid
internal data class TransactionCheckResultsUM(
val estimatedWalletChanges: WcEstimatedWalletChangesUM,
val notificationText: String? = null,
)

View file

@ -0,0 +1,75 @@
package com.tangem.features.walletconnect.transaction.ui.blockaid
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
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.PreviewParameterProvider
import androidx.compose.ui.unit.dp
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.blockaid.WcEstimatedWalletChangeUM
import com.tangem.features.walletconnect.transaction.entity.blockaid.WcEstimatedWalletChangesUM
import com.tangem.features.walletconnect.transaction.entity.blockaid.TransactionCheckResultsUM
import kotlinx.collections.immutable.persistentListOf
@Composable
internal fun TransactionCheckResultsItem(item: TransactionCheckResultsUM, modifier: Modifier = Modifier) {
Column(
modifier = modifier
.padding(12.dp)
.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(12.dp),
) {
if (item.notificationText != null) {
WcTransactionCheckErrorItem(item.notificationText)
}
WcEstimatedWalletChangesItem(item.estimatedWalletChanges)
}
}
@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 TransactionCheckResultsItemPreview(
@PreviewParameter(TransactionCheckResultsItemProvider::class) item: TransactionCheckResultsUM,
) {
TangemThemePreview {
Box(
modifier = Modifier
.background(TangemTheme.colors.background.tertiary),
) {
TransactionCheckResultsItem(item = item)
}
}
}
private class TransactionCheckResultsItemProvider : PreviewParameterProvider<TransactionCheckResultsUM> {
override val values = sequenceOf(
TransactionCheckResultsUM(
notificationText = "The transaction approves erc20 tokens to a known malicious address",
estimatedWalletChanges = WcEstimatedWalletChangesUM(
items = persistentListOf(
WcEstimatedWalletChangeUM(
iconRes = R.drawable.ic_send_new_24,
title = resourceReference(R.string.common_send),
description = "- 42 USDT",
tokenIconUrl = "https://tangem.com",
),
WcEstimatedWalletChangeUM(
iconRes = R.drawable.ic_receive_new_24,
title = resourceReference(R.string.common_receive),
description = "+ 1,131.46 MATIC",
tokenIconUrl = "https://tangem.com",
),
),
),
),
)
}

View file

@ -138,7 +138,7 @@ private class EstimatedWalletChangesPreviewProviderTwoItems :
tokenIconUrl = "https://tangem.com",
),
WcEstimatedWalletChangeUM(
iconRes = R.drawable.ic_recieve_new_24,
iconRes = R.drawable.ic_receive_new_24,
title = resourceReference(R.string.common_receive),
description = "+ 1,131.46 MATIC",
tokenIconUrl = "https://tangem.com",

View file

@ -0,0 +1,49 @@
package com.tangem.features.walletconnect.transaction.ui.blockaid
import android.content.res.Configuration
import androidx.compose.foundation.background
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 com.tangem.core.ui.components.notifications.Notification
import com.tangem.core.ui.components.notifications.NotificationConfig
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
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.impl.R
@Composable
internal fun WcTransactionCheckErrorItem(notificationText: String, modifier: Modifier = Modifier) {
Notification(
modifier = modifier
.fillMaxWidth(),
config = NotificationConfig(
title = resourceReference(R.string.wc_malicious_transaction),
subtitle = TextReference.Str(notificationText),
iconResId = R.drawable.ic_alert_circle_24,
),
containerColor = TangemColorPalette.Amaranth.copy(alpha = 0.1f),
titleColor = TangemTheme.colors.text.warning,
subtitleColor = TangemTheme.colors.text.primary1,
iconTint = TangemTheme.colors.icon.warning,
)
}
@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 WcTransactionCheckErrorItemPreview() {
TangemThemePreview {
Box(
modifier = Modifier
.background(TangemTheme.colors.background.tertiary),
) {
WcTransactionCheckErrorItem("The transaction approves erc20 tokens to a known malicious address")
}
}
}