Updated on 2026-08-14

This commit is contained in:
Tangem 2026-05-19 01:39:48 -07:00
parent 0cceaf3110
commit d99a6bf31e
11 changed files with 137 additions and 40 deletions

View file

@ -65,9 +65,9 @@ internal class DefaultTangemPayDetailsContainerComponent @AssistedInject constru
tokenReceiveComponentFactory = tokenReceiveComponentFactory,
expressTransactionsComponentProvider = expressTransactionsComponentProvider,
)
TangemPayAccountDetailsInnerRoute.CardDetails -> tangemPayCardPageFactory.create(
is TangemPayAccountDetailsInnerRoute.CardDetails -> tangemPayCardPageFactory.create(
context = childByContext(componentContext = componentContext, router = innerRouter),
params = TangemPayCardPageComponent.Params(userWalletId = params.userWalletId, config = params.config),
params = TangemPayCardPageComponent.Params(userWalletId = params.userWalletId, config = config.config),
)
TangemPayAccountDetailsInnerRoute.AddToWallet -> TangemPayAddToWalletComponent(
appComponentContext = childByContext(componentContext = componentContext, router = innerRouter),

View file

@ -21,7 +21,11 @@ internal class TangemPayDetailsStateFactory(
private val cardFrozenState: TangemPayCardFrozenState,
) {
@Suppress("LongMethod")
fun getInitialState(isTangemPayDeactivated: Boolean, cardNumberEnd: String): TangemPayDetailsUM {
fun getInitialState(
isTangemPayDeactivated: Boolean,
cardNumberEnd: String,
isReissuing: Boolean,
): TangemPayDetailsUM {
return TangemPayDetailsUM(
topBarConfig = TangemPayDetailsTopBarConfig(
onBackClick = onBack,
@ -51,7 +55,8 @@ internal class TangemPayDetailsStateFactory(
cards = persistentListOf(
TangemPayDetailsBalanceBlockState.Card(
lastDigits = cardNumberEnd,
onClick = intents::onCardClick,
onClick = {},
isReissuing = isReissuing,
),
),
onAddCardClick = intents::onAddCardClick,

View file

@ -84,7 +84,11 @@ internal sealed class TangemPayDetailsBalanceBlockState {
) : TangemPayDetailsBalanceBlockState()
data class CardsBlockState(val cards: ImmutableList<Card>, val onAddCardClick: () -> Unit)
data class Card(val lastDigits: String, val onClick: () -> Unit)
data class Card(
val lastDigits: String,
val onClick: () -> Unit,
val isReissuing: Boolean,
)
}
internal data class AddToWalletBlockState(

View file

@ -19,10 +19,14 @@ import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
import com.tangem.domain.feedback.models.FeedbackEmailType
import com.tangem.domain.feedback.models.WalletMetaInfo
import com.tangem.domain.models.StatusSource
import com.tangem.domain.models.TokenReceiveConfig
import com.tangem.domain.models.account.PaymentAccountStatusValue
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.pay.TangemPayCryptoCurrencyFactory
import com.tangem.domain.pay.TangemPayDetailsConfig
import com.tangem.domain.pay.flow.PaymentAccountStatusSupplier
import com.tangem.domain.pay.model.TangemPayCardBalance
import com.tangem.domain.pay.model.TangemPayTopUpData
import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository
@ -53,10 +57,7 @@ import com.tangem.utils.coroutines.saveIn
import com.tangem.utils.logging.TangemLogger
import com.tangem.utils.transformer.update
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import javax.inject.Inject
@ -65,6 +66,7 @@ import javax.inject.Inject
@ModelScoped
internal class TangemPayDetailsModel @Inject constructor(
paramsContainer: ParamsContainer,
paymentAccountStatusSupplier: PaymentAccountStatusSupplier,
override val dispatchers: CoroutineDispatcherProvider,
private val analytics: AnalyticsEventHandler,
private val router: Router,
@ -95,6 +97,7 @@ internal class TangemPayDetailsModel @Inject constructor(
stateFactory.getInitialState(
isTangemPayDeactivated = params.config.isTangemPayDeactivated,
cardNumberEnd = params.config.cardNumberEnd,
isReissuing = params.config.isReissuing,
),
)
@ -118,6 +121,21 @@ internal class TangemPayDetailsModel @Inject constructor(
if (!params.config.isTangemPayDeactivated) {
subscribeToCardFrozenState()
fetchAddToWalletBanner()
paymentAccountStatusSupplier.invoke(params.userWalletId)
.map { it.value }
.filterIsInstance<PaymentAccountStatusValue.Loaded>()
.filter { it.source == StatusSource.ACTUAL }
.onEach { state ->
val card = state.cards.firstOrNull() ?: return@onEach
uiState.update(
TangemPayCardDataTransformer(
card = card,
onCardClick = { onCardClick(params.config.copy(cardId = card.id)) },
),
)
}
.launchIn(modelScope)
}
}
@ -363,9 +381,9 @@ internal class TangemPayDetailsModel @Inject constructor(
urlOpener.openUrl(TangemPayConstants.TERMS_AND_LIMITS_LINK)
}
override fun onCardClick() {
override fun onCardClick(config: TangemPayDetailsConfig) {
analytics.send(TangemPayAnalyticsEvents.CardIconClicked())
router.push(TangemPayAccountDetailsInnerRoute.CardDetails)
router.push(TangemPayAccountDetailsInnerRoute.CardDetails(config))
}
override fun onAddCardClick() {

View file

@ -0,0 +1,30 @@
package com.tangem.features.tangempay.model.transformers
import com.tangem.domain.models.pay.TangemPayCard
import com.tangem.features.tangempay.entity.TangemPayDetailsBalanceBlockState
import com.tangem.features.tangempay.entity.TangemPayDetailsUM
import com.tangem.utils.transformer.Transformer
import kotlinx.collections.immutable.persistentListOf
internal class TangemPayCardDataTransformer(
private val card: TangemPayCard,
private val onCardClick: () -> Unit,
) : Transformer<TangemPayDetailsUM> {
override fun transform(prevState: TangemPayDetailsUM): TangemPayDetailsUM {
val updatedCard = TangemPayDetailsBalanceBlockState.Card(
lastDigits = card.lastDigits,
onClick = onCardClick,
isReissuing = card.isReissuing,
)
val cardsBlockState = prevState.balanceBlockState.cardsBlockState.copy(
cards = persistentListOf(updatedCard),
)
val newBalanceBlockState = when (val bs = prevState.balanceBlockState) {
is TangemPayDetailsBalanceBlockState.Loading -> bs.copy(cardsBlockState = cardsBlockState)
is TangemPayDetailsBalanceBlockState.Content -> bs.copy(cardsBlockState = cardsBlockState)
is TangemPayDetailsBalanceBlockState.Error -> bs.copy(cardsBlockState = cardsBlockState)
}
return prevState.copy(balanceBlockState = newBalanceBlockState)
}
}

View file

@ -1,6 +1,7 @@
package com.tangem.features.tangempay.navigation
import com.tangem.core.decompose.navigation.Route
import com.tangem.domain.pay.TangemPayDetailsConfig
import kotlinx.serialization.Serializable
@Serializable
@ -9,7 +10,7 @@ internal sealed class TangemPayAccountDetailsInnerRoute : Route {
data object AccountDetails : TangemPayAccountDetailsInnerRoute()
@Serializable
data object CardDetails : TangemPayAccountDetailsInnerRoute()
data class CardDetails(val config: TangemPayDetailsConfig) : TangemPayAccountDetailsInnerRoute()
@Serializable
data object AddToWallet : TangemPayAccountDetailsInnerRoute()

View file

@ -31,6 +31,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.util.fastAny
import androidx.compose.ui.util.fastForEach
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.ui.components.RectangleShimmer
@ -114,30 +115,45 @@ internal fun TangemPayDetailsScreen(
SpacerH12()
},
)
if (state.addToWalletBlockState != null) {
if (state.balanceBlockState.cardsBlockState.cards.fastAny { it.isReissuing }) {
item(
key = AddToWalletBlockState::class.java,
key = "REISSUE_MESSAGE",
content = {
TangemPayAddToWalletBlock(
state = state.addToWalletBlockState,
modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing16),
)
},
)
}
if (state.accountDeactivatedNotificationConfig != null) {
item(
key = "DEACTIVATION_MESSAGE",
content = {
Notification(
modifier = modifier
TangemPayReplacingCardBlock(
modifier = Modifier
.padding(horizontal = TangemTheme.dimens.spacing16)
.fillMaxWidth(),
config = state.accountDeactivatedNotificationConfig,
)
SpacerH12()
},
)
} else {
if (state.addToWalletBlockState != null) {
item(
key = AddToWalletBlockState::class.java,
content = {
TangemPayAddToWalletBlock(
state = state.addToWalletBlockState,
modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing16),
)
},
)
}
if (state.accountDeactivatedNotificationConfig != null) {
item(
key = "DEACTIVATION_MESSAGE",
content = {
Notification(
modifier = modifier
.padding(horizontal = TangemTheme.dimens.spacing16)
.fillMaxWidth(),
config = state.accountDeactivatedNotificationConfig,
)
SpacerH12()
},
)
}
}
if (state.accountDeactivatedNotificationConfig == null) {
with(expressTransactionsComponent) {
@ -289,14 +305,26 @@ private fun TangemPayCardItem(card: TangemPayDetailsBalanceBlockState.Card, modi
painter = painterResource(R.drawable.img_visa_card_48x32),
contentDescription = null,
)
Text(
modifier = Modifier
.align(Alignment.BottomStart)
.padding(4.dp, bottom = 2.dp),
text = card.lastDigits,
style = TangemTheme.typography.overline.copy(letterSpacing = 0.sp),
color = TangemTheme.colors.text.constantWhite,
)
if (card.isReissuing) {
Icon(
modifier = Modifier
.size(16.dp)
.align(Alignment.BottomStart)
.padding(2.dp),
painter = painterResource(R.drawable.ic_update_32),
contentDescription = null,
tint = TangemTheme.colors.text.constantWhite,
)
} else {
Text(
modifier = Modifier
.align(Alignment.BottomStart)
.padding(4.dp, bottom = 2.dp),
text = card.lastDigits,
style = TangemTheme.typography.overline.copy(letterSpacing = 0.sp),
color = TangemTheme.colors.text.constantWhite,
)
}
}
}
@ -421,8 +449,8 @@ private class TangemPayDetailsUMProvider : CollectionPreviewParameterProvider<Ta
isBalanceFlickering = false,
cardsBlockState = TangemPayDetailsBalanceBlockState.CardsBlockState(
cards = persistentListOf(
TangemPayDetailsBalanceBlockState.Card(lastDigits = "1234", onClick = {}),
TangemPayDetailsBalanceBlockState.Card(lastDigits = "3456", onClick = {}),
TangemPayDetailsBalanceBlockState.Card(lastDigits = "1234", onClick = {}, isReissuing = false),
TangemPayDetailsBalanceBlockState.Card(lastDigits = "3456", onClick = {}, isReissuing = false),
),
onAddCardClick = {},
),
@ -438,7 +466,13 @@ private class TangemPayDetailsUMProvider : CollectionPreviewParameterProvider<Ta
balanceBlockState = TangemPayDetailsBalanceBlockState.Loading(
actionButtons = persistentListOf(),
cardsBlockState = TangemPayDetailsBalanceBlockState.CardsBlockState(
cards = persistentListOf(TangemPayDetailsBalanceBlockState.Card(lastDigits = "1234", onClick = {})),
cards = persistentListOf(
TangemPayDetailsBalanceBlockState.Card(
lastDigits = "1234",
onClick = {},
isReissuing = true,
),
),
onAddCardClick = {},
),
),

View file

@ -1,6 +1,7 @@
package com.tangem.features.tangempay.utils
import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig.ShowRefreshState
import com.tangem.domain.pay.TangemPayDetailsConfig
internal interface TangemPayDetailIntents {
fun onContactSupportClicked()
@ -8,6 +9,6 @@ internal interface TangemPayDetailIntents {
fun onClickAddFunds()
fun onClickWithdraw()
fun onClickTermsAndLimits()
fun onCardClick()
fun onCardClick(config: TangemPayDetailsConfig)
fun onAddCardClick()
}

View file

@ -53,6 +53,7 @@ internal class TangemPayCardLimitSetupModelTest {
chainId = 1,
isTangemPayDeactivated = false,
displayName = null,
isReissuing = false,
),
)