Updated on 2026-08-14
This commit is contained in:
parent
b9a2ab5fed
commit
5c93d3973a
11 changed files with 139 additions and 86 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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<TangemPayCustomerInfoError, MainScreenCustomerInfo> = 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<Map<UserWalletId, Either<TangemPayCustomerInfoError, MainCustomerInfoContentState>>>
|
||||
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<Either<TangemPayCustomerInfoError, MainCustomerInfoContentState>> {
|
||||
return state.mapNotNull { map -> map[userWalletId] }
|
||||
}
|
||||
|
||||
private fun updateState(
|
||||
userWalletId: UserWalletId,
|
||||
either: Either<TangemPayCustomerInfoError, MainCustomerInfoContentState>,
|
||||
) {
|
||||
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<TangemPayCustomerInfoError, MainScreenCustomerInfo> {
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -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<Map<UserWalletId, Either<TangemPayCustomerInfoError, MainScreenCustomerInfo>>>
|
||||
field = MutableStateFlow(mapOf())
|
||||
|
||||
suspend fun refreshTangemPayInfo(userWalletId: UserWalletId) {
|
||||
mainScreenCustomerInfo.update { currentMap ->
|
||||
val info = tangemPayMainScreenCustomerInfoUseCase(userWalletId)
|
||||
currentMap.toMutableMap().apply { this[userWalletId] = info }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ internal sealed class TangemPayState {
|
|||
|
||||
object Empty : TangemPayState()
|
||||
|
||||
data object Loading : TangemPayState()
|
||||
|
||||
data class Progress(
|
||||
val title: TextReference,
|
||||
val description: TextReference,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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) }
|
||||
|
|
|
|||
|
|
@ -224,9 +224,9 @@ private fun WalletContent(
|
|||
contentType = selectedWallet.tangemPayState::class.java,
|
||||
) {
|
||||
TangemPayMainScreenBlock(
|
||||
selectedWallet.tangemPayState,
|
||||
state = selectedWallet.tangemPayState,
|
||||
isBalanceHidden = state.isHidingMode,
|
||||
itemModifier,
|
||||
modifier = itemModifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue