Updated on 2026-08-14
This commit is contained in:
parent
ef8a13cfaa
commit
2f60ccf3fc
14 changed files with 732 additions and 148 deletions
|
|
@ -2051,6 +2051,8 @@
|
|||
<string name="twins_recreate_toolbar">Tangem Twin</string>
|
||||
<string name="twins_recreate_warning">This action is irreversible. You will not have access to the old wallet.</string>
|
||||
<string name="twins_scan_twin_with_number">Tap the twin card with number %s and do not remove until the end of the operation</string>
|
||||
<string name="tx_history_onramp_top_up">Top up</string>
|
||||
<string name="tx_history_onramp_topped_up">Topped up</string>
|
||||
<string name="unexpected_error_description">Please try again later. If the issue persists, please contact support.</string>
|
||||
<string name="unexpected_error_title">Something went wrong!</string>
|
||||
<string name="universal_error">We\'ve encountered an error. Error code: %s. Please contact our support.</string>
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.Arrangement
|
|||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
|
|
@ -32,6 +33,8 @@ import androidx.compose.ui.text.style.TextDecoration
|
|||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.components.icons.identicon.IdentIcon
|
||||
import com.tangem.core.ui.components.transactions.state.TransactionItemUM
|
||||
import com.tangem.core.ui.components.transactions.state.TransactionItemUM.Content.Direction
|
||||
|
|
@ -70,55 +73,92 @@ fun TransactionItem(state: TransactionItemUM, isBalanceHidden: Boolean, modifier
|
|||
|
||||
@Composable
|
||||
private fun ContentItem(state: TransactionItemUM.Content, isBalanceHidden: Boolean, modifier: Modifier = Modifier) {
|
||||
val rowModifier = modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(onClick = state.onClick)
|
||||
.testTag(TransactionHistoryItemTestTags.ITEM)
|
||||
|
||||
TangemRowContainer(
|
||||
modifier = rowModifier,
|
||||
contentPadding = PaddingValues(
|
||||
horizontal = TangemTheme.dimens2.x4,
|
||||
vertical = TangemTheme.dimens2.x3,
|
||||
),
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(onClick = state.onClick)
|
||||
.testTag(TransactionHistoryItemTestTags.ITEM),
|
||||
) {
|
||||
StatusCircle(
|
||||
iconRes = state.iconRes,
|
||||
status = state.status,
|
||||
modifier = Modifier
|
||||
.layoutId(TangemRowLayoutId.HEAD)
|
||||
.padding(end = TangemTheme.dimens2.x3)
|
||||
.size(TangemTheme.dimens2.x10)
|
||||
.testTag(TransactionHistoryItemTestTags.STATUS_PREFIX + state.status.testTagSuffix),
|
||||
TangemRowContainer(
|
||||
contentPadding = PaddingValues(
|
||||
horizontal = TangemTheme.dimens2.x4,
|
||||
vertical = TangemTheme.dimens2.x3,
|
||||
),
|
||||
) {
|
||||
StatusCircle(
|
||||
iconRes = state.iconRes,
|
||||
status = state.status,
|
||||
modifier = Modifier
|
||||
.layoutId(TangemRowLayoutId.HEAD)
|
||||
.padding(end = TangemTheme.dimens2.x3)
|
||||
.size(TangemTheme.dimens2.x10)
|
||||
.testTag(TransactionHistoryItemTestTags.STATUS_PREFIX + state.status.testTagSuffix),
|
||||
)
|
||||
TitleText(
|
||||
title = state.title,
|
||||
status = state.status,
|
||||
modifier = Modifier
|
||||
.layoutId(TangemRowLayoutId.START_TOP)
|
||||
.testTag(TransactionHistoryItemTestTags.TITLE),
|
||||
)
|
||||
SubtitleText(
|
||||
subtitle = state.subtitle,
|
||||
status = state.status,
|
||||
modifier = Modifier
|
||||
.layoutId(TangemRowLayoutId.START_BOTTOM)
|
||||
.padding(top = TangemTheme.dimens2.x0_5),
|
||||
)
|
||||
state.amount?.let { amount ->
|
||||
AmountText(
|
||||
amount = amount,
|
||||
status = state.status,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
modifier = Modifier
|
||||
.layoutId(TangemRowLayoutId.END_TOP)
|
||||
.testTag(TransactionHistoryItemTestTags.AMOUNT),
|
||||
)
|
||||
}
|
||||
CurrencyText(
|
||||
symbol = state.currencySymbol,
|
||||
modifier = Modifier
|
||||
.layoutId(TangemRowLayoutId.END_BOTTOM)
|
||||
.padding(top = TangemTheme.dimens2.x0_5)
|
||||
.testTag(TransactionHistoryItemTestTags.CURRENCY),
|
||||
)
|
||||
}
|
||||
state.warning?.let { warning ->
|
||||
WarningLine(
|
||||
warning = warning,
|
||||
modifier = Modifier.padding(
|
||||
start = TangemTheme.dimens2.x4,
|
||||
end = TangemTheme.dimens2.x4,
|
||||
bottom = TangemTheme.dimens2.x3,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun WarningLine(warning: TextReference, modifier: Modifier = Modifier) {
|
||||
val attention = TangemTheme.colors2.text.status.attention
|
||||
Row(
|
||||
modifier = modifier,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x2),
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_alert_triangle_20),
|
||||
contentDescription = null,
|
||||
tint = attention,
|
||||
modifier = Modifier.size(TangemTheme.dimens2.x5),
|
||||
)
|
||||
TitleText(
|
||||
title = state.title,
|
||||
status = state.status,
|
||||
modifier = Modifier
|
||||
.layoutId(TangemRowLayoutId.START_TOP)
|
||||
.testTag(TransactionHistoryItemTestTags.TITLE),
|
||||
)
|
||||
SubtitleText(
|
||||
subtitle = state.subtitle,
|
||||
status = state.status,
|
||||
modifier = Modifier
|
||||
.layoutId(TangemRowLayoutId.START_BOTTOM)
|
||||
.padding(top = TangemTheme.dimens2.x0_5),
|
||||
)
|
||||
AmountText(
|
||||
amount = state.amount,
|
||||
status = state.status,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
modifier = Modifier
|
||||
.layoutId(TangemRowLayoutId.END_TOP)
|
||||
.testTag(TransactionHistoryItemTestTags.AMOUNT),
|
||||
)
|
||||
CurrencyText(
|
||||
symbol = state.currencySymbol,
|
||||
modifier = Modifier
|
||||
.layoutId(TangemRowLayoutId.END_BOTTOM)
|
||||
.padding(top = TangemTheme.dimens2.x0_5)
|
||||
.testTag(TransactionHistoryItemTestTags.CURRENCY),
|
||||
Text(
|
||||
text = warning.resolveReference(),
|
||||
color = attention,
|
||||
style = TangemTheme.typography2.captionMedium12,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -262,6 +302,21 @@ private fun SubtitleText(subtitle: ContentSubtitle, status: Status, modifier: Mo
|
|||
modifier = Modifier.fillMaxSize(),
|
||||
)
|
||||
}
|
||||
is ContentSubtitle.Asset -> InlineImageSubtitle(
|
||||
template = stringResourceSafe(subtitle.direction.templateResId(), subtitle.symbol),
|
||||
color = tertiary,
|
||||
afterIconColor = if (isFailed) tertiary else primary,
|
||||
modifier = modifier,
|
||||
) {
|
||||
subtitle.icon?.let { iconState ->
|
||||
CurrencyIcon(
|
||||
state = iconState,
|
||||
shouldDisplayNetwork = false,
|
||||
withFixedSize = false,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -491,4 +546,73 @@ private fun Preview_TransactionItem_Swap() {
|
|||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview_TransactionItem_Express() {
|
||||
TangemThemePreviewRedesign {
|
||||
PreviewColumn(
|
||||
items = listOf(
|
||||
TransactionItemUM.Content(
|
||||
txHash = "exp-swap-u",
|
||||
amount = "-390.00",
|
||||
currencySymbol = "USDT",
|
||||
time = "",
|
||||
status = Status.Unconfirmed,
|
||||
direction = Direction.OUTGOING,
|
||||
onClick = {},
|
||||
iconRes = R.drawable.ic_exchange_vertical_24,
|
||||
title = stringReference("Swapping"),
|
||||
subtitle = ContentSubtitle.Asset(
|
||||
direction = ContentSubtitle.Direction.TO,
|
||||
symbol = "POL",
|
||||
icon = CurrencyIconState.CoinIcon(
|
||||
url = null,
|
||||
fallbackResId = R.drawable.ic_custom_token_44,
|
||||
isGrayscale = false,
|
||||
shouldShowCustomBadge = false,
|
||||
),
|
||||
),
|
||||
timestamp = 0L,
|
||||
warning = stringReference("KYC verification required by provider"),
|
||||
),
|
||||
TransactionItemUM.Content(
|
||||
txHash = "exp-onramp-c",
|
||||
amount = "+0.006339",
|
||||
currencySymbol = "BTC",
|
||||
time = "",
|
||||
status = Status.Confirmed,
|
||||
direction = Direction.INCOMING,
|
||||
onClick = {},
|
||||
iconRes = R.drawable.ic_tangem_card_24,
|
||||
title = stringReference("Topped up"),
|
||||
subtitle = ContentSubtitle.Asset(
|
||||
direction = ContentSubtitle.Direction.FROM,
|
||||
symbol = "SEK",
|
||||
icon = null,
|
||||
),
|
||||
timestamp = 0L,
|
||||
),
|
||||
TransactionItemUM.Content(
|
||||
txHash = "exp-onramp-f",
|
||||
amount = "0.006339",
|
||||
currencySymbol = "BTC",
|
||||
time = "",
|
||||
status = Status.Failed,
|
||||
direction = Direction.INCOMING,
|
||||
onClick = {},
|
||||
iconRes = R.drawable.ic_tangem_card_24,
|
||||
title = stringReference("Top up failed"),
|
||||
subtitle = ContentSubtitle.Asset(
|
||||
direction = ContentSubtitle.Direction.FROM,
|
||||
symbol = "SEK",
|
||||
icon = null,
|
||||
),
|
||||
timestamp = 0L,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
|
@ -3,6 +3,7 @@ package com.tangem.core.ui.components.transactions.state
|
|||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.ds.image.DeviceIconUM
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
|
||||
|
|
@ -22,12 +23,13 @@ sealed interface TransactionItemUM {
|
|||
/**
|
||||
* Content state.
|
||||
*
|
||||
* @property amount signed numeric value, e.g. "+0.500913" / "-350.31"; no currency symbol embedded
|
||||
* @property amount signed numeric value, e.g. "+0.500913" / "-350.31"; no currency symbol embedded.
|
||||
* `null` hides the numeric value while [currencySymbol] still shows.
|
||||
* @property currencySymbol currency symbol shown alongside [amount], e.g. "BTC", "USDT"
|
||||
*/
|
||||
data class Content(
|
||||
override val txHash: String,
|
||||
val amount: String,
|
||||
val amount: String?,
|
||||
val currencySymbol: String,
|
||||
val time: String,
|
||||
val status: Status,
|
||||
|
|
@ -37,6 +39,7 @@ sealed interface TransactionItemUM {
|
|||
val title: TextReference,
|
||||
val subtitle: ContentSubtitle,
|
||||
val timestamp: Long,
|
||||
val warning: TextReference? = null,
|
||||
) : TransactionItemUM {
|
||||
|
||||
@Immutable
|
||||
|
|
@ -95,6 +98,19 @@ sealed interface TransactionItemUM {
|
|||
val deviceIconUM: DeviceIconUM,
|
||||
) : ContentSubtitle
|
||||
|
||||
/**
|
||||
* Counterparty asset ticker — renders as "to/from: <icon> <SYMBOL>". Used for express rows
|
||||
* (swap counterparty currency / onramp fiat), e.g. "to: ◎ POL" or "from: 🇸🇪 SEK".
|
||||
*
|
||||
* @property icon resolved counterparty currency icon, rendered via `CurrencyIcon`. `null` when no icon
|
||||
* is available (e.g. onramp fiat carries no `CryptoCurrency`) — the ticker then renders without a leading icon.
|
||||
*/
|
||||
data class Asset(
|
||||
val direction: Direction,
|
||||
val symbol: String,
|
||||
val icon: CurrencyIconState?,
|
||||
) : ContentSubtitle
|
||||
|
||||
enum class Direction { TO, FROM }
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue