Updated on 2026-08-14

This commit is contained in:
Tangem 2026-05-29 01:34:49 -07:00
parent 72c9e20396
commit 514e142989
8 changed files with 70 additions and 45 deletions

View file

@ -2,6 +2,7 @@ package com.tangem.domain.models.pay
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import java.util.Locale
@Serializable @Serializable
enum class TangemPayCardFrozenState { enum class TangemPayCardFrozenState {
@ -22,9 +23,9 @@ enum class TangemPayCardFrozenState {
} }
companion object { companion object {
fun fromString(value: String) = when (value) { fun fromString(value: String) = when (value.lowercase(Locale.US)) {
"Frozen" -> Frozen "frozen" -> Frozen
"Unfrozen" -> Unfrozen "unfrozen" -> Unfrozen
else -> Pending else -> Pending
} }
} }

View file

@ -13,7 +13,6 @@ import com.tangem.features.tangempay.components.cardDetails.DefaultTangemPayCard
import com.tangem.features.tangempay.components.cardDetails.TangemPayCardDetailsBlockComponent import com.tangem.features.tangempay.components.cardDetails.TangemPayCardDetailsBlockComponent
import com.tangem.features.tangempay.model.TangemPayAddToWalletModel import com.tangem.features.tangempay.model.TangemPayAddToWalletModel
import com.tangem.features.tangempay.ui.TangemPayAddToWalletScreen import com.tangem.features.tangempay.ui.TangemPayAddToWalletScreen
import com.tangem.features.tangempay.utils.firstCard
import com.tangem.features.tangempay.utils.userWalletId import com.tangem.features.tangempay.utils.userWalletId
internal class TangemPayAddToWalletComponent( internal class TangemPayAddToWalletComponent(
@ -26,7 +25,7 @@ internal class TangemPayAddToWalletComponent(
private val cardDetailsBlockComponent = DefaultTangemPayCardDetailsBlockComponent( private val cardDetailsBlockComponent = DefaultTangemPayCardDetailsBlockComponent(
appComponentContext = child("cardDetailsBlockComponent"), appComponentContext = child("cardDetailsBlockComponent"),
params = TangemPayCardDetailsBlockComponent.Params( params = TangemPayCardDetailsBlockComponent.Params(
card = params.initialStatus.firstCard(), initialStatus = params.initialStatus,
userWalletId = params.initialStatus.userWalletId, userWalletId = params.initialStatus.userWalletId,
isEditingNameEnabled = false, isEditingNameEnabled = false,
), ),

View file

@ -35,7 +35,7 @@ internal class TangemPayCardPageScreenComponent(
private val cardDetailsBlockComponent = DefaultTangemPayCardDetailsBlockComponent( private val cardDetailsBlockComponent = DefaultTangemPayCardDetailsBlockComponent(
appComponentContext = child("cardDetailsBlockComponent"), appComponentContext = child("cardDetailsBlockComponent"),
params = TangemPayCardDetailsBlockComponent.Params( params = TangemPayCardDetailsBlockComponent.Params(
card = params.initialStatus.firstCard(), initialStatus = params.initialStatus,
userWalletId = params.initialStatus.userWalletId, userWalletId = params.initialStatus.userWalletId,
isEditingNameEnabled = true, isEditingNameEnabled = true,
), ),

View file

@ -14,7 +14,6 @@ import com.tangem.features.tangempay.components.cardDetails.TangemPayCardDetails
import com.tangem.features.tangempay.entity.DisplayNameState import com.tangem.features.tangempay.entity.DisplayNameState
import com.tangem.features.tangempay.model.TangemPayEditDisplayNameModel import com.tangem.features.tangempay.model.TangemPayEditDisplayNameModel
import com.tangem.features.tangempay.ui.TangemPayEditDisplayNameScreen import com.tangem.features.tangempay.ui.TangemPayEditDisplayNameScreen
import com.tangem.features.tangempay.utils.firstCard
import com.tangem.features.tangempay.utils.userWalletId import com.tangem.features.tangempay.utils.userWalletId
internal class TangemPayEditDisplayNameComponent( internal class TangemPayEditDisplayNameComponent(
@ -27,7 +26,7 @@ internal class TangemPayEditDisplayNameComponent(
private val cardDetailsBlockComponent = DefaultTangemPayCardDetailsBlockComponent( private val cardDetailsBlockComponent = DefaultTangemPayCardDetailsBlockComponent(
appComponentContext = child("editDisplayNameCardDetails"), appComponentContext = child("editDisplayNameCardDetails"),
params = TangemPayCardDetailsBlockComponent.Params( params = TangemPayCardDetailsBlockComponent.Params(
card = params.initialStatus.firstCard(), initialStatus = params.initialStatus,
userWalletId = params.initialStatus.userWalletId, userWalletId = params.initialStatus.userWalletId,
isEditingNameEnabled = false, isEditingNameEnabled = false,
), ),

View file

@ -3,7 +3,7 @@ package com.tangem.features.tangempay.components.cardDetails
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable import androidx.compose.runtime.Stable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import com.tangem.domain.models.pay.TangemPayCard import com.tangem.domain.models.account.AccountStatus
import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.features.tangempay.entity.TangemPayCardDetailsUM import com.tangem.features.tangempay.entity.TangemPayCardDetailsUM
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
@ -16,7 +16,7 @@ internal interface TangemPayCardDetailsBlockComponent {
fun CardDetailsBlockContent(state: TangemPayCardDetailsUM, modifier: Modifier) fun CardDetailsBlockContent(state: TangemPayCardDetailsUM, modifier: Modifier)
data class Params( data class Params(
val card: TangemPayCard, val initialStatus: AccountStatus.Payment,
val userWalletId: UserWalletId, val userWalletId: UserWalletId,
val isEditingNameEnabled: Boolean, val isEditingNameEnabled: Boolean,
) )

View file

@ -12,10 +12,7 @@ import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.message.SnackbarMessage import com.tangem.core.ui.message.SnackbarMessage
import com.tangem.domain.models.StatusSource import com.tangem.domain.models.StatusSource
import com.tangem.domain.models.account.PaymentAccountStatusValue import com.tangem.domain.models.account.PaymentAccountStatusValue
import com.tangem.domain.models.account.hasCardWithId
import com.tangem.domain.models.account.requireCardWithId
import com.tangem.domain.models.pay.TangemPayCardFrozenState import com.tangem.domain.models.pay.TangemPayCardFrozenState
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.pay.flow.PaymentAccountStatusSupplier import com.tangem.domain.pay.flow.PaymentAccountStatusSupplier
import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository
import com.tangem.domain.tangempay.TangemPayAnalyticsEvents import com.tangem.domain.tangempay.TangemPayAnalyticsEvents
@ -30,11 +27,14 @@ import com.tangem.features.tangempay.model.transformers.DetailsRevealProgressSta
import com.tangem.features.tangempay.model.transformers.DetailsRevealedStateTransformer import com.tangem.features.tangempay.model.transformers.DetailsRevealedStateTransformer
import com.tangem.features.tangempay.model.transformers.TangemPayCardDetailsUpdateNameTransformer import com.tangem.features.tangempay.model.transformers.TangemPayCardDetailsUpdateNameTransformer
import com.tangem.features.tangempay.navigation.TangemPayCardDetailsInnerRoute import com.tangem.features.tangempay.navigation.TangemPayCardDetailsInnerRoute
import com.tangem.features.tangempay.utils.findCard
import com.tangem.features.tangempay.utils.firstCard
import com.tangem.utils.StringsSigns import com.tangem.utils.StringsSigns
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.JobHolder import com.tangem.utils.coroutines.JobHolder
import com.tangem.utils.coroutines.saveIn import com.tangem.utils.coroutines.saveIn
import com.tangem.utils.transformer.update import com.tangem.utils.transformer.update
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -59,11 +59,12 @@ internal class TangemPayCardDetailsBlockModel @Inject constructor(
) : Model() { ) : Model() {
private val params: TangemPayCardDetailsBlockComponent.Params = paramsContainer.require() private val params: TangemPayCardDetailsBlockComponent.Params = paramsContainer.require()
private val card = params.card private val initialCard = params.initialStatus.firstCard()
private var frozenStateJob: Job? = null
private val stateFactory = TangemPayCardDetailsBlockStateFactory( private val stateFactory = TangemPayCardDetailsBlockStateFactory(
cardNumberEnd = card.lastDigits, cardNumberEnd = initialCard.lastDigits,
displayName = card.displayName, displayName = initialCard.displayName,
isEditingNameEnabled = params.isEditingNameEnabled, isEditingNameEnabled = params.isEditingNameEnabled,
onEditNameClick = ::startEditingDisplayName, onEditNameClick = ::startEditingDisplayName,
onReveal = ::requestReveal, onReveal = ::requestReveal,
@ -77,8 +78,7 @@ internal class TangemPayCardDetailsBlockModel @Inject constructor(
private val showCardDetailsTimerJobHolder = JobHolder() private val showCardDetailsTimerJobHolder = JobHolder()
init { init {
subscribeToCardChanges(cardId = card.id, userWalletId = params.userWalletId) subscribeToCardChanges()
subscribeToCardFrozenState()
modelScope.launch { modelScope.launch {
cardDetailsEventListener.event.collectLatest { event -> cardDetailsEventListener.event.collectLatest { event ->
when (event) { when (event) {
@ -89,15 +89,12 @@ internal class TangemPayCardDetailsBlockModel @Inject constructor(
} }
} }
private fun subscribeToCardChanges(cardId: String, userWalletId: UserWalletId) { private fun subscribeToCardChanges() {
paymentAccountStatusSupplier.invoke(userWalletId) paymentAccountStatusSupplier.invoke(params.userWalletId)
.onEach { state -> .onEach { state ->
val status = state.value val status = state.value
if (status is PaymentAccountStatusValue.Loaded && if (status is PaymentAccountStatusValue.Loaded && status.source == StatusSource.ACTUAL) {
status.source == StatusSource.ACTUAL && val card = state.findCard(initialCard.id, params.initialStatus) ?: return@onEach
status.hasCardWithId(cardId)
) {
val card = status.requireCardWithId(cardId)
if (card.isReissuing) { if (card.isReissuing) {
requestHide() requestHide()
} }
@ -109,13 +106,15 @@ internal class TangemPayCardDetailsBlockModel @Inject constructor(
isActionsAvailable = !card.isReissuing, isActionsAvailable = !card.isReissuing,
) )
} }
subscribeToCardFrozenState(card.id)
} }
} }
.launchIn(modelScope) .launchIn(modelScope)
} }
private fun subscribeToCardFrozenState() { private fun subscribeToCardFrozenState(cardId: String) {
cardDetailsRepository.cardFrozenState(card.id) frozenStateJob?.cancel()
frozenStateJob = cardDetailsRepository.cardFrozenState(cardId)
.onEach { cardFrozenState -> .onEach { cardFrozenState ->
if (cardFrozenState == TangemPayCardFrozenState.Pending) { if (cardFrozenState == TangemPayCardFrozenState.Pending) {
uiState.update { state -> state.copy(cardFrozenState = TangemPayCardFrozenState.Pending) } uiState.update { state -> state.copy(cardFrozenState = TangemPayCardFrozenState.Pending) }

View file

@ -24,8 +24,6 @@ import com.tangem.core.ui.test.TangemPayTestTags
import com.tangem.domain.models.StatusSource import com.tangem.domain.models.StatusSource
import com.tangem.domain.models.TokenReceiveConfig import com.tangem.domain.models.TokenReceiveConfig
import com.tangem.domain.models.account.PaymentAccountStatusValue import com.tangem.domain.models.account.PaymentAccountStatusValue
import com.tangem.domain.models.account.hasCardWithId
import com.tangem.domain.models.account.requireCardWithId
import com.tangem.domain.models.pay.TangemPayCard import com.tangem.domain.models.pay.TangemPayCard
import com.tangem.domain.models.pay.TangemPayCardLimitPeriod import com.tangem.domain.models.pay.TangemPayCardLimitPeriod
import com.tangem.domain.models.pay.isFrozen import com.tangem.domain.models.pay.isFrozen
@ -43,10 +41,7 @@ import com.tangem.features.tangempay.entity.*
import com.tangem.features.tangempay.model.listener.CardDetailsEvent import com.tangem.features.tangempay.model.listener.CardDetailsEvent
import com.tangem.features.tangempay.model.listener.CardDetailsEventListener import com.tangem.features.tangempay.model.listener.CardDetailsEventListener
import com.tangem.features.tangempay.navigation.TangemPayCardDetailsInnerRoute import com.tangem.features.tangempay.navigation.TangemPayCardDetailsInnerRoute
import com.tangem.features.tangempay.utils.TangemPayMessagesFactory import com.tangem.features.tangempay.utils.*
import com.tangem.features.tangempay.utils.cryptoCurrency
import com.tangem.features.tangempay.utils.firstCard
import com.tangem.features.tangempay.utils.userWalletId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.JobHolder import com.tangem.utils.coroutines.JobHolder
import com.tangem.utils.coroutines.saveIn import com.tangem.utils.coroutines.saveIn
@ -75,14 +70,18 @@ internal class TangemPayCardPageModel @Inject constructor(
) : Model(), ViewPinListener, ReissueCardListener, AddFundsListener { ) : Model(), ViewPinListener, ReissueCardListener, AddFundsListener {
private val params: TangemPayCardPageComponent.Params = paramsContainer.require() private val params: TangemPayCardPageComponent.Params = paramsContainer.require()
private val cardId: String = params.initialStatus.firstCard().id
private val userWalletId = params.initialStatus.userWalletId
private val cryptoCurrency = params.initialStatus.cryptoCurrency
private val addToWalletBannerJobHolder = JobHolder() private val addToWalletBannerJobHolder = JobHolder()
private val addFundsJobHolder = JobHolder() private val addFundsJobHolder = JobHolder()
private val frozenStateJobHolder = JobHolder() private val frozenStateJobHolder = JobHolder()
private val currentStatus = MutableStateFlow(params.initialStatus)
private val initialCardId = params.initialStatus.firstCard().id
private val userWalletId = currentStatus.value.userWalletId
private val cryptoCurrency
get() = currentStatus.value.cryptoCurrency
val uiState: StateFlow<TangemPayCardPageUM> val uiState: StateFlow<TangemPayCardPageUM>
field = MutableStateFlow( field = MutableStateFlow(
TangemPayCardPageUM( TangemPayCardPageUM(
@ -102,12 +101,10 @@ internal class TangemPayCardPageModel @Inject constructor(
paymentAccountStatusSupplier.invoke(userWalletId) paymentAccountStatusSupplier.invoke(userWalletId)
.onEach { state -> .onEach { state ->
currentStatus.update { state }
val status = state.value val status = state.value
if (status is PaymentAccountStatusValue.Loaded && if (status is PaymentAccountStatusValue.Loaded && status.source == StatusSource.ACTUAL) {
status.source == StatusSource.ACTUAL && val card = state.findCard(initialCardId, params.initialStatus) ?: return@onEach
status.hasCardWithId(cardId)
) {
val card = status.requireCardWithId(cardId)
val limit = card.limit?.actualCardLimit?.takeIf { it.period == TangemPayCardLimitPeriod.DAY } val limit = card.limit?.actualCardLimit?.takeIf { it.period == TangemPayCardLimitPeriod.DAY }
val dailyLimitState = if (limit != null) { val dailyLimitState = if (limit != null) {
TangemPayDailyLimitBlockState.Content( TangemPayDailyLimitBlockState.Content(
@ -228,10 +225,11 @@ internal class TangemPayCardPageModel @Inject constructor(
if (!isPinSet) { if (!isPinSet) {
router.push(TangemPayCardDetailsInnerRoute.ChangePIN) router.push(TangemPayCardDetailsInnerRoute.ChangePIN)
} else { } else {
val card = currentStatus.value.findCard(initialCardId, params.initialStatus) ?: return
bottomSheetNavigation.activate( bottomSheetNavigation.activate(
TangemPayCardNavigation.ViewPinCode( TangemPayCardNavigation.ViewPinCode(
userWalletId = userWalletId, userWalletId = userWalletId,
cardId = cardId, cardId = card.id,
), ),
) )
} }
@ -313,10 +311,11 @@ internal class TangemPayCardPageModel @Inject constructor(
} }
private fun freezeCard() { private fun freezeCard() {
val card = currentStatus.value.findCard(initialCardId, params.initialStatus) ?: return
modelScope.launch { modelScope.launch {
changeCardFrozenStateUseCase( changeCardFrozenStateUseCase(
userWalletId = userWalletId, userWalletId = userWalletId,
cardId = cardId, cardId = card.id,
isFreezing = true, isFreezing = true,
).onLeft { ).onLeft {
val message = SnackbarMessage(resourceReference(R.string.tangem_pay_freeze_card_failed)) val message = SnackbarMessage(resourceReference(R.string.tangem_pay_freeze_card_failed))
@ -329,10 +328,11 @@ internal class TangemPayCardPageModel @Inject constructor(
} }
private fun unfreezeCard() { private fun unfreezeCard() {
val card = currentStatus.value.findCard(initialCardId, params.initialStatus) ?: return
modelScope.launch { modelScope.launch {
changeCardFrozenStateUseCase( changeCardFrozenStateUseCase(
userWalletId = userWalletId, userWalletId = userWalletId,
cardId = cardId, cardId = card.id,
isFreezing = false, isFreezing = false,
).onLeft { ).onLeft {
val message = SnackbarMessage(resourceReference(R.string.tangem_pay_unfreeze_card_failed)) val message = SnackbarMessage(resourceReference(R.string.tangem_pay_unfreeze_card_failed))

View file

@ -1,7 +1,9 @@
package com.tangem.features.tangempay.utils package com.tangem.features.tangempay.utils
import com.tangem.domain.models.StatusSource
import com.tangem.domain.models.account.AccountStatus import com.tangem.domain.models.account.AccountStatus
import com.tangem.domain.models.account.PaymentAccountStatusValue import com.tangem.domain.models.account.PaymentAccountStatusValue
import com.tangem.domain.models.account.findCardWithId
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.pay.TangemPayCard import com.tangem.domain.models.pay.TangemPayCard
import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.UserWalletId
@ -23,4 +25,29 @@ internal fun AccountStatus.Payment.requireLoaded(): PaymentAccountStatusValue.Lo
value as? PaymentAccountStatusValue.Loaded value as? PaymentAccountStatusValue.Loaded
?: error("Card-detail subflow requires Loaded status, got ${value::class.simpleName}") ?: error("Card-detail subflow requires Loaded status, got ${value::class.simpleName}")
internal fun AccountStatus.Payment.firstCard(): TangemPayCard = requireLoaded().cards.first() internal fun AccountStatus.Payment.firstCard(): TangemPayCard = requireLoaded().cards.first()
internal inline fun <T> AccountStatus.Payment.ifLoadedOrNull(call: (PaymentAccountStatusValue.Loaded) -> T): T? {
val value = value
return if (value is PaymentAccountStatusValue.Loaded) {
call(value)
} else {
null
}
}
internal fun AccountStatus.Payment.findCard(
initialCardId: String,
initialStatus: AccountStatus.Payment,
): TangemPayCard? {
val value = value
if (value !is PaymentAccountStatusValue.Loaded || value.source != StatusSource.ACTUAL) return null
val initialCard = value.findCardWithId(initialCardId)
val newCards = initialStatus.ifLoadedOrNull { status ->
val initialCardIds = status.cards.mapTo(mutableSetOf()) { it.id }
value.cards.filterNot { it.id in initialCardIds }
}
return initialCard ?: newCards?.firstOrNull()
}