Updated on 2026-08-14
This commit is contained in:
parent
6a82ee1600
commit
229dc89bcb
28 changed files with 623 additions and 36 deletions
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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) },
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
package com.tangem.core.ui.test
|
||||||
|
|
||||||
|
object HotWalletAccessCodeTestTags {
|
||||||
|
const val ACCESS_CODE_INPUT = "HOT_WALLET_ACCESS_CODE_INPUT"
|
||||||
|
}
|
||||||
|
|
@ -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"
|
||||||
|
}
|
||||||
|
|
@ -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"
|
||||||
}
|
}
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
)
|
)
|
||||||
|
|
@ -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),
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue