From d76fcd143c8ee0e72bce0a14aefb4a5ea7ecbbbc Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 30 Oct 2025 12:06:31 +0400 Subject: [PATCH] Updated on 2026-08-14 --- app/src/main/AndroidManifest.xml | 4 + .../tap/data/DefaultTangemPayStorage.kt | 20 ++ .../local/preferences/PreferencesKeys.kt | 3 + .../datasource/local/visa/TangemPayStorage.kt | 4 + core/res/src/main/res/values-fr/strings.xml | 2 + core/res/src/main/res/values/strings.xml | 9 + .../DefaultCardDetailsRepository.kt | 14 ++ .../pay/repository/CardDetailsRepository.kt | 4 + ...faultTangemPayDetailsContainerComponent.kt | 4 + .../TangemPayAddToWalletComponent.kt | 38 ++++ .../tangempay/di/TangemPayModelModule.kt | 6 + .../entity/TangemPayAddToWalletUM.kt | 16 ++ .../entity/TangemPayDetailsStateFactory.kt | 1 + .../tangempay/entity/TangemPayDetailsUM.kt | 8 +- .../model/TangemPayAddToWalletModel.kt | 59 ++++++ .../tangempay/model/TangemPayDetailsModel.kt | 33 +++ .../DetailsAddToWalletBannerTransformer.kt | 22 ++ .../navigation/TangemPayDetailsInnerRoute.kt | 3 + .../tangempay/ui/TangemPayAddToWalletBlock.kt | 107 ++++++++++ .../ui/TangemPayAddToWalletScreen.kt | 192 ++++++++++++++++++ .../ui/TangemPayCardDetailsBlockItem.kt | 15 ++ .../tangempay/ui/TangemPayDetailsScreen.kt | 21 +- .../tangempay/utils/GoogleWalletUtil.kt | 40 ++++ .../res/drawable/img_google_wallet_48.xml | 25 +++ 24 files changed, 645 insertions(+), 5 deletions(-) create mode 100644 features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/TangemPayAddToWalletComponent.kt create mode 100644 features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayAddToWalletUM.kt create mode 100644 features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayAddToWalletModel.kt create mode 100644 features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/transformers/DetailsAddToWalletBannerTransformer.kt create mode 100644 features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayAddToWalletBlock.kt create mode 100644 features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayAddToWalletScreen.kt create mode 100644 features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayCardDetailsBlockItem.kt create mode 100644 features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/utils/GoogleWalletUtil.kt create mode 100644 features/tangempay/details/impl/src/main/res/drawable/img_google_wallet_48.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 882377e5e5..1603427b46 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -310,4 +310,8 @@ + + + + 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 e615d85d62..ec16f63f50 100644 --- a/app/src/main/java/com/tangem/tap/data/DefaultTangemPayStorage.kt +++ b/app/src/main/java/com/tangem/tap/data/DefaultTangemPayStorage.kt @@ -3,6 +3,10 @@ package com.tangem.tap.data import android.content.Context import com.squareup.moshi.Moshi import com.tangem.datasource.di.NetworkMoshi +import com.tangem.datasource.local.preferences.AppPreferencesStore +import com.tangem.datasource.local.preferences.PreferencesKeys +import com.tangem.datasource.local.preferences.utils.getSyncOrNull +import com.tangem.datasource.local.preferences.utils.store import com.tangem.datasource.local.visa.TangemPayStorage import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.visa.model.VisaAuthTokens @@ -21,6 +25,7 @@ internal class DefaultTangemPayStorage @Inject constructor( @ApplicationContext applicationContext: Context, @NetworkMoshi moshi: Moshi, private val dispatcherProvider: CoroutineDispatcherProvider, + private val appPreferencesStore: AppPreferencesStore, ) : TangemPayStorage { private val secureStorage by lazy { @@ -75,6 +80,20 @@ internal class DefaultTangemPayStorage @Inject constructor( secureStorage.get(createOrderIdKey(customerWalletAddress))?.decodeToString(throwOnInvalidSequence = true) } + override suspend fun getAddToWalletDone(customerWalletAddress: String): Boolean { + return withContext(dispatcherProvider.io) { + appPreferencesStore.getSyncOrNull( + key = PreferencesKeys.getTangemPayAddToWalletKey(customerWalletAddress), + ) == true + } + } + + override suspend fun storeAddToWalletDone(customerWalletAddress: String, isDone: Boolean) { + withContext(dispatcherProvider.io) { + appPreferencesStore.store(PreferencesKeys.getTangemPayAddToWalletKey(customerWalletAddress), isDone) + } + } + override suspend fun clearOrderId(customerWalletAddress: String) = withContext(dispatcherProvider.io) { secureStorage.delete(createOrderIdKey(customerWalletAddress)) } @@ -84,6 +103,7 @@ internal class DefaultTangemPayStorage @Inject constructor( secureStorage.delete(createCustomerAddressKey(userWalletId)) secureStorage.delete(createKey(customerWalletAddress)) secureStorage.delete(createOrderIdKey(customerWalletAddress)) + appPreferencesStore.store(PreferencesKeys.getTangemPayAddToWalletKey(customerWalletAddress), false) } private fun createCustomerAddressKey(userWalletId: UserWalletId): String = userWalletId.stringValue 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 a91cb3cf09..f7da1d83cf 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 @@ -181,6 +181,9 @@ object PreferencesKeys { fun getHotWalletUnlockDeadlineKey(attemptId: String) = longPreferencesKey(name = "hotWalletUnlockDeadline_$attemptId") + fun getTangemPayAddToWalletKey(customerWalletAddress: String) = + booleanPreferencesKey("tangem_pay_add_to_wallet_done_key_$customerWalletAddress") + // 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 3d9558f45e..d74074f011 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 @@ -18,5 +18,9 @@ interface TangemPayStorage { suspend fun clearOrderId(customerWalletAddress: String) + suspend fun getAddToWalletDone(customerWalletAddress: String): Boolean + + suspend fun storeAddToWalletDone(customerWalletAddress: String, isDone: Boolean) + suspend fun clearAll(userWalletId: UserWalletId, customerWalletAddress: String) } \ No newline at end of file diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml index 8c6ab60718..c1aa707042 100644 --- a/core/res/src/main/res/values-fr/strings.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -1523,9 +1523,11 @@ Les frais de réseau seront déduits du montant que vous retirez. Rendement annuel brut (APY) APY + Les intérêts sont cumulés automatiquement. Les intérêts sont cumulés automatiquement. Mode de rendement Traitement de votre dépôt + Mode Rendement Automatique Déposez %1$s %2$s pour couvrir les frais de réseau liés aux transactions. Impossible de couvrir les frais %s diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 51c96443a0..8e7c5ebd52 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -1338,6 +1338,15 @@ Failed to load data. Try again later. Freeze Card Hide + Open Google Wallet + Set up Tangem Pay in a few taps and start paying with Google Pay. + Add your card to Google Pay + Open Google Wallet + Tap “Add a card” + Enter the card details manually + Verify card using the OTP sent to your device. + All set! Your card is ready to use. + Add card to Google Pay Technical issues detected. Please try again later or contact support. Receive unavailable now Reveal diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultCardDetailsRepository.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultCardDetailsRepository.kt index 6c820d0bd1..3478231e55 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultCardDetailsRepository.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultCardDetailsRepository.kt @@ -10,6 +10,7 @@ import com.tangem.datasource.api.common.config.managers.ApiConfigsManager import com.tangem.datasource.api.pay.TangemPayApi import com.tangem.datasource.api.pay.models.request.CardDetailsRequest import com.tangem.datasource.api.pay.models.request.SetPinRequest +import com.tangem.datasource.local.visa.TangemPayStorage import com.tangem.domain.pay.model.SetPinResult import com.tangem.domain.pay.model.TangemPayCardBalance import com.tangem.domain.pay.model.TangemPayCardDetails @@ -24,6 +25,7 @@ internal class DefaultCardDetailsRepository @Inject constructor( private val visaLibLoader: VisaLibLoader, private val apiConfigsManager: ApiConfigsManager, private val rainCryptoUtil: RainCryptoUtil, + private val storage: TangemPayStorage, ) : CardDetailsRepository { override suspend fun getCardBalance(): Either { @@ -98,6 +100,18 @@ internal class DefaultCardDetailsRepository @Inject constructor( } } + override suspend fun isAddToWalletDone(): Either { + return requestHelper.runWithErrorLogs(TAG) { + storage.getAddToWalletDone(requestHelper.getCustomerWalletAddress()) + } + } + + override suspend fun setAddToWalletAsDone(): Either { + return requestHelper.runWithErrorLogs(TAG) { + storage.storeAddToWalletDone(requestHelper.getCustomerWalletAddress(), isDone = true) + } + } + private suspend fun getPublicKeyBase64(): String { val env = apiConfigsManager.getEnvironmentConfig(ApiConfig.ID.TangemPay).environment return when (env) { diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/pay/repository/CardDetailsRepository.kt b/domain/visa/src/main/kotlin/com/tangem/domain/pay/repository/CardDetailsRepository.kt index 63211bc2c1..426b1814a8 100644 --- a/domain/visa/src/main/kotlin/com/tangem/domain/pay/repository/CardDetailsRepository.kt +++ b/domain/visa/src/main/kotlin/com/tangem/domain/pay/repository/CardDetailsRepository.kt @@ -13,4 +13,8 @@ interface CardDetailsRepository { suspend fun revealCardDetails(): Either suspend fun setPin(pin: String): Either + + suspend fun isAddToWalletDone(): Either + + suspend fun setAddToWalletAsDone(): Either } \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/DefaultTangemPayDetailsContainerComponent.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/DefaultTangemPayDetailsContainerComponent.kt index 28d4475d31..b1f3fb02be 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/DefaultTangemPayDetailsContainerComponent.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/DefaultTangemPayDetailsContainerComponent.kt @@ -67,6 +67,10 @@ class DefaultTangemPayDetailsContainerComponent @AssistedInject constructor( TangemPayDetailsInnerRoute.ChangePINSuccess -> TangemPayChangePinSuccessComponent( appComponentContext = childByContext(componentContext = componentContext, router = innerRouter), ) + TangemPayDetailsInnerRoute.AddToWallet -> TangemPayAddToWalletComponent( + appComponentContext = childByContext(componentContext = componentContext, router = innerRouter), + params = params, + ) } private fun onChildBack() { diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/TangemPayAddToWalletComponent.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/TangemPayAddToWalletComponent.kt new file mode 100644 index 0000000000..561ef1f9cf --- /dev/null +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/TangemPayAddToWalletComponent.kt @@ -0,0 +1,38 @@ +package com.tangem.features.tangempay.components + +import androidx.activity.compose.BackHandler +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.context.child +import com.tangem.core.decompose.model.getOrCreateModel +import com.tangem.core.ui.decompose.ComposableContentComponent +import com.tangem.features.tangempay.components.cardDetails.DefaultTangemPayCardDetailsBlockComponent +import com.tangem.features.tangempay.components.cardDetails.TangemPayCardDetailsBlockComponent +import com.tangem.features.tangempay.model.TangemPayAddToWalletModel +import com.tangem.features.tangempay.ui.TangemPayAddToWalletScreen + +internal class TangemPayAddToWalletComponent( + private val appComponentContext: AppComponentContext, + private val params: TangemPayDetailsContainerComponent.Params, +) : AppComponentContext by appComponentContext, ComposableContentComponent { + + private val model: TangemPayAddToWalletModel = getOrCreateModel() + + private val cardDetailsBlockComponent = DefaultTangemPayCardDetailsBlockComponent( + appComponentContext = child("cardDetailsBlockComponent"), + params = TangemPayCardDetailsBlockComponent.Params(params = params), + ) + + @Composable + override fun Content(modifier: Modifier) { + val state by model.uiState.collectAsStateWithLifecycle() + BackHandler(onBack = router::pop) + TangemPayAddToWalletScreen( + state = state, + cardDetailsBlockComponent = cardDetailsBlockComponent, + ) + } +} \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/di/TangemPayModelModule.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/di/TangemPayModelModule.kt index 65f28a6405..912654abc7 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/di/TangemPayModelModule.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/di/TangemPayModelModule.kt @@ -2,6 +2,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.TangemPayAddToWalletModel import com.tangem.features.tangempay.model.TangemPayCardDetailsBlockModel import com.tangem.features.tangempay.model.TangemPayChangePinModel import com.tangem.features.tangempay.model.TangemPayDetailsModel @@ -41,4 +42,9 @@ internal interface TangemPayModelModule { @IntoMap @ClassKey(TangemPayCardDetailsBlockModel::class) fun bindTangemPayCardDetailsBlockModel(model: TangemPayCardDetailsBlockModel): Model + + @Binds + @IntoMap + @ClassKey(TangemPayAddToWalletModel::class) + fun bindTangemPayAddToWalletModel(model: TangemPayAddToWalletModel): Model } \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayAddToWalletUM.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayAddToWalletUM.kt new file mode 100644 index 0000000000..c5aa65d1c5 --- /dev/null +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayAddToWalletUM.kt @@ -0,0 +1,16 @@ +package com.tangem.features.tangempay.entity + +import com.tangem.core.ui.extensions.TextReference +import kotlinx.collections.immutable.ImmutableList + +internal data class TangemPayAddToWalletUM( + val steps: ImmutableList, + val showAddToWalletButton: Boolean, + val onBackClick: () -> Unit, + val onClickOpenWallet: () -> Unit, +) + +internal data class TangemPayAddToWalletStepItemUM( + val count: Int, + val text: TextReference, +) \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayDetailsStateFactory.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayDetailsStateFactory.kt index baae7769b7..16de26202e 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayDetailsStateFactory.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayDetailsStateFactory.kt @@ -45,6 +45,7 @@ internal class TangemPayDetailsStateFactory( ), ), ), + addToWalletBlockState = null, isBalanceHidden = false, ) } \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayDetailsUM.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayDetailsUM.kt index a762ed7fdd..9ab8efa762 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayDetailsUM.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayDetailsUM.kt @@ -9,6 +9,7 @@ internal data class TangemPayDetailsUM( val topBarConfig: TangemPayDetailsTopBarConfig, val pullToRefreshConfig: PullToRefreshConfig, val balanceBlockState: TangemPayDetailsBalanceBlockState, + val addToWalletBlockState: AddToWalletBlockState?, val isBalanceHidden: Boolean, ) @@ -41,4 +42,9 @@ internal sealed class TangemPayDetailsBalanceBlockState { data class Error( override val actionButtons: ImmutableList, ) : TangemPayDetailsBalanceBlockState() -} \ No newline at end of file +} + +internal data class AddToWalletBlockState( + val onClick: () -> Unit, + val onClickClose: () -> Unit, +) \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayAddToWalletModel.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayAddToWalletModel.kt new file mode 100644 index 0000000000..0920d736e8 --- /dev/null +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayAddToWalletModel.kt @@ -0,0 +1,59 @@ +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.navigation.Router +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.features.tangempay.details.impl.R +import com.tangem.features.tangempay.entity.TangemPayAddToWalletStepItemUM +import com.tangem.features.tangempay.entity.TangemPayAddToWalletUM +import com.tangem.features.tangempay.utils.GoogleWalletUtil +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.collections.immutable.persistentListOf +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import javax.inject.Inject + +@Stable +@ModelScoped +internal class TangemPayAddToWalletModel @Inject constructor( + override val dispatchers: CoroutineDispatcherProvider, + private val router: Router, + private val googleWalletUtil: GoogleWalletUtil, +) : Model() { + + val uiState: StateFlow + field = MutableStateFlow(getInitialState()) + + @Suppress("MagicNumber") + private fun getInitialState(): TangemPayAddToWalletUM { + return TangemPayAddToWalletUM( + steps = persistentListOf( + TangemPayAddToWalletStepItemUM( + count = 1, + text = resourceReference(R.string.tangempay_card_details_open_wallet_step_1), + ), + TangemPayAddToWalletStepItemUM( + count = 2, + text = resourceReference(R.string.tangempay_card_details_open_wallet_step_2), + ), + TangemPayAddToWalletStepItemUM( + count = 3, + text = resourceReference(R.string.tangempay_card_details_open_wallet_step_3), + ), + TangemPayAddToWalletStepItemUM( + count = 4, + text = resourceReference(R.string.tangempay_card_details_open_wallet_step_4), + ), + TangemPayAddToWalletStepItemUM( + count = 5, + text = resourceReference(R.string.tangempay_card_details_open_wallet_step_5), + ), + ), + showAddToWalletButton = googleWalletUtil.isWalletAvailable(), + onBackClick = router::pop, + onClickOpenWallet = googleWalletUtil::openWallet, + ) + } +} \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayDetailsModel.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayDetailsModel.kt index fd80a9a75f..3b4c752852 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayDetailsModel.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayDetailsModel.kt @@ -20,6 +20,7 @@ import com.tangem.features.tangempay.entity.TangemPayDetailsStateFactory import com.tangem.features.tangempay.entity.TangemPayDetailsUM import com.tangem.features.tangempay.model.listener.CardDetailsEvent import com.tangem.features.tangempay.model.listener.CardDetailsEventListener +import com.tangem.features.tangempay.model.transformers.DetailsAddToWalletBannerTransformer import com.tangem.features.tangempay.model.transformers.DetailsBalanceTransformer import com.tangem.features.tangempay.model.transformers.TangemPayDetailsRefreshTransformer import com.tangem.features.tangempay.navigation.TangemPayDetailsInnerRoute @@ -63,10 +64,12 @@ internal class TangemPayDetailsModel @Inject constructor( private val refreshStateJobHolder = JobHolder() private val fetchBalanceJobHolder = JobHolder() + private val addToWalletBannerJobHolder = JobHolder() val bottomSheetNavigation: SlotNavigation = SlotNavigation() init { + fetchAddToWalletBanner() fetchBalance() } @@ -105,6 +108,19 @@ internal class TangemPayDetailsModel @Inject constructor( }.saveIn(fetchBalanceJobHolder) } + private fun fetchAddToWalletBanner() { + modelScope.launch { + val isDone = cardDetailsRepository.isAddToWalletDone().getOrNull() ?: false + uiState.update( + transformer = DetailsAddToWalletBannerTransformer( + onClickBanner = ::onClickAddToWalletBlock, + onClickCloseBanner = ::onClickCloseAddToWalletBlock, + isDone = isDone, + ), + ) + }.saveIn(addToWalletBannerJobHolder) + } + private fun onRefreshSwipe(refreshState: ShowRefreshState) { modelScope.launch { cardDetailsEventListener.send(CardDetailsEvent.Hide) @@ -114,6 +130,23 @@ internal class TangemPayDetailsModel @Inject constructor( }.saveIn(refreshStateJobHolder) } + private fun onClickAddToWalletBlock() { + router.push(TangemPayDetailsInnerRoute.AddToWallet) + } + + private fun onClickCloseAddToWalletBlock() { + modelScope.launch { + cardDetailsRepository.setAddToWalletAsDone() + uiState.update( + transformer = DetailsAddToWalletBannerTransformer( + onClickBanner = ::onClickAddToWalletBlock, + onClickCloseBanner = ::onClickCloseAddToWalletBlock, + isDone = true, + ), + ) + }.saveIn(addToWalletBannerJobHolder) + } + override fun onTransactionClick(item: TangemPayTxHistoryItem) { bottomSheetNavigation.activate(TangemPayDetailsNavigation.TransactionDetails(item)) } diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/transformers/DetailsAddToWalletBannerTransformer.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/transformers/DetailsAddToWalletBannerTransformer.kt new file mode 100644 index 0000000000..3fe7bddbf8 --- /dev/null +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/transformers/DetailsAddToWalletBannerTransformer.kt @@ -0,0 +1,22 @@ +package com.tangem.features.tangempay.model.transformers + +import com.tangem.features.tangempay.entity.AddToWalletBlockState +import com.tangem.features.tangempay.entity.TangemPayDetailsUM +import com.tangem.utils.transformer.Transformer + +internal class DetailsAddToWalletBannerTransformer( + private val onClickBanner: () -> Unit, + private val onClickCloseBanner: () -> Unit, + private val isDone: Boolean, +) : Transformer { + + override fun transform(prevState: TangemPayDetailsUM): TangemPayDetailsUM { + return prevState.copy( + addToWalletBlockState = if (isDone) { + null + } else { + AddToWalletBlockState(onClick = onClickBanner, onClickClose = onClickCloseBanner) + }, + ) + } +} \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/navigation/TangemPayDetailsInnerRoute.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/navigation/TangemPayDetailsInnerRoute.kt index 9aabeac0cd..7bc24c9979 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/navigation/TangemPayDetailsInnerRoute.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/navigation/TangemPayDetailsInnerRoute.kt @@ -13,4 +13,7 @@ internal sealed class TangemPayDetailsInnerRoute : Route { @Serializable data object ChangePINSuccess : TangemPayDetailsInnerRoute() + + @Serializable + data object AddToWallet : TangemPayDetailsInnerRoute() } \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayAddToWalletBlock.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayAddToWalletBlock.kt new file mode 100644 index 0000000000..50c72f534f --- /dev/null +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayAddToWalletBlock.kt @@ -0,0 +1,107 @@ +package com.tangem.features.tangempay.ui + +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.* +import androidx.compose.foundation.shape.CircleShape +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.extensions.stringResourceSafe +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.features.tangempay.details.impl.R +import com.tangem.features.tangempay.entity.AddToWalletBlockState + +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 TangemPayAddToWalletBlock(state: AddToWalletBlockState, 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, vertical = 12.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Image( + painter = painterResource(R.drawable.img_google_wallet_48), + contentDescription = null, + modifier = Modifier.size(36.dp).clip(CircleShape), + ) + + Spacer(Modifier.width(12.dp)) + + Column( + modifier = Modifier.weight(1f), + verticalArrangement = Arrangement.Center, + ) { + Text( + text = stringResourceSafe(R.string.tangempay_card_details_open_wallet_notification_title), + style = TangemTheme.typography.subtitle2, + color = TangemTheme.colors.text.constantWhite, + ) + + Spacer(Modifier.height(6.dp)) + + Text( + text = stringResourceSafe(R.string.tangempay_card_details_open_wallet_notification_subtitle), + 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.onClickClose) + .padding(4.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 PreviewTangemPayAddToWalletBlock() { + TangemThemePreview { + TangemPayAddToWalletBlock(AddToWalletBlockState({}, {})) + } +} \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayAddToWalletScreen.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayAddToWalletScreen.kt new file mode 100644 index 0000000000..249aee7ace --- /dev/null +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayAddToWalletScreen.kt @@ -0,0 +1,192 @@ +package com.tangem.features.tangempay.ui + +import android.content.res.Configuration +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.tangem.core.ui.components.PrimaryButton +import com.tangem.core.ui.components.SecondaryButton +import com.tangem.core.ui.components.appbar.AppBarWithBackButton +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.stringResourceSafe +import com.tangem.core.ui.res.TangemColorPalette +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.features.tangempay.components.cardDetails.PreviewTangemPayCardDetailsBlockComponent +import com.tangem.features.tangempay.components.cardDetails.TangemPayCardDetailsBlockComponent +import com.tangem.features.tangempay.details.impl.R +import com.tangem.features.tangempay.entity.TangemPayAddToWalletStepItemUM +import com.tangem.features.tangempay.entity.TangemPayAddToWalletUM +import com.tangem.features.tangempay.entity.TangemPayCardDetailsUM +import kotlinx.collections.immutable.persistentListOf + +@Composable +internal fun TangemPayAddToWalletScreen( + state: TangemPayAddToWalletUM, + cardDetailsBlockComponent: TangemPayCardDetailsBlockComponent, + modifier: Modifier = Modifier, +) { + val listState = rememberLazyListState() + val bottomBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getBottom(this).toDp() } + val cardDetailsState by cardDetailsBlockComponent.state.collectAsStateWithLifecycle() + + Column( + modifier = modifier + .fillMaxSize() + .navigationBarsPadding(), + ) { + AppBarWithBackButton( + modifier = Modifier.statusBarsPadding(), + iconRes = R.drawable.ic_close_24, + onBackClick = state.onBackClick, + ) + + LazyColumn( + modifier = Modifier + .fillMaxSize() + .weight(1f), + state = listState, + contentPadding = PaddingValues(bottom = TangemTheme.dimens.spacing16 + bottomBarHeight), + ) { + item(key = TangemPayCardDetailsUM::class.java) { + TangemPayCardDetailsBlockItem(component = cardDetailsBlockComponent, state = cardDetailsState) + } + + item(key = TangemPayAddToWalletUM::class.java) { + Text( + modifier = Modifier + .padding(horizontal = 16.dp, vertical = 24.dp) + .fillMaxWidth(), + text = stringResourceSafe(R.string.tangempay_card_details_open_wallet_title), + style = TangemTheme.typography.h2, + color = TangemTheme.colors.text.primary1, + textAlign = TextAlign.Center, + ) + } + itemsIndexed( + items = state.steps, + key = { _, step -> "${TangemPayAddToWalletStepItemUM::class.java}${step.count}" }, + ) { idx, step -> + StepItem( + modifier = Modifier.padding(bottom = if (idx < state.steps.lastIndex) 16.dp else 0.dp), + stepNumber = step.count, + title = step.text, + ) + } + } + + SecondaryButton( + modifier = Modifier + .imePadding() + .padding(start = 16.dp, end = 16.dp, bottom = 8.dp) + .fillMaxWidth(), + text = resourceReference(R.string.common_got_it).resolveReference(), + onClick = state.onBackClick, + + ) + if (state.showAddToWalletButton) { + PrimaryButton( + modifier = Modifier + .imePadding() + .padding(start = 16.dp, end = 16.dp, bottom = 16.dp) + .fillMaxWidth(), + text = resourceReference(R.string.tangempay_card_details_open_wallet_button).resolveReference(), + onClick = state.onClickOpenWallet, + ) + } + } +} + +@Composable +private fun StepItem(stepNumber: Int, title: TextReference, modifier: Modifier = Modifier) { + Row( + modifier = modifier + .background(color = Color.Transparent, shape = TangemTheme.shapes.roundedCorners8) + .padding(horizontal = 24.dp), + verticalAlignment = Alignment.Top, + ) { + Box( + modifier = Modifier + .size(24.dp) + .background(color = TangemColorPalette.Azure, shape = CircleShape), + contentAlignment = Alignment.Center, + ) { + Text( + text = stepNumber.toString(), + style = TangemTheme.typography.subtitle2, + color = TangemTheme.colors.text.primary2, + ) + } + + Spacer(modifier = Modifier.width(12.dp)) + + Text( + text = title.resolveReference(), + style = TangemTheme.typography.subtitle1, + color = TangemTheme.colors.text.primary1, + ) + } +} + +@Preview(showBackground = true) +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun PreviewTangemPayAddToWalletScreen() { + TangemThemePreview { + TangemPayAddToWalletScreen( + state = TangemPayAddToWalletUM( + steps = persistentListOf( + TangemPayAddToWalletStepItemUM( + count = 1, + text = resourceReference(R.string.tangempay_card_details_open_wallet_step_1), + ), + TangemPayAddToWalletStepItemUM( + count = 2, + text = resourceReference(R.string.tangempay_card_details_open_wallet_step_2), + ), + TangemPayAddToWalletStepItemUM( + count = 3, + text = resourceReference(R.string.tangempay_card_details_open_wallet_step_3), + ), + TangemPayAddToWalletStepItemUM( + count = 4, + text = resourceReference(R.string.tangempay_card_details_open_wallet_step_4), + ), + TangemPayAddToWalletStepItemUM( + count = 5, + text = resourceReference(R.string.tangempay_card_details_open_wallet_step_5), + ), + ), + showAddToWalletButton = true, + onBackClick = {}, + onClickOpenWallet = {}, + ), + cardDetailsBlockComponent = PreviewTangemPayCardDetailsBlockComponent( + TangemPayCardDetailsUM( + number = "•••• •••• •••• 1245", + expiry = "••/••", + cvv = "•••", + onCopy = {}, + onClick = {}, + buttonText = TextReference.Res(R.string.tangempay_card_details_hide_text), + ), + ), + ) + } +} \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayCardDetailsBlockItem.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayCardDetailsBlockItem.kt new file mode 100644 index 0000000000..01e3a91379 --- /dev/null +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayCardDetailsBlockItem.kt @@ -0,0 +1,15 @@ +package com.tangem.features.tangempay.ui + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.tangem.features.tangempay.components.cardDetails.TangemPayCardDetailsBlockComponent +import com.tangem.features.tangempay.entity.TangemPayCardDetailsUM + +@Composable +internal fun TangemPayCardDetailsBlockItem( + component: TangemPayCardDetailsBlockComponent, + state: TangemPayCardDetailsUM, + modifier: Modifier = Modifier, +) { + component.CardDetailsBlockContent(state = state, modifier = modifier) +} \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayDetailsScreen.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayDetailsScreen.kt index 47de0ecf3b..3cf7c2e7a6 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayDetailsScreen.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayDetailsScreen.kt @@ -90,10 +90,21 @@ internal fun TangemPayDetailsScreen( ) }, ) - with(cardDetailsBlockComponent) { - item(TangemPayCardDetailsUM::class.java) { - CardDetailsBlockContent(state = cardDetailsState) - } + if (state.addToWalletBlockState != null) { + item( + key = AddToWalletBlockState::class.java, + content = { + TangemPayAddToWalletBlock( + state = state.addToWalletBlockState, + modifier = Modifier + .padding(top = TangemTheme.dimens.spacing12) + .padding(horizontal = TangemTheme.dimens.spacing16), + ) + }, + ) + } + item(TangemPayCardDetailsUM::class.java) { + TangemPayCardDetailsBlockItem(component = cardDetailsBlockComponent, state = cardDetailsState) } with(txHistoryComponent) { txHistoryContent(listState = listState, state = txHistoryState) } } @@ -300,12 +311,14 @@ private class TangemPayDetailsUMProvider : CollectionPreviewParameterProvider + + + + + +