Updated on 2026-08-14

This commit is contained in:
Tangem 2026-06-11 14:55:37 +02:00
parent 7883fcc2c6
commit 407cfd491f
4 changed files with 344 additions and 2 deletions

View file

@ -9,6 +9,7 @@ import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.features.tangempay.model.TangemPayReissueCardModel
import com.tangem.features.tangempay.ui.TangemPayReissueCardContent
import com.tangem.features.tangempay.ui.TangemPayReissueCardContentV2
internal class TangemPayReissueCardComponent(
appComponentContext: AppComponentContext,
@ -22,7 +23,11 @@ internal class TangemPayReissueCardComponent(
@Composable
override fun BottomSheet() {
val state by model.state.collectAsStateWithLifecycle()
TangemPayReissueCardContent(state = state)
if (model.isRedesignEnabled()) {
TangemPayReissueCardContentV2(state)
} else {
TangemPayReissueCardContent(state)
}
}
data class Params(

View file

@ -4,6 +4,7 @@ import androidx.compose.runtime.Immutable
@Immutable
internal data class TangemPayReissueCardUM(
val cardBalance: String,
val feeAmount: String,
val isFeeLoading: Boolean,
val isReissuingInProgress: Boolean,
@ -16,11 +17,13 @@ internal data class TangemPayReissueCardUM(
companion object {
fun stub(
feeAmount: String = "$4.25",
cardBalance: String = "$0.05",
isFeeLoading: Boolean = false,
error: TangemPayReissueCardError = TangemPayReissueCardError.InitialDataLoading,
error: TangemPayReissueCardError? = null,
isReissuingInProgress: Boolean = false,
) = TangemPayReissueCardUM(
feeAmount = feeAmount,
cardBalance = cardBalance,
isFeeLoading = isFeeLoading,
error = error,
isReissuingInProgress = isReissuingInProgress,

View file

@ -15,6 +15,7 @@ import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository
import com.tangem.domain.pay.repository.TangemPayReissueCardRepository
import com.tangem.domain.pay.usecase.ReissueTangemPayCardUseCase
import com.tangem.domain.tangempay.TangemPayAnalyticsEvents
import com.tangem.features.tangempay.TangemPayFeatureToggles
import com.tangem.features.tangempay.components.TangemPayReissueCardComponent
import com.tangem.features.tangempay.details.impl.R
import com.tangem.features.tangempay.entity.TangemPayReissueCardError
@ -41,6 +42,7 @@ internal class TangemPayReissueCardModel @Inject constructor(
private val reissueTangemPayCardUseCase: ReissueTangemPayCardUseCase,
private val uiMessageSender: UiMessageSender,
private val analytics: AnalyticsEventHandler,
private val featureToggles: TangemPayFeatureToggles,
) : Model() {
private val params = paramsContainer.require<TangemPayReissueCardComponent.Params>()
@ -58,6 +60,7 @@ internal class TangemPayReissueCardModel @Inject constructor(
onRetryFee = ::loadData,
onAddFundsClick = { params.listener.onClickAddFunds() },
onDismissRequest = ::onDismiss,
cardBalance = "",
),
)
@ -66,6 +69,8 @@ internal class TangemPayReissueCardModel @Inject constructor(
loadData()
}
fun isRedesignEnabled(): Boolean = featureToggles.isRedesignEnabled
fun onDismiss() {
reissueJobHolder.cancel()
params.listener.onDismissReissueCard()
@ -113,6 +118,12 @@ internal class TangemPayReissueCardModel @Inject constructor(
}.orEmpty(),
isFeeLoading = false,
error = error,
cardBalance = cardBalance?.let {
cardBalance.fiatBalance.format {
val symbol = getJavaCurrencyByCode(cardBalance.currencyCode).symbol
fiat(cardBalance.currencyCode, symbol)
}
}.orEmpty(),
)
}
}.saveIn(loadDataJobHolder)

View file

@ -0,0 +1,323 @@
package com.tangem.features.tangempay.ui
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetType
import com.tangem.core.ui.ds.image.TangemIconUM
import com.tangem.core.ui.ds.topbar.TangemTopBar
import com.tangem.core.ui.ds.topbar.TangemTopBarType
import com.tangem.core.ui.ds2.button.TangemButton
import com.tangem.core.ui.ds2.row.TangemRow
import com.tangem.core.ui.ds2.row.TangemRowContentLead
import com.tangem.core.ui.ds2.row.TangemRowText
import com.tangem.core.ui.ds2.row.TangemRowTextRole
import com.tangem.core.ui.ds2.shimmers.TextShimmer
import com.tangem.core.ui.ds2.shimmers.TextShimmerStyle
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreviewRedesign
import com.tangem.core.ui.res.generated.icons.Icons
import com.tangem.core.ui.res.generated.icons.ic_arrow_refresh_32
import com.tangem.core.ui.res.generated.icons.ic_error_28
import com.tangem.features.tangempay.details.impl.R
import com.tangem.features.tangempay.entity.TangemPayReissueCardError
import com.tangem.features.tangempay.entity.TangemPayReissueCardUM
@Composable
internal fun TangemPayReissueCardContentV2(state: TangemPayReissueCardUM) {
TangemBottomSheet<TangemBottomSheetConfigContent.Empty>(
config = TangemBottomSheetConfig(
isShown = true,
onDismissRequest = state.onDismissRequest,
content = TangemBottomSheetConfigContent.Empty,
),
type = TangemBottomSheetType.Modal,
containerColor = TangemTheme.colors3.bg.secondary,
title = {
TangemTopBar(
type = TangemTopBarType.BottomSheet,
endContent = {
TangemButton(
iconStart = TangemIconUM.Icon(iconRes = R.drawable.ic_close_24),
onClick = state.onDismissRequest,
size = TangemButton.Size.X11,
variant = TangemButton.Variant.Material,
)
},
)
},
content = {
Content(state)
},
)
}
@Composable
private fun Content(state: TangemPayReissueCardUM) {
val appearance = state.contentAppearance()
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
SpacerH(16.dp)
StatusIcon(appearance = appearance)
SpacerH(32.dp)
CenteredMessageText(
textRes = appearance.titleRes,
style = TangemTheme.typography3.heading.small,
color = TangemTheme.colors3.text.primary,
)
CenteredMessageText(
textRes = appearance.subtitleRes,
style = TangemTheme.typography3.subheading.medium,
color = TangemTheme.colors3.text.secondary,
)
SpacerH(32.dp)
FeeBlock(
modifier = Modifier.padding(top = 16.dp),
state = state,
appearance = appearance,
)
SpacerH(8.dp)
BottomButtonsBlock(state = state, appearance = appearance)
}
}
@Composable
private fun StatusIcon(appearance: ReissueCardContentAppearance) {
Box(
modifier = Modifier
.size(80.dp)
.clip(CircleShape)
.background(appearance.iconBackgroundColor),
contentAlignment = Alignment.Center,
) {
Icon(
imageVector = appearance.icon,
contentDescription = null,
tint = appearance.iconColor,
modifier = Modifier.size(28.dp),
)
}
}
@Composable
private fun CenteredMessageText(textRes: Int, style: TextStyle, color: Color) {
Text(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
text = stringResourceSafe(textRes),
style = style,
color = color,
textAlign = TextAlign.Center,
)
}
@Composable
private fun FeeBlock(
state: TangemPayReissueCardUM,
appearance: ReissueCardContentAppearance,
modifier: Modifier = Modifier,
) {
if (!appearance.shouldShowFeeBlock) return
Column(modifier = modifier) {
FeeInfoRow(
titleRes = R.string.tangempay_reissue_card_fee_label,
value = state.feeAmount,
showDivider = appearance.shouldShowBalanceRow,
)
if (appearance.shouldShowBalanceRow) {
FeeInfoRow(
titleRes = R.string.common_balance_title,
value = state.cardBalance,
)
}
}
}
@Composable
private fun FeeInfoRow(titleRes: Int, value: String, showDivider: Boolean = false) {
TangemRow(
divider = showDivider,
contentLead = TangemRowContentLead.Start,
titleSlot = {
TangemRowText(
text = resourceReference(titleRes),
role = TangemRowTextRole.Title,
)
},
valueSlot = {
if (value.isEmpty()) {
TextShimmer(
text = "$ 0.00",
style = TextShimmerStyle.BODY,
radius = 48.dp,
)
} else {
TangemRowText(
text = value,
role = TangemRowTextRole.Value,
)
}
},
)
}
@Composable
private fun BottomButtonsBlock(
state: TangemPayReissueCardUM,
appearance: ReissueCardContentAppearance,
modifier: Modifier = Modifier,
) {
Column(
modifier = modifier
.fillMaxWidth()
.padding(top = 16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
TangemButton(
modifier = Modifier.fillMaxWidth(),
variant = TangemButton.Variant.Secondary,
size = TangemButton.Size.X12,
onClick = state.onDismissRequest,
text = resourceReference(R.string.common_cancel),
)
TangemButton(
modifier = Modifier.fillMaxWidth(),
size = TangemButton.Size.X12,
onClick = appearance.primaryAction(state),
isEnabled = !state.isFeeLoading && !state.isReissuingInProgress,
isLoading = state.isReissuingInProgress,
text = resourceReference(appearance.primaryButtonTextRes),
)
}
}
private data class ReissueCardContentAppearance(
val titleRes: Int,
val subtitleRes: Int,
val icon: ImageVector,
val iconColor: Color,
val iconBackgroundColor: Color,
val primaryButtonTextRes: Int,
val primaryAction: (TangemPayReissueCardUM) -> () -> Unit,
val shouldShowFeeBlock: Boolean,
val shouldShowBalanceRow: Boolean,
)
@Composable
private fun TangemPayReissueCardUM.contentAppearance(): ReissueCardContentAppearance {
val infoIconColor = TangemTheme.colors3.icon.status.info
val infoIconBackgroundColor = TangemTheme.colors3.bg.status.infoSubtle
val warningIconColor = TangemTheme.colors3.icon.status.warning
val warningIconBackgroundColor = TangemTheme.colors3.bg.status.warningSubtle
return when (error) {
TangemPayReissueCardError.InsufficientFunds -> ReissueCardContentAppearance(
titleRes = R.string.tangempay_reissue_card_insufficient_funds_title,
subtitleRes = R.string.tangempay_reissue_card_insufficient_funds_subtitle,
icon = Icons.ic_error_28,
iconColor = warningIconColor,
iconBackgroundColor = warningIconBackgroundColor,
primaryButtonTextRes = R.string.tangempay_card_details_add_funds,
primaryAction = { it.onAddFundsClick },
shouldShowFeeBlock = true,
shouldShowBalanceRow = true,
)
TangemPayReissueCardError.InitialDataLoading -> ReissueCardContentAppearance(
titleRes = R.string.tangempay_reissue_card_fee_unreachable_error_title,
subtitleRes = R.string.send_fee_unreachable_error_text,
icon = Icons.ic_error_28,
iconColor = warningIconColor,
iconBackgroundColor = warningIconBackgroundColor,
primaryButtonTextRes = R.string.warning_button_refresh,
primaryAction = { it.onRetryFee },
shouldShowFeeBlock = false,
shouldShowBalanceRow = false,
)
null -> ReissueCardContentAppearance(
titleRes = R.string.tangempay_reissue_card_title,
subtitleRes = R.string.tangempay_reissue_card_description,
icon = Icons.ic_arrow_refresh_32,
iconColor = infoIconColor,
iconBackgroundColor = infoIconBackgroundColor,
primaryButtonTextRes = R.string.tangempay_reissue_card_confirm,
primaryAction = { it.onConfirmClick },
shouldShowFeeBlock = true,
shouldShowBalanceRow = false,
)
}
}
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun TangemPayReissueCardContentV2Preview(
@PreviewParameter(TangemPayReissueCardUMPreviewProvider::class) state: TangemPayReissueCardUM,
) {
TangemThemePreviewRedesign {
ReissueCardSheetPreview(state = state)
}
}
@Composable
private fun ReissueCardSheetPreview(state: TangemPayReissueCardUM) {
Column(
modifier = Modifier
.fillMaxWidth()
.background(TangemTheme.colors3.bg.secondary),
) {
TangemTopBar(
type = TangemTopBarType.BottomSheet,
endContent = {
TangemButton(
iconStart = TangemIconUM.Icon(iconRes = R.drawable.ic_close_24),
onClick = state.onDismissRequest,
size = TangemButton.Size.X11,
variant = TangemButton.Variant.Material,
)
},
)
Content(state)
}
}
private class TangemPayReissueCardUMPreviewProvider : CollectionPreviewParameterProvider<TangemPayReissueCardUM>(
collection = listOf(
TangemPayReissueCardUM.stub(error = null),
TangemPayReissueCardUM.stub(
error = TangemPayReissueCardError.InsufficientFunds,
cardBalance = "$0.05",
feeAmount = "$4.25",
),
TangemPayReissueCardUM.stub(
error = TangemPayReissueCardError.InitialDataLoading,
),
),
)