Updated on 2026-08-14

This commit is contained in:
Tangem 2023-10-25 12:09:02 +05:00
parent 40f24bd65b
commit 724a1fe19b
19 changed files with 550 additions and 587 deletions

View file

@ -1,6 +1,9 @@
package com.tangem.tap.di.domain package com.tangem.tap.di.domain
import android.content.Context
import com.squareup.moshi.Moshi
import com.tangem.datasource.config.ConfigManager import com.tangem.datasource.config.ConfigManager
import com.tangem.datasource.di.SdkMoshi
import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.datasource.local.walletmanager.WalletManagersStore import com.tangem.datasource.local.walletmanager.WalletManagersStore
import com.tangem.domain.walletmanager.DefaultWalletManagersFacade import com.tangem.domain.walletmanager.DefaultWalletManagersFacade
@ -8,6 +11,7 @@ import com.tangem.domain.walletmanager.WalletManagersFacade
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import dagger.hilt.InstallIn import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton import javax.inject.Singleton
@ -21,7 +25,15 @@ internal object WalletManagersFacadeModule {
walletManagersStore: WalletManagersStore, walletManagersStore: WalletManagersStore,
userWalletsStore: UserWalletsStore, userWalletsStore: UserWalletsStore,
configManager: ConfigManager, configManager: ConfigManager,
@ApplicationContext appContext: Context,
@SdkMoshi moshi: Moshi,
): WalletManagersFacade { ): WalletManagersFacade {
return DefaultWalletManagersFacade(walletManagersStore, userWalletsStore, configManager) return DefaultWalletManagersFacade(
walletManagersStore = walletManagersStore,
userWalletsStore = userWalletsStore,
configManager = configManager,
assetManager = appContext.assets,
moshi = moshi,
)
} }
} }

View file

@ -453,6 +453,7 @@
<string name="transaction_history_error_failed_to_load">Не удалось загрузить историю транзакций.\nНажмите на кнопку перезагрузки, чтобы обновить информацию.</string> <string name="transaction_history_error_failed_to_load">Не удалось загрузить историю транзакций.\nНажмите на кнопку перезагрузки, чтобы обновить информацию.</string>
<string name="transaction_history_multiple_addresses">Несколько адресов</string> <string name="transaction_history_multiple_addresses">Несколько адресов</string>
<string name="transaction_history_not_supported_description">История транзакций в настоящее время не поддерживается для этого блокчейна. Но не волнуйтесь, мы работаем над этим! А пока вы можете проверить ее в обозревателе.</string> <string name="transaction_history_not_supported_description">История транзакций в настоящее время не поддерживается для этого блокчейна. Но не волнуйтесь, мы работаем над этим! А пока вы можете проверить ее в обозревателе.</string>
<string name="transaction_history_operation">Операция</string>
<string name="transaction_history_transaction_from_address">от: %s</string> <string name="transaction_history_transaction_from_address">от: %s</string>
<string name="transaction_history_transaction_to_address">на: %s</string> <string name="transaction_history_transaction_to_address">на: %s</string>
<string name="transaction_history_tx_in_progress">В процессе…</string> <string name="transaction_history_tx_in_progress">В процессе…</string>

View file

@ -469,6 +469,7 @@
<string name="transaction_history_error_failed_to_load">Failed to load transaction history.\nClick on reload button to update the information.</string> <string name="transaction_history_error_failed_to_load">Failed to load transaction history.\nClick on reload button to update the information.</string>
<string name="transaction_history_multiple_addresses">Multiple addresses</string> <string name="transaction_history_multiple_addresses">Multiple addresses</string>
<string name="transaction_history_not_supported_description">Transaction history is currently not supported for this blockchain. But don\'t worry, we\'re working on it! In the meantime you can check it in the explorer.</string> <string name="transaction_history_not_supported_description">Transaction history is currently not supported for this blockchain. But don\'t worry, we\'re working on it! In the meantime you can check it in the explorer.</string>
<string name="transaction_history_operation">Operation</string>
<string name="transaction_history_transaction_from_address">from: %s</string> <string name="transaction_history_transaction_from_address">from: %s</string>
<string name="transaction_history_transaction_to_address">to: %s</string> <string name="transaction_history_transaction_to_address">to: %s</string>
<string name="transaction_history_tx_in_progress">In progress…</string> <string name="transaction_history_tx_in_progress">In progress…</string>

View file

@ -29,6 +29,7 @@ import com.tangem.core.ui.components.transactions.state.TransactionState.Content
import com.tangem.core.ui.components.transactions.state.TransactionState.Content.Status import com.tangem.core.ui.components.transactions.state.TransactionState.Content.Status
import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemTheme
import java.util.UUID import java.util.UUID
@ -137,22 +138,7 @@ private fun Icon(state: TransactionState, modifier: Modifier = Modifier) {
), ),
) { ) {
Icon( Icon(
painter = painterResource( painter = painterResource(id = state.iconRes),
id = when (state.status) {
is Status.Failed -> R.drawable.ic_close_24
else -> when (state) {
is TransactionState.Transfer -> {
when (state.direction) {
Direction.OUTGOING -> R.drawable.ic_arrow_up_24
Direction.INCOMING -> R.drawable.ic_arrow_down_24
}
}
is TransactionState.Approve -> R.drawable.ic_doc_24
is TransactionState.Swap -> R.drawable.ic_exchange_vertical_24
is TransactionState.Custom -> R.drawable.ic_exchange_vertical_24
}
},
),
contentDescription = null, contentDescription = null,
modifier = Modifier modifier = Modifier
.size(TangemTheme.dimens.size20) .size(TangemTheme.dimens.size20)
@ -189,12 +175,7 @@ private fun Title(state: TransactionState, modifier: Modifier = Modifier) {
is TransactionState.Content -> { is TransactionState.Content -> {
Row(modifier = modifier, horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing6)) { Row(modifier = modifier, horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing6)) {
Text( Text(
text = when (state) { text = state.title.resolveReference(),
is TransactionState.Approve -> stringResource(R.string.common_approval)
is TransactionState.Transfer -> stringResource(R.string.common_transfer)
is TransactionState.Swap -> stringResource(R.string.common_swap)
is TransactionState.Custom -> state.title.resolveReference()
},
color = TangemTheme.colors.text.primary1, color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography.subtitle2, style = TangemTheme.typography.subtitle2,
) )
@ -228,35 +209,7 @@ private fun Subtitle(state: TransactionState, modifier: Modifier = Modifier) {
Text( Text(
text = when (state.status) { text = when (state.status) {
is Status.Failed -> stringResource(id = R.string.common_transaction_failed) is Status.Failed -> stringResource(id = R.string.common_transaction_failed)
else -> when (state) { else -> state.subtitle.resolveReference()
is TransactionState.Transfer -> {
when (state.direction) {
Direction.OUTGOING -> stringResource(
id = R.string.transaction_history_transaction_to_address,
state.address.resolveReference(),
)
Direction.INCOMING -> stringResource(
id = R.string.transaction_history_transaction_from_address,
state.address.resolveReference(),
)
}
}
is TransactionState.Approve -> stringResource(
id = R.string.transaction_history_transaction_from_address,
state.address.resolveReference(),
)
is TransactionState.Swap -> stringResource(
id = R.string.transaction_history_contract_address,
state.address.resolveReference(),
)
is TransactionState.Custom -> stringResource(
id = when (state.direction) {
Direction.OUTGOING -> R.string.transaction_history_transaction_to_address
Direction.INCOMING -> R.string.transaction_history_transaction_from_address
},
state.subtitle.resolveReference(),
)
}
}, },
modifier = modifier, modifier = modifier,
textAlign = TextAlign.Start, textAlign = TextAlign.Start,
@ -362,78 +315,98 @@ private fun Preview_TransactionItem_DarkTheme(
private class TransactionItemStateProvider : CollectionPreviewParameterProvider<TransactionState>( private class TransactionItemStateProvider : CollectionPreviewParameterProvider<TransactionState>(
collection = listOf( collection = listOf(
TransactionState.Transfer( TransactionState.Content(
txHash = UUID.randomUUID().toString(), txHash = UUID.randomUUID().toString(),
address = TextReference.Str("33BddS...ga2B"), address = TextReference.Str("33BddS...ga2B"),
amount = "-0.500913 BTC", amount = "-0.500913 BTC",
timestamp = "8:41", timestamp = "8:41",
status = Status.Confirmed, status = Status.Confirmed,
direction = Direction.OUTGOING, direction = Direction.OUTGOING,
iconRes = R.drawable.ic_arrow_up_24,
title = resourceReference(R.string.common_transfer),
subtitle = TextReference.Str("33BddS...ga2B"),
onClick = {}, onClick = {},
), ),
TransactionState.Transfer( TransactionState.Content(
txHash = UUID.randomUUID().toString(), txHash = UUID.randomUUID().toString(),
address = TextReference.Str("33BddS...ga2B"), address = TextReference.Str("33BddS...ga2B"),
amount = "+0.500913 BTC", amount = "+0.500913 BTC",
timestamp = "8:41", timestamp = "8:41",
status = Status.Unconfirmed, status = Status.Unconfirmed,
direction = Direction.INCOMING, direction = Direction.INCOMING,
iconRes = R.drawable.ic_arrow_down_24,
title = resourceReference(R.string.common_transfer),
subtitle = TextReference.Str("33BddS...ga2B"),
onClick = {}, onClick = {},
), ),
TransactionState.Approve( TransactionState.Content(
txHash = UUID.randomUUID().toString(), txHash = UUID.randomUUID().toString(),
address = TextReference.Str("33BddS...ga2B"), address = TextReference.Str("33BddS...ga2B"),
amount = "+0.500913 BTC", amount = "+0.500913 BTC",
timestamp = "8:41", timestamp = "8:41",
status = Status.Unconfirmed, status = Status.Unconfirmed,
direction = Direction.OUTGOING, direction = Direction.OUTGOING,
iconRes = R.drawable.ic_doc_24,
title = resourceReference(R.string.common_approval),
subtitle = TextReference.Str("33BddS...ga2B"),
onClick = {}, onClick = {},
), ),
TransactionState.Approve( TransactionState.Content(
txHash = UUID.randomUUID().toString(), txHash = UUID.randomUUID().toString(),
address = TextReference.Str("33BddS...ga2B"), address = TextReference.Str("33BddS...ga2B"),
amount = "+0.500913 BTC", amount = "+0.500913 BTC",
timestamp = "8:41", timestamp = "8:41",
status = Status.Failed, status = Status.Failed,
direction = Direction.OUTGOING, direction = Direction.OUTGOING,
iconRes = R.drawable.ic_doc_24,
title = resourceReference(R.string.common_approval),
subtitle = TextReference.Str("33BddS...ga2B"),
onClick = {}, onClick = {},
), ),
TransactionState.Approve( TransactionState.Content(
txHash = UUID.randomUUID().toString(), txHash = UUID.randomUUID().toString(),
address = TextReference.Str("33BddS...ga2B"), address = TextReference.Str("33BddS...ga2B"),
amount = "+0.500913 BTC", amount = "+0.500913 BTC",
timestamp = "8:41", timestamp = "8:41",
status = Status.Confirmed, status = Status.Confirmed,
direction = Direction.OUTGOING, direction = Direction.OUTGOING,
iconRes = R.drawable.ic_doc_24,
title = resourceReference(R.string.common_approval),
subtitle = TextReference.Str("33BddS...ga2B"),
onClick = {}, onClick = {},
), ),
TransactionState.Swap( TransactionState.Content(
txHash = UUID.randomUUID().toString(), txHash = UUID.randomUUID().toString(),
address = TextReference.Str("33BddS...ga2B"), address = TextReference.Str("33BddS...ga2B"),
amount = "+0.500913 BTC", amount = "+0.500913 BTC",
timestamp = "8:41", timestamp = "8:41",
status = Status.Unconfirmed, status = Status.Unconfirmed,
direction = Direction.INCOMING, direction = Direction.INCOMING,
iconRes = R.drawable.ic_arrow_down_24,
title = resourceReference(R.string.common_swap),
subtitle = TextReference.Str("33BddS...ga2B"),
onClick = {}, onClick = {},
), ),
TransactionState.Custom( TransactionState.Content(
txHash = UUID.randomUUID().toString(), txHash = UUID.randomUUID().toString(),
address = TextReference.Str("33BddS...ga2B"), address = TextReference.Str("33BddS...ga2B"),
amount = "+0.500913 BTC", amount = "+0.500913 BTC",
timestamp = "8:41", timestamp = "8:41",
status = Status.Confirmed, status = Status.Confirmed,
direction = Direction.INCOMING, direction = Direction.INCOMING,
iconRes = R.drawable.ic_doc_24,
title = TextReference.Str("Submit"), title = TextReference.Str("Submit"),
subtitle = TextReference.Str("33BddS...ga2B"), subtitle = TextReference.Str("33BddS...ga2B"),
onClick = {}, onClick = {},
), ),
TransactionState.Custom( TransactionState.Content(
txHash = UUID.randomUUID().toString(), txHash = UUID.randomUUID().toString(),
address = TextReference.Str("33BddS...ga2B"), address = TextReference.Str("33BddS...ga2B"),
amount = "+0.500913 BTC", amount = "+0.500913 BTC",
timestamp = "8:41", timestamp = "8:41",
status = Status.Confirmed, status = Status.Confirmed,
direction = Direction.OUTGOING, direction = Direction.OUTGOING,
iconRes = R.drawable.ic_arrow_up_24,
title = TextReference.Str("Submit"), title = TextReference.Str("Submit"),
subtitle = TextReference.Str("33BddS...ga2B"), subtitle = TextReference.Str("33BddS...ga2B"),
onClick = {}, onClick = {},

View file

@ -1,5 +1,6 @@
package com.tangem.core.ui.components.transactions.state package com.tangem.core.ui.components.transactions.state
import androidx.annotation.DrawableRes
import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.TextReference
/** /**
@ -22,30 +23,18 @@ sealed interface TransactionState {
* @property direction transaction direction * @property direction transaction direction
* @property onClick Lambda be invoked when manage button is clicked * @property onClick Lambda be invoked when manage button is clicked
*/ */
sealed class Content : TransactionState { data class Content(
override val txHash: String,
abstract val address: TextReference val address: TextReference,
abstract val amount: String val amount: String,
abstract val timestamp: String val timestamp: String,
abstract val status: Status val status: Status,
abstract val direction: Direction val direction: Direction,
abstract val onClick: () -> Unit val onClick: () -> Unit,
@DrawableRes val iconRes: Int,
fun copySealed( val title: TextReference,
txHash: String = this.txHash, val subtitle: TextReference,
address: TextReference = this.address, ) : TransactionState {
amount: String = this.amount,
timestamp: String = this.timestamp,
status: Status = this.status,
direction: Direction = this.direction,
): Content {
return when (this) {
is Approve -> copy(txHash, address, amount, timestamp, status, direction)
is Transfer -> copy(txHash, address, amount, timestamp, status, direction)
is Swap -> copy(txHash, address, amount, timestamp, status, direction)
is Custom -> copy(txHash, address, amount, timestamp, status, direction)
}
}
sealed class Status { sealed class Status {
object Failed : Status() object Failed : Status()
@ -59,75 +48,6 @@ sealed interface TransactionState {
} }
} }
/**
* Completed sending transaction state
*
* @property txHash transaction hash
* @property address address
* @property amount amount
* @property timestamp timestamp
* @property direction transaction direction
*/
data class Transfer(
override val txHash: String,
override val address: TextReference,
override val amount: String,
override val timestamp: String,
override val status: Status,
override val direction: Direction,
override val onClick: () -> Unit,
) : Content()
/**
* Completed approving transaction state
*
* @property txHash transaction hash
* @property address address
* @property amount amount
* @property timestamp timestamp
* @property direction transaction direction
*/
data class Approve(
override val txHash: String,
override val address: TextReference,
override val amount: String,
override val timestamp: String,
override val status: Status,
override val direction: Direction,
override val onClick: () -> Unit,
) : Content()
/**
* Completed swapping transaction state
*
* @property txHash transaction hash
* @property address address
* @property amount amount
* @property timestamp timestamp
* @property direction transaction direction
*/
data class Swap(
override val txHash: String,
override val address: TextReference,
override val amount: String,
override val timestamp: String,
override val status: Status,
override val direction: Direction,
override val onClick: () -> Unit,
) : Content()
data class Custom(
override val txHash: String,
override val address: TextReference,
override val amount: String,
override val timestamp: String,
override val status: Status,
override val direction: Direction,
override val onClick: () -> Unit,
val title: TextReference,
val subtitle: TextReference,
) : Content()
/** /**
* Loading state * Loading state
* *

View file

@ -0,0 +1,125 @@
{
"0xa9059cbb":{
"info":"ERC20",
"source":"https://eips.ethereum.org/EIPS/eip-20",
"name":"transfer"
},
"0x095ea7b3":{
"info":"ERC20",
"source":"https://eips.ethereum.org/EIPS/eip-20",
"name":"approve"
},
"0x357a150b":{
"info":"ERC721",
"source":"https://eips.ethereum.org/EIPS/eip-721",
"name":"run"
},
"0x23b872dd":{
"info":"ERC721",
"source":"https://eips.ethereum.org/EIPS/eip-721",
"name":"transferFrom"
},
"0x87201b41":{
"info":"ERC721",
"source":"https://eips.ethereum.org/EIPS/eip-721",
"name":"fulfillAvailableAdvancedOrders"
},
"0xe449f341":{
"info":"ERC721",
"source":"https://eips.ethereum.org/EIPS/eip-721",
"name":"unstake"
},
"0x84bb1e42":{
"info":"ERC721",
"source":"https://eips.ethereum.org/EIPS/eip-721",
"name":"claim"
},
"0xd48c03e5":{
"info":"ERC1155",
"source":"https://eips.ethereum.org/EIPS/eip-1155",
"name":"mint"
},
"0xe3afe0a3":{
"info":"Lido",
"source":"https://github.com/LedgerHQ/app-plugin-lido#smart-contracts",
"name":"withdrawal"
},
"0xd6681042":{
"info":"Lido",
"source":"https://github.com/LedgerHQ/app-plugin-lido#smart-contracts",
"name":"withdrawal"
},
"0x19aa6257":{
"info":"Lido",
"source":"https://github.com/LedgerHQ/app-plugin-lido#smart-contracts",
"name":"withdrawal"
},
"0xacf41e4d":{
"info":"Lido",
"source":"https://github.com/LedgerHQ/app-plugin-lido#smart-contracts",
"name":"withdrawal"
},
"0x7951b76f":{
"info":"Lido",
"source":"https://github.com/LedgerHQ/app-plugin-lido#smart-contracts",
"name":"withdrawal"
},
"0xa1903eab":{
"info":"Lido",
"source":"https://github.com/LedgerHQ/app-plugin-lido#smart-contracts",
"name":"submit"
},
"0x617ba037":{
"info":"AAVE",
"source":"https://etherscan.io/address/0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2",
"name":"supply"
},
"0x69328dec":{
"info":"AAVE",
"source":"https://etherscan.io/address/0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2",
"name":"withdraw"
},
"0x3ccfd60b":{
"info":"AAVE",
"source":"https://etherscan.io/address/0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2",
"name":"withdraw"
},
"0x573ade81":{
"info":"AAVE",
"source":"https://etherscan.io/address/0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2",
"name":"repay"
},
"0xa415bcad":{
"info":"AAVE",
"source":"https://etherscan.io/address/0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2",
"name":"borrow"
},
"0x12aa3caf":{
"info":"1inch v5",
"source":"https://github.com/LedgerHQ/app-plugin-1inch#functions-implemented",
"name":"swap"
},
"0x7c025200":{
"info":"1inch",
"source":"https://github.com/LedgerHQ/app-plugin-1inch#functions-implemented",
"name":"swap"
},
"0x2e95b6c8":{
"info":"1inch",
"source":"https://github.com/LedgerHQ/app-plugin-1inch#functions-implemented",
"name":"unoswap"
},
"0x0502b1c5":{
"info":"1inch",
"source":"https://github.com/LedgerHQ/app-plugin-1inch#functions-implemented",
"name":"unoswap"
},
"0x3593564c": {
"info":"unoswap",
"source":"https://etherscan.io/address/0x3fc91a3afd70395cd496c647d5a6cc9d4b2b7fad",
"name":"execute"
},
"0xd0e30db0": {
"name":"deposit"
}
}

View file

@ -4,6 +4,8 @@ import arrow.core.Either
import arrow.core.raise.either import arrow.core.raise.either
import arrow.core.raise.ensureNotNull import arrow.core.raise.ensureNotNull
import arrow.core.right import arrow.core.right
import android.content.res.AssetManager
import com.squareup.moshi.Moshi
import com.tangem.blockchain.blockchains.polkadot.ExistentialDepositProvider import com.tangem.blockchain.blockchains.polkadot.ExistentialDepositProvider
import com.tangem.blockchain.blockchains.solana.RentProvider import com.tangem.blockchain.blockchains.solana.RentProvider
import com.tangem.blockchain.common.* import com.tangem.blockchain.common.*
@ -41,6 +43,8 @@ class DefaultWalletManagersFacade(
private val walletManagersStore: WalletManagersStore, private val walletManagersStore: WalletManagersStore,
private val userWalletsStore: UserWalletsStore, private val userWalletsStore: UserWalletsStore,
configManager: ConfigManager, configManager: ConfigManager,
assetManager: AssetManager,
moshi: Moshi,
) : WalletManagersFacade { ) : WalletManagersFacade {
private val demoConfig by lazy { DemoConfig() } private val demoConfig by lazy { DemoConfig() }
@ -48,7 +52,7 @@ class DefaultWalletManagersFacade(
private val walletManagerFactory by lazy { WalletManagerFactory(configManager) } private val walletManagerFactory by lazy { WalletManagerFactory(configManager) }
private val sdkTokenConverter by lazy { SdkTokenConverter() } private val sdkTokenConverter by lazy { SdkTokenConverter() }
private val txHistoryStateConverter by lazy { SdkTransactionHistoryStateConverter() } private val txHistoryStateConverter by lazy { SdkTransactionHistoryStateConverter() }
private val txHistoryItemConverter by lazy { SdkTransactionHistoryItemConverter() } private val txHistoryItemConverter by lazy { SdkTransactionHistoryItemConverter(assetManager, moshi) }
override suspend fun update( override suspend fun update(
userWalletId: UserWalletId, userWalletId: UserWalletId,

View file

@ -0,0 +1,11 @@
package com.tangem.domain.walletmanager.model
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class SmartContractMethod(
@Json(name = "info") val info: String?,
@Json(name = "source") val source: String?,
@Json(name = "name") val name: String,
)

View file

@ -1,44 +1,93 @@
package com.tangem.domain.walletmanager.utils package com.tangem.domain.walletmanager.utils
import android.content.res.AssetManager
import com.squareup.moshi.Moshi
import com.tangem.blockchain.common.txhistory.TransactionHistoryItem import com.tangem.blockchain.common.txhistory.TransactionHistoryItem
import com.tangem.domain.txhistory.models.TxHistoryItem import com.tangem.domain.txhistory.models.TxHistoryItem
import com.tangem.utils.converter.Converter import com.tangem.utils.converter.Converter
import com.tangem.blockchain.common.txhistory.TransactionHistoryItem as SdkTransactionHistoryItem import com.tangem.blockchain.common.txhistory.TransactionHistoryItem as SdkTransactionHistoryItem
internal class SdkTransactionHistoryItemConverter : Converter<SdkTransactionHistoryItem, TxHistoryItem> { internal class SdkTransactionHistoryItemConverter(
assetManager: AssetManager,
moshi: Moshi,
) : Converter<SdkTransactionHistoryItem, TxHistoryItem> {
private val typeConverter by lazy {
SdkTransactionTypeConverter(assetManager, moshi)
}
override fun convert(value: SdkTransactionHistoryItem): TxHistoryItem = TxHistoryItem( override fun convert(value: SdkTransactionHistoryItem): TxHistoryItem = TxHistoryItem(
txHash = value.txHash, txHash = value.txHash,
timestampInMillis = value.timestamp, timestampInMillis = value.timestamp,
direction = value.direction.toDomain(), isOutgoing = value.isOutgoing,
destinationType = value.destinationType.toDomain(),
sourceType = value.sourceType.toDomain(),
interactionAddressType = value.extractInteractionAddressType(),
status = when (value.status) { status = when (value.status) {
TransactionHistoryItem.TransactionStatus.Confirmed -> TxHistoryItem.TransactionStatus.Confirmed SdkTransactionHistoryItem.TransactionStatus.Confirmed -> TxHistoryItem.TransactionStatus.Confirmed
TransactionHistoryItem.TransactionStatus.Failed -> TxHistoryItem.TransactionStatus.Failed SdkTransactionHistoryItem.TransactionStatus.Failed -> TxHistoryItem.TransactionStatus.Failed
TransactionHistoryItem.TransactionStatus.Unconfirmed -> TxHistoryItem.TransactionStatus.Unconfirmed SdkTransactionHistoryItem.TransactionStatus.Unconfirmed -> TxHistoryItem.TransactionStatus.Unconfirmed
},
type = when (val type = value.type) {
TransactionHistoryItem.TransactionType.Transfer -> TxHistoryItem.TransactionType.Transfer
TransactionHistoryItem.TransactionType.Approve -> TxHistoryItem.TransactionType.Approve
is TransactionHistoryItem.TransactionType.Custom -> TxHistoryItem.TransactionType.Custom(type.id)
TransactionHistoryItem.TransactionType.Deposit -> TxHistoryItem.TransactionType.Deposit
TransactionHistoryItem.TransactionType.Submit -> TxHistoryItem.TransactionType.Submit
TransactionHistoryItem.TransactionType.Supply -> TxHistoryItem.TransactionType.Supply
TransactionHistoryItem.TransactionType.Swap -> TxHistoryItem.TransactionType.Swap
TransactionHistoryItem.TransactionType.Unoswap -> TxHistoryItem.TransactionType.Unoswap
TransactionHistoryItem.TransactionType.Withdraw -> TxHistoryItem.TransactionType.Withdraw
}, },
type = typeConverter.convert(value.type),
amount = requireNotNull(value.amount.value) { "Transaction amount value must not be null" }, amount = requireNotNull(value.amount.value) { "Transaction amount value must not be null" },
) )
private fun TransactionHistoryItem.TransactionDirection.toDomain() = when (this) { private fun SdkTransactionHistoryItem.SourceType.toDomain(): TxHistoryItem.SourceType = when (this) {
is TransactionHistoryItem.TransactionDirection.Incoming -> is TransactionHistoryItem.SourceType.Single -> TxHistoryItem.SourceType.Single(address)
TxHistoryItem.TransactionDirection.Incoming(address.toDomain()) is TransactionHistoryItem.SourceType.Multiple -> TxHistoryItem.SourceType.Multiple(addresses)
is TransactionHistoryItem.TransactionDirection.Outgoing ->
TxHistoryItem.TransactionDirection.Outgoing(address.toDomain())
} }
private fun TransactionHistoryItem.Address.toDomain(): TxHistoryItem.Address = when (this) { private fun SdkTransactionHistoryItem.DestinationType.toDomain(): TxHistoryItem.DestinationType = when (this) {
TransactionHistoryItem.Address.Multiple -> TxHistoryItem.Address.Multiple is SdkTransactionHistoryItem.DestinationType.Single -> TxHistoryItem.DestinationType.Single(
is TransactionHistoryItem.Address.Single -> TxHistoryItem.Address.Single(rawAddress = rawAddress) addressType.toDomain(),
)
is SdkTransactionHistoryItem.DestinationType.Multiple -> TxHistoryItem.DestinationType.Multiple(
addressTypes.map { it.toDomain() },
)
}
private fun SdkTransactionHistoryItem.AddressType.toDomain(): TxHistoryItem.AddressType = when (this) {
is SdkTransactionHistoryItem.AddressType.Contract -> TxHistoryItem.AddressType.Contract(address)
is SdkTransactionHistoryItem.AddressType.User -> TxHistoryItem.AddressType.User(address)
}
private fun SdkTransactionHistoryItem.extractInteractionAddressType(): TxHistoryItem.InteractionAddressType {
return when (type) {
SdkTransactionHistoryItem.TransactionType.Transfer -> if (isOutgoing) {
mapToInteractionAddressType(destinationType = destinationType)
} else {
mapToInteractionAddressType(sourceType = sourceType)
}
is SdkTransactionHistoryItem.TransactionType.ContractMethod -> mapToInteractionAddressType(destinationType)
}
}
private fun mapToInteractionAddressType(
destinationType: SdkTransactionHistoryItem.DestinationType,
): TxHistoryItem.InteractionAddressType {
return when (destinationType) {
is TransactionHistoryItem.DestinationType.Multiple -> TxHistoryItem.InteractionAddressType.Multiple(
destinationType.addressTypes.map { it.address },
)
is TransactionHistoryItem.DestinationType.Single -> when (destinationType.addressType) {
is TransactionHistoryItem.AddressType.Contract -> TxHistoryItem.InteractionAddressType.Contract(
destinationType.addressType.address,
)
is TransactionHistoryItem.AddressType.User -> TxHistoryItem.InteractionAddressType.User(
destinationType.addressType.address,
)
}
}
}
private fun mapToInteractionAddressType(
sourceType: SdkTransactionHistoryItem.SourceType,
): TxHistoryItem.InteractionAddressType {
return when (sourceType) {
is TransactionHistoryItem.SourceType.Multiple -> TxHistoryItem.InteractionAddressType.Multiple(
sourceType.addresses,
)
is TransactionHistoryItem.SourceType.Single -> TxHistoryItem.InteractionAddressType.User(sourceType.address)
}
} }
} }

View file

@ -0,0 +1,50 @@
package com.tangem.domain.walletmanager.utils
import android.content.res.AssetManager
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import com.tangem.blockchain.common.txhistory.TransactionHistoryItem
import com.tangem.domain.txhistory.models.TxHistoryItem
import com.tangem.domain.walletmanager.model.SmartContractMethod
import com.tangem.utils.converter.Converter
import okio.buffer
import okio.source
class SdkTransactionTypeConverter(
private val assetManager: AssetManager,
private val moshi: Moshi,
) : Converter<TransactionHistoryItem.TransactionType, TxHistoryItem.TransactionType> {
private val adapter: JsonAdapter<Map<String, SmartContractMethod>> by lazy {
moshi.adapter(
Types.newParameterizedType(
Map::class.java,
String::class.java,
SmartContractMethod::class.java,
),
)
}
private val smartContractMethods by lazy { readSmartContractMethods() }
override fun convert(value: TransactionHistoryItem.TransactionType): TxHistoryItem.TransactionType {
return when (value) {
TransactionHistoryItem.TransactionType.Transfer -> TxHistoryItem.TransactionType.Transfer
is TransactionHistoryItem.TransactionType.ContractMethod -> {
return when (val name = smartContractMethods[value.id]?.name) {
"transfer" -> TxHistoryItem.TransactionType.Transfer
"approve" -> TxHistoryItem.TransactionType.Approve
"swap" -> TxHistoryItem.TransactionType.Swap
null -> TxHistoryItem.TransactionType.UnknownOperation
else -> TxHistoryItem.TransactionType.Operation(name = name.replaceFirstChar { it.titlecase() })
}
}
}
}
private fun readSmartContractMethods(): Map<String, SmartContractMethod> {
return assetManager.open("contract_methods.json").use { stream ->
adapter.fromJson(stream.source().buffer())
} ?: emptyMap()
}
}

View file

@ -118,18 +118,22 @@ internal class UpdateWalletManagerResultFactory {
} }
private fun createTxHistoryItem(walletAddresses: Set<String>, data: TransactionData): TxHistoryItem? { private fun createTxHistoryItem(walletAddresses: Set<String>, data: TransactionData): TxHistoryItem? {
val direction = extractDirection(walletAddresses, data) ?: run {
Timber.w("Can not determine address for $data")
return null
}
val hash = data.hash ?: return null val hash = data.hash ?: return null
val millis = data.date?.timeInMillis ?: return null val millis = data.date?.timeInMillis ?: return null
val amount = getTransactionAmountValue(data.amount) ?: return null val amount = getTransactionAmountValue(data.amount) ?: return null
val isOutgoing = data.sourceAddress in walletAddresses
return TxHistoryItem( return TxHistoryItem(
txHash = hash, txHash = hash,
timestampInMillis = TimeUnit.SECONDS.toMillis(millis), timestampInMillis = TimeUnit.SECONDS.toMillis(millis),
direction = direction, isOutgoing = isOutgoing,
destinationType = TxHistoryItem.DestinationType.Single(
TxHistoryItem.AddressType.User(data.destinationAddress),
),
sourceType = TxHistoryItem.SourceType.Single(data.sourceAddress),
interactionAddressType = TxHistoryItem.InteractionAddressType.User(
if (isOutgoing) data.destinationAddress else data.sourceAddress,
),
status = when (data.status) { status = when (data.status) {
TransactionStatus.Confirmed -> TxHistoryItem.TransactionStatus.Confirmed TransactionStatus.Confirmed -> TxHistoryItem.TransactionStatus.Confirmed
TransactionStatus.Unconfirmed -> TxHistoryItem.TransactionStatus.Unconfirmed TransactionStatus.Unconfirmed -> TxHistoryItem.TransactionStatus.Unconfirmed
@ -139,35 +143,6 @@ internal class UpdateWalletManagerResultFactory {
) )
} }
private fun extractDirection(
walletAddresses: Set<String>,
data: TransactionData,
): TxHistoryItem.TransactionDirection? {
val fromAddress = data.sourceAddress
val toAddress = data.destinationAddress
return when {
toAddress in walletAddresses -> {
TxHistoryItem.TransactionDirection.Incoming(TxHistoryItem.Address.Single(fromAddress))
}
fromAddress in walletAddresses -> {
TxHistoryItem.TransactionDirection.Outgoing(TxHistoryItem.Address.Single(toAddress))
}
else -> {
Timber.w(
"""
Unable to find transaction direction
|- To address: ${data.destinationAddress}
|- From address: ${data.sourceAddress}
|- Network addresses: $walletAddresses
""".trimIndent(),
)
return null
}
}
}
private fun getAvailableAddresses(addresses: Set<Address>): Set<String> { private fun getAvailableAddresses(addresses: Set<Address>): Set<String> {
return addresses.mapTo(hashSetOf()) { it.value } return addresses.mapTo(hashSetOf()) { it.value }
} }

View file

@ -5,29 +5,39 @@ import java.math.BigDecimal
data class TxHistoryItem( data class TxHistoryItem(
val txHash: String, val txHash: String,
val timestampInMillis: Long, val timestampInMillis: Long,
val direction: TransactionDirection, val isOutgoing: Boolean,
val destinationType: DestinationType,
val sourceType: SourceType,
val interactionAddressType: InteractionAddressType,
val status: TransactionStatus, val status: TransactionStatus,
val type: TransactionType, val type: TransactionType,
val amount: BigDecimal, val amount: BigDecimal,
) { ) {
sealed interface TransactionDirection {
val address: Address sealed class DestinationType {
data class Single(val addressType: AddressType) : DestinationType()
data class Multiple(val addressTypes: List<AddressType>) : DestinationType()
}
data class Incoming(override val address: Address) : TransactionDirection sealed class SourceType {
data class Outgoing(override val address: Address) : TransactionDirection
data class Single(val address: String) : SourceType()
data class Multiple(val addresses: List<String>) : SourceType()
}
sealed class AddressType {
abstract val address: String
data class User(override val address: String) : AddressType()
data class Contract(override val address: String) : AddressType()
} }
sealed interface TransactionType { sealed interface TransactionType {
object Transfer : TransactionType object Transfer : TransactionType
object Submit : TransactionType
object Approve : TransactionType object Approve : TransactionType
object Supply : TransactionType
object Withdraw : TransactionType
object Deposit : TransactionType
object Swap : TransactionType object Swap : TransactionType
object Unoswap : TransactionType object UnknownOperation : TransactionType
data class Custom(val id: String) : TransactionType data class Operation(val name: String) : TransactionType
} }
sealed class TransactionStatus { sealed class TransactionStatus {
@ -36,8 +46,9 @@ data class TxHistoryItem(
object Confirmed : TransactionStatus() object Confirmed : TransactionStatus()
} }
sealed class Address { sealed class InteractionAddressType {
data class Single(val rawAddress: String) : Address() data class User(val address: String) : InteractionAddressType()
object Multiple : Address() data class Contract(val address: String) : InteractionAddressType()
data class Multiple(val addresses: List<String>) : InteractionAddressType()
} }
} }

View file

@ -101,9 +101,7 @@ internal class TokenDetailsTxHistoryItemFlowConverter(
) { ) {
val txContent = txHistoryItemState.state as TransactionState.Content val txContent = txHistoryItemState.state as TransactionState.Content
txHistoryItemState.copy( txHistoryItemState.copy(
state = txContent.copySealed( state = txContent.copy(timestamp = txContent.timestamp.toTimeFormat()),
timestamp = txContent.timestamp.toTimeFormat(),
),
) )
} else { } else {
txHistoryItemState txHistoryItemState

View file

@ -2,6 +2,9 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.
import com.tangem.core.ui.components.transactions.state.TransactionState import com.tangem.core.ui.components.transactions.state.TransactionState
import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.utils.DateTimeFormatters import com.tangem.core.ui.utils.DateTimeFormatters
import com.tangem.domain.txhistory.models.TxHistoryItem import com.tangem.domain.txhistory.models.TxHistoryItem
import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents
@ -22,121 +25,65 @@ internal class TokenDetailsTxHistoryToTransactionStateConverter(
return createTransactionStateItem(item = value) return createTransactionStateItem(item = value)
} }
// TODO: Finalize transaction types [REDACTED_JIRA]
@Suppress("LongMethod")
private fun createTransactionStateItem(item: TxHistoryItem): TransactionState { private fun createTransactionStateItem(item: TxHistoryItem): TransactionState {
return when (val type = item.type) { return TransactionState.Content(
TxHistoryItem.TransactionType.Transfer -> mapTransfer(item) txHash = item.txHash,
TxHistoryItem.TransactionType.Approve -> mapApprove(item) address = stringReference("mocked address"),
TxHistoryItem.TransactionType.Swap -> mapSwap(item) amount = item.getAmount(),
TxHistoryItem.TransactionType.Deposit -> TransactionState.Custom( timestamp = item.timestampInMillis.toTimeFormat(),
txHash = item.txHash, status = item.status.tiUiStatus(),
address = item.direction.extractAddress(), direction = item.extractDirection(),
amount = item.getAmount(), iconRes = item.extractIcon(),
timestamp = item.timestampInMillis.toTimeFormat(), title = item.extractTitle(),
status = item.status.tiUiStatus(), subtitle = item.extractSubtitle(),
direction = item.direction.toUiDirection(), onClick = { clickIntents.onTransactionClick(item.txHash) },
title = TextReference.Str("Deposit"), )
subtitle = item.direction.extractAddress(), }
onClick = { clickIntents.onTransactionClick(item.txHash) },
) private fun TxHistoryItem.extractIcon(): Int = if (status == TxHistoryItem.TransactionStatus.Failed) {
TxHistoryItem.TransactionType.Submit -> TransactionState.Custom( R.drawable.ic_close_24
txHash = item.txHash, } else {
address = item.direction.extractAddress(), when (type) {
amount = item.getAmount(), is TxHistoryItem.TransactionType.Approve -> R.drawable.ic_doc_24
timestamp = item.timestampInMillis.toTimeFormat(), is TxHistoryItem.TransactionType.Operation,
status = item.status.tiUiStatus(), is TxHistoryItem.TransactionType.Swap,
direction = item.direction.toUiDirection(), is TxHistoryItem.TransactionType.Transfer,
title = TextReference.Str("Submit"), is TxHistoryItem.TransactionType.UnknownOperation,
subtitle = item.direction.extractAddress(), -> if (isOutgoing) R.drawable.ic_arrow_up_24 else R.drawable.ic_arrow_down_24
onClick = { clickIntents.onTransactionClick(item.txHash) },
)
TxHistoryItem.TransactionType.Supply -> TransactionState.Custom(
txHash = item.txHash,
address = item.direction.extractAddress(),
amount = item.getAmount(),
timestamp = item.timestampInMillis.toTimeFormat(),
status = item.status.tiUiStatus(),
direction = item.direction.toUiDirection(),
title = TextReference.Str("Supply"),
subtitle = item.direction.extractAddress(),
onClick = { clickIntents.onTransactionClick(item.txHash) },
)
TxHistoryItem.TransactionType.Unoswap -> TransactionState.Custom(
txHash = item.txHash,
address = item.direction.extractAddress(),
amount = item.getAmount(),
timestamp = item.timestampInMillis.toTimeFormat(),
status = item.status.tiUiStatus(),
direction = item.direction.toUiDirection(),
title = TextReference.Str("Unoswap"),
subtitle = item.direction.extractAddress(),
onClick = { clickIntents.onTransactionClick(item.txHash) },
)
TxHistoryItem.TransactionType.Withdraw -> TransactionState.Custom(
txHash = item.txHash,
address = item.direction.extractAddress(),
amount = item.getAmount(),
timestamp = item.timestampInMillis.toTimeFormat(),
status = item.status.tiUiStatus(),
direction = item.direction.toUiDirection(),
title = TextReference.Str("Withdraw"),
subtitle = item.direction.extractAddress(),
onClick = { clickIntents.onTransactionClick(item.txHash) },
)
is TxHistoryItem.TransactionType.Custom -> TransactionState.Custom(
txHash = item.txHash,
address = item.direction.extractAddress(),
amount = item.getAmount(),
timestamp = item.timestampInMillis.toTimeFormat(),
status = item.status.tiUiStatus(),
direction = item.direction.toUiDirection(),
title = TextReference.Str(type.id),
subtitle = item.direction.extractAddress(),
onClick = { clickIntents.onTransactionClick(item.txHash) },
)
} }
} }
private fun mapTransfer(item: TxHistoryItem): TransactionState { private fun TxHistoryItem.extractTitle(): TextReference = when (val type = type) {
return TransactionState.Transfer( is TxHistoryItem.TransactionType.Approve -> resourceReference(R.string.common_approval)
txHash = item.txHash, is TxHistoryItem.TransactionType.Operation -> stringReference(type.name)
address = item.direction.extractAddress(), is TxHistoryItem.TransactionType.Swap -> resourceReference(R.string.common_swap)
amount = item.getAmount(), is TxHistoryItem.TransactionType.Transfer -> resourceReference(R.string.common_transfer)
timestamp = item.timestampInMillis.toTimeFormat(), is TxHistoryItem.TransactionType.UnknownOperation -> resourceReference(R.string.transaction_history_operation)
status = item.status.tiUiStatus(),
direction = item.direction.toUiDirection(),
onClick = { clickIntents.onTransactionClick(item.txHash) },
)
} }
private fun mapApprove(item: TxHistoryItem): TransactionState { private fun TxHistoryItem.extractSubtitle(): TextReference =
return TransactionState.Approve( when (val interactionAddress = interactionAddressType) {
txHash = item.txHash, is TxHistoryItem.InteractionAddressType.Contract -> resourceReference(
address = item.direction.extractAddress(), id = R.string.transaction_history_contract_address,
amount = item.getAmount(), formatArgs = wrappedList(interactionAddress.address.toBriefAddressFormat()),
timestamp = item.timestampInMillis.toTimeFormat(), )
status = item.status.tiUiStatus(), is TxHistoryItem.InteractionAddressType.Multiple -> resourceReference(
direction = item.direction.toUiDirection(), id = if (isOutgoing) {
onClick = { clickIntents.onTransactionClick(item.txHash) }, R.string.transaction_history_transaction_to_address
) } else {
} R.string.transaction_history_transaction_from_address
private fun mapSwap(item: TxHistoryItem): TransactionState { },
return TransactionState.Swap( formatArgs = wrappedList(resourceReference(R.string.transaction_history_multiple_addresses)),
txHash = item.txHash, )
address = item.direction.extractAddress(), is TxHistoryItem.InteractionAddressType.User -> resourceReference(
amount = item.getAmount(), id = if (isOutgoing) {
timestamp = item.timestampInMillis.toTimeFormat(), R.string.transaction_history_transaction_to_address
status = item.status.tiUiStatus(), } else {
direction = item.direction.toUiDirection(), R.string.transaction_history_transaction_from_address
onClick = { clickIntents.onTransactionClick(item.txHash) }, },
) formatArgs = wrappedList(interactionAddress.address.toBriefAddressFormat()),
} )
}
private fun TxHistoryItem.TransactionDirection.extractAddress(): TextReference = when (val addr = address) {
TxHistoryItem.Address.Multiple -> TextReference.Res(R.string.transaction_history_multiple_addresses)
is TxHistoryItem.Address.Single -> TextReference.Str(addr.rawAddress.toBriefAddressFormat())
}
private fun TxHistoryItem.TransactionStatus.tiUiStatus() = when (this) { private fun TxHistoryItem.TransactionStatus.tiUiStatus() = when (this) {
TxHistoryItem.TransactionStatus.Confirmed -> TransactionState.Content.Status.Confirmed TxHistoryItem.TransactionStatus.Confirmed -> TransactionState.Content.Status.Confirmed
@ -148,16 +95,11 @@ internal class TokenDetailsTxHistoryToTransactionStateConverter(
return DateTimeFormatters.formatTime(time = DateTime(this, DateTimeZone.getDefault())) return DateTimeFormatters.formatTime(time = DateTime(this, DateTimeZone.getDefault()))
} }
private fun TxHistoryItem.TransactionDirection.toUiDirection() = when (this) { private fun TxHistoryItem.extractDirection() =
is TxHistoryItem.TransactionDirection.Incoming -> TransactionState.Content.Direction.INCOMING if (isOutgoing) TransactionState.Content.Direction.OUTGOING else TransactionState.Content.Direction.INCOMING
is TxHistoryItem.TransactionDirection.Outgoing -> TransactionState.Content.Direction.OUTGOING
}
private fun TxHistoryItem.getAmount(): String { private fun TxHistoryItem.getAmount(): String {
val prefix = when (direction) { val prefix = if (isOutgoing) "-" else "+"
is TxHistoryItem.TransactionDirection.Incoming -> "+"
is TxHistoryItem.TransactionDirection.Outgoing -> "-"
}
return prefix + amount.toFormattedCurrencyString(currency = symbol, decimals = decimals) return prefix + amount.toFormattedCurrencyString(currency = symbol, decimals = decimals)
} }
} }

View file

@ -1,7 +1,11 @@
package com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.txhistory package com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.txhistory
import com.tangem.core.ui.components.transactions.state.TransactionState import com.tangem.core.ui.components.transactions.state.TransactionState
import com.tangem.core.ui.components.transactions.state.TransactionState.Content.Direction
import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.domain.txhistory.models.TxHistoryItem import com.tangem.domain.txhistory.models.TxHistoryItem
import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents
import com.tangem.features.tokendetails.impl.R import com.tangem.features.tokendetails.impl.R
@ -19,116 +23,68 @@ internal class TokenDetailsTxHistoryTransactionStateConverter(
return createTransactionStateItem(item = value) return createTransactionStateItem(item = value)
} }
// TODO: Finalize transaction types [REDACTED_JIRA]
@Suppress("LongMethod") @Suppress("LongMethod")
private fun createTransactionStateItem(item: TxHistoryItem): TransactionState { private fun createTransactionStateItem(item: TxHistoryItem): TransactionState {
return when (val type = item.type) { return TransactionState.Content(
TxHistoryItem.TransactionType.Transfer -> mapTransfer(item) txHash = item.txHash,
TxHistoryItem.TransactionType.Approve -> mapApprove(item) address = stringReference("mocked address"),
TxHistoryItem.TransactionType.Swap -> mapSwap(item) amount = item.getAmount(),
TxHistoryItem.TransactionType.Deposit -> TransactionState.Custom( timestamp = item.getRawTimestamp(),
txHash = item.txHash, status = item.status.tiUiStatus(),
address = item.direction.extractAddress(), direction = item.extractDirection(),
amount = item.getAmount(), iconRes = item.extractIcon(),
timestamp = item.getRawTimestamp(), title = item.extractTitle(),
status = item.status.tiUiStatus(), subtitle = item.extractSubtitle(),
direction = item.direction.toUiDirection(), onClick = { clickIntents.onTransactionClick(item.txHash) },
title = TextReference.Str("Deposit"), )
subtitle = item.direction.extractAddress(), }
onClick = { clickIntents.onTransactionClick(item.txHash) },
) private fun TxHistoryItem.extractIcon(): Int = if (status == TxHistoryItem.TransactionStatus.Failed) {
TxHistoryItem.TransactionType.Submit -> TransactionState.Custom( R.drawable.ic_close_24
txHash = item.txHash, } else {
address = item.direction.extractAddress(), when (type) {
amount = item.getAmount(), is TxHistoryItem.TransactionType.Approve -> R.drawable.ic_doc_24
timestamp = item.getRawTimestamp(), is TxHistoryItem.TransactionType.Operation,
status = item.status.tiUiStatus(), is TxHistoryItem.TransactionType.Swap,
direction = item.direction.toUiDirection(), is TxHistoryItem.TransactionType.Transfer,
title = TextReference.Str("Submit"), is TxHistoryItem.TransactionType.UnknownOperation,
subtitle = item.direction.extractAddress(), -> if (isOutgoing) R.drawable.ic_arrow_up_24 else R.drawable.ic_arrow_down_24
onClick = { clickIntents.onTransactionClick(item.txHash) },
)
TxHistoryItem.TransactionType.Supply -> TransactionState.Custom(
txHash = item.txHash,
address = item.direction.extractAddress(),
amount = item.getAmount(),
timestamp = item.getRawTimestamp(),
status = item.status.tiUiStatus(),
direction = item.direction.toUiDirection(),
title = TextReference.Str("Supply"),
subtitle = item.direction.extractAddress(),
onClick = { clickIntents.onTransactionClick(item.txHash) },
)
TxHistoryItem.TransactionType.Unoswap -> TransactionState.Custom(
txHash = item.txHash,
address = item.direction.extractAddress(),
amount = item.getAmount(),
timestamp = item.getRawTimestamp(),
status = item.status.tiUiStatus(),
direction = item.direction.toUiDirection(),
title = TextReference.Str("Unoswap"),
subtitle = item.direction.extractAddress(),
onClick = { clickIntents.onTransactionClick(item.txHash) },
)
TxHistoryItem.TransactionType.Withdraw -> TransactionState.Custom(
txHash = item.txHash,
address = item.direction.extractAddress(),
amount = item.getAmount(),
timestamp = item.getRawTimestamp(),
status = item.status.tiUiStatus(),
direction = item.direction.toUiDirection(),
title = TextReference.Str("Withdraw"),
subtitle = item.direction.extractAddress(),
onClick = { clickIntents.onTransactionClick(item.txHash) },
)
is TxHistoryItem.TransactionType.Custom -> TransactionState.Custom(
txHash = item.txHash,
address = item.direction.extractAddress(),
amount = item.getAmount(),
timestamp = item.getRawTimestamp(),
status = item.status.tiUiStatus(),
direction = item.direction.toUiDirection(),
title = TextReference.Str(type.id),
subtitle = item.direction.extractAddress(),
onClick = { clickIntents.onTransactionClick(item.txHash) },
)
} }
} }
private fun mapTransfer(item: TxHistoryItem): TransactionState { private fun TxHistoryItem.extractTitle(): TextReference = when (val type = type) {
return TransactionState.Transfer( is TxHistoryItem.TransactionType.Approve -> resourceReference(R.string.common_approval)
txHash = item.txHash, is TxHistoryItem.TransactionType.Operation -> stringReference(type.name)
address = item.direction.extractAddress(), is TxHistoryItem.TransactionType.Swap -> resourceReference(R.string.common_swap)
amount = item.getAmount(), is TxHistoryItem.TransactionType.Transfer -> resourceReference(R.string.common_transfer)
timestamp = item.getRawTimestamp(), is TxHistoryItem.TransactionType.UnknownOperation -> resourceReference(R.string.transaction_history_operation)
status = item.status.tiUiStatus(),
direction = item.direction.toUiDirection(),
onClick = { clickIntents.onTransactionClick(item.txHash) },
)
} }
private fun mapApprove(item: TxHistoryItem): TransactionState { private fun TxHistoryItem.extractSubtitle(): TextReference =
return TransactionState.Approve( when (val interactionAddress = interactionAddressType) {
txHash = item.txHash, is TxHistoryItem.InteractionAddressType.Contract -> resourceReference(
address = item.direction.extractAddress(), id = R.string.transaction_history_contract_address,
amount = item.getAmount(), formatArgs = wrappedList(interactionAddress.address.toBriefAddressFormat()),
timestamp = item.getRawTimestamp(), )
status = item.status.tiUiStatus(), is TxHistoryItem.InteractionAddressType.Multiple -> resourceReference(
direction = item.direction.toUiDirection(), id = if (isOutgoing) {
onClick = { clickIntents.onTransactionClick(item.txHash) }, R.string.transaction_history_transaction_to_address
) } else {
} R.string.transaction_history_transaction_from_address
private fun mapSwap(item: TxHistoryItem): TransactionState { },
return TransactionState.Swap( formatArgs = wrappedList(resourceReference(R.string.transaction_history_multiple_addresses)),
txHash = item.txHash, )
address = item.direction.extractAddress(), is TxHistoryItem.InteractionAddressType.User -> resourceReference(
amount = item.getAmount(), id = if (isOutgoing) {
timestamp = item.getRawTimestamp(), R.string.transaction_history_transaction_to_address
status = item.status.tiUiStatus(), } else {
direction = item.direction.toUiDirection(), R.string.transaction_history_transaction_from_address
onClick = { clickIntents.onTransactionClick(item.txHash) }, },
) formatArgs = wrappedList(interactionAddress.address.toBriefAddressFormat()),
} )
}
private fun TxHistoryItem.extractDirection() = if (isOutgoing) Direction.OUTGOING else Direction.INCOMING
/** /**
* Get timestamp without formatting. * Get timestamp without formatting.
@ -138,27 +94,14 @@ internal class TokenDetailsTxHistoryTransactionStateConverter(
*/ */
private fun TxHistoryItem.getRawTimestamp() = this.timestampInMillis.toString() private fun TxHistoryItem.getRawTimestamp() = this.timestampInMillis.toString()
private fun TxHistoryItem.TransactionDirection.extractAddress(): TextReference = when (val addr = address) {
TxHistoryItem.Address.Multiple -> TextReference.Res(R.string.transaction_history_multiple_addresses)
is TxHistoryItem.Address.Single -> TextReference.Str(addr.rawAddress.toBriefAddressFormat())
}
private fun TxHistoryItem.TransactionStatus.tiUiStatus() = when (this) { private fun TxHistoryItem.TransactionStatus.tiUiStatus() = when (this) {
TxHistoryItem.TransactionStatus.Confirmed -> TransactionState.Content.Status.Confirmed TxHistoryItem.TransactionStatus.Confirmed -> TransactionState.Content.Status.Confirmed
TxHistoryItem.TransactionStatus.Failed -> TransactionState.Content.Status.Failed TxHistoryItem.TransactionStatus.Failed -> TransactionState.Content.Status.Failed
TxHistoryItem.TransactionStatus.Unconfirmed -> TransactionState.Content.Status.Unconfirmed TxHistoryItem.TransactionStatus.Unconfirmed -> TransactionState.Content.Status.Unconfirmed
} }
private fun TxHistoryItem.TransactionDirection.toUiDirection() = when (this) {
is TxHistoryItem.TransactionDirection.Incoming -> TransactionState.Content.Direction.INCOMING
is TxHistoryItem.TransactionDirection.Outgoing -> TransactionState.Content.Direction.OUTGOING
}
private fun TxHistoryItem.getAmount(): String { private fun TxHistoryItem.getAmount(): String {
val prefix = when (direction) { val prefix = if (isOutgoing) "-" else "+"
is TxHistoryItem.TransactionDirection.Incoming -> "+"
is TxHistoryItem.TransactionDirection.Outgoing -> "-"
}
return prefix + amount.toFormattedCurrencyString(currency = symbol, decimals = decimals) return prefix + amount.toFormattedCurrencyString(currency = symbol, decimals = decimals)
} }
} }

View file

@ -9,6 +9,7 @@ import com.tangem.core.ui.components.transactions.state.TransactionState
import com.tangem.core.ui.components.transactions.state.TxHistoryState import com.tangem.core.ui.components.transactions.state.TxHistoryState
import com.tangem.core.ui.event.consumedEvent import com.tangem.core.ui.event.consumedEvent
import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.TangemColorPalette import com.tangem.core.ui.res.TangemColorPalette
import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.models.UserWalletId
@ -417,25 +418,31 @@ internal object WalletPreviewData {
listOf( listOf(
TxHistoryState.TxHistoryItemState.GroupTitle("Today"), TxHistoryState.TxHistoryItemState.GroupTitle("Today"),
TxHistoryState.TxHistoryItemState.Transaction( TxHistoryState.TxHistoryItemState.Transaction(
TransactionState.Transfer( TransactionState.Content(
txHash = UUID.randomUUID().toString(), txHash = UUID.randomUUID().toString(),
address = TextReference.Str("33BddS...ga2B"), address = TextReference.Str("33BddS...ga2B"),
amount = "-0.500913 BTC", amount = "-0.500913 BTC",
timestamp = "8:41", timestamp = "8:41",
status = TransactionState.Content.Status.Unconfirmed, status = TransactionState.Content.Status.Unconfirmed,
direction = TransactionState.Content.Direction.OUTGOING, direction = TransactionState.Content.Direction.OUTGOING,
iconRes = com.tangem.core.ui.R.drawable.ic_arrow_up_24,
title = resourceReference(com.tangem.core.ui.R.string.common_transfer),
subtitle = TextReference.Str("33BddS...ga2B"),
onClick = {}, onClick = {},
), ),
), ),
TxHistoryState.TxHistoryItemState.GroupTitle("Yesterday"), TxHistoryState.TxHistoryItemState.GroupTitle("Yesterday"),
TxHistoryState.TxHistoryItemState.Transaction( TxHistoryState.TxHistoryItemState.Transaction(
TransactionState.Transfer( TransactionState.Content(
txHash = UUID.randomUUID().toString(), txHash = UUID.randomUUID().toString(),
address = TextReference.Str("33BddS...ga2B"), address = TextReference.Str("33BddS...ga2B"),
amount = "-0.500913 BTC", amount = "-0.500913 BTC",
timestamp = "8:41", timestamp = "8:41",
status = TransactionState.Content.Status.Confirmed, status = TransactionState.Content.Status.Confirmed,
direction = TransactionState.Content.Direction.OUTGOING, direction = TransactionState.Content.Direction.OUTGOING,
iconRes = com.tangem.core.ui.R.drawable.ic_arrow_up_24,
title = resourceReference(com.tangem.core.ui.R.string.common_transfer),
subtitle = TextReference.Str("33BddS...ga2B"),
onClick = {}, onClick = {},
), ),
), ),

View file

@ -109,9 +109,7 @@ internal class WalletTxHistoryItemFlowConverter(
) { ) {
val txContent = txHistoryItemState.state as TransactionState.Content val txContent = txHistoryItemState.state as TransactionState.Content
txHistoryItemState.copy( txHistoryItemState.copy(
state = txContent.copySealed( state = txContent.copy(timestamp = txContent.timestamp.toTimeFormat()),
timestamp = txContent.timestamp.toTimeFormat(),
),
) )
} else { } else {
txHistoryItemState txHistoryItemState

View file

@ -2,6 +2,9 @@ package com.tangem.feature.wallet.presentation.wallet.state.factory.txhistory
import com.tangem.core.ui.components.transactions.state.TransactionState import com.tangem.core.ui.components.transactions.state.TransactionState
import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.domain.txhistory.models.TxHistoryItem import com.tangem.domain.txhistory.models.TxHistoryItem
import com.tangem.feature.wallet.impl.R import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletClickIntents import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletClickIntents
@ -19,116 +22,69 @@ internal class WalletTxHistoryTransactionStateConverter(
return createTransactionStateItem(item = value) return createTransactionStateItem(item = value)
} }
// TODO: Finalize transaction types [REDACTED_JIRA]
@Suppress("LongMethod") @Suppress("LongMethod")
private fun createTransactionStateItem(item: TxHistoryItem): TransactionState { private fun createTransactionStateItem(item: TxHistoryItem): TransactionState {
return when (val type = item.type) { return TransactionState.Content(
TxHistoryItem.TransactionType.Transfer -> mapTransfer(item) txHash = item.txHash,
TxHistoryItem.TransactionType.Approve -> mapApprove(item) address = stringReference("mocked address"),
TxHistoryItem.TransactionType.Swap -> mapSwap(item) amount = item.getAmount(),
TxHistoryItem.TransactionType.Deposit -> TransactionState.Custom( timestamp = item.getRawTimestamp(),
txHash = item.txHash, status = item.status.tiUiStatus(),
address = item.direction.extractAddress(), direction = item.extractDirection(),
amount = item.getAmount(), iconRes = item.extractIcon(),
timestamp = item.getRawTimestamp(), title = item.extractTitle(),
status = item.status.tiUiStatus(), subtitle = item.extractSubtitle(),
direction = item.direction.toUiDirection(), onClick = { clickIntents.onTransactionClick(item.txHash) },
title = TextReference.Str("Deposit"), )
subtitle = item.direction.extractAddress(), }
onClick = { clickIntents.onTransactionClick(item.txHash) },
) private fun TxHistoryItem.extractIcon(): Int = if (status == TxHistoryItem.TransactionStatus.Failed) {
TxHistoryItem.TransactionType.Submit -> TransactionState.Custom( R.drawable.ic_close_24
txHash = item.txHash, } else {
address = item.direction.extractAddress(), when (type) {
amount = item.getAmount(), is TxHistoryItem.TransactionType.Approve -> R.drawable.ic_doc_24
timestamp = item.getRawTimestamp(), is TxHistoryItem.TransactionType.Operation,
status = item.status.tiUiStatus(), is TxHistoryItem.TransactionType.Swap,
direction = item.direction.toUiDirection(), is TxHistoryItem.TransactionType.Transfer,
title = TextReference.Str("Submit"), is TxHistoryItem.TransactionType.UnknownOperation,
subtitle = item.direction.extractAddress(), -> if (isOutgoing) R.drawable.ic_arrow_up_24 else R.drawable.ic_arrow_down_24
onClick = { clickIntents.onTransactionClick(item.txHash) },
)
TxHistoryItem.TransactionType.Supply -> TransactionState.Custom(
txHash = item.txHash,
address = item.direction.extractAddress(),
amount = item.getAmount(),
timestamp = item.getRawTimestamp(),
status = item.status.tiUiStatus(),
direction = item.direction.toUiDirection(),
title = TextReference.Str("Supply"),
subtitle = item.direction.extractAddress(),
onClick = { clickIntents.onTransactionClick(item.txHash) },
)
TxHistoryItem.TransactionType.Unoswap -> TransactionState.Custom(
txHash = item.txHash,
address = item.direction.extractAddress(),
amount = item.getAmount(),
timestamp = item.getRawTimestamp(),
status = item.status.tiUiStatus(),
direction = item.direction.toUiDirection(),
title = TextReference.Str("Unoswap"),
subtitle = item.direction.extractAddress(),
onClick = { clickIntents.onTransactionClick(item.txHash) },
)
TxHistoryItem.TransactionType.Withdraw -> TransactionState.Custom(
txHash = item.txHash,
address = item.direction.extractAddress(),
amount = item.getAmount(),
timestamp = item.getRawTimestamp(),
status = item.status.tiUiStatus(),
direction = item.direction.toUiDirection(),
title = TextReference.Str("Withdraw"),
subtitle = item.direction.extractAddress(),
onClick = { clickIntents.onTransactionClick(item.txHash) },
)
is TxHistoryItem.TransactionType.Custom -> TransactionState.Custom(
txHash = item.txHash,
address = item.direction.extractAddress(),
amount = item.getAmount(),
timestamp = item.getRawTimestamp(),
status = item.status.tiUiStatus(),
direction = item.direction.toUiDirection(),
title = TextReference.Str(type.id),
subtitle = item.direction.extractAddress(),
onClick = { clickIntents.onTransactionClick(item.txHash) },
)
} }
} }
private fun mapTransfer(item: TxHistoryItem): TransactionState { private fun TxHistoryItem.extractTitle(): TextReference = when (val type = type) {
return TransactionState.Transfer( is TxHistoryItem.TransactionType.Approve -> resourceReference(R.string.common_approval)
txHash = item.txHash, is TxHistoryItem.TransactionType.Operation -> stringReference(type.name)
address = item.direction.extractAddress(), is TxHistoryItem.TransactionType.Swap -> resourceReference(R.string.common_swap)
amount = item.getAmount(), is TxHistoryItem.TransactionType.Transfer -> resourceReference(R.string.common_transfer)
timestamp = item.getRawTimestamp(), is TxHistoryItem.TransactionType.UnknownOperation -> resourceReference(R.string.transaction_history_operation)
status = item.status.tiUiStatus(),
direction = item.direction.toUiDirection(),
onClick = { clickIntents.onTransactionClick(item.txHash) },
)
} }
private fun mapApprove(item: TxHistoryItem): TransactionState { private fun TxHistoryItem.extractSubtitle(): TextReference =
return TransactionState.Approve( when (val interactionAddress = interactionAddressType) {
txHash = item.txHash, is TxHistoryItem.InteractionAddressType.Contract -> resourceReference(
address = item.direction.extractAddress(), id = R.string.transaction_history_contract_address,
amount = item.getAmount(), formatArgs = wrappedList(interactionAddress.address.toBriefAddressFormat()),
timestamp = item.getRawTimestamp(), )
status = item.status.tiUiStatus(), is TxHistoryItem.InteractionAddressType.Multiple -> resourceReference(
direction = item.direction.toUiDirection(), id = if (isOutgoing) {
onClick = { clickIntents.onTransactionClick(item.txHash) }, R.string.transaction_history_transaction_to_address
) } else {
} R.string.transaction_history_transaction_from_address
private fun mapSwap(item: TxHistoryItem): TransactionState { },
return TransactionState.Swap( formatArgs = wrappedList(resourceReference(R.string.transaction_history_multiple_addresses)),
txHash = item.txHash, )
address = item.direction.extractAddress(), is TxHistoryItem.InteractionAddressType.User -> resourceReference(
amount = item.getAmount(), id = if (isOutgoing) {
timestamp = item.getRawTimestamp(), R.string.transaction_history_transaction_to_address
status = item.status.tiUiStatus(), } else {
direction = item.direction.toUiDirection(), R.string.transaction_history_transaction_from_address
onClick = { clickIntents.onTransactionClick(item.txHash) }, },
) formatArgs = wrappedList(interactionAddress.address.toBriefAddressFormat()),
} )
}
private fun TxHistoryItem.extractDirection() =
if (isOutgoing) TransactionState.Content.Direction.OUTGOING else TransactionState.Content.Direction.INCOMING
/** /**
* Get timestamp without formatting. * Get timestamp without formatting.
@ -138,27 +94,14 @@ internal class WalletTxHistoryTransactionStateConverter(
*/ */
private fun TxHistoryItem.getRawTimestamp() = this.timestampInMillis.toString() private fun TxHistoryItem.getRawTimestamp() = this.timestampInMillis.toString()
private fun TxHistoryItem.TransactionDirection.extractAddress(): TextReference = when (val addr = address) {
TxHistoryItem.Address.Multiple -> TextReference.Res(R.string.transaction_history_multiple_addresses)
is TxHistoryItem.Address.Single -> TextReference.Str(addr.rawAddress.toBriefAddressFormat())
}
private fun TxHistoryItem.TransactionStatus.tiUiStatus() = when (this) { private fun TxHistoryItem.TransactionStatus.tiUiStatus() = when (this) {
TxHistoryItem.TransactionStatus.Confirmed -> TransactionState.Content.Status.Confirmed TxHistoryItem.TransactionStatus.Confirmed -> TransactionState.Content.Status.Confirmed
TxHistoryItem.TransactionStatus.Failed -> TransactionState.Content.Status.Failed TxHistoryItem.TransactionStatus.Failed -> TransactionState.Content.Status.Failed
TxHistoryItem.TransactionStatus.Unconfirmed -> TransactionState.Content.Status.Unconfirmed TxHistoryItem.TransactionStatus.Unconfirmed -> TransactionState.Content.Status.Unconfirmed
} }
private fun TxHistoryItem.TransactionDirection.toUiDirection() = when (this) {
is TxHistoryItem.TransactionDirection.Incoming -> TransactionState.Content.Direction.INCOMING
is TxHistoryItem.TransactionDirection.Outgoing -> TransactionState.Content.Direction.OUTGOING
}
private fun TxHistoryItem.getAmount(): String { private fun TxHistoryItem.getAmount(): String {
val prefix = when (direction) { val prefix = if (isOutgoing) "-" else "+"
is TxHistoryItem.TransactionDirection.Incoming -> "+"
is TxHistoryItem.TransactionDirection.Outgoing -> "-"
}
return prefix + amount.toFormattedCurrencyString(currency = symbol, decimals = decimals) return prefix + amount.toFormattedCurrencyString(currency = symbol, decimals = decimals)
} }
} }

View file

@ -83,8 +83,8 @@ spr-client = "3.6.2"
# endregion Other libraries # endregion Other libraries
# region Tangem # region Tangem
tangemBlockchainSdk = "release-app_5.0-363" #tangemBlockchainSdk = "release-app_5.0-363"
#tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds
tangemCardSdk = "release-app_5.0-308" tangemCardSdk = "release-app_5.0-308"
#tangemCardSdk = "0.0.1" # Keep it! - used for local builds #tangemCardSdk = "0.0.1" # Keep it! - used for local builds
# endregion Tangem # endregion Tangem