Updated on 2026-08-14
This commit is contained in:
parent
fc319f2b70
commit
30788b2c52
6 changed files with 279 additions and 39 deletions
|
|
@ -2688,6 +2688,8 @@
|
|||
<string name="yield_module_transaction_exit_subtitle">%1$s withdrawn from Aave</string>
|
||||
<string name="yield_module_transaction_initialize">Yield Mode initialized</string>
|
||||
<string name="yield_module_transaction_reactivate">Yield Mode reactivated</string>
|
||||
<string name="yield_module_transaction_returned">Returned</string>
|
||||
<string name="yield_module_transaction_supplied">Supplied</string>
|
||||
<string name="yield_module_transaction_topup">Supply to Aave</string>
|
||||
<string name="yield_module_transaction_topup_subtitle">%1$s supplied to Aave</string>
|
||||
<string name="yield_module_transaction_withdraw">Withdraw from Aave</string>
|
||||
|
|
|
|||
|
|
@ -27,6 +27,9 @@ import com.tangem.utils.toBriefAddressFormat
|
|||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
/** Website the yield-supply protocol row links to — the hard-wired Aave integration's home page. */
|
||||
private const val AAVE_WEBSITE = "https://aave.com/"
|
||||
|
||||
/**
|
||||
* Converts an on-chain [TxInfo] to the [TxHistoryDetailsUM.SingleAsset] details card (Receive / Send / Transfer /
|
||||
* staking / yield-supply). A two-asset swap always surfaces as an [com.tangem.domain.txhistory.model.ExpressTx.Swap]
|
||||
|
|
@ -62,14 +65,17 @@ internal class OnChainTxToDetailsUMConverter(
|
|||
/**
|
||||
* Protocol row of a yield-supply tx: the DeFi protocol the funds are supplied to. The yield-supply product is a
|
||||
* single hard-wired integration across the app (Aave — the [yield_module_provider][R.string.yield_module_provider]
|
||||
* name), so the value is that constant provider rather than a per-tx resolved name. Mutually exclusive with
|
||||
* [validatorRow] — a tx is either staking or yield-supply, never both.
|
||||
* name), so the value is that constant provider rather than a per-tx resolved name, and the tap opens the
|
||||
* constant [AAVE_WEBSITE]. Mutually exclusive with [validatorRow] — a tx is either staking or yield-supply,
|
||||
* never both.
|
||||
*/
|
||||
private fun TxInfo.protocolRow(): TxHistoryDetailsUM.InfoRowUM? {
|
||||
if (type !is TransactionType.YieldSupply) return null
|
||||
return TxHistoryDetailsUM.InfoRowUM(
|
||||
label = resourceReference(R.string.staking_validator),
|
||||
value = resourceReference(R.string.yield_module_provider),
|
||||
trailingIconRes = R.drawable.ic_arrow_top_right_24,
|
||||
onClick = { onOpenValidator(AAVE_WEBSITE) },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -125,11 +131,37 @@ internal class OnChainTxToDetailsUMConverter(
|
|||
}
|
||||
|
||||
private fun TxInfo.toAmountBlockUM(): TxHistoryDetailsUM.AmountBlockUM = TxHistoryDetailsUM.AmountBlockUM(
|
||||
currencyIcon = iconStateConverter.convert(currency),
|
||||
icon = amountIcon(),
|
||||
amount = stringReference(signedAmount(currency)),
|
||||
label = amountLabel(),
|
||||
isFailed = status is TxInfo.TransactionStatus.Failed,
|
||||
)
|
||||
|
||||
/**
|
||||
* Amount icon. Yield-supply enter/exit shows the asset paired with the hard-wired Aave protocol icon, ordered by
|
||||
* direction (Aave leads on "Supplied"/enter, the asset leads on "Returned"/exit — mirroring the two states in the
|
||||
* design); every other type shows the single token avatar. Only enter/exit are paired for now — the remaining
|
||||
* yield-supply variants (topup / withdraw) can adopt the same pair later.
|
||||
*/
|
||||
private fun TxInfo.amountIcon(): TxHistoryDetailsUM.AmountIconUM {
|
||||
val asset = TxHistoryDetailsUM.AmountIconUM.Item.Currency(iconStateConverter.convert(currency))
|
||||
val aave = TxHistoryDetailsUM.AmountIconUM.Item.Resource(R.drawable.img_aave_22)
|
||||
return when (type) {
|
||||
is TransactionType.YieldSupply.Enter ->
|
||||
TxHistoryDetailsUM.AmountIconUM.OverlappingPair(leading = aave, trailing = asset)
|
||||
is TransactionType.YieldSupply.Exit ->
|
||||
TxHistoryDetailsUM.AmountIconUM.OverlappingPair(leading = asset, trailing = aave)
|
||||
else -> TxHistoryDetailsUM.AmountIconUM.Single(iconStateConverter.convert(currency))
|
||||
}
|
||||
}
|
||||
|
||||
/** "Supplied"/"Returned" label above the amount for yield-supply enter/exit; `null` (no label) for other types. */
|
||||
private fun TxInfo.amountLabel(): TextReference? = when (type) {
|
||||
is TransactionType.YieldSupply.Enter -> resourceReference(R.string.yield_module_transaction_supplied)
|
||||
is TransactionType.YieldSupply.Exit -> resourceReference(R.string.yield_module_transaction_returned)
|
||||
else -> null
|
||||
}
|
||||
|
||||
/**
|
||||
* Counterparty card ("Recipient" / "From"). Only the external-address avatar is produced — built from the `User`
|
||||
* interaction address (the same source the history list uses for its external-address subtitle); a counterparty that
|
||||
|
|
@ -140,6 +172,12 @@ internal class OnChainTxToDetailsUMConverter(
|
|||
* now — a follow-up.
|
||||
*/
|
||||
private fun TxInfo.toCounterpartyUM(): TxHistoryDetailsUM.CounterpartyUM? {
|
||||
// Contract interactions (yield-supply / staking / approve) talk to a protocol/validator, not a real recipient —
|
||||
// no copyable counterparty card.
|
||||
val isContractInteraction = type is TransactionType.YieldSupply ||
|
||||
type is TransactionType.Staking ||
|
||||
type is TransactionType.Approve
|
||||
if (isContractInteraction) return null
|
||||
val address = (interactionAddressType as? TxInfo.InteractionAddressType.User)?.address ?: return null
|
||||
return TxHistoryDetailsUM.CounterpartyUM(
|
||||
label = counterpartyLabel(),
|
||||
|
|
@ -158,13 +196,15 @@ internal class OnChainTxToDetailsUMConverter(
|
|||
|
||||
/**
|
||||
* Signed crypto amount with inline symbol, e.g. `+ 350.31 USDT` / `- 350.31 USDT`. The sign is `-` for outgoing, `+`
|
||||
* otherwise, and is dropped for zero amounts and for the failed state (a failed tx moved nothing) — the UI then only
|
||||
* strikes the amount through and dims it via [TxHistoryDetailsUM.AmountBlockUM.isFailed].
|
||||
* otherwise, and is dropped for zero amounts, for the failed state (a failed tx moved nothing) and for yield-supply
|
||||
* enter/exit (which reads "Supplied"/"Returned" via the label instead of a signed transfer) — the UI then only strikes
|
||||
* the amount through and dims it via [TxHistoryDetailsUM.AmountBlockUM.isFailed].
|
||||
*/
|
||||
private fun TxInfo.signedAmount(currency: CryptoCurrency): String {
|
||||
val formatted = amount.format { crypto(cryptoCurrency = currency, ignoreSymbolPosition = true) }
|
||||
val prefix = when {
|
||||
status is TxInfo.TransactionStatus.Failed -> ""
|
||||
type is TransactionType.YieldSupply.Enter || type is TransactionType.YieldSupply.Exit -> ""
|
||||
amount.isZero() -> ""
|
||||
isOutgoing -> "${StringsSigns.MINUS} "
|
||||
else -> "${StringsSigns.PLUS} "
|
||||
|
|
|
|||
|
|
@ -137,22 +137,51 @@ internal sealed interface TxHistoryDetailsUM : TangemBottomSheetConfigContent {
|
|||
}
|
||||
|
||||
/**
|
||||
* Centered amount block of the single-asset card: token avatar (with network badge), the big signed crypto
|
||||
* [amount] and the secondary [fiatAmount].
|
||||
* Centered amount block of the single-asset card: the [icon] (a single token avatar, or a yield-supply asset+Aave
|
||||
* pair), an optional [label] above the amount ("Supplied" / "Returned" for yield supply), the big [amount] and the
|
||||
* secondary [fiatAmount].
|
||||
*
|
||||
* [fiatAmount] is `null` while no fiat value is available (`TxInfo` has no fiat field yet) — the fiat line is then
|
||||
* omitted entirely rather than shown as a placeholder.
|
||||
* [label] is `null` for the plain single-asset types (Send / Receive / Transfer / staking) — the line is then
|
||||
* omitted. [fiatAmount] is `null` while no fiat value is available (`TxInfo` has no fiat field yet) — the fiat line
|
||||
* is then omitted entirely rather than shown as a placeholder.
|
||||
*
|
||||
* [isFailed] drives the failed visual state — the amount is struck through, recolored to tertiary and carries no
|
||||
* `+`/`−` sign (mirrors the status-driven recolor in the shared header).
|
||||
*/
|
||||
data class AmountBlockUM(
|
||||
val currencyIcon: CurrencyIconState,
|
||||
val icon: AmountIconUM,
|
||||
val amount: TextReference,
|
||||
val label: TextReference? = null,
|
||||
val fiatAmount: TextReference? = null,
|
||||
val isFailed: Boolean,
|
||||
)
|
||||
|
||||
/** Icon shown above the amount of the single-asset card. */
|
||||
@Immutable
|
||||
sealed interface AmountIconUM {
|
||||
|
||||
/** A single token avatar — Send / Receive / Transfer / staking. */
|
||||
data class Single(val currencyIcon: CurrencyIconState) : AmountIconUM
|
||||
|
||||
/**
|
||||
* Two overlapping avatars: [leading] is drawn on top (left, with a background-colored ring), [trailing] behind
|
||||
* it (right). Used by yield supply — the asset and the hard-wired Aave protocol icon — with the order set by the
|
||||
* transaction direction: Aave leads on "Supplied" (enter), the asset leads on "Returned" (exit).
|
||||
*/
|
||||
data class OverlappingPair(val leading: Item, val trailing: Item) : AmountIconUM
|
||||
|
||||
/** One avatar of an [OverlappingPair]. */
|
||||
@Immutable
|
||||
sealed interface Item {
|
||||
|
||||
/** A token avatar backed by its [state]. */
|
||||
data class Currency(val state: CurrencyIconState) : Item
|
||||
|
||||
/** A hard-wired drawable (e.g. the Aave protocol icon). */
|
||||
data class Resource(@DrawableRes val resId: Int) : Item
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A single info row of the details card: a [label] on the leading side and its [value] on the trailing side
|
||||
* (e.g. `Network fee` → `0.00056 ETH`, `Rate` → `1 POL ≈ 0.36 USDT`). Rendered by [TxHistoryDetailsInfoRows].
|
||||
|
|
|
|||
|
|
@ -1,15 +1,22 @@
|
|||
package com.tangem.features.txhistory.ui
|
||||
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextDecoration
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
|
|
@ -25,13 +32,18 @@ import com.tangem.core.ui.res.TangemTheme
|
|||
import com.tangem.core.ui.res.TangemThemePreviewRedesign
|
||||
import com.tangem.features.txhistory.entity.TxHistoryDetailsUM
|
||||
|
||||
private val IconSize = 72.dp
|
||||
private val PairOverlap = 20.dp
|
||||
private val PairRing = 4.dp
|
||||
private val LeadingSize = IconSize + PairRing * 2
|
||||
|
||||
/**
|
||||
* Centered amount block of the single-asset card: token avatar (with network badge) over the big signed amount and the
|
||||
* secondary fiat line.
|
||||
* Centered amount block of the single-asset card: the token avatar (single, or a yield-supply asset+Aave pair), an
|
||||
* optional label ("Supplied"/"Returned"), the big amount and the secondary fiat line.
|
||||
*
|
||||
* The failed state ([TxHistoryDetailsUM.AmountBlockUM.isFailed]) strikes the amount through and dims it (primary ->
|
||||
* secondary) — matching the status-driven recolor of the shared header. The `+`/`−` sign is already dropped upstream
|
||||
* by the converter for failed transactions (a failed tx moved nothing), so the [amount] text arrives unsigned here.
|
||||
* secondary) — matching the status-driven recolor of the shared header. The `+`/`−` sign is resolved upstream by the
|
||||
* converter (dropped for failed and yield-supply transactions), so the [amount] text arrives ready to render here.
|
||||
*/
|
||||
@Composable
|
||||
internal fun TxHistoryDetailsAmountBlock(amountBlock: TxHistoryDetailsUM.AmountBlockUM, modifier: Modifier = Modifier) {
|
||||
|
|
@ -41,11 +53,17 @@ internal fun TxHistoryDetailsAmountBlock(amountBlock: TxHistoryDetailsUM.AmountB
|
|||
.padding(vertical = 48.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
TangemCurrencyIcon(
|
||||
state = amountBlock.currencyIcon,
|
||||
modifier = Modifier.size(72.dp),
|
||||
)
|
||||
AmountIcon(amountBlock.icon)
|
||||
SpacerH(24.dp)
|
||||
amountBlock.label?.let { label ->
|
||||
Text(
|
||||
text = label.resolveReference(),
|
||||
color = TangemTheme.colors3.text.secondary,
|
||||
style = TangemTheme.typography3.subheading.medium,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
SpacerH(4.dp)
|
||||
}
|
||||
Text(
|
||||
text = amountBlock.amount.resolveReference(),
|
||||
color = if (amountBlock.isFailed) {
|
||||
|
|
@ -73,6 +91,60 @@ internal fun TxHistoryDetailsAmountBlock(amountBlock: TxHistoryDetailsUM.AmountB
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AmountIcon(icon: TxHistoryDetailsUM.AmountIconUM, modifier: Modifier = Modifier) {
|
||||
when (icon) {
|
||||
is TxHistoryDetailsUM.AmountIconUM.Single -> AmountIconItem(
|
||||
item = TxHistoryDetailsUM.AmountIconUM.Item.Currency(icon.currencyIcon),
|
||||
modifier = modifier,
|
||||
)
|
||||
is TxHistoryDetailsUM.AmountIconUM.OverlappingPair -> Box(
|
||||
modifier = modifier
|
||||
.height(LeadingSize)
|
||||
.width(PairRing + IconSize * 2 - PairOverlap),
|
||||
) {
|
||||
// Trailing behind, right; the pair drops the network badge so the two token arts read as equal-size icons.
|
||||
AmountIconItem(
|
||||
item = icon.trailing,
|
||||
modifier = Modifier.align(Alignment.CenterEnd),
|
||||
shouldDisplayNetwork = false,
|
||||
)
|
||||
// Leading on top, left, wrapped in a background-colored ring that separates it from the trailing icon.
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterStart)
|
||||
.size(LeadingSize)
|
||||
.background(TangemTheme.colors3.bg.secondary, CircleShape)
|
||||
.padding(PairRing),
|
||||
) {
|
||||
AmountIconItem(item = icon.leading, shouldDisplayNetwork = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AmountIconItem(
|
||||
item: TxHistoryDetailsUM.AmountIconUM.Item,
|
||||
modifier: Modifier = Modifier,
|
||||
shouldDisplayNetwork: Boolean = true,
|
||||
) {
|
||||
when (item) {
|
||||
is TxHistoryDetailsUM.AmountIconUM.Item.Currency -> TangemCurrencyIcon(
|
||||
state = item.state,
|
||||
modifier = modifier.size(IconSize),
|
||||
shouldDisplayNetwork = shouldDisplayNetwork,
|
||||
)
|
||||
is TxHistoryDetailsUM.AmountIconUM.Item.Resource -> Image(
|
||||
painter = painterResource(item.resId),
|
||||
contentDescription = null,
|
||||
modifier = modifier
|
||||
.size(IconSize)
|
||||
.clip(CircleShape),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// region Preview
|
||||
|
||||
@Preview(name = "Light", showBackground = true, widthDp = 360)
|
||||
|
|
@ -87,21 +159,42 @@ private fun TxHistoryDetailsAmountBlockPreview() {
|
|||
TxHistoryDetailsAmountBlock(amountBlock = previewAmountBlock(isFailed = true))
|
||||
// No fiat — the fiat line is omitted entirely.
|
||||
TxHistoryDetailsAmountBlock(amountBlock = previewAmountBlock(isFailed = false, fiatAmount = null))
|
||||
// Yield supply — "Supplied" (Aave leads) and "Returned" (asset leads), unsigned amount.
|
||||
TxHistoryDetailsAmountBlock(
|
||||
amountBlock = previewAmountBlock(
|
||||
isFailed = false,
|
||||
fiatAmount = null,
|
||||
icon = TxHistoryDetailsUM.AmountIconUM.OverlappingPair(
|
||||
leading = TxHistoryDetailsUM.AmountIconUM.Item.Resource(R.drawable.img_aave_22),
|
||||
trailing = TxHistoryDetailsUM.AmountIconUM.Item.Currency(previewCurrencyIcon()),
|
||||
),
|
||||
label = stringReference("Supplied"),
|
||||
amount = stringReference("1,294.23 USDT"),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun previewAmountBlock(isFailed: Boolean, fiatAmount: TextReference? = stringReference("$350.31")) =
|
||||
TxHistoryDetailsUM.AmountBlockUM(
|
||||
currencyIcon = CurrencyIconState.CoinIcon(
|
||||
url = null,
|
||||
fallbackResId = R.drawable.img_eth_22,
|
||||
isGrayscale = false,
|
||||
shouldShowCustomBadge = false,
|
||||
),
|
||||
amount = stringReference("+ 350.31 USDT"),
|
||||
fiatAmount = fiatAmount,
|
||||
isFailed = isFailed,
|
||||
)
|
||||
private fun previewCurrencyIcon() = CurrencyIconState.CoinIcon(
|
||||
url = null,
|
||||
fallbackResId = R.drawable.img_eth_22,
|
||||
isGrayscale = false,
|
||||
shouldShowCustomBadge = false,
|
||||
)
|
||||
|
||||
private fun previewAmountBlock(
|
||||
isFailed: Boolean,
|
||||
fiatAmount: TextReference? = stringReference("$350.31"),
|
||||
icon: TxHistoryDetailsUM.AmountIconUM = TxHistoryDetailsUM.AmountIconUM.Single(previewCurrencyIcon()),
|
||||
label: TextReference? = null,
|
||||
amount: TextReference = stringReference("+ 350.31 USDT"),
|
||||
) = TxHistoryDetailsUM.AmountBlockUM(
|
||||
icon = icon,
|
||||
amount = amount,
|
||||
label = label,
|
||||
fiatAmount = fiatAmount,
|
||||
isFailed = isFailed,
|
||||
)
|
||||
|
||||
// endregion
|
||||
|
|
@ -78,11 +78,13 @@ private fun previewSingleAsset() = TxHistoryDetailsUM.SingleAsset(
|
|||
menu = previewMenu(),
|
||||
),
|
||||
amountBlock = TxHistoryDetailsUM.AmountBlockUM(
|
||||
currencyIcon = CurrencyIconState.CoinIcon(
|
||||
url = null,
|
||||
fallbackResId = R.drawable.img_eth_22,
|
||||
isGrayscale = false,
|
||||
shouldShowCustomBadge = false,
|
||||
icon = TxHistoryDetailsUM.AmountIconUM.Single(
|
||||
CurrencyIconState.CoinIcon(
|
||||
url = null,
|
||||
fallbackResId = R.drawable.img_eth_22,
|
||||
isGrayscale = false,
|
||||
shouldShowCustomBadge = false,
|
||||
),
|
||||
),
|
||||
amount = stringReference("- 350.31 USDT"),
|
||||
fiatAmount = stringReference("$350.31"),
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import kotlinx.collections.immutable.persistentListOf
|
|||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import org.junit.jupiter.params.ParameterizedTest
|
||||
import org.junit.jupiter.params.provider.MethodSource
|
||||
import java.math.BigDecimal
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
|
|
@ -207,6 +208,55 @@ internal class OnChainTxToDetailsUMConverterTest : TxDetailsConverterTestBase()
|
|||
assertThat(amount).doesNotContain("-")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN non-yield Transfer WHEN convert THEN single icon and no label`() {
|
||||
// Arrange
|
||||
val tx = txInfo(type = TransactionType.Transfer)
|
||||
|
||||
// Act
|
||||
val amountBlock = converter.convert(tx).amountBlock
|
||||
|
||||
// Assert
|
||||
assertThat(amountBlock.icon).isInstanceOf(TxHistoryDetailsUM.AmountIconUM.Single::class.java)
|
||||
assertThat(amountBlock.label).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN yield-supply Enter WHEN convert THEN Supplied label, Aave-leading pair and unsigned amount`() {
|
||||
// Arrange — enter (funds outgoing to Aave): the amount must still be unsigned.
|
||||
val tx = txInfo(type = TransactionType.YieldSupply.Enter(address = USER_ADDRESS), isOutgoing = true)
|
||||
|
||||
// Act
|
||||
val amountBlock = converter.convert(tx).amountBlock
|
||||
|
||||
// Assert
|
||||
assertThat(amountBlock.label).isEqualTo(resourceReference(R.string.yield_module_transaction_supplied))
|
||||
val pair = amountBlock.icon as TxHistoryDetailsUM.AmountIconUM.OverlappingPair
|
||||
assertThat(pair.leading).isEqualTo(TxHistoryDetailsUM.AmountIconUM.Item.Resource(R.drawable.img_aave_22))
|
||||
assertThat(pair.trailing).isInstanceOf(TxHistoryDetailsUM.AmountIconUM.Item.Currency::class.java)
|
||||
val amount = amountBlock.amount.resolveString()
|
||||
assertThat(amount).doesNotContain("+")
|
||||
assertThat(amount).doesNotContain("-")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN yield-supply Exit WHEN convert THEN Returned label, asset-leading pair and unsigned amount`() {
|
||||
// Arrange
|
||||
val tx = txInfo(type = TransactionType.YieldSupply.Exit(address = USER_ADDRESS), isOutgoing = false)
|
||||
|
||||
// Act
|
||||
val amountBlock = converter.convert(tx).amountBlock
|
||||
|
||||
// Assert
|
||||
assertThat(amountBlock.label).isEqualTo(resourceReference(R.string.yield_module_transaction_returned))
|
||||
val pair = amountBlock.icon as TxHistoryDetailsUM.AmountIconUM.OverlappingPair
|
||||
assertThat(pair.leading).isInstanceOf(TxHistoryDetailsUM.AmountIconUM.Item.Currency::class.java)
|
||||
assertThat(pair.trailing).isEqualTo(TxHistoryDetailsUM.AmountIconUM.Item.Resource(R.drawable.img_aave_22))
|
||||
val amount = amountBlock.amount.resolveString()
|
||||
assertThat(amount).doesNotContain("+")
|
||||
assertThat(amount).doesNotContain("-")
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Counterparty
|
||||
|
|
@ -223,6 +273,29 @@ internal class OnChainTxToDetailsUMConverterTest : TxDetailsConverterTestBase()
|
|||
assertThat(counterparty).isNull()
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideContractInteractionTypes")
|
||||
fun `GIVEN contract-interaction tx with User interaction address WHEN convert THEN no counterparty card`(
|
||||
type: TransactionType,
|
||||
) {
|
||||
// Arrange — yield-supply / staking / approve talk to a protocol/validator, not a copyable recipient.
|
||||
val tx = txInfo(type = type, interactionAddressType = TxInfo.InteractionAddressType.User(USER_ADDRESS))
|
||||
|
||||
// Act
|
||||
val counterparty = converter.convert(tx).counterparty
|
||||
|
||||
// Assert
|
||||
assertThat(counterparty).isNull()
|
||||
}
|
||||
|
||||
private fun provideContractInteractionTypes() = listOf(
|
||||
TransactionType.YieldSupply.Enter(address = USER_ADDRESS),
|
||||
TransactionType.YieldSupply.Exit(address = USER_ADDRESS),
|
||||
TransactionType.Staking.Stake,
|
||||
TransactionType.Staking.Vote(validatorAddress = VALIDATOR_ADDRESS),
|
||||
TransactionType.Approve,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `GIVEN incoming Transfer with User address WHEN convert THEN address-avatar counterparty with From label`() {
|
||||
// Arrange
|
||||
|
|
@ -435,8 +508,8 @@ internal class OnChainTxToDetailsUMConverterTest : TxDetailsConverterTestBase()
|
|||
// region Protocol row (yield-supply)
|
||||
|
||||
@Test
|
||||
fun `GIVEN yield-supply tx WHEN convert THEN protocol row shows the hard-wired Aave protocol`() {
|
||||
// Arrange — yield-supply is a single hard-wired integration (Aave), so the value is constant, not resolved.
|
||||
fun `GIVEN yield-supply tx WHEN convert THEN protocol row shows the hard-wired Aave protocol with its link`() {
|
||||
// Arrange — yield-supply is a single hard-wired integration (Aave), so the value and link are constant.
|
||||
val tx = txInfo(type = TransactionType.YieldSupply.Enter(address = USER_ADDRESS))
|
||||
|
||||
// Act
|
||||
|
|
@ -445,8 +518,9 @@ internal class OnChainTxToDetailsUMConverterTest : TxDetailsConverterTestBase()
|
|||
// Assert
|
||||
assertThat(row.label).isEqualTo(resourceReference(R.string.staking_validator))
|
||||
assertThat(row.value).isEqualTo(resourceReference(R.string.yield_module_provider))
|
||||
assertThat(row.trailingIconRes).isNull()
|
||||
assertThat(row.onClick).isNull()
|
||||
assertThat(row.trailingIconRes).isEqualTo(R.drawable.ic_arrow_top_right_24)
|
||||
row.onClick?.invoke()
|
||||
assertThat(openedUrls).containsExactly("https://aave.com/")
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue