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

@ -165,5 +165,6 @@ data class CustomerMeResponse(
@Json(name = "card_status") val cardStatus: String, @Json(name = "card_status") val cardStatus: String,
@Json(name = "card_number_end") val cardNumberEnd: String, @Json(name = "card_number_end") val cardNumberEnd: String,
@Json(name = "is_pin_set") val isPinSet: Boolean?, @Json(name = "is_pin_set") val isPinSet: Boolean?,
@Json(name = "images") val images: List<Image>?,
) )
} }

View file

@ -93,6 +93,13 @@ sealed interface PaymentAccountStatusValueDM {
@Json(name = "admin_daily_limit") val adminDailyLimit: SerializedBigDecimal?, @Json(name = "admin_daily_limit") val adminDailyLimit: SerializedBigDecimal?,
@Json(name = "frozen_state") val frozenState: String, @Json(name = "frozen_state") val frozenState: String,
@Json(name = "last_digits") val lastDigits: String, @Json(name = "last_digits") val lastDigits: String,
@Json(name = "images") val images: List<ImageDM>,
@Json(name = "state") val state: String, @Json(name = "state") val state: String,
) )
@JsonClass(generateAdapter = true)
data class ImageDM(
@Json(name = "type") val type: String,
@Json(name = "url") val url: String,
)
} }

View file

@ -5,6 +5,7 @@ import com.tangem.datasource.local.visa.entity.PaymentAccountStatusValueDM
import com.tangem.domain.models.StatusSource import com.tangem.domain.models.StatusSource
import com.tangem.domain.models.account.CardDisplayName import com.tangem.domain.models.account.CardDisplayName
import com.tangem.domain.models.account.PaymentAccountStatusValue import com.tangem.domain.models.account.PaymentAccountStatusValue
import com.tangem.domain.models.account.TangemPayTariffPlan
import com.tangem.domain.models.pay.* import com.tangem.domain.models.pay.*
import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.pay.TangemPayCurrencyFactory import com.tangem.domain.pay.TangemPayCurrencyFactory
@ -51,6 +52,12 @@ internal class PaymentAccountStatusValueDMConverter @Inject constructor(
adminDailyLimit = card.limit?.adminCardLimit?.amount, adminDailyLimit = card.limit?.adminCardLimit?.amount,
frozenState = card.frozenState.toString(), frozenState = card.frozenState.toString(),
lastDigits = card.lastDigits, lastDigits = card.lastDigits,
images = card.images.map { image ->
PaymentAccountStatusValueDM.ImageDM(
type = image.type.name,
url = image.url,
)
},
state = card.state.toString(), state = card.state.toString(),
) )
}, },
@ -91,11 +98,7 @@ internal class PaymentAccountStatusValueDMConverter @Inject constructor(
source = StatusSource.CACHE, source = StatusSource.CACHE,
customerId = value.customerId, customerId = value.customerId,
depositAddress = value.depositAddress, depositAddress = value.depositAddress,
balance = PaymentAccountStatusValue.Balance( balance = value.getBalance(),
fiatBalance = value.fiatBalance.toDomain(),
cryptoBalance = value.cryptoBalance.toDomain(),
availableForWithdrawal = value.availableForWithdrawal,
),
cryptoCurrency = cryptoCurrency, cryptoCurrency = cryptoCurrency,
fiatRate = value.fiatRate, fiatRate = value.fiatRate,
cards = value.cards.map { card -> cards = value.cards.map { card ->
@ -115,6 +118,7 @@ internal class PaymentAccountStatusValueDMConverter @Inject constructor(
), ),
frozenState = TangemPayCardFrozenState.fromString(card.frozenState), frozenState = TangemPayCardFrozenState.fromString(card.frozenState),
lastDigits = card.lastDigits, lastDigits = card.lastDigits,
images = card.getImages(),
state = TangemPayCardState.fromString(card.state), state = TangemPayCardState.fromString(card.state),
) )
}, },
@ -160,6 +164,14 @@ internal class PaymentAccountStatusValueDMConverter @Inject constructor(
) )
} }
private fun PaymentAccountStatusValueDM.ActiveAccount.getBalance(): PaymentAccountStatusValue.Balance {
return PaymentAccountStatusValue.Balance(
fiatBalance = fiatBalance.toDomain(),
cryptoBalance = cryptoBalance.toDomain(),
availableForWithdrawal = availableForWithdrawal,
)
}
private fun PaymentAccountStatusValueDM.FiatBalanceDM.toDomain(): PaymentAccountStatusValue.FiatBalance { private fun PaymentAccountStatusValueDM.FiatBalanceDM.toDomain(): PaymentAccountStatusValue.FiatBalance {
return PaymentAccountStatusValue.FiatBalance( return PaymentAccountStatusValue.FiatBalance(
availableBalance = availableBalance, availableBalance = availableBalance,
@ -176,4 +188,13 @@ internal class PaymentAccountStatusValueDMConverter @Inject constructor(
balance = balance, balance = balance,
) )
} }
private fun PaymentAccountStatusValueDM.TangemPayCard.getImages(): List<TangemPayTariffPlan.Image> {
return images.map { image ->
TangemPayTariffPlan.Image(
type = TangemPayTariffPlan.Image.Type.fromString(image.type),
url = image.url,
)
}
}
} }

View file

@ -360,6 +360,7 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor(
productInstance.frozenState productInstance.frozenState
}, },
lastDigits = cardInfo.lastFourDigits, lastDigits = cardInfo.lastFourDigits,
images = cardInfo.images,
state = getCardState(cardId, userWalletId), state = getCardState(cardId, userWalletId),
) )
} }
@ -509,6 +510,7 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor(
limit = null, limit = null,
frozenState = TangemPayCardFrozenState.Unfrozen, frozenState = TangemPayCardFrozenState.Unfrozen,
lastDigits = "", lastDigits = "",
images = emptyList(),
state = TangemPayCardState.Issuing, state = TangemPayCardState.Issuing,
) )

View file

@ -92,6 +92,15 @@ internal object CustomerInfoConverter : Converter<CustomerMeResponse.Result, Cus
cardStatus = TangemPayCard.Status.fromString(card.cardStatus), cardStatus = TangemPayCard.Status.fromString(card.cardStatus),
lastFourDigits = card.cardNumberEnd, lastFourDigits = card.cardNumberEnd,
isPinSet = card.isPinSet == true, isPinSet = card.isPinSet == true,
images = card.images.orEmpty().mapNotNull(::convertCardImage),
)
}
private fun convertCardImage(image: CustomerMeResponse.Image): TangemPayTariffPlan.Image? {
val url = image.url ?: return null
return TangemPayTariffPlan.Image(
type = TangemPayTariffPlan.Image.Type.fromString(image.type),
url = url,
) )
} }

View file

@ -108,6 +108,7 @@ internal class DefaultPaymentAccountStatusFetcherTest {
cardStatus = TangemPayCard.Status.ACTIVE, cardStatus = TangemPayCard.Status.ACTIVE,
lastFourDigits = "1234", lastFourDigits = "1234",
isPinSet = true, isPinSet = true,
images = emptyList(),
) )
private fun buildCustomerInfo( private fun buildCustomerInfo(

View file

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

View file

@ -1,6 +1,7 @@
package com.tangem.domain.models.pay package com.tangem.domain.models.pay
import com.tangem.domain.models.account.CardDisplayName import com.tangem.domain.models.account.CardDisplayName
import com.tangem.domain.models.account.TangemPayTariffPlan
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@ -16,6 +17,7 @@ import kotlinx.serialization.Serializable
* @property limit spending limit configuration for the card; `null` if not configured or not yet loaded. * @property limit spending limit configuration for the card; `null` if not configured or not yet loaded.
* @property frozenState whether the card is currently frozen (blocked for payments). * @property frozenState whether the card is currently frozen (blocked for payments).
* @property lastDigits The last four digits of the card number. * @property lastDigits The last four digits of the card number.
* @property images card artwork images provided by the backend, keyed by [TangemPayTariffPlan.Image.Type].
* @property state current lifecycle state of the card (reissuing / closing / active). * @property state current lifecycle state of the card (reissuing / closing / active).
*/ */
@Serializable @Serializable
@ -28,10 +30,9 @@ data class TangemPayCard(
@SerialName("limit") val limit: TangemPayCardLimitData?, @SerialName("limit") val limit: TangemPayCardLimitData?,
@SerialName("frozen_state") val frozenState: TangemPayCardFrozenState, @SerialName("frozen_state") val frozenState: TangemPayCardFrozenState,
@SerialName("last_digits") val lastDigits: String, @SerialName("last_digits") val lastDigits: String,
@SerialName("images") val images: List<TangemPayTariffPlan.Image>,
@SerialName("state") val state: TangemPayCardState, @SerialName("state") val state: TangemPayCardState,
) { ) {
/** Backend card status — unknown values map to [UNDEFINED] without crashing. */
@Serializable @Serializable
enum class Status { enum class Status {
@SerialName("ACTIVE") @SerialName("ACTIVE")
@ -65,4 +66,10 @@ data class TangemPayCard(
} }
val TangemPayCard.isFrozen val TangemPayCard.isFrozen
get() = frozenState == TangemPayCardFrozenState.Frozen get() = frozenState == TangemPayCardFrozenState.Frozen
val TangemPayCard.thumbnailUrl: String?
get() = images.firstOrNull { it.type == TangemPayTariffPlan.Image.Type.THUMBNAIL }?.url
val TangemPayCard.mainImageUrl: String?
get() = images.firstOrNull { it.type == TangemPayTariffPlan.Image.Type.MAIN }?.url

View file

@ -3,6 +3,7 @@ package com.tangem.domain.pay.model
import com.tangem.domain.models.account.CardDisplayName import com.tangem.domain.models.account.CardDisplayName
import com.tangem.domain.models.account.PaymentAccountStatusValue import com.tangem.domain.models.account.PaymentAccountStatusValue
import com.tangem.domain.models.account.TangemPayCustomerTariffPlan import com.tangem.domain.models.account.TangemPayCustomerTariffPlan
import com.tangem.domain.models.account.TangemPayTariffPlan
import com.tangem.domain.models.kyc.KycStatus import com.tangem.domain.models.kyc.KycStatus
import com.tangem.domain.models.pay.TangemPayCard import com.tangem.domain.models.pay.TangemPayCard
import com.tangem.domain.models.pay.TangemPayCardFrozenState import com.tangem.domain.models.pay.TangemPayCardFrozenState
@ -103,5 +104,6 @@ data class CustomerInfo(
val cardStatus: TangemPayCard.Status, val cardStatus: TangemPayCard.Status,
val lastFourDigits: String, val lastFourDigits: String,
val isPinSet: Boolean, val isPinSet: Boolean,
val images: List<TangemPayTariffPlan.Image>,
) )
} }

View file

@ -13,6 +13,7 @@ internal class TangemPayCardDetailsBlockStateFactory(
private val displayName: CardDisplayName?, private val displayName: CardDisplayName?,
private val isEditingNameEnabled: Boolean, private val isEditingNameEnabled: Boolean,
private val cardState: TangemPayCardState, private val cardState: TangemPayCardState,
private val cardImageUrl: String?,
private val onEditNameClick: () -> Unit, private val onEditNameClick: () -> Unit,
private val onReveal: () -> Unit, private val onReveal: () -> Unit,
private val onCopy: (String, CardDataType) -> Unit, private val onCopy: (String, CardDataType) -> Unit,
@ -41,6 +42,7 @@ internal class TangemPayCardDetailsBlockStateFactory(
}, },
shouldShowCardDetailsButtonOnCard = shouldShowCardDetailsButtonOnCard, shouldShowCardDetailsButtonOnCard = shouldShowCardDetailsButtonOnCard,
cardState = cardState, 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.TangemPayCardFrozenState
import com.tangem.domain.models.pay.TangemPayCardState import com.tangem.domain.models.pay.TangemPayCardState
import com.tangem.domain.models.pay.isFrozen 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.details.impl.R
import com.tangem.features.tangempay.utils.TangemPayDetailIntents import com.tangem.features.tangempay.utils.TangemPayDetailIntents
import com.tangem.features.tangempay.utils.hasWithdrawableAmount import com.tangem.features.tangempay.utils.hasWithdrawableAmount
@ -102,6 +103,7 @@ internal class TangemPayDetailsStateFactory(
.map { cardItem -> .map { cardItem ->
TangemPayDetailsBalanceBlockState.Card( TangemPayDetailsBalanceBlockState.Card(
lastDigits = cardItem.lastDigits, lastDigits = cardItem.lastDigits,
imageUrl = cardItem.thumbnailUrl,
onClick = { intents.onCardClick(cardItem.id) }, onClick = { intents.onCardClick(cardItem.id) },
isEnabled = status.error == null, isEnabled = status.error == null,
isFrozen = cardItem.isFrozen, isFrozen = cardItem.isFrozen,

View file

@ -36,6 +36,7 @@ internal data class TangemPayCardDetailsUM(
val isLoading: Boolean = false, val isLoading: Boolean = false,
val cardFrozenState: TangemPayCardFrozenState, val cardFrozenState: TangemPayCardFrozenState,
val displayNameState: DisplayNameState?, val displayNameState: DisplayNameState?,
val cardImageUrl: String?,
val isActionsAvailable: Boolean = false, val isActionsAvailable: Boolean = false,
val shouldShowCardDetailsButtonOnCard: Boolean = false, val shouldShowCardDetailsButtonOnCard: Boolean = false,
val cardState: TangemPayCardState = TangemPayCardState.Active, val cardState: TangemPayCardState = TangemPayCardState.Active,
@ -111,6 +112,7 @@ internal sealed class TangemPayDetailsBalanceBlockState {
data class Card( data class Card(
val lastDigits: String, val lastDigits: String,
val imageUrl: String?,
val onClick: () -> Unit, val onClick: () -> Unit,
val state: TangemPayCardUiState, val state: TangemPayCardUiState,
val isFrozen: Boolean, 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.TangemPayCard
import com.tangem.domain.models.pay.TangemPayCardFrozenState import com.tangem.domain.models.pay.TangemPayCardFrozenState
import com.tangem.domain.models.pay.TangemPayCardState 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.models.wallet.UserWalletId
import com.tangem.domain.pay.flow.PaymentAccountStatusSupplier import com.tangem.domain.pay.flow.PaymentAccountStatusSupplier
import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository
@ -78,6 +79,7 @@ internal class TangemPayCardDetailsController @AssistedInject constructor(
onCopy = ::copyData, onCopy = ::copyData,
shouldShowCardDetailsButtonOnCard = config.shouldShowCardDetailsButtonOnCard, shouldShowCardDetailsButtonOnCard = config.shouldShowCardDetailsButtonOnCard,
cardState = card.state, cardState = card.state,
cardImageUrl = card.mainImageUrl,
) )
val uiState: StateFlow<TangemPayCardDetailsUM> val uiState: StateFlow<TangemPayCardDetailsUM>
@ -119,6 +121,7 @@ internal class TangemPayCardDetailsController @AssistedInject constructor(
cardFrozenState = card.frozenState, cardFrozenState = card.frozenState,
isActionsAvailable = card.state == TangemPayCardState.Active, isActionsAvailable = card.state == TangemPayCardState.Active,
cardState = card.state, cardState = card.state,
cardImageUrl = card.mainImageUrl,
) )
} }
subscribeToCardFrozenState(card.id) subscribeToCardFrozenState(card.id)

View file

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

View file

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

View file

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

View file

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

View file

@ -412,6 +412,7 @@ private fun CardsBlock(
TangemPayCardView( TangemPayCardView(
isIssueInProgress = item.state != TangemPayCardUiState.Active, isIssueInProgress = item.state != TangemPayCardUiState.Active,
lastDigits = item.lastDigits, lastDigits = item.lastDigits,
imageUrl = item.imageUrl,
onClick = item.onClick, onClick = item.onClick,
isEnabled = item.isEnabled, isEnabled = item.isEnabled,
isFrozen = item.isFrozen, 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.Color
import androidx.compose.ui.graphics.TileMode import androidx.compose.ui.graphics.TileMode
import androidx.compose.ui.graphics.vector.ImageVector 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.platform.testTag
import androidx.compose.ui.res.vectorResource import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.tooling.preview.Devices import androidx.compose.ui.tooling.preview.Devices
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp 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.R
import com.tangem.core.ui.components.SpacerH import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.extensions.clickableSingle 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 REISSUING_CARD_BG = 0xFF1E1E1E
private const val DISABLED_ALPHA = 0.5f private const val DISABLED_ALPHA = 0.5f
@Suppress("LongParameterList")
@Composable @Composable
internal fun TangemPayCardView( internal fun TangemPayCardView(
isIssueInProgress: Boolean, isIssueInProgress: Boolean,
isEnabled: Boolean, isEnabled: Boolean,
lastDigits: String, lastDigits: String,
imageUrl: String?,
onClick: () -> Unit, onClick: () -> Unit,
isFrozen: Boolean, isFrozen: Boolean,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
@ -58,6 +64,7 @@ internal fun TangemPayCardView(
.testTag(TangemPayTestTags.PAYMENT_ACCOUNT_CARD_BUTTON), .testTag(TangemPayTestTags.PAYMENT_ACCOUNT_CARD_BUTTON),
isIssueInProgress = isIssueInProgress, isIssueInProgress = isIssueInProgress,
isEnabled = isEnabled, isEnabled = isEnabled,
imageUrl = imageUrl,
onClick = onClick, onClick = onClick,
) { ) {
Row( Row(
@ -78,7 +85,6 @@ internal fun TangemPayCardView(
tint = TangemTheme.colors3.icon.staticDark, tint = TangemTheme.colors3.icon.staticDark,
contentDescription = null, contentDescription = null,
) )
Icon( Icon(
modifier = Modifier.size(height = TangemTheme.dimens2.x2, width = 22.dp), modifier = Modifier.size(height = TangemTheme.dimens2.x2, width = 22.dp),
imageVector = ImageVector.vectorResource(R.drawable.ic_visa_logo), imageVector = ImageVector.vectorResource(R.drawable.ic_visa_logo),
@ -126,6 +132,7 @@ internal fun TangemPayAddCardView(onClick: () -> Unit, isEnabled: Boolean, modif
private fun CardBackground( private fun CardBackground(
isIssueInProgress: Boolean, isIssueInProgress: Boolean,
isEnabled: Boolean, isEnabled: Boolean,
imageUrl: String?,
onClick: () -> Unit, onClick: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
content: @Composable BoxScope.() -> Unit, content: @Composable BoxScope.() -> Unit,
@ -137,7 +144,6 @@ private fun CardBackground(
Color(DEFAULT_CARD_BG) Color(DEFAULT_CARD_BG)
} }
} }
Box( Box(
modifier = modifier modifier = modifier
.alpha(if (isEnabled) 1f else DISABLED_ALPHA) .alpha(if (isEnabled) 1f else DISABLED_ALPHA)
@ -171,8 +177,22 @@ private fun CardBackground(
shape = RoundedCornerShape(6.dp), shape = RoundedCornerShape(6.dp),
) )
.clickableSingle(onClick = onClick, enabled = isEnabled), .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) @Preview(uiMode = Configuration.UI_MODE_NIGHT_YES, device = Devices.PIXEL_7_PRO)
@ -189,6 +209,7 @@ private fun CardBackgroundPreview() {
onClick = {}, onClick = {},
content = {}, content = {},
isEnabled = true, isEnabled = true,
imageUrl = null,
) )
SpacerH(TangemTheme.dimens2.x4) SpacerH(TangemTheme.dimens2.x4)
CardBackground( CardBackground(
@ -197,6 +218,7 @@ private fun CardBackgroundPreview() {
width = TangemTheme.dimens2.x14, width = TangemTheme.dimens2.x14,
), ),
isIssueInProgress = true, isIssueInProgress = true,
imageUrl = null,
onClick = {}, onClick = {},
content = {}, content = {},
isEnabled = true, isEnabled = true,
@ -206,6 +228,7 @@ private fun CardBackgroundPreview() {
isIssueInProgress = false, isIssueInProgress = false,
onClick = {}, onClick = {},
lastDigits = "1234", lastDigits = "1234",
imageUrl = null,
isEnabled = true, isEnabled = true,
isFrozen = false, isFrozen = false,
) )
@ -214,6 +237,7 @@ private fun CardBackgroundPreview() {
isIssueInProgress = true, isIssueInProgress = true,
onClick = {}, onClick = {},
lastDigits = "", lastDigits = "",
imageUrl = null,
isEnabled = true, isEnabled = true,
isFrozen = false, isFrozen = false,
) )
@ -224,6 +248,7 @@ private fun CardBackgroundPreview() {
isIssueInProgress = false, isIssueInProgress = false,
onClick = {}, onClick = {},
lastDigits = "1234", lastDigits = "1234",
imageUrl = null,
isEnabled = true, isEnabled = true,
isFrozen = true, isFrozen = true,
) )

View file

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

View file

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

View file

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

View file

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