Updated on 2026-08-14

This commit is contained in:
Tangem 2026-06-12 13:43:44 +05:00
parent f6b98ab9fa
commit db16ba6cef
20 changed files with 618 additions and 96 deletions

View file

@ -98,7 +98,7 @@ class TangemLoggingInitializer(
BlockchainSdkConfig.serializer(),
environmentConfig.blockchainSdkConfig,
)
// Drop URL-shaped values (e.g. public endpoint URLs from BlockchainSdkConfig like
// Drop URL-shaped drawable (e.g. public endpoint URLs from BlockchainSdkConfig like
// kaspaSecondaryApiUrl); they are not secrets and would obscure unrelated requests in logs.
val values = JsonStringValuesExtractor.extract(json)
.filter { it.isNotBlank() && !it.startsWith("http", ignoreCase = true) }

View file

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<paths>
<files-path name="files" path="/"/>
<cache-path name="shared_images" path="." />
</paths>

View file

@ -2,9 +2,9 @@ package com.tangem.common.ui.expressStatus
import androidx.compose.runtime.Composable
import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet
import com.tangem.core.ui.res.TangemTheme
data class ExpressStatusBottomSheetConfig(
@ -18,7 +18,7 @@ fun ExpressStatusBottomSheet(config: TangemBottomSheetConfig) {
containerColor = TangemTheme.colors.background.tertiary,
) { content: ExpressStatusBottomSheetConfig ->
when (val state = content.value) {
is ExpressTransactionStateUM.OnrampUM -> OnrampStatusBottomSheetContent(state)
is ExpressTransactionStateUM.OnrampUM -> OnrampStatusBottomSheetContent(state, false)
}
}
}

View file

@ -3,13 +3,7 @@ package com.tangem.common.ui.expressStatus
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
@ -22,11 +16,7 @@ import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.common.ui.R
import com.tangem.common.ui.expressStatus.state.ExpressLinkUM
import com.tangem.common.ui.expressStatus.state.ExpressStatusUM
import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateIconUM
import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateInfoUM
import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM
import com.tangem.common.ui.expressStatus.state.*
import com.tangem.core.ui.components.atoms.text.EllipsisText
import com.tangem.core.ui.components.atoms.text.TextEllipsis
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
@ -221,13 +211,17 @@ private val PreviewExpressTransactionState: ExpressTransactionStateUM = object :
onDisposeExpressStatus = {},
iconState = ExpressTransactionStateIconUM.None,
toAmount = stringReference("0,11441958 BTC"),
toAmountValue = "0.11441958".toBigDecimal(),
toFiatAmount = null,
toAmountSymbol = "BTC",
toCurrencyIcon = CurrencyIconState.Loading,
toAddress = "0x",
fromAmount = stringReference("100 SOL"),
fromAmountValue = "100".toBigDecimal(),
fromFiatAmount = null,
fromAmountSymbol = "SOL",
fromCurrencyIcon = CurrencyIconState.Loading,
fromAddress = "0x",
)
}
// endregion

View file

@ -9,18 +9,16 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment.Companion.CenterHorizontally
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM
import com.tangem.core.ui.R
import com.tangem.core.ui.components.SpacerH10
import com.tangem.core.ui.components.SpacerH12
import com.tangem.core.ui.components.SpacerH16
import com.tangem.core.ui.components.SpacerH24
import com.tangem.core.ui.components.*
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
@Composable
fun OnrampStatusBottomSheetContent(state: ExpressTransactionStateUM.OnrampUM) {
fun OnrampStatusBottomSheetContent(state: ExpressTransactionStateUM.OnrampUM, isExpressShareButtonEnabled: Boolean) {
Column(
modifier = Modifier
.padding(horizontal = TangemTheme.dimens.spacing16)
@ -70,6 +68,10 @@ fun OnrampStatusBottomSheetContent(state: ExpressTransactionStateUM.OnrampUM) {
isAutoDisposable = state.activeStatus.isAutoDisposable,
onClick = state.info.onDisposeExpressStatus,
)
if (isExpressShareButtonEnabled) {
SpacerH(80.dp)
} else {
SpacerH24()
}
}
}

View file

@ -1,10 +1,13 @@
package com.tangem.common.ui.expressStatus.state
import androidx.compose.runtime.Immutable
import com.tangem.common.ui.notifications.NotificationUM
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.extensions.TextReference
import com.tangem.domain.onramp.model.OnrampStatus
import java.math.BigDecimal
@Immutable
interface ExpressTransactionStateUM {
val info: ExpressTransactionStateInfoUM
@ -35,13 +38,17 @@ data class ExpressTransactionStateInfoUM(
val onDisposeExpressStatus: () -> Unit,
val iconState: ExpressTransactionStateIconUM,
val toAmount: TextReference,
val toAmountValue: BigDecimal,
val toFiatAmount: TextReference?,
val toAmountSymbol: String,
val toCurrencyIcon: CurrencyIconState,
val toAddress: String,
val fromAmount: TextReference,
val fromAmountValue: BigDecimal,
val fromFiatAmount: TextReference?,
val fromAmountSymbol: String,
val fromCurrencyIcon: CurrencyIconState,
val fromAddress: String?,
) {
val subtitle: TextReference
get() = buildExpressStatusSubtitle(activeStatus = activeStatus, date = timestampAgoFormatted)

View file

@ -138,5 +138,9 @@
{
"name": "TWI_83_ADDRESS_BOOK_ENABLED",
"version": "undefined"
},
{
"name": "AND_15489_EXPRESS_SHARE_BUTTON_ENABLED",
"version": "undefined"
}
]

View file

@ -8,4 +8,5 @@ interface SwapFeatureToggles {
val isSwapProviderFilterEnabled: Boolean
val isSwapRateExperienceEnabled: Boolean
val isSwapPredefinedButtonsEnabled: Boolean
val isExpressShareButtonEnabled: Boolean
}

View file

@ -6,34 +6,45 @@ import com.tangem.features.swap.SwapFeatureToggles
import javax.inject.Inject
internal class DefaultSwapFeatureToggles @Inject constructor(
featureTogglesManager: FeatureTogglesManager,
private val featureTogglesManager: FeatureTogglesManager,
) : SwapFeatureToggles {
override val isYieldSwapEnabled: Boolean = featureTogglesManager.isFeatureEnabled(
override val isYieldSwapEnabled: Boolean
get() = featureTogglesManager.isFeatureEnabled(
toggle = FeatureToggles.TWI_1326_YIELD_MODE_SWAP_ENABLED,
)
override val isSwapSwitchToTransferEnabled: Boolean = featureTogglesManager.isFeatureEnabled(
override val isSwapSwitchToTransferEnabled: Boolean
get() = featureTogglesManager.isFeatureEnabled(
toggle = FeatureToggles.AND_15207_SWAP_SWITCH_TO_TRANSFER_ENABLED,
)
override val isSwapIntegratedApproveEnabled: Boolean = featureTogglesManager.isFeatureEnabled(
override val isSwapIntegratedApproveEnabled: Boolean
get() = featureTogglesManager.isFeatureEnabled(
toggle = FeatureToggles.AND_15120_SWAP_INTEGRATED_APPROVE,
)
override val isSwapAbEnabled: Boolean = featureTogglesManager.isFeatureEnabled(
override val isSwapAbEnabled: Boolean
get() = featureTogglesManager.isFeatureEnabled(
toggle = FeatureToggles.SWAP_AB_ENABLED,
)
override val isSwapProviderFilterEnabled: Boolean = featureTogglesManager.isFeatureEnabled(
override val isSwapProviderFilterEnabled: Boolean
get() = featureTogglesManager.isFeatureEnabled(
toggle = FeatureToggles.AND_15009_SWAP_PROVIDER_FILTER_ENABLED,
)
override val isSwapRateExperienceEnabled: Boolean = featureTogglesManager.isFeatureEnabled(
override val isSwapRateExperienceEnabled: Boolean
get() = featureTogglesManager.isFeatureEnabled(
toggle = FeatureToggles.AND_15103_SWAP_RATE_EXPERIENCE_ENABLED,
)
override val isSwapPredefinedButtonsEnabled: Boolean = featureTogglesManager.isFeatureEnabled(
override val isSwapPredefinedButtonsEnabled: Boolean
get() = featureTogglesManager.isFeatureEnabled(
toggle = FeatureToggles.AND_15122_SWAP_PREDEFINED_BUTTONS_ENABLED,
)
override val isExpressShareButtonEnabled: Boolean
get() = featureTogglesManager.isFeatureEnabled(
toggle = FeatureToggles.AND_15489_EXPRESS_SHARE_BUTTON_ENABLED,
)
}

View file

@ -3,14 +3,7 @@ package com.tangem.features.tangempay.components.express
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.ui.Modifier
import com.tangem.common.ui.expressStatus.expressTransactionsItemsLegacy
import com.tangem.common.ui.expressStatus.state.ExpressLinkUM
import com.tangem.common.ui.expressStatus.state.ExpressStatusItemState
import com.tangem.common.ui.expressStatus.state.ExpressStatusItemUM
import com.tangem.common.ui.expressStatus.state.ExpressStatusUM
import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateIconUM
import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateInfoUM
import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM
import com.tangem.common.ui.expressStatus.state.ExpressTransactionsBlockState
import com.tangem.common.ui.expressStatus.state.*
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.extensions.TextReference
import com.tangem.domain.onramp.model.OnrampStatus
@ -122,13 +115,17 @@ internal class PreviewEmptyExpressTransactionsComponent : ExpressTransactionsCom
onDisposeExpressStatus = {},
iconState = iconState,
toAmount = TextReference.Str(toAmount),
toAmountValue = toAmount.toBigDecimal(),
toFiatAmount = null,
toAmountSymbol = toSymbol,
toCurrencyIcon = CurrencyIconState.Empty(),
toAddress = "",
fromAmount = TextReference.Str(fromAmount),
fromAmountValue = fromAmount.toBigDecimal(),
fromFiatAmount = null,
fromAmountSymbol = fromSymbol,
fromCurrencyIcon = CurrencyIconState.Empty(),
fromAddress = "",
),
providerName = "Preview Provider",
providerImageUrl = "",

View file

@ -1,5 +1,6 @@
package com.tangem.feature.tokendetails.presentation.tokendetails.state.factory
import com.tangem.common.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
import com.tangem.common.ui.expressStatus.state.*
import com.tangem.common.ui.expressStatus.toActiveStatusText
import com.tangem.common.ui.expressStatus.toIconState
@ -7,7 +8,6 @@ import com.tangem.common.ui.notifications.ExpressNotificationsUM
import com.tangem.common.ui.notifications.NotificationUM
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.common.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.wrappedList
@ -41,6 +41,7 @@ internal class TokenDetailsOnrampTransactionStateConverter(
override fun convert(value: OnrampTransaction): ExpressTransactionStateUM.OnrampUM {
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
val statusValue = cryptoCurrencyStatus?.value
val appCurrency = appCurrencyProvider()
return ExpressTransactionStateUM.OnrampUM(
info = ExpressTransactionStateInfoUM(
@ -63,8 +64,9 @@ internal class TokenDetailsOnrampTransactionStateConverter(
toAmount = stringReference(
value.toAmount.format { crypto(cryptoCurrency) },
),
toAmountValue = value.toAmount,
toFiatAmount = stringReference(
cryptoCurrencyStatus?.value?.fiatRate?.multiply(value.toAmount).format {
statusValue?.fiatRate?.multiply(value.toAmount).format {
fiat(
fiatCurrencyCode = appCurrency.code,
fiatCurrencySymbol = appCurrency.symbol,
@ -73,6 +75,7 @@ internal class TokenDetailsOnrampTransactionStateConverter(
),
toAmountSymbol = cryptoCurrency.symbol,
toCurrencyIcon = iconStateConverter.convert(cryptoCurrency),
toAddress = statusValue?.networkAddress?.defaultAddress?.value.orEmpty(),
fromAmount = stringReference(
value.fromAmount.format {
fiat(
@ -81,12 +84,14 @@ internal class TokenDetailsOnrampTransactionStateConverter(
)
},
),
fromAmountValue = value.fromAmount,
fromFiatAmount = null,
fromAmountSymbol = value.fromCurrency.code,
fromCurrencyIcon = CurrencyIconState.FiatIcon(
url = value.fromCurrency.image,
fallbackResId = R.drawable.ic_currency_24,
),
fromAddress = null,
iconState = value.status.toIconState(),
onGoToProviderClick = { url ->
analyticsEventHandler.send(TokenOnrampAnalyticsEvent.GoToProvider())

View file

@ -1,11 +1,11 @@
package com.tangem.feature.tokendetails.presentation.tokendetails.state.factory
import com.tangem.common.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
import com.tangem.common.ui.expressStatus.state.ExpressLinkUM
import com.tangem.common.ui.expressStatus.state.ExpressStatusUM
import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateIconUM
import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateInfoUM
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.common.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
@ -17,9 +17,9 @@ import com.tangem.core.ui.utils.mapFormattedDate
import com.tangem.core.ui.utils.toDateFormatWithTodayYesterday
import com.tangem.core.ui.utils.toTimeFormat
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.quote.QuoteStatus
import com.tangem.domain.models.quote.mapData
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.tokens.model.analytics.TokenExchangeAnalyticsEvent
import com.tangem.feature.swap.domain.models.domain.ExchangeStatus
@ -34,11 +34,11 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.E
import com.tangem.features.tokendetails.impl.R
import com.tangem.utils.Provider
import com.tangem.utils.converter.Converter
import com.tangem.utils.logging.TangemLogger
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toPersistentList
import com.tangem.utils.logging.TangemLogger
import java.math.BigDecimal
// Fixme [REDACTED_JIRA]
@ -59,7 +59,7 @@ internal class TokenDetailsSwapTransactionsStateConverter(
fun convert(
savedTransactions: List<SavedSwapTransactionListModel>,
quoteStatuses: Set<QuoteStatus>,
accountStatuses: Map<Account, CryptoCurrencyStatus>,
): PersistentList<ExchangeUM> {
val result = mutableListOf<ExchangeUM>()
@ -69,23 +69,25 @@ internal class TokenDetailsSwapTransactionsStateConverter(
val fromCryptoCurrency = swapTransaction.fromCryptoCurrency
val toCryptoCurrencyRawId = swapTransaction.toCryptoCurrency.id.rawCurrencyId
val fromCryptoCurrencyRawId = swapTransaction.fromCryptoCurrency.id.rawCurrencyId
var fromCryptoCurrencyStatus: CryptoCurrencyStatus? = null
var toCryptoCurrencyStatus: CryptoCurrencyStatus? = null
swapTransaction.transactions.forEach { transaction ->
val toAmount = transaction.toCryptoAmount
val fromAmount = transaction.fromCryptoAmount
var toFiatAmount: BigDecimal? = null
var fromFiatAmount: BigDecimal? = null
quoteStatuses.forEach { quote ->
quote.mapData {
if (quote.rawCurrencyId == toCryptoCurrencyRawId) {
toFiatAmount = fiatRate.multiply(toAmount)
}
}
quote.mapData {
if (quote.rawCurrencyId == fromCryptoCurrencyRawId) {
fromFiatAmount = fiatRate.multiply(fromAmount)
accountStatuses.forEach { (account, cryptoCurrencyStatus) ->
if (cryptoCurrencyStatus.currency.id.rawCurrencyId == fromCryptoCurrencyRawId &&
account.userWalletId.stringValue == swapTransaction.fromUserWalletId
) {
fromFiatAmount = cryptoCurrencyStatus.value.fiatRate?.multiply(fromAmount)
fromCryptoCurrencyStatus = cryptoCurrencyStatus
}
if (cryptoCurrencyStatus.currency.id.rawCurrencyId == toCryptoCurrencyRawId &&
account.userWalletId.stringValue == swapTransaction.toUserWalletId
) {
toFiatAmount = cryptoCurrencyStatus.value.fiatRate?.multiply(toAmount)
toCryptoCurrencyStatus = cryptoCurrencyStatus
}
}
val statusModel = transaction.status
@ -111,6 +113,8 @@ internal class TokenDetailsSwapTransactionsStateConverter(
transaction = transaction,
toCryptoCurrency = toCryptoCurrency,
fromCryptoCurrency = fromCryptoCurrency,
fromCryptoCurrencyStatus = fromCryptoCurrencyStatus,
toCryptoCurrencyStatus = toCryptoCurrencyStatus,
toFiatAmount = toFiatAmount,
fromFiatAmount = fromFiatAmount,
),
@ -149,14 +153,19 @@ internal class TokenDetailsSwapTransactionsStateConverter(
)
}
@Suppress("LongParameterList")
private fun createStateInfo(
transaction: SavedSwapTransactionModel,
toCryptoCurrency: CryptoCurrency,
fromCryptoCurrency: CryptoCurrency,
fromCryptoCurrencyStatus: CryptoCurrencyStatus?,
toCryptoCurrencyStatus: CryptoCurrencyStatus?,
toFiatAmount: BigDecimal?,
fromFiatAmount: BigDecimal?,
): ExpressTransactionStateInfoUM {
val timestamp = transaction.timestamp
val fromStatusValue = fromCryptoCurrencyStatus?.value
val toStatusValue = toCryptoCurrencyStatus?.value
return ExpressTransactionStateInfoUM(
title = resourceReference(R.string.express_exchange_by, wrappedList(transaction.provider.name)),
txId = transaction.txId,
@ -169,13 +178,17 @@ internal class TokenDetailsSwapTransactionsStateConverter(
timestampAgoFormatted = mapFormattedDate(timestamp),
activeStatus = getActiveStatusText(transaction.status?.status),
toAmount = getCryptoAmount(transaction.toCryptoAmount, toCryptoCurrency),
toAmountValue = transaction.toCryptoAmount,
toFiatAmount = getFiatAmount(toFiatAmount),
toCurrencyIcon = iconStateConverter.convert(toCryptoCurrency),
toAmountSymbol = toCryptoCurrency.symbol,
toAddress = toStatusValue?.networkAddress?.defaultAddress?.value.orEmpty(),
fromAmount = getCryptoAmount(transaction.fromCryptoAmount, fromCryptoCurrency),
fromAmountValue = transaction.fromCryptoAmount,
fromFiatAmount = getFiatAmount(fromFiatAmount),
fromCurrencyIcon = iconStateConverter.convert(fromCryptoCurrency),
fromAmountSymbol = fromCryptoCurrency.symbol,
fromAddress = fromStatusValue?.networkAddress?.defaultAddress?.value.orEmpty(),
onClick = { clickIntents.onExpressTransactionClick(transaction.txId) },
onGoToProviderClick = { url ->
analyticsEventsHandler.send(

View file

@ -8,12 +8,13 @@ import com.tangem.datasource.local.swap.SwapTransactionStatusStore
import com.tangem.domain.account.status.usecase.GetAccountCurrencyStatusUseCase
import com.tangem.domain.account.status.usecase.ManageCryptoCurrenciesUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.quote.QuoteStatus
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.quotes.QuotesRepository
import com.tangem.domain.pay.usecase.GetPaymentAccountCryptoCurrencyStatusUseCase
import com.tangem.domain.tokens.model.analytics.TokenExchangeAnalyticsEvent
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.feature.swap.domain.SwapTransactionRepository
@ -37,8 +38,8 @@ import kotlinx.coroutines.flow.map
internal class ExchangeStatusFactory @AssistedInject constructor(
private val swapTransactionRepository: SwapTransactionRepository,
private val swapRepository: SwapRepository,
private val quotesRepository: QuotesRepository,
private val getAccountCurrencyStatusUseCase: GetAccountCurrencyStatusUseCase,
private val getPaymentAccountCryptoCurrencyStatusUseCase: GetPaymentAccountCryptoCurrencyStatusUseCase,
private val manageCryptoCurrenciesUseCase: ManageCryptoCurrenciesUseCase,
private val swapTransactionStatusStore: SwapTransactionStatusStore,
private val analyticsEventsHandler: AnalyticsEventHandler,
@ -62,19 +63,23 @@ internal class ExchangeStatusFactory @AssistedInject constructor(
operator fun invoke(): Flow<PersistentList<ExchangeUM>> {
return swapTransactionRepository.getTransactions(
userWallet = userWallet,
cryptoCurrencyId = cryptoCurrency.id,
).conflate()
.map { savedTransactions ->
val quotes = savedTransactions
?.flatMap { setOf(it.fromCryptoCurrency.id, it.toCryptoCurrency.id) }
?.toSet()
?.getQuotesOrEmpty()
?: emptySet()
val accountStatuses = savedTransactions
?.flatMap { swapTransaction ->
setOf(
swapTransaction.fromAccount to swapTransaction.fromCryptoCurrency,
swapTransaction.toAccount to swapTransaction.toCryptoCurrency,
)
}
?.toMap()
?.getStatuses()
.orEmpty()
getExchangeStatusState(
savedTransactions = savedTransactions,
quoteStatuses = quotes,
accountStatuses = accountStatuses,
)
}
}
@ -194,7 +199,7 @@ internal class ExchangeStatusFactory @AssistedInject constructor(
private fun getExchangeStatusState(
savedTransactions: List<SavedSwapTransactionListModel>?,
quoteStatuses: Set<QuoteStatus>,
accountStatuses: Map<Account, CryptoCurrencyStatus>,
): PersistentList<ExchangeUM> {
if (savedTransactions == null) {
return persistentListOf()
@ -202,7 +207,7 @@ internal class ExchangeStatusFactory @AssistedInject constructor(
return swapTransactionsStateConverter.convert(
savedTransactions = savedTransactions,
quoteStatuses = quoteStatuses,
accountStatuses = accountStatuses,
)
}
@ -226,12 +231,24 @@ internal class ExchangeStatusFactory @AssistedInject constructor(
}
}
private suspend fun Set<CryptoCurrency.ID>.getQuotesOrEmpty(): Set<QuoteStatus> {
val rawIds = mapNotNull { it.rawCurrencyId }.toSet()
private suspend fun Map<Account?, CryptoCurrency>.getStatuses(): Map<Account, CryptoCurrencyStatus> {
return mapNotNull { (account, cryptoCurrency) ->
when (account) {
is Account.CryptoPortfolio -> {
val (cryptoPortfolioAccount, currencyStatus) = getAccountCurrencyStatusUseCase.invokeSync(
userWalletId = account.userWalletId,
currency = cryptoCurrency,
).getOrNull() ?: return@mapNotNull null
return runCatching { quotesRepository.getMultiQuoteSyncOrNull(currenciesIds = rawIds) }
.getOrNull()
.orEmpty()
cryptoPortfolioAccount to currencyStatus
}
is Account.Payment -> getPaymentAccountCryptoCurrencyStatusUseCase.invokeSync(
userWalletId = account.userWalletId,
cryptoCurrency = cryptoCurrency,
).getOrNull()
else -> null
}
}.toMap()
}
@AssistedFactory

View file

@ -19,6 +19,7 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.model.ExpressTr
import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.ExchangeStatusNotification
import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExchangeUM
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express.ExpressStatusBottomSheet
import com.tangem.features.swap.SwapFeatureToggles
import com.tangem.utils.Provider
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.assisted.Assisted
@ -45,6 +46,7 @@ internal class ExpressStatusFactory @AssistedInject constructor(
private val analyticsEventsHandler: AnalyticsEventHandler,
onrampStatusFactory: OnrampStatusFactory.Factory,
exchangeStatusFactory: ExchangeStatusFactory.Factory,
private val swapFeatureToggles: SwapFeatureToggles,
) {
private val exchangeStatusFactory by lazy(mode = LazyThreadSafetyMode.NONE) {
@ -212,6 +214,7 @@ internal class ExpressStatusFactory @AssistedInject constructor(
is ExpressStatusBottomSheetConfig -> ExpressStatusBottomSheet(
config = this,
extraContent = extraContent,
isExpressShareButtonEnabled = swapFeatureToggles.isExpressShareButtonEnabled,
)
}
}

View file

@ -1,14 +1,18 @@
package com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express
import android.content.res.Configuration
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig
import com.tangem.common.ui.expressStatus.OnrampStatusBottomSheetContent
import com.tangem.common.ui.expressStatus.state.*
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet
import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM
import com.tangem.core.ui.components.BottomFade
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExchangeUM
@ -17,15 +21,44 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.e
@Composable
internal fun ExpressStatusBottomSheet(
config: TangemBottomSheetConfig,
isExpressShareButtonEnabled: Boolean,
extraContent: (@Composable () -> Unit)? = null,
) {
TangemBottomSheet(
config = config,
containerColor = TangemTheme.colors.background.tertiary,
) { content: ExpressStatusBottomSheetConfig ->
if (isExpressShareButtonEnabled) {
Box {
when (val state = content.value) {
is ExpressTransactionStateUM.OnrampUM -> OnrampStatusBottomSheetContent(state)
is ExchangeUM -> ExchangeStatusBottomSheetContent(state = state, extraContent = extraContent)
is ExpressTransactionStateUM.OnrampUM -> OnrampStatusBottomSheetContent(
state = state,
isExpressShareButtonEnabled = true,
)
is ExchangeUM -> ExchangeStatusBottomSheetContent(
state = state,
extraContent = extraContent,
isExpressShareButtonEnabled = true,
)
}
BottomFade(
backgroundColor = TangemTheme.colors.background.tertiary,
modifier = Modifier.align(Alignment.BottomCenter),
)
ExpressShareContent(state = content.value)
}
} else {
when (val state = content.value) {
is ExpressTransactionStateUM.OnrampUM -> OnrampStatusBottomSheetContent(
state = state,
isExpressShareButtonEnabled = false,
)
is ExchangeUM -> ExchangeStatusBottomSheetContent(
state = state,
extraContent = extraContent,
isExpressShareButtonEnabled = false,
)
}
}
}
}
@ -43,6 +76,7 @@ private fun PreviewExpressStatusBottomSheet(
onDismissRequest = {},
content = param,
),
isExpressShareButtonEnabled = false,
)
}
}

View file

@ -24,7 +24,7 @@ class ExpressStatusBottomSheetStateProvider : PreviewParameterProvider<ExpressSt
get() = sequenceOf(
ExpressStatusBottomSheetConfig(
ExchangeUM(
fromUserWalletId = UserWalletId("AnyUserWalletID"),
fromUserWalletId = UserWalletId("0123456789ABCDEF"),
info = ExpressTransactionStateInfoUM(
title = TextReference.Str("Transaction Status"),
status = ExpressStatusUM(
@ -49,13 +49,17 @@ class ExpressStatusBottomSheetStateProvider : PreviewParameterProvider<ExpressSt
onDisposeExpressStatus = {},
iconState = ExpressTransactionStateIconUM.None,
toAmount = TextReference.Str("0.1 BTC"),
toAmountValue = "0.1".toBigDecimal(),
toFiatAmount = TextReference.Str("$5000"),
toAmountSymbol = "BTC",
toCurrencyIcon = CurrencyIconState.Empty(),
toAddress = "0x",
fromAmount = TextReference.Str("5000 USDT"),
fromAmountValue = "5000".toBigDecimal(),
fromFiatAmount = TextReference.Str("$5000"),
fromAmountSymbol = "USDT",
fromCurrencyIcon = CurrencyIconState.Empty(),
fromAddress = "0x",
),
provider = SwapProvider(
providerId = "1",
@ -67,14 +71,20 @@ class ExpressStatusBottomSheetStateProvider : PreviewParameterProvider<ExpressSt
privacyPolicy = null,
slippage = BigDecimal.ZERO,
),
activeStatus = null,
activeStatus = ExchangeStatus.Failed,
statuses = persistentListOf(
ExchangeStatusState(
status = ExchangeStatus.Exchanging,
text = stringReference("Exchanging"),
isActive = true,
isActive = false,
isDone = false,
),
ExchangeStatusState(
status = ExchangeStatus.Failed,
text = stringReference("Failed"),
isActive = true,
isDone = true,
),
),
notification = ExchangeStatusNotification.LongTimeExchange(
onGoToProviderClick = {},

View file

@ -0,0 +1,409 @@
package com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express
import android.content.ClipData
import android.content.Context
import android.content.Intent
import android.content.res.Configuration
import android.graphics.Bitmap
import android.net.Uri
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithCache
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.PathEffect
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.asAndroidBitmap
import androidx.compose.ui.graphics.layer.GraphicsLayer
import androidx.compose.ui.graphics.rememberGraphicsLayer
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalInspectionMode
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.tooling.preview.Devices
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import androidx.core.content.FileProvider
import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig
import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM
import com.tangem.core.ui.components.SecondaryButtonIconStart
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.ds.image.TangemIcon
import com.tangem.core.ui.ds.image.TangemIconUM
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemColorPalette
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreviewRedesign
import com.tangem.core.ui.res.TangemThemeRedesign
import com.tangem.core.ui.utils.toPx
import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExchangeUM
import com.tangem.features.tokendetails.impl.R
import com.tangem.utils.extensions.stripZeroPlainString
import kotlinx.coroutines.launch
import java.io.File
import java.math.BigDecimal
@Composable
internal fun BoxScope.ExpressShareContent(state: ExpressTransactionStateUM) {
val context = LocalContext.current
val coroutineScope = rememberCoroutineScope()
var graphicsLayer = rememberGraphicsLayer()
val shareText = makeExpressShareContent(state) ?: return
TangemThemeRedesign {
SecondaryButtonIconStart(
text = stringResourceSafe(R.string.common_share),
iconResId = R.drawable.ic_share_24,
onClick = {
coroutineScope.launch {
if (graphicsLayer.size.width > 0 && graphicsLayer.size.height > 0) {
val uri = graphicsLayer.saveAsShareableFile(context)
val shareIntent = if (uri != null) {
Intent().apply {
action = Intent.ACTION_SEND
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) // Temporary external read grant
putExtra(Intent.EXTRA_TEXT, shareText)
putExtra(Intent.EXTRA_STREAM, uri)
clipData = ClipData.newRawUri(null, uri)
type = "image/png"
}
} else {
Intent().apply {
action = Intent.ACTION_SEND
putExtra(Intent.EXTRA_TEXT, shareText)
type = "text/plain"
}
}
context.startActivity(Intent.createChooser(shareIntent, null))
}
}
},
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.align(Alignment.BottomCenter),
)
ExpressShareImageContent(
state = state,
onGraphicsLayer = { layer -> graphicsLayer = layer },
modifier = Modifier.size(0.dp), // size 0 so that no space is used in the UI
)
}
}
@Composable
private fun ExpressShareImageContent(
state: ExpressTransactionStateUM,
onGraphicsLayer: (GraphicsLayer) -> Unit,
modifier: Modifier = Modifier,
) {
Box(modifier = modifier.drawForShare(onGraphicsLayer)) {
Box(
// override the parent size with desired size of the recording
modifier = Modifier
.wrapContentHeight(unbounded = true, align = Alignment.Top)
.wrapContentWidth(unbounded = true, align = Alignment.Start)
.requiredSize(525.dp, 580.dp),
) {
Column(
modifier = Modifier
.matchParentSize()
.background(TangemColorPalette.Black),
) {
Column(
modifier = Modifier.padding(
start = 40.dp,
end = 40.dp,
top = 40.dp,
bottom = 20.dp,
),
) {
Icon(
imageVector = ImageVector.vectorResource(R.drawable.img_tangem_logo_90_24),
contentDescription = null,
tint = TangemColorPalette.White,
)
SpacerH(36.dp)
ExpressShareImageAmount(
prefix = stringResourceSafe(R.string.common_send),
amountValue = state.info.fromAmountValue,
currencyIconState = state.info.fromCurrencyIcon,
currencySymbol = state.info.fromAmountSymbol,
)
ExpressShareImageAddress(
prefix = stringResourceSafe(R.string.common_from),
address = state.info.fromAddress,
)
SpacerH(24.dp)
ExpressShareImageSeparator()
SpacerH(24.dp)
ExpressShareImageAmount(
prefix = stringResourceSafe(R.string.common_receive),
amountValue = state.info.toAmountValue,
currencyIconState = state.info.toCurrencyIcon,
currencySymbol = state.info.toAmountSymbol,
)
ExpressShareImageAddress(
prefix = stringResourceSafe(R.string.common_to),
address = state.info.toAddress,
)
SpacerH(24.dp)
ExpressShareImageProvider(state)
Text(
text = stringResourceSafe(R.string.express_transaction_id, state.info.txExternalId.orEmpty()),
style = TangemTheme.typography2.bodySemibold16,
color = TangemTheme.colors3.text.staticDark.secondary,
)
}
Image(
painter = painterResource(R.drawable.img_share_express_background),
contentDescription = null,
modifier = Modifier,
contentScale = ContentScale.Crop,
)
}
}
}
}
@Composable
private fun ExpressShareImageAmount(
prefix: String,
amountValue: BigDecimal,
currencyIconState: CurrencyIconState,
currencySymbol: String,
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
Text(
text = prefix,
color = TangemTheme.colors3.text.staticDark.primary,
style = TangemTheme.typography2.bodySemibold16,
)
Text(
text = amountValue.stripZeroPlainString(),
color = TangemTheme.colors3.text.staticDark.primary,
style = TangemTheme.typography2.bodySemibold16,
)
CurrencyIcon(
state = currencyIconState,
iconSize = 20.dp,
shouldDisplayNetwork = false,
)
Text(
text = currencySymbol,
color = TangemTheme.colors3.text.staticDark.primary,
style = TangemTheme.typography2.bodySemibold16,
)
}
}
@Composable
private fun ExpressShareImageAddress(prefix: String, address: String?) {
if (address != null) {
Text(
text = "$prefix:",
color = TangemTheme.colors3.text.staticDark.secondary,
)
Text(
text = address,
color = TangemTheme.colors3.text.staticDark.secondary,
)
}
}
@Composable
private fun ExpressShareImageSeparator() {
val width = 1.dp
val height = 39.dp
val dashOnInterval = (width * 2).toPx()
val dashOffInterval = (width * 2).toPx()
val pathEffect = PathEffect.dashPathEffect(
intervals = floatArrayOf(dashOnInterval, dashOffInterval),
)
Canvas(modifier = Modifier.size(width = width, height = height)) {
drawLine(
color = TangemColorPalette.Dark2,
start = Offset(0f, 0f),
end = Offset(size.width, size.height),
strokeWidth = width.toPx(),
cap = StrokeCap.Round,
pathEffect = pathEffect,
)
}
}
@Composable
private fun ExpressShareImageProvider(state: ExpressTransactionStateUM) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
Text(
text = stringResourceSafe(R.string.express_by_provider),
style = TangemTheme.typography2.bodySemibold16,
color = TangemTheme.colors3.text.staticDark.primary,
)
val providerImageUrl = when (state) {
is ExchangeUM -> state.provider.imageLarge
is ExpressTransactionStateUM.OnrampUM -> state.providerImageUrl
else -> null
}
val providerName = when (state) {
is ExchangeUM -> state.provider.name
is ExpressTransactionStateUM.OnrampUM -> state.providerName
else -> null
}
val providerType = when (state) {
is ExchangeUM -> state.provider.type.providerName
is ExpressTransactionStateUM.OnrampUM -> state.providerType
else -> null
}
if (providerImageUrl != null) {
TangemIcon(
tangemIconUM = TangemIconUM.Url(providerImageUrl, fallbackRes = R.drawable.ic_empty_64),
modifier = Modifier.size(20.dp),
)
}
Text(
text = providerName.orEmpty(),
style = TangemTheme.typography2.bodySemibold16,
color = TangemTheme.colors3.text.staticDark.primary,
)
Text(
text = providerType.orEmpty(),
style = TangemTheme.typography2.bodySemibold16,
color = TangemTheme.colors3.text.staticDark.secondary,
)
}
}
@Composable
private fun Modifier.drawForShare(onGraphicsLayer: (GraphicsLayer) -> Unit): Modifier {
val isInspectionMode = LocalInspectionMode.current
return drawWithCache {
// draw to graphics layer
onGraphicsLayer(
obtainGraphicsLayer().apply {
record(
size = IntSize(
width = 525.dp.toPx().toInt(),
height = 580.dp.toPx().toInt(),
),
) {
drawContent()
}
},
)
if (isInspectionMode) {
// draw only for preview mode
onDrawWithContent { drawContent() }
} else {
// leave blank to skip drawing on the screen
onDrawWithContent { }
}
}
}
@Suppress("MagicNumber")
private suspend fun GraphicsLayer.saveAsShareableFile(context: Context): Uri? {
// convert to bitmap
val bitmap = this.toImageBitmap().asAndroidBitmap()
// create file
val cachePath = File(context.cacheDir, "images")
cachePath.mkdir()
val file = File(cachePath, "shared_image.png")
// write bitmap to file as PNG
file.outputStream().use { out ->
bitmap.compress(/* format = */ Bitmap.CompressFormat.PNG, /* quality = */ 100, /* stream = */ out)
out.flush()
}
// Generate secure Content URI using the registered authority
return FileProvider.getUriForFile(
/* context = */ context,
/* authority = */ "${context.packageName}.provider",
/* file = */ file,
)
}
@ReadOnlyComposable
@Composable
internal fun makeExpressShareContent(state: ExpressTransactionStateUM): String? {
val txExternalId = state.info.txExternalId
val txId = if (txExternalId != null) {
stringResourceSafe(R.string.express_transaction_id, txExternalId)
} else {
""
}
val (providerName, providerType) = when (state) {
is ExchangeUM -> state.provider.name to state.provider.type
is ExpressTransactionStateUM.OnrampUM -> state.providerName to state.providerType
else -> return null
}
val providerInfo = "${stringResourceSafe(R.string.express_by_provider)} $providerName $providerType"
val fromAddress = if (state.info.fromAddress != null) {
"${stringResourceSafe(R.string.common_from)}: ${state.info.fromAddress}"
} else {
""
}
val text = """
${stringResourceSafe(R.string.common_tangem)}
${stringResourceSafe(R.string.common_send)} ${state.info.fromAmount.resolveReference()}
$fromAddress
${stringResourceSafe(R.string.common_receive)} ${state.info.toAmount.resolveReference()}
${stringResourceSafe(R.string.common_to)}: ${state.info.toAddress}
$providerInfo
$txId
""".trimIndent()
return text
}
// region Preview
@Composable
@Preview(showBackground = true, widthDp = 360, device = Devices.PIXEL_7_PRO)
@Preview(showBackground = true, widthDp = 360, device = Devices.PIXEL_7_PRO, uiMode = Configuration.UI_MODE_NIGHT_YES)
private fun ExpressShareImageContent_Preview(
@PreviewParameter(ExpressStatusBottomSheetStateProvider::class) param: ExpressStatusBottomSheetConfig,
) {
TangemThemePreviewRedesign {
ExpressShareImageContent(param.value, {}, modifier = Modifier)
}
}
// endregion

View file

@ -10,10 +10,12 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment.Companion.CenterHorizontally
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.tangem.common.ui.expressStatus.ExpressEstimate
import com.tangem.common.ui.expressStatus.ExpressHideButton
import com.tangem.common.ui.expressStatus.ExpressProvider
import com.tangem.core.ui.R
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.SpacerH10
import com.tangem.core.ui.components.SpacerH12
import com.tangem.core.ui.components.SpacerH16
@ -28,7 +30,11 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.state.component
import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExchangeUM
@Composable
internal fun ExchangeStatusBottomSheetContent(state: ExchangeUM, extraContent: (@Composable () -> Unit)? = null) {
internal fun ExchangeStatusBottomSheetContent(
state: ExchangeUM,
isExpressShareButtonEnabled: Boolean,
extraContent: (@Composable () -> Unit)? = null,
) {
Column(
modifier = Modifier
.padding(horizontal = TangemTheme.dimens.spacing16)
@ -87,9 +93,13 @@ internal fun ExchangeStatusBottomSheetContent(state: ExchangeUM, extraContent: (
isAutoDisposable = state.activeStatus?.isAutoDisposable == true,
onClick = state.info.onDisposeExpressStatus,
)
if (isExpressShareButtonEnabled) {
SpacerH(80.dp)
} else {
SpacerH24()
}
}
}
@Composable
private fun Notification(state: ExchangeStatusNotification, activeStatus: ExchangeStatus?) {

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 KiB

View file

@ -63,6 +63,7 @@ internal class SingleWalletOnrampTransactionConverter(
timestampAgoFormatted = mapFormattedDate(value.timestamp),
activeStatus = value.status.toActiveStatusText(currency.name),
toAmount = stringReference(value.toAmount.format { crypto(currency) }),
toAmountValue = value.toAmount,
toFiatAmount = stringReference(
status.fiatRate?.multiply(value.toAmount).format {
fiat(
@ -73,6 +74,7 @@ internal class SingleWalletOnrampTransactionConverter(
),
toAmountSymbol = currency.symbol,
toCurrencyIcon = iconStateConverter.convert(currency),
toAddress = status.networkAddress?.defaultAddress?.value.orEmpty(),
fromAmount = stringReference(
value.fromAmount.format {
fiat(
@ -81,12 +83,14 @@ internal class SingleWalletOnrampTransactionConverter(
)
},
),
fromAmountValue = value.fromAmount,
fromFiatAmount = null,
fromAmountSymbol = value.fromCurrency.code,
fromCurrencyIcon = CurrencyIconState.FiatIcon(
url = value.fromCurrency.image,
fallbackResId = R.drawable.ic_currency_24,
),
fromAddress = null,
iconState = value.status.toIconState(),
onGoToProviderClick = { url ->
analyticsEventHandler.send(TokenOnrampAnalyticsEvent.GoToProvider())