Updated on 2026-08-14
This commit is contained in:
parent
7e02e72a40
commit
c2e67ba6bc
41 changed files with 863 additions and 119 deletions
|
|
@ -115,6 +115,7 @@ internal class DefaultTangemPayStorage @Inject constructor(
|
|||
appPreferencesStore.store(PreferencesKeys.getTangemPayCustomerWalletAddressKey(userWalletId), "")
|
||||
appPreferencesStore.store(PreferencesKeys.getTangemPayOrderIdKey(customerWalletAddress), "")
|
||||
appPreferencesStore.store(PreferencesKeys.getTangemPayAddToWalletKey(customerWalletAddress), false)
|
||||
appPreferencesStore.store(PreferencesKeys.getTangemPayHideOnboardingKey(userWalletId), false)
|
||||
}
|
||||
|
||||
override suspend fun storeWithdrawOrder(userWalletId: UserWalletId, orderId: String) {
|
||||
|
|
@ -144,6 +145,20 @@ internal class DefaultTangemPayStorage @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun storeHideOnboardingBanner(userWalletId: UserWalletId, hide: Boolean) {
|
||||
withContext(dispatcherProvider.io) {
|
||||
appPreferencesStore.store(PreferencesKeys.getTangemPayHideOnboardingKey(userWalletId), hide)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getHideMainOnboardingBanner(userWalletId: UserWalletId): Boolean {
|
||||
return withContext(dispatcherProvider.io) {
|
||||
appPreferencesStore.getSyncOrNull(
|
||||
key = PreferencesKeys.getTangemPayHideOnboardingKey(userWalletId),
|
||||
) == true
|
||||
}
|
||||
}
|
||||
|
||||
private fun createAuthTokensKey(address: String): String = "${AUTH_TOKENS_DEFAULT_KEY}_$address"
|
||||
|
||||
private fun createWithdrawOrderIdKey(userWalletId: UserWalletId): String = "${WITHDRAW_ORDER_ID_KEY}_$userWalletId"
|
||||
|
|
|
|||
|
|
@ -40,6 +40,8 @@ import com.tangem.features.tangempay.components.TangemPayDetailsContainerCompone
|
|||
import com.tangem.features.tangempay.components.TangemPayOnboardingComponent
|
||||
import com.tangem.features.tangempay.components.TangemPayOnboardingComponent.Params.ContinueOnboarding
|
||||
import com.tangem.features.tangempay.components.TangemPayOnboardingComponent.Params.Deeplink
|
||||
import com.tangem.features.tangempay.components.TangemPayOnboardingComponent.Params.FromBannerOnMain
|
||||
import com.tangem.features.tangempay.components.TangemPayOnboardingComponent.Params.FromBannerInSettings
|
||||
import com.tangem.features.tokendetails.TokenDetailsComponent
|
||||
import com.tangem.features.wallet.WalletEntryComponent
|
||||
import com.tangem.features.walletconnect.components.WalletConnectEntryComponent
|
||||
|
|
@ -667,6 +669,9 @@ internal class ChildFactory @Inject constructor(
|
|||
)
|
||||
is AppRoute.TangemPayOnboarding.Mode.Deeplink -> Deeplink(
|
||||
deeplink = mode.deeplink,
|
||||
)
|
||||
is AppRoute.TangemPayOnboarding.Mode.FromBannerInSettings -> FromBannerInSettings
|
||||
is AppRoute.TangemPayOnboarding.Mode.FromBannerOnMain -> FromBannerOnMain(
|
||||
userWalletId = mode.userWalletId,
|
||||
)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -433,13 +433,20 @@ sealed class AppRoute(val path: String) : Route {
|
|||
@Serializable
|
||||
data class Deeplink(
|
||||
val deeplink: String,
|
||||
val userWalletId: UserWalletId?,
|
||||
) : Mode()
|
||||
|
||||
@Serializable
|
||||
data class ContinueOnboarding(
|
||||
val userWalletId: UserWalletId?,
|
||||
val userWalletId: UserWalletId,
|
||||
) : Mode()
|
||||
|
||||
@Serializable
|
||||
data class FromBannerOnMain(
|
||||
val userWalletId: UserWalletId,
|
||||
) : Mode()
|
||||
|
||||
@Serializable
|
||||
data object FromBannerInSettings : Mode()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -128,6 +128,9 @@ interface TangemPayApi {
|
|||
@POST("v1/deeplink/validate")
|
||||
suspend fun validateDeeplink(@Body body: DeeplinkValidityRequest): ApiResponse<DeeplinkValidityResponse>
|
||||
|
||||
@GET("v1/customer/eligibility")
|
||||
suspend fun checkCustomerEligibility(): ApiResponse<CustomerEligibilityResponse>
|
||||
|
||||
@GET("v1/order/{order_id}")
|
||||
suspend fun getOrder(
|
||||
@Header("Authorization") authHeader: String,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
package com.tangem.datasource.api.pay.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class CustomerEligibilityResponse(
|
||||
@Json(name = "result") val result: Result?,
|
||||
@Json(name = "error") val error: String?,
|
||||
) {
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Result(
|
||||
@Json(name = "is_tangem_pay_available") val isTangemPayAvailable: Boolean,
|
||||
)
|
||||
}
|
||||
|
|
@ -192,6 +192,9 @@ object PreferencesKeys {
|
|||
fun getTangemPayCheckCustomerByWalletId(userWalletId: UserWalletId) =
|
||||
booleanPreferencesKey("tangem_pay_check_customer_by_wallet_id_$userWalletId")
|
||||
|
||||
fun getTangemPayHideOnboardingKey(userWalletId: UserWalletId) =
|
||||
booleanPreferencesKey("tangem_pay_hide_onboarding_key_$userWalletId")
|
||||
|
||||
// endregion
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,5 +30,9 @@ interface TangemPayStorage {
|
|||
|
||||
suspend fun deleteWithdrawOrder(userWalletId: UserWalletId)
|
||||
|
||||
suspend fun getHideMainOnboardingBanner(userWalletId: UserWalletId): Boolean
|
||||
|
||||
suspend fun storeHideOnboardingBanner(userWalletId: UserWalletId, hide: Boolean)
|
||||
|
||||
suspend fun clearAll(userWalletId: UserWalletId, customerWalletAddress: String)
|
||||
}
|
||||
9
core/ui/src/main/res/drawable/ic_tangem_pay_24.xml
Normal file
9
core/ui/src/main/res/drawable/ic_tangem_pay_24.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M18.327,4.365C20.007,4.365 20.847,4.365 21.489,4.692C22.053,4.98 22.512,5.439 22.8,6.003C23.127,6.645 23.127,7.485 23.127,9.165V14.836C23.127,16.516 23.127,17.356 22.8,17.998C22.512,18.562 22.053,19.021 21.489,19.309C20.847,19.636 20.007,19.636 18.327,19.636H5.675C3.995,19.636 3.154,19.636 2.513,19.309C1.948,19.021 1.49,18.562 1.202,17.998C0.875,17.356 0.875,16.516 0.875,14.836V9.165C0.875,7.485 0.875,6.645 1.202,6.003C1.49,5.439 1.948,4.98 2.513,4.692C3.154,4.365 3.995,4.365 5.675,4.365H18.327ZM14.403,9.195C13.006,9.195 11.988,9.949 11.988,10.995C11.988,11.79 12.708,12.208 13.217,12.46C13.767,12.711 13.979,12.878 13.936,13.129C13.936,13.505 13.513,13.673 13.09,13.673C12.581,13.673 12.073,13.547 11.607,13.338L11.354,14.51C11.862,14.719 12.412,14.803 12.921,14.803C14.488,14.844 15.462,14.092 15.462,12.962C15.462,11.539 13.471,11.455 13.471,10.828C13.513,10.535 13.767,10.367 14.064,10.367C14.53,10.325 15.038,10.409 15.462,10.618L15.716,9.447C15.292,9.28 14.826,9.195 14.403,9.195ZM2.881,9.321V9.489C3.389,9.573 3.855,9.74 4.278,9.949C4.448,10.033 4.563,10.21 4.617,10.41L5.761,14.762H7.285L9.572,9.321H8.09L6.565,13.004L5.973,9.865C5.93,9.573 5.676,9.321 5.337,9.321H2.881ZM10.164,9.321L8.979,14.762H10.419L11.604,9.321H10.164ZM18.13,9.321C17.876,9.321 17.622,9.489 17.537,9.74L15.42,14.762H16.902L17.198,13.967H19.02L19.188,14.762H20.502L19.358,9.321H18.13ZM18.76,12.838H17.574L18.337,10.787L18.76,12.838Z"
|
||||
android:fillColor="#656565"/>
|
||||
</vector>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 7.6 KiB |
|
|
@ -0,0 +1,94 @@
|
|||
package com.tangem.data.pay
|
||||
|
||||
import com.tangem.common.card.FirmwareVersion
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.isLocked
|
||||
import com.tangem.domain.models.wallet.isMultiCurrency
|
||||
import com.tangem.domain.pay.TangemPayEligibilityManager
|
||||
import com.tangem.domain.pay.repository.OnboardingRepository
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.features.hotwallet.HotWalletFeatureToggles
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class DefaultTangemPayEligibilityManager @Inject constructor(
|
||||
private val userWalletsListManager: UserWalletsListManager,
|
||||
private val userWalletsListRepository: UserWalletsListRepository,
|
||||
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
private val onboardingRepository: OnboardingRepository,
|
||||
) : TangemPayEligibilityManager {
|
||||
|
||||
private var cachedEligibleWallets: List<UserWallet>? = null
|
||||
private var eligibleWalletsDeferred: Deferred<List<UserWallet>>? = null
|
||||
private val loadMutex = Mutex()
|
||||
|
||||
override suspend fun getEligibleWallets(): List<UserWallet> {
|
||||
cachedEligibleWallets?.let { return it }
|
||||
|
||||
return loadMutex.withLock {
|
||||
cachedEligibleWallets?.let { return it }
|
||||
eligibleWalletsDeferred?.let { return it.await() }
|
||||
|
||||
coroutineScope {
|
||||
val deferred = async {
|
||||
getPossibleWalletsForTangemPay()
|
||||
.excludePaeraCustomers()
|
||||
.also { cachedEligibleWallets = it }
|
||||
}
|
||||
eligibleWalletsDeferred = deferred
|
||||
try {
|
||||
deferred.await()
|
||||
} finally {
|
||||
eligibleWalletsDeferred = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getPossibleWalletsForTangemPay(): List<UserWallet> {
|
||||
if (!onboardingRepository.checkCustomerEligibility()) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
val wallets = if (hotWalletFeatureToggles.isHotWalletEnabled) {
|
||||
userWalletsListRepository.userWallets.value
|
||||
} else {
|
||||
userWalletsListManager.userWalletsSync
|
||||
} ?: return emptyList()
|
||||
|
||||
return wallets.filter { wallet ->
|
||||
wallet.isMultiCurrency && !wallet.isLocked && wallet.isCompatible()
|
||||
}
|
||||
}
|
||||
|
||||
private fun UserWallet.isCompatible(): Boolean = when (this) {
|
||||
is UserWallet.Cold ->
|
||||
scanResponse.card.firmwareVersion >= FirmwareVersion.HDWalletAvailable
|
||||
is UserWallet.Hot -> true
|
||||
}
|
||||
|
||||
private suspend fun List<UserWallet>.excludePaeraCustomers(): List<UserWallet> {
|
||||
if (isEmpty()) return this
|
||||
|
||||
return coroutineScope {
|
||||
map { wallet ->
|
||||
async {
|
||||
val isCustomer = onboardingRepository
|
||||
.checkCustomerWallet(wallet.walletId)
|
||||
.getOrNull() == true
|
||||
wallet to isCustomer
|
||||
}
|
||||
}
|
||||
.awaitAll()
|
||||
.mapNotNull { (wallet, isCustomer) ->
|
||||
wallet.takeUnless { isCustomer }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,12 @@
|
|||
package com.tangem.data.pay.di
|
||||
|
||||
import com.tangem.data.pay.DefaultTangemPayCryptoCurrencyFactory
|
||||
import com.tangem.data.pay.DefaultTangemPayEligibilityManager
|
||||
import com.tangem.data.pay.repository.*
|
||||
import com.tangem.data.pay.usecase.DefaultGetTangemPayCurrencyStatusUseCase
|
||||
import com.tangem.data.pay.usecase.DefaultTangemPayWithdrawUseCase
|
||||
import com.tangem.domain.pay.TangemPayCryptoCurrencyFactory
|
||||
import com.tangem.domain.pay.TangemPayEligibilityManager
|
||||
import com.tangem.domain.pay.repository.*
|
||||
import com.tangem.domain.pay.usecase.ProduceTangemPayInitialDataUseCase
|
||||
import com.tangem.domain.pay.usecase.TangemPayMainScreenCustomerInfoUseCase
|
||||
|
|
@ -62,6 +64,10 @@ internal interface TangemPayDataModule {
|
|||
@Singleton
|
||||
fun bindTangemPayWithdrawUseCase(impl: DefaultTangemPayWithdrawUseCase): TangemPayWithdrawUseCase
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindTangemPayEligibilityManager(impl: DefaultTangemPayEligibilityManager): TangemPayEligibilityManager
|
||||
|
||||
companion object {
|
||||
@Provides
|
||||
@Singleton
|
||||
|
|
@ -69,11 +75,13 @@ internal interface TangemPayDataModule {
|
|||
repository: OnboardingRepository,
|
||||
customerOrderRepository: CustomerOrderRepository,
|
||||
tangemPayOnboardingRepository: OnboardingRepository,
|
||||
eligibilityManager: TangemPayEligibilityManager,
|
||||
): TangemPayMainScreenCustomerInfoUseCase {
|
||||
return TangemPayMainScreenCustomerInfoUseCase(
|
||||
repository = repository,
|
||||
customerOrderRepository = customerOrderRepository,
|
||||
tangemPayOnboardingRepository = tangemPayOnboardingRepository,
|
||||
eligibilityManager = eligibilityManager,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -84,7 +84,6 @@ internal class DefaultOnboardingRepository @Inject constructor(
|
|||
}
|
||||
|
||||
override suspend fun getCustomerInfo(userWalletId: UserWalletId): Either<VisaApiError, CustomerInfo> {
|
||||
// TODO implement selector
|
||||
return requestHelper.performRequest(userWalletId) { authHeader -> tangemPayApi.getCustomerMe(authHeader) }
|
||||
.map { response -> getCustomerInfo(userWalletId = userWalletId, response = response.result) }
|
||||
}
|
||||
|
|
@ -193,4 +192,19 @@ internal class DefaultOnboardingRepository @Inject constructor(
|
|||
error
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun checkCustomerEligibility(): Boolean {
|
||||
val response = requestHelper.performWithoutToken {
|
||||
tangemPayApi.checkCustomerEligibility()
|
||||
}.getOrNull()
|
||||
return response?.result?.isTangemPayAvailable == true
|
||||
}
|
||||
|
||||
override suspend fun getHideMainOnboardingBanner(userWalletId: UserWalletId): Boolean {
|
||||
return tangemPayStorage.getHideMainOnboardingBanner(userWalletId)
|
||||
}
|
||||
|
||||
override suspend fun setHideMainOnboardingBanner(userWalletId: UserWalletId) {
|
||||
tangemPayStorage.storeHideOnboardingBanner(userWalletId, hide = true)
|
||||
}
|
||||
}
|
||||
|
|
@ -89,6 +89,19 @@ internal class TangemPayRequestPerformer @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
suspend fun <T : Any> performWithoutToken(requestBlock: suspend () -> ApiResponse<T>): Either<VisaApiError, T> =
|
||||
withContext(dispatchers.io) {
|
||||
catch(
|
||||
block = {
|
||||
when (val apiResponse = requestBlock()) {
|
||||
is ApiResponse.Error -> errorConverter.convert(apiResponse.cause).left()
|
||||
is ApiResponse.Success<T> -> apiResponse.data.right()
|
||||
}
|
||||
},
|
||||
catch = { errorConverter.convert(it).left() },
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun <T : Any> performRequest(
|
||||
userWalletId: UserWalletId,
|
||||
requestBlock: suspend (header: String) -> ApiResponse<T>,
|
||||
|
|
|
|||
|
|
@ -1,38 +0,0 @@
|
|||
package com.tangem.data.pay.util
|
||||
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.features.hotwallet.HotWalletFeatureToggles
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.first
|
||||
import javax.inject.Inject
|
||||
|
||||
// TODO remove after implement wallet selector in pay
|
||||
class TangemPayWalletsManager @Inject constructor(
|
||||
private val manager: UserWalletsListManager,
|
||||
private val repository: UserWalletsListRepository,
|
||||
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
) {
|
||||
|
||||
@Deprecated("Don't use and put userWallet in features that need it")
|
||||
suspend fun getDefaultWalletForTangemPay(): UserWallet.Cold {
|
||||
val userWalletsFlow = if (useNewRepository()) repository.userWallets else manager.userWallets
|
||||
val userWallets = userWalletsFlow.filter { !it.isNullOrEmpty() }.first()
|
||||
return findColdWallet(userWallets)
|
||||
}
|
||||
|
||||
@Deprecated("Don't use and put userWallet in features that need it")
|
||||
fun getDefaultWalletForTangemPayBlocking(): UserWallet.Cold {
|
||||
val userWallets = if (useNewRepository()) repository.userWallets.value else manager.userWalletsSync
|
||||
return findColdWallet(userWallets)
|
||||
}
|
||||
|
||||
private fun useNewRepository(): Boolean = hotWalletFeatureToggles.isHotWalletEnabled
|
||||
|
||||
private fun findColdWallet(userWallets: List<UserWallet>?): UserWallet.Cold {
|
||||
return userWallets?.find {
|
||||
it is UserWallet.Cold && it.isMultiCurrency
|
||||
} as? UserWallet.Cold ?: error("Cannot find cold user wallet")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.domain.pay
|
||||
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
|
||||
interface TangemPayEligibilityManager {
|
||||
|
||||
suspend fun getEligibleWallets(): List<UserWallet>
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import java.math.BigDecimal
|
|||
|
||||
sealed class MainCustomerInfoContentState {
|
||||
object Loading : MainCustomerInfoContentState()
|
||||
object OnboardingBanner : MainCustomerInfoContentState()
|
||||
data class Content(val info: MainScreenCustomerInfo) : MainCustomerInfoContentState()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,5 +25,11 @@ interface OnboardingRepository {
|
|||
|
||||
suspend fun checkCustomerWallet(userWalletId: UserWalletId): Either<VisaApiError, Boolean>
|
||||
|
||||
suspend fun checkCustomerEligibility(): Boolean
|
||||
|
||||
fun getSavedCustomerInfo(userWalletId: UserWalletId): CustomerInfo?
|
||||
|
||||
suspend fun getHideMainOnboardingBanner(userWalletId: UserWalletId): Boolean
|
||||
|
||||
suspend fun setHideMainOnboardingBanner(userWalletId: UserWalletId)
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import arrow.core.Either
|
|||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.pay.TangemPayEligibilityManager
|
||||
import com.tangem.domain.pay.model.*
|
||||
import com.tangem.domain.pay.repository.CustomerOrderRepository
|
||||
import com.tangem.domain.pay.repository.OnboardingRepository
|
||||
|
|
@ -21,6 +22,7 @@ class TangemPayMainScreenCustomerInfoUseCase(
|
|||
private val repository: OnboardingRepository,
|
||||
private val customerOrderRepository: CustomerOrderRepository,
|
||||
private val tangemPayOnboardingRepository: OnboardingRepository,
|
||||
private val eligibilityManager: TangemPayEligibilityManager,
|
||||
) {
|
||||
|
||||
val state: StateFlow<Map<UserWalletId, Either<TangemPayCustomerInfoError, MainCustomerInfoContentState>>>
|
||||
|
|
@ -46,8 +48,17 @@ class TangemPayMainScreenCustomerInfoUseCase(
|
|||
.map(MainCustomerInfoContentState::Content)
|
||||
updateState(userWalletId, result)
|
||||
} else {
|
||||
// ignore if there's no TangemPay
|
||||
updateState(userWalletId, TangemPayCustomerInfoError.UnknownError.left())
|
||||
// if there's no tangem pay, check eligibility and show onboarding banner
|
||||
val isEligible = eligibilityManager.getEligibleWallets().any { it.walletId == userWalletId }
|
||||
if (isEligible) {
|
||||
if (tangemPayOnboardingRepository.getHideMainOnboardingBanner(userWalletId)) {
|
||||
updateState(userWalletId, TangemPayCustomerInfoError.UnknownError.left())
|
||||
} else {
|
||||
updateState(userWalletId, MainCustomerInfoContentState.OnboardingBanner.right())
|
||||
}
|
||||
} else {
|
||||
updateState(userWalletId, TangemPayCustomerInfoError.UnknownError.left())
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ dependencies {
|
|||
implementation(projects.features.tester.api)
|
||||
implementation(projects.features.createWalletSelection.api)
|
||||
implementation(projects.features.hotWallet.api)
|
||||
implementation(projects.features.tangempay.details.api)
|
||||
|
||||
/* Project - Core */
|
||||
implementation(projects.core.decompose)
|
||||
|
|
@ -46,6 +47,7 @@ dependencies {
|
|||
implementation(projects.domain.balanceHiding)
|
||||
implementation(projects.domain.balanceHiding.models)
|
||||
implementation(projects.domain.legacy)
|
||||
implementation(projects.domain.visa)
|
||||
|
||||
/* SDK */
|
||||
// TODO: For TangemError model, should be removed after card domain scanning refactoring
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import com.tangem.domain.feedback.models.FeedbackEmailType
|
|||
import com.tangem.domain.feedback.models.WalletMetaInfo
|
||||
import com.tangem.domain.feedback.repository.FeedbackFeatureToggles
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.pay.TangemPayEligibilityManager
|
||||
import com.tangem.domain.redux.LegacyAction
|
||||
import com.tangem.domain.redux.ReduxStateHolder
|
||||
import com.tangem.domain.walletconnect.CheckIsWalletConnectAvailableUseCase
|
||||
|
|
@ -34,6 +35,7 @@ import com.tangem.features.details.entity.SelectEmailFeedbackTypeBS
|
|||
import com.tangem.features.details.utils.ItemsBuilder
|
||||
import com.tangem.features.details.utils.SocialsBuilder
|
||||
import com.tangem.features.hotwallet.HotWalletFeatureToggles
|
||||
import com.tangem.features.tangempay.TangemPayFeatureToggles
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.version.AppVersionProvider
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
|
@ -51,7 +53,7 @@ import javax.inject.Inject
|
|||
@Suppress("LongParameterList")
|
||||
internal class DetailsModel @Inject constructor(
|
||||
socialsBuilder: SocialsBuilder,
|
||||
itemsBuilder: ItemsBuilder,
|
||||
private val itemsBuilder: ItemsBuilder,
|
||||
private val appVersionProvider: AppVersionProvider,
|
||||
private val checkIsWalletConnectAvailableUseCase: CheckIsWalletConnectAvailableUseCase,
|
||||
private val router: Router,
|
||||
|
|
@ -68,6 +70,8 @@ internal class DetailsModel @Inject constructor(
|
|||
private val generateBuyTangemCardLinkUseCase: GenerateBuyTangemCardLinkUseCase,
|
||||
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val tangemPayEligibilityManager: TangemPayEligibilityManager,
|
||||
private val tangemPayFeatureToggles: TangemPayFeatureToggles,
|
||||
) : Model() {
|
||||
|
||||
private val params: DetailsComponent.Params = paramsContainer.require()
|
||||
|
|
@ -100,6 +104,8 @@ internal class DetailsModel @Inject constructor(
|
|||
),
|
||||
)
|
||||
|
||||
addTangemPayItemIfEligible()
|
||||
|
||||
state = MutableStateFlow(
|
||||
value = DetailsUM(
|
||||
items = items.value,
|
||||
|
|
@ -239,6 +245,16 @@ internal class DetailsModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun addTangemPayItemIfEligible() {
|
||||
if (!tangemPayFeatureToggles.isTangemPayEnabled) return
|
||||
modelScope.launch {
|
||||
val isEligible = tangemPayEligibilityManager.getEligibleWallets().isNotEmpty()
|
||||
if (isEligible) {
|
||||
items.update { itemsBuilder.addVisaItem(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getAppVersion(): String = "${appVersionProvider.versionName} (${appVersionProvider.versionCode})"
|
||||
|
||||
private suspend fun buildBuyLink(): String {
|
||||
|
|
|
|||
|
|
@ -37,6 +37,17 @@ internal class ItemsBuilder @Inject constructor(private val router: Router) {
|
|||
).let(::add)
|
||||
}.toImmutableList()
|
||||
|
||||
fun addVisaItem(items: ImmutableList<DetailsItemUM>): ImmutableList<DetailsItemUM> {
|
||||
return items.toMutableList().map { block ->
|
||||
if (block.id == "shop" && block is DetailsItemUM.Basic) {
|
||||
val newItems = block.items.toMutableList().apply { add(getVisaItem()) }
|
||||
block.copy(items = newItems.toImmutableList())
|
||||
} else {
|
||||
block
|
||||
}
|
||||
}.toImmutableList()
|
||||
}
|
||||
|
||||
private fun buildWalletConnectBlock(isWalletConnectAvailable: Boolean, userWalletId: UserWalletId): DetailsItemUM? {
|
||||
return if (isWalletConnectAvailable) {
|
||||
DetailsItemUM.WalletConnect(
|
||||
|
|
@ -114,4 +125,15 @@ internal class ItemsBuilder @Inject constructor(private val router: Router) {
|
|||
).let(::add)
|
||||
}.toPersistentList(),
|
||||
)
|
||||
|
||||
private fun getVisaItem(): DetailsItemUM.Basic.Item = DetailsItemUM.Basic.Item(
|
||||
id = "get_tangem_visa",
|
||||
block = BlockUM(
|
||||
text = resourceReference(R.string.details_get_visa),
|
||||
iconRes = R.drawable.ic_tangem_pay_24,
|
||||
onClick = {
|
||||
router.push(AppRoute.TangemPayOnboarding(AppRoute.TangemPayOnboarding.Mode.FromBannerInSettings))
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -8,16 +8,19 @@ interface TangemPayOnboardingComponent : ComposableContentComponent {
|
|||
|
||||
sealed class Params {
|
||||
|
||||
abstract val userWalletId: UserWalletId?
|
||||
|
||||
data class Deeplink(
|
||||
val deeplink: String,
|
||||
override val userWalletId: UserWalletId?,
|
||||
) : Params()
|
||||
|
||||
data class ContinueOnboarding(
|
||||
override val userWalletId: UserWalletId?,
|
||||
val userWalletId: UserWalletId,
|
||||
) : Params()
|
||||
|
||||
data class FromBannerOnMain(
|
||||
val userWalletId: UserWalletId,
|
||||
) : Params()
|
||||
|
||||
data object FromBannerInSettings : Params()
|
||||
}
|
||||
|
||||
interface Factory : ComponentFactory<Params, TangemPayOnboardingComponent>
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ dependencies {
|
|||
implementation(projects.features.tangempay.onboarding.api)
|
||||
implementation(projects.features.tangempay.details.api)
|
||||
implementation(projects.features.kyc.api)
|
||||
implementation(projects.features.wallet.api)
|
||||
implementation(projects.features.hotWallet.api)
|
||||
|
||||
/** Domain */
|
||||
implementation(projects.domain.visa)
|
||||
|
|
@ -40,6 +42,7 @@ dependencies {
|
|||
implementation(deps.compose.material3)
|
||||
implementation(deps.compose.ui)
|
||||
implementation(deps.compose.ui.tooling)
|
||||
implementation(deps.decompose.ext.compose)
|
||||
|
||||
/** DI */
|
||||
implementation(deps.hilt.android)
|
||||
|
|
@ -48,4 +51,5 @@ dependencies {
|
|||
/** Other */
|
||||
implementation(deps.timber)
|
||||
implementation(deps.arrow.core)
|
||||
implementation(deps.kotlin.immutable.collections)
|
||||
}
|
||||
|
|
@ -4,9 +4,15 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.arkivanov.decompose.extensions.compose.subscribeAsState
|
||||
import com.arkivanov.decompose.router.slot.childSlot
|
||||
import com.arkivanov.decompose.ComponentContext
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.context.childByContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
|
||||
import com.tangem.features.tangempay.model.TangemPayOnboardingModel
|
||||
import com.tangem.features.tangempay.ui.TangemPayOnboardingNavigation
|
||||
import com.tangem.features.tangempay.ui.TandemPayOnboardingScreen
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
|
|
@ -19,10 +25,32 @@ internal class DefaultTangemPayOnboardingComponent @AssistedInject constructor(
|
|||
|
||||
private val model: TangemPayOnboardingModel = getOrCreateModel(params)
|
||||
|
||||
private val bottomSheetSlot = childSlot(
|
||||
source = model.bottomSheetNavigation,
|
||||
serializer = TangemPayOnboardingNavigation.serializer(),
|
||||
handleBackButton = false,
|
||||
childFactory = ::bottomSheetChild,
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val state by model.uiState.collectAsStateWithLifecycle()
|
||||
val bottomSheet by bottomSheetSlot.subscribeAsState()
|
||||
TandemPayOnboardingScreen(modifier = modifier, state = state)
|
||||
bottomSheet.child?.instance?.BottomSheet()
|
||||
}
|
||||
|
||||
private fun bottomSheetChild(
|
||||
navigation: TangemPayOnboardingNavigation,
|
||||
componentContext: ComponentContext,
|
||||
): ComposableBottomSheetComponent {
|
||||
val context = childByContext(componentContext)
|
||||
return when (navigation) {
|
||||
is TangemPayOnboardingNavigation.WalletSelector -> TangemPayWalletSelectorComponent(
|
||||
appComponentContext = context,
|
||||
params = TangemPayWalletSelectorComponent.Params(listener = model, walletsIds = navigation.walletsIds),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
package com.tangem.features.tangempay.components
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.features.tangempay.model.TangemPayWalletSelectorModel
|
||||
import com.tangem.features.tangempay.ui.WalletSelectorBottomSheet
|
||||
|
||||
internal class TangemPayWalletSelectorComponent(
|
||||
appComponentContext: AppComponentContext,
|
||||
params: Params,
|
||||
) : ComposableBottomSheetComponent, AppComponentContext by appComponentContext {
|
||||
|
||||
private val model: TangemPayWalletSelectorModel = getOrCreateModel(params = params)
|
||||
|
||||
override fun dismiss() {
|
||||
model.onDismiss()
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun BottomSheet() {
|
||||
val state by model.uiState.collectAsStateWithLifecycle()
|
||||
WalletSelectorBottomSheet(state = state)
|
||||
}
|
||||
|
||||
data class Params(
|
||||
val listener: WalletSelectorListener,
|
||||
val walletsIds: List<UserWalletId>,
|
||||
)
|
||||
}
|
||||
|
||||
internal interface WalletSelectorListener {
|
||||
fun onWalletSelected(userWalletId: UserWalletId)
|
||||
fun onWalletSelectorDismiss()
|
||||
}
|
||||
|
|
@ -3,7 +3,6 @@ package com.tangem.features.tangempay.deeplink
|
|||
import android.net.Uri
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
||||
import com.tangem.features.tangempay.TangemPayFeatureToggles
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
|
|
@ -13,15 +12,12 @@ internal class DefaultOnboardVisaDeepLinkHandler @AssistedInject constructor(
|
|||
@Assisted uri: Uri,
|
||||
appRouter: AppRouter,
|
||||
tangemPayFeatureToggles: TangemPayFeatureToggles,
|
||||
getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
|
||||
) : OnboardVisaDeepLinkHandler {
|
||||
|
||||
init {
|
||||
if (tangemPayFeatureToggles.isTangemPayEnabled) {
|
||||
val userWallet = getSelectedWalletSyncUseCase.invoke().getOrNull()
|
||||
val mode = AppRoute.TangemPayOnboarding.Mode.Deeplink(
|
||||
deeplink = uri.toString(),
|
||||
userWalletId = userWallet?.walletId,
|
||||
)
|
||||
appRouter.push(AppRoute.TangemPayOnboarding(mode))
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.tangem.features.tangempay.di
|
|||
import com.tangem.core.decompose.di.ModelComponent
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.features.tangempay.model.TangemPayOnboardingModel
|
||||
import com.tangem.features.tangempay.model.TangemPayWalletSelectorModel
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -16,5 +17,10 @@ internal interface TangemPayOnboardingModelsModule {
|
|||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(TangemPayOnboardingModel::class)
|
||||
fun bindModel(model: TangemPayOnboardingModel): Model
|
||||
fun bindTangemPayOnboardingModel(model: TangemPayOnboardingModel): Model
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(TangemPayWalletSelectorModel::class)
|
||||
fun bindTangemPayWalletSelectorModel(model: TangemPayWalletSelectorModel): Model
|
||||
}
|
||||
|
|
@ -1,6 +1,9 @@
|
|||
package com.tangem.features.tangempay.model
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.arkivanov.decompose.router.slot.SlotNavigation
|
||||
import com.arkivanov.decompose.router.slot.activate
|
||||
import com.arkivanov.decompose.router.slot.dismiss
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
|
|
@ -8,17 +11,16 @@ import com.tangem.core.decompose.model.Model
|
|||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.core.navigation.url.UrlOpener
|
||||
import com.tangem.data.pay.util.TangemPayWalletsManager
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.models.wallet.isMultiCurrency
|
||||
import com.tangem.domain.pay.TangemPayEligibilityManager
|
||||
import com.tangem.domain.pay.repository.OnboardingRepository
|
||||
import com.tangem.domain.pay.usecase.ProduceTangemPayInitialDataUseCase
|
||||
import com.tangem.domain.tangempay.TangemPayAnalyticsEvents
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.features.tangempay.TangemPayConstants
|
||||
import com.tangem.features.tangempay.components.TangemPayOnboardingComponent
|
||||
import com.tangem.features.tangempay.components.WalletSelectorListener
|
||||
import com.tangem.features.tangempay.model.transformers.TangemPayOnboardingButtonLoadingTransformer
|
||||
import com.tangem.features.tangempay.ui.TangemPayOnboardingNavigation
|
||||
import com.tangem.features.tangempay.ui.TangemPayOnboardingScreenState
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
|
@ -40,27 +42,33 @@ internal class TangemPayOnboardingModel @Inject constructor(
|
|||
private val repository: OnboardingRepository,
|
||||
private val produceInitialDataUseCase: ProduceTangemPayInitialDataUseCase,
|
||||
private val urlOpener: UrlOpener,
|
||||
private val tangemPayWalletsManager: TangemPayWalletsManager,
|
||||
private val getUserWalletUseCase: GetUserWalletUseCase,
|
||||
private val getSelectedWalletUseCase: GetSelectedWalletUseCase,
|
||||
) : Model() {
|
||||
private val eligibilityManager: TangemPayEligibilityManager,
|
||||
) : Model(), WalletSelectorListener {
|
||||
|
||||
private val params = paramsContainer.require<TangemPayOnboardingComponent.Params>()
|
||||
|
||||
val bottomSheetNavigation: SlotNavigation<TangemPayOnboardingNavigation> = SlotNavigation()
|
||||
|
||||
val uiState: StateFlow<TangemPayOnboardingScreenState>
|
||||
field = MutableStateFlow(getInitialState())
|
||||
|
||||
init {
|
||||
analytics.send(TangemPayAnalyticsEvents.ActivationScreenOpened())
|
||||
init()
|
||||
}
|
||||
|
||||
private fun init() {
|
||||
modelScope.launch {
|
||||
when (params) {
|
||||
is TangemPayOnboardingComponent.Params.ContinueOnboarding -> {
|
||||
checkCustomerInfo()
|
||||
}
|
||||
is TangemPayOnboardingComponent.Params.Deeplink -> {
|
||||
repository.validateDeeplink(params.deeplink)
|
||||
.onRight { isValid -> if (isValid) showOnboarding() else back() }
|
||||
.onLeft { back() }
|
||||
}
|
||||
is TangemPayOnboardingComponent.Params.ContinueOnboarding,
|
||||
is TangemPayOnboardingComponent.Params.FromBannerInSettings,
|
||||
is TangemPayOnboardingComponent.Params.FromBannerOnMain,
|
||||
-> showOnboarding()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -78,42 +86,25 @@ internal class TangemPayOnboardingModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun checkCustomerInfo() {
|
||||
// TODO implement selector
|
||||
val userWalletId = getUserWalletForPay(params.userWalletId)
|
||||
repository.getCustomerInfo(
|
||||
userWalletId = userWalletId,
|
||||
)
|
||||
// selector
|
||||
.onRight { customerInfo ->
|
||||
when {
|
||||
!customerInfo.isKycApproved -> {
|
||||
when (params) {
|
||||
is TangemPayOnboardingComponent.Params.Deeplink -> showOnboarding()
|
||||
else -> openKyc()
|
||||
private fun checkCustomerInfo(userWalletId: UserWalletId) {
|
||||
modelScope.launch {
|
||||
uiState.transformerUpdate(TangemPayOnboardingButtonLoadingTransformer(isLoading = true))
|
||||
repository.getCustomerInfo(
|
||||
userWalletId = userWalletId,
|
||||
)
|
||||
.onRight { customerInfo ->
|
||||
uiState.transformerUpdate(TangemPayOnboardingButtonLoadingTransformer(isLoading = false))
|
||||
when {
|
||||
!customerInfo.isKycApproved -> {
|
||||
when (params) {
|
||||
is TangemPayOnboardingComponent.Params.ContinueOnboarding -> openKyc(userWalletId)
|
||||
else -> startOnboarding(userWalletId)
|
||||
}
|
||||
}
|
||||
else -> back()
|
||||
}
|
||||
else -> back()
|
||||
}
|
||||
}
|
||||
.onLeft { back() }
|
||||
}
|
||||
|
||||
private fun getUserWalletForPay(userWalletId: UserWalletId?): UserWalletId {
|
||||
val userWallet = userWalletId?.let { getUserWalletUseCase(it).getOrNull() }
|
||||
return if (userWallet?.isMultiCurrency == true) {
|
||||
userWallet.walletId
|
||||
} else {
|
||||
tryGetSelectedWalletId()
|
||||
}
|
||||
}
|
||||
|
||||
private fun tryGetSelectedWalletId(): UserWalletId {
|
||||
val selectedWallet = getSelectedWalletUseCase.sync().getOrNull()
|
||||
return if (selectedWallet?.isMultiCurrency == true) {
|
||||
selectedWallet.walletId
|
||||
} else {
|
||||
tangemPayWalletsManager.getDefaultWalletForTangemPayBlocking().walletId
|
||||
.onLeft { back() }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -122,21 +113,52 @@ internal class TangemPayOnboardingModel @Inject constructor(
|
|||
urlOpener.openUrl(TangemPayConstants.TERMS_AND_LIMITS_LINK)
|
||||
}
|
||||
|
||||
@Suppress("NullableToStringCall")
|
||||
private fun onGetCardClick() {
|
||||
analytics.send(TangemPayAnalyticsEvents.GetCardClicked())
|
||||
modelScope.launch {
|
||||
val eligibleWalletsIds = eligibilityManager.getEligibleWallets().map { it.walletId }
|
||||
if (eligibleWalletsIds.isEmpty()) {
|
||||
back()
|
||||
return@launch
|
||||
}
|
||||
when (params) {
|
||||
is TangemPayOnboardingComponent.Params.ContinueOnboarding -> {
|
||||
checkCustomerInfo(params.userWalletId)
|
||||
}
|
||||
is TangemPayOnboardingComponent.Params.FromBannerOnMain -> {
|
||||
if (eligibleWalletsIds.any { walletId -> walletId == params.userWalletId }) {
|
||||
checkCustomerInfo(userWalletId = params.userWalletId)
|
||||
} else {
|
||||
openWalletSelectorIfNeeds(eligibleWalletsIds)
|
||||
}
|
||||
}
|
||||
is TangemPayOnboardingComponent.Params.Deeplink,
|
||||
is TangemPayOnboardingComponent.Params.FromBannerInSettings,
|
||||
-> {
|
||||
openWalletSelectorIfNeeds(eligibleWalletsIds)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun openWalletSelectorIfNeeds(eligibleWalletsIds: List<UserWalletId>) {
|
||||
if (eligibleWalletsIds.size == 1) {
|
||||
checkCustomerInfo(userWalletId = eligibleWalletsIds[0])
|
||||
} else {
|
||||
bottomSheetNavigation.activate(TangemPayOnboardingNavigation.WalletSelector(eligibleWalletsIds))
|
||||
}
|
||||
}
|
||||
|
||||
private fun startOnboarding(userWalletId: UserWalletId) {
|
||||
uiState.transformerUpdate(TangemPayOnboardingButtonLoadingTransformer(isLoading = true))
|
||||
modelScope.launch {
|
||||
// TODO implement selector
|
||||
val userWalletId = getUserWalletForPay(params.userWalletId)
|
||||
val result = produceInitialDataUseCase(userWalletId)
|
||||
if (result.isLeft()) {
|
||||
Timber.e("Error producing initial data: ${result.leftOrNull()?.message}")
|
||||
val errorMessage = result.leftOrNull()?.message ?: "Unknown error"
|
||||
Timber.e("Error producing initial data: $errorMessage")
|
||||
uiState.transformerUpdate(TangemPayOnboardingButtonLoadingTransformer(isLoading = false))
|
||||
return@launch
|
||||
}
|
||||
|
||||
// TODO implement selector
|
||||
repository.getCustomerInfo(
|
||||
userWalletId = userWalletId,
|
||||
).fold(
|
||||
|
|
@ -148,20 +170,17 @@ internal class TangemPayOnboardingModel @Inject constructor(
|
|||
if (customerInfo.isKycApproved) {
|
||||
back()
|
||||
} else {
|
||||
openKyc()
|
||||
openKyc(userWalletId)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun openKyc() {
|
||||
// TODO implement selector
|
||||
private fun openKyc(userWalletId: UserWalletId) {
|
||||
router.replaceAll(
|
||||
AppRoute.Wallet,
|
||||
AppRoute.Kyc(
|
||||
userWalletId = getUserWalletForPay(params.userWalletId),
|
||||
),
|
||||
AppRoute.Kyc(userWalletId = userWalletId),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -172,4 +191,13 @@ internal class TangemPayOnboardingModel @Inject constructor(
|
|||
private fun getInitialState(): TangemPayOnboardingScreenState {
|
||||
return TangemPayOnboardingScreenState.Loading(onBack = ::back)
|
||||
}
|
||||
|
||||
override fun onWalletSelected(userWalletId: UserWalletId) {
|
||||
bottomSheetNavigation.dismiss()
|
||||
checkCustomerInfo(userWalletId)
|
||||
}
|
||||
|
||||
override fun onWalletSelectorDismiss() {
|
||||
bottomSheetNavigation.dismiss()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
package com.tangem.features.tangempay.model
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.decompose.ui.UiMessageSender
|
||||
import com.tangem.features.tangempay.components.TangemPayWalletSelectorComponent
|
||||
import com.tangem.features.tangempay.ui.WalletSelectorBSContentUM
|
||||
import com.tangem.features.wallet.utils.UserWalletsFetcher
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@Stable
|
||||
@ModelScoped
|
||||
internal class TangemPayWalletSelectorModel @Inject constructor(
|
||||
paramsContainer: ParamsContainer,
|
||||
userWalletsFetcherFactory: UserWalletsFetcher.Factory,
|
||||
messageSender: UiMessageSender,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
) : Model() {
|
||||
|
||||
private val params = paramsContainer.require<TangemPayWalletSelectorComponent.Params>()
|
||||
|
||||
private val userWalletsFetcher = userWalletsFetcherFactory.create(
|
||||
messageSender = messageSender,
|
||||
onlyMultiCurrency = true,
|
||||
isAuthMode = false,
|
||||
isClickableIfLocked = false,
|
||||
onWalletClick = { params.listener.onWalletSelected(it) },
|
||||
)
|
||||
|
||||
init {
|
||||
fetchUserWalletsUM()
|
||||
}
|
||||
|
||||
val uiState: StateFlow<WalletSelectorBSContentUM>
|
||||
field = MutableStateFlow(getInitialState())
|
||||
|
||||
private fun getInitialState(): WalletSelectorBSContentUM {
|
||||
return WalletSelectorBSContentUM(
|
||||
userWallets = persistentListOf(),
|
||||
onDismiss = { params.listener.onWalletSelectorDismiss() },
|
||||
)
|
||||
}
|
||||
|
||||
private fun fetchUserWalletsUM() {
|
||||
modelScope.launch {
|
||||
val eligibleWalletsIds = params.walletsIds.map { it.stringValue }.toSet()
|
||||
userWalletsFetcher.userWallets.collectLatest { userWalletsListUM ->
|
||||
uiState.update {
|
||||
WalletSelectorBSContentUM(
|
||||
userWallets = userWalletsListUM
|
||||
.filter { wallet -> wallet.id in eligibleWalletsIds }
|
||||
.toImmutableList(),
|
||||
onDismiss = { params.listener.onWalletSelectorDismiss() },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onDismiss() {
|
||||
params.listener.onWalletSelectorDismiss()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.features.tangempay.ui
|
||||
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
internal sealed class TangemPayOnboardingNavigation {
|
||||
|
||||
@Serializable
|
||||
data class WalletSelector(
|
||||
val walletsIds: List<UserWalletId>,
|
||||
) : TangemPayOnboardingNavigation()
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.features.tangempay.ui
|
||||
|
||||
import com.tangem.common.ui.userwallet.state.UserWalletItemUM
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
internal data class WalletSelectorBSContentUM(
|
||||
val userWallets: ImmutableList<UserWalletItemUM>,
|
||||
val onDismiss: () -> Unit,
|
||||
) : TangemBottomSheetConfigContent
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
package com.tangem.features.tangempay.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.key
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.common.ui.userwallet.UserWalletItem
|
||||
import com.tangem.common.ui.userwallet.state.UserWalletItemUM
|
||||
import com.tangem.core.ui.components.SpacerH
|
||||
import com.tangem.core.ui.components.appbar.TangemTopAppBar
|
||||
import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM
|
||||
import com.tangem.core.ui.components.block.BlockCard
|
||||
import com.tangem.core.ui.components.block.TangemBlockCardColors
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.features.tangempay.onboarding.impl.R
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
@Composable
|
||||
internal fun WalletSelectorBottomSheet(state: WalletSelectorBSContentUM) {
|
||||
TangemBottomSheet<TangemBottomSheetConfigContent.Empty>(
|
||||
config = TangemBottomSheetConfig(
|
||||
isShown = true,
|
||||
onDismissRequest = state.onDismiss,
|
||||
content = TangemBottomSheetConfigContent.Empty,
|
||||
),
|
||||
onBack = state.onDismiss,
|
||||
containerColor = TangemTheme.colors.background.tertiary,
|
||||
title = { content ->
|
||||
TangemTopAppBar(
|
||||
title = resourceReference(R.string.common_choose_wallet),
|
||||
titleAlignment = Alignment.CenterHorizontally,
|
||||
endButton = TopAppBarButtonUM.Close(onCloseClick = state.onDismiss),
|
||||
)
|
||||
},
|
||||
) { content ->
|
||||
Content(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(
|
||||
horizontal = TangemTheme.dimens.spacing16,
|
||||
vertical = TangemTheme.dimens.spacing8,
|
||||
),
|
||||
state = state,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Content(state: WalletSelectorBSContentUM, modifier: Modifier = Modifier) {
|
||||
val bottomBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getBottom(this).toDp() }
|
||||
|
||||
Column(
|
||||
modifier = modifier
|
||||
.verticalScroll(rememberScrollState()),
|
||||
) {
|
||||
BlockCard(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
colors = TangemBlockCardColors.copy(
|
||||
containerColor = TangemTheme.colors.background.action,
|
||||
disabledContainerColor = TangemTheme.colors.background.action,
|
||||
),
|
||||
) {
|
||||
state.userWallets.forEach { state ->
|
||||
key(state.id) {
|
||||
UserWalletItem(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
blockColors = TangemBlockCardColors.copy(
|
||||
containerColor = TangemTheme.colors.background.action,
|
||||
disabledContainerColor = TangemTheme.colors.background.action,
|
||||
),
|
||||
state = state,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
SpacerH(bottomBarHeight)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun PreviewWalletSelectorBottomSheet() {
|
||||
TangemThemePreview {
|
||||
WalletSelectorBottomSheet(
|
||||
state = WalletSelectorBSContentUM(
|
||||
userWallets = persistentListOf(
|
||||
UserWalletItemUM(
|
||||
id = "1",
|
||||
name = stringReference("Wallet 1"),
|
||||
information = UserWalletItemUM.Information.Loaded(TextReference.Str("3 cards")),
|
||||
balance = UserWalletItemUM.Balance.Loading,
|
||||
isEnabled = true,
|
||||
endIcon = UserWalletItemUM.EndIcon.None,
|
||||
onClick = {},
|
||||
),
|
||||
UserWalletItemUM(
|
||||
id = "2",
|
||||
name = stringReference("Wallet 2"),
|
||||
information = UserWalletItemUM.Information.Loaded(TextReference.Str("3 cards")),
|
||||
balance = UserWalletItemUM.Balance.Loading,
|
||||
isEnabled = true,
|
||||
endIcon = UserWalletItemUM.EndIcon.None,
|
||||
onClick = {},
|
||||
),
|
||||
),
|
||||
onDismiss = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.feature.wallet.child.wallet.model.intents
|
||||
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.ui.UiMessageSender
|
||||
import com.tangem.core.res.R
|
||||
|
|
@ -14,6 +15,7 @@ import com.tangem.domain.pay.repository.OnboardingRepository
|
|||
import com.tangem.domain.pay.usecase.ProduceTangemPayInitialDataUseCase
|
||||
import com.tangem.domain.pay.usecase.TangemPayMainScreenCustomerInfoUseCase
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.TangemPayHideOnboardingStateTransformer
|
||||
import com.tangem.features.tangempay.TangemPayFeatureToggles
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
|
@ -29,6 +31,10 @@ internal interface TangemPayIntents {
|
|||
fun onIssuingFailedClicked()
|
||||
|
||||
fun onPaySupportClick()
|
||||
|
||||
fun onOnboardingBannerClick(userWalletId: UserWalletId)
|
||||
|
||||
fun onOnboardingBannerCloseClick(userWalletId: UserWalletId)
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
|
|
@ -115,4 +121,15 @@ internal class TangemPayClickIntentsImplementor @Inject constructor(
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onOnboardingBannerClick(userWalletId: UserWalletId) {
|
||||
router.openTangemPayOnboarding(mode = AppRoute.TangemPayOnboarding.Mode.FromBannerOnMain(userWalletId))
|
||||
}
|
||||
|
||||
override fun onOnboardingBannerCloseClick(userWalletId: UserWalletId) {
|
||||
modelScope.launch {
|
||||
stateHolder.update(transformer = TangemPayHideOnboardingStateTransformer(userWalletId))
|
||||
onboardingRepository.setHideMainOnboardingBanner(userWalletId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -111,14 +111,8 @@ internal class DefaultWalletRouter @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
override fun openTangemPayOnboarding(userWalletId: UserWalletId) {
|
||||
router.push(
|
||||
AppRoute.TangemPayOnboarding(
|
||||
AppRoute.TangemPayOnboarding.Mode.ContinueOnboarding(
|
||||
userWalletId = userWalletId,
|
||||
),
|
||||
),
|
||||
)
|
||||
override fun openTangemPayOnboarding(mode: AppRoute.TangemPayOnboarding.Mode) {
|
||||
router.push(route = AppRoute.TangemPayOnboarding(mode = mode))
|
||||
}
|
||||
|
||||
override fun openTangemPayDetails(userWalletId: UserWalletId, config: TangemPayDetailsConfig) {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.feature.wallet.presentation.router
|
|||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.arkivanov.decompose.router.slot.SlotNavigation
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.domain.models.TokenReceiveConfig
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
|
|
@ -63,7 +64,7 @@ internal interface InnerWalletRouter {
|
|||
|
||||
fun openTokenReceiveBottomSheet(tokenReceiveConfig: TokenReceiveConfig)
|
||||
|
||||
fun openTangemPayOnboarding(userWalletId: UserWalletId)
|
||||
fun openTangemPayOnboarding(mode: AppRoute.TangemPayOnboarding.Mode)
|
||||
|
||||
fun openTangemPayDetails(userWalletId: UserWalletId, config: TangemPayDetailsConfig)
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,11 @@ internal sealed class TangemPayState {
|
|||
|
||||
data object Loading : TangemPayState()
|
||||
|
||||
data class OnboardingBanner(
|
||||
val onClick: () -> Unit,
|
||||
val closeOnClick: () -> Unit,
|
||||
) : TangemPayState()
|
||||
|
||||
data class Progress(
|
||||
val title: TextReference,
|
||||
val description: TextReference,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state.transformers
|
||||
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
|
||||
|
||||
internal class TangemPayHideOnboardingStateTransformer(
|
||||
userWalletId: UserWalletId,
|
||||
) : WalletStateTransformer(userWalletId) {
|
||||
|
||||
override fun transform(prevState: WalletState): WalletState {
|
||||
return if (prevState is WalletState.MultiCurrency.Content) {
|
||||
prevState.copy(tangemPayState = TangemPayState.Empty)
|
||||
} else {
|
||||
prevState
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state.transformers
|
||||
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
|
||||
|
||||
internal class TangemPayOnboardingBannerStateTransformer(
|
||||
userWalletId: UserWalletId,
|
||||
private val onClick: (UserWalletId) -> Unit,
|
||||
private val closeOnClick: (UserWalletId) -> Unit,
|
||||
) : WalletStateTransformer(userWalletId) {
|
||||
|
||||
override fun transform(prevState: WalletState): WalletState {
|
||||
return if (prevState is WalletState.MultiCurrency.Content) {
|
||||
prevState.copy(
|
||||
tangemPayState = TangemPayState.OnboardingBanner(
|
||||
onClick = { onClick(userWalletId) },
|
||||
closeOnClick = { closeOnClick(userWalletId) },
|
||||
),
|
||||
)
|
||||
} else {
|
||||
prevState
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.subscribers
|
||||
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.pay.model.MainCustomerInfoContentState
|
||||
|
|
@ -79,6 +80,13 @@ internal class TangemPayMainSubscriber @AssistedInject constructor(
|
|||
updateTangemPay(data = state.info, userWalletId = userWalletId)
|
||||
analytics.send(customerInfo = state.info)
|
||||
}
|
||||
is MainCustomerInfoContentState.OnboardingBanner -> stateController.update(
|
||||
transformer = TangemPayOnboardingBannerStateTransformer(
|
||||
userWalletId = userWalletId,
|
||||
onClick = clickIntents::onOnboardingBannerClick,
|
||||
closeOnClick = clickIntents::onOnboardingBannerCloseClick,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -91,7 +99,11 @@ internal class TangemPayMainSubscriber @AssistedInject constructor(
|
|||
userWalletId = userWalletId,
|
||||
value = data,
|
||||
cardFrozenState = cardFrozenState,
|
||||
onClickKyc = { innerWalletRouter.openTangemPayOnboarding(userWalletId) },
|
||||
onClickKyc = {
|
||||
innerWalletRouter.openTangemPayOnboarding(
|
||||
mode = AppRoute.TangemPayOnboarding.Mode.ContinueOnboarding(userWalletId),
|
||||
)
|
||||
},
|
||||
onIssuingCard = clickIntents::onIssuingCardClicked,
|
||||
onIssuingFailed = clickIntents::onIssuingFailedClicked,
|
||||
openDetails = { config ->
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ internal fun TangemPayMainScreenBlock(state: TangemPayState, isBalanceHidden: Bo
|
|||
is TangemPayState.RefreshNeeded -> TangemPayRefreshBlock(state, modifier)
|
||||
is TangemPayState.TemporaryUnavailable -> TangemPayUnavailableBlock(state, modifier)
|
||||
is TangemPayState.FailedIssue -> TangemPayFailedIssueState(state, modifier)
|
||||
is TangemPayState.OnboardingBanner -> TangemPayOnboardingBanner(state, modifier)
|
||||
TangemPayState.Loading -> TangemPayLoadingScreenBlock(modifier)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,129 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.ui.components.visa
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.IntrinsicSize
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
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.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.SpacerH
|
||||
import com.tangem.core.ui.components.SpacerH4
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState
|
||||
|
||||
private const val GRADIENT_START_COLOR = 0xFF252934
|
||||
private const val GRADIENT_END_COLOR = 0xFF12141E
|
||||
private const val GRADIENT_OFFSET_X = 0f
|
||||
private const val GRADIENT_OFFSET_Y = 80F
|
||||
private const val GRADIENT_RADIUS = 200F
|
||||
|
||||
@Composable
|
||||
internal fun TangemPayOnboardingBanner(state: TangemPayState.OnboardingBanner, modifier: Modifier = Modifier) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.clip(TangemTheme.shapes.roundedCornersXMedium)
|
||||
.background(
|
||||
brush = Brush.radialGradient(
|
||||
colors = listOf(Color(GRADIENT_START_COLOR), Color(GRADIENT_END_COLOR)),
|
||||
center = Offset(GRADIENT_OFFSET_X, GRADIENT_OFFSET_Y),
|
||||
radius = GRADIENT_RADIUS,
|
||||
),
|
||||
)
|
||||
.clickable(onClick = state.onClick),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(IntrinsicSize.Min)
|
||||
.padding(horizontal = 16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(R.drawable.img_tangem_pay_onboarding_banner),
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 8.dp)
|
||||
.width(55.dp)
|
||||
.height(95.dp)
|
||||
.offset(y = 10.dp),
|
||||
)
|
||||
|
||||
SpacerH4()
|
||||
|
||||
Column(
|
||||
modifier = Modifier.weight(1f),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
) {
|
||||
Text(
|
||||
text = stringResourceSafe(R.string.tangempay_onboarding_banner_title),
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.constantWhite,
|
||||
)
|
||||
|
||||
SpacerH(6.dp)
|
||||
|
||||
Text(
|
||||
text = stringResourceSafe(R.string.tangempay_onboarding_banner_description),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier.fillMaxHeight(),
|
||||
contentAlignment = Alignment.TopEnd,
|
||||
) {
|
||||
Image(
|
||||
modifier = Modifier
|
||||
.clip(TangemTheme.shapes.roundedCornersXMedium)
|
||||
.clickable(onClick = state.closeOnClick)
|
||||
.padding(4.dp)
|
||||
.padding(top = 12.dp)
|
||||
.size(12.dp),
|
||||
painter = painterResource(id = R.drawable.ic_close_24),
|
||||
colorFilter = ColorFilter.tint(TangemTheme.colors.icon.inactive),
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun PreviewTangemOnboardingBanner() {
|
||||
TangemThemePreview {
|
||||
TangemPayOnboardingBanner(
|
||||
TangemPayState.OnboardingBanner(
|
||||
onClick = {},
|
||||
closeOnClick = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue