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.Serializable
import java.util.Locale
@Serializable
enum class TangemPayCardFrozenState {
@ -22,9 +23,9 @@ enum class TangemPayCardFrozenState {
}
companion object {
fun fromString(value: String) = when (value) {
"Frozen" -> Frozen
"Unfrozen" -> Unfrozen
fun fromString(value: String) = when (value.lowercase(Locale.US)) {
"frozen" -> Frozen
"unfrozen" -> Unfrozen
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.model.TangemPayAddToWalletModel
import com.tangem.features.tangempay.ui.TangemPayAddToWalletScreen
import com.tangem.features.tangempay.utils.firstCard
import com.tangem.features.tangempay.utils.userWalletId
internal class TangemPayAddToWalletComponent(
@ -26,7 +25,7 @@ internal class TangemPayAddToWalletComponent(
private val cardDetailsBlockComponent = DefaultTangemPayCardDetailsBlockComponent(
appComponentContext = child("cardDetailsBlockComponent"),
params = TangemPayCardDetailsBlockComponent.Params(
card = params.initialStatus.firstCard(),
initialStatus = params.initialStatus,
userWalletId = params.initialStatus.userWalletId,
isEditingNameEnabled = false,
),

View file

@ -35,7 +35,7 @@ internal class TangemPayCardPageScreenComponent(
private val cardDetailsBlockComponent = DefaultTangemPayCardDetailsBlockComponent(
appComponentContext = child("cardDetailsBlockComponent"),
params = TangemPayCardDetailsBlockComponent.Params(
card = params.initialStatus.firstCard(),
initialStatus = params.initialStatus,
userWalletId = params.initialStatus.userWalletId,
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.model.TangemPayEditDisplayNameModel
import com.tangem.features.tangempay.ui.TangemPayEditDisplayNameScreen
import com.tangem.features.tangempay.utils.firstCard
import com.tangem.features.tangempay.utils.userWalletId
internal class TangemPayEditDisplayNameComponent(
@ -27,7 +26,7 @@ internal class TangemPayEditDisplayNameComponent(
private val cardDetailsBlockComponent = DefaultTangemPayCardDetailsBlockComponent(
appComponentContext = child("editDisplayNameCardDetails"),
params = TangemPayCardDetailsBlockComponent.Params(
card = params.initialStatus.firstCard(),
initialStatus = params.initialStatus,
userWalletId = params.initialStatus.userWalletId,
isEditingNameEnabled = false,
),

View file

@ -3,7 +3,7 @@ package com.tangem.features.tangempay.components.cardDetails
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
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.features.tangempay.entity.TangemPayCardDetailsUM
import kotlinx.coroutines.flow.StateFlow
@ -16,7 +16,7 @@ internal interface TangemPayCardDetailsBlockComponent {
fun CardDetailsBlockContent(state: TangemPayCardDetailsUM, modifier: Modifier)
data class Params(
val card: TangemPayCard,
val initialStatus: AccountStatus.Payment,
val userWalletId: UserWalletId,
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.domain.models.StatusSource
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.wallet.UserWalletId
import com.tangem.domain.pay.flow.PaymentAccountStatusSupplier
import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository
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.TangemPayCardDetailsUpdateNameTransformer
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.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.JobHolder
import com.tangem.utils.coroutines.saveIn
import com.tangem.utils.transformer.update
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
@ -59,11 +59,12 @@ internal class TangemPayCardDetailsBlockModel @Inject constructor(
) : Model() {
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(
cardNumberEnd = card.lastDigits,
displayName = card.displayName,
cardNumberEnd = initialCard.lastDigits,
displayName = initialCard.displayName,
isEditingNameEnabled = params.isEditingNameEnabled,
onEditNameClick = ::startEditingDisplayName,
onReveal = ::requestReveal,
@ -77,8 +78,7 @@ internal class TangemPayCardDetailsBlockModel @Inject constructor(
private val showCardDetailsTimerJobHolder = JobHolder()
init {
subscribeToCardChanges(cardId = card.id, userWalletId = params.userWalletId)
subscribeToCardFrozenState()
subscribeToCardChanges()
modelScope.launch {
cardDetailsEventListener.event.collectLatest { event ->
when (event) {
@ -89,15 +89,12 @@ internal class TangemPayCardDetailsBlockModel @Inject constructor(
}
}
private fun subscribeToCardChanges(cardId: String, userWalletId: UserWalletId) {
paymentAccountStatusSupplier.invoke(userWalletId)
private fun subscribeToCardChanges() {
paymentAccountStatusSupplier.invoke(params.userWalletId)
.onEach { state ->
val status = state.value
if (status is PaymentAccountStatusValue.Loaded &&
status.source == StatusSource.ACTUAL &&
status.hasCardWithId(cardId)
) {
val card = status.requireCardWithId(cardId)
if (status is PaymentAccountStatusValue.Loaded && status.source == StatusSource.ACTUAL) {
val card = state.findCard(initialCard.id, params.initialStatus) ?: return@onEach
if (card.isReissuing) {
requestHide()
}
@ -109,13 +106,15 @@ internal class TangemPayCardDetailsBlockModel @Inject constructor(
isActionsAvailable = !card.isReissuing,
)
}
subscribeToCardFrozenState(card.id)
}
}
.launchIn(modelScope)
}
private fun subscribeToCardFrozenState() {
cardDetailsRepository.cardFrozenState(card.id)
private fun subscribeToCardFrozenState(cardId: String) {
frozenStateJob?.cancel()
frozenStateJob = cardDetailsRepository.cardFrozenState(cardId)
.onEach { cardFrozenState ->
if (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.TokenReceiveConfig
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.TangemPayCardLimitPeriod
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.CardDetailsEventListener
import com.tangem.features.tangempay.navigation.TangemPayCardDetailsInnerRoute
import com.tangem.features.tangempay.utils.TangemPayMessagesFactory
import com.tangem.features.tangempay.utils.cryptoCurrency
import com.tangem.features.tangempay.utils.firstCard
import com.tangem.features.tangempay.utils.userWalletId
import com.tangem.features.tangempay.utils.*
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.JobHolder
import com.tangem.utils.coroutines.saveIn
@ -75,14 +70,18 @@ internal class TangemPayCardPageModel @Inject constructor(
) : Model(), ViewPinListener, ReissueCardListener, AddFundsListener {
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 addFundsJobHolder = 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>
field = MutableStateFlow(
TangemPayCardPageUM(
@ -102,12 +101,10 @@ internal class TangemPayCardPageModel @Inject constructor(
paymentAccountStatusSupplier.invoke(userWalletId)
.onEach { state ->
currentStatus.update { state }
val status = state.value
if (status is PaymentAccountStatusValue.Loaded &&
status.source == StatusSource.ACTUAL &&
status.hasCardWithId(cardId)
) {
val card = status.requireCardWithId(cardId)
if (status is PaymentAccountStatusValue.Loaded && status.source == StatusSource.ACTUAL) {
val card = state.findCard(initialCardId, params.initialStatus) ?: return@onEach
val limit = card.limit?.actualCardLimit?.takeIf { it.period == TangemPayCardLimitPeriod.DAY }
val dailyLimitState = if (limit != null) {
TangemPayDailyLimitBlockState.Content(
@ -228,10 +225,11 @@ internal class TangemPayCardPageModel @Inject constructor(
if (!isPinSet) {
router.push(TangemPayCardDetailsInnerRoute.ChangePIN)
} else {
val card = currentStatus.value.findCard(initialCardId, params.initialStatus) ?: return
bottomSheetNavigation.activate(
TangemPayCardNavigation.ViewPinCode(
userWalletId = userWalletId,
cardId = cardId,
cardId = card.id,
),
)
}
@ -313,10 +311,11 @@ internal class TangemPayCardPageModel @Inject constructor(
}
private fun freezeCard() {
val card = currentStatus.value.findCard(initialCardId, params.initialStatus) ?: return
modelScope.launch {
changeCardFrozenStateUseCase(
userWalletId = userWalletId,
cardId = cardId,
cardId = card.id,
isFreezing = true,
).onLeft {
val message = SnackbarMessage(resourceReference(R.string.tangem_pay_freeze_card_failed))
@ -329,10 +328,11 @@ internal class TangemPayCardPageModel @Inject constructor(
}
private fun unfreezeCard() {
val card = currentStatus.value.findCard(initialCardId, params.initialStatus) ?: return
modelScope.launch {
changeCardFrozenStateUseCase(
userWalletId = userWalletId,
cardId = cardId,
cardId = card.id,
isFreezing = false,
).onLeft {
val message = SnackbarMessage(resourceReference(R.string.tangem_pay_unfreeze_card_failed))

View file

@ -1,7 +1,9 @@
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.PaymentAccountStatusValue
import com.tangem.domain.models.account.findCardWithId
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.pay.TangemPayCard
import com.tangem.domain.models.wallet.UserWalletId
@ -24,3 +26,28 @@ internal fun AccountStatus.Payment.requireLoaded(): PaymentAccountStatusValue.Lo
?: error("Card-detail subflow requires Loaded status, got ${value::class.simpleName}")
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()
}