Updated on 2026-08-14

This commit is contained in:
Tangem 2024-02-09 12:02:55 +03:00
parent f6fd1a51fb
commit 3affe7395c
10 changed files with 624 additions and 146 deletions

View file

@ -55,6 +55,10 @@ object DateTimeFormatters {
.withLocale(Locale.getDefault())
}
val dateTimeFormatter: DateTimeFormatter by lazy {
DateTimeFormat.forPattern("dd.MM.yyyy HH:mm")
}
fun formatTime(formatter: DateTimeFormatter = timeFormatter, time: DateTime): String {
return formatter.print(time)
}

View file

@ -3,11 +3,8 @@ package com.tangem.feature.wallet.presentation.wallet.state.model
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
internal data class BalancesAndLimitsBottomSheetConfig(
val currency: String,
val balance: Balance,
val limit: Limit,
val onBalanceInfoClick: () -> Unit,
val onLimitInfoClick: () -> Unit,
) : TangemBottomSheetConfigContent {
data class Balance(
@ -17,6 +14,7 @@ internal data class BalancesAndLimitsBottomSheetConfig(
val debit: String,
val pending: String,
val amlVerified: String,
val onInfoClick: () -> Unit,
)
data class Limit(
@ -24,5 +22,6 @@ internal data class BalancesAndLimitsBottomSheetConfig(
val inStore: String,
val other: String,
val singleTransaction: String,
val onInfoClick: () -> Unit,
)
}

View file

@ -0,0 +1,39 @@
package com.tangem.feature.wallet.presentation.wallet.state.model
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import kotlinx.collections.immutable.ImmutableList
internal data class VisaTxDetailsBottomSheetConfig(
val transaction: Transaction,
val requests: ImmutableList<Request>,
) : TangemBottomSheetConfigContent {
data class Transaction(
val id: String,
val type: String,
val status: String,
val blockchainAmount: String,
val blockchainFee: String,
val transactionAmount: String,
val transactionCurrencyCode: String,
val merchantName: String,
val merchantCity: String,
val merchantCountryCode: String,
val merchantCategoryCode: String,
)
data class Request(
val id: String,
val type: String,
val status: String,
val blockchainAmount: String,
val blockchainFee: String,
val transactionAmount: String,
val currencyCode: String,
val errorCode: Int,
val date: String,
val txHash: String,
val txStatus: String,
val onExploreClick: (() -> Unit)?,
)
}

View file

@ -15,8 +15,13 @@ internal class BalancesAndLimitsBottomSheetConverter(
) : Converter<VisaCurrency, BalancesAndLimitsBottomSheetConfig> {
override fun convert(value: VisaCurrency): BalancesAndLimitsBottomSheetConfig {
fun formatAmount(amount: BigDecimal): String = BigDecimalFormatter.formatCryptoAmount(
amount,
cryptoCurrency = value.symbol,
decimals = value.decimals,
)
return BalancesAndLimitsBottomSheetConfig(
currency = value.symbol,
balance = BalancesAndLimitsBottomSheetConfig.Balance(
totalBalance = value.balances.total.let(::formatAmount),
availableBalance = value.balances.available.let(::formatAmount),
@ -24,24 +29,18 @@ internal class BalancesAndLimitsBottomSheetConverter(
debit = value.balances.debt.let(::formatAmount),
pending = value.balances.pendingRefund.let(::formatAmount),
amlVerified = value.balances.verified.let(::formatAmount),
onInfoClick = this::showBalanceInfo,
),
limit = BalancesAndLimitsBottomSheetConfig.Limit(
availableBy = DateTimeFormatters.formatDate(date = value.limits.expirationDate),
inStore = value.limits.remainingOtp.let(::formatAmount),
other = value.limits.remainingNoOtp.let(::formatAmount),
singleTransaction = value.limits.singleTransaction.let(::formatAmount),
onInfoClick = this::showLimitInfo,
),
onBalanceInfoClick = this::showBalanceInfo,
onLimitInfoClick = this::showLimitInfo,
)
}
private fun formatAmount(amount: BigDecimal): String = BigDecimalFormatter.formatCryptoAmount(
amount,
cryptoCurrency = "",
decimals = 2,
)
private fun showBalanceInfo() {
eventSender.send(WalletEvent.ShowAlert(WalletAlertState.VisaBalancesInfo))
}

View file

@ -0,0 +1,86 @@
package com.tangem.feature.wallet.presentation.wallet.state.transformers.converter
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.core.ui.utils.DateTimeFormatters
import com.tangem.domain.visa.model.VisaCurrency
import com.tangem.domain.visa.model.VisaTxDetails
import com.tangem.feature.wallet.presentation.wallet.state.model.VisaTxDetailsBottomSheetConfig
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.VisaWalletIntents
import com.tangem.utils.converter.Converter
import kotlinx.collections.immutable.toImmutableList
import org.joda.time.DateTimeZone
import java.math.BigDecimal
import java.util.Currency
internal class VisaTxDetailsBottomSheetConverter(
private val visaCurrency: VisaCurrency,
private val clickIntents: VisaWalletIntents,
) : Converter<VisaTxDetails, VisaTxDetailsBottomSheetConfig> {
override fun convert(value: VisaTxDetails): VisaTxDetailsBottomSheetConfig {
return VisaTxDetailsBottomSheetConfig(
transaction = createTransaction(value),
requests = value.requests.map(::createRequest).toImmutableList(),
)
}
private fun createTransaction(details: VisaTxDetails): VisaTxDetailsBottomSheetConfig.Transaction {
return VisaTxDetailsBottomSheetConfig.Transaction(
id = details.id,
type = details.type,
status = details.status,
blockchainAmount = formatNetworkAmount(details.blockchainAmount),
blockchainFee = formatNetworkAmount(details.blockchainFee),
transactionAmount = formatFiatAmount(details.transactionAmount, details.fiatCurrency),
transactionCurrencyCode = details.transactionCurrencyCode.toString(),
merchantName = details.merchantName ?: UNKNOWN,
merchantCity = details.merchantCity ?: UNKNOWN,
merchantCountryCode = details.merchantCountryCode ?: UNKNOWN,
merchantCategoryCode = details.merchantCategoryCode ?: UNKNOWN,
)
}
private fun createRequest(request: VisaTxDetails.Request): VisaTxDetailsBottomSheetConfig.Request {
val localDate = request.requestDate.withZone(DateTimeZone.getDefault())
val exploreUrl = request.exploreUrl
return VisaTxDetailsBottomSheetConfig.Request(
id = request.id,
type = request.requestType,
status = request.requestStatus,
blockchainAmount = formatNetworkAmount(request.blockchainAmount),
blockchainFee = formatNetworkAmount(request.blockchainFee),
transactionAmount = formatFiatAmount(request.transactionAmount, request.fiatCurrency),
currencyCode = request.billingCurrencyCode.toString(),
errorCode = request.errorCode,
date = DateTimeFormatters.formatDate(DateTimeFormatters.dateTimeFormatter, date = localDate),
txHash = request.txHash ?: UNKNOWN,
txStatus = request.txStatus ?: UNKNOWN,
onExploreClick = if (exploreUrl != null) {
{ clickIntents.onExploreClick(exploreUrl) }
} else {
null
},
)
}
private fun formatNetworkAmount(amount: BigDecimal): String {
return BigDecimalFormatter.formatCryptoAmount(
cryptoAmount = amount,
cryptoCurrency = visaCurrency.symbol,
decimals = visaCurrency.decimals,
)
}
private fun formatFiatAmount(amount: BigDecimal, fiatCurrency: Currency): String {
return BigDecimalFormatter.formatFiatAmount(
fiatAmount = amount,
fiatCurrencyCode = fiatCurrency.currencyCode,
fiatCurrencySymbol = fiatCurrency.symbol,
)
}
private companion object {
const val UNKNOWN = "Unknown"
}
}

View file

@ -35,6 +35,7 @@ import com.tangem.feature.wallet.presentation.wallet.ui.components.multicurrency
import com.tangem.feature.wallet.presentation.wallet.ui.components.singlecurrency.controlButtons
import com.tangem.feature.wallet.presentation.wallet.ui.components.singlecurrency.marketPriceBlock
import com.tangem.feature.wallet.presentation.wallet.ui.components.visa.BalancesAndLimitsBottomSheet
import com.tangem.feature.wallet.presentation.wallet.ui.components.visa.VisaTxDetailsBottomSheet
import com.tangem.feature.wallet.presentation.wallet.ui.components.visa.balancesAndLimitsBlock
import com.tangem.feature.wallet.presentation.wallet.ui.components.visa.depositButton
import com.tangem.feature.wallet.presentation.wallet.ui.utils.changeWalletAnimator
@ -169,6 +170,7 @@ private fun WalletContent(
is ActionsBottomSheetConfig -> TokenActionsBottomSheet(config = bottomSheetConfig)
is ChooseAddressBottomSheetConfig -> ChooseAddressBottomSheet(config = bottomSheetConfig)
is BalancesAndLimitsBottomSheetConfig -> BalancesAndLimitsBottomSheet(config = bottomSheetConfig)
is VisaTxDetailsBottomSheetConfig -> VisaTxDetailsBottomSheet(config = bottomSheetConfig)
}
}

View file

@ -13,11 +13,8 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import com.tangem.core.ui.components.SpacerH16
import com.tangem.core.ui.components.SpacerWMax
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.wallet.impl.R
@ -45,136 +42,92 @@ private fun BalancesAndLimitsContent(config: BalancesAndLimitsBottomSheetConfig,
)
},
firstBlock = {
BlockContent(
title = stringReference("Balance, ${config.currency}"),
content = {
BlockItem(
title = stringReference("Total"),
value = config.balance.totalBalance,
)
BlockItem(
title = stringReference("AML Verified"),
value = config.balance.amlVerified,
)
BlockItem(
title = stringReference("Available"),
value = config.balance.availableBalance,
)
BlockItem(
title = stringReference("Blocked"),
value = config.balance.blockedBalance,
)
BlockItem(
title = stringReference("Debit"),
value = config.balance.debit,
)
BlockItem(
title = stringReference("Pending refund"),
value = config.balance.pending,
)
},
onInfoIconClick = config.onBalanceInfoClick,
)
BalancesBlock(balances = config.balance)
},
secondBlock = {
BlockContent(
title = stringReference("Limits, ${config.currency}"),
description = stringReference("Available by ${config.limit.availableBy}"),
content = {
BlockItem(
title = stringReference("In-store (otp)"),
value = config.limit.inStore,
)
BlockItem(
title = stringReference("Other (no-otp)"),
value = config.limit.other,
)
BlockItem(
title = stringReference("Single transaction"),
value = config.limit.singleTransaction,
)
},
onInfoIconClick = config.onBalanceInfoClick,
)
LimitsBlock(limits = config.limit)
},
)
}
@Composable
private inline fun BlockContent(
title: TextReference,
content: @Composable ColumnScope.() -> Unit,
noinline onInfoIconClick: () -> Unit,
modifier: Modifier = Modifier,
description: TextReference? = null,
) {
Column(
modifier = modifier
.padding(horizontal = TangemTheme.dimens.spacing16)
.fillMaxWidth()
.background(
color = TangemTheme.colors.background.primary,
shape = TangemTheme.shapes.roundedCornersXMedium,
private fun BalancesBlock(balances: BalancesAndLimitsBottomSheetConfig.Balance, modifier: Modifier = Modifier) {
BlockContent(
modifier = modifier,
title = stringReference("Balance"),
content = {
BlockItem(
title = stringReference("Total"),
value = balances.totalBalance,
)
.padding(vertical = TangemTheme.dimens.spacing8),
) {
Row(
modifier = Modifier
.padding(
start = TangemTheme.dimens.spacing12,
end = TangemTheme.dimens.spacing4,
)
.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = title.resolveReference(),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.tertiary,
BlockItem(
title = stringReference("AML Verified"),
value = balances.amlVerified,
)
SpacerWMax()
if (description != null) {
Text(
text = description.resolveReference(),
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.tertiary,
)
}
IconButton(
modifier = Modifier.size(TangemTheme.dimens.size32),
onClick = onInfoIconClick,
) {
Icon(
modifier = Modifier.size(TangemTheme.dimens.size16),
painter = painterResource(id = R.drawable.ic_information_24),
tint = TangemTheme.colors.icon.informative,
contentDescription = null,
)
}
}
content()
}
BlockItem(
title = stringReference("Available"),
value = balances.availableBalance,
)
BlockItem(
title = stringReference("Blocked"),
value = balances.blockedBalance,
)
BlockItem(
title = stringReference("Debit"),
value = balances.debit,
)
BlockItem(
title = stringReference("Pending refund"),
value = balances.pending,
)
},
description = {
InfoButton(onClick = balances.onInfoClick)
},
)
}
@Composable
private fun BlockItem(title: TextReference, value: String, modifier: Modifier = Modifier) {
Row(
modifier = modifier
.padding(horizontal = TangemTheme.dimens.spacing12)
.fillMaxWidth()
.heightIn(min = TangemTheme.dimens.size32),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
private fun LimitsBlock(limits: BalancesAndLimitsBottomSheetConfig.Limit, modifier: Modifier = Modifier) {
BlockContent(
modifier = modifier,
title = stringReference("Limits"),
content = {
BlockItem(
title = stringReference("In-store (otp)"),
value = limits.inStore,
)
BlockItem(
title = stringReference("Other (no-otp)"),
value = limits.other,
)
BlockItem(
title = stringReference("Single transaction"),
value = limits.singleTransaction,
)
},
description = {
Text(
text = "Available till ${limits.availableBy}",
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.tertiary,
)
InfoButton(onClick = limits.onInfoClick)
},
)
}
@Composable
private fun InfoButton(onClick: () -> Unit, modifier: Modifier = Modifier) {
IconButton(
modifier = modifier.size(TangemTheme.dimens.size32),
onClick = onClick,
) {
Text(
text = title.resolveReference(),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
)
Text(
text = value,
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.primary1,
Icon(
modifier = Modifier.size(TangemTheme.dimens.size16),
painter = painterResource(id = R.drawable.ic_information_24),
tint = TangemTheme.colors.icon.informative,
contentDescription = null,
)
}
}
@ -229,23 +182,22 @@ private class BalancesAndLimitsBottomSheetParameterProvider :
CollectionPreviewParameterProvider<BalancesAndLimitsBottomSheetConfig>(
collection = listOf(
BalancesAndLimitsBottomSheetConfig(
currency = "USDT",
balance = BalancesAndLimitsBottomSheetConfig.Balance(
totalBalance = "492.45",
availableBalance = "392.45",
blockedBalance = "36.00",
debit = "00.00",
pending = "20.99",
amlVerified = "356.45",
totalBalance = "492.45 USDT",
availableBalance = "392.45 USDT",
blockedBalance = "36.00 USDT",
debit = "00.00 USDT",
pending = "20.99 USDT",
amlVerified = "356.45 USDT",
onInfoClick = {},
),
limit = BalancesAndLimitsBottomSheetConfig.Limit(
availableBy = "Nov, 11",
inStore = "563.00",
other = "100.00",
singleTransaction = "100.00",
availableBy = "Nov, 11 USDT",
inStore = "563.00 USDT",
other = "100.00 USDT",
singleTransaction = "100.00 USDT",
onInfoClick = {},
),
onBalanceInfoClick = {},
onLimitInfoClick = {},
),
),
)

View file

@ -0,0 +1,83 @@
package com.tangem.feature.wallet.presentation.wallet.ui.components.visa
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import com.tangem.core.ui.components.SpacerH8
import com.tangem.core.ui.components.SpacerWMax
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
private const val BLOCK_ITEM_NAME_WEIGHT = .45f
private const val BLOCK_ITEM_VALUE_WEIGHT = .55f
@Composable
internal inline fun BlockContent(
title: TextReference,
content: @Composable ColumnScope.() -> Unit,
modifier: Modifier = Modifier,
description: @Composable RowScope.() -> Unit = {},
) {
Column(
modifier = modifier
.padding(horizontal = TangemTheme.dimens.spacing16)
.fillMaxWidth()
.background(
color = TangemTheme.colors.background.primary,
shape = TangemTheme.shapes.roundedCornersXMedium,
),
) {
Row(
modifier = Modifier
.padding(start = TangemTheme.dimens.spacing12)
.fillMaxWidth()
.heightIn(min = TangemTheme.dimens.size42),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = title.resolveReference(),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.tertiary,
)
SpacerWMax()
description()
}
content()
SpacerH8()
}
}
@Composable
internal fun BlockItem(title: TextReference, value: String, modifier: Modifier = Modifier) {
Row(
modifier = modifier
.fillMaxWidth()
.heightIn(min = TangemTheme.dimens.size32)
.padding(
vertical = TangemTheme.dimens.spacing8,
horizontal = TangemTheme.dimens.spacing12,
),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.Top,
) {
Text(
modifier = Modifier.weight(BLOCK_ITEM_NAME_WEIGHT),
text = title.resolveReference(),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
textAlign = TextAlign.Start,
)
Text(
modifier = Modifier.weight(BLOCK_ITEM_VALUE_WEIGHT),
text = value,
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.primary1,
textAlign = TextAlign.End,
)
}
}

View file

@ -0,0 +1,286 @@
package com.tangem.feature.wallet.presentation.wallet.ui.components.visa
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
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.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import com.tangem.core.ui.components.SpacerW12
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.wallet.state.model.VisaTxDetailsBottomSheetConfig
import kotlinx.collections.immutable.persistentListOf
@Composable
internal fun VisaTxDetailsBottomSheet(config: TangemBottomSheetConfig) {
TangemBottomSheet(
config = config,
containerColor = TangemTheme.colors.background.secondary,
) { content: VisaTxDetailsBottomSheetConfig ->
VisaTxDetailsBottomSheetContent(content)
}
}
@Composable
private fun VisaTxDetailsBottomSheetContent(config: VisaTxDetailsBottomSheetConfig, modifier: Modifier = Modifier) {
ContentContainer(
modifier = modifier,
blocksCount = config.requests.size.inc(),
title = {
Text(
text = "Transaction Details",
style = TangemTheme.typography.subtitle1,
color = TangemTheme.colors.text.primary1,
)
},
block = { index ->
if (index == 0) {
TransactionBlock(config.transaction)
} else {
BlockchainRequestBlock(config.requests[index - 1])
}
},
)
}
@Composable
private fun TransactionBlock(transaction: VisaTxDetailsBottomSheetConfig.Transaction, modifier: Modifier = Modifier) {
BlockContent(
modifier = modifier,
title = stringReference(value = "Transaction"),
content = {
BlockItem(
title = stringReference(value = "Type"),
value = transaction.type,
)
BlockItem(
title = stringReference(value = "Status"),
value = transaction.status,
)
BlockItem(
title = stringReference(value = "Blockchain Amount"),
value = transaction.blockchainAmount,
)
BlockItem(
title = stringReference(value = "Blockchain Fee"),
value = transaction.blockchainFee,
)
BlockItem(
title = stringReference(value = "Transaction Amount"),
value = transaction.transactionAmount,
)
BlockItem(
title = stringReference(value = "Currency Code"),
value = transaction.transactionCurrencyCode,
)
BlockItem(
title = stringReference(value = "Merchant Name"),
value = transaction.merchantName,
)
BlockItem(
title = stringReference(value = "Merchant City"),
value = transaction.merchantCity,
)
BlockItem(
title = stringReference(value = "Merchant Country Code"),
value = transaction.merchantCountryCode,
)
BlockItem(
title = stringReference(value = "Merchant Category Code"),
value = transaction.merchantCategoryCode,
)
},
)
}
@Composable
private fun BlockchainRequestBlock(request: VisaTxDetailsBottomSheetConfig.Request, modifier: Modifier = Modifier) {
BlockContent(
modifier = modifier,
title = stringReference(value = "Blockchain request"),
description = {
if (request.onExploreClick == null) return
Row(
modifier = Modifier.clickable(onClick = request.onExploreClick),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing4),
) {
Icon(
painter = painterResource(id = R.drawable.ic_compass_24),
contentDescription = null,
modifier = Modifier.size(size = TangemTheme.dimens.size18),
tint = TangemTheme.colors.icon.informative,
)
Text(
text = "Explore",
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.caption1,
)
}
SpacerW12()
},
content = {
BlockItem(
title = stringReference(value = "Type"),
value = request.type,
)
BlockItem(
title = stringReference(value = "Status"),
value = request.status,
)
BlockItem(
title = stringReference(value = "Blockchain Amount"),
value = request.blockchainAmount,
)
BlockItem(
title = stringReference(value = "Blockchain Fee"),
value = request.blockchainFee,
)
BlockItem(
title = stringReference(value = "Transaction Amount"),
value = request.transactionAmount,
)
BlockItem(
title = stringReference(value = "Currency Code"),
value = request.currencyCode,
)
BlockItem(
title = stringReference(value = "Error Code"),
value = request.errorCode.toString(),
)
BlockItem(
title = stringReference(value = "Date"),
value = request.date,
)
BlockItem(
title = stringReference(value = "Tx Hash"),
value = request.txHash,
)
BlockItem(
title = stringReference(value = "Tx Status"),
value = request.txStatus,
)
},
)
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun ContentContainer(
blocksCount: Int,
title: @Composable BoxScope.() -> Unit,
block: @Composable ColumnScope.(Int) -> Unit,
modifier: Modifier = Modifier,
) {
LazyColumn(
modifier = modifier.background(TangemTheme.colors.background.secondary),
contentPadding = PaddingValues(
bottom = TangemTheme.dimens.spacing16,
),
verticalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing12),
horizontalAlignment = Alignment.CenterHorizontally,
) {
stickyHeader {
Box(
modifier = Modifier
.fillMaxWidth()
.heightIn(min = TangemTheme.dimens.size44)
.background(TangemTheme.colors.background.secondary),
contentAlignment = Alignment.Center,
content = title,
)
}
items(blocksCount) { index ->
Column {
block(index)
}
}
}
}
// region Preview
@Preview(showBackground = true, widthDp = 360)
@Composable
private fun VisaTxDetailsBottomSheetPreview_Light(
@PreviewParameter(VisaTxDetailsBottomSheetParameterProvider::class) state: VisaTxDetailsBottomSheetConfig,
) {
TangemTheme {
VisaTxDetailsBottomSheetContent(state)
}
}
@Preview(showBackground = true, widthDp = 360)
@Composable
private fun VisaTxDetailsBottomSheetPreview_Dark(
@PreviewParameter(VisaTxDetailsBottomSheetParameterProvider::class) state: VisaTxDetailsBottomSheetConfig,
) {
TangemTheme(isDark = true) {
VisaTxDetailsBottomSheetContent(state)
}
}
private class VisaTxDetailsBottomSheetParameterProvider :
CollectionPreviewParameterProvider<VisaTxDetailsBottomSheetConfig>(
collection = listOf(
VisaTxDetailsBottomSheetConfig(
transaction = VisaTxDetailsBottomSheetConfig.Transaction(
id = "518385816101345408",
type = "payment",
status = "authorized",
blockchainAmount = "1.0614 USDT",
blockchainFee = "0.12",
transactionAmount = "0.99 €",
transactionCurrencyCode = "978",
merchantName = "SQ *FORMATIVE",
merchantCity = "London",
merchantCountryCode = "GB",
merchantCategoryCode = "5814",
),
requests = persistentListOf(
VisaTxDetailsBottomSheetConfig.Request(
id = "524582128501966718",
type = "authorize_payment",
status = "accepted",
blockchainAmount = "1.0593 USDT",
blockchainFee = "0.10",
transactionAmount = "0.99 €",
currencyCode = "978",
errorCode = 0,
date = "2023-12-01 14:20:09.230 +0300",
txHash = "0xc458f0204fe43b82c775004baabb38435b5595f4307d8c3ac74625c827be7c29",
txStatus = "confirmed",
onExploreClick = {},
),
VisaTxDetailsBottomSheetConfig.Request(
id = "524582128501966799",
type = "settlement",
status = "accepted",
blockchainAmount = "1.0614 USDT",
blockchainFee = "0.12",
transactionAmount = "0.99 €",
currencyCode = "978",
errorCode = 0,
date = "2023-12-01 00:01:00.000 +0300",
txHash = "0x635841d5fbdf1087cdd929019c863ee88a7165e4340bc17ddd0b1d04dfb11daa",
txStatus = "confirmed",
onExploreClick = {},
),
),
),
),
)
// endregion Preview

View file

@ -7,9 +7,11 @@ import com.tangem.core.ui.components.bottomsheets.tokenreceive.mapToAddressModel
import com.tangem.domain.tokens.GetCryptoCurrencyStatusSyncUseCase
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.visa.GetVisaCurrencyUseCase
import com.tangem.domain.visa.GetVisaTxDetailsUseCase
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.BalancesAndLimitsBottomSheetConverter
import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.VisaTxDetailsBottomSheetConverter
import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletEventSender
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.hilt.android.scopes.ViewModelScoped
@ -25,6 +27,8 @@ internal interface VisaWalletIntents {
fun onBalancesAndLimitsClick()
fun onVisaTransactionClick(id: String)
fun onExploreClick(exploreUrl: String)
}
@ViewModelScoped
@ -33,6 +37,7 @@ internal class VisaWalletIntentsImplementor @Inject constructor(
private val eventSender: WalletEventSender,
private val getCurrencyStatusUseCase: GetCryptoCurrencyStatusSyncUseCase,
private val getVisaCurrencyUseCase: GetVisaCurrencyUseCase,
private val getVisaTxDetailsUseCase: GetVisaTxDetailsUseCase,
private val dispatchers: CoroutineDispatcherProvider,
) : BaseWalletClickIntents(), VisaWalletIntents {
@ -97,6 +102,29 @@ internal class VisaWalletIntentsImplementor @Inject constructor(
}
override fun onVisaTransactionClick(id: String) {
// TODO: Implement [REDACTED_JIRA]
viewModelScope.launch(dispatchers.main) {
val userWalletId = stateController.getSelectedWalletId()
val visaCurrency = getVisaCurrencyUseCase(userWalletId)
.getOrElse {
Timber.e(it, "Failed to get visa currency")
return@launch
}
val transactionDetails = getVisaTxDetailsUseCase(userWalletId, id)
.getOrElse {
Timber.e(it, "Failed to get transaction details")
return@launch
}
val converter = VisaTxDetailsBottomSheetConverter(
visaCurrency,
clickIntents = this@VisaWalletIntentsImplementor,
)
stateController.showBottomSheet(content = converter.convert(transactionDetails))
}
}
override fun onExploreClick(exploreUrl: String) {
router.openUrl(exploreUrl)
}
}