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 7f1092646f..ad9583243f 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 @@ -3,6 +3,11 @@ package com.tangem.domain.pay.model import com.tangem.domain.visa.model.TangemPayCardFrozenState import java.math.BigDecimal +sealed class MainCustomerInfoContentState { + object Loading : MainCustomerInfoContentState() + data class Content(val info: MainScreenCustomerInfo) : MainCustomerInfoContentState() +} + data class MainScreenCustomerInfo( val info: CustomerInfo, val orderStatus: OrderStatus, 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 313aa94bb3..07950a5cec 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 @@ -2,16 +2,13 @@ package com.tangem.domain.pay.usecase import arrow.core.Either import arrow.core.left -import arrow.core.raise.catch import arrow.core.right import com.tangem.domain.models.wallet.UserWalletId -import com.tangem.domain.pay.model.CustomerInfo -import com.tangem.domain.pay.model.MainScreenCustomerInfo -import com.tangem.domain.pay.model.OrderStatus -import com.tangem.domain.pay.model.TangemPayCustomerInfoError +import com.tangem.domain.pay.model.* import com.tangem.domain.pay.repository.CustomerOrderRepository import com.tangem.domain.pay.repository.OnboardingRepository import com.tangem.domain.visa.error.VisaApiError +import kotlinx.coroutines.flow.* import timber.log.Timber private const val TAG = "TangemPayMainScreenCustomerInfoUseCase" @@ -26,32 +23,50 @@ class TangemPayMainScreenCustomerInfoUseCase( private val tangemPayOnboardingRepository: OnboardingRepository, ) { - suspend operator fun invoke( - userWalletId: UserWalletId, - ): Either = catch( - block = { - Timber.tag(TAG).d("checkCustomerWallet") - repository.checkCustomerWallet(userWalletId) - .fold( - ifLeft = { error -> - Timber.tag(TAG).e("Failed to check customer wallet ${error.javaClass.simpleName}") - TangemPayCustomerInfoError.UnknownError.left() - }, - ifRight = { hasTangemPay -> - Timber.tag(TAG).i("checkCustomerWallet $hasTangemPay") - if (hasTangemPay) { - proceedWithPaeraCustomerResult(userWalletId) - } else { - TangemPayCustomerInfoError.UnknownError.left() // ignore if there's no TangemPay + val state: StateFlow>> + field = MutableStateFlow(value = mapOf()) + + suspend fun fetch(userWalletId: UserWalletId) { + Timber.tag(TAG).i("fetch: $userWalletId") + repository.checkCustomerWallet(userWalletId) + .fold( + ifLeft = { error -> + Timber.tag(TAG).e("Failed checkCustomerWallet for $userWalletId: ${error.javaClass.simpleName}") + updateState(userWalletId, TangemPayCustomerInfoError.UnknownError.left()) + }, + ifRight = { hasTangemPay -> + Timber.tag(TAG).i("checkCustomerWallet for $userWalletId: $hasTangemPay") + if (hasTangemPay) { + val oldResult = state.value[userWalletId] + if (oldResult == null) { + updateState(userWalletId, MainCustomerInfoContentState.Loading.right()) } - }, - ) - }, - catch = { error -> - Timber.tag(TAG).e(error) - TangemPayCustomerInfoError.UnknownError.left() - }, - ) + + val result = proceedWithPaeraCustomerResult(userWalletId) + .map(MainCustomerInfoContentState::Content) + updateState(userWalletId, result) + } else { + // ignore if there's no TangemPay + updateState(userWalletId, TangemPayCustomerInfoError.UnknownError.left()) + } + }, + ) + } + + operator fun invoke( + userWalletId: UserWalletId, + ): Flow> { + return state.mapNotNull { map -> map[userWalletId] } + } + + private fun updateState( + userWalletId: UserWalletId, + either: Either, + ) { + state.update { currentMap -> + currentMap.toMutableMap().apply { this[userWalletId] = either } + } + } private suspend fun proceedWithPaeraCustomerResult( userWalletId: UserWalletId, @@ -70,14 +85,13 @@ class TangemPayMainScreenCustomerInfoUseCase( private suspend fun proceedWithoutOrder( userWalletId: UserWalletId, ): Either { - Timber.tag(TAG).d("proceedWithoutOrder") return repository.getCustomerInfo(userWalletId) .mapLeft { error -> Timber.tag(TAG).e("mapErrorForCustomer: $error") error.mapErrorForCustomer() } .map { customerInfo -> - Timber.tag(TAG).d("customerInfo") + Timber.tag(TAG).i("customerInfo") if (customerInfo.cardInfo == null && customerInfo.isKycApproved) { // If order id wasn't saved -> start order creation and get customer info repository.createOrder(userWalletId) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/TangemPayMainInfoManager.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/TangemPayMainInfoManager.kt deleted file mode 100644 index ee1c60aee1..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/TangemPayMainInfoManager.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.tangem.feature.wallet.child.wallet.model - -import arrow.core.Either -import com.tangem.domain.models.wallet.UserWalletId -import com.tangem.domain.pay.model.MainScreenCustomerInfo -import com.tangem.domain.pay.model.TangemPayCustomerInfoError -import com.tangem.domain.pay.usecase.TangemPayMainScreenCustomerInfoUseCase -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.update -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -internal class TangemPayMainInfoManager @Inject constructor( - private val tangemPayMainScreenCustomerInfoUseCase: TangemPayMainScreenCustomerInfoUseCase, -) { - - val mainScreenCustomerInfo: - StateFlow>> - field = MutableStateFlow(mapOf()) - - suspend fun refreshTangemPayInfo(userWalletId: UserWalletId) { - mainScreenCustomerInfo.update { currentMap -> - val info = tangemPayMainScreenCustomerInfoUseCase(userWalletId) - currentMap.toMutableMap().apply { this[userWalletId] = info } - } - } -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/WalletModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/WalletModel.kt index 12f7478a3e..87e381b581 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/WalletModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/WalletModel.kt @@ -20,6 +20,7 @@ import com.tangem.domain.nft.ObserveAndClearNFTCacheIfNeedUseCase import com.tangem.domain.notifications.GetIsHuaweiDeviceWithoutGoogleServicesUseCase import com.tangem.domain.notifications.repository.NotificationsRepository import com.tangem.domain.pay.repository.OnboardingRepository +import com.tangem.domain.pay.usecase.TangemPayMainScreenCustomerInfoUseCase import com.tangem.domain.settings.* import com.tangem.domain.tokens.RefreshMultiCurrencyWalletQuotesUseCase import com.tangem.domain.wallets.usecase.* @@ -94,7 +95,7 @@ internal class WalletModel @Inject constructor( private val tangemPayOnboardingRepository: OnboardingRepository, private val yieldSupplyFeatureToggles: YieldSupplyFeatureToggles, private val accountsFeatureToggles: AccountsFeatureToggles, - private val tangemPayMainInfoManager: TangemPayMainInfoManager, + private val tangemPayMainScreenCustomerInfoUseCase: TangemPayMainScreenCustomerInfoUseCase, val screenLifecycleProvider: ScreenLifecycleProvider, val innerWalletRouter: InnerWalletRouter, ) : Model() { @@ -371,15 +372,15 @@ internal class WalletModel @Inject constructor( if (isShouldLaunchPeriodicUpdate) { updateTangemPayJobHolder.cancel() modelScope.launch { - tangemPayMainInfoManager.refreshTangemPayInfo(userWalletId) + tangemPayMainScreenCustomerInfoUseCase.fetch(userWalletId) while (isActive) { delay(TANGEM_PAY_UPDATE_INTERVAL) - tangemPayMainInfoManager.refreshTangemPayInfo(userWalletId) + tangemPayMainScreenCustomerInfoUseCase.fetch(userWalletId) } }.saveIn(updateTangemPayJobHolder) } else { // Don't refresh customer info periodically if the card was already issued, only update on swipe to refresh - tangemPayMainInfoManager.refreshTangemPayInfo(userWalletId) + tangemPayMainScreenCustomerInfoUseCase.fetch(userWalletId) } }.launchIn(modelScope) } 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 7ccd2dac17..480d649409 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 @@ -13,8 +13,8 @@ import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.requireColdWallet import com.tangem.domain.pay.repository.OnboardingRepository import com.tangem.domain.pay.usecase.ProduceTangemPayInitialDataUseCase +import com.tangem.domain.pay.usecase.TangemPayMainScreenCustomerInfoUseCase import com.tangem.domain.wallets.usecase.GetUserWalletUseCase -import com.tangem.feature.wallet.child.wallet.model.TangemPayMainInfoManager import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController import com.tangem.features.tangempay.TangemPayFeatureToggles import kotlinx.coroutines.launch @@ -43,7 +43,7 @@ internal class TangemPayClickIntentsImplementor @Inject constructor( private val getWalletMetainfoUseCase: GetWalletMetaInfoUseCase, private val getUserWalletUseCase: GetUserWalletUseCase, private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase, - private val tangemPayInfoManager: TangemPayMainInfoManager, + private val tangemPayMainScreenCustomerInfoUseCase: TangemPayMainScreenCustomerInfoUseCase, private val uiMessageSender: UiMessageSender, ) : BaseWalletClickIntents(), TangemPayIntents { @@ -54,13 +54,13 @@ internal class TangemPayClickIntentsImplementor @Inject constructor( ) { return } - tangemPayInfoManager.refreshTangemPayInfo(userWalletId) + tangemPayMainScreenCustomerInfoUseCase.fetch(userWalletId) } override fun onRefreshPayToken(userWalletId: UserWalletId) { modelScope.launch { produceInitialDataTangemPay.invoke(userWalletId) - tangemPayInfoManager.refreshTangemPayInfo(userWalletId) + tangemPayMainScreenCustomerInfoUseCase.fetch(userWalletId) } } 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 71ad45e71a..bbe8472d49 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 @@ -9,6 +9,8 @@ internal sealed class TangemPayState { object Empty : TangemPayState() + data object Loading : 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/TangemPayLoadingStateTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/TangemPayLoadingStateTransformer.kt new file mode 100644 index 0000000000..f731cb119d --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/TangemPayLoadingStateTransformer.kt @@ -0,0 +1,15 @@ +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 TangemPayLoadingStateTransformer(userWalletId: UserWalletId) : WalletStateTransformer(userWalletId) { + override fun transform(prevState: WalletState): WalletState { + return if (prevState is WalletState.MultiCurrency.Content) { + prevState.copy(tangemPayState = TangemPayState.Loading) + } 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 44174f221d..6f182566d8 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 @@ -2,26 +2,23 @@ package com.tangem.feature.wallet.presentation.wallet.subscribers import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.pay.model.MainCustomerInfoContentState import com.tangem.domain.pay.model.MainScreenCustomerInfo import com.tangem.domain.pay.model.TangemPayCustomerInfoError import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository +import com.tangem.domain.pay.usecase.TangemPayMainScreenCustomerInfoUseCase import com.tangem.domain.visa.model.TangemPayCardFrozenState -import com.tangem.feature.wallet.child.wallet.model.TangemPayMainInfoManager import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import com.tangem.feature.wallet.presentation.router.InnerWalletRouter import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletTangemPayAnalyticsEventSender import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController -import com.tangem.feature.wallet.presentation.wallet.state.transformers.TangemPayHiddenStateTransformer -import com.tangem.feature.wallet.presentation.wallet.state.transformers.TangemPayRefreshNeededStateTransformer -import com.tangem.feature.wallet.presentation.wallet.state.transformers.TangemPayUnavailableStateTransformer -import com.tangem.feature.wallet.presentation.wallet.state.transformers.TangemPayUpdateInfoStateTransformer +import com.tangem.feature.wallet.presentation.wallet.state.transformers.* import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.onEach import timber.log.Timber @@ -32,7 +29,7 @@ internal class TangemPayMainSubscriber @AssistedInject constructor( private val clickIntents: WalletClickIntents, private val innerWalletRouter: InnerWalletRouter, private val cardDetailsRepository: TangemPayCardDetailsRepository, - private val tangemPayMainInfoManager: TangemPayMainInfoManager, + private val tangemPayMainScreenCustomerInfoUseCase: TangemPayMainScreenCustomerInfoUseCase, private val analytics: WalletTangemPayAnalyticsEventSender, ) : WalletSubscriber() { @@ -41,12 +38,10 @@ internal class TangemPayMainSubscriber @AssistedInject constructor( } private fun subscribeOnTangemPayInfoUpdates(): Flow<*> { - return tangemPayMainInfoManager.mainScreenCustomerInfo - .filter { it.isNotEmpty() } + return tangemPayMainScreenCustomerInfoUseCase(userWalletId = userWallet.walletId) .distinctUntilChanged() - .onEach { data -> + .onEach { mainInfoData -> val userWalletId = userWallet.walletId - val mainInfoData = data[userWalletId] ?: return@onEach mainInfoData.onLeft { tangemPayError -> when (tangemPayError) { TangemPayCustomerInfoError.RefreshNeededError -> { @@ -70,13 +65,23 @@ internal class TangemPayMainSubscriber @AssistedInject constructor( ) } } - }.onRight { customerInfo -> - updateTangemPay(customerInfo, userWalletId) - analytics.send(customerInfo = customerInfo) - } + }.onRight { contentState -> handleContentState(state = contentState) } } } + private suspend fun handleContentState(state: MainCustomerInfoContentState) { + val userWalletId = userWallet.walletId + when (state) { + MainCustomerInfoContentState.Loading -> stateController.update( + transformer = TangemPayLoadingStateTransformer(userWalletId), + ) + is MainCustomerInfoContentState.Content -> { + updateTangemPay(data = state.info, userWalletId = userWalletId) + analytics.send(customerInfo = state.info) + } + } + } + private suspend fun updateTangemPay(data: MainScreenCustomerInfo, userWalletId: UserWalletId) { val cardFrozenState = data.info.productInstance?.cardId?.let { cardDetailsRepository.cardFrozenStateSync(it) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt index 3a0f5202b9..16212dc865 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt @@ -224,9 +224,9 @@ private fun WalletContent( contentType = selectedWallet.tangemPayState::class.java, ) { TangemPayMainScreenBlock( - selectedWallet.tangemPayState, + state = selectedWallet.tangemPayState, isBalanceHidden = state.isHidingMode, - itemModifier, + modifier = itemModifier, ) } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/visa/TangemPayLoadingScreenBlock.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/visa/TangemPayLoadingScreenBlock.kt new file mode 100644 index 0000000000..a2dbe13375 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/visa/TangemPayLoadingScreenBlock.kt @@ -0,0 +1,37 @@ +package com.tangem.feature.wallet.presentation.wallet.ui.components.visa + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.tangem.core.ui.components.CircleShimmer +import com.tangem.core.ui.components.RectangleShimmer +import com.tangem.core.ui.components.SpacerWMax +import com.tangem.core.ui.res.TangemTheme + +@Composable +internal fun TangemPayLoadingScreenBlock(modifier: Modifier = Modifier) { + Row( + modifier = modifier + .fillMaxWidth() + .background(color = TangemTheme.colors.background.primary, shape = TangemTheme.shapes.roundedCornersXMedium) + .padding(horizontal = 12.dp, vertical = 16.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + CircleShimmer(modifier = Modifier.size(36.dp)) + Column( + modifier = Modifier.padding(start = 12.dp), + verticalArrangement = Arrangement.spacedBy(2.dp), + ) { + RectangleShimmer(modifier = Modifier.padding(vertical = 4.dp).sizeIn(minWidth = 70.dp, minHeight = 12.dp)) + RectangleShimmer(modifier = Modifier.padding(vertical = 2.dp).sizeIn(minWidth = 52.dp, minHeight = 12.dp)) + } + SpacerWMax() + Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { + RectangleShimmer(modifier = Modifier.padding(vertical = 4.dp).sizeIn(minWidth = 40.dp, minHeight = 12.dp)) + RectangleShimmer(modifier = Modifier.padding(vertical = 2.dp).sizeIn(minWidth = 40.dp, minHeight = 12.dp)) + } + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/visa/TangemPayMainScreenBlock.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/visa/TangemPayMainScreenBlock.kt index d2750a8903..24f11c043c 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) + TangemPayState.Loading -> TangemPayLoadingScreenBlock(modifier) } } @@ -32,6 +33,8 @@ internal fun TangemPayMainScreenBlock(state: TangemPayState, isBalanceHidden: Bo private fun TangemPayMainScreenBlockPreview() { TangemThemePreview { Column(verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8)) { + TangemPayMainScreenBlock(state = TangemPayState.Loading, isBalanceHidden = false) + TangemPayMainScreenBlock( Progress( title = TextReference.Res(R.string.tangempay_kyc_in_progress_notification_title),