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 4f7f4a0166..80e06699c1 100644 --- a/app/src/main/java/com/tangem/tap/data/DefaultTangemPayStorage.kt +++ b/app/src/main/java/com/tangem/tap/data/DefaultTangemPayStorage.kt @@ -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" 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 df8495fe6c..715bf51463 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 @@ -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, ) }, 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 cbe8919317..c2d8b480d7 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 @@ -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() } } diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/pay/TangemPayApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/pay/TangemPayApi.kt index 9c4fca8149..57d31d6a0d 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/pay/TangemPayApi.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/pay/TangemPayApi.kt @@ -128,6 +128,9 @@ interface TangemPayApi { @POST("v1/deeplink/validate") suspend fun validateDeeplink(@Body body: DeeplinkValidityRequest): ApiResponse + @GET("v1/customer/eligibility") + suspend fun checkCustomerEligibility(): ApiResponse + @GET("v1/order/{order_id}") suspend fun getOrder( @Header("Authorization") authHeader: String, diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/CustomerEligibilityResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/CustomerEligibilityResponse.kt new file mode 100644 index 0000000000..8ddcfab3f7 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/CustomerEligibilityResponse.kt @@ -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, + ) +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt index 037f71a2a6..775e916143 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt @@ -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 } 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 e32fe05975..a9279e9193 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 @@ -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) } \ No newline at end of file diff --git a/core/ui/src/main/res/drawable/ic_tangem_pay_24.xml b/core/ui/src/main/res/drawable/ic_tangem_pay_24.xml new file mode 100644 index 0000000000..42130a709f --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_tangem_pay_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/core/ui/src/main/res/drawable/img_tangem_pay_onboarding_banner.webp b/core/ui/src/main/res/drawable/img_tangem_pay_onboarding_banner.webp new file mode 100644 index 0000000000..e3a0bccac9 Binary files /dev/null and b/core/ui/src/main/res/drawable/img_tangem_pay_onboarding_banner.webp differ diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/DefaultTangemPayEligibilityManager.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/DefaultTangemPayEligibilityManager.kt new file mode 100644 index 0000000000..97a6d9e7ca --- /dev/null +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/DefaultTangemPayEligibilityManager.kt @@ -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? = null + private var eligibleWalletsDeferred: Deferred>? = null + private val loadMutex = Mutex() + + override suspend fun getEligibleWallets(): List { + 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 { + 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.excludePaeraCustomers(): List { + 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 } + } + } + } +} \ No newline at end of file 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 bae2d97baf..ff79c711bf 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 @@ -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, ) } 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 563b3ee0eb..2e66473cae 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 @@ -84,7 +84,6 @@ internal class DefaultOnboardingRepository @Inject constructor( } override suspend fun getCustomerInfo(userWalletId: UserWalletId): Either { - // 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) + } } \ 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 788aafe9d8..83e30d675e 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 @@ -89,6 +89,19 @@ internal class TangemPayRequestPerformer @Inject constructor( ) } + suspend fun performWithoutToken(requestBlock: suspend () -> ApiResponse): Either = + withContext(dispatchers.io) { + catch( + block = { + when (val apiResponse = requestBlock()) { + is ApiResponse.Error -> errorConverter.convert(apiResponse.cause).left() + is ApiResponse.Success -> apiResponse.data.right() + } + }, + catch = { errorConverter.convert(it).left() }, + ) + } + suspend fun performRequest( userWalletId: UserWalletId, requestBlock: suspend (header: String) -> ApiResponse, diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/util/TangemPayWalletsManager.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/util/TangemPayWalletsManager.kt deleted file mode 100644 index 4423dddf32..0000000000 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/util/TangemPayWalletsManager.kt +++ /dev/null @@ -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.Cold { - return userWallets?.find { - it is UserWallet.Cold && it.isMultiCurrency - } as? UserWallet.Cold ?: error("Cannot find cold user wallet") - } -} \ No newline at end of file diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/pay/TangemPayEligibilityManager.kt b/domain/visa/src/main/kotlin/com/tangem/domain/pay/TangemPayEligibilityManager.kt new file mode 100644 index 0000000000..b68cd25513 --- /dev/null +++ b/domain/visa/src/main/kotlin/com/tangem/domain/pay/TangemPayEligibilityManager.kt @@ -0,0 +1,8 @@ +package com.tangem.domain.pay + +import com.tangem.domain.models.wallet.UserWallet + +interface TangemPayEligibilityManager { + + suspend fun getEligibleWallets(): List +} \ No newline at end of file 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 ad9583243f..d747e41066 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 @@ -5,6 +5,7 @@ import java.math.BigDecimal sealed class MainCustomerInfoContentState { object Loading : MainCustomerInfoContentState() + object OnboardingBanner : MainCustomerInfoContentState() data class Content(val info: MainScreenCustomerInfo) : MainCustomerInfoContentState() } 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 58046210b6..e3fc78e653 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 @@ -25,5 +25,11 @@ interface OnboardingRepository { suspend fun checkCustomerWallet(userWalletId: UserWalletId): Either + suspend fun checkCustomerEligibility(): Boolean + fun getSavedCustomerInfo(userWalletId: UserWalletId): CustomerInfo? + + suspend fun getHideMainOnboardingBanner(userWalletId: UserWalletId): Boolean + + suspend fun setHideMainOnboardingBanner(userWalletId: UserWalletId) } \ 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 index 07950a5cec..b84b7f061b 100644 --- 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 @@ -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>> @@ -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()) + } } }, ) diff --git a/features/details/impl/build.gradle.kts b/features/details/impl/build.gradle.kts index 8f41e0f6c1..8da459c047 100644 --- a/features/details/impl/build.gradle.kts +++ b/features/details/impl/build.gradle.kts @@ -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 diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/model/DetailsModel.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/model/DetailsModel.kt index be5d74ca8e..69fe323b69 100644 --- a/features/details/impl/src/main/kotlin/com/tangem/features/details/model/DetailsModel.kt +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/model/DetailsModel.kt @@ -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 { diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/ItemsBuilder.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/ItemsBuilder.kt index 617de52a47..0cdd6b6a10 100644 --- a/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/ItemsBuilder.kt +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/ItemsBuilder.kt @@ -37,6 +37,17 @@ internal class ItemsBuilder @Inject constructor(private val router: Router) { ).let(::add) }.toImmutableList() + fun addVisaItem(items: ImmutableList): ImmutableList { + 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)) + }, + ), + ) } \ 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 408d809672..df83da98da 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 @@ -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 diff --git a/features/tangempay/onboarding/impl/build.gradle.kts b/features/tangempay/onboarding/impl/build.gradle.kts index 7b71ee3d94..c94b59ab29 100644 --- a/features/tangempay/onboarding/impl/build.gradle.kts +++ b/features/tangempay/onboarding/impl/build.gradle.kts @@ -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) } \ 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 4ee5eb136c..e11789c134 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 @@ -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 diff --git a/features/tangempay/onboarding/impl/src/main/kotlin/com/tangem/features/tangempay/components/TangemPayWalletSelectorComponent.kt b/features/tangempay/onboarding/impl/src/main/kotlin/com/tangem/features/tangempay/components/TangemPayWalletSelectorComponent.kt new file mode 100644 index 0000000000..d8e84fe4e0 --- /dev/null +++ b/features/tangempay/onboarding/impl/src/main/kotlin/com/tangem/features/tangempay/components/TangemPayWalletSelectorComponent.kt @@ -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, + ) +} + +internal interface WalletSelectorListener { + fun onWalletSelected(userWalletId: UserWalletId) + fun onWalletSelectorDismiss() +} \ No newline at end of file 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 cb6bf2f491..8c9400fe8e 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 @@ -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 { diff --git a/features/tangempay/onboarding/impl/src/main/kotlin/com/tangem/features/tangempay/di/TangemPayOnboardingModelsModule.kt b/features/tangempay/onboarding/impl/src/main/kotlin/com/tangem/features/tangempay/di/TangemPayOnboardingModelsModule.kt index b4b1892e56..3cd13c821b 100644 --- a/features/tangempay/onboarding/impl/src/main/kotlin/com/tangem/features/tangempay/di/TangemPayOnboardingModelsModule.kt +++ b/features/tangempay/onboarding/impl/src/main/kotlin/com/tangem/features/tangempay/di/TangemPayOnboardingModelsModule.kt @@ -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 } \ No newline at end of file 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 5b9fed7d81..ff881de155 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,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() + + val bottomSheetNavigation: SlotNavigation = SlotNavigation() + val uiState: StateFlow 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) { + 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() + } } \ No newline at end of file diff --git a/features/tangempay/onboarding/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayWalletSelectorModel.kt b/features/tangempay/onboarding/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayWalletSelectorModel.kt new file mode 100644 index 0000000000..27a15aeeeb --- /dev/null +++ b/features/tangempay/onboarding/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayWalletSelectorModel.kt @@ -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() + + private val userWalletsFetcher = userWalletsFetcherFactory.create( + messageSender = messageSender, + onlyMultiCurrency = true, + isAuthMode = false, + isClickableIfLocked = false, + onWalletClick = { params.listener.onWalletSelected(it) }, + ) + + init { + fetchUserWalletsUM() + } + + val uiState: StateFlow + 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() + } +} \ No newline at end of file diff --git a/features/tangempay/onboarding/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayOnboardingNavigation.kt b/features/tangempay/onboarding/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayOnboardingNavigation.kt new file mode 100644 index 0000000000..8fe01286d7 --- /dev/null +++ b/features/tangempay/onboarding/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayOnboardingNavigation.kt @@ -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, + ) : TangemPayOnboardingNavigation() +} \ No newline at end of file diff --git a/features/tangempay/onboarding/impl/src/main/kotlin/com/tangem/features/tangempay/ui/WalletSelectorBSContentUM.kt b/features/tangempay/onboarding/impl/src/main/kotlin/com/tangem/features/tangempay/ui/WalletSelectorBSContentUM.kt new file mode 100644 index 0000000000..e0028184e6 --- /dev/null +++ b/features/tangempay/onboarding/impl/src/main/kotlin/com/tangem/features/tangempay/ui/WalletSelectorBSContentUM.kt @@ -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, + val onDismiss: () -> Unit, +) : TangemBottomSheetConfigContent \ No newline at end of file diff --git a/features/tangempay/onboarding/impl/src/main/kotlin/com/tangem/features/tangempay/ui/WalletSelectorBottomSheet.kt b/features/tangempay/onboarding/impl/src/main/kotlin/com/tangem/features/tangempay/ui/WalletSelectorBottomSheet.kt new file mode 100644 index 0000000000..fd3f9a755e --- /dev/null +++ b/features/tangempay/onboarding/impl/src/main/kotlin/com/tangem/features/tangempay/ui/WalletSelectorBottomSheet.kt @@ -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( + 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 = {}, + ), + ) + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/TangemPayClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/TangemPayClickIntents.kt index ed0005c791..b00b4b5886 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/TangemPayClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/TangemPayClickIntents.kt @@ -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) + } + } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt index 98d68adf5f..934d258f18 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt @@ -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) { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/InnerWalletRouter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/InnerWalletRouter.kt index cf9d3ea617..c423b80481 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/InnerWalletRouter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/InnerWalletRouter.kt @@ -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) 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 index bbe8472d49..25c8c2f0a5 100644 --- 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 @@ -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, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/TangemPayHideOnboardingStateTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/TangemPayHideOnboardingStateTransformer.kt new file mode 100644 index 0000000000..9e4e6bafd7 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/TangemPayHideOnboardingStateTransformer.kt @@ -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 + } + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/TangemPayOnboardingBannerStateTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/TangemPayOnboardingBannerStateTransformer.kt new file mode 100644 index 0000000000..15b2ad4dee --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/TangemPayOnboardingBannerStateTransformer.kt @@ -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 + } + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/TangemPayMainSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/TangemPayMainSubscriber.kt index 6f182566d8..2f3bad4547 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/TangemPayMainSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/TangemPayMainSubscriber.kt @@ -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 -> 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 index 24f11c043c..7ee74d5ec2 100644 --- 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 @@ -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) } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/visa/TangemPayOnboardingBanner.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/visa/TangemPayOnboardingBanner.kt new file mode 100644 index 0000000000..b57061cb36 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/visa/TangemPayOnboardingBanner.kt @@ -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 = {}, + ), + ) + } +} \ No newline at end of file