Updated on 2026-08-14
This commit is contained in:
parent
e72c2c87bc
commit
5e78d82235
31 changed files with 976 additions and 123 deletions
|
|
@ -31,6 +31,13 @@ internal fun ContentIcon(
|
|||
alpha = alpha,
|
||||
colorFilter = colorFilter,
|
||||
)
|
||||
is CurrencyIconState.FiatIcon -> CoinIcon(
|
||||
modifier = modifier,
|
||||
url = icon.url,
|
||||
fallbackResId = icon.fallbackResId,
|
||||
alpha = alpha,
|
||||
colorFilter = colorFilter,
|
||||
)
|
||||
is CurrencyIconState.TokenIcon -> TokenIcon(
|
||||
modifier = modifier,
|
||||
url = icon.url,
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ fun CurrencyIcon(state: CurrencyIconState, modifier: Modifier = Modifier, should
|
|||
is CurrencyIconState.Locked -> LockedIcon(modifier = iconModifier)
|
||||
is CurrencyIconState.Empty -> EmptyIcon(resId = state.resId, modifier = iconModifier)
|
||||
is CurrencyIconState.CoinIcon,
|
||||
is CurrencyIconState.FiatIcon,
|
||||
is CurrencyIconState.CustomTokenIcon,
|
||||
is CurrencyIconState.TokenIcon,
|
||||
-> {
|
||||
|
|
|
|||
|
|
@ -71,6 +71,21 @@ sealed class CurrencyIconState {
|
|||
override val showCustomBadge: Boolean = true,
|
||||
) : CurrencyIconState()
|
||||
|
||||
/**
|
||||
* Represents a fiat icon.
|
||||
*
|
||||
* @property url The URL where the fiat icon can be fetched from. May be `null` if not found.
|
||||
* @property fallbackResId The drawable resource ID to be used as a fallback if the URL is not available.
|
||||
*/
|
||||
data class FiatIcon(
|
||||
val url: String?,
|
||||
@DrawableRes val fallbackResId: Int,
|
||||
) : CurrencyIconState() {
|
||||
override val isGrayscale: Boolean = false
|
||||
override val showCustomBadge: Boolean = false
|
||||
override val topBadgeIconResId: Int? = null
|
||||
}
|
||||
|
||||
data object Loading : CurrencyIconState() {
|
||||
override val isGrayscale: Boolean = false
|
||||
override val showCustomBadge: Boolean = false
|
||||
|
|
@ -113,6 +128,7 @@ sealed class CurrencyIconState {
|
|||
is Loading,
|
||||
is Locked,
|
||||
is Empty,
|
||||
is FiatIcon,
|
||||
-> this
|
||||
}
|
||||
}
|
||||
|
|
@ -37,10 +37,10 @@ import com.tangem.core.ui.res.TangemThemePreview
|
|||
fun InputRowApprox(
|
||||
leftIcon: CurrencyIconState,
|
||||
leftTitle: TextReference,
|
||||
leftSubtitle: TextReference,
|
||||
leftSubtitle: TextReference?,
|
||||
rightIcon: CurrencyIconState,
|
||||
rightTitle: TextReference,
|
||||
rightSubtitle: TextReference,
|
||||
rightSubtitle: TextReference?,
|
||||
modifier: Modifier = Modifier,
|
||||
leftTitleEllipsisOffset: Int = 0,
|
||||
rightTitleEllipsisOffset: Int = 0,
|
||||
|
|
@ -69,7 +69,7 @@ fun InputRowApprox(
|
|||
tint = TangemTheme.colors.text.tertiary,
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
horizontal = TangemTheme.dimens.spacing4,
|
||||
horizontal = TangemTheme.dimens.spacing8,
|
||||
vertical = TangemTheme.dimens.spacing10,
|
||||
),
|
||||
)
|
||||
|
|
@ -88,12 +88,13 @@ fun InputRowApprox(
|
|||
private fun InputRowApproxItem(
|
||||
iconState: CurrencyIconState,
|
||||
title: TextReference,
|
||||
subtitle: TextReference,
|
||||
subtitle: TextReference?,
|
||||
modifier: Modifier = Modifier,
|
||||
titleEllipsisOffset: Int = 0,
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
CurrencyIcon(
|
||||
state = iconState,
|
||||
|
|
@ -112,13 +113,15 @@ private fun InputRowApproxItem(
|
|||
color = TangemTheme.colors.text.primary1,
|
||||
ellipsis = TextEllipsis.OffsetEnd(titleEllipsisOffset),
|
||||
)
|
||||
EllipsisText(
|
||||
text = subtitle.resolveReference(),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
modifier = Modifier
|
||||
.padding(top = TangemTheme.dimens.spacing2),
|
||||
)
|
||||
if (subtitle != null) {
|
||||
EllipsisText(
|
||||
text = subtitle.resolveReference(),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
modifier = Modifier
|
||||
.padding(top = TangemTheme.dimens.spacing2),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -154,6 +157,18 @@ private fun InputRowApproxPreview() {
|
|||
modifier = Modifier
|
||||
.background(TangemTheme.colors.background.action),
|
||||
)
|
||||
InputRowApprox(
|
||||
leftIcon = CurrencyIconState.Loading,
|
||||
leftTitle = TextReference.Str("Left title Left title Left title Left title Left title USD"),
|
||||
leftSubtitle = null,
|
||||
leftTitleEllipsisOffset = 3,
|
||||
rightIcon = CurrencyIconState.Loading,
|
||||
rightTitle = TextReference.Str("Right title USD"),
|
||||
rightSubtitle = null,
|
||||
rightTitleEllipsisOffset = 3,
|
||||
modifier = Modifier
|
||||
.background(TangemTheme.colors.background.action),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
package com.tangem.data.onramp
|
||||
|
||||
import com.tangem.data.onramp.converters.TransactionConverter
|
||||
import com.tangem.data.onramp.models.OnrampTransactionDTO
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.datasource.local.preferences.utils.getObjectSet
|
||||
|
|
@ -19,17 +21,21 @@ internal class DefaultOnrampTransactionRepository(
|
|||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : OnrampTransactionRepository {
|
||||
|
||||
private val transactionConverter = TransactionConverter()
|
||||
|
||||
override suspend fun storeTransaction(transaction: OnrampTransaction) {
|
||||
withContext(dispatchers.io) {
|
||||
appPreferencesStore.editData { mutablePreferences ->
|
||||
val stored = mutablePreferences.getObjectSet<OnrampTransaction>(
|
||||
val stored = mutablePreferences.getObjectSet<OnrampTransactionDTO>(
|
||||
PreferencesKeys.ONRAMP_TRANSACTIONS_STATUSES_KEY,
|
||||
)
|
||||
val updated = stored?.toMutableSet()
|
||||
?.addOrReplace(transaction) { it.txId == transaction.txId }
|
||||
?: mutableSetOf(transaction)
|
||||
)?.map(transactionConverter::convert) ?: mutableSetOf()
|
||||
|
||||
mutablePreferences.setObjectSet(
|
||||
val updated = stored.toMutableSet()
|
||||
.addOrReplace(transaction) { it.txId == transaction.txId }
|
||||
.map(transactionConverter::convertBack)
|
||||
.toSet()
|
||||
|
||||
mutablePreferences.setObjectSet<OnrampTransactionDTO>(
|
||||
key = PreferencesKeys.ONRAMP_TRANSACTIONS_STATUSES_KEY,
|
||||
value = updated,
|
||||
)
|
||||
|
|
@ -38,9 +44,9 @@ internal class DefaultOnrampTransactionRepository(
|
|||
}
|
||||
|
||||
override suspend fun getTransactionById(txId: String): OnrampTransaction? = withContext(dispatchers.io) {
|
||||
val stored = appPreferencesStore.getObjectSetSync<OnrampTransaction>(
|
||||
val stored = appPreferencesStore.getObjectSetSync<OnrampTransactionDTO>(
|
||||
PreferencesKeys.ONRAMP_TRANSACTIONS_STATUSES_KEY,
|
||||
)
|
||||
).map(transactionConverter::convert)
|
||||
|
||||
stored.firstOrNull { it.txId == txId }
|
||||
}
|
||||
|
|
@ -49,24 +55,24 @@ internal class DefaultOnrampTransactionRepository(
|
|||
userWalletId: UserWalletId,
|
||||
cryptoCurrencyId: CryptoCurrency.ID,
|
||||
): Flow<List<OnrampTransaction>> = appPreferencesStore
|
||||
.getObjectSet<OnrampTransaction>(PreferencesKeys.ONRAMP_TRANSACTIONS_STATUSES_KEY)
|
||||
.getObjectSet<OnrampTransactionDTO>(PreferencesKeys.ONRAMP_TRANSACTIONS_STATUSES_KEY)
|
||||
.map { transactions ->
|
||||
transactions.filter {
|
||||
it.userWalletId == userWalletId && it.toCurrencyId == cryptoCurrencyId.value
|
||||
}
|
||||
}.map(transactionConverter::convert)
|
||||
}
|
||||
|
||||
override suspend fun removeTransaction(txId: String) {
|
||||
withContext(dispatchers.io) {
|
||||
appPreferencesStore.editData { mutablePreferences ->
|
||||
runCatching {
|
||||
val stored = mutablePreferences.getObjectSet<OnrampTransaction>(
|
||||
val stored = mutablePreferences.getObjectSet<OnrampTransactionDTO>(
|
||||
PreferencesKeys.ONRAMP_TRANSACTIONS_STATUSES_KEY,
|
||||
)?.toMutableSet()
|
||||
|
||||
stored?.removeIf { it.txId == txId }
|
||||
|
||||
mutablePreferences.setObjectSet(
|
||||
mutablePreferences.setObjectSet<OnrampTransactionDTO>(
|
||||
key = PreferencesKeys.ONRAMP_TRANSACTIONS_STATUSES_KEY,
|
||||
value = stored ?: emptySet(),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
package com.tangem.data.onramp.converters
|
||||
|
||||
import com.tangem.data.onramp.models.OnrampTransactionDTO
|
||||
import com.tangem.datasource.api.onramp.models.response.Status
|
||||
import com.tangem.domain.onramp.model.OnrampStatus
|
||||
import com.tangem.domain.onramp.model.cache.OnrampTransaction
|
||||
import com.tangem.utils.converter.TwoWayConverter
|
||||
|
||||
internal class TransactionConverter : TwoWayConverter<OnrampTransactionDTO, OnrampTransaction> {
|
||||
|
||||
private val currencyConverter = CurrencyConverter()
|
||||
|
||||
override fun convert(value: OnrampTransactionDTO): OnrampTransaction {
|
||||
return OnrampTransaction(
|
||||
txId = value.txId,
|
||||
userWalletId = value.userWalletId,
|
||||
fromAmount = value.fromAmount,
|
||||
fromCurrency = currencyConverter.convert(value.fromCurrency),
|
||||
toAmount = value.toAmount,
|
||||
toCurrencyId = value.toCurrencyId,
|
||||
status = OnrampStatus.Status.valueOf(value.status.name),
|
||||
externalTxUrl = value.externalTxUrl,
|
||||
externalTxId = value.externalTxId,
|
||||
timestamp = value.timestamp,
|
||||
providerName = value.providerName,
|
||||
providerImageUrl = value.providerImageUrl,
|
||||
providerType = value.providerType,
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
override fun convertBack(value: OnrampTransaction): OnrampTransactionDTO {
|
||||
return OnrampTransactionDTO(
|
||||
txId = value.txId,
|
||||
userWalletId = value.userWalletId,
|
||||
fromAmount = value.fromAmount,
|
||||
fromCurrency = currencyConverter.convertBack(value.fromCurrency),
|
||||
toAmount = value.toAmount,
|
||||
toCurrencyId = value.toCurrencyId,
|
||||
status = Status.valueOf(value.status.name),
|
||||
externalTxUrl = value.externalTxUrl,
|
||||
externalTxId = value.externalTxId,
|
||||
timestamp = value.timestamp,
|
||||
providerName = value.providerName,
|
||||
providerImageUrl = value.providerImageUrl,
|
||||
providerType = value.providerType,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
package com.tangem.data.onramp.models
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.tangem.datasource.api.onramp.models.response.Status
|
||||
import com.tangem.datasource.api.onramp.models.response.model.OnrampCurrencyDTO
|
||||
import com.tangem.domain.core.serialization.SerializedBigDecimal
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
/**
|
||||
* Model for local storing onramp transaction
|
||||
*
|
||||
* @property txId Inner express tx id
|
||||
* @property userWalletId Wallet where tx was performed
|
||||
* @property fromAmount Onramp amount to buy crypto
|
||||
* @property fromCurrency Onramp currency
|
||||
* @property toAmount Receiving crypto amount
|
||||
* @property toCurrencyId Receiving crypto currency id [CryptoCurrency.ID.value]
|
||||
* @property status Transaction status
|
||||
* @property externalTxUrl Link to transaction on provider side
|
||||
* @property externalTxId Id of transaction on provider side
|
||||
* @property timestamp Transaction create time
|
||||
* @property providerName Provider name
|
||||
* @property providerImageUrl Provider image link
|
||||
* @property providerType Provider type
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class OnrampTransactionDTO(
|
||||
@Json(name = "txId")
|
||||
val txId: String,
|
||||
@Json(name = "userWalletId")
|
||||
val userWalletId: UserWalletId,
|
||||
@Json(name = "fromAmount")
|
||||
val fromAmount: SerializedBigDecimal,
|
||||
@Json(name = "fromCurrency")
|
||||
val fromCurrency: OnrampCurrencyDTO,
|
||||
@Json(name = "toAmount")
|
||||
val toAmount: SerializedBigDecimal,
|
||||
@Json(name = "toCurrencyId")
|
||||
val toCurrencyId: String,
|
||||
@Json(name = "status")
|
||||
val status: Status,
|
||||
@Json(name = "externalTxUrl")
|
||||
val externalTxUrl: String?,
|
||||
@Json(name = "externalTxId")
|
||||
val externalTxId: String?,
|
||||
@Json(name = "timestamp")
|
||||
val timestamp: Long,
|
||||
@Json(name = "providerName")
|
||||
val providerName: String, // todo onramp fix after SwapProvider moved to own module
|
||||
@Json(name = "providerImageUrl")
|
||||
val providerImageUrl: String, // todo onramp fix after SwapProvider moved to own module
|
||||
@Json(name = "providerType")
|
||||
val providerType: String, // todo onramp fix after SwapProvider moved to own module
|
||||
)
|
||||
|
|
@ -1,8 +1,5 @@
|
|||
package com.tangem.domain.onramp.model
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class OnrampStatus(
|
||||
val txId: String,
|
||||
val providerId: String,
|
||||
|
|
|
|||
|
|
@ -2,10 +2,26 @@ package com.tangem.domain.onramp.model.cache
|
|||
|
||||
import com.tangem.domain.core.serialization.SerializedBigDecimal
|
||||
import com.tangem.domain.onramp.model.OnrampCurrency
|
||||
import com.tangem.domain.onramp.model.OnrampStatus
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
/**
|
||||
* Model for local storing onramp transaction
|
||||
*
|
||||
* @property txId Inner express tx id
|
||||
* @property userWalletId Wallet where tx was performed
|
||||
* @property fromAmount Onramp amount to buy crypto
|
||||
* @property fromCurrency Onramp currency
|
||||
* @property toAmount Receiving crypto amount
|
||||
* @property toCurrencyId Receiving crypto currency id [CryptoCurrency.ID.value]
|
||||
* @property status Transaction status
|
||||
* @property externalTxUrl Link to transaction on provider side
|
||||
* @property externalTxId Id of transaction on provider side
|
||||
* @property timestamp Transaction create time
|
||||
* @property providerName Provider name
|
||||
* @property providerImageUrl Provider image link
|
||||
* @property providerType Provider type
|
||||
*/
|
||||
data class OnrampTransaction(
|
||||
val txId: String,
|
||||
val userWalletId: UserWalletId,
|
||||
|
|
@ -13,6 +29,11 @@ data class OnrampTransaction(
|
|||
val fromCurrency: OnrampCurrency,
|
||||
val toAmount: SerializedBigDecimal,
|
||||
val toCurrencyId: String,
|
||||
val providerName: String,
|
||||
val providerImageUrl: String,
|
||||
val status: OnrampStatus.Status,
|
||||
val externalTxUrl: String?,
|
||||
val externalTxId: String?,
|
||||
val timestamp: Long,
|
||||
val providerName: String, // todo onramp fix after SwapProvider moved to own module
|
||||
val providerImageUrl: String, // todo onramp fix after SwapProvider moved to own module
|
||||
val providerType: String, // todo onramp fix after SwapProvider moved to own module
|
||||
)
|
||||
|
|
@ -80,6 +80,8 @@ dependencies {
|
|||
implementation(projects.domain.transaction)
|
||||
implementation(projects.domain.staking)
|
||||
implementation(projects.domain.markets.models)
|
||||
implementation(projects.domain.onramp)
|
||||
implementation(projects.domain.onramp.models)
|
||||
|
||||
/** Temp dependency to swap domain */
|
||||
implementation(projects.features.swap.domain)
|
||||
|
|
|
|||
|
|
@ -308,6 +308,7 @@ internal object TokenDetailsPreviewData {
|
|||
dialogConfig = null,
|
||||
pendingTxs = persistentListOf(),
|
||||
swapTxs = persistentListOf(),
|
||||
onrampTxs = persistentListOf(),
|
||||
pullToRefreshConfig = pullToRefreshConfig,
|
||||
bottomSheetConfig = null,
|
||||
isBalanceHidden = false,
|
||||
|
|
@ -338,6 +339,7 @@ internal object TokenDetailsPreviewData {
|
|||
dialogConfig = null,
|
||||
pendingTxs = persistentListOf(),
|
||||
swapTxs = persistentListOf(),
|
||||
onrampTxs = persistentListOf(),
|
||||
pullToRefreshConfig = pullToRefreshConfig,
|
||||
bottomSheetConfig = null,
|
||||
isBalanceHidden = false,
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ internal data class SwapTransactionsState(
|
|||
val toFiatAmount: String,
|
||||
val toCurrencyIcon: CurrencyIconState,
|
||||
val fromCryptoCurrency: CryptoCurrency,
|
||||
val fromCryptoAmount: String,
|
||||
val fromCryptoAmount: TextReference,
|
||||
val fromFiatAmount: String,
|
||||
val fromCurrencyIcon: CurrencyIconState,
|
||||
val showProviderLink: Boolean,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import com.tangem.core.ui.extensions.TextReference
|
|||
import com.tangem.core.ui.pullToRefresh.PullToRefreshConfig
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsDialogConfig
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsNotification
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExpressTransactionStateUM
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.PersistentList
|
||||
|
||||
|
|
@ -21,6 +22,7 @@ internal data class TokenDetailsState(
|
|||
val notifications: ImmutableList<TokenDetailsNotification>,
|
||||
val pendingTxs: PersistentList<TransactionState>,
|
||||
val swapTxs: PersistentList<SwapTransactionsState>,
|
||||
val onrampTxs: PersistentList<ExpressTransactionStateUM.OnrampUM>,
|
||||
val txHistoryState: TxHistoryState,
|
||||
val dialogConfig: TokenDetailsDialogConfig?,
|
||||
val pullToRefreshConfig: PullToRefreshConfig,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,57 @@
|
|||
package com.tangem.feature.tokendetails.presentation.tokendetails.state.express
|
||||
|
||||
import com.tangem.common.ui.expressStatus.state.ExpressStatusUM
|
||||
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 com.tangem.feature.swap.domain.models.domain.ExchangeStatus
|
||||
import com.tangem.feature.swap.domain.models.domain.SwapProvider
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.ExchangeStatusState
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.ExchangeStatusNotifications
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
internal sealed class ExpressTransactionStateUM {
|
||||
|
||||
abstract val info: ExpressTransactionStateInfoUM
|
||||
|
||||
data class ExchangeUM(
|
||||
override val info: ExpressTransactionStateInfoUM,
|
||||
val provider: SwapProvider,
|
||||
val activeStatus: ExchangeStatus?,
|
||||
val statuses: ImmutableList<ExchangeStatusState>,
|
||||
val notification: ExchangeStatusNotifications? = null,
|
||||
val showProviderLink: Boolean,
|
||||
val isRefundTerminalStatus: Boolean,
|
||||
) : ExpressTransactionStateUM()
|
||||
|
||||
data class OnrampUM(
|
||||
override val info: ExpressTransactionStateInfoUM,
|
||||
val providerName: String, // todo onramp fix after SwapProvider moved to own module
|
||||
val providerImageUrl: String, // todo onramp fix after SwapProvider moved to own module
|
||||
val providerType: String, // todo onramp fix after SwapProvider moved to own module
|
||||
val cryptoCurrencyName: String,
|
||||
val activeStatus: OnrampStatus.Status,
|
||||
) : ExpressTransactionStateUM()
|
||||
}
|
||||
|
||||
internal data class ExpressTransactionStateInfoUM(
|
||||
val status: ExpressStatusUM,
|
||||
val notification: NotificationUM?,
|
||||
val txId: String,
|
||||
val txUrl: String?,
|
||||
val txExternalId: String?,
|
||||
val timestamp: TextReference,
|
||||
val onGoToProviderClick: (String) -> Unit,
|
||||
val onClick: () -> Unit,
|
||||
|
||||
val toAmount: TextReference,
|
||||
val toFiatAmount: TextReference?,
|
||||
val toAmountSymbol: String,
|
||||
val toCurrencyIcon: CurrencyIconState,
|
||||
|
||||
val fromAmount: TextReference,
|
||||
val fromFiatAmount: TextReference?,
|
||||
val fromAmountSymbol: String,
|
||||
val fromCurrencyIcon: CurrencyIconState,
|
||||
)
|
||||
|
|
@ -0,0 +1,245 @@
|
|||
package com.tangem.feature.tokendetails.presentation.tokendetails.state.factory
|
||||
|
||||
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.notifications.ExpressNotificationsUM
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.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
|
||||
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.core.ui.utils.DateTimeFormatters
|
||||
import com.tangem.core.ui.utils.toTimeFormat
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.onramp.model.OnrampStatus
|
||||
import com.tangem.domain.onramp.model.cache.OnrampTransaction
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExpressTransactionStateInfoUM
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExpressTransactionStateUM
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents
|
||||
import com.tangem.features.tokendetails.impl.R
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.converter.Converter
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
internal class TokenDetailsOnrampTransactionStateConverter(
|
||||
private val clickIntents: TokenDetailsClickIntents,
|
||||
private val cryptoCurrency: CryptoCurrency,
|
||||
private val cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus?>,
|
||||
private val appCurrencyProvider: Provider<AppCurrency>,
|
||||
) : Converter<OnrampTransaction, ExpressTransactionStateUM.OnrampUM> {
|
||||
|
||||
private val iconStateConverter = CryptoCurrencyToIconStateConverter()
|
||||
|
||||
override fun convert(value: OnrampTransaction): ExpressTransactionStateUM.OnrampUM {
|
||||
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
|
||||
val appCurrency = appCurrencyProvider()
|
||||
return ExpressTransactionStateUM.OnrampUM(
|
||||
info = ExpressTransactionStateInfoUM(
|
||||
status = convertStatuses(value.status, value.externalTxUrl),
|
||||
notification = getNotification(value.status, value.externalTxUrl),
|
||||
txId = value.txId,
|
||||
txExternalId = value.externalTxId,
|
||||
txUrl = value.externalTxUrl,
|
||||
timestamp = resourceReference(
|
||||
R.string.send_date_format,
|
||||
wrappedList(
|
||||
value.timestamp.toTimeFormat(DateTimeFormatters.dateFormatter),
|
||||
value.timestamp.toTimeFormat(),
|
||||
),
|
||||
),
|
||||
toAmount = stringReference(
|
||||
value.toAmount.format { crypto(cryptoCurrency) },
|
||||
),
|
||||
toFiatAmount = stringReference(
|
||||
cryptoCurrencyStatus?.value?.fiatRate?.multiply(value.toAmount).format {
|
||||
fiat(
|
||||
fiatCurrencyCode = appCurrency.code,
|
||||
fiatCurrencySymbol = appCurrency.symbol,
|
||||
)
|
||||
},
|
||||
),
|
||||
toAmountSymbol = cryptoCurrency.symbol,
|
||||
toCurrencyIcon = iconStateConverter.convert(cryptoCurrency),
|
||||
fromAmount = stringReference(
|
||||
value.fromAmount.format {
|
||||
fiat(
|
||||
fiatCurrencyCode = value.fromCurrency.name,
|
||||
fiatCurrencySymbol = value.fromCurrency.code,
|
||||
)
|
||||
},
|
||||
),
|
||||
fromFiatAmount = null,
|
||||
fromAmountSymbol = value.fromCurrency.code,
|
||||
fromCurrencyIcon = CurrencyIconState.FiatIcon(
|
||||
url = value.fromCurrency.image,
|
||||
fallbackResId = R.drawable.ic_currency_24,
|
||||
),
|
||||
onGoToProviderClick = clickIntents::onGoToProviderClick,
|
||||
onClick = { clickIntents.onOnrampTransactionClick(value.txId) },
|
||||
),
|
||||
providerName = value.providerName,
|
||||
providerImageUrl = value.providerImageUrl,
|
||||
providerType = value.providerType,
|
||||
cryptoCurrencyName = cryptoCurrency.name,
|
||||
activeStatus = value.status,
|
||||
)
|
||||
}
|
||||
|
||||
private fun getNotification(status: OnrampStatus.Status, externalTxUrl: String?): NotificationUM? {
|
||||
if (externalTxUrl == null) return null
|
||||
return when (status) {
|
||||
OnrampStatus.Status.Verifying -> {
|
||||
ExpressNotificationsUM.NeedVerification {
|
||||
clickIntents.onGoToProviderClick(externalTxUrl)
|
||||
}
|
||||
}
|
||||
OnrampStatus.Status.Failed -> {
|
||||
ExpressNotificationsUM.FailedByProvider {
|
||||
clickIntents.onGoToProviderClick(externalTxUrl)
|
||||
}
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun convertStatuses(status: OnrampStatus.Status, externalTxUrl: String?): ExpressStatusUM {
|
||||
val statuses = with(status) {
|
||||
persistentListOf(
|
||||
getAwaitingDepositItem(),
|
||||
getPaymentProcessingItem(),
|
||||
getBuyingItem(),
|
||||
getSendingItem(),
|
||||
)
|
||||
}
|
||||
|
||||
return ExpressStatusUM(
|
||||
title = resourceReference(R.string.common_transaction_status),
|
||||
link = getStatusLink(status, externalTxUrl),
|
||||
statuses = statuses,
|
||||
)
|
||||
}
|
||||
|
||||
private fun OnrampStatus.Status.getAwaitingDepositItem() = ExpressStatusItemUM(
|
||||
text = when {
|
||||
order < OnrampStatus.Status.WaitingForPayment.order -> {
|
||||
resourceReference(R.string.express_exchange_status_receiving)
|
||||
}
|
||||
this == OnrampStatus.Status.WaitingForPayment -> {
|
||||
resourceReference(R.string.express_exchange_status_receiving_active)
|
||||
}
|
||||
else -> {
|
||||
resourceReference(R.string.express_exchange_status_received)
|
||||
}
|
||||
},
|
||||
state = getStatusState(OnrampStatus.Status.WaitingForPayment),
|
||||
)
|
||||
|
||||
private fun OnrampStatus.Status.getPaymentProcessingItem() = ExpressStatusItemUM(
|
||||
text = when {
|
||||
order < OnrampStatus.Status.PaymentProcessing.order -> {
|
||||
resourceReference(R.string.express_exchange_status_confirming)
|
||||
}
|
||||
this == OnrampStatus.Status.PaymentProcessing -> {
|
||||
resourceReference(R.string.express_exchange_status_confirming_active)
|
||||
}
|
||||
this == OnrampStatus.Status.Verifying -> {
|
||||
resourceReference(R.string.express_exchange_status_verifying)
|
||||
}
|
||||
this == OnrampStatus.Status.Failed -> {
|
||||
resourceReference(R.string.express_exchange_status_failed)
|
||||
}
|
||||
else -> resourceReference(R.string.express_exchange_status_confirmed)
|
||||
},
|
||||
state = when {
|
||||
order < OnrampStatus.Status.PaymentProcessing.order -> {
|
||||
ExpressStatusItemState.Default
|
||||
}
|
||||
this == OnrampStatus.Status.PaymentProcessing -> {
|
||||
ExpressStatusItemState.Active
|
||||
}
|
||||
this == OnrampStatus.Status.Verifying -> {
|
||||
ExpressStatusItemState.Warning
|
||||
}
|
||||
this == OnrampStatus.Status.Failed -> {
|
||||
ExpressStatusItemState.Error
|
||||
}
|
||||
else -> {
|
||||
ExpressStatusItemState.Done
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
private fun OnrampStatus.Status.getBuyingItem() = ExpressStatusItemUM(
|
||||
text = when {
|
||||
order < OnrampStatus.Status.Paid.order -> {
|
||||
resourceReference(R.string.express_status_buying, wrappedList(cryptoCurrency.name))
|
||||
}
|
||||
this == OnrampStatus.Status.Paid -> {
|
||||
resourceReference(
|
||||
R.string.express_status_buying_active,
|
||||
wrappedList(cryptoCurrency.name),
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
resourceReference(R.string.express_status_bought, wrappedList(cryptoCurrency.name))
|
||||
}
|
||||
},
|
||||
state = getStatusState(OnrampStatus.Status.Paid),
|
||||
)
|
||||
|
||||
private fun OnrampStatus.Status.getSendingItem() = ExpressStatusItemUM(
|
||||
text = when {
|
||||
order < OnrampStatus.Status.Sending.order -> {
|
||||
resourceReference(
|
||||
R.string.express_exchange_status_sending,
|
||||
wrappedList(cryptoCurrency.name),
|
||||
)
|
||||
}
|
||||
this == OnrampStatus.Status.Sending -> {
|
||||
resourceReference(
|
||||
R.string.express_exchange_status_sending_active,
|
||||
wrappedList(cryptoCurrency.name),
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
resourceReference(
|
||||
R.string.express_exchange_status_sent,
|
||||
wrappedList(cryptoCurrency.name),
|
||||
)
|
||||
}
|
||||
},
|
||||
state = getStatusState(OnrampStatus.Status.Sending),
|
||||
)
|
||||
|
||||
private fun getStatusLink(status: OnrampStatus.Status, externalTxUrl: String?): ExpressLinkUM {
|
||||
if (externalTxUrl == null) return ExpressLinkUM.Empty
|
||||
return when (status) {
|
||||
OnrampStatus.Status.Verifying,
|
||||
OnrampStatus.Status.Failed,
|
||||
-> {
|
||||
ExpressLinkUM.Content(
|
||||
icon = R.drawable.ic_arrow_top_right_24,
|
||||
text = resourceReference(R.string.common_go_to_provider),
|
||||
onClick = {
|
||||
clickIntents.onGoToProviderClick(externalTxUrl)
|
||||
},
|
||||
)
|
||||
}
|
||||
else -> ExpressLinkUM.Empty
|
||||
}
|
||||
}
|
||||
|
||||
private fun OnrampStatus.Status.getStatusState(targetState: OnrampStatus.Status) = when {
|
||||
order < targetState.order -> ExpressStatusItemState.Default
|
||||
this == targetState -> ExpressStatusItemState.Active
|
||||
else -> ExpressStatusItemState.Done
|
||||
}
|
||||
}
|
||||
|
|
@ -67,6 +67,7 @@ internal class TokenDetailsSkeletonStateConverter(
|
|||
notifications = persistentListOf(),
|
||||
pendingTxs = persistentListOf(),
|
||||
swapTxs = persistentListOf(),
|
||||
onrampTxs = persistentListOf(),
|
||||
txHistoryState = TxHistoryState.Content(
|
||||
contentItems = MutableStateFlow(
|
||||
value = TxHistoryState.getDefaultLoadingTransactions(clickIntents::onExploreClick),
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.state.component
|
|||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.txhistory.TokenDetailsLoadedTxHistoryConverter
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.txhistory.TokenDetailsLoadingTxHistoryConverter
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.txhistory.TokenDetailsLoadingTxHistoryConverter.TokenDetailsLoadingTxHistoryModel
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.exchange.ExchangeStatusBottomSheetConfig
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express.exchange.ExchangeStatusBottomSheetConfig
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents
|
||||
import com.tangem.features.tokendetails.impl.R
|
||||
import com.tangem.utils.Provider
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import com.tangem.core.analytics.api.AnalyticsEventHandler
|
|||
import com.tangem.core.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
|
||||
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
|
|
@ -94,7 +95,9 @@ internal class TokenDetailsSwapTransactionsStateConverter(
|
|||
toFiatAmount = getFiatAmount(toFiatAmount),
|
||||
toCurrencyIcon = iconStateConverter.convert(toCryptoCurrency),
|
||||
fromCryptoCurrency = fromCryptoCurrency,
|
||||
fromCryptoAmount = fromAmount.format { crypto(fromCryptoCurrency) },
|
||||
fromCryptoAmount = stringReference(
|
||||
fromAmount.format { crypto(fromCryptoCurrency) },
|
||||
),
|
||||
fromFiatAmount = getFiatAmount(fromFiatAmount),
|
||||
fromCurrencyIcon = iconStateConverter.convert(fromCryptoCurrency),
|
||||
showProviderLink = showProviderLink,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels
|
||||
package com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.express
|
||||
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.datasource.local.swaptx.ExchangeAnalyticsStatus
|
||||
|
|
@ -17,7 +17,8 @@ import com.tangem.feature.swap.domain.models.domain.*
|
|||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.SwapTransactionsState
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.TokenDetailsSwapTransactionsStateConverter
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.exchange.ExchangeStatusBottomSheetConfig
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express.exchange.ExchangeStatusBottomSheetConfig
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.collections.immutable.PersistentList
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
package com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.express
|
||||
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.onramp.GetOnrampStatusUseCase
|
||||
import com.tangem.domain.onramp.GetOnrampTransactionsUseCase
|
||||
import com.tangem.domain.onramp.OnrampRemoveTransactionUseCase
|
||||
import com.tangem.domain.onramp.OnrampSaveTransactionUseCase
|
||||
import com.tangem.domain.onramp.model.cache.OnrampTransaction
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExpressTransactionStateUM
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.TokenDetailsOnrampTransactionStateConverter
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express.onramp.OnrampStatusBottomSheetConfig
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class OnrampStatusFactory(
|
||||
private val stateProvider: Provider<TokenDetailsState>,
|
||||
private val cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus?>,
|
||||
private val appCurrencyProvider: Provider<AppCurrency>,
|
||||
private val clickIntents: TokenDetailsClickIntents,
|
||||
private val cryptoCurrency: CryptoCurrency,
|
||||
private val userWalletId: UserWalletId,
|
||||
private val getOnrampTransactionsUseCase: GetOnrampTransactionsUseCase,
|
||||
private val onrampSaveTransactionUseCase: OnrampSaveTransactionUseCase,
|
||||
private val getOnrampStatusUseCase: GetOnrampStatusUseCase,
|
||||
private val onrampRemoveTransactionUseCase: OnrampRemoveTransactionUseCase,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) {
|
||||
|
||||
private val onrampTransactionStateConverter by lazy(LazyThreadSafetyMode.NONE) {
|
||||
TokenDetailsOnrampTransactionStateConverter(
|
||||
clickIntents = clickIntents,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider,
|
||||
appCurrencyProvider = appCurrencyProvider,
|
||||
)
|
||||
}
|
||||
|
||||
operator fun invoke(): Flow<List<OnrampTransaction>> {
|
||||
return getOnrampTransactionsUseCase(
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrencyId = cryptoCurrency.id,
|
||||
).fold(
|
||||
ifRight = { savedTransactions ->
|
||||
savedTransactions
|
||||
},
|
||||
ifLeft = { flowOf(persistentListOf()) },
|
||||
)
|
||||
}
|
||||
|
||||
fun getStateWithOnrampStatusBottomSheet(onrampTxState: ExpressTransactionStateUM.OnrampUM): TokenDetailsState {
|
||||
return stateProvider().copy(
|
||||
bottomSheetConfig = TangemBottomSheetConfig(
|
||||
isShow = true,
|
||||
onDismissRequest = clickIntents::onDismissBottomSheet,
|
||||
content = OnrampStatusBottomSheetConfig(onrampTxState),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun updateOnrampStatusBottomSheet(onrampTxs: List<OnrampTransaction>): TokenDetailsState {
|
||||
val state = stateProvider()
|
||||
val bottomSheetConfig = state.bottomSheetConfig
|
||||
val onrampBottomSheet = bottomSheetConfig?.content as? OnrampStatusBottomSheetConfig
|
||||
|
||||
val updatedOnrampTxState = onrampTransactionStateConverter.convertList(onrampTxs).toPersistentList()
|
||||
val currentOnrampTxState =
|
||||
updatedOnrampTxState.firstOrNull { it.info.txId == onrampBottomSheet?.value?.info?.txId }
|
||||
|
||||
return state.copy(
|
||||
onrampTxs = updatedOnrampTxState,
|
||||
bottomSheetConfig = bottomSheetConfig?.copy(
|
||||
content = if (currentOnrampTxState != null && currentOnrampTxState != onrampBottomSheet?.value) {
|
||||
OnrampStatusBottomSheetConfig(currentOnrampTxState)
|
||||
} else {
|
||||
onrampBottomSheet
|
||||
} ?: bottomSheetConfig.content,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun removeTransactionOnBottomSheetClosed(): TokenDetailsState {
|
||||
val state = stateProvider()
|
||||
val bottomSheetConfig = state.bottomSheetConfig?.content as? OnrampStatusBottomSheetConfig ?: return state
|
||||
val selectedTx = bottomSheetConfig.value
|
||||
|
||||
return if (selectedTx.activeStatus.isTerminal()) {
|
||||
onrampRemoveTransactionUseCase(txId = selectedTx.info.txId)
|
||||
val filteredTxs = state.onrampTxs
|
||||
.filterNot { it.info.txId == selectedTx.info.txId }
|
||||
.toPersistentList()
|
||||
state.copy(onrampTxs = filteredTxs)
|
||||
} else {
|
||||
state
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun updateOnrmapTxStatuses(onrampTxList: List<OnrampTransaction>) = withContext(dispatchers.io) {
|
||||
onrampTxList.map { tx ->
|
||||
async {
|
||||
getOnrampStatusUseCase(tx.txId).fold(
|
||||
ifLeft = { null },
|
||||
ifRight = { statusModel ->
|
||||
val updatedStatus = tx.copy(status = statusModel.status)
|
||||
onrampSaveTransactionUseCase(updatedStatus)
|
||||
updatedStatus
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
.awaitAll()
|
||||
.filterNotNull()
|
||||
.toPersistentList()
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,6 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.ui
|
|||
|
||||
import android.content.res.Configuration
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
|
|
@ -49,15 +48,18 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.T
|
|||
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.TokenDetailsDialogs
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.TokenDetailsTopAppBar
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.TokenInfoBlock
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.exchange.ExchangeStatusBottomSheet
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.exchange.ExchangeStatusBottomSheetConfig
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.exchange.swapTransactionsItems
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express.exchange.ExchangeStatusBottomSheet
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express.exchange.ExchangeStatusBottomSheetConfig
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express.exchange.swapTransactionsItems
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express.onramp.OnrampStatusBottomSheet
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express.onramp.OnrampStatusBottomSheetConfig
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express.onramp.onrampTransactionsItems
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.staking.TokenStakingBlock
|
||||
import com.tangem.features.markets.token.block.TokenMarketBlockComponent
|
||||
|
||||
// TODO: Split to blocks [REDACTED_JIRA]
|
||||
@Suppress("LongMethod")
|
||||
@OptIn(ExperimentalMaterialApi::class, ExperimentalFoundationApi::class)
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
internal fun TokenDetailsScreen(state: TokenDetailsState, tokenMarketBlockComponent: TokenMarketBlockComponent?) {
|
||||
BackHandler(onBack = state.topAppBarConfig.onBackClick)
|
||||
|
|
@ -127,11 +129,11 @@ internal fun TokenDetailsScreen(state: TokenDetailsState, tokenMarketBlockCompon
|
|||
if (it is TokenDetailsNotification.SwapPromo) {
|
||||
OkxPromoNotification(
|
||||
config = it.config,
|
||||
modifier = itemModifier.animateItemPlacement(),
|
||||
modifier = itemModifier.animateItem(),
|
||||
)
|
||||
} else {
|
||||
Notification(
|
||||
modifier = itemModifier.animateItemPlacement(),
|
||||
modifier = itemModifier.animateItem(),
|
||||
config = it.config,
|
||||
iconTint = when (it) {
|
||||
is TokenDetailsNotification.Informational -> TangemTheme.colors.icon.accent
|
||||
|
|
@ -179,8 +181,13 @@ internal fun TokenDetailsScreen(state: TokenDetailsState, tokenMarketBlockCompon
|
|||
}
|
||||
|
||||
swapTransactionsItems(
|
||||
state.swapTxs,
|
||||
itemModifier,
|
||||
swapTxs = state.swapTxs,
|
||||
modifier = itemModifier,
|
||||
)
|
||||
|
||||
onrampTransactionsItems(
|
||||
onrampTxs = state.onrampTxs,
|
||||
modifier = itemModifier,
|
||||
)
|
||||
|
||||
txHistoryItems(
|
||||
|
|
@ -210,6 +217,9 @@ internal fun TokenDetailsScreen(state: TokenDetailsState, tokenMarketBlockCompon
|
|||
is ExchangeStatusBottomSheetConfig -> {
|
||||
ExchangeStatusBottomSheet(config = config)
|
||||
}
|
||||
is OnrampStatusBottomSheetConfig -> {
|
||||
OnrampStatusBottomSheet(config = config)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.exchange
|
||||
package com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
|
|
@ -17,7 +17,7 @@ import com.tangem.features.tokendetails.impl.R
|
|||
|
||||
@Suppress("LongParameterList")
|
||||
@Composable
|
||||
internal fun ExchangeEstimate(
|
||||
internal fun ExpressEstimate(
|
||||
timestamp: TextReference,
|
||||
fromTokenIconState: CurrencyIconState,
|
||||
toTokenIconState: CurrencyIconState,
|
||||
|
|
@ -25,8 +25,8 @@ internal fun ExchangeEstimate(
|
|||
fromCryptoSymbol: String,
|
||||
toCryptoAmount: TextReference,
|
||||
toCryptoSymbol: String,
|
||||
fromFiatAmount: TextReference,
|
||||
toFiatAmount: TextReference,
|
||||
fromFiatAmount: TextReference?,
|
||||
toFiatAmount: TextReference?,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.exchange
|
||||
package com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express
|
||||
|
||||
import android.content.res.Configuration
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
|
|
@ -17,15 +18,18 @@ import androidx.compose.ui.platform.LocalHapticFeedback
|
|||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.SpacerWMax
|
||||
import com.tangem.core.ui.components.inputrow.InputRowBestRate
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
|
||||
@Composable
|
||||
internal fun ExchangeProvider(
|
||||
internal fun ExpressProvider(
|
||||
providerName: TextReference,
|
||||
providerType: TextReference,
|
||||
providerTxId: String?,
|
||||
|
|
@ -39,7 +43,7 @@ internal fun ExchangeProvider(
|
|||
.clip(TangemTheme.shapes.roundedCornersXMedium)
|
||||
.background(TangemTheme.colors.background.action),
|
||||
) {
|
||||
Box(
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(top = TangemTheme.dimens.spacing12)
|
||||
.padding(horizontal = TangemTheme.dimens.spacing12)
|
||||
|
|
@ -49,12 +53,12 @@ internal fun ExchangeProvider(
|
|||
text = stringResource(id = R.string.express_provider),
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
modifier = Modifier.align(Alignment.CenterStart),
|
||||
)
|
||||
SpacerWMax()
|
||||
if (!providerTxId.isNullOrEmpty()) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterEnd)
|
||||
.padding(start = 8.dp)
|
||||
.clickable {
|
||||
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
clipboardManager.setText(AnnotatedString(providerTxId))
|
||||
|
|
@ -73,6 +77,8 @@ internal fun ExchangeProvider(
|
|||
Text(
|
||||
modifier = Modifier.align(Alignment.CenterVertically),
|
||||
text = stringResource(R.string.express_transaction_id, providerTxId),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
|
|
@ -88,14 +94,15 @@ internal fun ExchangeProvider(
|
|||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun ExchangeProvider_Preview() {
|
||||
TangemThemePreview(isDark = false) {
|
||||
ExchangeProvider(
|
||||
private fun ExpressProvider_Preview() {
|
||||
TangemThemePreview {
|
||||
ExpressProvider(
|
||||
providerName = TextReference.Str("Changelly"),
|
||||
providerType = TextReference.Str("CEX"),
|
||||
providerTxId = "hjsbajcqb",
|
||||
providerTxId = "hjsbajcqbhjsbajcqbhjsbajcqbhjsbajcqbhjsbajcqb",
|
||||
imageUrl = "",
|
||||
)
|
||||
}
|
||||
|
|
@ -1,14 +1,12 @@
|
|||
package com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.exchange
|
||||
package com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
|
|
@ -16,7 +14,6 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
|
|
@ -25,60 +22,25 @@ 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
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.feature.swap.domain.models.domain.ExchangeStatus
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.SwapTransactionsState
|
||||
import com.tangem.features.tokendetails.impl.R
|
||||
import kotlinx.collections.immutable.PersistentList
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
internal fun LazyListScope.swapTransactionsItems(
|
||||
swapTxs: PersistentList<SwapTransactionsState>,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
if (swapTxs.isNotEmpty()) {
|
||||
items(
|
||||
count = swapTxs.size,
|
||||
key = { swapTxs[it].txId },
|
||||
contentType = { swapTxs[it]::class.java },
|
||||
) {
|
||||
val item = swapTxs[it]
|
||||
val (iconRes, tint) = when (item.activeStatus) {
|
||||
ExchangeStatus.Verifying -> R.drawable.ic_alert_triangle_20 to TangemTheme.colors.icon.attention
|
||||
ExchangeStatus.Failed, ExchangeStatus.Cancelled -> {
|
||||
R.drawable.ic_alert_circle_24 to TangemTheme.colors.icon.warning
|
||||
}
|
||||
else -> null to null
|
||||
}
|
||||
|
||||
ExchangeStatusItem(
|
||||
providerName = item.provider.name,
|
||||
fromTokenIconState = item.fromCurrencyIcon,
|
||||
toTokenIconState = item.toCurrencyIcon,
|
||||
fromAmount = item.fromCryptoAmount,
|
||||
fromSymbol = item.fromCryptoCurrency.symbol,
|
||||
toSymbol = item.toCryptoCurrency.symbol,
|
||||
onClick = item.onClick,
|
||||
infoIconRes = iconRes,
|
||||
infoIconTint = tint,
|
||||
modifier = modifier.animateItemPlacement(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("DestructuringDeclarationWithTooManyEntries", "LongMethod", "LongParameterList")
|
||||
@Composable
|
||||
private fun ExchangeStatusItem(
|
||||
providerName: String,
|
||||
internal fun ExpressStatusItem(
|
||||
title: TextReference,
|
||||
fromTokenIconState: CurrencyIconState,
|
||||
toTokenIconState: CurrencyIconState,
|
||||
fromAmount: String,
|
||||
fromAmount: TextReference,
|
||||
fromSymbol: String,
|
||||
toSymbol: String,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
toAmount: TextReference = TextReference.EMPTY,
|
||||
@DrawableRes infoIconRes: Int? = null,
|
||||
infoIconTint: Color? = null,
|
||||
) {
|
||||
|
|
@ -94,7 +56,7 @@ private fun ExchangeStatusItem(
|
|||
val padding6 = TangemTheme.dimens.spacing6
|
||||
|
||||
Text(
|
||||
text = stringResource(id = R.string.express_exchange_by, providerName),
|
||||
text = title.resolveReference(),
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
modifier = Modifier.constrainAs(titleRef) {
|
||||
|
|
@ -114,7 +76,7 @@ private fun ExchangeStatusItem(
|
|||
},
|
||||
)
|
||||
EllipsisText(
|
||||
text = fromAmount,
|
||||
text = fromAmount.resolveReference(),
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
ellipsis = TextEllipsis.OffsetEnd(fromSymbol.length),
|
||||
|
|
@ -151,10 +113,11 @@ private fun ExchangeStatusItem(
|
|||
bottom.linkTo(parent.bottom)
|
||||
},
|
||||
)
|
||||
Text(
|
||||
text = toSymbol,
|
||||
EllipsisText(
|
||||
text = toAmount.resolveReference(),
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
ellipsis = TextEllipsis.OffsetEnd(toSymbol.length),
|
||||
modifier = Modifier.constrainAs(toRef) {
|
||||
start.linkTo(toIconRef.end, padding6)
|
||||
top.linkTo(titleRef.bottom, padding6)
|
||||
|
|
@ -199,16 +162,17 @@ private fun ExchangeStatusItem(
|
|||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun ExchangeStatusItemPreview(
|
||||
@PreviewParameter(ExchangeStatusItemsPreviewParameterProvider::class) amount: String,
|
||||
private fun ExpressStatusItemPreview(
|
||||
@PreviewParameter(ExpressStatusItemPreviewParameterProvider::class) amount: String,
|
||||
) {
|
||||
TangemThemePreview {
|
||||
ExchangeStatusItem(
|
||||
providerName = "ChangeNow",
|
||||
ExpressStatusItem(
|
||||
title = stringReference("ChangeNow"),
|
||||
fromTokenIconState = CurrencyIconState.Loading,
|
||||
toTokenIconState = CurrencyIconState.Loading,
|
||||
fromAmount = amount,
|
||||
fromAmount = stringReference(amount),
|
||||
fromSymbol = "USDT",
|
||||
toAmount = stringReference(amount),
|
||||
toSymbol = "USDT",
|
||||
onClick = {},
|
||||
infoIconRes = null,
|
||||
|
|
@ -217,7 +181,7 @@ private fun ExchangeStatusItemPreview(
|
|||
}
|
||||
}
|
||||
|
||||
private class ExchangeStatusItemsPreviewParameterProvider : PreviewParameterProvider<String> {
|
||||
private class ExpressStatusItemPreviewParameterProvider : PreviewParameterProvider<String> {
|
||||
override val values: Sequence<String>
|
||||
get() = sequenceOf(
|
||||
"1111111111111111111111111111 USDT",
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.exchange
|
||||
package com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express.exchange
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.exchange
|
||||
package com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express.exchange
|
||||
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.foundation.layout.Column
|
||||
|
|
@ -23,6 +23,8 @@ import com.tangem.core.ui.res.TangemTheme
|
|||
import com.tangem.feature.swap.domain.models.domain.ExchangeStatus
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.SwapTransactionsState
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.ExchangeStatusNotifications
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express.ExpressEstimate
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express.ExpressProvider
|
||||
|
||||
@Composable
|
||||
internal fun ExchangeStatusBottomSheet(config: TangemBottomSheetConfig) {
|
||||
|
|
@ -54,11 +56,11 @@ private fun ExchangeStatusBottomSheetContent(config: SwapTransactionsState) {
|
|||
.align(CenterHorizontally),
|
||||
)
|
||||
SpacerH16()
|
||||
ExchangeEstimate(
|
||||
ExpressEstimate(
|
||||
timestamp = config.timestamp,
|
||||
fromTokenIconState = config.fromCurrencyIcon,
|
||||
toTokenIconState = config.toCurrencyIcon,
|
||||
fromCryptoAmount = TextReference.Str(config.fromCryptoAmount),
|
||||
fromCryptoAmount = config.fromCryptoAmount,
|
||||
fromCryptoSymbol = config.fromCryptoCurrency.symbol,
|
||||
toCryptoAmount = TextReference.Str(config.toCryptoAmount),
|
||||
toCryptoSymbol = config.toCryptoCurrency.symbol,
|
||||
|
|
@ -66,7 +68,7 @@ private fun ExchangeStatusBottomSheetContent(config: SwapTransactionsState) {
|
|||
toFiatAmount = TextReference.Str(config.toFiatAmount),
|
||||
)
|
||||
SpacerH12()
|
||||
ExchangeProvider(
|
||||
ExpressProvider(
|
||||
providerName = TextReference.Str(config.provider.name),
|
||||
providerType = TextReference.Str(config.provider.type.providerName),
|
||||
providerTxId = config.txExternalId,
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
package com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express.exchange
|
||||
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.feature.swap.domain.models.domain.ExchangeStatus
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.SwapTransactionsState
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express.ExpressStatusItem
|
||||
import com.tangem.features.tokendetails.impl.R
|
||||
import kotlinx.collections.immutable.PersistentList
|
||||
|
||||
internal fun LazyListScope.swapTransactionsItems(
|
||||
swapTxs: PersistentList<SwapTransactionsState>,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
if (swapTxs.isNotEmpty()) {
|
||||
items(
|
||||
count = swapTxs.size,
|
||||
key = { swapTxs[it].txId },
|
||||
contentType = { swapTxs[it]::class.java },
|
||||
) {
|
||||
val item = swapTxs[it]
|
||||
val (iconRes, tint) = when (item.activeStatus) {
|
||||
ExchangeStatus.Verifying -> R.drawable.ic_alert_triangle_20 to TangemTheme.colors.icon.attention
|
||||
ExchangeStatus.Failed, ExchangeStatus.Cancelled -> {
|
||||
R.drawable.ic_alert_circle_24 to TangemTheme.colors.icon.warning
|
||||
}
|
||||
else -> null to null
|
||||
}
|
||||
|
||||
ExpressStatusItem(
|
||||
title = resourceReference(id = R.string.express_exchange_by, wrappedList(item.provider.name)),
|
||||
fromTokenIconState = item.fromCurrencyIcon,
|
||||
toTokenIconState = item.toCurrencyIcon,
|
||||
fromAmount = item.fromCryptoAmount,
|
||||
fromSymbol = item.fromCryptoCurrency.symbol,
|
||||
toSymbol = item.toCryptoCurrency.symbol,
|
||||
onClick = item.onClick,
|
||||
infoIconRes = iconRes,
|
||||
infoIconTint = tint,
|
||||
modifier = modifier.animateItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
package com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express.onramp
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment.Companion.CenterHorizontally
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import com.tangem.common.ui.expressStatus.ExpressStatusBlock
|
||||
import com.tangem.common.ui.expressStatus.ExpressStatusNotificationBlock
|
||||
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.bottomsheets.TangemBottomSheet
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExpressTransactionStateUM
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express.ExpressEstimate
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express.ExpressProvider
|
||||
|
||||
@Composable
|
||||
internal fun OnrampStatusBottomSheet(config: TangemBottomSheetConfig) {
|
||||
TangemBottomSheet(
|
||||
config = config,
|
||||
containerColor = TangemTheme.colors.background.tertiary,
|
||||
) { content: OnrampStatusBottomSheetConfig ->
|
||||
OnrampStatusBottomSheetContent(config = content.value)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun OnrampStatusBottomSheetContent(config: ExpressTransactionStateUM.OnrampUM) {
|
||||
Column(modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing16)) {
|
||||
SpacerH10()
|
||||
Text(
|
||||
text = stringResource(id = R.string.common_transaction_status),
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
modifier = Modifier.align(CenterHorizontally),
|
||||
)
|
||||
SpacerH10()
|
||||
Text(
|
||||
text = stringResource(id = R.string.express_exchange_status_subtitle),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier
|
||||
.align(CenterHorizontally),
|
||||
)
|
||||
SpacerH16()
|
||||
ExpressEstimate(
|
||||
timestamp = config.info.timestamp,
|
||||
fromTokenIconState = config.info.fromCurrencyIcon,
|
||||
toTokenIconState = config.info.toCurrencyIcon,
|
||||
fromCryptoAmount = config.info.fromAmount,
|
||||
fromCryptoSymbol = config.info.fromAmountSymbol,
|
||||
toCryptoAmount = config.info.toAmount,
|
||||
toCryptoSymbol = config.info.toAmountSymbol,
|
||||
fromFiatAmount = config.info.fromFiatAmount,
|
||||
toFiatAmount = config.info.toFiatAmount,
|
||||
)
|
||||
SpacerH12()
|
||||
|
||||
ExpressProvider(
|
||||
providerName = stringReference(config.providerName),
|
||||
providerType = stringReference(config.providerType),
|
||||
providerTxId = config.info.txExternalId,
|
||||
imageUrl = config.providerImageUrl,
|
||||
)
|
||||
SpacerH12()
|
||||
ExpressStatusBlock(state = config.info.status)
|
||||
if (config.info.notification != null) {
|
||||
ExpressStatusNotificationBlock(state = config.info.notification)
|
||||
}
|
||||
SpacerH24()
|
||||
}
|
||||
}
|
||||
|
||||
internal data class OnrampStatusBottomSheetConfig(
|
||||
val value: ExpressTransactionStateUM.OnrampUM,
|
||||
) : TangemBottomSheetConfigContent
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
package com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express.onramp
|
||||
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.domain.onramp.model.OnrampStatus
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExpressTransactionStateUM
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express.ExpressStatusItem
|
||||
import com.tangem.features.tokendetails.impl.R
|
||||
import kotlinx.collections.immutable.PersistentList
|
||||
|
||||
internal fun LazyListScope.onrampTransactionsItems(
|
||||
onrampTxs: PersistentList<ExpressTransactionStateUM.OnrampUM>,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
items(
|
||||
count = onrampTxs.size,
|
||||
key = { onrampTxs[it].info.txId },
|
||||
contentType = { onrampTxs[it]::class.java },
|
||||
) {
|
||||
val item = onrampTxs[it]
|
||||
val itemInfo = item.info
|
||||
|
||||
val (iconRes, tint) = when (item.activeStatus) {
|
||||
OnrampStatus.Status.Verifying -> R.drawable.ic_alert_triangle_20 to TangemTheme.colors.icon.attention
|
||||
OnrampStatus.Status.Failed -> {
|
||||
R.drawable.ic_alert_circle_24 to TangemTheme.colors.icon.warning
|
||||
}
|
||||
else -> null to null
|
||||
}
|
||||
|
||||
ExpressStatusItem(
|
||||
title = resourceReference(id = R.string.express_status_buying, wrappedList(item.cryptoCurrencyName)),
|
||||
fromTokenIconState = itemInfo.fromCurrencyIcon,
|
||||
toTokenIconState = itemInfo.toCurrencyIcon,
|
||||
fromAmount = itemInfo.fromAmount,
|
||||
fromSymbol = itemInfo.fromAmountSymbol,
|
||||
toAmount = itemInfo.toAmount,
|
||||
toSymbol = itemInfo.toAmountSymbol,
|
||||
onClick = item.info.onClick,
|
||||
infoIconRes = iconRes,
|
||||
infoIconTint = tint,
|
||||
modifier = modifier.animateItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -47,6 +47,8 @@ interface TokenDetailsClickIntents {
|
|||
|
||||
fun onSwapTransactionClick(txId: String)
|
||||
|
||||
fun onOnrampTransactionClick(txId: String)
|
||||
|
||||
fun onGoToProviderClick(url: String)
|
||||
|
||||
fun onSwapPromoDismiss()
|
||||
|
|
|
|||
|
|
@ -30,6 +30,11 @@ import com.tangem.domain.card.GetExtendedPublicKeyForCurrencyUseCase
|
|||
import com.tangem.domain.card.NetworkHasDerivationUseCase
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.demo.IsDemoCardUseCase
|
||||
import com.tangem.domain.onramp.GetOnrampStatusUseCase
|
||||
import com.tangem.domain.onramp.GetOnrampTransactionsUseCase
|
||||
import com.tangem.domain.onramp.OnrampRemoveTransactionUseCase
|
||||
import com.tangem.domain.onramp.OnrampSaveTransactionUseCase
|
||||
import com.tangem.domain.onramp.model.cache.OnrampTransaction
|
||||
import com.tangem.domain.redux.ReduxStateHolder
|
||||
import com.tangem.domain.settings.ShouldShowSwapPromoTokenUseCase
|
||||
import com.tangem.domain.staking.GetStakingAvailabilityUseCase
|
||||
|
|
@ -71,7 +76,10 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.state.SwapTrans
|
|||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenBalanceSegmentedButtonConfig
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.TokenDetailsStateFactory
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.exchange.ExchangeStatusBottomSheetConfig
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.express.ExchangeStatusFactory
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.express.OnrampStatusFactory
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express.exchange.ExchangeStatusBottomSheetConfig
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express.onramp.OnrampStatusBottomSheetConfig
|
||||
import com.tangem.features.tokendetails.impl.R
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.coroutines.*
|
||||
|
|
@ -122,6 +130,10 @@ internal class TokenDetailsViewModel @Inject constructor(
|
|||
private val analyticsEventsHandler: AnalyticsEventHandler,
|
||||
private val vibratorHapticManager: VibratorHapticManager,
|
||||
private val clipboardManager: ClipboardManager,
|
||||
private val getOnrampTransactionsUseCase: GetOnrampTransactionsUseCase,
|
||||
private val onrampSaveTransactionUseCase: OnrampSaveTransactionUseCase,
|
||||
private val onrampRemoveTransactionUseCase: OnrampRemoveTransactionUseCase,
|
||||
private val getOnrampStatusUseCase: GetOnrampStatusUseCase,
|
||||
getUserWalletUseCase: GetUserWalletUseCase,
|
||||
getStakingIntegrationIdUseCase: GetStakingIntegrationIdUseCase,
|
||||
deepLinksRegistry: DeepLinksRegistry,
|
||||
|
|
@ -146,12 +158,14 @@ internal class TokenDetailsViewModel @Inject constructor(
|
|||
private val refreshStateJobHolder = JobHolder()
|
||||
private val warningsJobHolder = JobHolder()
|
||||
private val swapTxJobHolder = JobHolder()
|
||||
private val onrampTxJobHolder = JobHolder()
|
||||
private val selectedAppCurrencyFlow: StateFlow<AppCurrency> = createSelectedAppCurrencyFlow()
|
||||
|
||||
private var cryptoCurrencyStatus: CryptoCurrencyStatus? = null
|
||||
private var stakingEntryInfo: StakingEntryInfo? = null
|
||||
private var stakingAvailability: StakingAvailability = StakingAvailability.Unavailable
|
||||
private var swapTxStatusTaskScheduler = SingleTaskScheduler<PersistentList<SwapTransactionsState>>()
|
||||
private var onrampTxStatusTaskScheduler = SingleTaskScheduler<PersistentList<OnrampTransaction>>()
|
||||
|
||||
private val stateFactory = TokenDetailsStateFactory(
|
||||
currentStateProvider = Provider { uiState.value },
|
||||
|
|
@ -186,6 +200,22 @@ internal class TokenDetailsViewModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private val onrampStatusFactory by lazy(LazyThreadSafetyMode.NONE) {
|
||||
OnrampStatusFactory(
|
||||
stateProvider = Provider { uiState.value },
|
||||
appCurrencyProvider = Provider { selectedAppCurrencyFlow.value },
|
||||
cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus },
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
clickIntents = this,
|
||||
getOnrampTransactionsUseCase = getOnrampTransactionsUseCase,
|
||||
onrampSaveTransactionUseCase = onrampSaveTransactionUseCase,
|
||||
onrampRemoveTransactionUseCase = onrampRemoveTransactionUseCase,
|
||||
getOnrampStatusUseCase = getOnrampStatusUseCase,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
|
||||
private val notificationsAnalyticsSender by lazy(mode = LazyThreadSafetyMode.NONE) {
|
||||
TokenDetailsNotificationsAnalyticsSender(
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
|
|
@ -226,12 +256,15 @@ internal class TokenDetailsViewModel @Inject constructor(
|
|||
|
||||
override fun onCleared() {
|
||||
swapTxStatusTaskScheduler.cancelTask()
|
||||
onrampTxStatusTaskScheduler.cancelTask()
|
||||
onrampTxJobHolder.cancel()
|
||||
super.onCleared()
|
||||
}
|
||||
|
||||
private fun updateContent() {
|
||||
subscribeOnCurrencyStatusUpdates()
|
||||
subscribeOnExchangeTransactionsUpdates()
|
||||
subscribeOnOnrampTransactionsUpdates()
|
||||
updateTxHistory(refresh = false, showItemsLoading = true)
|
||||
|
||||
updateStakingInfo()
|
||||
|
|
@ -332,6 +365,38 @@ internal class TokenDetailsViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun subscribeOnOnrampTransactionsUpdates() {
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
onrampTxStatusTaskScheduler.cancelTask()
|
||||
onrampStatusFactory.invoke().distinctUntilChanged()
|
||||
.filterNot { it.isEmpty() }
|
||||
.onEach { onrampTxs ->
|
||||
internalUiState.value = onrampStatusFactory.updateOnrampStatusBottomSheet(onrampTxs)
|
||||
onrampTxStatusTaskScheduler.scheduleTask(
|
||||
viewModelScope,
|
||||
PeriodicTask(
|
||||
delay = EXCHANGE_STATUS_UPDATE_DELAY,
|
||||
task = {
|
||||
runCatching {
|
||||
val onrampTxsToUpdate = onrampTxs.filter { onrampTx ->
|
||||
internalUiState.value.onrampTxs.any { it.info.txId == onrampTx.txId }
|
||||
}
|
||||
onrampStatusFactory.updateOnrmapTxStatuses(onrampTxsToUpdate)
|
||||
}
|
||||
},
|
||||
onSuccess = {
|
||||
internalUiState.value = onrampStatusFactory.updateOnrampStatusBottomSheet(onrampTxs)
|
||||
},
|
||||
onError = { /* no-op */ },
|
||||
),
|
||||
)
|
||||
}
|
||||
.flowOn(dispatchers.main)
|
||||
.launchIn(viewModelScope)
|
||||
.saveIn(onrampTxJobHolder)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateSwapTx(swapTxs: PersistentList<SwapTransactionsState>) {
|
||||
val config = internalUiState.value.bottomSheetConfig
|
||||
val exchangeBottomSheet = config?.content as? ExchangeStatusBottomSheetConfig
|
||||
|
|
@ -771,6 +836,7 @@ internal class TokenDetailsViewModel @Inject constructor(
|
|||
showItemsLoading = internalUiState.value.txHistoryState !is TxHistoryState.Content,
|
||||
)
|
||||
subscribeOnExchangeTransactionsUpdates()
|
||||
subscribeOnOnrampTransactionsUpdates()
|
||||
},
|
||||
).awaitAll()
|
||||
internalUiState.value = stateFactory.getRefreshedState()
|
||||
|
|
@ -779,9 +845,16 @@ internal class TokenDetailsViewModel @Inject constructor(
|
|||
|
||||
override fun onDismissBottomSheet() {
|
||||
val bsContent = internalUiState.value.bottomSheetConfig?.content
|
||||
if (bsContent is ExchangeStatusBottomSheetConfig) {
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
internalUiState.value = exchangeStatusFactory.removeTransactionOnBottomSheetClosed()
|
||||
when (bsContent) {
|
||||
is ExchangeStatusBottomSheetConfig -> {
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
internalUiState.value = exchangeStatusFactory.removeTransactionOnBottomSheetClosed()
|
||||
}
|
||||
}
|
||||
is OnrampStatusBottomSheetConfig -> {
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
internalUiState.value = onrampStatusFactory.removeTransactionOnBottomSheetClosed()
|
||||
}
|
||||
}
|
||||
}
|
||||
internalUiState.value = stateFactory.getStateWithClosedBottomSheet()
|
||||
|
|
@ -797,6 +870,11 @@ internal class TokenDetailsViewModel @Inject constructor(
|
|||
internalUiState.value = stateFactory.getStateWithExchangeStatusBottomSheet(swapTxState)
|
||||
}
|
||||
|
||||
override fun onOnrampTransactionClick(txId: String) {
|
||||
val onrampTxState = internalUiState.value.onrampTxs.first { it.info.txId == txId }
|
||||
internalUiState.value = onrampStatusFactory.getStateWithOnrampStatusBottomSheet(onrampTxState)
|
||||
}
|
||||
|
||||
override fun onGoToProviderClick(url: String) {
|
||||
router.openUrl(url)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue