Updated on 2026-08-14

This commit is contained in:
Tangem 2026-07-14 01:36:42 -07:00
parent 65d2e84c6e
commit 9e986a6cb3
21 changed files with 180 additions and 77 deletions

View file

@ -20,6 +20,7 @@ import com.tangem.domain.models.pay.TangemPayCardState
import com.tangem.domain.models.pay.isFrozen
import com.tangem.domain.models.pay.thumbnailUrl
import com.tangem.features.tangempay.details.impl.R
import com.tangem.features.tangempay.model.transformers.DetailsBalanceTransformer
import com.tangem.features.tangempay.utils.TangemPayDetailIntents
import com.tangem.features.tangempay.utils.hasWithdrawableAmount
import com.tangem.features.tangempay.utils.isFresh
@ -28,7 +29,7 @@ import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import com.tangem.core.ui.R as CoreUiR
@Suppress("LongParameterList")
@Suppress("LongParameterList", "LargeClass")
internal class TangemPayDetailsStateFactory(
private val onBack: () -> Unit,
private val onOpenMenu: () -> Unit,
@ -159,6 +160,51 @@ internal class TangemPayDetailsStateFactory(
)
}
fun getInactiveState(status: PaymentAccountStatusValue.Inactive): TangemPayDetailsUM {
val notification = notificationFactory.createAwaitingDepositConfig(status.tariffPlan)
return TangemPayDetailsUM(
topBarConfig = TangemPayDetailsTopBarConfig(
onBackClick = onBack,
onOpenMenu = onOpenMenu,
items = getTopBarMenuItems(),
itemsV2 = getTopBarMenuItemsV2(tariffPlan = null),
),
pullToRefreshConfig = PullToRefreshConfig(
isRefreshing = false,
onRefresh = intents::onRefreshSwipe,
),
balanceBlockState = TangemPayDetailsBalanceBlockState.Content(
actionButtons = getActionButtonsConfig(
isAddFundsEnabled = true,
isWithdrawEnabled = false,
),
cardsBlockState = TangemPayDetailsBalanceBlockState.CardsBlockState(
cards = persistentListOf(
TangemPayDetailsBalanceBlockState.Card(
lastDigits = "",
imageUrl = null,
onClick = {},
isEnabled = false,
isFrozen = false,
state = TangemPayCardUiState.InProgress,
),
),
onAddCardClick = intents::onAddCardClick,
isAddCardEnabled = false,
),
fiatBalance = DetailsBalanceTransformer.getFiatBalanceText(status.fiatBalance),
isInactive = true,
isNegative = false,
isBalanceFlickering = false,
),
isBalanceHidden = false,
addToWalletBlockState = null,
errorNotificationConfig = notification,
accountDeactivatedNotificationConfig = null,
cashbackBlockState = null,
)
}
private fun getTopBarMenuItems(): ImmutableList<TangemDropdownMenuItem> {
return persistentListOf(
TangemDropdownMenuItem(

View file

@ -110,6 +110,7 @@ internal sealed class TangemPayDetailsBalanceBlockState {
val fiatBalance: TextReference,
val isBalanceFlickering: Boolean,
val isNegative: Boolean,
val isInactive: Boolean,
val isMuted: Boolean = false,
) : TangemPayDetailsBalanceBlockState()

View file

@ -56,6 +56,7 @@ import com.tangem.features.tangempay.entity.TangemPayDetailsStateFactory
import com.tangem.features.tangempay.entity.TangemPayDetailsUM
import com.tangem.features.tangempay.model.transformers.*
import com.tangem.features.tangempay.navigation.TangemPayAccountDetailsInnerRoute
import com.tangem.features.tangempay.tiers.feeCurrency
import com.tangem.features.tangempay.utils.*
import com.tangem.features.tokendetails.ExpressTransactionsEvent
import com.tangem.features.tokendetails.ExpressTransactionsEventListener
@ -171,6 +172,9 @@ internal class TangemPayDetailsModel @Inject constructor(
)
uiState.update { balanceTransformer.transform(stateFactory.getLoadedState(state)) }
}
is PaymentAccountStatusValue.Inactive -> uiState.update {
stateFactory.getInactiveState(state)
}
else -> uiState.update { stateFactory.getLoadingState() }
}
}
@ -484,11 +488,6 @@ internal class TangemPayDetailsModel @Inject constructor(
)
return
}
val activeCardsCount = currentStatus.value.ifLoadedOrNull { loaded -> loaded.cards.count() } ?: 0
if (activeCardsCount >= ALLOWED_MAX_CARDS_COUNT) {
uiMessageSender.send(TangemPayMessagesFactory.createMaximumCardsIssued(maxCards = ALLOWED_MAX_CARDS_COUNT))
return
}
modelScope.launch {
val offer = getCustomerOffers.additionalCardOffer(userWalletId).getOrNull()
if (offer == null) {
@ -569,8 +568,4 @@ internal class TangemPayDetailsModel @Inject constructor(
private fun showBottomSheetError(type: TangemPayDetailsErrorType) {
uiMessageSender.send(message = TangemPayMessagesFactory.createErrorMessage(errorType = type))
}
private companion object {
const val ALLOWED_MAX_CARDS_COUNT = 3
}
}

View file

@ -12,6 +12,7 @@ import java.util.Currency
internal class DetailsBalanceTransformer(
private val fiatBalance: PaymentAccountStatusValue.FiatBalance,
private val isInactive: Boolean = false,
private val isMuted: Boolean = false,
) : Transformer<TangemPayDetailsUM> {
@ -23,18 +24,21 @@ internal class DetailsBalanceTransformer(
cardsBlockState = prevState.balanceBlockState.cardsBlockState,
isMuted = isMuted,
isNegative = fiatBalance.availableBalance.signum() < 0,
isInactive = isInactive,
)
return prevState.copy(balanceBlockState = balance)
}
private fun getFiatBalanceText(fiatBalance: PaymentAccountStatusValue.FiatBalance): TextReference {
val currency = Currency.getInstance(fiatBalance.currency)
return fiatBalance.availableBalance.formatStyled {
fiat(
fiatCurrencyCode = currency.currencyCode,
fiatCurrencySymbol = currency.symbol,
spanStyleReference = { TangemTheme.typography3.heading.medium.toSpanStyle() },
)
companion object {
fun getFiatBalanceText(fiatBalance: PaymentAccountStatusValue.FiatBalance): TextReference {
val currency = Currency.getInstance(fiatBalance.currency)
return fiatBalance.availableBalance.formatStyled {
fiat(
fiatCurrencyCode = currency.currencyCode,
fiatCurrencySymbol = currency.symbol,
spanStyleReference = { TangemTheme.typography3.heading.medium.toSpanStyle() },
)
}
}
}
}

View file

@ -9,6 +9,9 @@ import com.tangem.domain.models.account.TangemPayCustomerTariffPlan
import com.tangem.domain.models.account.TangemPayTariffPlan
import org.joda.time.format.DateTimeFormatter
val TangemPayTariffPlan.feeCurrency: String?
get() = fees.firstOrNull()?.currency
fun TangemPayTariffPlan.formatRecurringFeeOrNull(): String? {
val fee = fees.find { it.type == TangemPayTariffPlan.Fee.Type.RECURRING } ?: return null
val currency = getJavaCurrencyByCode(fee.currency)

View file

@ -510,6 +510,7 @@ internal class TangemPayDetailsUMProvider : CollectionPreviewParameterProvider<T
isAddCardEnabled = true,
),
isNegative = false,
isInactive = false,
),
isBalanceHidden = false,
addToWalletBlockState = AddToWalletBlockState(

View file

@ -40,6 +40,7 @@ import com.tangem.core.ui.components.topFade
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.core.ui.ds.button.*
import com.tangem.core.ui.ds.image.TangemIconUM
import com.tangem.core.ui.ds2.badge.TangemBadge
import com.tangem.core.ui.ds.message.TangemMessage
import com.tangem.core.ui.ds.message.TangemMessageEffect
import com.tangem.core.ui.ds.topbar.TangemTopBar
@ -359,39 +360,7 @@ private fun BalanceBlock(
fadeOut(animationSpec = tween(durationMillis = 90))
},
) { animatedState ->
when (animatedState) {
is TangemPayDetailsBalanceBlockState.Loading -> TangemShimmer(
style = TangemTheme.typography3.heading.medium,
)
is TangemPayDetailsBalanceBlockState.Content -> {
val balanceColor = when {
animatedState.isMuted -> TangemTheme.colors3.text.secondary
animatedState.isNegative -> TangemTheme.colors3.text.status.error
else -> TangemTheme.colors3.text.primary
}
Text(
modifier = Modifier.testTag(TangemPayTestTags.PAYMENT_ACCOUNT_BALANCE),
text = animatedState.fiatBalance.orMaskWithStars(isBalanceHidden).resolveAnnotatedReference(),
style = TangemTheme.typography3.display.medium.applyBladeBrush(
isEnabled = animatedState.isBalanceFlickering,
textColor = balanceColor,
),
color = balanceColor,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
autoSize = TextAutoSize.StepBased(
minFontSize = TangemTheme.typography3.heading.medium.fontSize,
maxFontSize = TangemTheme.typography3.display.medium.fontSize,
),
)
}
is TangemPayDetailsBalanceBlockState.Error -> Text(
modifier = Modifier.testTag(TangemPayTestTags.PAYMENT_ACCOUNT_BALANCE),
text = DASH_SIGN.orMaskWithStars(isBalanceHidden),
style = TangemTheme.typography3.display.medium,
color = TangemTheme.colors3.text.primary,
)
}
BalanceValue(animatedState, isBalanceHidden)
}
Text(
@ -400,6 +369,58 @@ private fun BalanceBlock(
color = TangemTheme.colors3.text.secondary,
style = TangemTheme.typography3.caption.medium,
)
when (state) {
is TangemPayDetailsBalanceBlockState.Loading -> Unit
is TangemPayDetailsBalanceBlockState.Content -> {
TangemBadge(
modifier = Modifier.padding(vertical = TangemTheme.dimens2.x1),
text = resourceReference(R.string.tangempay_status_inactive),
variant = TangemBadge.Variant.Outline,
status = TangemBadge.Status.Warning,
size = TangemBadge.Size.X6,
iconStart = TangemIconUM.Icon(iconRes = CoreUiR.drawable.ic_information_24),
)
}
is TangemPayDetailsBalanceBlockState.Error -> Unit
}
}
}
@Composable
private fun BalanceValue(state: TangemPayDetailsBalanceBlockState, isBalanceHidden: Boolean) {
when (state) {
is TangemPayDetailsBalanceBlockState.Loading -> TangemShimmer(
style = TangemTheme.typography3.heading.medium,
)
is TangemPayDetailsBalanceBlockState.Content -> {
val balanceColor = when {
state.isMuted -> TangemTheme.colors3.text.secondary
state.isNegative -> TangemTheme.colors3.text.status.error
else -> TangemTheme.colors3.text.primary
}
Text(
modifier = Modifier.testTag(TangemPayTestTags.PAYMENT_ACCOUNT_BALANCE),
text = state.fiatBalance.orMaskWithStars(isBalanceHidden).resolveAnnotatedReference(),
style = TangemTheme.typography3.display.medium.applyBladeBrush(
isEnabled = state.isBalanceFlickering,
textColor = balanceColor,
),
color = balanceColor,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
autoSize = TextAutoSize.StepBased(
minFontSize = TangemTheme.typography3.heading.medium.fontSize,
maxFontSize = TangemTheme.typography3.display.medium.fontSize,
),
)
}
is TangemPayDetailsBalanceBlockState.Error -> Text(
modifier = Modifier.testTag(TangemPayTestTags.PAYMENT_ACCOUNT_BALANCE),
text = DASH_SIGN.orMaskWithStars(isBalanceHidden),
style = TangemTheme.typography3.display.medium,
color = TangemTheme.colors3.text.primary,
)
}
}

View file

@ -12,6 +12,8 @@ internal val AccountStatus.Payment.cryptoCurrency: CryptoCurrency.Token
get() = when (val v = value) {
is PaymentAccountStatusValue.Loaded -> v.cryptoCurrency
is PaymentAccountStatusValue.Deactivated -> v.cryptoCurrency
is PaymentAccountStatusValue.Inactive -> v.cryptoCurrency
is PaymentAccountStatusValue.AwaitingPlanSelection -> v.cryptoCurrency
else -> error("TangemPayDetails opened with unsupported status: $v")
}

View file

@ -5,12 +5,10 @@ import com.tangem.core.ui.components.bottomsheets.message.*
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.message.BottomSheetMessage
import com.tangem.core.ui.message.DialogMessage
import com.tangem.core.ui.message.bottomSheetMessage
import com.tangem.core.ui.res.generated.icons.Icons
import com.tangem.core.ui.res.generated.icons.ic_error_28
import com.tangem.core.ui.res.generated.icons.ic_snowflake_20
import com.tangem.core.ui.res.generated.icons.ic_sun_20
import com.tangem.features.tangempay.entity.TangemPayDetailsErrorType
@ -151,26 +149,6 @@ internal object TangemPayMessagesFactory {
}
}
fun createMaximumCardsIssued(maxCards: Int): BottomSheetMessage {
return bottomSheetMessage {
infoBlock {
vector(Icons.ic_error_28) {
type = MessageBottomSheetUM.Vector.Type.Attention
backgroundType = MessageBottomSheetUM.Vector.BackgroundType.Attention
}
title = resourceReference(R.string.tangempay_maximum_cards_issued_title)
body = resourceReference(
id = R.string.tangempay_maximum_cards_issued_description,
formatArgs = wrappedList(maxCards),
)
}
primaryButton {
text = resourceReference(R.string.common_got_it)
onClick { closeBs() }
}
}
}
// TODO v_rodionov: #[REDACTED_TASK_KEY] fix hardcoded strings
fun createStayOnPlanMessage(
planName: String,

View file

@ -153,6 +153,7 @@ internal class CashbackBlockTransformerTest {
fiatBalance = TextReference.EMPTY,
isBalanceFlickering = false,
isNegative = false,
isInactive = false,
),
addToWalletBlockState = null,
isBalanceHidden = false,

View file

@ -61,6 +61,7 @@ internal class TangemPayActionButtonsTransformerTest {
fiatBalance = TextReference.EMPTY,
isBalanceFlickering = false,
isNegative = false,
isInactive = false,
),
addToWalletBlockState = null,
isBalanceHidden = false,