Updated on 2026-08-14

This commit is contained in:
Tangem 2025-11-04 14:09:34 +04:00
parent 507b608807
commit 768422ea7c
34 changed files with 661 additions and 80 deletions

View file

@ -4,6 +4,7 @@ import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.wallet.UserWalletId
import java.math.BigDecimal
interface SwapComponent : ComposableContentComponent {
@ -13,7 +14,14 @@ interface SwapComponent : ComposableContentComponent {
val userWalletId: UserWalletId,
val isInitialReverseOrder: Boolean = false,
val screenSource: String,
)
val tangemPayInput: TangemPayInput? = null,
) {
data class TangemPayInput(
val cryptoAmount: BigDecimal,
val fiatAmount: BigDecimal,
val depositAddress: String,
)
}
interface Factory : ComponentFactory<Params, SwapComponent>
}

View file

@ -46,6 +46,7 @@ dependencies {
implementation(projects.domain.express.models)
implementation(projects.domain.account)
implementation(projects.domain.account.status)
implementation(projects.domain.visa)
/** Feature modules */
implementation(projects.features.swap.domain)

View file

@ -44,6 +44,7 @@ import com.tangem.domain.promo.models.StoryContentIds
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
import com.tangem.domain.settings.usercountry.models.UserCountry
import com.tangem.domain.settings.usercountry.models.needApplyFCARestrictions
import com.tangem.domain.tangempay.GetTangemPayCurrencyStatusUseCase
import com.tangem.domain.tokens.GetFeePaidCryptoCurrencyStatusSyncUseCase
import com.tangem.domain.tokens.GetMinimumTransactionAmountSyncUseCase
import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase
@ -113,6 +114,7 @@ internal class SwapModel @Inject constructor(
private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase,
private val getAccountCurrencyStatusUseCase: GetAccountCurrencyStatusUseCase,
private val accountsFeatureToggles: AccountsFeatureToggles,
private val getTangemPayCurrencyStatusUseCase: GetTangemPayCurrencyStatusUseCase,
) : Model() {
private val params = paramsContainer.require<SwapComponent.Params>()
@ -121,6 +123,7 @@ internal class SwapModel @Inject constructor(
private val initialCurrencyTo = params.currencyTo
private val userWalletId = params.userWalletId
private val isInitiallyReversed = params.isInitialReverseOrder
private val tangemPayInput = params.tangemPayInput
private val userWallet by lazy {
requireNotNull(
@ -196,7 +199,7 @@ internal class SwapModel @Inject constructor(
}
modelScope.launch(dispatchers.io) {
if (accountsFeatureToggles.isFeatureEnabled) {
if (accountsFeatureToggles.isFeatureEnabled && tangemPayInput == null) {
isAccountsMode = isAccountsModeEnabledUseCase.invokeSync()
val fromAccountStatus = getAccountCurrencyStatusUseCase.invokeSync(
@ -220,10 +223,19 @@ internal class SwapModel @Inject constructor(
initTokens(isInitiallyReversed)
}
} else {
val fromStatus = getSingleCryptoCurrencyStatusUseCase.invokeMultiWalletSync(
userWalletId = userWalletId,
cryptoCurrencyId = initialCurrencyFrom.id,
).getOrNull()
val fromStatus = if (tangemPayInput != null) {
getTangemPayCurrencyStatusUseCase(
currency = initialCurrencyFrom,
cryptoAmount = tangemPayInput.cryptoAmount,
fiatAmount = tangemPayInput.fiatAmount,
depositAddress = tangemPayInput.depositAddress,
)
} else {
getSingleCryptoCurrencyStatusUseCase.invokeMultiWalletSync(
userWalletId = userWalletId,
cryptoCurrencyId = initialCurrencyFrom.id,
).getOrNull()
}
val toStatus = initialCurrencyTo?.let {
getSingleCryptoCurrencyStatusUseCase.invokeMultiWalletSync(
userWalletId = userWalletId,
@ -555,6 +567,7 @@ internal class SwapModel @Inject constructor(
selectedFeeType = dataState.selectedFee?.feeType ?: FeeType.NORMAL,
isReverseSwapPossible = isReverseSwapPossible(),
needApplyFCARestrictions = userCountry.needApplyFCARestrictions(),
isForTangemPaySwap = tangemPayInput != null,
)
if (uiState.notifications.any { it is SwapNotificationUM.Error.UnableToCoverFeeWarning }) {
analyticsEventHandler.send(
@ -574,6 +587,7 @@ internal class SwapModel @Inject constructor(
toTokenStatus = toTokenStatus,
isReverseSwapPossible = isReverseSwapPossible(),
toAccount = dataState.toAccount,
isForTangemPaySwap = tangemPayInput != null,
)
}
is SwapState.SwapError -> {
@ -588,6 +602,7 @@ internal class SwapModel @Inject constructor(
isReverseSwapPossible = isReverseSwapPossible(),
needApplyFCARestrictions = userCountry.needApplyFCARestrictions(),
toAccount = dataState.toAccount,
isForTangemPaySwap = tangemPayInput != null,
)
sendErrorAnalyticsEvent(state.error, provider)
}

View file

@ -277,6 +277,7 @@ internal class StateBuilder(
selectedFeeType: FeeType,
isReverseSwapPossible: Boolean,
needApplyFCARestrictions: Boolean,
isForTangemPaySwap: Boolean,
): SwapStateHolder {
if (uiStateHolder.sendCardData !is SwapCardState.SwapCardData) return uiStateHolder
if (uiStateHolder.receiveCardData !is SwapCardState.SwapCardData) return uiStateHolder
@ -369,7 +370,7 @@ internal class StateBuilder(
enabled = getSwapButtonEnabled(notifications),
onClick = actions.onSwapClick,
),
changeCardsButtonState = if (isReverseSwapPossible) {
changeCardsButtonState = if (isReverseSwapPossible && !isForTangemPaySwap) {
ChangeCardsButtonState.ENABLED
} else {
ChangeCardsButtonState.DISABLED
@ -442,6 +443,7 @@ internal class StateBuilder(
expressDataError: ExpressDataError,
isReverseSwapPossible: Boolean,
needApplyFCARestrictions: Boolean,
isForTangemPaySwap: Boolean,
): SwapStateHolder {
if (uiStateHolder.sendCardData !is SwapCardState.SwapCardData) return uiStateHolder
if (uiStateHolder.receiveCardData !is SwapCardState.SwapCardData) return uiStateHolder
@ -500,7 +502,7 @@ internal class StateBuilder(
enabled = false,
onClick = actions.onSwapClick,
),
changeCardsButtonState = if (isReverseSwapPossible) {
changeCardsButtonState = if (isReverseSwapPossible && !isForTangemPaySwap) {
ChangeCardsButtonState.ENABLED
} else {
ChangeCardsButtonState.DISABLED
@ -559,6 +561,7 @@ internal class StateBuilder(
toTokenStatus: CryptoCurrencyStatus?,
toAccount: Account.CryptoPortfolio?,
isReverseSwapPossible: Boolean,
isForTangemPaySwap: Boolean,
): SwapStateHolder {
if (uiStateHolder.sendCardData !is SwapCardState.SwapCardData) return uiStateHolder
if (uiStateHolder.receiveCardData !is SwapCardState.SwapCardData) return uiStateHolder
@ -601,7 +604,7 @@ internal class StateBuilder(
enabled = false,
onClick = { },
),
changeCardsButtonState = if (isReverseSwapPossible) {
changeCardsButtonState = if (isReverseSwapPossible && !isForTangemPaySwap) {
ChangeCardsButtonState.ENABLED
} else {
ChangeCardsButtonState.DISABLED

View file

@ -18,9 +18,11 @@ dependencies {
implementation(projects.core.error)
implementation(projects.core.navigation)
implementation(projects.core.ui)
implementation(projects.core.analytics.models)
/** Common */
implementation(projects.common.ui)
implementation(projects.common.routing)
/** Features api */
implementation(projects.features.tangempay.details.api)

View file

@ -0,0 +1,43 @@
package com.tangem.features.tangempay.components
import androidx.compose.runtime.Composable
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.pay.TangemPayTopUpData
import com.tangem.features.tangempay.model.TangemPayAddFundsModel
import com.tangem.features.tangempay.ui.TangemPayAddFundsContent
import java.math.BigDecimal
internal class TangemPayAddFundsComponent(
appComponentContext: AppComponentContext,
params: Params,
) : ComposableBottomSheetComponent, AppComponentContext by appComponentContext {
private val model: TangemPayAddFundsModel = getOrCreateModel(params = params)
override fun dismiss() {
model.onDismiss()
}
@Composable
override fun BottomSheet() {
TangemPayAddFundsContent(state = model.uiState)
}
data class Params(
val listener: AddFundsListener,
val walletId: UserWalletId,
val cryptoBalance: BigDecimal,
val fiatBalance: BigDecimal,
val depositAddress: String,
val chainId: Int,
)
}
internal interface AddFundsListener {
fun onClickReceive(data: TangemPayTopUpData)
fun onClickSwap(data: TangemPayTopUpData)
fun onDismissAddFunds()
}

View file

@ -87,6 +87,17 @@ internal class TangemPayDetailsComponent(
onDismiss = model.bottomSheetNavigation::dismiss,
),
)
is TangemPayDetailsNavigation.AddFunds -> TangemPayAddFundsComponent(
appComponentContext = context,
params = TangemPayAddFundsComponent.Params(
walletId = navigation.walletId,
cryptoBalance = navigation.cryptoBalance,
fiatBalance = navigation.fiatBalance,
depositAddress = navigation.depositAddress,
chainId = navigation.chainId,
listener = model,
),
)
}
}
}

View file

@ -2,6 +2,7 @@ package com.tangem.features.tangempay.di
import com.tangem.core.decompose.di.ModelComponent
import com.tangem.core.decompose.model.Model
import com.tangem.features.tangempay.model.TangemPayAddFundsModel
import com.tangem.features.tangempay.model.TangemPayAddToWalletModel
import com.tangem.features.tangempay.model.TangemPayCardDetailsBlockModel
import com.tangem.features.tangempay.model.TangemPayChangePinModel
@ -47,4 +48,9 @@ internal interface TangemPayModelModule {
@IntoMap
@ClassKey(TangemPayAddToWalletModel::class)
fun bindTangemPayAddToWalletModel(model: TangemPayAddToWalletModel): Model
@Binds
@IntoMap
@ClassKey(TangemPayAddFundsModel::class)
fun bindTangemPayAddFundsModel(model: TangemPayAddFundsModel): Model
}

View file

@ -0,0 +1,19 @@
package com.tangem.features.tangempay.entity
import androidx.annotation.DrawableRes
import com.tangem.core.ui.components.bottomsheets.message.MessageBottomSheetUMV2
import com.tangem.core.ui.extensions.TextReference
import kotlinx.collections.immutable.ImmutableList
internal data class TangemPayAddFundsUM(
val items: ImmutableList<TangemPayAddFundsItemUM>,
val dismiss: () -> Unit,
val errorMessage: MessageBottomSheetUMV2?,
)
internal data class TangemPayAddFundsItemUM(
@DrawableRes val iconRes: Int,
val title: TextReference,
val description: TextReference,
val onClick: () -> Unit,
)

View file

@ -1,6 +1,8 @@
package com.tangem.features.tangempay.entity
import com.tangem.domain.models.TokenReceiveConfig
import com.tangem.domain.models.serialization.SerializedBigDecimal
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.visa.model.TangemPayTxHistoryItem
import kotlinx.serialization.Serializable
@ -10,6 +12,15 @@ internal sealed class TangemPayDetailsNavigation {
@Serializable
data class Receive(val config: TokenReceiveConfig) : TangemPayDetailsNavigation()
@Serializable
data class AddFunds(
val walletId: UserWalletId,
val cryptoBalance: SerializedBigDecimal,
val fiatBalance: SerializedBigDecimal,
val depositAddress: String,
val chainId: Int,
) : TangemPayDetailsNavigation()
@Serializable
data class TransactionDetails(val transaction: TangemPayTxHistoryItem) : TangemPayDetailsNavigation()
}

View file

@ -14,7 +14,7 @@ import kotlinx.collections.immutable.persistentListOf
internal class TangemPayDetailsStateFactory(
private val onBack: () -> Unit,
private val onRefresh: (ShowRefreshState) -> Unit,
private val onReceive: () -> Unit,
private val onAddFunds: () -> Unit,
private val onClickChangePin: () -> Unit,
private val onClickFreezeCard: () -> Unit,
) {
@ -39,13 +39,14 @@ internal class TangemPayDetailsStateFactory(
balanceBlockState = TangemPayDetailsBalanceBlockState.Loading(
actionButtons = persistentListOf(
ActionButtonConfig(
text = resourceReference(id = R.string.common_receive),
text = resourceReference(id = R.string.tangempay_card_details_add_funds),
iconResId = R.drawable.ic_arrow_down_24,
onClick = onReceive,
onClick = onAddFunds,
),
),
),
addToWalletBlockState = null,
isBalanceHidden = false,
addFundsEnabled = true,
)
}

View file

@ -11,6 +11,7 @@ internal data class TangemPayDetailsUM(
val balanceBlockState: TangemPayDetailsBalanceBlockState,
val addToWalletBlockState: AddToWalletBlockState?,
val isBalanceHidden: Boolean,
val addFundsEnabled: Boolean,
)
internal data class TangemPayCardDetailsUM(

View file

@ -0,0 +1,40 @@
package com.tangem.features.tangempay.model
import androidx.compose.runtime.Stable
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.domain.pay.TangemPayTopUpDataFactory
import com.tangem.features.tangempay.components.TangemPayAddFundsComponent
import com.tangem.features.tangempay.entity.TangemPayAddFundsUM
import com.tangem.features.tangempay.model.transformers.TangemPayAddFundsUMConverter
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import javax.inject.Inject
@Stable
@ModelScoped
internal class TangemPayAddFundsModel @Inject constructor(
paramsContainer: ParamsContainer,
override val dispatchers: CoroutineDispatcherProvider,
private val tangemPayTopUpDataFactory: TangemPayTopUpDataFactory,
) : Model() {
private val params = paramsContainer.require<TangemPayAddFundsComponent.Params>()
val uiState: TangemPayAddFundsUM = getInitialState()
private fun getInitialState(): TangemPayAddFundsUM {
val data = tangemPayTopUpDataFactory.create(
depositAddress = params.depositAddress,
chainId = params.chainId,
cryptoBalance = params.cryptoBalance,
fiatBalance = params.fiatBalance,
).getOrNull()
return TangemPayAddFundsUMConverter(listener = params.listener).convert(data)
}
fun onDismiss() {
params.listener.onDismissAddFunds()
}
}

View file

@ -3,6 +3,9 @@ package com.tangem.features.tangempay.model
import androidx.compose.runtime.Stable
import com.arkivanov.decompose.router.slot.SlotNavigation
import com.arkivanov.decompose.router.slot.activate
import com.arkivanov.decompose.router.slot.dismiss
import com.tangem.common.routing.AppRoute
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
@ -10,9 +13,11 @@ 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.domain.models.TokenReceiveConfig
import com.tangem.domain.pay.DataForReceiveFactory
import com.tangem.domain.pay.TangemPayTopUpData
import com.tangem.domain.pay.model.TangemPayCardBalance
import com.tangem.domain.pay.repository.CardDetailsRepository
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.entity.TangemPayDetailsErrorType
import com.tangem.features.tangempay.entity.TangemPayDetailsNavigation
@ -44,17 +49,16 @@ internal class TangemPayDetailsModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider,
private val router: Router,
private val cardDetailsRepository: CardDetailsRepository,
private val dataForReceiveFactory: DataForReceiveFactory,
private val uiMessageSender: UiMessageSender,
private val cardDetailsEventListener: CardDetailsEventListener,
) : Model(), TangemPayTxHistoryUiActions {
) : Model(), TangemPayTxHistoryUiActions, AddFundsListener {
private val params: TangemPayDetailsContainerComponent.Params = paramsContainer.require()
private val stateFactory = TangemPayDetailsStateFactory(
onBack = router::pop,
onRefresh = ::onRefreshSwipe,
onReceive = ::onClickReceive,
onAddFunds = ::onClickAddFunds,
onClickChangePin = ::onClickChangePin,
onClickFreezeCard = ::onClickFreezeCard,
)
@ -66,6 +70,8 @@ internal class TangemPayDetailsModel @Inject constructor(
private val fetchBalanceJobHolder = JobHolder()
private val addToWalletBannerJobHolder = JobHolder()
private var balance: TangemPayCardBalance? = null
val bottomSheetNavigation: SlotNavigation<TangemPayDetailsNavigation> = SlotNavigation()
init {
@ -81,30 +87,27 @@ internal class TangemPayDetailsModel @Inject constructor(
// TODO [REDACTED_JIRA]
}
private fun onClickReceive() {
val depositAddress = params.config.depositAddress
if (depositAddress == null) {
private fun onClickAddFunds() {
if (params.config.depositAddress == null || balance == null) {
showBottomSheetError(TangemPayDetailsErrorType.Receive)
} else {
dataForReceiveFactory.getDataForReceive(depositAddress = depositAddress, chainId = params.config.chainId)
.onRight {
val config = TokenReceiveConfig(
shouldShowWarning = false,
cryptoCurrency = it.currency,
userWalletId = it.walletId,
showMemoDisclaimer = false,
receiveAddress = it.receiveAddress,
)
bottomSheetNavigation.activate(TangemPayDetailsNavigation.Receive(config))
}
.onLeft { showBottomSheetError(TangemPayDetailsErrorType.Receive) }
bottomSheetNavigation.activate(
TangemPayDetailsNavigation.AddFunds(
walletId = params.userWalletId,
fiatBalance = requireNotNull(balance).balance,
// Using the fiat balance as crypto balance. This will be changed when back returns crypto balance
cryptoBalance = requireNotNull(balance).balance,
depositAddress = requireNotNull(params.config.depositAddress),
chainId = params.config.chainId,
),
)
}
}
private fun fetchBalance(): Job {
return modelScope.launch {
val result = cardDetailsRepository.getCardBalance()
uiState.update(DetailsBalanceTransformer(result))
val result = cardDetailsRepository.getCardBalance().onRight { balance = it }
uiState.update(DetailsBalanceTransformer(balance = result))
}.saveIn(fetchBalanceJobHolder)
}
@ -147,6 +150,39 @@ internal class TangemPayDetailsModel @Inject constructor(
}.saveIn(addToWalletBannerJobHolder)
}
override fun onClickSwap(data: TangemPayTopUpData) {
bottomSheetNavigation.dismiss()
router.push(
AppRoute.Swap(
currencyFrom = data.currency,
userWalletId = data.walletId,
isInitialReverseOrder = true,
screenSource = AnalyticsParam.ScreensSources.TangemPay.value,
tangemPayInput = AppRoute.Swap.TangemPayInput(
cryptoAmount = data.cryptoBalance,
fiatAmount = data.fiatBalance,
depositAddress = data.depositAddress,
),
),
)
}
override fun onClickReceive(data: TangemPayTopUpData) {
bottomSheetNavigation.dismiss()
val config = TokenReceiveConfig(
shouldShowWarning = false,
cryptoCurrency = data.currency,
userWalletId = data.walletId,
showMemoDisclaimer = false,
receiveAddress = data.receiveAddress,
)
bottomSheetNavigation.activate(TangemPayDetailsNavigation.Receive(config))
}
override fun onDismissAddFunds() {
bottomSheetNavigation.dismiss()
}
override fun onTransactionClick(item: TangemPayTxHistoryItem) {
bottomSheetNavigation.activate(TangemPayDetailsNavigation.TransactionDetails(item))
}

View file

@ -0,0 +1,48 @@
package com.tangem.features.tangempay.model.transformers
import com.tangem.core.ui.extensions.TextReference
import com.tangem.domain.pay.TangemPayTopUpData
import com.tangem.features.tangempay.components.AddFundsListener
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.utils.converter.Converter
import kotlinx.collections.immutable.persistentListOf
internal class TangemPayAddFundsUMConverter(
val listener: AddFundsListener,
) : Converter<TangemPayTopUpData?, TangemPayAddFundsUM> {
override fun convert(value: TangemPayTopUpData?): TangemPayAddFundsUM {
return if (value == null) {
TangemPayAddFundsUM(
items = persistentListOf(),
dismiss = listener::onDismissAddFunds,
errorMessage = TangemPayErrorMessageFactory.createErrorMessage(
type = TangemPayDetailsErrorType.Receive,
).messageBottomSheetUMV2,
)
} else {
TangemPayAddFundsUM(
items = persistentListOf(
TangemPayAddFundsItemUM(
iconRes = R.drawable.ic_exchange_vertical_24,
title = TextReference.Res(R.string.common_exchange),
description = TextReference.Res(R.string.tangempay_card_details_swap_description),
onClick = { listener.onClickSwap(value) },
),
TangemPayAddFundsItemUM(
iconRes = R.drawable.ic_arrow_down_24,
title = TextReference.Res(R.string.common_receive),
description = TextReference.Res(R.string.tangempay_card_details_receive_description),
onClick = { listener.onClickReceive(value) },
),
),
dismiss = listener::onDismissAddFunds,
errorMessage = null,
)
}
}
}

View file

@ -0,0 +1,154 @@
package com.tangem.features.tangempay.ui
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Icon
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.key
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
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 com.tangem.core.ui.components.SpacerH16
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import com.tangem.core.ui.components.bottomsheets.message.MessageBottomSheetV2Content
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheet
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetTitle
import com.tangem.core.ui.extensions.TextReference.Res
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.features.tangempay.details.impl.R
import com.tangem.features.tangempay.entity.TangemPayAddFundsItemUM
import com.tangem.features.tangempay.entity.TangemPayAddFundsUM
import kotlinx.collections.immutable.persistentListOf
@Composable
internal fun TangemPayAddFundsContent(state: TangemPayAddFundsUM) {
TangemModalBottomSheet<TangemBottomSheetConfigContent.Empty>(
config = TangemBottomSheetConfig(
isShown = true,
onDismissRequest = state.dismiss,
content = TangemBottomSheetConfigContent.Empty,
),
onBack = state.dismiss,
containerColor = TangemTheme.colors.background.tertiary,
title = {
TangemModalBottomSheetTitle(
title = resourceReference(R.string.tangempay_card_details_add_funds),
endIconRes = R.drawable.ic_close_24,
onEndClick = state.dismiss,
)
},
) {
if (state.errorMessage != null) {
MessageBottomSheetV2Content(state.errorMessage)
} else {
Surface(
modifier = Modifier
.padding(horizontal = 16.dp)
.fillMaxWidth(),
shape = TangemTheme.shapes.roundedCornersXMedium,
color = TangemTheme.colors.background.primary,
) {
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.Start,
) {
Text(
modifier = Modifier.padding(top = 12.dp, start = 16.dp, end = 16.dp),
text = resourceReference(R.string.tangempay_card_details_add_funds_subtitle).resolveReference(),
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.subtitle2,
)
state.items.forEach {
key(it.title) {
TangemPayTopUpItem(state = it)
}
}
}
}
SpacerH16()
}
}
}
@Composable
private fun TangemPayTopUpItem(state: TangemPayAddFundsItemUM, modifier: Modifier = Modifier) {
Row(
modifier = modifier
.fillMaxWidth()
.clickable(onClick = state.onClick)
.padding(horizontal = 12.dp, vertical = 16.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(12.dp),
) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.background(
color = TangemTheme.colors.icon.accent.copy(alpha = 0.1f),
shape = CircleShape,
)
.size(36.dp),
) {
Icon(
modifier = Modifier.size(16.dp),
imageVector = ImageVector.vectorResource(id = state.iconRes),
contentDescription = null,
tint = TangemTheme.colors.icon.accent,
)
}
Column(
verticalArrangement = Arrangement.spacedBy(2.dp),
) {
Text(
text = state.title.resolveReference(),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.primary1,
)
Text(
text = state.description.resolveReference(),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
)
}
}
}
@Preview(showBackground = true)
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun TangemPayAddFundsContentPreview() {
TangemThemePreview {
TangemPayAddFundsContent(
state = TangemPayAddFundsUM(
items = persistentListOf(
TangemPayAddFundsItemUM(
iconRes = R.drawable.ic_exchange_vertical_24,
title = Res(R.string.common_exchange),
description = Res(R.string.exсhange_token_description),
onClick = {},
),
TangemPayAddFundsItemUM(
iconRes = R.drawable.ic_arrow_down_24,
title = Res(R.string.common_receive),
description = Res(R.string.receive_token_description),
onClick = {},
),
),
dismiss = {},
errorMessage = null,
),
)
}
}

View file

@ -313,6 +313,7 @@ private class TangemPayDetailsUMProvider : CollectionPreviewParameterProvider<Ta
),
addToWalletBlockState = AddToWalletBlockState({}, {}),
isBalanceHidden = false,
addFundsEnabled = true,
),
TangemPayDetailsUM(
topBarConfig = TangemPayDetailsTopBarConfig(onBackClick = {}, items = null),
@ -320,6 +321,7 @@ private class TangemPayDetailsUMProvider : CollectionPreviewParameterProvider<Ta
balanceBlockState = TangemPayDetailsBalanceBlockState.Loading(actionButtons = persistentListOf()),
addToWalletBlockState = AddToWalletBlockState({}, {}),
isBalanceHidden = false,
addFundsEnabled = true,
),
),
)