Updated on 2026-08-14

This commit is contained in:
Tangem 2025-11-06 15:31:30 +05:00
parent e037233aa5
commit 804e4eca23
31 changed files with 635 additions and 147 deletions

View file

@ -4,49 +4,80 @@ import com.tangem.core.ui.components.buttons.actions.ActionButtonConfig
import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig
import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig.ShowRefreshState
import com.tangem.core.ui.components.dropdownmenu.TangemDropdownMenuItem
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.themedColor
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.tangempay.details.impl.R
import kotlinx.collections.immutable.persistentListOf
@Suppress("LongParameterList")
internal class TangemPayDetailsStateFactory(
private val onBack: () -> Unit,
private val onRefresh: (ShowRefreshState) -> Unit,
private val onAddFunds: () -> Unit,
private val onClickChangePin: () -> Unit,
private val isCardFrozen: Boolean,
private val onClickFreezeCard: () -> Unit,
private val onClickUnfreezeCard: () -> Unit,
) {
fun getInitialState() = TangemPayDetailsUM(
topBarConfig = TangemPayDetailsTopBarConfig(
onBackClick = onBack,
items = persistentListOf(
TangemDropdownMenuItem(
title = TextReference.Res(R.string.tangempay_card_details_change_pin),
fun getInitialState(): TangemPayDetailsUM {
val cardFrozenStateItem = if (isCardFrozen) {
TangemPayDetailsTopBarMenuItem(
type = TangemPayDetailsTopBarMenuItemType.UnfreezeCard,
dropdownItem = TangemDropdownMenuItem(
title = resourceReference(R.string.tangempay_card_details_unfreeze_card),
textColor = themedColor { TangemTheme.colors.text.primary1 },
onClick = onClickChangePin,
onClick = onClickUnfreezeCard,
),
TangemDropdownMenuItem(
title = TextReference.Res(R.string.tangempay_card_details_freeze_card),
)
} else {
TangemPayDetailsTopBarMenuItem(
type = TangemPayDetailsTopBarMenuItemType.FreezeCard,
dropdownItem = TangemDropdownMenuItem(
title = resourceReference(R.string.tangempay_card_details_freeze_card),
textColor = themedColor { TangemTheme.colors.text.primary1 },
onClick = onClickFreezeCard,
),
),
),
pullToRefreshConfig = PullToRefreshConfig(isRefreshing = false, onRefresh = onRefresh),
balanceBlockState = TangemPayDetailsBalanceBlockState.Loading(
actionButtons = persistentListOf(
ActionButtonConfig(
text = resourceReference(id = R.string.tangempay_card_details_add_funds),
iconResId = R.drawable.ic_arrow_down_24,
onClick = onAddFunds,
)
}
return TangemPayDetailsUM(
topBarConfig = TangemPayDetailsTopBarConfig(
onBackClick = onBack,
items = persistentListOf(
TangemPayDetailsTopBarMenuItem(
type = TangemPayDetailsTopBarMenuItemType.ChangePin,
dropdownItem = TangemDropdownMenuItem(
title = resourceReference(R.string.tangempay_card_details_change_pin),
textColor = themedColor { TangemTheme.colors.text.primary1 },
onClick = onClickChangePin,
),
),
cardFrozenStateItem,
),
),
),
addToWalletBlockState = null,
isBalanceHidden = false,
addFundsEnabled = true,
)
pullToRefreshConfig = PullToRefreshConfig(
isRefreshing = false,
onRefresh = onRefresh,
),
balanceBlockState = TangemPayDetailsBalanceBlockState.Loading(
actionButtons = persistentListOf(
ActionButtonConfig(
text = resourceReference(id = R.string.tangempay_card_details_add_funds),
iconResId = R.drawable.ic_arrow_down_24,
onClick = onAddFunds,
),
),
frozenState = if (isCardFrozen) {
CardFrozenState.Frozen(onClickUnfreezeCard)
} else {
CardFrozenState.Unfrozen
},
),
addToWalletBlockState = null,
isBalanceHidden = false,
addFundsEnabled = true,
)
}
}

View file

@ -5,5 +5,16 @@ import kotlinx.collections.immutable.ImmutableList
internal data class TangemPayDetailsTopBarConfig(
val onBackClick: () -> Unit,
val items: ImmutableList<TangemDropdownMenuItem>?,
)
val items: ImmutableList<TangemPayDetailsTopBarMenuItem>?,
)
internal data class TangemPayDetailsTopBarMenuItem(
val type: TangemPayDetailsTopBarMenuItemType,
val dropdownItem: TangemDropdownMenuItem,
)
internal enum class TangemPayDetailsTopBarMenuItemType {
ChangePin,
FreezeCard,
UnfreezeCard,
}

View file

@ -15,9 +15,9 @@ internal data class TangemPayDetailsUM(
)
internal data class TangemPayCardDetailsUM(
val number: String = "",
val expiry: String = "",
val cvv: String = "",
val number: String,
val expiry: String,
val cvv: String,
val buttonText: TextReference = TextReference.EMPTY,
val onClick: () -> Unit = {},
val onCopy: (String) -> Unit = {},
@ -28,13 +28,16 @@ internal data class TangemPayCardDetailsUM(
internal sealed class TangemPayDetailsBalanceBlockState {
abstract val actionButtons: ImmutableList<ActionButtonConfig>
abstract val frozenState: CardFrozenState
data class Loading(
override val actionButtons: ImmutableList<ActionButtonConfig>,
override val frozenState: CardFrozenState,
) : TangemPayDetailsBalanceBlockState()
data class Content(
override val actionButtons: ImmutableList<ActionButtonConfig>,
override val frozenState: CardFrozenState,
val cryptoBalance: String,
val fiatBalance: String,
val isBalanceFlickering: Boolean,
@ -42,9 +45,16 @@ internal sealed class TangemPayDetailsBalanceBlockState {
data class Error(
override val actionButtons: ImmutableList<ActionButtonConfig>,
override val frozenState: CardFrozenState,
) : TangemPayDetailsBalanceBlockState()
}
sealed class CardFrozenState {
data object Pending : CardFrozenState()
data class Frozen(val onUnfreeze: () -> Unit) : CardFrozenState()
data object Unfrozen : CardFrozenState()
}
internal data class AddToWalletBlockState(
val onClick: () -> Unit,
val onClickClose: () -> Unit,

View file

@ -8,7 +8,7 @@ import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.ui.clipboard.ClipboardManager
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.message.SnackbarMessage
import com.tangem.domain.pay.repository.CardDetailsRepository
import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository
import com.tangem.features.tangempay.components.cardDetails.TangemPayCardDetailsBlockComponent
import com.tangem.features.tangempay.details.impl.R
import com.tangem.features.tangempay.entity.TangemPayCardDetailsBlockStateFactory
@ -33,7 +33,7 @@ import javax.inject.Inject
internal class TangemPayCardDetailsBlockModel @Inject constructor(
paramsContainer: ParamsContainer,
override val dispatchers: CoroutineDispatcherProvider,
private val cardDetailsRepository: CardDetailsRepository,
private val cardDetailsRepository: TangemPayCardDetailsRepository,
private val clipboardManager: ClipboardManager,
private val uiMessageSender: UiMessageSender,
private val cardDetailsEventListener: CardDetailsEventListener,

View file

@ -5,7 +5,7 @@ import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.navigation.Router
import com.tangem.domain.pay.model.SetPinResult
import com.tangem.domain.pay.repository.CardDetailsRepository
import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository
import com.tangem.features.tangempay.entity.TangemPayChangePinUM
import kotlinx.coroutines.flow.*
import com.tangem.features.tangempay.model.transformers.PinCodeChangeTransformer
@ -22,7 +22,7 @@ import javax.inject.Inject
internal class TangemPayChangePinModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider,
private val router: Router,
private val cardDetailsRepository: CardDetailsRepository,
private val cardDetailsRepository: TangemPayCardDetailsRepository,
) : Model() {
val uiState: StateFlow<TangemPayChangePinUM>

View file

@ -12,24 +12,25 @@ import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.decompose.navigation.Router
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig.ShowRefreshState
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.message.SnackbarMessage
import com.tangem.domain.models.TokenReceiveConfig
import com.tangem.domain.pay.TangemPayTopUpData
import com.tangem.domain.pay.model.TangemPayCardBalance
import com.tangem.domain.pay.repository.CardDetailsRepository
import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository
import com.tangem.domain.visa.model.TangemPayTxHistoryItem
import com.tangem.features.tangempay.components.AddFundsListener
import com.tangem.features.tangempay.components.TangemPayDetailsContainerComponent
import com.tangem.features.tangempay.details.impl.R
import com.tangem.features.tangempay.entity.TangemPayDetailsErrorType
import com.tangem.features.tangempay.entity.TangemPayDetailsNavigation
import com.tangem.features.tangempay.entity.TangemPayDetailsStateFactory
import com.tangem.features.tangempay.entity.TangemPayDetailsUM
import com.tangem.features.tangempay.model.listener.CardDetailsEvent
import com.tangem.features.tangempay.model.listener.CardDetailsEventListener
import com.tangem.features.tangempay.model.transformers.DetailsAddToWalletBannerTransformer
import com.tangem.features.tangempay.model.transformers.DetailsBalanceTransformer
import com.tangem.features.tangempay.model.transformers.TangemPayDetailsRefreshTransformer
import com.tangem.features.tangempay.model.transformers.*
import com.tangem.features.tangempay.navigation.TangemPayDetailsInnerRoute
import com.tangem.features.tangempay.utils.TangemPayErrorMessageFactory
import com.tangem.features.tangempay.utils.TangemPayMessagesFactory
import com.tangem.features.tangempay.utils.TangemPayTxHistoryUiActions
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.JobHolder
@ -48,7 +49,7 @@ internal class TangemPayDetailsModel @Inject constructor(
paramsContainer: ParamsContainer,
override val dispatchers: CoroutineDispatcherProvider,
private val router: Router,
private val cardDetailsRepository: CardDetailsRepository,
private val cardDetailsRepository: TangemPayCardDetailsRepository,
private val uiMessageSender: UiMessageSender,
private val cardDetailsEventListener: CardDetailsEventListener,
) : Model(), TangemPayTxHistoryUiActions, AddFundsListener {
@ -60,7 +61,9 @@ internal class TangemPayDetailsModel @Inject constructor(
onRefresh = ::onRefreshSwipe,
onAddFunds = ::onClickAddFunds,
onClickChangePin = ::onClickChangePin,
isCardFrozen = params.config.isCardFrozen,
onClickFreezeCard = ::onClickFreezeCard,
onClickUnfreezeCard = ::onClickUnfreezeCard,
)
val uiState: StateFlow<TangemPayDetailsUM>
@ -84,7 +87,65 @@ internal class TangemPayDetailsModel @Inject constructor(
}
private fun onClickFreezeCard() {
// TODO [REDACTED_JIRA]
uiMessageSender.send(TangemPayMessagesFactory.createFreezeCardMessage(onFreezeClicked = ::freezeCard))
}
private fun onClickUnfreezeCard() {
uiMessageSender.send(TangemPayMessagesFactory.createUnfreezeCardMessage(onUnfreezeClicked = ::unfreezeCard))
}
private fun freezeCard() {
modelScope.launch {
uiState.update(TangemPayFreezeUnfreezeProcessTransformer)
cardDetailsRepository.freezeCard(cardId = params.config.cardId)
.onLeft {
uiMessageSender.send(SnackbarMessage(resourceReference(R.string.tangem_pay_freeze_card_failed)))
uiState.update(
TangemPayFreezeUnfreezeStateTransformer(
frozen = false,
onFreezeClick = ::onClickFreezeCard,
onUnfreezeClick = ::onClickUnfreezeCard,
),
)
}
.onRight {
uiMessageSender.send(SnackbarMessage(resourceReference(R.string.tangem_pay_freeze_card_success)))
uiState.update(
TangemPayFreezeUnfreezeStateTransformer(
frozen = true,
onFreezeClick = ::onClickFreezeCard,
onUnfreezeClick = ::onClickUnfreezeCard,
),
)
}
}
}
private fun unfreezeCard() {
modelScope.launch {
uiState.update(TangemPayFreezeUnfreezeProcessTransformer)
cardDetailsRepository.unfreezeCard(cardId = params.config.cardId)
.onLeft {
uiMessageSender.send(SnackbarMessage(resourceReference(R.string.tangem_pay_unfreeze_card_failed)))
uiState.update(
TangemPayFreezeUnfreezeStateTransformer(
frozen = true,
onFreezeClick = ::onClickFreezeCard,
onUnfreezeClick = ::onClickUnfreezeCard,
),
)
}
.onRight {
uiMessageSender.send(SnackbarMessage(resourceReference(R.string.tangem_pay_unfreeze_card_success)))
uiState.update(
TangemPayFreezeUnfreezeStateTransformer(
frozen = false,
onFreezeClick = ::onClickFreezeCard,
onUnfreezeClick = ::onClickUnfreezeCard,
),
)
}
}
}
private fun onClickAddFunds() {
@ -188,6 +249,6 @@ internal class TangemPayDetailsModel @Inject constructor(
}
private fun showBottomSheetError(type: TangemPayDetailsErrorType) {
uiMessageSender.send(message = TangemPayErrorMessageFactory.createErrorMessage(type = type))
uiMessageSender.send(message = TangemPayMessagesFactory.createErrorMessage(type = type))
}
}

View file

@ -18,7 +18,10 @@ internal class DetailsBalanceTransformer(
override fun transform(prevState: TangemPayDetailsUM): TangemPayDetailsUM {
val balance = when (balance) {
is Either.Left<UniversalError> -> {
TangemPayDetailsBalanceBlockState.Error(persistentListOf())
TangemPayDetailsBalanceBlockState.Error(
actionButtons = persistentListOf(),
frozenState = prevState.balanceBlockState.frozenState,
)
}
is Either.Right<TangemPayCardBalance> -> {
TangemPayDetailsBalanceBlockState.Content(
@ -27,6 +30,7 @@ internal class DetailsBalanceTransformer(
// TODO [REDACTED_TASK_KEY]: Add crypto balance when the BFF is ready
cryptoBalance = "",
actionButtons = prevState.balanceBlockState.actionButtons,
frozenState = prevState.balanceBlockState.frozenState,
)
}
}

View file

@ -7,7 +7,7 @@ import com.tangem.features.tangempay.details.impl.R
import com.tangem.features.tangempay.entity.TangemPayAddFundsItemUM
import com.tangem.features.tangempay.entity.TangemPayAddFundsUM
import com.tangem.features.tangempay.entity.TangemPayDetailsErrorType
import com.tangem.features.tangempay.utils.TangemPayErrorMessageFactory
import com.tangem.features.tangempay.utils.TangemPayMessagesFactory
import com.tangem.utils.converter.Converter
import kotlinx.collections.immutable.persistentListOf
@ -20,7 +20,7 @@ internal class TangemPayAddFundsUMConverter(
TangemPayAddFundsUM(
items = persistentListOf(),
dismiss = listener::onDismissAddFunds,
errorMessage = TangemPayErrorMessageFactory.createErrorMessage(
errorMessage = TangemPayMessagesFactory.createErrorMessage(
type = TangemPayDetailsErrorType.Receive,
).messageBottomSheetUMV2,
)

View file

@ -0,0 +1,21 @@
package com.tangem.features.tangempay.model.transformers
import com.tangem.features.tangempay.entity.CardFrozenState
import com.tangem.features.tangempay.entity.TangemPayDetailsBalanceBlockState
import com.tangem.features.tangempay.entity.TangemPayDetailsUM
import com.tangem.utils.transformer.Transformer
internal object TangemPayFreezeUnfreezeProcessTransformer : Transformer<TangemPayDetailsUM> {
override fun transform(prevState: TangemPayDetailsUM): TangemPayDetailsUM {
val newBalanceBlockState = when (val balanceState = prevState.balanceBlockState) {
is TangemPayDetailsBalanceBlockState.Error -> balanceState.copy(frozenState = CardFrozenState.Pending)
is TangemPayDetailsBalanceBlockState.Loading -> balanceState.copy(frozenState = CardFrozenState.Pending)
is TangemPayDetailsBalanceBlockState.Content -> balanceState.copy(
isBalanceFlickering = true,
frozenState = CardFrozenState.Pending,
)
}
return prevState.copy(balanceBlockState = newBalanceBlockState)
}
}

View file

@ -0,0 +1,73 @@
package com.tangem.features.tangempay.model.transformers
import com.tangem.core.ui.components.dropdownmenu.TangemDropdownMenuItem
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.themedColor
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.tangempay.details.impl.R
import com.tangem.features.tangempay.entity.*
import com.tangem.utils.transformer.Transformer
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toPersistentList
internal class TangemPayFreezeUnfreezeStateTransformer(
private val frozen: Boolean,
private val onFreezeClick: () -> Unit,
private val onUnfreezeClick: () -> Unit,
) : Transformer<TangemPayDetailsUM> {
private val cardFrozenState = if (frozen) CardFrozenState.Frozen(onUnfreezeClick) else CardFrozenState.Unfrozen
private val removeType =
if (frozen) TangemPayDetailsTopBarMenuItemType.FreezeCard else TangemPayDetailsTopBarMenuItemType.UnfreezeCard
private val addType =
if (frozen) TangemPayDetailsTopBarMenuItemType.UnfreezeCard else TangemPayDetailsTopBarMenuItemType.FreezeCard
override fun transform(prevState: TangemPayDetailsUM): TangemPayDetailsUM {
val dropdownMenuItems = createUpdatedMenuItems(prevState.topBarConfig.items)
val newBalanceBlockState = createUpdatedBalanceState(prevState.balanceBlockState)
return prevState.copy(
topBarConfig = prevState.topBarConfig.copy(items = dropdownMenuItems),
balanceBlockState = newBalanceBlockState,
)
}
private fun createUpdatedMenuItems(
items: ImmutableList<TangemPayDetailsTopBarMenuItem>?,
): ImmutableList<TangemPayDetailsTopBarMenuItem>? {
return items
?.filter { it.type != removeType }
?.plus(createMenuItemToAdd())
?.toPersistentList()
}
private fun createMenuItemToAdd(): TangemPayDetailsTopBarMenuItem {
return TangemPayDetailsTopBarMenuItem(
type = addType,
dropdownItem = TangemDropdownMenuItem(
title = resourceReference(
id = if (frozen) {
R.string.tangempay_card_details_unfreeze_card
} else {
R.string.tangempay_card_details_freeze_card
},
),
textColor = themedColor { TangemTheme.colors.text.primary1 },
onClick = if (frozen) onUnfreezeClick else onFreezeClick,
),
)
}
private fun createUpdatedBalanceState(
balanceState: TangemPayDetailsBalanceBlockState,
): TangemPayDetailsBalanceBlockState {
return when (balanceState) {
is TangemPayDetailsBalanceBlockState.Error -> balanceState.copy(frozenState = cardFrozenState)
is TangemPayDetailsBalanceBlockState.Loading -> balanceState.copy(frozenState = cardFrozenState)
is TangemPayDetailsBalanceBlockState.Content -> balanceState.copy(
isBalanceFlickering = false,
frozenState = cardFrozenState,
)
}
}
}

View file

@ -16,6 +16,7 @@ import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEach
import com.tangem.core.ui.components.SpacerH16
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
@ -70,7 +71,7 @@ internal fun TangemPayAddFundsContent(state: TangemPayAddFundsUM) {
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.subtitle2,
)
state.items.forEach {
state.items.fastForEach {
key(it.title) {
TangemPayTopUpItem(state = it)
}

View file

@ -9,7 +9,9 @@ import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
@ -20,6 +22,7 @@ import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameter
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEach
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.ui.components.PrimaryButton
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.buttons.HorizontalActionChips
import com.tangem.core.ui.components.buttons.actions.ActionButtonConfig
@ -27,9 +30,12 @@ import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfi
import com.tangem.core.ui.components.containers.pullToRefresh.TangemPullToRefreshContainer
import com.tangem.core.ui.components.dropdownmenu.TangemDropdownItem
import com.tangem.core.ui.components.dropdownmenu.TangemDropdownMenu
import com.tangem.core.ui.components.snackbar.TangemSnackbarHost
import com.tangem.core.ui.components.text.applyBladeBrush
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.orMaskWithStars
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemColorPalette
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.TokenDetailsTopBarTestTags
@ -48,19 +54,12 @@ internal fun TangemPayDetailsScreen(
txHistoryComponent: TangemPayTxHistoryComponent,
cardDetailsBlockComponent: TangemPayCardDetailsBlockComponent,
modifier: Modifier = Modifier,
snackbarHostState: SnackbarHostState = remember { SnackbarHostState() },
) {
Scaffold(
modifier = modifier,
topBar = { TangemPayDetailsTopAppBar(config = state.topBarConfig) },
contentWindowInsets = ScaffoldDefaults.contentWindowInsets.exclude(WindowInsets.navigationBars),
containerColor = TangemTheme.colors.background.secondary,
snackbarHost = {
TangemSnackbarHost(
modifier = Modifier.padding(start = 16.dp, end = 16.dp, bottom = 16.dp),
hostState = snackbarHostState,
)
},
) { scaffoldPaddings ->
val listState = rememberLazyListState()
val bottomBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getBottom(this).toDp() }
@ -106,6 +105,20 @@ internal fun TangemPayDetailsScreen(
item(TangemPayCardDetailsUM::class.java) {
TangemPayCardDetailsBlockItem(component = cardDetailsBlockComponent, state = cardDetailsState)
}
when (val cardFrozenState = state.balanceBlockState.frozenState) {
is CardFrozenState.Frozen -> item(CardFrozenState.Frozen::class.java) {
PrimaryButton(
modifier = Modifier
.animateItem()
.padding(horizontal = 16.dp)
.padding(top = 12.dp)
.fillMaxWidth(),
text = stringResourceSafe(R.string.tangempay_card_details_unfreeze_card),
onClick = cardFrozenState.onUnfreeze,
)
}
else -> Unit
}
with(txHistoryComponent) { txHistoryContent(listState = listState, state = txHistoryState) }
}
}
@ -127,14 +140,22 @@ internal fun TangemPayDetailsBalanceBlock(
)
.padding(vertical = 12.dp),
) {
Text(
modifier = Modifier.padding(start = 12.dp),
text = stringResourceSafe(R.string.tangempay_title),
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.subtitle2,
)
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 12.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = stringResourceSafe(R.string.tangempay_title),
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.subtitle2,
)
TangemPayCardStatus(state = state.frozenState)
}
FiatBalance(
modifier = Modifier.padding(start = 12.dp, top = 4.dp),
modifier = Modifier.padding(start = 12.dp, top = 8.dp),
state = state,
isBalanceHidden = isBalanceHidden,
)
@ -155,6 +176,36 @@ internal fun TangemPayDetailsBalanceBlock(
}
}
@Composable
private fun TangemPayCardStatus(state: CardFrozenState, modifier: Modifier = Modifier) {
when (state) {
is CardFrozenState.Unfrozen -> Unit
is CardFrozenState.Pending -> CircularProgressIndicator(
modifier = modifier.size(18.dp),
trackColor = Color.Transparent,
color = TangemColorPalette.Azure,
strokeWidth = 2.dp,
)
is CardFrozenState.Frozen -> Row(
modifier = modifier,
horizontalArrangement = Arrangement.spacedBy(4.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
painter = painterResource(id = R.drawable.ic_snow_24),
tint = TangemTheme.colors.text.disabled,
contentDescription = null,
modifier = Modifier.size(16.dp),
)
Text(
text = stringResourceSafe(R.string.tangem_pay_card_frozen),
color = TangemTheme.colors.text.disabled,
style = TangemTheme.typography.body2,
)
}
}
}
@Composable
private fun FiatBalance(
state: TangemPayDetailsBalanceBlockState,
@ -173,18 +224,30 @@ private fun FiatBalance(
text = state.fiatBalance.orMaskWithStars(isBalanceHidden),
style = TangemTheme.typography.h2.applyBladeBrush(
isEnabled = state.isBalanceFlickering,
textColor = TangemTheme.colors.text.primary1,
textColor = getTextColor(
isCardFrozen = state.frozenState is CardFrozenState.Frozen,
defaultColor = TangemTheme.colors.text.primary1,
),
),
)
is TangemPayDetailsBalanceBlockState.Error -> Text(
modifier = modifier,
text = DASH_SIGN.orMaskWithStars(isBalanceHidden),
style = TangemTheme.typography.h2,
color = TangemTheme.colors.text.primary1,
color = getTextColor(
isCardFrozen = state.frozenState is CardFrozenState.Frozen,
defaultColor = TangemTheme.colors.text.primary1,
),
)
}
}
@ReadOnlyComposable
@Composable
private fun getTextColor(isCardFrozen: Boolean, defaultColor: Color): Color {
return if (isCardFrozen) TangemTheme.colors.text.tertiary else defaultColor
}
@Suppress("UnusedPrivateMember")
@Composable
private fun CryptoBalance(
@ -252,7 +315,7 @@ private fun TangemPayDetailsTopAppBar(config: TangemPayDetailsTopBarConfig, modi
content = {
config.items?.fastForEach {
TangemDropdownItem(
item = it,
item = it.dropdownItem,
dismissParent = { showDropdownMenu = false },
)
}
@ -310,6 +373,7 @@ private class TangemPayDetailsUMProvider : CollectionPreviewParameterProvider<Ta
cryptoBalance = "1234.56 USDT",
fiatBalance = "$1234.56",
isBalanceFlickering = false,
frozenState = CardFrozenState.Frozen(onUnfreeze = {}),
),
addToWalletBlockState = AddToWalletBlockState({}, {}),
isBalanceHidden = false,
@ -318,7 +382,10 @@ private class TangemPayDetailsUMProvider : CollectionPreviewParameterProvider<Ta
TangemPayDetailsUM(
topBarConfig = TangemPayDetailsTopBarConfig(onBackClick = {}, items = null),
pullToRefreshConfig = PullToRefreshConfig(isRefreshing = false, onRefresh = {}),
balanceBlockState = TangemPayDetailsBalanceBlockState.Loading(actionButtons = persistentListOf()),
balanceBlockState = TangemPayDetailsBalanceBlockState.Loading(
actionButtons = persistentListOf(),
frozenState = CardFrozenState.Pending,
),
addToWalletBlockState = AddToWalletBlockState({}, {}),
isBalanceHidden = false,
addFundsEnabled = true,

View file

@ -1,30 +0,0 @@
package com.tangem.features.tangempay.utils
import com.tangem.core.ui.R
import com.tangem.core.ui.components.bottomsheets.message.*
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.message.BottomSheetMessageV2
import com.tangem.core.ui.message.bottomSheetMessage
import com.tangem.features.tangempay.entity.TangemPayDetailsErrorType
internal object TangemPayErrorMessageFactory {
fun createErrorMessage(type: TangemPayDetailsErrorType): BottomSheetMessageV2 {
return when (type) {
TangemPayDetailsErrorType.Receive -> bottomSheetMessage {
infoBlock {
icon(R.drawable.img_attention_20) {
backgroundType = MessageBottomSheetUMV2.Icon.BackgroundType.Attention
}
title = TextReference.Res(R.string.tangempay_card_details_receive_error_title)
body = TextReference.Res(R.string.tangempay_card_details_receive_error_description)
}
primaryButton {
text = resourceReference(R.string.common_got_it)
onClick { closeBs() }
}
}
}
}
}

View file

@ -0,0 +1,70 @@
package com.tangem.features.tangempay.utils
import com.tangem.core.ui.R
import com.tangem.core.ui.components.bottomsheets.message.*
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.message.BottomSheetMessageV2
import com.tangem.core.ui.message.bottomSheetMessage
import com.tangem.features.tangempay.entity.TangemPayDetailsErrorType
internal object TangemPayMessagesFactory {
fun createErrorMessage(type: TangemPayDetailsErrorType): BottomSheetMessageV2 {
return when (type) {
TangemPayDetailsErrorType.Receive -> bottomSheetMessage {
infoBlock {
icon(R.drawable.img_attention_20) {
backgroundType = MessageBottomSheetUMV2.Icon.BackgroundType.Attention
}
title = TextReference.Res(R.string.tangempay_card_details_receive_error_title)
body = TextReference.Res(R.string.tangempay_card_details_receive_error_description)
}
primaryButton {
text = resourceReference(R.string.common_got_it)
onClick { closeBs() }
}
}
}
}
fun createFreezeCardMessage(onFreezeClicked: () -> Unit): BottomSheetMessageV2 {
return bottomSheetMessage {
infoBlock {
icon(R.drawable.ic_snow_24) {
type = MessageBottomSheetUMV2.Icon.Type.Accent
backgroundType = MessageBottomSheetUMV2.Icon.BackgroundType.Accent
}
title = TextReference.Res(R.string.tangem_pay_freeze_card_alert_title)
body = TextReference.Res(R.string.tangem_pay_freeze_card_alert_body)
}
primaryButton {
text = resourceReference(R.string.tangem_pay_freeze_card_freeze)
onClick {
onFreezeClicked()
closeBs()
}
}
}
}
fun createUnfreezeCardMessage(onUnfreezeClicked: () -> Unit): BottomSheetMessageV2 {
return bottomSheetMessage {
infoBlock {
icon(R.drawable.ic_snow_24) {
type = MessageBottomSheetUMV2.Icon.Type.Accent
backgroundType = MessageBottomSheetUMV2.Icon.BackgroundType.Accent
}
title = TextReference.Res(R.string.tangem_pay_unfreeze_card_alert_title)
body = TextReference.Res(R.string.tangem_pay_unfreeze_card_alert_body)
}
primaryButton {
text = resourceReference(R.string.tangempay_card_details_unfreeze_card)
onClick {
onUnfreezeClicked()
closeBs()
}
}
}
}
}

View file

@ -5,6 +5,7 @@ import com.tangem.core.ui.format.bigdecimal.fiat
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.domain.pay.TangemPayDetailsConfig
import com.tangem.domain.pay.model.CustomerInfo.CardInfo
import com.tangem.domain.pay.model.CustomerInfo.ProductInstance
import com.tangem.domain.pay.model.MainScreenCustomerInfo
import com.tangem.domain.pay.model.OrderStatus.CANCELED
import com.tangem.domain.pay.model.OrderStatus.UNKNOWN
@ -35,29 +36,36 @@ internal class TangemPayInitialStateTransformer(
private fun createInitialState(): TangemPayState {
val cardInfo = value?.info?.cardInfo
val productInstance = value?.info?.productInstance
return when {
value == null -> TangemPayState.Empty
!value.info.isKycApproved -> createKycInProgressState(onClickKyc)
cardInfo != null -> getCardInfoState(cardInfo)
cardInfo != null && productInstance != null -> getCardInfoState(cardInfo, productInstance)
value.orderStatus == UNKNOWN || value.orderStatus == CANCELED -> createIssueAvailableState(onClickIssue)
else -> createIssueProgressState()
}
}
private fun getCardInfoState(cardInfo: CardInfo): TangemPayState = TangemPayState.Card(
lastFourDigits = TextReference.Str("*${cardInfo.lastFourDigits}"),
balanceText = TextReference.Str(getBalanceText(cardInfo)),
onClick = {
openDetails(
TangemPayDetailsConfig(
customerWalletAddress = cardInfo.customerWalletAddress,
cardNumberEnd = cardInfo.lastFourDigits,
chainId = POLYGON_CHAIN_ID,
depositAddress = cardInfo.depositAddress,
),
)
},
)
private fun getCardInfoState(cardInfo: CardInfo, productInstance: ProductInstance): TangemPayState =
TangemPayState.Card(
lastFourDigits = TextReference.Str("*${cardInfo.lastFourDigits}"),
balanceText = TextReference.Str(getBalanceText(cardInfo)),
onClick = {
openDetails(
TangemPayDetailsConfig(
cardId = productInstance.cardId,
isCardFrozen = when (productInstance.status) {
ProductInstance.Status.ACTIVE -> false
ProductInstance.Status.INACTIVE -> true
},
customerWalletAddress = cardInfo.customerWalletAddress,
cardNumberEnd = cardInfo.lastFourDigits,
chainId = POLYGON_CHAIN_ID,
depositAddress = cardInfo.depositAddress,
),
)
},
)
private fun getBalanceText(cardInfo: CardInfo): String {
val currency = Currency.getInstance(cardInfo.currencyCode)