diff --git a/app/src/main/java/com/tangem/tap/data/DefaultTangemPayStorage.kt b/app/src/main/java/com/tangem/tap/data/DefaultTangemPayStorage.kt
index f990fdc0fd..19171195db 100644
--- a/app/src/main/java/com/tangem/tap/data/DefaultTangemPayStorage.kt
+++ b/app/src/main/java/com/tangem/tap/data/DefaultTangemPayStorage.kt
@@ -14,7 +14,6 @@ import javax.inject.Singleton
import kotlin.text.encodeToByteArray
private const val DEFAULT_KEY = "tangem_pay_default_key"
-private const val DEFAULT_CUSTOMER_WALLET_ADDRESS_KEY = "tangem_pay_default_customer_wallet_address_key"
@Singleton
internal class DefaultTangemPayStorage @Inject constructor(
@@ -54,21 +53,6 @@ internal class DefaultTangemPayStorage @Inject constructor(
?.let(tokensAdapter::fromJson)
}
- /**
- * Store the only customer wallet address, since for the f&f user can issue only one card tied to one address
- */
- override suspend fun storeCustomerWalletAddress(customerWalletAddress: String) =
- withContext(dispatcherProvider.io) {
- secureStorage.store(
- customerWalletAddress.encodeToByteArray(throwOnInvalidSequence = true),
- DEFAULT_CUSTOMER_WALLET_ADDRESS_KEY,
- )
- }
-
- override suspend fun getCustomerWalletAddress(): String? = withContext(dispatcherProvider.io) {
- secureStorage.get(DEFAULT_CUSTOMER_WALLET_ADDRESS_KEY)?.decodeToString(throwOnInvalidSequence = true)
- }
-
override suspend fun clear(customerWalletAddress: String) = withContext(dispatcherProvider.io) {
secureStorage.delete(createKey(customerWalletAddress))
}
diff --git a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt
index 04197aacb2..ebdf08b11a 100644
--- a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt
+++ b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt
@@ -16,6 +16,7 @@ import com.tangem.features.details.component.DetailsComponent
import com.tangem.features.disclaimer.api.components.DisclaimerComponent
import com.tangem.features.home.api.HomeComponent
import com.tangem.features.hotwallet.*
+import com.tangem.features.kyc.KycComponent
import com.tangem.features.managetokens.component.ChooseManagedTokensComponent
import com.tangem.features.managetokens.component.ManageTokensComponent
import com.tangem.features.managetokens.component.ManageTokensSource
@@ -35,6 +36,7 @@ import com.tangem.features.swap.SwapComponent
import com.tangem.features.swap.v2.api.SendWithSwapComponent
import com.tangem.features.tangempay.components.TangemPayDetailsComponent
import com.tangem.features.tangempay.components.TangemPayOnboardingComponent
+import com.tangem.features.tangempay.components.TangemPayOnboardingComponent.Params.*
import com.tangem.features.tokendetails.TokenDetailsComponent
import com.tangem.features.wallet.WalletEntryComponent
import com.tangem.features.walletconnect.components.WalletConnectEntryComponent
@@ -106,6 +108,7 @@ internal class ChildFactory @Inject constructor(
private val sendEntryPointComponentFactory: SendEntryPointComponent.Factory,
private val tangemPayDetailsComponentFactory: TangemPayDetailsComponent.Factory,
private val tangemPayOnboardingComponentFactory: TangemPayOnboardingComponent.Factory,
+ private val kycComponentFactory: KycComponent.Factory,
private val yieldSupplyPromoComponentFactory: YieldSupplyPromoComponent.Factory,
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
) {
@@ -597,10 +600,20 @@ internal class ChildFactory @Inject constructor(
is AppRoute.TangemPayOnboarding -> {
createComponentChild(
context = context,
- params = TangemPayOnboardingComponent.Params(route.deeplink),
+ params = when (val mode = route.mode) {
+ is AppRoute.TangemPayOnboarding.Mode.ContinueOnboarding -> ContinueOnboarding
+ is AppRoute.TangemPayOnboarding.Mode.Deeplink -> Deeplink(deeplink = mode.deeplink)
+ },
componentFactory = tangemPayOnboardingComponentFactory,
)
}
+ is AppRoute.Kyc -> {
+ createComponentChild(
+ context = context,
+ params = KycComponent.Params,
+ componentFactory = kycComponentFactory,
+ )
+ }
is AppRoute.YieldSupplyPromo -> {
createComponentChild(
context = context,
diff --git a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt
index 716edd6750..0a8969253b 100644
--- a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt
+++ b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt
@@ -376,8 +376,23 @@ sealed class AppRoute(val path: String) : Route {
@Serializable
data class TangemPayOnboarding(
- val deeplink: String,
- ) : AppRoute(path = "/tangem_pay_onboarding/$deeplink")
+ val mode: Mode,
+ ) : AppRoute(path = "/tangem_pay_onboarding/$mode") {
+
+ @Serializable
+ sealed class Mode {
+ @Serializable
+ data class Deeplink(
+ val deeplink: String,
+ ) : Mode()
+
+ @Serializable
+ object ContinueOnboarding : Mode()
+ }
+ }
+
+ @Serializable
+ data object Kyc : AppRoute(path = "/kyc")
@Serializable
data class YieldSupplyPromo(
diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/CustomerMeResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/CustomerMeResponse.kt
index 6c60787d46..24b58f4b95 100644
--- a/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/CustomerMeResponse.kt
+++ b/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/CustomerMeResponse.kt
@@ -2,6 +2,7 @@ package com.tangem.datasource.api.pay.models.response
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
+import java.math.BigDecimal
@JsonClass(generateAdapter = true)
data class CustomerMeResponse(
@@ -16,6 +17,8 @@ data class CustomerMeResponse(
@Json(name = "product_instance") val productInstance: ProductInstance?,
@Json(name = "payment_account") val paymentAccount: PaymentAccount?,
@Json(name = "kyc") val kyc: Kyc?,
+ @Json(name = "card") val card: Card?,
+ @Json(name = "balance") val balance: Balance?,
)
@JsonClass(generateAdapter = true)
@@ -45,4 +48,25 @@ data class CustomerMeResponse(
@Json(name = "review_answer") val reviewAnswer: String,
@Json(name = "created_at") val createdAt: String,
)
+
+ @JsonClass(generateAdapter = true)
+ data class Card(
+ @Json(name = "token") val token: String,
+ @Json(name = "expiration_month") val expirationMonth: Int,
+ @Json(name = "expiration_year") val expirationYear: Int,
+ @Json(name = "emboss_name") val embossName: String,
+ @Json(name = "card_type") val cardType: String,
+ @Json(name = "card_status") val cardStatus: String,
+ @Json(name = "card_number_end") val cardNumberEnd: String,
+ )
+
+ @JsonClass(generateAdapter = true)
+ data class Balance(
+ @Json(name = "currency") val currency: String,
+ @Json(name = "available_balance") val availableBalance: BigDecimal,
+ @Json(name = "credit_limit") val creditLimit: BigDecimal,
+ @Json(name = "pending_charges") val pendingCharges: BigDecimal,
+ @Json(name = "posted_charges") val postedCharges: BigDecimal,
+ @Json(name = "balance_due") val balanceDue: BigDecimal,
+ )
}
\ No newline at end of file
diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/visa/TangemPayStorage.kt b/core/datasource/src/main/java/com/tangem/datasource/local/visa/TangemPayStorage.kt
index b988222b09..edf36a599f 100644
--- a/core/datasource/src/main/java/com/tangem/datasource/local/visa/TangemPayStorage.kt
+++ b/core/datasource/src/main/java/com/tangem/datasource/local/visa/TangemPayStorage.kt
@@ -8,9 +8,5 @@ interface TangemPayStorage {
suspend fun getAuthTokens(customerWalletAddress: String): VisaAuthTokens?
- suspend fun storeCustomerWalletAddress(customerWalletAddress: String)
-
- suspend fun getCustomerWalletAddress(): String?
-
suspend fun clear(customerWalletAddress: String)
}
\ No newline at end of file
diff --git a/core/ui/src/main/res/drawable/ic_promo_kyc_36.xml b/core/ui/src/main/res/drawable/ic_promo_kyc_36.xml
new file mode 100644
index 0000000000..54962fbcae
--- /dev/null
+++ b/core/ui/src/main/res/drawable/ic_promo_kyc_36.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
diff --git a/core/ui/src/main/res/drawable/ic_tangem_pay_promo_card_36.xml b/core/ui/src/main/res/drawable/ic_tangem_pay_promo_card_36.xml
new file mode 100644
index 0000000000..4fc9a9cf1c
--- /dev/null
+++ b/core/ui/src/main/res/drawable/ic_tangem_pay_promo_card_36.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
diff --git a/core/ui/src/main/res/drawable/img_tangem_pay_card_main_36.xml b/core/ui/src/main/res/drawable/img_tangem_pay_card_main_36.xml
new file mode 100644
index 0000000000..1b23c9f415
--- /dev/null
+++ b/core/ui/src/main/res/drawable/img_tangem_pay_card_main_36.xml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/core/ui/src/main/res/drawable/img_visa_label_26_16.xml b/core/ui/src/main/res/drawable/img_visa_label_26_16.xml
new file mode 100644
index 0000000000..c5859a2fda
--- /dev/null
+++ b/core/ui/src/main/res/drawable/img_visa_label_26_16.xml
@@ -0,0 +1,17 @@
+
+
+
+
diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/di/TangemPayDataModule.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/di/TangemPayDataModule.kt
index 7519633126..a29a800638 100644
--- a/data/visa/src/main/kotlin/com/tangem/data/pay/di/TangemPayDataModule.kt
+++ b/data/visa/src/main/kotlin/com/tangem/data/pay/di/TangemPayDataModule.kt
@@ -5,9 +5,11 @@ import com.tangem.data.pay.repository.DefaultTangemPayTxHistoryRepository
import com.tangem.data.pay.repository.DefaultOnboardingRepository
import com.tangem.domain.pay.repository.KycRepository
import com.tangem.domain.pay.repository.OnboardingRepository
+import com.tangem.domain.pay.usecase.TangemPayMainScreenCustomerInfoUseCase
import com.tangem.domain.tangempay.repository.TangemPayTxHistoryRepository
import dagger.Binds
import dagger.Module
+import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@@ -27,4 +29,14 @@ internal interface TangemPayDataModule {
@Binds
@Singleton
fun bindTangemPayTxHistoryRepository(repository: DefaultTangemPayTxHistoryRepository): TangemPayTxHistoryRepository
+
+ companion object {
+ @Provides
+ @Singleton
+ fun provideTangemPayMainScreenCustomerInfoUseCase(
+ repository: OnboardingRepository,
+ ): TangemPayMainScreenCustomerInfoUseCase {
+ return TangemPayMainScreenCustomerInfoUseCase(repository = repository)
+ }
+ }
}
\ No newline at end of file
diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultOnboardingRepository.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultOnboardingRepository.kt
index cd8d7634f2..0307d16174 100644
--- a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultOnboardingRepository.kt
+++ b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultOnboardingRepository.kt
@@ -5,8 +5,10 @@ import arrow.core.raise.either
import com.tangem.core.error.UniversalError
import com.tangem.datasource.api.pay.TangemPayApi
import com.tangem.datasource.api.pay.models.request.DeeplinkValidityRequest
+import com.tangem.datasource.api.pay.models.response.CustomerMeResponse
+import com.tangem.domain.pay.model.CustomerInfo.CardInfo
import com.tangem.domain.pay.model.CustomerInfo
-import com.tangem.domain.pay.model.ProductInstance
+import com.tangem.domain.pay.model.CustomerInfo.ProductInstance
import com.tangem.domain.pay.repository.OnboardingRepository
import javax.inject.Inject
@@ -26,11 +28,31 @@ internal class DefaultOnboardingRepository @Inject constructor(
override suspend fun getCustomerInfo(): Either = either {
return requestHelper.request { authHeader ->
tangemPayApi.getCustomerMe(authHeader)
- }.map {
- CustomerInfo(
- productInstance = it.result?.productInstance?.let { ProductInstance(id = it.id, status = it.status) },
- kycStatus = it.result?.kyc?.status,
+ }.map { getCustomerInfo(it.result) }
+ }
+
+ override suspend fun getMainScreenCustomerInfo(): Either = either {
+ return requestHelper.requestWithPersistedToken { authHeader ->
+ tangemPayApi.getCustomerMe(authHeader)
+ }.map { getCustomerInfo(it.result) }
+ }
+
+ private fun getCustomerInfo(response: CustomerMeResponse.Result?): CustomerInfo {
+ val card = response?.card
+ val balance = response?.balance
+ val cardInfo = if (card != null && balance != null) {
+ CardInfo(
+ lastFourDigits = card.cardNumberEnd,
+ balance = balance.availableBalance,
+ currencyCode = balance.currency,
)
+ } else {
+ null
}
+ return CustomerInfo(
+ productInstance = response?.productInstance?.let { ProductInstance(id = it.id, status = it.status) },
+ kycStatus = response?.kyc?.status,
+ cardInfo = cardInfo,
+ )
}
}
\ No newline at end of file
diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/TangemPayRequestPerformer.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/TangemPayRequestPerformer.kt
index dabdf56d8b..ccd82930e5 100644
--- a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/TangemPayRequestPerformer.kt
+++ b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/TangemPayRequestPerformer.kt
@@ -10,18 +10,20 @@ import com.tangem.datasource.api.common.response.getOrThrow
import com.tangem.datasource.api.pay.models.response.VisaErrorResponseJsonAdapter
import com.tangem.datasource.di.NetworkMoshi
import com.tangem.datasource.local.visa.TangemPayStorage
-import com.tangem.domain.core.wallets.UserWalletsListRepository
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.pay.datasource.TangemPayAuthDataSource
import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase
import com.tangem.domain.visa.error.VisaApiError
import com.tangem.domain.visa.model.VisaAuthTokens
+import com.tangem.domain.wallets.usecase.GetWalletsUseCase
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import javax.inject.Inject
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.first
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
@@ -35,9 +37,9 @@ internal class TangemPayRequestPerformer @Inject constructor(
@NetworkMoshi moshi: Moshi,
private val dispatchers: CoroutineDispatcherProvider,
private val tangemPayStorage: TangemPayStorage,
- private val userWalletsRepository: UserWalletsListRepository,
private val getCurrencyUseCase: GetSingleCryptoCurrencyStatusUseCase,
private val authDataSource: TangemPayAuthDataSource,
+ private val getWalletsUseCase: GetWalletsUseCase,
) {
private val visaErrorAdapter = VisaErrorResponseJsonAdapter(moshi)
@@ -49,22 +51,39 @@ internal class TangemPayRequestPerformer @Inject constructor(
suspend fun request(requestBlock: suspend (header: String) -> ApiResponse): Either =
either {
withContext(dispatchers.io) {
- performRequest(requestBlock = requestBlock, refreshTokens = ::refreshAuthTokens).bind()
+ performRequest(
+ requestBlock = requestBlock,
+ getTokens = ::getAccessTokens,
+ refreshTokens = ::refreshAuthTokens,
+ ).bind()
}
}
+ suspend fun requestWithPersistedToken(
+ requestBlock: suspend (header: String) -> ApiResponse,
+ ): Either = either {
+ withContext(dispatchers.io) {
+ performRequest(
+ requestBlock = requestBlock,
+ getTokens = ::getAccessTokensIfSaved,
+ refreshTokens = ::refreshAuthTokens,
+ ).bind()
+ }
+ }
+
private suspend fun performRequest(
requestBlock: suspend (header: String) -> ApiResponse,
+ getTokens: (suspend () -> Either),
refreshTokens: (suspend () -> Either)? = null,
): Either = either {
runCatching {
- requestBlock("Bearer ${getAccessTokens().bind().accessToken}").getOrThrow()
+ requestBlock("Bearer ${getTokens().bind().accessToken}").getOrThrow()
}.getOrElse { error ->
when (error) {
is ApiResponseError.HttpException -> {
if (refreshTokens != null && error.code == ApiResponseError.HttpException.Code.UNAUTHORIZED) {
refreshOrJoin(refreshTokens).bind()
- performRequest(requestBlock, refreshTokens = null).bind()
+ performRequest(requestBlock, refreshTokens = null, getTokens = getTokens).bind()
} else {
raise(mapHttpError(error))
}
@@ -103,9 +122,7 @@ internal class TangemPayRequestPerformer @Inject constructor(
}
private suspend fun getCustomerWalletAddress(): Either = either {
- customerWalletAddress
- ?: tangemPayStorage.getCustomerWalletAddress()
- ?: fetchAuthInputData().bind().address
+ customerWalletAddress ?: fetchAuthInputData().bind().address
}
private suspend fun getAccessTokens(): Either = either {
@@ -113,6 +130,11 @@ internal class TangemPayRequestPerformer @Inject constructor(
tangemPayStorage.getAuthTokens(address) ?: fetchTokens().bind()
}
+ private suspend fun getAccessTokensIfSaved(): Either = either {
+ tangemPayStorage.getAuthTokens(getCustomerWalletAddress().bind())
+ ?: raise(VisaApiError.UnknownWithoutCode)
+ }
+
private fun mapHttpError(throwable: ApiResponseError.HttpException): UniversalError {
val errorBody = throwable.errorBody ?: return VisaApiError.UnknownWithoutCode
return runCatching {
@@ -125,14 +147,16 @@ internal class TangemPayRequestPerformer @Inject constructor(
}
private suspend fun fetchAuthInputData(): Either = either {
- val wallet = userWalletsRepository.userWalletsSync().find { it is UserWallet.Cold } as? UserWallet.Cold
+ val userWallets = getWalletsUseCase()
+ .filter { it.isNotEmpty() }
+ .first()
+ val wallet = userWallets.find { it is UserWallet.Cold } as? UserWallet.Cold
?: raise(VisaApiError.UnknownWithoutCode)
val address = getCurrencyUseCase.invokeMultiWalletSync(wallet.walletId, CryptoCurrency.ID.fromValue(POL_VALUE))
.getOrNull()?.value?.networkAddress?.defaultAddress?.value ?: raise(VisaApiError.UnknownWithoutCode)
customerWalletAddress = address
- tangemPayStorage.storeCustomerWalletAddress(address)
AuthInputData(address, wallet.cardId)
}
diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/pay/model/CustomerInfo.kt b/domain/visa/src/main/kotlin/com/tangem/domain/pay/model/CustomerInfo.kt
index b018ac10da..bee0c16152 100644
--- a/domain/visa/src/main/kotlin/com/tangem/domain/pay/model/CustomerInfo.kt
+++ b/domain/visa/src/main/kotlin/com/tangem/domain/pay/model/CustomerInfo.kt
@@ -1,19 +1,28 @@
package com.tangem.domain.pay.model
+import java.math.BigDecimal
+
private const val APPROVED_KYC_STATUS = "APPROVED"
private const val ACTIVE_PI_STATUS = "active"
data class CustomerInfo(
val productInstance: ProductInstance?,
val kycStatus: String?,
+ val cardInfo: CardInfo?,
) {
+ data class ProductInstance(
+ val id: String,
+ val status: String,
+ )
+
+ data class CardInfo(
+ val lastFourDigits: String,
+ val balance: BigDecimal,
+ val currencyCode: String,
+ )
+
fun isKycApproved() = kycStatus == APPROVED_KYC_STATUS
fun isProductInstanceActive() = productInstance?.status == ACTIVE_PI_STATUS
-}
-
-data class ProductInstance(
- val id: String,
- val status: String,
-)
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/pay/repository/OnboardingRepository.kt b/domain/visa/src/main/kotlin/com/tangem/domain/pay/repository/OnboardingRepository.kt
index 4838f46c53..d6a888bc10 100644
--- a/domain/visa/src/main/kotlin/com/tangem/domain/pay/repository/OnboardingRepository.kt
+++ b/domain/visa/src/main/kotlin/com/tangem/domain/pay/repository/OnboardingRepository.kt
@@ -9,4 +9,9 @@ interface OnboardingRepository {
suspend fun validateDeeplink(link: String): Either
suspend fun getCustomerInfo(): Either
+
+ /**
+ * Returns only if the user already authorised at least once
+ */
+ suspend fun getMainScreenCustomerInfo(): Either
}
\ No newline at end of file
diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/pay/usecase/TangemPayMainScreenCustomerInfoUseCase.kt b/domain/visa/src/main/kotlin/com/tangem/domain/pay/usecase/TangemPayMainScreenCustomerInfoUseCase.kt
new file mode 100644
index 0000000000..cefdd9f95a
--- /dev/null
+++ b/domain/visa/src/main/kotlin/com/tangem/domain/pay/usecase/TangemPayMainScreenCustomerInfoUseCase.kt
@@ -0,0 +1,15 @@
+package com.tangem.domain.pay.usecase
+
+import com.tangem.domain.pay.model.CustomerInfo
+import com.tangem.domain.pay.repository.OnboardingRepository
+
+/**
+ * Returns tangem pay customer info for the main screen banner
+ * Works only if the user already authorised at least once (won't emit anything otherwise)
+ */
+class TangemPayMainScreenCustomerInfoUseCase(
+ private val repository: OnboardingRepository,
+) {
+
+ suspend operator fun invoke(): CustomerInfo? = repository.getMainScreenCustomerInfo().getOrNull()
+}
\ No newline at end of file
diff --git a/features/kyc/api/src/main/kotlin/com/tangem/features/kyc/KycComponent.kt b/features/kyc/api/src/main/kotlin/com/tangem/features/kyc/KycComponent.kt
index 6a18888000..97e2d1f0cc 100644
--- a/features/kyc/api/src/main/kotlin/com/tangem/features/kyc/KycComponent.kt
+++ b/features/kyc/api/src/main/kotlin/com/tangem/features/kyc/KycComponent.kt
@@ -1,12 +1,11 @@
package com.tangem.features.kyc
-import com.tangem.core.decompose.context.AppComponentContext
+import com.tangem.core.decompose.factory.ComponentFactory
+import com.tangem.core.ui.decompose.ComposableContentComponent
-interface KycComponent {
+interface KycComponent : ComposableContentComponent {
- fun launch()
+ data object Params
- interface Factory {
- fun create(appComponentContext: AppComponentContext): KycComponent
- }
+ interface Factory : ComponentFactory
}
\ No newline at end of file
diff --git a/features/kyc/impl/src/main/kotlin/com/tangem/features/kyc/DefaultKycComponent.kt b/features/kyc/impl/src/main/kotlin/com/tangem/features/kyc/DefaultKycComponent.kt
index 1be34aa554..76713a61b2 100644
--- a/features/kyc/impl/src/main/kotlin/com/tangem/features/kyc/DefaultKycComponent.kt
+++ b/features/kyc/impl/src/main/kotlin/com/tangem/features/kyc/DefaultKycComponent.kt
@@ -1,45 +1,62 @@
package com.tangem.features.kyc
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
import com.sumsub.sns.core.SNSMobileSDK
import com.sumsub.sns.core.data.listener.TokenExpirationHandler
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel
+import com.tangem.domain.pay.KycStartInfo
import com.tangem.features.kyc.theme.TangemSNSIconHandler
import com.tangem.features.kyc.theme.TangemSNSTheme
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.launch
import java.util.Locale
class DefaultKycComponent @AssistedInject constructor(
@Assisted appComponentContext: AppComponentContext,
+ @Assisted params: KycComponent.Params,
) : KycComponent, AppComponentContext by appComponentContext {
- private val model: DefaultKycModel = getOrCreateModel()
+ private val model: DefaultKycModel = getOrCreateModel(params)
- override fun launch() {
+ init {
componentScope.launch {
- model.uiState.collect {
- it?.let { startInfo ->
- val tokenExpirationHandler = object : TokenExpirationHandler {
- override fun onTokenExpired() = ""
- }
- val snsSdk = SNSMobileSDK.Builder(activity)
- .withAccessToken(accessToken = startInfo.token, onTokenExpiration = tokenExpirationHandler)
- .withTheme(TangemSNSTheme.theme(activity))
- .withIconHandler(TangemSNSIconHandler())
- .withLocale(Locale(startInfo.locale))
- .build()
- snsSdk.launch()
- }
+ model.uiState.drop(1).collectLatest { startInfo ->
+ startInfo?.let { launchSdk(startInfo) }
+ router.pop()
}
}
- model.getKycToken()
+ }
+
+ private fun launchSdk(startInfo: KycStartInfo) {
+ val tokenExpirationHandler = object : TokenExpirationHandler {
+ /**
+ * We don't refresh this token for f&f release. Assume it lives long enough to finish KYC
+ * [REDACTED_TODO_COMMENT]
+ */
+ override fun onTokenExpired() = ""
+ }
+ val snsSdk = SNSMobileSDK.Builder(activity)
+ .withAccessToken(accessToken = startInfo.token, onTokenExpiration = tokenExpirationHandler)
+ .withTheme(TangemSNSTheme.theme(activity))
+ .withIconHandler(TangemSNSIconHandler())
+ .withLocale(Locale(startInfo.locale))
+ .build()
+ snsSdk.launch()
+ }
+
+ @Composable
+ override fun Content(modifier: Modifier) {
+ KycLoadingScreen(router::pop, modifier)
}
@AssistedFactory
interface Factory : KycComponent.Factory {
- override fun create(appComponentContext: AppComponentContext): DefaultKycComponent
+ override fun create(context: AppComponentContext, params: KycComponent.Params): DefaultKycComponent
}
}
\ No newline at end of file
diff --git a/features/kyc/impl/src/main/kotlin/com/tangem/features/kyc/DefaultKycModel.kt b/features/kyc/impl/src/main/kotlin/com/tangem/features/kyc/DefaultKycModel.kt
index 21b8e8e9bb..7df782304f 100644
--- a/features/kyc/impl/src/main/kotlin/com/tangem/features/kyc/DefaultKycModel.kt
+++ b/features/kyc/impl/src/main/kotlin/com/tangem/features/kyc/DefaultKycModel.kt
@@ -21,7 +21,7 @@ class DefaultKycModel @Inject constructor(
private val _uiState: MutableStateFlow = MutableStateFlow(null)
val uiState = _uiState.asStateFlow()
- fun getKycToken() {
+ init {
modelScope.launch {
kycRepository.getKycStartInfo().getOrNull()?.let { _uiState.emit(it) }
}
diff --git a/features/kyc/impl/src/main/kotlin/com/tangem/features/kyc/KycLoadingScreen.kt b/features/kyc/impl/src/main/kotlin/com/tangem/features/kyc/KycLoadingScreen.kt
new file mode 100644
index 0000000000..c96c285fc3
--- /dev/null
+++ b/features/kyc/impl/src/main/kotlin/com/tangem/features/kyc/KycLoadingScreen.kt
@@ -0,0 +1,41 @@
+package com.tangem.features.kyc
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.statusBarsPadding
+import androidx.compose.material3.CircularProgressIndicator
+import androidx.compose.material3.Scaffold
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import com.tangem.core.ui.R
+import com.tangem.core.ui.components.appbar.AppBarWithBackButton
+import com.tangem.core.ui.res.TangemTheme
+
+@Composable
+internal fun KycLoadingScreen(onBack: () -> Unit, modifier: Modifier = Modifier) {
+ Scaffold(
+ modifier = modifier,
+ topBar = {
+ AppBarWithBackButton(
+ modifier = Modifier.statusBarsPadding(),
+ onBackClick = onBack,
+ iconRes = R.drawable.ic_back_24,
+ )
+ },
+ content = { paddingValues ->
+ Box(
+ modifier = modifier
+ .padding(paddingValues)
+ .fillMaxSize(),
+ contentAlignment = Alignment.Center,
+ ) {
+ CircularProgressIndicator(
+ modifier = Modifier,
+ color = TangemTheme.colors.icon.primary1,
+ )
+ }
+ },
+ )
+}
\ No newline at end of file
diff --git a/features/kyc/mock/build.gradle.kts b/features/kyc/mock/build.gradle.kts
index a64c1f8a89..2ba00e1182 100644
--- a/features/kyc/mock/build.gradle.kts
+++ b/features/kyc/mock/build.gradle.kts
@@ -15,6 +15,8 @@ dependencies {
implementation(projects.features.kyc.api)
implementation(projects.core.decompose)
+ implementation(deps.compose.ui)
+ implementation(projects.core.ui)
/** DI */
implementation(deps.hilt.android)
diff --git a/features/kyc/mock/src/main/kotlin/com/tangem/features/kyc/MockKycComponent.kt b/features/kyc/mock/src/main/kotlin/com/tangem/features/kyc/MockKycComponent.kt
index 71bd1f9b5d..7119fdd452 100644
--- a/features/kyc/mock/src/main/kotlin/com/tangem/features/kyc/MockKycComponent.kt
+++ b/features/kyc/mock/src/main/kotlin/com/tangem/features/kyc/MockKycComponent.kt
@@ -1,23 +1,25 @@
package com.tangem.features.kyc
+import androidx.compose.runtime.Composable
import com.tangem.core.decompose.context.AppComponentContext
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
+import androidx.compose.ui.Modifier
/**
* Mocking it for release/external builds to exclude SumSub dependency
+ * This will never be called if the FT [isTangemPayEnabled] is off
*/
internal class MockKycComponent @AssistedInject constructor(
@Assisted appComponentContext: AppComponentContext,
) : KycComponent, AppComponentContext by appComponentContext {
- override fun launch() {
- /* no op */
- }
+ @Composable
+ override fun Content(modifier: Modifier) { /* no op */ }
@AssistedFactory
interface Factory : KycComponent.Factory {
- override fun create(appComponentContext: AppComponentContext): MockKycComponent
+ override fun create(context: AppComponentContext, params: KycComponent.Params): MockKycComponent
}
}
\ No newline at end of file
diff --git a/features/tangempay/onboarding/api/src/main/kotlin/com/tangem/features/tangempay/components/TangemPayOnboardingComponent.kt b/features/tangempay/onboarding/api/src/main/kotlin/com/tangem/features/tangempay/components/TangemPayOnboardingComponent.kt
index d2257362fb..0ad307aa79 100644
--- a/features/tangempay/onboarding/api/src/main/kotlin/com/tangem/features/tangempay/components/TangemPayOnboardingComponent.kt
+++ b/features/tangempay/onboarding/api/src/main/kotlin/com/tangem/features/tangempay/components/TangemPayOnboardingComponent.kt
@@ -5,9 +5,13 @@ import com.tangem.core.ui.decompose.ComposableContentComponent
interface TangemPayOnboardingComponent : ComposableContentComponent {
- data class Params(
- val deeplink: String,
- )
+ sealed class Params {
+ data class Deeplink(
+ val deeplink: String,
+ ) : Params()
+
+ object ContinueOnboarding : Params()
+ }
interface Factory : ComponentFactory
}
\ No newline at end of file
diff --git a/features/tangempay/onboarding/impl/src/main/kotlin/com/tangem/features/tangempay/components/DefaultTangemPayOnboardingComponent.kt b/features/tangempay/onboarding/impl/src/main/kotlin/com/tangem/features/tangempay/components/DefaultTangemPayOnboardingComponent.kt
index 58a1454848..01effd1f8b 100644
--- a/features/tangempay/onboarding/impl/src/main/kotlin/com/tangem/features/tangempay/components/DefaultTangemPayOnboardingComponent.kt
+++ b/features/tangempay/onboarding/impl/src/main/kotlin/com/tangem/features/tangempay/components/DefaultTangemPayOnboardingComponent.kt
@@ -5,33 +5,29 @@ import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.decompose.context.AppComponentContext
-import com.tangem.core.decompose.context.child
-import com.tangem.features.kyc.KycComponent
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.features.tangempay.model.TangemPayOnboardingModel
+import com.tangem.features.tangempay.ui.TandemPayOnboardingScreen
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
-import com.tangem.features.tangempay.ui.TandemPayOnboardingScreen
internal class DefaultTangemPayOnboardingComponent @AssistedInject constructor(
@Assisted appComponentContext: AppComponentContext,
@Assisted params: TangemPayOnboardingComponent.Params,
- kycComponentFactory: KycComponent.Factory,
) : TangemPayOnboardingComponent, AppComponentContext by appComponentContext {
- private val kycComponent = kycComponentFactory.create(child("kycComponent"))
-
private val model: TangemPayOnboardingModel = getOrCreateModel(params)
@Composable
override fun Content(modifier: Modifier) {
val state by model.screenState.collectAsStateWithLifecycle()
- TandemPayOnboardingScreen(modifier = modifier, state = state, onButtonClick = ::onButtonClick)
- }
-
- private fun onButtonClick() {
- kycComponent.launch()
+ TandemPayOnboardingScreen(
+ modifier = modifier,
+ state = state,
+ onBackClick = model::back,
+ onOpenKycClick = model::openKyc,
+ )
}
@AssistedFactory
diff --git a/features/tangempay/onboarding/impl/src/main/kotlin/com/tangem/features/tangempay/deeplink/DefaultOnboardVisaDeepLinkHandler.kt b/features/tangempay/onboarding/impl/src/main/kotlin/com/tangem/features/tangempay/deeplink/DefaultOnboardVisaDeepLinkHandler.kt
index 3b10b702a9..15313ee912 100644
--- a/features/tangempay/onboarding/impl/src/main/kotlin/com/tangem/features/tangempay/deeplink/DefaultOnboardVisaDeepLinkHandler.kt
+++ b/features/tangempay/onboarding/impl/src/main/kotlin/com/tangem/features/tangempay/deeplink/DefaultOnboardVisaDeepLinkHandler.kt
@@ -16,7 +16,8 @@ internal class DefaultOnboardVisaDeepLinkHandler @AssistedInject constructor(
init {
if (tangemPayFeatureToggles.isTangemPayEnabled) {
- appRouter.push(AppRoute.TangemPayOnboarding(uri.toString()))
+ val mode = AppRoute.TangemPayOnboarding.Mode.Deeplink(uri.toString())
+ appRouter.push(AppRoute.TangemPayOnboarding(mode))
} else {
appRouter.push(AppRoute.Home())
}
diff --git a/features/tangempay/onboarding/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayOnboardingModel.kt b/features/tangempay/onboarding/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayOnboardingModel.kt
index 61b942c805..bb4f1621ce 100644
--- a/features/tangempay/onboarding/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayOnboardingModel.kt
+++ b/features/tangempay/onboarding/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayOnboardingModel.kt
@@ -1,6 +1,7 @@
package com.tangem.features.tangempay.model
import androidx.compose.runtime.Stable
+import com.tangem.common.routing.AppRoute
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
@@ -30,29 +31,44 @@ internal class TangemPayOnboardingModel @Inject constructor(
init {
modelScope.launch {
- repository.validateDeeplink(params.deeplink)
- .onRight { isValid -> if (isValid) checkCustomerInfo() }
- .onLeft { exit() }
+ when (params) {
+ is TangemPayOnboardingComponent.Params.ContinueOnboarding -> {
+ checkCustomerInfo()
+ }
+ is TangemPayOnboardingComponent.Params.Deeplink -> {
+ repository.validateDeeplink(params.deeplink)
+ .onRight { isValid -> if (isValid) checkCustomerInfo() }
+ .onLeft { back() }
+ }
+ }
}
}
+ fun openKyc() {
+ router.replaceAll(AppRoute.Wallet, AppRoute.Kyc)
+ }
+
+ fun back() {
+ router.pop()
+ }
+
private suspend fun checkCustomerInfo() {
repository.getCustomerInfo()
.onRight { customerInfo ->
when {
!customerInfo.isKycApproved() -> {
- screenState.value = screenState.value.copy(fullScreenLoading = false)
+ when (params) {
+ is TangemPayOnboardingComponent.Params.Deeplink ->
+ screenState.value = screenState.value.copy(fullScreenLoading = false)
+ else -> openKyc()
+ }
}
!customerInfo.isProductInstanceActive() -> {
// TODO [REDACTED_TASK_KEY]: create order and poll order status (API is not ready yet)
}
- else -> exit()
+ else -> back()
}
}
- .onLeft { exit() }
- }
-
- private fun exit() {
- router.pop()
+ .onLeft { back() }
}
}
\ No newline at end of file
diff --git a/features/tangempay/onboarding/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TandemPayOnboardingScreen.kt b/features/tangempay/onboarding/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TandemPayOnboardingScreen.kt
index b0b18abdd6..7240b154e5 100644
--- a/features/tangempay/onboarding/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TandemPayOnboardingScreen.kt
+++ b/features/tangempay/onboarding/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TandemPayOnboardingScreen.kt
@@ -15,7 +15,8 @@ import com.tangem.core.ui.res.TangemThemePreview
@Composable
internal fun TandemPayOnboardingScreen(
state: TangemPayOnboardingScreenState,
- onButtonClick: () -> Unit,
+ onOpenKycClick: () -> Unit,
+ onBackClick: () -> Unit,
modifier: Modifier = Modifier,
) {
Scaffold(
@@ -23,7 +24,7 @@ internal fun TandemPayOnboardingScreen(
topBar = {
AppBarWithBackButton(
modifier = Modifier.statusBarsPadding(),
- onBackClick = {},
+ onBackClick = onBackClick,
iconRes = R.drawable.ic_back_24,
)
},
@@ -33,7 +34,7 @@ internal fun TandemPayOnboardingScreen(
.padding(paddingValues)
.fillMaxSize(),
state = state,
- onButtonClick = onButtonClick,
+ onButtonClick = onOpenKycClick,
)
},
)
@@ -44,6 +45,6 @@ internal fun TandemPayOnboardingScreen(
@Composable
private fun PreviewDarkTheme() {
TangemThemePreview {
- TandemPayOnboardingScreen(state = TangemPayOnboardingScreenState(), {})
+ TandemPayOnboardingScreen(state = TangemPayOnboardingScreenState(), {}, {})
}
}
\ No newline at end of file
diff --git a/features/wallet/impl/build.gradle.kts b/features/wallet/impl/build.gradle.kts
index 9a04d605c3..d06b12cd86 100644
--- a/features/wallet/impl/build.gradle.kts
+++ b/features/wallet/impl/build.gradle.kts
@@ -116,6 +116,7 @@ dependencies {
implementation(projects.features.kyc.api)
implementation(projects.features.tokenRecieve.api)
implementation(projects.features.yieldSupply.api)
+ implementation(projects.features.tangempay.details.api)
/** Common modules */
implementation(projects.common)
diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/WalletModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/WalletModel.kt
index 4921d64c1e..3ea6b5b660 100644
--- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/WalletModel.kt
+++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/WalletModel.kt
@@ -18,6 +18,7 @@ import com.tangem.domain.models.wallet.isMultiCurrency
import com.tangem.domain.nft.ObserveAndClearNFTCacheIfNeedUseCase
import com.tangem.domain.notifications.GetIsHuaweiDeviceWithoutGoogleServicesUseCase
import com.tangem.domain.notifications.repository.NotificationsRepository
+import com.tangem.domain.pay.usecase.TangemPayMainScreenCustomerInfoUseCase
import com.tangem.domain.settings.*
import com.tangem.domain.tokens.RefreshMultiCurrencyWalletQuotesUseCase
import com.tangem.domain.wallets.usecase.*
@@ -33,12 +34,14 @@ import com.tangem.feature.wallet.presentation.wallet.state.model.WalletEvent
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletEvent.DemonstrateWalletsScrollPreview.Direction
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenState
import com.tangem.feature.wallet.presentation.wallet.state.transformers.*
+import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.TangemPayStateConverter
import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletEventSender
import com.tangem.feature.wallet.presentation.wallet.utils.ScreenLifecycleProvider
import com.tangem.features.biometry.AskBiometryComponent
import com.tangem.features.hotwallet.HotWalletFeatureToggles
import com.tangem.features.pushnotifications.api.PushNotificationsModelCallbacks
import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION
+import com.tangem.features.tangempay.TangemPayFeatureToggles
import com.tangem.features.wallet.deeplink.WalletDeepLinkActionListener
import com.tangem.utils.Provider
import com.tangem.utils.coroutines.*
@@ -84,6 +87,9 @@ internal class WalletModel @Inject constructor(
private val getIsHuaweiDeviceWithoutGoogleServicesUseCase: GetIsHuaweiDeviceWithoutGoogleServicesUseCase,
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
private val userWalletsListRepository: UserWalletsListRepository,
+ private val tangemPayMainScreenCustomerInfoUseCase: TangemPayMainScreenCustomerInfoUseCase,
+ private val tangemPayStateConverter: TangemPayStateConverter,
+ private val tangemPayFeatureToggles: TangemPayFeatureToggles,
val screenLifecycleProvider: ScreenLifecycleProvider,
val innerWalletRouter: InnerWalletRouter,
) : Model() {
@@ -112,6 +118,7 @@ internal class WalletModel @Inject constructor(
subscribeOnSelectedWalletFlow()
subscribeToScreenBackgroundState()
subscribeOnPushNotificationsPermission()
+ subscribeToTangemPayInfo()
enableNotificationsIfNeeded()
clickIntents.initialize(innerWalletRouter, modelScope)
@@ -313,6 +320,27 @@ internal class WalletModel @Inject constructor(
.saveIn(clearNFTCacheJobHolder)
}
+ private fun subscribeToTangemPayInfo() {
+ /**
+ * Update state each time a user opens/returns to wallet screen
+ */
+ screenLifecycleProvider.isBackgroundState.onEach { isBackground ->
+ if (!isBackground) { updateTangemPayInfo() }
+ }.launchIn(modelScope)
+ }
+
+ private fun updateTangemPayInfo() {
+ if (tangemPayFeatureToggles.isTangemPayEnabled) {
+ modelScope.launch {
+ tangemPayMainScreenCustomerInfoUseCase()?.let { info ->
+ stateHolder.update {
+ it.copy(tangemPayState = tangemPayStateConverter.convert(info))
+ }
+ }
+ }
+ }
+ }
+
private fun needToRefreshTimer() {
modelScope.launch {
delay(REFRESH_WALLET_BACKGROUND_TIMER_MILLIS)
diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/TangemPayState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/TangemPayState.kt
new file mode 100644
index 0000000000..cced51be15
--- /dev/null
+++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/TangemPayState.kt
@@ -0,0 +1,23 @@
+package com.tangem.feature.wallet.presentation.wallet.state.model
+
+import androidx.annotation.DrawableRes
+import androidx.compose.runtime.Immutable
+import com.tangem.core.ui.extensions.TextReference
+
+@Immutable
+internal sealed class TangemPayState {
+
+ object Empty : TangemPayState()
+
+ data class Progress(
+ val title: TextReference,
+ val buttonText: TextReference,
+ @DrawableRes val iconRes: Int,
+ val onButtonClick: () -> Unit,
+ ) : TangemPayState()
+
+ data class Card(
+ val lastFourDigits: TextReference,
+ val balanceText: TextReference,
+ ) : TangemPayState()
+}
\ No newline at end of file
diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletScreenState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletScreenState.kt
index df260b5856..e142c4af9d 100644
--- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletScreenState.kt
+++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletScreenState.kt
@@ -14,4 +14,5 @@ internal data class WalletScreenState(
val isHidingMode: Boolean,
val showMarketsOnboarding: Boolean,
val onDismissMarketsOnboarding: () -> Unit,
+ val tangemPayState: TangemPayState = TangemPayState.Empty,
)
\ No newline at end of file
diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TangemPayStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TangemPayStateConverter.kt
new file mode 100644
index 0000000000..b2afc216b5
--- /dev/null
+++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TangemPayStateConverter.kt
@@ -0,0 +1,52 @@
+package com.tangem.feature.wallet.presentation.wallet.state.transformers.converter
+
+import com.tangem.common.routing.AppRoute
+import com.tangem.common.ui.R
+import com.tangem.core.decompose.navigation.Router
+import com.tangem.core.ui.extensions.TextReference
+import com.tangem.core.ui.format.bigdecimal.fiat
+import com.tangem.core.ui.format.bigdecimal.format
+import com.tangem.domain.pay.model.CustomerInfo.CardInfo
+import com.tangem.domain.pay.model.CustomerInfo
+import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState
+import com.tangem.utils.converter.Converter
+import java.util.Currency
+import javax.inject.Inject
+
+internal class TangemPayStateConverter @Inject constructor(
+ private val router: Router,
+) : Converter {
+
+ override fun convert(value: CustomerInfo): TangemPayState {
+ val route = AppRoute.TangemPayOnboarding(AppRoute.TangemPayOnboarding.Mode.ContinueOnboarding)
+ val cardInfo = value.cardInfo
+ return when {
+ !value.isKycApproved() -> TangemPayState.Progress(
+ title = TextReference.Res(R.string.tangempay_kyc_in_progress_notification_title),
+ buttonText = TextReference.Res(R.string.tangempay_kyc_in_progress_notification_button),
+ iconRes = R.drawable.ic_promo_kyc_36,
+ onButtonClick = { router.push(route) },
+ )
+ !value.isProductInstanceActive() -> TangemPayState.Progress(
+ title = TextReference.Res(R.string.tangempay_issue_card_notification_title),
+ buttonText = TextReference.Res(R.string.common_continue),
+ iconRes = R.drawable.ic_tangem_pay_promo_card_36,
+ onButtonClick = { router.push(route) },
+ )
+ cardInfo != null -> {
+ TangemPayState.Card(
+ lastFourDigits = TextReference.Str("*${cardInfo.lastFourDigits}"),
+ balanceText = TextReference.Str(getBalanceText(cardInfo)),
+ )
+ }
+ else -> TangemPayState.Empty
+ }
+ }
+
+ private fun getBalanceText(cardInfo: CardInfo): String {
+ val currency = Currency.getInstance(cardInfo.currencyCode)
+ return cardInfo.balance.format {
+ fiat(fiatCurrencyCode = currency.currencyCode, fiatCurrencySymbol = currency.symbol)
+ }
+ }
+}
\ No newline at end of file
diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt
index 28adcb811c..073d32efd8 100644
--- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt
+++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt
@@ -79,6 +79,7 @@ import com.tangem.feature.wallet.presentation.wallet.ui.components.multicurrency
import com.tangem.feature.wallet.presentation.wallet.ui.components.multicurrency.organizeTokensButton
import com.tangem.feature.wallet.presentation.wallet.ui.components.singlecurrency.marketPriceBlock
import com.tangem.feature.wallet.presentation.wallet.ui.components.visa.BalancesAndLimitsBottomSheet
+import com.tangem.feature.wallet.presentation.wallet.ui.components.visa.TangemPayMainScreenBlock
import com.tangem.feature.wallet.presentation.wallet.ui.components.visa.VisaTxDetailsBottomSheet
import com.tangem.feature.wallet.presentation.wallet.ui.components.visa.balancesAndLimitsBlock
import com.tangem.feature.wallet.presentation.wallet.ui.utils.changeWalletAnimator
@@ -212,6 +213,11 @@ private fun WalletContent(
notifications(configs = selectedWallet.warnings, modifier = itemModifier)
+ item(
+ key = "TangemPayMainScreenBlock",
+ contentType = state.tangemPayState::class.java,
+ ) { TangemPayMainScreenBlock(state.tangemPayState, itemModifier) }
+
(selectedWallet as? WalletState.SingleCurrency)?.let { walletState ->
walletState.marketPriceBlockState?.let { marketPriceBlockState ->
marketPriceBlock(state = marketPriceBlockState, modifier = itemModifier)
diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/singlecurrency/TangemPayCardMainBlock.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/singlecurrency/TangemPayCardMainBlock.kt
new file mode 100644
index 0000000000..7cc3d681e6
--- /dev/null
+++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/singlecurrency/TangemPayCardMainBlock.kt
@@ -0,0 +1,82 @@
+package com.tangem.feature.wallet.presentation.wallet.ui.components.singlecurrency
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import com.tangem.common.ui.R
+import com.tangem.core.ui.extensions.resolveReference
+import com.tangem.core.ui.extensions.stringResourceSafe
+import com.tangem.core.ui.res.TangemTheme
+import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState
+
+@Composable
+internal fun TangemPayCardMainBlock(state: TangemPayState.Card, modifier: Modifier = Modifier) {
+ Surface(
+ modifier = modifier.fillMaxWidth(),
+ shape = TangemTheme.shapes.roundedCornersXMedium,
+ color = TangemTheme.colors.background.primary,
+ ) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(IntrinsicSize.Min)
+ .padding(horizontal = 16.dp, vertical = 12.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ Image(
+ painter = painterResource(R.drawable.img_tangem_pay_card_main_36),
+ contentDescription = null,
+ modifier = Modifier.size(36.dp).clip(CircleShape),
+ )
+
+ Spacer(Modifier.width(12.dp))
+
+ Column(
+ modifier = Modifier.weight(1f),
+ verticalArrangement = Arrangement.Center,
+ ) {
+ Text(
+ text = stringResourceSafe(R.string.tangempay_kyc_card_ready_notification_title),
+ style = TangemTheme.typography.subtitle2,
+ color = TangemTheme.colors.text.primary1,
+ )
+
+ Spacer(Modifier.height(6.dp))
+
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ Image(
+ painter = painterResource(R.drawable.img_visa_label_26_16),
+ contentDescription = null,
+ modifier = Modifier.size(26.dp, 16.dp),
+ )
+ Spacer(Modifier.width(4.dp))
+ Text(
+ text = state.lastFourDigits.resolveReference(),
+ style = TangemTheme.typography.caption2,
+ color = TangemTheme.colors.text.tertiary,
+ )
+ }
+ }
+ Box(
+ modifier = Modifier.fillMaxHeight(),
+ contentAlignment = Alignment.TopEnd,
+ ) {
+ Text(
+ text = state.balanceText.resolveReference(),
+ style = TangemTheme.typography.body2,
+ color = TangemTheme.colors.text.primary1,
+ textAlign = TextAlign.End,
+ )
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/visa/TangemPayMainScreenBlock.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/visa/TangemPayMainScreenBlock.kt
new file mode 100644
index 0000000000..2f03ffe4e0
--- /dev/null
+++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/visa/TangemPayMainScreenBlock.kt
@@ -0,0 +1,79 @@
+package com.tangem.feature.wallet.presentation.wallet.ui.components.visa
+
+import android.content.res.Configuration
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.tangem.common.ui.R
+import com.tangem.core.ui.components.notifications.Notification
+import com.tangem.core.ui.components.notifications.NotificationConfig
+import com.tangem.core.ui.extensions.TextReference
+import com.tangem.core.ui.res.TangemTheme
+import com.tangem.core.ui.res.TangemThemePreview
+import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState
+import com.tangem.feature.wallet.presentation.wallet.ui.components.singlecurrency.TangemPayCardMainBlock
+
+@Composable
+internal fun TangemPayMainScreenBlock(state: TangemPayState, modifier: Modifier = Modifier) {
+ AnimatedVisibility(state !is TangemPayState.Empty) {
+ when (state) {
+ is TangemPayState.Progress -> {
+ Notification(
+ modifier = modifier
+ .fillMaxWidth(),
+ config = NotificationConfig(
+ title = state.title,
+ iconSize = 36.dp,
+ subtitle = TextReference.EMPTY,
+ iconResId = state.iconRes,
+ buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
+ text = state.buttonText,
+ onClick = state.onButtonClick,
+ ),
+ ),
+ )
+ }
+ is TangemPayState.Card -> TangemPayCardMainBlock(state, modifier)
+ is TangemPayState.Empty -> Unit
+ }
+ }
+}
+
+@Preview(showBackground = true)
+@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
+@Composable
+private fun ResetCardScreenPreview() {
+ TangemThemePreview {
+ Column(verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8)) {
+ TangemPayMainScreenBlock(
+ TangemPayState.Progress(
+ title = TextReference.Res(R.string.tangempay_kyc_in_progress_notification_title),
+ buttonText = TextReference.Res(R.string.tangempay_kyc_in_progress_notification_button),
+ iconRes = R.drawable.ic_promo_kyc_36,
+ onButtonClick = {},
+ ),
+ )
+
+ TangemPayMainScreenBlock(
+ TangemPayState.Progress(
+ title = TextReference.Res(R.string.tangempay_issue_card_notification_title),
+ buttonText = TextReference.Res(R.string.common_continue),
+ iconRes = R.drawable.ic_tangem_pay_promo_card_36,
+ onButtonClick = {},
+ ),
+ )
+
+ TangemPayMainScreenBlock(
+ TangemPayState.Card(
+ lastFourDigits = TextReference.Str("*1234"),
+ balanceText = TextReference.Str("$ 0.00"),
+ ),
+ )
+ }
+ }
+}
\ No newline at end of file