Updated on 2026-08-14
This commit is contained in:
parent
f6fd1a51fb
commit
3affe7395c
10 changed files with 624 additions and 146 deletions
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
@ -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)?,
|
||||
)
|
||||
}
|
||||
|
|
@ -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))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 = {},
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue