Updated on 2026-08-14

This commit is contained in:
Tangem 2026-04-30 10:28:27 +02:00
parent 6a82ee1600
commit 229dc89bcb
28 changed files with 623 additions and 36 deletions

View file

@ -61,7 +61,7 @@ android {
} }
flavorDimensions += "services" flavorDimensions += "services"
productFlavors { productFlavors {
create("google") { create("google") {
dimension = "services" dimension = "services"
@ -73,6 +73,15 @@ android {
} }
} }
// `src/prodDi/` holds production DI bindings for interfaces with a `mocked` counterpart.
// Wired into every build type EXCEPT `mocked`, which supplies its own bindings from `src/mocked/`.
buildTypes.configureEach {
if (name != "mocked") {
sourceSets.named(name) {
java.srcDir("src/prodDi/java")
}
}
}
} }
configurations.all { configurations.all {

View file

@ -1,9 +1,7 @@
package com.tangem.tap.di.data package com.tangem.tap.di.data
import com.tangem.datasource.local.visa.TangemPayStorage
import com.tangem.datasource.local.visa.VisaAuthTokenStorage import com.tangem.datasource.local.visa.VisaAuthTokenStorage
import com.tangem.datasource.local.visa.VisaOTPStorage import com.tangem.datasource.local.visa.VisaOTPStorage
import com.tangem.tap.data.DefaultTangemPayStorage
import com.tangem.tap.data.DefaultVisaAuthTokenStorage import com.tangem.tap.data.DefaultVisaAuthTokenStorage
import com.tangem.tap.data.DefaultVisaOTPStorage import com.tangem.tap.data.DefaultVisaOTPStorage
import dagger.Binds import dagger.Binds
@ -23,8 +21,4 @@ internal interface VisaStorageModule {
@Binds @Binds
@Singleton @Singleton
fun bindVisaOTPStorage(impl: DefaultVisaOTPStorage): VisaOTPStorage fun bindVisaOTPStorage(impl: DefaultVisaOTPStorage): VisaOTPStorage
@Binds
@Singleton
fun bindTangemPayStorage(impl: DefaultTangemPayStorage): TangemPayStorage
} }

View file

@ -0,0 +1,30 @@
package com.tangem.tap.core.security
import com.tangem.datasource.api.common.config.ApiConfig
import com.tangem.datasource.api.common.config.ApiEnvironment
import com.tangem.datasource.api.common.config.managers.ApiConfigsManager
import com.tangem.security.DeviceSecurityInfoProvider
/** In MOCK env reports a clean device; otherwise delegates (DexProtector RTC flags emulators). */
internal class MockAwareDeviceSecurityInfoProvider(
private val real: DeviceSecurityInfoProvider,
private val apiConfigsManager: ApiConfigsManager,
) : DeviceSecurityInfoProvider {
private val isMockMode: Boolean
get() = apiConfigsManager
.getEnvironmentConfig(ApiConfig.ID.TangemPay)
.environment == ApiEnvironment.MOCK
override val isRooted: Boolean
get() = if (isMockMode) false else real.isRooted
override val isBootloaderUnlocked: Boolean
get() = if (isMockMode) false else real.isBootloaderUnlocked
override val isXposed: Boolean
get() = if (isMockMode) false else real.isXposed
override val isVulnerableToMediaTekExploit: Boolean
get() = if (isMockMode) false else real.isVulnerableToMediaTekExploit
}

View file

@ -0,0 +1,129 @@
package com.tangem.tap.data
import com.tangem.datasource.api.common.config.ApiConfig
import com.tangem.datasource.api.common.config.ApiEnvironment
import com.tangem.datasource.api.common.config.managers.ApiConfigsManager
import com.tangem.datasource.local.visa.TangemPayStorage
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.pay.TangemPayWithdrawState
import com.tangem.domain.visa.model.TangemPayAuthTokens
import javax.inject.Inject
import javax.inject.Singleton
private const val MOCK_CUSTOMER_WALLET_ADDRESS = "0x0000000000000000000000000000000000000002"
private const val MOCK_ACCESS_TOKEN = "mock-access-token"
private const val MOCK_REFRESH_TOKEN = "mock-refresh-token"
private const val MOCK_IDEMPOTENCY_KEY = "mock-idempotency-key"
private const val MOCK_TOKEN_EXPIRES_AT = 9_999_999_999L
/** In MOCK env returns synthetic auth tokens + customer wallet address; otherwise delegates. */
@Singleton
internal class MockAwareTangemPayStorage @Inject constructor(
private val real: DefaultTangemPayStorage,
private val apiConfigsManager: ApiConfigsManager,
) : TangemPayStorage {
private val isMockMode: Boolean
get() = apiConfigsManager
.getEnvironmentConfig(ApiConfig.ID.TangemPay)
.environment == ApiEnvironment.MOCK
override suspend fun storeCustomerWalletAddress(userWalletId: UserWalletId, customerWalletAddress: String) {
if (isMockMode) return
real.storeCustomerWalletAddress(userWalletId, customerWalletAddress)
}
override suspend fun getCustomerWalletAddress(userWalletId: UserWalletId): String? {
if (isMockMode) return MOCK_CUSTOMER_WALLET_ADDRESS
return real.getCustomerWalletAddress(userWalletId)
}
override suspend fun clearCustomerWalletAddress(userWalletId: UserWalletId) {
if (isMockMode) return
real.clearCustomerWalletAddress(userWalletId)
}
override suspend fun storeAuthTokens(customerWalletAddress: String, tokens: TangemPayAuthTokens) {
if (isMockMode) return
real.storeAuthTokens(customerWalletAddress, tokens)
}
override suspend fun getAuthTokens(customerWalletAddress: String): TangemPayAuthTokens? {
if (isMockMode) {
return TangemPayAuthTokens(
accessToken = MOCK_ACCESS_TOKEN,
expiresAt = MOCK_TOKEN_EXPIRES_AT,
refreshToken = MOCK_REFRESH_TOKEN,
refreshExpiresAt = MOCK_TOKEN_EXPIRES_AT,
idempotencyKey = MOCK_IDEMPOTENCY_KEY,
)
}
return real.getAuthTokens(customerWalletAddress)
}
override suspend fun clearAuthTokens(customerWalletAddress: String) {
if (isMockMode) return
real.clearAuthTokens(customerWalletAddress)
}
override suspend fun storeOrderId(customerWalletAddress: String, orderId: String) =
real.storeOrderId(customerWalletAddress, orderId)
override suspend fun getOrderId(customerWalletAddress: String): String? =
real.getOrderId(customerWalletAddress)
override suspend fun getAddToWalletDone(customerWalletAddress: String): Boolean =
real.getAddToWalletDone(customerWalletAddress)
override suspend fun storeAddToWalletDone(customerWalletAddress: String, isDone: Boolean) =
real.storeAddToWalletDone(customerWalletAddress, isDone)
override suspend fun clearOrderId(customerWalletAddress: String) =
real.clearOrderId(customerWalletAddress)
override suspend fun storeCheckCustomerWalletResult(userWalletId: UserWalletId, isPaeraCustomer: Boolean) =
real.storeCheckCustomerWalletResult(userWalletId, isPaeraCustomer)
override suspend fun checkCustomerWalletResult(userWalletId: UserWalletId): Boolean? {
if (isMockMode) return true
return real.checkCustomerWalletResult(userWalletId)
}
override suspend fun storeActiveWithdrawOrderId(userWalletId: UserWalletId, orderId: String) =
real.storeActiveWithdrawOrderId(userWalletId, orderId)
override suspend fun storeWithdrawOrder(userWalletId: UserWalletId, data: TangemPayWithdrawState) =
real.storeWithdrawOrder(userWalletId, data)
override suspend fun getActiveWithdrawOrderId(userWalletId: UserWalletId): String? =
real.getActiveWithdrawOrderId(userWalletId)
override suspend fun getWithdrawOrders(userWalletId: UserWalletId): List<TangemPayWithdrawState>? =
real.getWithdrawOrders(userWalletId)
override suspend fun deleteActiveWithdrawOrder(userWalletId: UserWalletId) =
real.deleteActiveWithdrawOrder(userWalletId)
override suspend fun deleteWithdrawOrder(userWalletId: UserWalletId, orderId: String) =
real.deleteWithdrawOrder(userWalletId, orderId)
override suspend fun storeHideOnboardingBanner(userWalletId: UserWalletId, hide: Boolean) =
real.storeHideOnboardingBanner(userWalletId, hide)
override suspend fun getHideMainOnboardingBanner(userWalletId: UserWalletId): Boolean =
real.getHideMainOnboardingBanner(userWalletId)
override suspend fun storeTangemPayEligibility(eligibility: Set<String>) =
real.storeTangemPayEligibility(eligibility)
override suspend fun getTangemPayEligibility(): Set<String> = real.getTangemPayEligibility()
override suspend fun storeIsTangemPayDeactivated(userWalletId: UserWalletId) =
real.storeIsTangemPayDeactivated(userWalletId)
override suspend fun isTangemPayDeactivated(userWalletId: UserWalletId): Boolean =
real.isTangemPayDeactivated(userWalletId)
override suspend fun clearAll(userWalletId: UserWalletId, customerWalletAddress: String) =
real.clearAll(userWalletId, customerWalletAddress)
}

View file

@ -0,0 +1,25 @@
package com.tangem.tap.di.core.security
import com.tangem.datasource.api.common.config.managers.ApiConfigsManager
import com.tangem.security.DeviceSecurityInfoProvider
import com.tangem.tap.core.security.DefaultDeviceSecurityInfoProvider
import com.tangem.tap.core.security.MockAwareDeviceSecurityInfoProvider
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal object SecurityMockedModule {
@Provides
@Singleton
fun provideDeviceSecurityInfoProvider(
apiConfigsManager: ApiConfigsManager,
): DeviceSecurityInfoProvider {
val real = DefaultDeviceSecurityInfoProvider()
return MockAwareDeviceSecurityInfoProvider(real = real, apiConfigsManager = apiConfigsManager)
}
}

View file

@ -0,0 +1,18 @@
package com.tangem.tap.di.data
import com.tangem.datasource.local.visa.TangemPayStorage
import com.tangem.tap.data.MockAwareTangemPayStorage
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal interface TangemPayStorageMockedModule {
@Binds
@Singleton
fun bindTangemPayStorage(impl: MockAwareTangemPayStorage): TangemPayStorage
}

View file

@ -10,7 +10,7 @@ import javax.inject.Singleton
@Module @Module
@InstallIn(SingletonComponent::class) @InstallIn(SingletonComponent::class)
internal object SecurityModule { internal object SecurityProductionModule {
@Provides @Provides
@Singleton @Singleton

View file

@ -0,0 +1,18 @@
package com.tangem.tap.di.data
import com.tangem.datasource.local.visa.TangemPayStorage
import com.tangem.tap.data.DefaultTangemPayStorage
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal interface TangemPayStorageProductionModule {
@Binds
@Singleton
fun bindTangemPayStorage(impl: DefaultTangemPayStorage): TangemPayStorage
}

View file

@ -216,7 +216,15 @@ private fun ButtonsContainer(
} ?: TangemButtonIconPosition.None } ?: TangemButtonIconPosition.None
TangemButton( TangemButton(
modifier = Modifier.fillMaxWidth(), modifier = Modifier
.fillMaxWidth()
.testTag(
if (button.isPrimary) {
WarningBottomSheetTestTags.BUTTON_PRIMARY
} else {
WarningBottomSheetTestTags.BUTTON_SECONDARY
},
),
text = button.text?.resolveReference().orEmpty(), text = button.text?.resolveReference().orEmpty(),
icon = icon, icon = icon,
onClick = { button.onClick?.invoke(closeScope) }, onClick = { button.onClick?.invoke(closeScope) },

View file

@ -0,0 +1,5 @@
package com.tangem.core.ui.test
object HotWalletAccessCodeTestTags {
const val ACCESS_CODE_INPUT = "HOT_WALLET_ACCESS_CODE_INPUT"
}

View file

@ -0,0 +1,39 @@
package com.tangem.core.ui.test
object TangemPayTestTags {
// Main wallet screen tile (entry point into Tangem Pay)
const val MAIN_SCREEN_TILE = "TANGEM_PAY_MAIN_SCREEN_TILE"
// Payment account details screen
const val PAYMENT_ACCOUNT_BALANCE = "TANGEM_PAY_PAYMENT_ACCOUNT_BALANCE"
const val PAYMENT_ACCOUNT_CARD_BUTTON = "TANGEM_PAY_PAYMENT_ACCOUNT_CARD_BUTTON"
// Card details (reveal + copy)
const val CARD_DETAILS_SHOW_BUTTON = "TANGEM_PAY_CARD_DETAILS_SHOW_BUTTON"
const val CARD_DETAILS_HIDE_BUTTON = "TANGEM_PAY_CARD_DETAILS_HIDE_BUTTON"
const val CARD_DETAILS_NUMBER_VALUE = "TANGEM_PAY_CARD_DETAILS_NUMBER_VALUE"
const val CARD_DETAILS_EXPIRATION_VALUE = "TANGEM_PAY_CARD_DETAILS_EXPIRATION_VALUE"
const val CARD_DETAILS_CVC_VALUE = "TANGEM_PAY_CARD_DETAILS_CVC_VALUE"
const val CARD_DETAILS_COPY_NUMBER = "TANGEM_PAY_CARD_DETAILS_COPY_NUMBER"
const val CARD_DETAILS_COPY_EXPIRATION = "TANGEM_PAY_CARD_DETAILS_COPY_EXPIRATION"
const val CARD_DETAILS_COPY_CVC = "TANGEM_PAY_CARD_DETAILS_COPY_CVC"
// Card management (card page settings)
const val CHANGE_PIN_ROW = "TANGEM_PAY_CHANGE_PIN_ROW"
const val FREEZE_CARD_ROW = "TANGEM_PAY_FREEZE_CARD_ROW"
// Freeze confirmation bottom sheet
const val FREEZE_CONFIRMATION_SUBMIT_BUTTON = "TANGEM_PAY_FREEZE_CONFIRMATION_SUBMIT_BUTTON"
// PIN entry screen
const val PIN_SCREEN_TITLE = "TANGEM_PAY_PIN_SCREEN_TITLE"
const val PIN_SCREEN_DESCRIPTION = "TANGEM_PAY_PIN_SCREEN_DESCRIPTION"
const val PIN_INPUT_FIELD = "TANGEM_PAY_PIN_INPUT_FIELD"
const val PIN_SUBMIT_BUTTON = "TANGEM_PAY_PIN_SUBMIT_BUTTON"
const val PIN_ERROR_MESSAGE = "TANGEM_PAY_PIN_ERROR_MESSAGE"
// PIN success screen
const val PIN_SUCCESS_TITLE = "TANGEM_PAY_PIN_SUCCESS_TITLE"
const val PIN_SUCCESS_DESCRIPTION = "TANGEM_PAY_PIN_SUCCESS_DESCRIPTION"
const val PIN_DONE_BUTTON = "TANGEM_PAY_PIN_DONE_BUTTON"
}

View file

@ -4,4 +4,6 @@ object WarningBottomSheetTestTags {
const val ICON = "BASE_WARNING_BOTTOM_SHEET_ICON" const val ICON = "BASE_WARNING_BOTTOM_SHEET_ICON"
const val TITLE = "BASE_WARNING_BOTTOM_SHEET_TITLE" const val TITLE = "BASE_WARNING_BOTTOM_SHEET_TITLE"
const val MESSAGE = "BASE_WARNING_BOTTOM_SHEET_MESSAGE" const val MESSAGE = "BASE_WARNING_BOTTOM_SHEET_MESSAGE"
const val BUTTON_PRIMARY = "BASE_WARNING_BOTTOM_SHEET_BUTTON_PRIMARY"
const val BUTTON_SECONDARY = "BASE_WARNING_BOTTOM_SHEET_BUTTON_SECONDARY"
} }

View file

@ -10,6 +10,16 @@ plugins {
android { android {
namespace = "com.tangem.data.visa" namespace = "com.tangem.data.visa"
// `src/prodDi/` holds production DI bindings for TangemPay repos with a `mocked` counterpart.
// Wired into every build type EXCEPT `mocked`, which supplies its own bindings from `src/mocked/`.
buildTypes.configureEach {
if (name != "mocked") {
sourceSets.named(name) {
java.srcDir("src/prodDi/kotlin")
}
}
}
} }
tasks.withType<Test>().configureEach { tasks.withType<Test>().configureEach {

View file

@ -51,18 +51,10 @@ internal interface TangemPayDataModule {
@Singleton @Singleton
fun bindKycRepository(repository: DefaultKycRepository): KycRepository fun bindKycRepository(repository: DefaultKycRepository): KycRepository
@Binds
@Singleton
fun bindOnboardingRepository(repository: DefaultOnboardingRepository): OnboardingRepository
@Binds @Binds
@Singleton @Singleton
fun bindTangemPayTxHistoryRepository(repository: DefaultTangemPayTxHistoryRepository): TangemPayTxHistoryRepository fun bindTangemPayTxHistoryRepository(repository: DefaultTangemPayTxHistoryRepository): TangemPayTxHistoryRepository
@Binds
@Singleton
fun bindCardDetailsRepository(repository: DefaultTangemPayCardDetailsRepository): TangemPayCardDetailsRepository
@Binds @Binds
@Singleton @Singleton
fun bindTangemPaySwapRepository(repository: DefaultTangemPayWithdrawRepository): TangemPayWithdrawRepository fun bindTangemPaySwapRepository(repository: DefaultTangemPayWithdrawRepository): TangemPayWithdrawRepository

View file

@ -0,0 +1,24 @@
package com.tangem.data.pay.di
import com.tangem.data.pay.repository.MockAwareOnboardingRepository
import com.tangem.data.pay.repository.MockAwareTangemPayCardDetailsRepository
import com.tangem.domain.pay.repository.OnboardingRepository
import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal interface TangemPayDataMockedModule {
@Binds
@Singleton
fun bindOnboardingRepository(repository: MockAwareOnboardingRepository): OnboardingRepository
@Binds
@Singleton
fun bindCardDetailsRepository(repository: MockAwareTangemPayCardDetailsRepository): TangemPayCardDetailsRepository
}

View file

@ -0,0 +1,110 @@
package com.tangem.data.pay.repository
import arrow.core.Either
import arrow.core.right
import com.tangem.core.error.UniversalError
import com.tangem.datasource.api.common.config.ApiConfig
import com.tangem.datasource.api.common.config.ApiEnvironment
import com.tangem.datasource.api.common.config.managers.ApiConfigsManager
import com.tangem.domain.models.pay.TangemPayEligibilityType
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.pay.model.CustomerInfo
import com.tangem.domain.pay.repository.OnboardingRepository
import com.tangem.domain.visa.error.VisaApiError
import java.util.concurrent.ConcurrentHashMap
import javax.inject.Inject
import javax.inject.Singleton
/** In MOCK env skips local-storage / signing enrollment; server calls go to WireMock. */
@Singleton
internal class MockAwareOnboardingRepository @Inject constructor(
private val real: DefaultOnboardingRepository,
private val apiConfigsManager: ApiConfigsManager,
) : OnboardingRepository {
private val mockOrderIds: MutableSet<UserWalletId> = ConcurrentHashMap.newKeySet()
private val isMockMode: Boolean
get() = apiConfigsManager
.getEnvironmentConfig(ApiConfig.ID.TangemPay)
.environment == ApiEnvironment.MOCK
override suspend fun validateDeeplink(link: String): Either<UniversalError, Boolean> {
if (isMockMode) return true.right()
return real.validateDeeplink(link)
}
override suspend fun isTangemPayInitialDataProduced(userWalletId: UserWalletId): Boolean {
if (isMockMode) return true
return real.isTangemPayInitialDataProduced(userWalletId)
}
override suspend fun produceInitialData(userWalletId: UserWalletId) {
if (isMockMode) return
real.produceInitialData(userWalletId)
}
override suspend fun getCustomerInfo(userWalletId: UserWalletId): Either<VisaApiError, CustomerInfo> =
real.getCustomerInfo(userWalletId)
override suspend fun createOrder(userWalletId: UserWalletId): Either<VisaApiError, String> {
if (isMockMode) {
mockOrderIds.add(userWalletId)
return MOCK_ORDER_ID.right()
}
return real.createOrder(userWalletId)
}
override suspend fun clearOrderId(userWalletId: UserWalletId) {
if (isMockMode) {
mockOrderIds.remove(userWalletId)
return
}
real.clearOrderId(userWalletId)
}
override suspend fun getOrderId(userWalletId: UserWalletId): String? {
if (isMockMode) return MOCK_ORDER_ID.takeIf { userWalletId in mockOrderIds }
return real.getOrderId(userWalletId)
}
override suspend fun hasTangemPayInWallet(userWalletId: UserWalletId): Either<VisaApiError, Boolean> =
real.hasTangemPayInWallet(userWalletId)
override suspend fun checkCustomerEligibility(): List<TangemPayEligibilityType> {
if (isMockMode) return listOf(TangemPayEligibilityType.DETAILS)
return real.checkCustomerEligibility()
}
override suspend fun getCustomerEligibility(): List<TangemPayEligibilityType> {
if (isMockMode) return listOf(TangemPayEligibilityType.DETAILS)
return real.getCustomerEligibility()
}
override fun getSavedCustomerInfo(userWalletId: UserWalletId): CustomerInfo? =
real.getSavedCustomerInfo(userWalletId)
override suspend fun getHideMainOnboardingBanner(userWalletId: UserWalletId): Boolean {
if (isMockMode) return false
return real.getHideMainOnboardingBanner(userWalletId)
}
override suspend fun setHideMainOnboardingBanner(userWalletId: UserWalletId) {
if (isMockMode) return
real.setHideMainOnboardingBanner(userWalletId)
}
override suspend fun disableTangemPay(userWalletId: UserWalletId): Either<VisaApiError, Unit> {
if (isMockMode) return Unit.right()
return real.disableTangemPay(userWalletId)
}
override suspend fun isTangemPayDeactivated(userWalletId: UserWalletId): Boolean {
if (isMockMode) return false
return real.isTangemPayDeactivated(userWalletId)
}
private companion object {
const val MOCK_ORDER_ID = "mock-order-id"
}
}

View file

@ -0,0 +1,100 @@
package com.tangem.data.pay.repository
import arrow.core.Either
import arrow.core.right
import com.tangem.core.error.UniversalError
import com.tangem.datasource.api.common.config.ApiConfig
import com.tangem.datasource.api.common.config.ApiEnvironment
import com.tangem.datasource.api.common.config.managers.ApiConfigsManager
import com.tangem.domain.models.account.CardDisplayName
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.pay.model.SetPinResult
import com.tangem.domain.pay.model.TangemPayCardBalance
import com.tangem.domain.pay.model.TangemPayCardDetails
import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository
import com.tangem.domain.visa.model.TangemPayCardFrozenState
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject
import javax.inject.Singleton
/** In MOCK env short-circuits RSA-encrypted flows (reveal/getPin/setPin) with hardcoded values. */
@Singleton
internal class MockAwareTangemPayCardDetailsRepository @Inject constructor(
private val real: DefaultTangemPayCardDetailsRepository,
private val apiConfigsManager: ApiConfigsManager,
) : TangemPayCardDetailsRepository {
private val isMockMode: Boolean
get() = apiConfigsManager
.getEnvironmentConfig(ApiConfig.ID.TangemPay)
.environment == ApiEnvironment.MOCK
override suspend fun getCardBalance(userWalletId: UserWalletId): Either<UniversalError, TangemPayCardBalance> =
real.getCardBalance(userWalletId)
override suspend fun revealCardDetails(
userWalletId: UserWalletId,
): Either<UniversalError, TangemPayCardDetails> {
if (isMockMode) {
return TangemPayCardDetails(
pan = MOCK_PAN,
cvv = MOCK_CVV,
expirationYear = MOCK_EXPIRATION_YEAR,
expirationMonth = MOCK_EXPIRATION_MONTH,
).right()
}
return real.revealCardDetails(userWalletId)
}
override suspend fun getPin(userWalletId: UserWalletId, cardId: String): Either<UniversalError, String?> {
if (isMockMode) return MOCK_PIN.right()
return real.getPin(userWalletId, cardId)
}
override suspend fun setPin(userWalletId: UserWalletId, pin: String): Either<UniversalError, SetPinResult> {
if (isMockMode) return SetPinResult.SUCCESS.right()
return real.setPin(userWalletId, pin)
}
override suspend fun isAddToWalletDone(userWalletId: UserWalletId): Either<UniversalError, Boolean> =
real.isAddToWalletDone(userWalletId)
override suspend fun setAddToWalletAsDone(userWalletId: UserWalletId): Either<UniversalError, Unit> =
real.setAddToWalletAsDone(userWalletId)
override suspend fun freezeCard(
userWalletId: UserWalletId,
cardId: String,
): Either<UniversalError, TangemPayCardFrozenState> = real.freezeCard(userWalletId, cardId)
override suspend fun unfreezeCard(
userWalletId: UserWalletId,
cardId: String,
): Either<UniversalError, TangemPayCardFrozenState> = real.unfreezeCard(userWalletId, cardId)
override fun cardFrozenState(cardId: String): Flow<TangemPayCardFrozenState> =
real.cardFrozenState(cardId)
override suspend fun cardFrozenStateSync(cardId: String): TangemPayCardFrozenState? =
real.cardFrozenStateSync(cardId)
override suspend fun updateCardDisplayName(
cardId: String,
userWalletId: UserWalletId,
displayName: CardDisplayName,
): Either<UniversalError, Unit> = real.updateCardDisplayName(cardId, userWalletId, displayName)
override suspend fun updateCardLimit(
cardId: String,
userWalletId: UserWalletId,
limit: String,
): Either<UniversalError, Unit> = real.updateCardLimit(cardId, userWalletId, limit)
private companion object {
const val MOCK_PAN = "4242 4242 4242 4242"
const val MOCK_CVV = "123"
const val MOCK_EXPIRATION_YEAR = "2028"
const val MOCK_EXPIRATION_MONTH = "12"
const val MOCK_PIN = "1234"
}
}

View file

@ -0,0 +1,24 @@
package com.tangem.data.pay.di
import com.tangem.data.pay.repository.DefaultOnboardingRepository
import com.tangem.data.pay.repository.DefaultTangemPayCardDetailsRepository
import com.tangem.domain.pay.repository.OnboardingRepository
import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal interface TangemPayDataProductionModule {
@Binds
@Singleton
fun bindOnboardingRepository(repository: DefaultOnboardingRepository): OnboardingRepository
@Binds
@Singleton
fun bindCardDetailsRepository(repository: DefaultTangemPayCardDetailsRepository): TangemPayCardDetailsRepository
}

View file

@ -11,6 +11,7 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@ -22,6 +23,7 @@ import com.tangem.core.ui.haptic.TangemHapticEffect
import com.tangem.core.ui.res.LocalHapticManager import com.tangem.core.ui.res.LocalHapticManager
import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.HotWalletAccessCodeTestTags
import com.tangem.features.hotwallet.accesscode.entity.AccessCodeUM import com.tangem.features.hotwallet.accesscode.entity.AccessCodeUM
import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
@ -109,6 +111,7 @@ internal fun AccessCode(
pinTextColor = state.accessCodeColor, pinTextColor = state.accessCodeColor,
onValueChange = state.onAccessCodeChange, onValueChange = state.onAccessCodeChange,
focusRequester = focusRequester, focusRequester = focusRequester,
modifier = Modifier.testTag(HotWalletAccessCodeTestTags.ACCESS_CODE_INPUT),
) )
} }
} }

View file

@ -36,5 +36,6 @@ internal data class TangemPayCardPageUM(
@Immutable @Immutable
internal data class TangemPayCardPageSetting( internal data class TangemPayCardPageSetting(
val title: TextReference, val title: TextReference,
val testTag: String? = null,
val onSettingClick: () -> Unit, val onSettingClick: () -> Unit,
) )

View file

@ -117,10 +117,12 @@ internal class TangemPayCardPageModel @Inject constructor(
TangemPayCardPageSetting( TangemPayCardPageSetting(
title = TextReference.Res(R.string.tangempay_card_details_change_pin), title = TextReference.Res(R.string.tangempay_card_details_change_pin),
onSettingClick = { onClickChangePIN(card.hasPinCode) }, onSettingClick = { onClickChangePIN(card.hasPinCode) },
testTag = com.tangem.core.ui.test.TangemPayTestTags.CHANGE_PIN_ROW,
), ),
TangemPayCardPageSetting( TangemPayCardPageSetting(
title = TextReference.Res(R.string.tangempay_card_details_freeze_card), title = TextReference.Res(R.string.tangempay_card_details_freeze_card),
onSettingClick = { onClickFreezeOrUnfreezeCard(card.isFrozen) }, onSettingClick = { onClickFreezeOrUnfreezeCard(card.isFrozen) },
testTag = com.tangem.core.ui.test.TangemPayTestTags.FREEZE_CARD_ROW,
), ),
TangemPayCardPageSetting( TangemPayCardPageSetting(
title = TextReference.Res(R.string.tangempay_card_details_reissue_card), title = TextReference.Res(R.string.tangempay_card_details_reissue_card),

View file

@ -29,6 +29,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.TextRange import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.ImeAction
@ -49,6 +50,7 @@ import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.TangemPayTestTags
import com.tangem.domain.models.account.CardDisplayName import com.tangem.domain.models.account.CardDisplayName
import com.tangem.domain.visa.model.TangemPayCardFrozenState import com.tangem.domain.visa.model.TangemPayCardFrozenState
import com.tangem.features.tangempay.details.impl.R import com.tangem.features.tangempay.details.impl.R
@ -209,10 +211,12 @@ private fun TangemPayCardDetailsHiddenBlock(state: TangemPayCardDetailsUM, modif
} }
TangemPayCardDetailsCustomButton( TangemPayCardDetailsCustomButton(
modifier = Modifier.constrainAs(buttonRef) { modifier = Modifier
end.linkTo(parent.end) .constrainAs(buttonRef) {
bottom.linkTo(parent.bottom) end.linkTo(parent.end)
}, bottom.linkTo(parent.bottom)
}
.testTag(TangemPayTestTags.CARD_DETAILS_SHOW_BUTTON),
text = stringResourceSafe(id = R.string.tangempay_card_details_show_details), text = stringResourceSafe(id = R.string.tangempay_card_details_show_details),
onClick = state.onClick, onClick = state.onClick,
showProgress = state.isLoading, showProgress = state.isLoading,
@ -336,6 +340,8 @@ private fun TangemPayCardDetailsShownBlock(
title = stringResourceSafe(R.string.tangempay_card_details_card_number), title = stringResourceSafe(R.string.tangempay_card_details_card_number),
text = cardNumber, text = cardNumber,
onCopy = onCopyCardNumber, onCopy = onCopyCardNumber,
valueTestTag = TangemPayTestTags.CARD_DETAILS_NUMBER_VALUE,
copyTestTag = TangemPayTestTags.CARD_DETAILS_COPY_NUMBER,
) )
Row( Row(
modifier = Modifier modifier = Modifier
@ -350,6 +356,8 @@ private fun TangemPayCardDetailsShownBlock(
title = stringResourceSafe(R.string.tangempay_card_details_expiry), title = stringResourceSafe(R.string.tangempay_card_details_expiry),
text = expiry, text = expiry,
onCopy = onCopyExpiry, onCopy = onCopyExpiry,
valueTestTag = TangemPayTestTags.CARD_DETAILS_EXPIRATION_VALUE,
copyTestTag = TangemPayTestTags.CARD_DETAILS_COPY_EXPIRATION,
) )
CardDetailsTextContainer( CardDetailsTextContainer(
modifier = Modifier modifier = Modifier
@ -358,13 +366,17 @@ private fun TangemPayCardDetailsShownBlock(
title = stringResourceSafe(R.string.tangempay_card_details_cvc), title = stringResourceSafe(R.string.tangempay_card_details_cvc),
text = cvv, text = cvv,
onCopy = onCopyCvv, onCopy = onCopyCvv,
valueTestTag = TangemPayTestTags.CARD_DETAILS_CVC_VALUE,
copyTestTag = TangemPayTestTags.CARD_DETAILS_COPY_CVC,
) )
} }
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
Row { Row {
SpacerWMax() SpacerWMax()
TangemPayCardDetailsCustomButton( TangemPayCardDetailsCustomButton(
modifier = Modifier.padding(end = 16.dp, bottom = 8.dp), modifier = Modifier
.padding(end = 16.dp, bottom = 8.dp)
.testTag(TangemPayTestTags.CARD_DETAILS_HIDE_BUTTON),
text = stringResourceSafe(id = R.string.tangempay_card_details_hide_details), text = stringResourceSafe(id = R.string.tangempay_card_details_hide_details),
onClick = onHideDetails, onClick = onHideDetails,
showProgress = false, showProgress = false,
@ -374,7 +386,14 @@ private fun TangemPayCardDetailsShownBlock(
} }
@Composable @Composable
private fun CardDetailsTextContainer(title: String, text: String, onCopy: () -> Unit, modifier: Modifier = Modifier) { private fun CardDetailsTextContainer(
title: String,
text: String,
onCopy: () -> Unit,
modifier: Modifier = Modifier,
valueTestTag: String? = null,
copyTestTag: String? = null,
) {
Row( Row(
modifier = modifier modifier = modifier
.background( .background(
@ -395,10 +414,13 @@ private fun CardDetailsTextContainer(title: String, text: String, onCopy: () ->
text = text, text = text,
style = TangemTheme.typography.body2, style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.constantWhite, color = TangemTheme.colors.text.constantWhite,
modifier = if (valueTestTag != null) Modifier.testTag(valueTestTag) else Modifier,
) )
} }
IconButton( IconButton(
modifier = Modifier.size(TangemTheme.dimens.size32), modifier = Modifier
.size(TangemTheme.dimens.size32)
.then(if (copyTestTag != null) Modifier.testTag(copyTestTag) else Modifier),
onClick = onCopy, onClick = onCopy,
) { ) {
Icon( Icon(

View file

@ -31,6 +31,7 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.util.fastForEach import androidx.compose.ui.util.fastForEach
import com.tangem.core.ui.components.appbar.AppBarWithBackButton import com.tangem.core.ui.components.appbar.AppBarWithBackButton
@ -169,7 +170,8 @@ private fun TangemPayCardPageSettingRow(
modifier = modifier modifier = modifier
.fillMaxWidth() .fillMaxWidth()
.clickable(onClick = onClick) .clickable(onClick = onClick)
.padding(TangemTheme.dimens.spacing12), .padding(TangemTheme.dimens.spacing12)
.then(if (item.testTag != null) Modifier.testTag(item.testTag) else Modifier),
contentAlignment = Alignment.CenterStart, contentAlignment = Alignment.CenterStart,
) { ) {
Text( Text(

View file

@ -8,6 +8,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.vectorResource import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
@ -23,6 +24,7 @@ import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.TangemPayTestTags
import com.tangem.features.tangempay.details.impl.R import com.tangem.features.tangempay.details.impl.R
@Composable @Composable
@ -43,7 +45,7 @@ internal fun TangemPayChangePinCodeSuccessScreen(onClick: () -> Unit, modifier:
titleAlignment = Alignment.CenterHorizontally, titleAlignment = Alignment.CenterHorizontally,
) )
Column( Column(
modifier modifier = Modifier
.fillMaxSize(), .fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
) { ) {
@ -58,7 +60,8 @@ internal fun TangemPayChangePinCodeSuccessScreen(onClick: () -> Unit, modifier:
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 16.dp) .padding(horizontal = 16.dp)
.padding(bottom = 16.dp) .padding(bottom = 16.dp)
.navigationBarsPadding(), .navigationBarsPadding()
.testTag(TangemPayTestTags.PIN_DONE_BUTTON),
text = stringResourceSafe(R.string.common_done), text = stringResourceSafe(R.string.common_done),
onClick = onClick, onClick = onClick,
) )
@ -101,14 +104,18 @@ private fun SuccessContent(modifier: Modifier = Modifier) {
) )
SpacerH32() SpacerH32()
Text( Text(
modifier = Modifier.padding(horizontal = 32.dp), modifier = Modifier
.padding(horizontal = 32.dp)
.testTag(TangemPayTestTags.PIN_SUCCESS_TITLE),
text = stringResourceSafe(R.string.tangempay_card_details_change_pin_success_title), text = stringResourceSafe(R.string.tangempay_card_details_change_pin_success_title),
style = TangemTheme.typography.h2, style = TangemTheme.typography.h2,
color = TangemTheme.colors.text.primary1, color = TangemTheme.colors.text.primary1,
) )
SpacerH12() SpacerH12()
Text( Text(
modifier = Modifier.padding(horizontal = 32.dp), modifier = Modifier
.padding(horizontal = 32.dp)
.testTag(TangemPayTestTags.PIN_SUCCESS_DESCRIPTION),
text = stringResourceSafe(R.string.tangempay_card_details_change_pin_success_description), text = stringResourceSafe(R.string.tangempay_card_details_change_pin_success_description),
style = TangemTheme.typography.body1, style = TangemTheme.typography.body1,
color = TangemTheme.colors.text.secondary, color = TangemTheme.colors.text.secondary,

View file

@ -17,6 +17,7 @@ import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color.Companion.Transparent import androidx.compose.ui.graphics.Color.Companion.Transparent
import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
@ -31,6 +32,7 @@ import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.test.TangemPayTestTags
import com.tangem.features.tangempay.details.impl.R import com.tangem.features.tangempay.details.impl.R
import com.tangem.features.tangempay.entity.TangemPayChangePinUM import com.tangem.features.tangempay.entity.TangemPayChangePinUM
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
@ -52,7 +54,7 @@ internal fun TangemPayChangePinScreen(
) )
Column( Column(
modifier = modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(top = 48.dp) .padding(top = 48.dp)
.padding(horizontal = 36.dp) .padding(horizontal = 36.dp)
@ -64,6 +66,7 @@ internal fun TangemPayChangePinScreen(
style = TangemTheme.typography.h2, style = TangemTheme.typography.h2,
color = TangemTheme.colors.text.primary1, color = TangemTheme.colors.text.primary1,
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
modifier = Modifier.testTag(TangemPayTestTags.PIN_SCREEN_TITLE),
) )
SpacerH16() SpacerH16()
@ -73,6 +76,7 @@ internal fun TangemPayChangePinScreen(
style = TangemTheme.typography.body1, style = TangemTheme.typography.body1,
color = TangemTheme.colors.text.secondary, color = TangemTheme.colors.text.secondary,
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
modifier = Modifier.testTag(TangemPayTestTags.PIN_SCREEN_DESCRIPTION),
) )
SpacerH(26.dp) SpacerH(26.dp)
@ -84,7 +88,8 @@ internal fun TangemPayChangePinScreen(
modifier = Modifier modifier = Modifier
.imePadding() .imePadding()
.padding(start = 16.dp, end = 16.dp, bottom = 16.dp) .padding(start = 16.dp, end = 16.dp, bottom = 16.dp)
.fillMaxWidth(), .fillMaxWidth()
.testTag(TangemPayTestTags.PIN_SUBMIT_BUTTON),
primaryButton = NavigationButton( primaryButton = NavigationButton(
textReference = resourceReference(R.string.common_submit), textReference = resourceReference(R.string.common_submit),
onClick = state.onSubmitClick, onClick = state.onSubmitClick,
@ -117,6 +122,7 @@ private fun PinCodeSection(state: TangemPayChangePinUM, modifier: Modifier = Mod
style = TangemTheme.typography.caption2, style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.warning, color = TangemTheme.colors.text.warning,
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
modifier = Modifier.testTag(TangemPayTestTags.PIN_ERROR_MESSAGE),
) )
} }
} }
@ -150,7 +156,8 @@ private fun PinCode(
.clickable { .clickable {
focusRequester.requestFocus() focusRequester.requestFocus()
keyboardController?.show() keyboardController?.show()
}, }
.testTag(TangemPayTestTags.PIN_INPUT_FIELD),
textStyle = TangemTheme.typography.h1.copy(color = Transparent), textStyle = TangemTheme.typography.h1.copy(color = Transparent),
keyboardOptions = KeyboardOptions( keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.NumberPassword, keyboardType = KeyboardType.NumberPassword,

View file

@ -49,6 +49,7 @@ import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.TangemPayTestTags
import com.tangem.core.ui.test.TokenDetailsTopBarTestTags import com.tangem.core.ui.test.TokenDetailsTopBarTestTags
import com.tangem.core.ui.test.WalletConnectBottomSheetTestTags import com.tangem.core.ui.test.WalletConnectBottomSheetTestTags
import com.tangem.features.tangempay.components.express.PreviewEmptyExpressTransactionsComponent import com.tangem.features.tangempay.components.express.PreviewEmptyExpressTransactionsComponent
@ -304,7 +305,7 @@ private fun FiatBalance(
), ),
) )
is TangemPayDetailsBalanceBlockState.Content -> Text( is TangemPayDetailsBalanceBlockState.Content -> Text(
modifier = modifier, modifier = modifier.testTag(TangemPayTestTags.PAYMENT_ACCOUNT_BALANCE),
text = state.fiatBalance.orMaskWithStars(isBalanceHidden), text = state.fiatBalance.orMaskWithStars(isBalanceHidden),
style = TangemTheme.typography.h2.applyBladeBrush( style = TangemTheme.typography.h2.applyBladeBrush(
isEnabled = state.isBalanceFlickering, isEnabled = state.isBalanceFlickering,
@ -312,7 +313,7 @@ private fun FiatBalance(
), ),
) )
is TangemPayDetailsBalanceBlockState.Error -> Text( is TangemPayDetailsBalanceBlockState.Error -> Text(
modifier = modifier, modifier = modifier.testTag(TangemPayTestTags.PAYMENT_ACCOUNT_BALANCE),
text = DASH_SIGN.orMaskWithStars(isBalanceHidden), text = DASH_SIGN.orMaskWithStars(isBalanceHidden),
style = TangemTheme.typography.h2, style = TangemTheme.typography.h2,
color = TangemTheme.colors.text.primary1, color = TangemTheme.colors.text.primary1,

View file

@ -16,6 +16,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.layout.layoutId import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
@ -31,6 +32,7 @@ import com.tangem.core.ui.ds.row.TangemRowLayoutId
import com.tangem.core.ui.extensions.* import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreviewRedesign import com.tangem.core.ui.res.TangemThemePreviewRedesign
import com.tangem.core.ui.test.TangemPayTestTags
import com.tangem.features.tangempay.entity.TangemPayMainUM import com.tangem.features.tangempay.entity.TangemPayMainUM
import com.tangem.features.tangempay.main.impl.R import com.tangem.features.tangempay.main.impl.R
import com.tangem.utils.StringsSigns.DASH_SIGN import com.tangem.utils.StringsSigns.DASH_SIGN
@ -90,7 +92,8 @@ private fun TangemPayMainContent(
modifier = modifier modifier = modifier
.clip(RoundedCornerShape(size = 18.dp)) .clip(RoundedCornerShape(size = 18.dp))
.background(TangemTheme.colors2.surface.level3) .background(TangemTheme.colors2.surface.level3)
.clickableSingle(onClick = payMainUM.onClick), .clickableSingle(onClick = payMainUM.onClick)
.testTag(TangemPayTestTags.MAIN_SCREEN_TILE),
) { ) {
Image( Image(
painter = painterResource(R.drawable.img_visa_36), painter = painterResource(R.drawable.img_visa_36),

View file

@ -14,6 +14,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
@ -29,6 +30,7 @@ import com.tangem.core.ui.components.text.applyBladeBrush
import com.tangem.core.ui.extensions.* import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.TangemPayTestTags
import com.tangem.features.tangempay.entity.TangemPayMainUM import com.tangem.features.tangempay.entity.TangemPayMainUM
import com.tangem.features.tangempay.main.impl.R import com.tangem.features.tangempay.main.impl.R
import com.tangem.utils.StringsSigns.DASH_SIGN import com.tangem.utils.StringsSigns.DASH_SIGN
@ -57,7 +59,7 @@ private fun TangemPayMainBlockContent(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
Surface( Surface(
modifier = modifier, modifier = modifier.testTag(TangemPayTestTags.MAIN_SCREEN_TILE),
shape = TangemTheme.shapes.roundedCornersXMedium, shape = TangemTheme.shapes.roundedCornersXMedium,
color = TangemTheme.colors.background.primary, color = TangemTheme.colors.background.primary,
onClick = state.onClick, onClick = state.onClick,