Updated on 2026-08-14

This commit is contained in:
Tangem 2026-06-15 11:29:24 +02:00
parent 85cbd2669c
commit ae4029c4d5
15 changed files with 698 additions and 78 deletions

View file

@ -6,7 +6,7 @@ import androidx.compose.runtime.Immutable
internal sealed interface TangemPayDailyLimitBlockState {
data object Loading : TangemPayDailyLimitBlockState
data object Error : TangemPayDailyLimitBlockState
data class Error(val onReloadClick: () -> Unit) : TangemPayDailyLimitBlockState
data class Content(
val limit: String,

View file

@ -26,11 +26,13 @@ import com.tangem.core.ui.res.generated.icons.ic_arrow_refresh_20
import com.tangem.core.ui.test.TangemPayTestTags
import com.tangem.domain.models.StatusSource
import com.tangem.domain.models.TokenReceiveConfig
import com.tangem.domain.models.account.AccountStatus
import com.tangem.domain.models.account.PaymentAccountStatusValue
import com.tangem.domain.models.pay.TangemPayCard
import com.tangem.domain.models.pay.TangemPayCardLimitPeriod
import com.tangem.domain.models.pay.TangemPayCardState
import com.tangem.domain.models.pay.isFrozen
import com.tangem.domain.pay.flow.PaymentAccountStatusFetcher
import com.tangem.domain.pay.flow.PaymentAccountStatusSupplier
import com.tangem.domain.pay.model.TangemPayTopUpData
import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository
@ -65,6 +67,7 @@ import com.tangem.core.ui.R as CoreUiR
internal class TangemPayCardPageModel @Inject constructor(
paramsContainer: ParamsContainer,
paymentAccountStatusSupplier: PaymentAccountStatusSupplier,
private val paymentAccountStatusFetcher: PaymentAccountStatusFetcher,
override val dispatchers: CoroutineDispatcherProvider,
private val router: Router,
private val analytics: AnalyticsEventHandler,
@ -80,6 +83,7 @@ internal class TangemPayCardPageModel @Inject constructor(
private val addToWalletBannerJobHolder = JobHolder()
private val addFundsJobHolder = JobHolder()
private val frozenStateJobHolder = JobHolder()
private val reloadLimitsJobHolder = JobHolder()
private val currentStatus = MutableStateFlow(params.initialStatus)
private val initialCardId = params.initialStatus.firstCard().id
@ -111,38 +115,46 @@ internal class TangemPayCardPageModel @Inject constructor(
paymentAccountStatusSupplier.invoke(userWalletId)
.onEach { state ->
currentStatus.update { state }
uiState.update { it.copy(dailyLimitState = buildDailyLimitState(state)) }
val status = state.value
if (status is PaymentAccountStatusValue.Loaded && status.source == StatusSource.ACTUAL) {
val card = state.findCard(initialCardId, params.initialStatus) ?: return@onEach
val limit = card.limit?.actualCardLimit?.takeIf { it.period == TangemPayCardLimitPeriod.DAY }
val dailyLimitState = if (limit != null) {
TangemPayDailyLimitBlockState.Content(
limit = limit.amount.format {
val currencyCode = status.balance.fiatBalance.currency
val symbol = getJavaCurrencyByCode(currencyCode).symbol
fiat(currencyCode, symbol).optionalDecimals()
},
onChangeClick = ::onClickLimitChange,
)
} else {
TangemPayDailyLimitBlockState.Error
}
uiState.update { uiState ->
uiState.copy(
dailyLimitState = dailyLimitState,
settings = buildSettings(card),
settingsV2 = buildSettingsV2(card),
menuItems = buildMenuItems(isLastCard = status.cards.isLastCard()),
cardState = card.state,
)
}
} else {
uiState.update { it.copy(dailyLimitState = TangemPayDailyLimitBlockState.Error) }
}
}
.launchIn(modelScope)
}
private fun buildDailyLimitState(state: AccountStatus.Payment): TangemPayDailyLimitBlockState {
val status = state.value
val card = if (status is PaymentAccountStatusValue.Loaded && status.source == StatusSource.ACTUAL) {
state.findCard(initialCardId, params.initialStatus)
} else {
null
}
val limit = card?.limit?.actualCardLimit?.takeIf { it.period == TangemPayCardLimitPeriod.DAY }
return if (status is PaymentAccountStatusValue.Loaded && limit != null) {
TangemPayDailyLimitBlockState.Content(
limit = limit.amount.format {
val currencyCode = status.balance.fiatBalance.currency
val symbol = getJavaCurrencyByCode(currencyCode).symbol
fiat(currencyCode, symbol).optionalDecimals()
},
onChangeClick = ::onClickLimitChange,
)
} else {
TangemPayDailyLimitBlockState.Error(onReloadClick = ::onClickReloadLimits)
}
}
fun isRedesignEnabled(): Boolean = tangemPayFeatureToggles.isRedesignEnabled
private fun buildSettings(card: TangemPayCard): ImmutableList<TangemPayCardPageSetting> {
@ -270,6 +282,15 @@ internal class TangemPayCardPageModel @Inject constructor(
}
}
private fun onClickReloadLimits() {
if (reloadLimitsJobHolder.isActive) return
uiState.update { it.copy(dailyLimitState = TangemPayDailyLimitBlockState.Loading) }
modelScope.launch {
paymentAccountStatusFetcher(userWalletId)
uiState.update { it.copy(dailyLimitState = buildDailyLimitState(currentStatus.value)) }
}.saveIn(reloadLimitsJobHolder)
}
private fun onClickLimitChange() {
analytics.send(TangemPayAnalyticsEvents.LimitChangeClicked())
router.push(TangemPayCardDetailsInnerRoute.LimitSetup)

View file

@ -109,7 +109,7 @@ private fun LazyListScope.cardState(state: TangemPayCardPageUM) {
cardPageItem(key = "Limit") {
TangemPayDailyLimitBlock(state = state.dailyLimitState)
}
if (state.dailyLimitState == TangemPayDailyLimitBlockState.Error) {
if (state.dailyLimitState is TangemPayDailyLimitBlockState.Error) {
cardPageItem(key = "LimitError") {
TangemPayDailyLimitErrorBlock()
}

View file

@ -24,14 +24,18 @@ import com.tangem.core.ui.components.*
import com.tangem.core.ui.components.buttons.common.TangemButtonSize
import com.tangem.core.ui.components.notifications.Notification
import com.tangem.core.ui.components.notifications.NotificationConfig
import com.tangem.core.ui.ds.image.TangemIconUM
import com.tangem.core.ui.ds.row.TangemRowContainer
import com.tangem.core.ui.ds.row.TangemRowLayoutId
import com.tangem.core.ui.ds2.button.TangemButton
import com.tangem.core.ui.ds2.shimmers.RectangleShimmer
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.*
import com.tangem.core.ui.res.generated.icons.Icons
import com.tangem.core.ui.res.generated.icons.ic_arrow_refresh_32
import com.tangem.features.tangempay.details.impl.R
import com.tangem.features.tangempay.entity.TangemPayDailyLimitBlockState
@ -96,7 +100,7 @@ private fun CurrentLimitBlockV1(state: TangemPayDailyLimitBlockState) {
color = TangemTheme.colors.text.tertiary,
)
when (state) {
TangemPayDailyLimitBlockState.Error,
is TangemPayDailyLimitBlockState.Error,
is TangemPayDailyLimitBlockState.Content,
-> Text(
text = if (state is TangemPayDailyLimitBlockState.Content) state.limit else "",
@ -152,8 +156,8 @@ private fun CurrentLimitBlockV2(state: TangemPayDailyLimitBlockState, modifier:
state = state,
)
if (state is TangemPayDailyLimitBlockState.Content) {
CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides 0.dp) {
when (state) {
is TangemPayDailyLimitBlockState.Content -> {
TangemButton(
modifier = Modifier
.padding(start = TangemTheme.dimens2.x3)
@ -164,6 +168,26 @@ private fun CurrentLimitBlockV2(state: TangemPayDailyLimitBlockState, modifier:
size = TangemButton.Size.X10,
)
}
is TangemPayDailyLimitBlockState.Error -> {
TangemButton(
modifier = Modifier
.padding(start = TangemTheme.dimens2.x3)
.layoutId(TangemRowLayoutId.TAIL),
iconStart = TangemIconUM.Icon(imageVector = Icons.ic_arrow_refresh_32),
variant = TangemButton.Variant.Secondary,
onClick = state.onReloadClick,
size = TangemButton.Size.X10,
)
}
TangemPayDailyLimitBlockState.Loading -> {
RectangleShimmer(
modifier = Modifier
.padding(start = TangemTheme.dimens2.x3)
.layoutId(TangemRowLayoutId.TAIL)
.size(width = 64.dp, height = 40.dp),
radius = 20.dp,
)
}
}
}
}
@ -178,7 +202,7 @@ private fun LimitHeadIcon(state: TangemPayDailyLimitBlockState, modifier: Modifi
is TangemPayDailyLimitBlockState.Content,
TangemPayDailyLimitBlockState.Loading,
-> TangemTheme.colors3.bg.status.infoSubtle
TangemPayDailyLimitBlockState.Error -> TangemTheme.colors3.bg.status.warningSubtle
is TangemPayDailyLimitBlockState.Error -> TangemTheme.colors3.bg.status.warningSubtle
},
shape = CircleShape,
),
@ -194,7 +218,7 @@ private fun LimitHeadIcon(state: TangemPayDailyLimitBlockState, modifier: Modifi
tint = TangemTheme.colors3.icon.brand,
)
}
TangemPayDailyLimitBlockState.Error -> {
is TangemPayDailyLimitBlockState.Error -> {
Icon(
imageVector = ImageVector.vectorResource(R.drawable.ic_warning_20),
contentDescription = null,
@ -218,7 +242,7 @@ private fun TitleLimit(state: TangemPayDailyLimitBlockState, modifier: Modifier
color = TangemTheme.colors3.text.secondary,
)
}
TangemPayDailyLimitBlockState.Error -> {
is TangemPayDailyLimitBlockState.Error -> {
Text(
modifier = modifier,
text = stringResourceSafe(R.string.tangempay_card_page_daily_limit_error_title),
@ -232,7 +256,7 @@ private fun TitleLimit(state: TangemPayDailyLimitBlockState, modifier: Modifier
@Composable
private fun SubtitleLimit(state: TangemPayDailyLimitBlockState, modifier: Modifier = Modifier) {
when (state) {
TangemPayDailyLimitBlockState.Error -> {
is TangemPayDailyLimitBlockState.Error -> {
Text(
modifier = modifier,
text = stringResourceSafe(R.string.tangempay_card_page_daily_limit_error_subtitle),
@ -286,7 +310,7 @@ private fun Preview() {
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
TangemPayDailyLimitBlock(state = TangemPayDailyLimitBlockState.Content.stub())
TangemPayDailyLimitBlock(state = TangemPayDailyLimitBlockState.Error)
TangemPayDailyLimitBlock(state = TangemPayDailyLimitBlockState.Error(onReloadClick = {}))
TangemPayDailyLimitBlock(state = TangemPayDailyLimitBlockState.Loading)
TangemPayDailyLimitErrorBlock()
}
@ -306,7 +330,7 @@ private fun PreviewV2() {
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
TangemPayDailyLimitBlock(state = TangemPayDailyLimitBlockState.Content.stub())
TangemPayDailyLimitBlock(state = TangemPayDailyLimitBlockState.Error)
TangemPayDailyLimitBlock(state = TangemPayDailyLimitBlockState.Error(onReloadClick = {}))
TangemPayDailyLimitBlock(state = TangemPayDailyLimitBlockState.Loading)
TangemPayDailyLimitErrorBlock()
}