Updated on 2026-08-14

This commit is contained in:
Tangem 2026-07-13 02:20:35 -07:00
parent 579b3cba32
commit f74c37a080
25 changed files with 154 additions and 40 deletions

View file

@ -13,6 +13,7 @@ internal class TangemPayCardDetailsBlockStateFactory(
private val displayName: CardDisplayName?,
private val isEditingNameEnabled: Boolean,
private val cardState: TangemPayCardState,
private val cardImageUrl: String?,
private val onEditNameClick: () -> Unit,
private val onReveal: () -> Unit,
private val onCopy: (String, CardDataType) -> Unit,
@ -41,6 +42,7 @@ internal class TangemPayCardDetailsBlockStateFactory(
},
shouldShowCardDetailsButtonOnCard = shouldShowCardDetailsButtonOnCard,
cardState = cardState,
cardImageUrl = cardImageUrl,
)
}
}

View file

@ -18,6 +18,7 @@ import com.tangem.domain.models.pay.TangemPayCard
import com.tangem.domain.models.pay.TangemPayCardFrozenState
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.utils.TangemPayDetailIntents
import com.tangem.features.tangempay.utils.hasWithdrawableAmount
@ -102,6 +103,7 @@ internal class TangemPayDetailsStateFactory(
.map { cardItem ->
TangemPayDetailsBalanceBlockState.Card(
lastDigits = cardItem.lastDigits,
imageUrl = cardItem.thumbnailUrl,
onClick = { intents.onCardClick(cardItem.id) },
isEnabled = status.error == null,
isFrozen = cardItem.isFrozen,

View file

@ -36,6 +36,7 @@ internal data class TangemPayCardDetailsUM(
val isLoading: Boolean = false,
val cardFrozenState: TangemPayCardFrozenState,
val displayNameState: DisplayNameState?,
val cardImageUrl: String?,
val isActionsAvailable: Boolean = false,
val shouldShowCardDetailsButtonOnCard: Boolean = false,
val cardState: TangemPayCardState = TangemPayCardState.Active,
@ -111,6 +112,7 @@ internal sealed class TangemPayDetailsBalanceBlockState {
data class Card(
val lastDigits: String,
val imageUrl: String?,
val onClick: () -> Unit,
val state: TangemPayCardUiState,
val isFrozen: Boolean,

View file

@ -12,6 +12,7 @@ import com.tangem.domain.models.account.findCardWithId
import com.tangem.domain.models.pay.TangemPayCard
import com.tangem.domain.models.pay.TangemPayCardFrozenState
import com.tangem.domain.models.pay.TangemPayCardState
import com.tangem.domain.models.pay.mainImageUrl
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.pay.flow.PaymentAccountStatusSupplier
import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository
@ -78,6 +79,7 @@ internal class TangemPayCardDetailsController @AssistedInject constructor(
onCopy = ::copyData,
shouldShowCardDetailsButtonOnCard = config.shouldShowCardDetailsButtonOnCard,
cardState = card.state,
cardImageUrl = card.mainImageUrl,
)
val uiState: StateFlow<TangemPayCardDetailsUM>
@ -119,6 +121,7 @@ internal class TangemPayCardDetailsController @AssistedInject constructor(
cardFrozenState = card.frozenState,
isActionsAvailable = card.state == TangemPayCardState.Active,
cardState = card.state,
cardImageUrl = card.mainImageUrl,
)
}
subscribeToCardFrozenState(card.id)

View file

@ -2,12 +2,12 @@ package com.tangem.features.tangempay.tiers.select
import android.content.res.Configuration
import androidx.compose.animation.AnimatedContent
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
@ -16,7 +16,6 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
@ -26,7 +25,6 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEach
import coil.compose.SubcomposeAsyncImage
import coil.request.ImageRequest
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.ds.TangemPagerIndicator
import com.tangem.core.ui.ds.topbar.TangemTopBar
import com.tangem.core.ui.ds2.button.Back
@ -268,31 +266,29 @@ private fun ConfirmFooter(content: TangemPaySelectPlanUM.Content.Confirm, modifi
@Suppress("MagicNumber")
@Composable
private fun PlanCard(imageUrl: String?, modifier: Modifier = Modifier) {
SubcomposeAsyncImage(
Box(
modifier = modifier
.fillMaxWidth()
.aspectRatio(ratio = 266f / 172f)
.clip(RoundedCornerShape(12.dp)),
model = ImageRequest.Builder(LocalContext.current)
.data(imageUrl)
.crossfade(true)
.build(),
loading = {
RectangleShimmer(
modifier = Modifier.fillMaxSize(),
radius = 12.dp,
)
},
error = {
Box(
modifier = Modifier
.fillMaxSize()
.background(TangemTheme.colors3.bg.secondary),
)
},
contentScale = ContentScale.Crop,
contentDescription = null,
)
.aspectRatio(ratio = 266f / 172f),
) {
Image(
modifier = Modifier.matchParentSize(),
painter = painterResource(R.drawable.img_tangem_pay_visa_reissuing),
contentDescription = null,
contentScale = ContentScale.FillBounds,
)
SubcomposeAsyncImage(
modifier = Modifier.matchParentSize(),
model = ImageRequest.Builder(LocalContext.current)
.data(imageUrl)
.crossfade(true)
.build(),
loading = {},
error = {},
contentScale = ContentScale.FillBounds,
contentDescription = null,
)
}
}
@Preview(showBackground = true, device = Devices.PIXEL_7_PRO)

View file

@ -186,6 +186,7 @@ private fun PreviewTangemPayAddToWalletScreen() {
buttonText = TextReference.Res(R.string.tangempay_card_details_hide_text),
cardFrozenState = TangemPayCardFrozenState.Unfrozen,
displayNameState = null,
cardImageUrl = null,
),
)
}

View file

@ -220,6 +220,7 @@ private fun PreviewTangemPayAddToWalletScreen() {
buttonText = TextReference.Res(R.string.tangempay_card_details_hide_text),
cardFrozenState = TangemPayCardFrozenState.Unfrozen,
displayNameState = null,
cardImageUrl = null,
),
)
}

View file

@ -28,6 +28,7 @@ import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.SubcomposeLayout
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
@ -46,6 +47,8 @@ import androidx.constraintlayout.compose.ConstrainedLayoutReference
import androidx.constraintlayout.compose.ConstraintLayout
import androidx.constraintlayout.compose.ConstraintLayoutScope
import androidx.constraintlayout.compose.Dimension
import coil.compose.SubcomposeAsyncImage
import coil.request.ImageRequest
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.SpacerWMax
import com.tangem.core.ui.components.buttons.common.TangemButton
@ -123,6 +126,7 @@ private fun TangemPayCardDetailsHiddenBlock(state: TangemPayCardDetailsUM, modif
.zIndex(0f),
cardFrozenState = state.cardFrozenState,
cardState = state.cardState,
cardImageUrl = state.cardImageUrl,
)
Box(
@ -219,6 +223,7 @@ private fun TangemPayCardDetailsHiddenBlock(state: TangemPayCardDetailsUM, modif
private fun TangemPayCardBackground(
cardState: TangemPayCardState,
cardFrozenState: TangemPayCardFrozenState,
cardImageUrl: String?,
modifier: Modifier = Modifier,
) {
val isFrozen = cardFrozenState == TangemPayCardFrozenState.Frozen
@ -233,7 +238,7 @@ private fun TangemPayCardBackground(
Box(modifier = modifier.fillMaxSize()) {
Image(
modifier = Modifier.fillMaxSize(),
modifier = Modifier.matchParentSize(),
painter = when (cardState) {
TangemPayCardState.Active,
-> painterResource(R.drawable.img_tangem_pay_visa)
@ -245,7 +250,19 @@ private fun TangemPayCardBackground(
contentDescription = null,
contentScale = ContentScale.FillBounds,
)
if (cardImageUrl != null && cardState == TangemPayCardState.Active) {
SubcomposeAsyncImage(
modifier = Modifier.matchParentSize(),
model = ImageRequest.Builder(LocalContext.current)
.data(cardImageUrl)
.crossfade(true)
.build(),
loading = {},
error = {},
contentScale = ContentScale.FillBounds,
contentDescription = null,
)
}
if (isFrozen || freezeProgress > 0f) {
Image(
modifier = Modifier
@ -756,6 +773,7 @@ private class TangemPayCardDetailsUMProvider : CollectionPreviewParameterProvide
cvv = "",
buttonText = resourceReference(R.string.tangempay_card_details_show_details),
onCopy = { _, _ -> },
cardImageUrl = null,
isHidden = true,
cardFrozenState = TangemPayCardFrozenState.Frozen,
displayNameState = DisplayNameState.Editing(
@ -776,6 +794,7 @@ private class TangemPayCardDetailsUMProvider : CollectionPreviewParameterProvide
cvv = "",
buttonText = resourceReference(R.string.tangempay_card_details_show_details),
onCopy = { _, _ -> },
cardImageUrl = null,
isHidden = true,
cardFrozenState = TangemPayCardFrozenState.Unfrozen,
displayNameState = DisplayNameState.Editing(
@ -796,6 +815,7 @@ private class TangemPayCardDetailsUMProvider : CollectionPreviewParameterProvide
cvv = "",
buttonText = resourceReference(R.string.tangempay_card_details_show_details),
onCopy = { _, _ -> },
cardImageUrl = null,
isHidden = true,
cardFrozenState = TangemPayCardFrozenState.Pending,
displayNameState = DisplayNameState.Display(
@ -809,6 +829,7 @@ private class TangemPayCardDetailsUMProvider : CollectionPreviewParameterProvide
onClick = {},
buttonText = resourceReference(R.string.tangempay_card_details_hide_details),
onCopy = { _, _ -> },
cardImageUrl = null,
isHidden = false,
number = "1234 5678 9012 3456",
numberShort = "*3456",

View file

@ -428,6 +428,7 @@ private fun previewCardDetailsState(): TangemPayCardDetailsUM = TangemPayCardDet
onClick = {},
isEditingEnabled = false,
),
cardImageUrl = null,
)
@Preview

View file

@ -491,6 +491,7 @@ internal class TangemPayDetailsUMProvider : CollectionPreviewParameterProvider<T
cards = persistentListOf(
TangemPayDetailsBalanceBlockState.Card(
lastDigits = "1234",
imageUrl = null,
onClick = {},
isEnabled = false,
isFrozen = false,
@ -498,6 +499,7 @@ internal class TangemPayDetailsUMProvider : CollectionPreviewParameterProvider<T
),
TangemPayDetailsBalanceBlockState.Card(
lastDigits = "3456",
imageUrl = null,
onClick = {},
isEnabled = true,
isFrozen = false,
@ -531,6 +533,7 @@ internal class TangemPayDetailsUMProvider : CollectionPreviewParameterProvider<T
cards = persistentListOf(
TangemPayDetailsBalanceBlockState.Card(
lastDigits = "1234",
imageUrl = null,
onClick = {},
isFrozen = false,
isEnabled = true,

View file

@ -412,6 +412,7 @@ private fun CardsBlock(
TangemPayCardView(
isIssueInProgress = item.state != TangemPayCardUiState.Active,
lastDigits = item.lastDigits,
imageUrl = item.imageUrl,
onClick = item.onClick,
isEnabled = item.isEnabled,
isFrozen = item.isFrozen,

View file

@ -19,12 +19,16 @@ import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.TileMode
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.tooling.preview.Devices
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import coil.compose.SubcomposeAsyncImage
import coil.request.ImageRequest
import com.tangem.core.ui.R
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.extensions.clickableSingle
@ -40,11 +44,13 @@ private const val DEFAULT_CARD_BG = 0xFF1C1F29
private const val REISSUING_CARD_BG = 0xFF1E1E1E
private const val DISABLED_ALPHA = 0.5f
@Suppress("LongParameterList")
@Composable
internal fun TangemPayCardView(
isIssueInProgress: Boolean,
isEnabled: Boolean,
lastDigits: String,
imageUrl: String?,
onClick: () -> Unit,
isFrozen: Boolean,
modifier: Modifier = Modifier,
@ -58,6 +64,7 @@ internal fun TangemPayCardView(
.testTag(TangemPayTestTags.PAYMENT_ACCOUNT_CARD_BUTTON),
isIssueInProgress = isIssueInProgress,
isEnabled = isEnabled,
imageUrl = imageUrl,
onClick = onClick,
) {
Row(
@ -78,7 +85,6 @@ internal fun TangemPayCardView(
tint = TangemTheme.colors3.icon.staticDark,
contentDescription = null,
)
Icon(
modifier = Modifier.size(height = TangemTheme.dimens2.x2, width = 22.dp),
imageVector = ImageVector.vectorResource(R.drawable.ic_visa_logo),
@ -126,6 +132,7 @@ internal fun TangemPayAddCardView(onClick: () -> Unit, isEnabled: Boolean, modif
private fun CardBackground(
isIssueInProgress: Boolean,
isEnabled: Boolean,
imageUrl: String?,
onClick: () -> Unit,
modifier: Modifier = Modifier,
content: @Composable BoxScope.() -> Unit,
@ -137,7 +144,6 @@ private fun CardBackground(
Color(DEFAULT_CARD_BG)
}
}
Box(
modifier = modifier
.alpha(if (isEnabled) 1f else DISABLED_ALPHA)
@ -171,8 +177,22 @@ private fun CardBackground(
shape = RoundedCornerShape(6.dp),
)
.clickableSingle(onClick = onClick, enabled = isEnabled),
content = content,
)
) {
if (imageUrl != null) {
SubcomposeAsyncImage(
modifier = Modifier.matchParentSize(),
model = ImageRequest.Builder(LocalContext.current)
.data(imageUrl)
.crossfade(true)
.build(),
loading = {},
error = {},
contentScale = ContentScale.Crop,
contentDescription = null,
)
}
content()
}
}
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES, device = Devices.PIXEL_7_PRO)
@ -189,6 +209,7 @@ private fun CardBackgroundPreview() {
onClick = {},
content = {},
isEnabled = true,
imageUrl = null,
)
SpacerH(TangemTheme.dimens2.x4)
CardBackground(
@ -197,6 +218,7 @@ private fun CardBackgroundPreview() {
width = TangemTheme.dimens2.x14,
),
isIssueInProgress = true,
imageUrl = null,
onClick = {},
content = {},
isEnabled = true,
@ -206,6 +228,7 @@ private fun CardBackgroundPreview() {
isIssueInProgress = false,
onClick = {},
lastDigits = "1234",
imageUrl = null,
isEnabled = true,
isFrozen = false,
)
@ -214,6 +237,7 @@ private fun CardBackgroundPreview() {
isIssueInProgress = true,
onClick = {},
lastDigits = "",
imageUrl = null,
isEnabled = true,
isFrozen = false,
)
@ -224,6 +248,7 @@ private fun CardBackgroundPreview() {
isIssueInProgress = false,
onClick = {},
lastDigits = "1234",
imageUrl = null,
isEnabled = true,
isFrozen = true,
)

View file

@ -58,5 +58,6 @@ internal fun tangemPayCard(
limit = null,
frozenState = frozenState,
lastDigits = lastDigits,
images = emptyList(),
state = state,
)

View file

@ -47,6 +47,7 @@ internal class TangemPayCardLimitSetupModelTest {
frozenState = TangemPayCardFrozenState.Unfrozen,
lastDigits = "1234",
limit = null,
images = emptyList(),
state = TangemPayCardState.Active,
)
@ -72,6 +73,7 @@ internal class TangemPayCardLimitSetupModelTest {
)
}
),
images = emptyList(),
state = TangemPayCardState.Active,
)
val statusWithLimit: PaymentAccountStatusValue.Loaded = mockk(relaxed = true) {

View file

@ -162,6 +162,7 @@ internal class TangemPayEditDisplayNameModelTest {
limit = null,
frozenState = TangemPayCardFrozenState.Unfrozen,
lastDigits = "1234",
images = emptyList(),
state = TangemPayCardState.Active,
)
}

View file

@ -238,6 +238,7 @@ internal class TangemPayCardDetailsControllerTest {
limit = null,
frozenState = frozenState,
lastDigits = lastDigits,
images = emptyList(),
state = state,
)