Updated on 2026-08-14

This commit is contained in:
Tangem 2026-07-09 02:57:52 -07:00
parent c716d80696
commit ae9eb162f6
18 changed files with 269 additions and 94 deletions

View file

@ -1,5 +1,21 @@
package com.tangem.features.tangempay
import com.tangem.utils.SupportedLanguages
object TangemPayConstants {
const val TERMS_AND_LIMITS_LINK = "https://tangem.com/docs/en/tangem-visa-tariffs.pdf"
fun visaBenefitsLink(): String {
val lang = SupportedLanguages.getCurrentSupportedLanguageCode()
val segment = when (lang) {
"es" -> "es"
"pt" -> "pt"
"ja" -> "ja"
"zh" -> "zh-Hans"
"fr" -> "fr"
"de" -> "de"
else -> "en"
}
return "https://tangem.com/$segment/tangem-pay/visa-benefits/"
}
}

View file

@ -0,0 +1,98 @@
package com.tangem.features.tangempay.entity
import com.tangem.core.ui.components.notifications.NotificationConfig
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.domain.models.account.PaymentAccountStatusValue
import com.tangem.domain.models.account.TangemPayCustomerTariffPlan
import com.tangem.domain.models.account.TangemPayTariffPlanState
import com.tangem.features.tangempay.details.impl.R
import com.tangem.features.tangempay.tiers.formatNextBillingDateOrNull
import com.tangem.features.tangempay.tiers.formatRecurringFeeOrNull
import com.tangem.features.tangempay.utils.TangemPayDetailIntents
internal class TangemPayDetailsNotificationFactory(
private val intents: TangemPayDetailIntents,
private val isRemoveAccountEnabled: Boolean,
private val isTiersPlusPlanEnabled: Boolean,
private val isRedesignEnabled: Boolean,
) {
fun createErrorConfig(error: PaymentAccountStatusValue.Error?): NotificationConfig? = when (error) {
null -> null
PaymentAccountStatusValue.Error.NotSynced -> createRenewSessionNotificationConfig(isRedesignEnabled)
else -> createAccountUnavailableConfig(isRedesignEnabled)
}
fun createAccountDeactivatedConfig(isRedesignEnabled: Boolean) = NotificationConfig(
title = resourceReference(R.string.tangempay_account_deactivated_message_title),
subtitle = resourceReference(R.string.tangempay_account_deactivated_message_subtitle),
iconResId = if (isRedesignEnabled) R.drawable.ic_alert_circle_24 else R.drawable.img_attention_20,
buttonsState = if (isRemoveAccountEnabled) {
NotificationConfig.ButtonsState.SecondaryButtonConfig(
text = resourceReference(R.string.tangempay_remove_account),
onClick = intents::onRemoveAccount,
)
} else {
null
},
)
// TODO v_rodionov: strings hardcoded for now - wait for localization
fun createAwaitingDepositConfig(tariffPlan: TangemPayTariffPlanState?): NotificationConfig? {
if (!isTiersPlusPlanEnabled) return null
if (tariffPlan == null) return null
if (tariffPlan.tariff.status == TangemPayCustomerTariffPlan.Status.SYSTEM_DOWNGRADE_PENDING) {
return createTariffSystemDownGradePendingConfig(tariffPlan)
}
val order = tariffPlan.order ?: return null
val orderStep = order.step
if (orderStep !is TangemPayTariffPlanState.OrderStep.AwaitingDeposit) return null
val feeText = orderStep.toPlan.formatRecurringFeeOrNull() ?: return null
val title = "Top-up your account on $feeText"
return NotificationConfig(
title = stringReference(title),
subtitle = stringReference("To pay monthly fee for plan and start use card"),
iconResId = R.drawable.ic_alert_circle_24,
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
text = stringReference("Cancel ${orderStep.toPlan.name}, move to ${orderStep.fromPlan.name}"),
onClick = { intents.onCancelPlusTransition(order.orderId) },
),
)
}
private fun createRenewSessionNotificationConfig(isRedesignEnabled: Boolean) = NotificationConfig(
title = resourceReference(R.string.tangempay_sync_needed_title),
subtitle = resourceReference(R.string.tangempay_sync_needed_body),
iconResId = if (isRedesignEnabled) 0 else R.drawable.img_attention_20,
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
text = resourceReference(R.string.tangempay_sync_needed_button),
onClick = intents::onRenewSession,
iconResId = R.drawable.ic_tangem_24,
),
)
private fun createAccountUnavailableConfig(isRedesignEnabled: Boolean) = NotificationConfig(
title = resourceReference(R.string.tangempay_temporarily_unavailable),
subtitle = resourceReference(R.string.tangempay_service_unreachable_try_later),
iconResId = if (isRedesignEnabled) R.drawable.ic_alert_circle_24 else R.drawable.img_attention_20,
)
// TODO v_rodionov: strings hardcoded for now - wait for localization
private fun createTariffSystemDownGradePendingConfig(tariffPlan: TangemPayTariffPlanState): NotificationConfig? {
val date = tariffPlan.tariff.formatNextBillingDateOrNull() ?: return null
val planName = tariffPlan.tariff.plan.name
return NotificationConfig(
title = stringReference("Top up your account shortly"),
subtitle = stringReference("If it will remain below zero your $planName cards will be closed on $date"),
iconResId = R.drawable.ic_alert_circle_24,
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
text = stringReference("Add funds"),
onClick = intents::onClickAddFunds,
),
)
}
}

View file

@ -3,19 +3,15 @@ package com.tangem.features.tangempay.entity
import com.tangem.core.ui.components.buttons.actions.ActionButtonConfig
import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig
import com.tangem.core.ui.components.dropdownmenu.TangemDropdownMenuItem
import com.tangem.core.ui.components.notifications.NotificationConfig
import com.tangem.core.ui.ds.image.TangemIconUM
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.themedColor
import com.tangem.core.ui.format.bigdecimal.fiat
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.core.ui.format.bigdecimal.getJavaCurrencyByCode
import com.tangem.core.ui.format.bigdecimal.optionalDecimals
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.generated.icons.Icons
import com.tangem.core.ui.res.generated.icons.ic_document_20
import com.tangem.domain.models.account.PaymentAccountStatusValue
import com.tangem.domain.models.account.TangemPayCustomerTariffPlan
import com.tangem.domain.models.account.TangemPayTariffPlan
import com.tangem.domain.models.account.TangemPayTariffPlanState
import com.tangem.domain.models.pay.TangemPayCard
@ -41,6 +37,13 @@ internal class TangemPayDetailsStateFactory(
private val isMultipleCardsEnabled: Boolean,
private val isTiersPlusPlanEnabled: Boolean,
) {
private val notificationFactory = TangemPayDetailsNotificationFactory(
intents = intents,
isRemoveAccountEnabled = isRemoveAccountEnabled,
isTiersPlusPlanEnabled = isTiersPlusPlanEnabled,
isRedesignEnabled = isRedesignEnabled,
)
fun getLoadingState(): TangemPayDetailsUM {
return TangemPayDetailsUM(
topBarConfig = TangemPayDetailsTopBarConfig(
@ -75,6 +78,8 @@ internal class TangemPayDetailsStateFactory(
val isAddCardEnabled = isFresh && !hasIssuingCard
val areActionButtonsEnabled = isFresh && hasUnfrozenCard
val hasWithdrawableBalance = status.balance.hasWithdrawableAmount
val errorNotification = notificationFactory.createErrorConfig(status.error)
val awaitingDepositNotification = notificationFactory.createAwaitingDepositConfig(status.tariffPlan)
return TangemPayDetailsUM(
topBarConfig = TangemPayDetailsTopBarConfig(
onBackClick = onBack,
@ -111,17 +116,11 @@ internal class TangemPayDetailsStateFactory(
),
isBalanceHidden = false,
addToWalletBlockState = null,
errorNotificationConfig = createErrorConfig(status.error) ?: createAwaitingDepositConfig(status.tariffPlan),
errorNotificationConfig = errorNotification ?: awaitingDepositNotification,
accountDeactivatedNotificationConfig = null,
)
}
private fun createErrorConfig(error: PaymentAccountStatusValue.Error?): NotificationConfig? = when (error) {
null -> null
PaymentAccountStatusValue.Error.NotSynced -> createRenewSessionNotificationConfig(isRedesignEnabled)
else -> createAccountUnavailableConfig(isRedesignEnabled)
}
private fun List<TangemPayCard>.resolveProgressBanner(): CardsProgressBannerUM? = when {
any { it.state == TangemPayCardState.Reissuing } -> CardsProgressBannerUM.Reissuing
any { it.state == TangemPayCardState.Issuing } -> CardsProgressBannerUM.Issuing
@ -129,6 +128,7 @@ internal class TangemPayDetailsStateFactory(
}
fun getDeactivatedState(hasWithdrawableBalance: Boolean): TangemPayDetailsUM {
val accountDeactivatedNotification = notificationFactory.createAccountDeactivatedConfig(isRedesignEnabled)
return TangemPayDetailsUM(
topBarConfig = TangemPayDetailsTopBarConfig(
onBackClick = onBack,
@ -150,65 +150,10 @@ internal class TangemPayDetailsStateFactory(
isBalanceHidden = false,
addToWalletBlockState = null,
errorNotificationConfig = null,
accountDeactivatedNotificationConfig = createAccountDeactivatedConfig(isRedesignEnabled),
accountDeactivatedNotificationConfig = accountDeactivatedNotification,
)
}
private fun createAccountUnavailableConfig(isRedesignEnabled: Boolean) = NotificationConfig(
title = resourceReference(R.string.tangempay_temporarily_unavailable),
subtitle = resourceReference(R.string.tangempay_service_unreachable_try_later),
iconResId = if (isRedesignEnabled) R.drawable.ic_alert_circle_24 else R.drawable.img_attention_20,
)
private fun createAccountDeactivatedConfig(isRedesignEnabled: Boolean) = NotificationConfig(
title = resourceReference(R.string.tangempay_account_deactivated_message_title),
subtitle = resourceReference(R.string.tangempay_account_deactivated_message_subtitle),
iconResId = if (isRedesignEnabled) R.drawable.ic_alert_circle_24 else R.drawable.img_attention_20,
buttonsState = if (isRemoveAccountEnabled) {
NotificationConfig.ButtonsState.SecondaryButtonConfig(
text = resourceReference(R.string.tangempay_remove_account),
onClick = intents::onRemoveAccount,
)
} else {
null
},
)
// TODO v_rodionov: strings hardcoded for now - wait for localization
private fun createAwaitingDepositConfig(tariffPlan: TangemPayTariffPlanState?): NotificationConfig? {
val order = tariffPlan?.order ?: return null
val orderStep = order.step
if (orderStep !is TangemPayTariffPlanState.OrderStep.AwaitingDeposit) return null
val recurringFee = orderStep.toPlan.fees.find { it.type == TangemPayTariffPlan.Fee.Type.RECURRING }
val feeText = recurringFee?.let { fee ->
val currency = getJavaCurrencyByCode(fee.currency)
fee.amount.format { fiat(currency.currencyCode, currency.symbol).optionalDecimals() }
}
val title = if (feeText != null) "Top-up your account on $feeText" else "Top-up your account"
return NotificationConfig(
title = stringReference(title),
subtitle = stringReference("To pay monthly fee for plan and start use card"),
iconResId = R.drawable.ic_alert_circle_24,
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
text = stringReference("Cancel ${orderStep.toPlan.name}, move to ${orderStep.fromPlan.name}"),
onClick = { intents.onCancelPlusTransition(order.orderId) },
),
)
}
private fun createRenewSessionNotificationConfig(isRedesignEnabled: Boolean) = NotificationConfig(
title = resourceReference(R.string.tangempay_sync_needed_title),
subtitle = resourceReference(R.string.tangempay_sync_needed_body),
iconResId = if (isRedesignEnabled) 0 else R.drawable.img_attention_20,
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
text = resourceReference(R.string.tangempay_sync_needed_button),
onClick = intents::onRenewSession,
iconResId = R.drawable.ic_tangem_24,
),
)
private fun getTopBarMenuItems(): ImmutableList<TangemDropdownMenuItem> {
return persistentListOf(
TangemDropdownMenuItem(
@ -285,6 +230,23 @@ internal class TangemPayDetailsStateFactory(
tintReference = { TangemTheme.colors3.icon.primary },
),
subtitle = stringReference(tariffPlan.tariff.plan.name),
isEnabled = tariffPlan.order?.step !is TangemPayTariffPlanState.OrderStep.AwaitingDeposit &&
tariffPlan.tariff.status != TangemPayCustomerTariffPlan.Status.TRANSITIONING,
),
)
}
if (isTiersPlusPlanEnabled &&
tariffPlan != null &&
tariffPlan.tariff.plan.type != TangemPayTariffPlan.Type.BASIC
) {
add(
TangemPayDropDownItemUM(
title = resourceReference(R.string.tangempay_visa_benefits),
onClick = intents::onClickVisaBenefits,
icon = TangemIconUM.Icon(
iconRes = CoreUiR.drawable.ic_heart_20,
tintReference = { TangemTheme.colors3.icon.primary },
),
),
)
}

View file

@ -93,6 +93,7 @@ internal sealed class TangemPayDetailsBalanceBlockState {
override val cardsBlockState: CardsBlockState?,
val fiatBalance: TextReference,
val isBalanceFlickering: Boolean,
val isNegative: Boolean,
val isMuted: Boolean = false,
) : TangemPayDetailsBalanceBlockState()

View file

@ -173,11 +173,13 @@ internal class TangemPayDetailsModel @Inject constructor(
}
override fun onCancelPlusTransition(orderId: String) {
uiState.update(TangemPayErrorNotificationTransformer(shouldShowProgress = true))
modelScope.launch {
cancelTangemPayOrderUseCase(userWalletId = userWalletId, orderId = orderId)
.onLeft {
uiMessageSender.send(TangemPayMessagesFactory.createGenericError())
}
uiState.update(TangemPayErrorNotificationTransformer(shouldShowProgress = false))
}
}
@ -444,6 +446,10 @@ internal class TangemPayDetailsModel @Inject constructor(
urlOpener.openUrl(TangemPayConstants.TERMS_AND_LIMITS_LINK)
}
override fun onClickVisaBenefits() {
urlOpener.openUrl(TangemPayConstants.visaBenefitsLink())
}
override fun onClickCurrentPlan(tariffPlan: TangemPayCustomerTariffPlan) {
router.push(TangemPayAccountDetailsInnerRoute.CurrentPlan(tariffPlan))
}
@ -504,16 +510,16 @@ internal class TangemPayDetailsModel @Inject constructor(
}
override fun onRenewSession() {
uiState.update(TangemPayRenewSessionTransformer(shouldShowProgress = true))
uiState.update(TangemPayErrorNotificationTransformer(shouldShowProgress = true))
modelScope.launch {
produceTangemPayInitialDataUseCase(userWalletId)
.onRight {
paymentAccountStatusFetcher.invoke(userWalletId)
uiState.update(TangemPayRenewSessionTransformer(shouldShowProgress = false))
uiState.update(TangemPayErrorNotificationTransformer(shouldShowProgress = false))
}
.onLeft {
uiMessageSender.send(SnackbarMessage(resourceReference(R.string.common_error)))
uiState.update(TangemPayRenewSessionTransformer(shouldShowProgress = false))
uiState.update(TangemPayErrorNotificationTransformer(shouldShowProgress = false))
}
}
}

View file

@ -22,6 +22,7 @@ internal class DetailsBalanceTransformer(
actionButtons = prevState.balanceBlockState.actionButtons,
cardsBlockState = prevState.balanceBlockState.cardsBlockState,
isMuted = isMuted,
isNegative = fiatBalance.availableBalance.signum() < 0,
)
return prevState.copy(balanceBlockState = balance)
}

View file

@ -4,7 +4,7 @@ import com.tangem.core.ui.components.notifications.NotificationConfig
import com.tangem.features.tangempay.entity.TangemPayDetailsUM
import com.tangem.utils.transformer.Transformer
internal class TangemPayRenewSessionTransformer(
internal class TangemPayErrorNotificationTransformer(
private val shouldShowProgress: Boolean,
) : Transformer<TangemPayDetailsUM> {

View file

@ -0,0 +1,22 @@
package com.tangem.features.tangempay.tiers
import com.tangem.core.ui.format.bigdecimal.fiat
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.core.ui.format.bigdecimal.getJavaCurrencyByCode
import com.tangem.core.ui.format.bigdecimal.optionalDecimals
import com.tangem.core.ui.utils.DateTimeFormatters
import com.tangem.domain.models.account.TangemPayCustomerTariffPlan
import com.tangem.domain.models.account.TangemPayTariffPlan
import org.joda.time.format.DateTimeFormatter
fun TangemPayTariffPlan.formatRecurringFeeOrNull(): String? {
val fee = fees.find { it.type == TangemPayTariffPlan.Fee.Type.RECURRING } ?: return null
val currency = getJavaCurrencyByCode(fee.currency)
return fee.amount.format { fiat(currency.currencyCode, currency.symbol).optionalDecimals() }
}
fun TangemPayCustomerTariffPlan.formatNextBillingDateOrNull(
formatter: DateTimeFormatter = DateTimeFormatters.dateMMMMdYYYY,
): String? {
return nextBillingAt?.let { DateTimeFormatters.formatDate(it, formatter) }
}

View file

@ -7,9 +7,13 @@ import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.decompose.navigation.Router
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.utils.DateTimeFormatters
import com.tangem.domain.models.account.TangemPayCustomerTariffPlan
import com.tangem.domain.models.account.TangemPayTariffPlan
import com.tangem.features.tangempay.details.impl.R
import com.tangem.features.tangempay.navigation.TangemPayAccountDetailsInnerRoute
import com.tangem.features.tangempay.tiers.formatNextBillingDateOrNull
import com.tangem.features.tangempay.tiers.formatRecurringFeeOrNull
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
@ -28,16 +32,41 @@ internal class TangemPayCurrentPlanModel @Inject constructor(
private val params = paramsContainer.require<TangemPayCurrentPlanComponent.Params>()
val state: StateFlow<TangemPayCurrentPlanUM>
field = MutableStateFlow(createState(params.tariffPlan.plan))
field = MutableStateFlow(createState(params.tariffPlan))
private fun createState(plan: TangemPayTariffPlan): TangemPayCurrentPlanUM = TangemPayCurrentPlanUM(
planName = stringReference(plan.name),
notification = null,
sections = buildSections(plan),
private fun createState(customerPlan: TangemPayCustomerTariffPlan): TangemPayCurrentPlanUM = TangemPayCurrentPlanUM(
planName = stringReference(customerPlan.plan.name),
notification = createNotification(customerPlan),
sections = buildSections(customerPlan.plan),
onBackClick = router::pop,
onChangePlanClick = { router.push(TangemPayAccountDetailsInnerRoute.SelectPlan) },
)
// TODO v_rodionov: strings hardcoded for now - wait for localization
private fun createNotification(customerPlan: TangemPayCustomerTariffPlan): TangemPayCurrentPlanUM.Notification? {
val date = customerPlan.formatNextBillingDateOrNull(formatter = DateTimeFormatters.dateMMMd) ?: return null
val feeText = customerPlan.plan.formatRecurringFeeOrNull() ?: return null
return when (customerPlan.status) {
TangemPayCustomerTariffPlan.Status.DOWNGRADE_PENDING -> {
val targetPlan = customerPlan.pendingPlan ?: return null
TangemPayCurrentPlanUM.Notification(
text = stringReference(
"Your ${customerPlan.plan.name} plan is active till $date, then we will move you to " +
"${targetPlan.name}. $feeText won't be charged.",
),
button = TangemPayCurrentPlanUM.Notification.Button(
text = stringReference("Stay on ${customerPlan.plan.name}"),
onClick = {}, // TODO v_rodionov: #[REDACTED_TASK_KEY] - Downgrade
),
)
}
TangemPayCustomerTariffPlan.Status.ACTIVE -> TangemPayCurrentPlanUM.Notification(
text = stringReference("$feeText monthly fee will be charged on $date"),
)
else -> null
}
}
private fun buildSections(plan: TangemPayTariffPlan) = persistentListOf(
sectionOf(plan, TangemPayTariffPlan.Section.CARD_RELATED, R.string.tangempay_current_plan_section_card),
sectionOf(plan, TangemPayTariffPlan.Section.PLAN_RELATED, R.string.tangempay_current_plan_section_plan),

View file

@ -14,6 +14,7 @@ import com.tangem.domain.pay.usecase.CreateTariffPlanTransitionOrderUseCase
import com.tangem.domain.pay.usecase.GetTangemPayTariffPlanTransitionsUseCase
import com.tangem.features.tangempay.details.impl.R
import com.tangem.features.tangempay.navigation.TangemPayAccountDetailsInnerRoute
import com.tangem.features.tangempay.tiers.formatRecurringFeeOrNull
import com.tangem.features.tangempay.utils.TangemPayMessagesFactory
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.collections.immutable.ImmutableList
@ -38,6 +39,10 @@ internal class TangemPaySelectPlanModel @Inject constructor(
private val params = paramsContainer.require<TangemPaySelectPlanComponent.Params>()
private var transitions: List<TangemPayTariffPlanTransition> = emptyList()
private val allowedTransitions: List<TangemPayTariffPlanTransition>
get() = transitions.filter { it.type in ALLOWED_TYPES }
private var selectedIndex: Int = 0
private var isConfirm: Boolean = false
private var isProcessing: Boolean = false
@ -52,7 +57,7 @@ internal class TangemPaySelectPlanModel @Inject constructor(
private fun loadTransitions() {
modelScope.launch {
getTransitions(params.userWalletId).onRight { result ->
transitions = result.filter { it.type in ALLOWED_TYPES }
transitions = result
state.update { buildState() }
}
}
@ -65,13 +70,13 @@ internal class TangemPaySelectPlanModel @Inject constructor(
}
private fun onSelectClick() {
if (transitions.isEmpty()) return
if (allowedTransitions.isEmpty()) return
isConfirm = true
state.update { buildState() }
}
private fun onComparePlansClick() {
if (transitions.isEmpty()) return
if (allowedTransitions.isEmpty()) return
state.update { buildState(showPlanCompare = true) }
}
@ -90,8 +95,11 @@ internal class TangemPaySelectPlanModel @Inject constructor(
}
private fun onConfirmClick() {
val transition = transitions.getOrNull(selectedIndex) ?: return
val transition = allowedTransitions.getOrNull(selectedIndex) ?: return
// TODO v_rodionov: #[REDACTED_TASK_KEY] - Downgrade
if (transition.type != TangemPayTariffPlanTransition.Type.UPGRADE) return
if (isProcessing) return
isProcessing = true
@ -118,7 +126,7 @@ internal class TangemPaySelectPlanModel @Inject constructor(
} else {
resourceReference(R.string.tangempay_select_plan_title)
},
plans = transitions.map { it.plan.toPlanUM() }.toImmutableList(),
plans = allowedTransitions.map { it.plan.toPlanUM() }.toImmutableList(),
selectedIndex = selectedIndex,
onPlanSelected = ::onPlanSelected,
onBackClick = ::onBackClick,
@ -135,14 +143,16 @@ internal class TangemPaySelectPlanModel @Inject constructor(
private fun buildCompare(): TangemPaySelectPlanUM.ComparePlans {
val plans = transitions.map { it.plan }
val orderedTitles = plans
.flatMap { it.descriptionItems }
.flatMap { plan -> plan.descriptionItems.filter { it.section in COMPARE_SECTIONS } }
.sortedWith(compareBy({ it.section.ordinal }, { it.order }))
.map { it.title }
.distinct()
return TangemPaySelectPlanUM.ComparePlans(
attributes = orderedTitles.map(::stringReference).toImmutableList(),
plans = plans.map { plan ->
val valueByTitle = plan.descriptionItems.associate { it.title to it.body }
val valueByTitle = plan.descriptionItems
.filter { it.section in COMPARE_SECTIONS }
.associate { it.title to it.body }
TangemPaySelectPlanUM.ComparePlans.Plan(
name = stringReference(plan.name),
values = orderedTitles
@ -154,8 +164,8 @@ internal class TangemPaySelectPlanModel @Inject constructor(
)
}
private fun buildConfirmContent(): TangemPaySelectPlanUM.Content.Confirm {
val transition = transitions[selectedIndex]
private fun buildConfirmContent(): TangemPaySelectPlanUM.Content {
val transition = allowedTransitions.getOrNull(selectedIndex) ?: return buildSelectContent()
val planName = transition.plan.name
val isUpgrade = transition.type == TangemPayTariffPlanTransition.Type.UPGRADE
return TangemPaySelectPlanUM.Content.Confirm(
@ -181,9 +191,7 @@ internal class TangemPaySelectPlanModel @Inject constructor(
planName: String,
isUpgrade: Boolean,
): ImmutableList<TangemPaySelectPlanUM.PointUM> = if (isUpgrade) {
val feeText = transition.plan.descriptionItems
.firstOrNull { it.section == TangemPayTariffPlan.Section.PLAN_RELATED }
?.title
val feeText = transition.plan.formatRecurringFeeOrNull()
listOf(
stringReference("You will get your virtual Visa $planName in minutes"),
if (feeText != null) {
@ -205,7 +213,8 @@ internal class TangemPaySelectPlanModel @Inject constructor(
name = stringReference(name),
imageUrl = images.firstOrNull { it.type == TangemPayTariffPlan.Image.Type.MAIN }?.url,
points = descriptionItems
.sortedWith(compareBy({ it.section.ordinal }, { it.order }))
.filter { it.section == TangemPayTariffPlan.Section.ONBOARDING_RELATED }
.sortedBy { it.order }
.map { item ->
TangemPaySelectPlanUM.PointUM(
title = stringReference(item.title),
@ -220,5 +229,9 @@ internal class TangemPaySelectPlanModel @Inject constructor(
TangemPayTariffPlanTransition.Type.UPGRADE,
TangemPayTariffPlanTransition.Type.DOWNGRADE,
)
private val COMPARE_SECTIONS = setOf(
TangemPayTariffPlan.Section.CARD_RELATED,
TangemPayTariffPlan.Section.PLAN_RELATED,
)
}
}

View file

@ -507,6 +507,7 @@ internal class TangemPayDetailsUMProvider : CollectionPreviewParameterProvider<T
onAddCardClick = {},
isAddCardEnabled = true,
),
isNegative = false,
),
isBalanceHidden = false,
addToWalletBlockState = AddToWalletBlockState(

View file

@ -255,6 +255,7 @@ private fun ErrorMessage(config: NotificationConfig, modifier: Modifier = Modifi
buttonUM = TangemButtonUM(
text = button.text,
onClick = button.onClick,
isLoading = button.shouldShowProgress,
iconPosition = TangemButtonIconPosition.End,
tangemIconUM = if (iconResId != null) {
TangemIconUM.Icon(
@ -352,10 +353,10 @@ private fun BalanceBlock(
style = TangemTheme.typography3.heading.medium,
)
is TangemPayDetailsBalanceBlockState.Content -> {
val balanceColor = if (animatedState.isMuted) {
TangemTheme.colors3.text.secondary
} else {
TangemTheme.colors3.text.primary
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),

View file

@ -10,6 +10,7 @@ internal interface TangemPayDetailIntents {
fun onClickAddFunds()
fun onClickWithdraw()
fun onClickTermsAndLimits()
fun onClickVisaBenefits()
fun onClickCurrentPlan(tariffPlan: TangemPayCustomerTariffPlan)
fun onCancelPlusTransition(orderId: String)
fun onCardClick(cardId: String)

View file

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