Updated on 2026-08-14

This commit is contained in:
Tangem 2026-05-12 11:46:20 +03:00
parent a54c3ce6ea
commit b0e07c0258
23 changed files with 406 additions and 887 deletions

View file

@ -1,12 +1,42 @@
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.lazy.LazyListScope
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
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.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.isNullOrEmpty
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.TangemThemePreviewRedesign
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.persistentListOf
fun LazyListScope.expressTransactionsItems(
expressTxs: PersistentList<ExpressTransactionStateUM>,
@ -19,28 +49,170 @@ fun LazyListScope.expressTransactionsItems(
) { index ->
val itemInfo = expressTxs[index].info
val (iconRes, tint) = when (itemInfo.iconState) {
ExpressTransactionStateIconUM.Warning -> {
R.drawable.ic_alert_triangle_20 to TangemTheme.colors.icon.attention
}
ExpressTransactionStateIconUM.Error -> {
R.drawable.ic_alert_circle_24 to TangemTheme.colors.icon.warning
}
ExpressTransactionStateIconUM.Warning ->
R.drawable.ic_attention_default_24 to TangemTheme.colors2.graphic.status.attention
ExpressTransactionStateIconUM.Error ->
R.drawable.ic_alert_circle_24 to TangemTheme.colors2.graphic.status.warning
ExpressTransactionStateIconUM.None -> null to null
}
ExpressStatusItem(
title = itemInfo.title,
fromTokenIconState = itemInfo.fromCurrencyIcon,
toTokenIconState = itemInfo.toCurrencyIcon,
fromAmount = itemInfo.fromAmount,
fromSymbol = itemInfo.fromAmountSymbol,
toAmount = itemInfo.toAmount,
toSymbol = itemInfo.toAmountSymbol,
subtitle = itemInfo.subtitle,
onClick = itemInfo.onClick,
ExpressTransactionItem(
state = expressTxs[index],
infoIconRes = iconRes,
infoIconTint = tint,
modifier = modifier.animateItem(),
)
}
}
}
@Composable
private fun ExpressTransactionItem(
state: ExpressTransactionStateUM,
infoIconRes: Int?,
infoIconTint: Color?,
modifier: Modifier = Modifier,
) {
val info = state.info
Column(
modifier = modifier
.fillMaxWidth()
.clip(TangemTheme.shapes.roundedCornersXMedium)
.background(TangemTheme.colors2.surface.level3)
.clickable(onClick = info.onClick)
.padding(TangemTheme.dimens2.x4),
) {
TitleRow(
title = info.title.resolveReference(),
infoIconRes = infoIconRes,
infoIconTint = infoIconTint,
)
if (!info.subtitle.isNullOrEmpty()) {
Text(
text = info.subtitle.resolveReference(),
style = TangemTheme.typography2.captionRegular13,
color = TangemTheme.colors3.text.tertiary,
)
}
Spacer(Modifier.size(TangemTheme.dimens2.x3))
AmountsRow(info = info)
}
}
@Composable
private fun TitleRow(title: String, infoIconRes: Int?, infoIconTint: Color?) {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(
text = title,
style = TangemTheme.typography2.bodyMedium16,
color = TangemTheme.colors3.text.primary,
modifier = Modifier.weight(1f),
)
if (infoIconRes != null && infoIconTint != null) {
Icon(
painter = painterResource(infoIconRes),
contentDescription = null,
tint = infoIconTint,
modifier = Modifier.padding(horizontal = TangemTheme.dimens2.x3),
)
}
}
}
@Composable
private fun AmountsRow(info: ExpressTransactionStateInfoUM) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x1_5),
) {
CurrencyIcon(
state = info.fromCurrencyIcon,
shouldDisplayNetwork = false,
modifier = Modifier.size(TangemTheme.dimens.size18),
)
EllipsisText(
text = info.fromAmount.resolveReference(),
style = TangemTheme.typography2.bodyMedium16,
color = TangemTheme.colors3.text.primary,
ellipsis = TextEllipsis.OffsetEnd(info.fromAmountSymbol.length),
modifier = Modifier.weight(weight = 1f, fill = false),
)
Icon(
painter = painterResource(R.drawable.ic_forward_24),
contentDescription = null,
tint = TangemTheme.colors3.icon.tertiary,
modifier = Modifier.size(TangemTheme.dimens.size18),
)
CurrencyIcon(
state = info.toCurrencyIcon,
shouldDisplayNetwork = false,
modifier = Modifier.size(TangemTheme.dimens.size18),
)
EllipsisText(
text = info.toAmount.resolveReference(),
style = TangemTheme.typography2.bodyMedium16,
color = TangemTheme.colors3.text.primary,
ellipsis = TextEllipsis.OffsetEnd(info.toAmountSymbol.length),
modifier = Modifier.weight(weight = 1f, fill = false),
)
}
}
// region Preview
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun ExpressTransactionItemPreview() {
TangemThemePreviewRedesign {
Column(
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x2),
modifier = Modifier.padding(TangemTheme.dimens2.x4),
) {
ExpressTransactionItem(
state = PreviewExpressTransactionState,
infoIconRes = null,
infoIconTint = null,
)
ExpressTransactionItem(
state = PreviewExpressTransactionState,
infoIconRes = R.drawable.ic_attention_default_24,
infoIconTint = TangemTheme.colors2.graphic.status.attention,
)
ExpressTransactionItem(
state = PreviewExpressTransactionState,
infoIconRes = R.drawable.ic_alert_circle_24,
infoIconTint = TangemTheme.colors2.graphic.status.warning,
)
}
}
}
private val PreviewExpressTransactionState: ExpressTransactionStateUM = object : ExpressTransactionStateUM {
override val info = ExpressTransactionStateInfoUM(
title = stringReference("Exchange by ChangeHero"),
status = ExpressStatusUM(
title = stringReference(""),
link = ExpressLinkUM.Empty,
statuses = persistentListOf(),
),
notification = null,
txId = "preview",
txExternalId = null,
txExternalUrl = null,
timestamp = 0L,
timestampFormatted = stringReference(""),
timestampAgoFormatted = stringReference("Confirming ~ 59 min ago"),
activeStatus = stringReference(""),
onGoToProviderClick = {},
onClick = {},
onDisposeExpressStatus = {},
iconState = ExpressTransactionStateIconUM.None,
toAmount = stringReference("0,11441958 BTC"),
toFiatAmount = null,
toAmountSymbol = "BTC",
toCurrencyIcon = CurrencyIconState.Loading,
fromAmount = stringReference("100 SOL"),
fromFiatAmount = null,
fromAmountSymbol = "SOL",
fromCurrencyIcon = CurrencyIconState.Loading,
)
}
// endregion

View file

@ -0,0 +1,46 @@
package com.tangem.common.ui.expressStatus
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.ui.Modifier
import com.tangem.common.ui.R
import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateIconUM
import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM
import com.tangem.core.ui.res.TangemTheme
import kotlinx.collections.immutable.PersistentList
fun LazyListScope.expressTransactionsItemsLegacy(
expressTxs: PersistentList<ExpressTransactionStateUM>,
modifier: Modifier = Modifier,
) {
items(
count = expressTxs.size,
key = { index -> expressTxs[index].info.txId },
contentType = { index -> expressTxs[index]::class.java },
) { index ->
val itemInfo = expressTxs[index].info
val (iconRes, tint) = when (itemInfo.iconState) {
ExpressTransactionStateIconUM.Warning -> {
R.drawable.ic_alert_triangle_20 to TangemTheme.colors.icon.attention
}
ExpressTransactionStateIconUM.Error -> {
R.drawable.ic_alert_circle_24 to TangemTheme.colors.icon.warning
}
ExpressTransactionStateIconUM.None -> null to null
}
ExpressStatusItem(
title = itemInfo.title,
fromTokenIconState = itemInfo.fromCurrencyIcon,
toTokenIconState = itemInfo.toCurrencyIcon,
fromAmount = itemInfo.fromAmount,
fromSymbol = itemInfo.fromAmountSymbol,
toAmount = itemInfo.toAmount,
toSymbol = itemInfo.toAmountSymbol,
subtitle = itemInfo.subtitle,
onClick = itemInfo.onClick,
infoIconRes = iconRes,
infoIconTint = tint,
modifier = modifier.animateItem(),
)
}
}

View file

@ -19,6 +19,11 @@ internal class EmptyExpressTransactionsComponent(
override val state: StateFlow<ExpressTransactionsBlockState> = MutableStateFlow(getInitialState())
override fun LazyListScope.expressTransactionsContentLegacy(
state: PersistentList<ExpressTransactionStateUM>,
modifier: Modifier,
) {}
override fun LazyListScope.expressTransactionsContent(
state: PersistentList<ExpressTransactionStateUM>,
modifier: Modifier,

View file

@ -17,6 +17,11 @@ internal class PreviewEmptyExpressTransactionsComponent : ExpressTransactionsCom
override val state: StateFlow<ExpressTransactionsBlockState> = MutableStateFlow(getInitialState())
override fun LazyListScope.expressTransactionsContentLegacy(
state: PersistentList<ExpressTransactionStateUM>,
modifier: Modifier,
) {}
override fun LazyListScope.expressTransactionsContent(
state: PersistentList<ExpressTransactionStateUM>,
modifier: Modifier,

View file

@ -140,7 +140,7 @@ internal fun TangemPayDetailsScreen(
}
if (state.accountDeactivatedNotificationConfig == null) {
with(expressTransactionsComponent) {
expressTransactionsContent(
expressTransactionsContentLegacy(
state = expressState.transactionsToDisplay,
modifier = modifier
.padding(horizontal = 16.dp)

View file

@ -16,6 +16,11 @@ interface ExpressTransactionsComponent {
val state: StateFlow<ExpressTransactionsBlockState>
fun LazyListScope.expressTransactionsContentLegacy(
state: PersistentList<ExpressTransactionStateUM>,
modifier: Modifier,
)
fun LazyListScope.expressTransactionsContent(state: PersistentList<ExpressTransactionStateUM>, modifier: Modifier)
data class Params(

View file

@ -9,6 +9,8 @@ interface ExpressTransactionsEventListener {
suspend fun send(event: ExpressTransactionsEvent)
}
enum class ExpressTransactionsEvent {
Update, Clear
sealed interface ExpressTransactionsEvent {
data object Update : ExpressTransactionsEvent
data object Clear : ExpressTransactionsEvent
data class OpenTx(val txId: String) : ExpressTransactionsEvent
}

View file

@ -8,7 +8,6 @@ import com.arkivanov.decompose.ComponentContext
import com.arkivanov.decompose.extensions.compose.subscribeAsState
import com.arkivanov.decompose.router.slot.childSlot
import com.arkivanov.decompose.router.slot.dismiss
import com.arkivanov.essenty.lifecycle.subscribe
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.context.child
import com.tangem.core.decompose.context.childByContext
@ -26,6 +25,7 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.ui.bottomsheet.
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.bottomsheet.CloreMigrationBottomSheetComponent
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.bottomsheet.DynamicAddressesBottomSheetComponent
import com.tangem.features.markets.token.block.TokenMarketBlockComponent
import com.tangem.features.tokendetails.ExpressTransactionsComponent
import com.tangem.features.tokendetails.TokenDetailsComponent
import com.tangem.features.tokenreceive.TokenReceiveComponent
import com.tangem.features.txhistory.component.TxHistoryComponent
@ -41,6 +41,7 @@ internal class DefaultTokenDetailsComponent @AssistedInject constructor(
@Assisted params: TokenDetailsComponent.Params,
tokenMarketBlockComponentFactory: TokenMarketBlockComponent.Factory,
txHistoryComponentFactory: TxHistoryComponent.Factory,
expressTransactionsComponentFactory: ExpressTransactionsComponent.Factory,
private val tokenReceiveComponentFactory: TokenReceiveComponent.Factory,
private val yieldSupplyWarningComponentFactory: YieldSupplyDepositedWarningComponent.Factory,
yieldSupplyComponentFactory: YieldSupplyComponent.Factory,
@ -56,6 +57,14 @@ internal class DefaultTokenDetailsComponent @AssistedInject constructor(
),
)
private val expressTransactionsComponent = expressTransactionsComponentFactory.create(
context = child("expressTransactionsComponent"),
params = ExpressTransactionsComponent.Params(
userWalletId = params.userWalletId,
currency = params.currency,
),
)
private val bottomSheetSlot = childSlot(
source = model.bottomSheetNavigation,
serializer = TokenDetailsBottomSheetConfig.serializer(),
@ -63,13 +72,6 @@ internal class DefaultTokenDetailsComponent @AssistedInject constructor(
childFactory = ::bottomSheetChild,
)
init {
lifecycle.subscribe(
onPause = model::onPause,
onResume = model::onResume,
)
}
private val tokenMarketBlockComponent = params.currency.toTokenMarketParam()?.let { tokenMarketParams ->
tokenMarketBlockComponentFactory.create(
appComponentContext = child("tokenMarketBlockComponent"),
@ -100,6 +102,7 @@ internal class DefaultTokenDetailsComponent @AssistedInject constructor(
tokenMarketBlockComponent = tokenMarketBlockComponent,
yieldSupplyComponent = yieldSupplyComponent,
txHistoryComponent = txHistoryComponent,
expressTransactionsComponent = expressTransactionsComponent,
modifier = modifier,
)
} else {
@ -109,6 +112,7 @@ internal class DefaultTokenDetailsComponent @AssistedInject constructor(
tokenMarketBlockComponent = tokenMarketBlockComponent,
txHistoryComponent = txHistoryComponent,
yieldSupplyComponent = yieldSupplyComponent,
expressTransactionsComponent = expressTransactionsComponent,
)
}

View file

@ -3,7 +3,9 @@ package com.tangem.feature.tokendetails.presentation
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.runtime.Stable
import androidx.compose.ui.Modifier
import com.arkivanov.essenty.lifecycle.subscribe
import com.tangem.common.ui.expressStatus.expressTransactionsItems
import com.tangem.common.ui.expressStatus.expressTransactionsItemsLegacy
import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM
import com.tangem.common.ui.expressStatus.state.ExpressTransactionsBlockState
import com.tangem.core.decompose.context.AppComponentContext
@ -25,6 +27,20 @@ internal class DefaultExpressTransactionsComponent @AssistedInject constructor(
private val model: ExpressTransactionsModel = getOrCreateModel(params = params)
override val state: StateFlow<ExpressTransactionsBlockState> = model.uiState
init {
lifecycle.subscribe(
onPause = model::onPause,
onResume = model::onResume,
)
}
override fun LazyListScope.expressTransactionsContentLegacy(
state: PersistentList<ExpressTransactionStateUM>,
modifier: Modifier,
) {
expressTransactionsItemsLegacy(expressTxs = state, modifier = modifier)
}
override fun LazyListScope.expressTransactionsContent(
state: PersistentList<ExpressTransactionStateUM>,
modifier: Modifier,

View file

@ -169,10 +169,7 @@ internal object TokenDetailsPreviewData {
marketPriceBlockState = marketPriceLoading,
stakingBlocksState = stakingLoadingBlock,
notifications = persistentListOf(),
expressTxs = persistentListOf(),
expressTxsToDisplay = persistentListOf(),
pullToRefreshConfig = pullToRefreshConfig,
bottomSheetConfig = null,
isBalanceHidden = false,
isMarketPriceAvailable = false,
)
@ -193,10 +190,7 @@ internal object TokenDetailsPreviewData {
),
stakingBlocksState = stakingAvailableBlock,
notifications = persistentListOf(),
expressTxs = persistentListOf(),
expressTxsToDisplay = persistentListOf(),
pullToRefreshConfig = pullToRefreshConfig,
bottomSheetConfig = null,
isBalanceHidden = false,
isMarketPriceAvailable = true,
)

View file

@ -23,6 +23,7 @@ import com.tangem.domain.models.currency.CryptoCurrency
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.tokens.UpdateDelayedNetworkStatusUseCase
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.feature.tokendetails.presentation.router.InnerTokenDetailsRouter
import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.ExpressStateFactory
@ -51,6 +52,7 @@ internal class ExpressTransactionsModel @Inject constructor(
private val router: InnerTokenDetailsRouter,
private val getAccountCryptoCurrencyStatusUseCase: GetAccountCurrencyStatusUseCase,
private val expressTransactionsEventListener: ExpressTransactionsEventListener,
private val updateDelayedNetworkStatusUseCase: UpdateDelayedNetworkStatusUseCase,
) : Model(), ExpressTransactionsClickIntents {
private val params = paramsContainer.require<ExpressTransactionsComponent.Params>()
@ -67,7 +69,7 @@ internal class ExpressTransactionsModel @Inject constructor(
private var account: Account.CryptoPortfolio? = null
private val expressTxStatusTaskScheduler = SingleTaskScheduler<PersistentList<ExpressTransactionStateUM>>()
private val waitForFirstExpressStatusEmmit = MutableStateFlow(false)
private val waitForFirstExpressStatusEmit = MutableStateFlow(false)
private val currentStateProvider: Provider<ExpressTransactionsBlockState> = Provider { internalUiState.value }
@ -97,6 +99,14 @@ internal class ExpressTransactionsModel @Inject constructor(
subscribeOnExpressTransactionsUpdates()
}
fun onResume() {
subscribeOnExpressTransactionsUpdates()
}
fun onPause() {
clear()
}
override fun onExpressTransactionClick(txId: String) {
val expressTxState = internalUiState.value.transactionsToDisplay.firstOrNull { it.info.txId == txId }
?: return
@ -155,7 +165,7 @@ internal class ExpressTransactionsModel @Inject constructor(
override fun onDismissBottomSheet() {
when (val bsContent = internalUiState.value.bottomSheetSlot?.config?.content) {
is ExpressStatusBottomSheetConfig -> {
modelScope.launch(dispatchers.main) {
modelScope.launch(dispatchers.mainImmediate) {
expressStatusFactory.removeTransactionOnBottomSheetClosed(bsContent.value)
}
}
@ -174,11 +184,19 @@ internal class ExpressTransactionsModel @Inject constructor(
when (event) {
ExpressTransactionsEvent.Update -> subscribeOnExpressTransactionsUpdates()
ExpressTransactionsEvent.Clear -> clear()
is ExpressTransactionsEvent.OpenTx -> openTxOnFirstEmit(event.txId)
}
}
}
}
private fun openTxOnFirstEmit(txId: String) {
modelScope.launch {
waitForFirstExpressStatusEmit.first { it }
onExpressTransactionClick(txId)
}
}
private fun subscribeOnCurrencyStatusUpdates() {
getAccountCryptoCurrencyStatusUseCase(userWalletId, cryptoCurrency)
.onEach { account = it.account }
@ -193,11 +211,11 @@ internal class ExpressTransactionsModel @Inject constructor(
expressTxStatusTaskScheduler.cancelTask()
expressStatusFactory.getExpressStatuses()
.distinctUntilChanged()
.onEach { waitForFirstExpressStatusEmmit.value = true }
.onEach { waitForFirstExpressStatusEmit.value = true }
.onEach { expressTxs ->
internalUiState.value = expressStatusFactory.getStateWithUpdatedExpressTxs(
expressTxs = expressTxs,
updateBalance = { /* no-op */ },
updateBalance = ::updateNetworkToSwapBalance,
)
expressTxStatusTaskScheduler.scheduleTask(
scope = modelScope,
@ -217,7 +235,7 @@ internal class ExpressTransactionsModel @Inject constructor(
onSuccess = { updatedTxs ->
internalUiState.value = expressStatusFactory.getStateWithUpdatedExpressTxs(
expressTxs = updatedTxs,
updateBalance = { /* no-op */ },
updateBalance = ::updateNetworkToSwapBalance,
)
},
onError = { /* no-op */ },
@ -229,6 +247,15 @@ internal class ExpressTransactionsModel @Inject constructor(
.saveIn(expressTxJobHolder)
}
private fun updateNetworkToSwapBalance(toCryptoCurrency: CryptoCurrency) {
modelScope.launch {
updateDelayedNetworkStatusUseCase(
userWalletId = userWalletId,
network = toCryptoCurrency.network,
)
}
}
private fun createSelectedAppCurrencyFlow(): StateFlow<AppCurrency> {
return getSelectedAppCurrencyUseCase()
.map { maybeAppCurrency ->

View file

@ -68,14 +68,6 @@ interface TokenDetailsClickIntents {
fun onBalanceSelect(config: TokenBalanceSegmentedButtonConfig)
fun onGoToRefundedTokenClick(cryptoCurrency: CryptoCurrency)
fun onOpenUrlClick(url: String)
fun onConfirmDisposeExpressStatus()
fun onDisposeExpressStatus()
fun onYieldInfoClick()
// region Clore migration
@ -174,14 +166,6 @@ internal class EmptyTokenDetailsClickIntents : TokenDetailsClickIntents {
return null
}
override fun onGoToRefundedTokenClick(cryptoCurrency: CryptoCurrency) { /* no op */ }
override fun onOpenUrlClick(url: String) { /* no op */ }
override fun onConfirmDisposeExpressStatus() { /* no op */ }
override fun onDisposeExpressStatus() { /* no op */ }
// region Clore migration
// TODO: Remove after 2025-04-01 when Clore migration ends ([REDACTED_TASK_KEY])

View file

@ -70,20 +70,4 @@ internal class TokenDetailsDialogFactory @Inject constructor(
fun showError(text: TextReference) {
uiMessageSender.send(DialogMessage(message = text))
}
fun showConfirmHideExpressStatus(onConfirm: () -> Unit) {
uiMessageSender.send(
DialogMessage(
title = resourceReference(R.string.express_status_hide_dialog_title),
message = resourceReference(R.string.express_status_hide_dialog_text),
firstActionBuilder = {
EventMessageAction(
title = resourceReference(R.string.common_hide),
onClick = onConfirm,
)
},
secondActionBuilder = { cancelAction() },
),
)
}
}

View file

@ -14,13 +14,10 @@ import com.tangem.domain.dynamicaddresses.DynamicAddressesFeatureToggles
import com.tangem.domain.dynamicaddresses.DynamicAddressesSupportedBlockchains
import com.tangem.domain.dynamicaddresses.IsXpubSupportedUseCase
import com.tangem.domain.dynamicaddresses.repository.DynamicAddressesRepository
import com.tangem.common.TangemBlogUrlBuilder
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRouter
import com.tangem.common.ui.bottomsheet.receive.AddressModel
import com.tangem.common.ui.bottomsheet.receive.mapToAddressModels
import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig
import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.event.OfframpAnalyticsEvent
@ -104,7 +101,6 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDeta
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsStateController
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsUM
import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.TokenDetailsStateFactory
import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.express.TokenDetailsExpressStatusFactory
import com.tangem.feature.tokendetails.presentation.tokendetails.state.transformer.InitializeWithCryptoCurrencyTransformer
import com.tangem.feature.tokendetails.presentation.tokendetails.state.transformer.SetBalanceLoadingTransformer
import com.tangem.feature.tokendetails.presentation.tokendetails.state.transformer.SetBalanceTransformer
@ -113,6 +109,8 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.state.transform
import com.tangem.feature.tokendetails.presentation.tokendetails.state.transformer.UpdateStakingNotificationTransformer
import com.tangem.feature.tokendetails.presentation.tokendetails.state.transformer.UpdateNotificationsTransformer
import com.tangem.feature.tokendetails.presentation.tokendetails.state.transformer.UpdateTopBarMenuTransformer
import com.tangem.features.tokendetails.ExpressTransactionsEvent
import com.tangem.features.tokendetails.ExpressTransactionsEventListener
import com.tangem.features.tokendetails.TokenDetailsComponent
import com.tangem.features.tokendetails.impl.R
import com.tangem.features.txhistory.entity.TxHistoryContentUpdateEmitter
@ -121,7 +119,6 @@ import com.tangem.features.yield.supply.api.analytics.YieldSupplyAnalytics
import com.tangem.utils.Provider
import com.tangem.utils.coroutines.*
import com.tangem.utils.extensions.isZero
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
@ -144,7 +141,6 @@ internal class TokenDetailsModel @Inject constructor(
private val getCurrencyWarningsUseCase: GetCurrencyWarningsUseCase,
private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase,
private val shouldShowPromoTokenUseCase: ShouldShowPromoTokenUseCase,
private val updateDelayedCurrencyStatusUseCase: UpdateDelayedNetworkStatusUseCase,
private val getExtendedPublicKeyForCurrencyUseCase: GetExtendedPublicKeyForCurrencyUseCase,
private val getStakingEntryInfoUseCase: GetStakingEntryInfoUseCase,
private val getStakingAvailabilityUseCase: GetStakingAvailabilityUseCase,
@ -161,8 +157,8 @@ internal class TokenDetailsModel @Inject constructor(
private val clipboardManager: ClipboardManager,
@GlobalUiMessageSender private val uiMessageSender: UiMessageSender,
private val txHistoryContentUpdateEmitter: TxHistoryContentUpdateEmitter,
private val expressTransactionsEventListener: ExpressTransactionsEventListener,
paramsContainer: ParamsContainer,
tokenDetailsExpressStatusFactory: TokenDetailsExpressStatusFactory.Factory,
getUserWalletUseCase: GetUserWalletUseCase,
private val appRouter: AppRouter,
private val router: InnerTokenDetailsRouter,
@ -189,7 +185,6 @@ internal class TokenDetailsModel @Inject constructor(
private val redesignStateController: TokenDetailsStateController,
) : Model(),
TokenDetailsClickIntents,
ExpressTransactionsClickIntents,
YieldSupplyDepositedWarningComponent.ModelCallback {
private val params = paramsContainer.require<TokenDetailsComponent.Params>()
@ -202,7 +197,6 @@ internal class TokenDetailsModel @Inject constructor(
private val marketPriceJobHolder = JobHolder()
private val refreshStateJobHolder = JobHolder()
private val warningsJobHolder = JobHolder()
private val expressTxJobHolder = JobHolder()
private val buttonsJobHolder = JobHolder()
private val stakingJobHolder = JobHolder()
private val yieldSupplyBalanceJobHolder = JobHolder()
@ -213,10 +207,6 @@ internal class TokenDetailsModel @Inject constructor(
private var cryptoCurrencyStatus: CryptoCurrencyStatus? = null
private var account: Account.CryptoPortfolio? = null
private var isBalanceLoadedEventSent = false
private val expressTxStatusTaskScheduler = SingleTaskScheduler<PersistentList<ExpressTransactionStateUM>>()
/** Transaction id to check for status */
private val waitForFirstExpressStatusEmmit = MutableStateFlow(false)
val bottomSheetNavigation: SlotNavigation<TokenDetailsBottomSheetConfig> = SlotNavigation()
@ -267,17 +257,6 @@ internal class TokenDetailsModel @Inject constructor(
}
// endregion Dynamic Addresses
private val expressStatusFactory by lazy(mode = LazyThreadSafetyMode.NONE) {
tokenDetailsExpressStatusFactory.create(
clickIntents = this,
appCurrencyProvider = Provider { selectedAppCurrencyFlow.value },
currentStateProvider = Provider { uiState.value },
cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus },
userWallet = userWallet,
cryptoCurrency = cryptoCurrency,
)
}
private val notificationsAnalyticsSender by lazy(mode = LazyThreadSafetyMode.NONE) {
TokenDetailsNotificationsAnalyticsSender(
cryptoCurrency = cryptoCurrency,
@ -299,21 +278,6 @@ internal class TokenDetailsModel @Inject constructor(
handleNavigationParam()
}
fun onResume() {
subscribeOnExpressTransactionsUpdates()
}
fun onPause() {
expressTxStatusTaskScheduler.cancelTask()
expressTxJobHolder.cancel()
}
override fun onDestroy() {
expressTxStatusTaskScheduler.cancelTask()
expressTxJobHolder.cancel()
super.onDestroy()
}
private fun initButtons() {
// we need also init buttons before start all loading to avoid buttons blocking
modelScope.launch {
@ -335,7 +299,6 @@ internal class TokenDetailsModel @Inject constructor(
private fun updateContent() {
subscribeOnCurrencyStatusUpdates()
subscribeOnExpressTransactionsUpdates()
}
private fun handleBalanceHiding() {
@ -426,40 +389,6 @@ internal class TokenDetailsModel @Inject constructor(
.saveIn(marketPriceJobHolder)
}
private fun subscribeOnExpressTransactionsUpdates() {
expressTxStatusTaskScheduler.cancelTask()
expressStatusFactory.getExpressStatuses()
.distinctUntilChanged()
.onEach { waitForFirstExpressStatusEmmit.value = true }
.onEach { expressTxs ->
uiState.value = expressStatusFactory.getStateWithUpdatedExpressTxs(
expressTxs = expressTxs,
updateBalance = ::updateNetworkToSwapBalance,
)
expressTxStatusTaskScheduler.scheduleTask(
scope = modelScope,
task = PeriodicTask(
delay = EXPRESS_STATUS_UPDATE_DELAY,
task = {
runSuspendCatching {
expressStatusFactory.getUpdatedExpressStatuses(uiState.value.expressTxs)
}
},
onSuccess = { updatedTxs ->
uiState.value = expressStatusFactory.getStateWithUpdatedExpressTxs(
updatedTxs,
::updateNetworkToSwapBalance,
)
},
onError = { /* no-op */ },
),
)
}
.flowOn(dispatchers.main)
.launchIn(modelScope)
.saveIn(expressTxJobHolder)
}
private fun subscribeOnYieldSupplyBalanceIfActive(status: CryptoCurrencyStatus) {
if (status.value.yieldSupplyStatus?.isActive == true) {
if (yieldSupplyBalanceJobHolder.isActive && status.value.sources.networkSource != StatusSource.ACTUAL) {
@ -480,15 +409,6 @@ internal class TokenDetailsModel @Inject constructor(
}
}
private fun updateNetworkToSwapBalance(toCryptoCurrency: CryptoCurrency) {
modelScope.launch {
updateDelayedCurrencyStatusUseCase(
userWalletId = userWalletId,
network = toCryptoCurrency.network,
)
}
}
private fun updateTxHistory() {
modelScope.launch { txHistoryContentUpdateEmitter.triggerUpdate() }
}
@ -922,52 +842,17 @@ internal class TokenDetailsModel @Inject constructor(
},
async {
updateTxHistory()
subscribeOnExpressTransactionsUpdates()
expressTransactionsEventListener.send(ExpressTransactionsEvent.Update)
},
).awaitAll()
uiState.value = stateFactory.getRefreshedState()
}.saveIn(refreshStateJobHolder)
}
override fun onDismissBottomSheet() {
when (val bsContent = uiState.value.bottomSheetConfig?.content) {
is ExpressStatusBottomSheetConfig -> {
modelScope.launch(dispatchers.main) {
expressStatusFactory.removeTransactionOnBottomSheetClosed(bsContent.value)
}
}
}
uiState.value = stateFactory.getStateWithClosedBottomSheet()
}
override fun onCloseRentInfoNotification() {
uiState.value = stateFactory.getStateWithRemovedRentNotification()
}
override fun onExpressTransactionClick(txId: String) {
val expressTxState = uiState.value.expressTxsToDisplay.firstOrNull { it.info.txId == txId }
?: return
uiState.value = expressStatusFactory.getStateWithExpressStatusBottomSheet(expressTxState)
}
override fun onGoToProviderClick(url: String) {
router.openUrl(url)
}
override fun onGoToRefundedTokenClick(cryptoCurrency: CryptoCurrency) {
router.openTokenDetails(userWalletId, cryptoCurrency)
}
override fun onOpenUrlClick(url: String) {
router.openUrl(url)
}
override fun onReadAboutCrossChainBridgesClick() {
modelScope.launch {
router.openUrl(TangemBlogUrlBuilder.build(TangemBlogUrlBuilder.Post.AboutCrossChainBridges))
}
}
override fun onSwapPromoDismiss(promoId: PromoId) {
modelScope.launch(dispatchers.main) {
shouldShowPromoTokenUseCase.neverToShow(promoId)
@ -1161,23 +1046,6 @@ internal class TokenDetailsModel @Inject constructor(
uiState.value = stateFactory.getStateWithUpdatedBalanceSegmentedButtonConfig(config)
}
override fun onConfirmDisposeExpressStatus() {
dialogFactory.showConfirmHideExpressStatus(onConfirm = ::onDisposeExpressStatus)
}
override fun onDisposeExpressStatus() {
val bottomSheetState = uiState.value.bottomSheetConfig?.content
if (bottomSheetState is ExpressStatusBottomSheetConfig) {
modelScope.launch {
expressStatusFactory.removeTransactionOnBottomSheetClosed(
expressState = bottomSheetState.value,
isForceDispose = true,
)
}
}
uiState.value = stateFactory.getStateWithClosedBottomSheet()
}
override fun onYieldInfoClick() {
analyticsEventsHandler.send(
YieldSupplyAnalytics.EarnedFundsInfo(
@ -1226,11 +1094,8 @@ internal class TokenDetailsModel @Inject constructor(
}
private fun checkForActionUpdates() {
combine(
tokenDetailsDeepLinkActionListener.tokenDetailsActionFlow,
waitForFirstExpressStatusEmmit.filter { it },
) { transactionId, _ -> transactionId }
.onEach(::onExpressTransactionClick)
tokenDetailsDeepLinkActionListener.tokenDetailsActionFlow
.onEach { txId -> expressTransactionsEventListener.send(ExpressTransactionsEvent.OpenTx(txId)) }
.launchIn(modelScope)
}
@ -1519,7 +1384,6 @@ internal class TokenDetailsModel @Inject constructor(
)
private companion object {
const val EXPRESS_STATUS_UPDATE_DELAY = 10_000L
const val BASE_DERIVATION_NODE_COUNT = 5
}
}

View file

@ -1,12 +1,9 @@
package com.tangem.feature.tokendetails.presentation.tokendetails.state
import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig
import com.tangem.core.ui.components.marketprice.MarketPriceBlockState
import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsNotification
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.PersistentList
internal data class TokenDetailsState(
val topAppBarConfig: TokenDetailsTopAppBarConfig,
@ -15,10 +12,7 @@ internal data class TokenDetailsState(
val marketPriceBlockState: MarketPriceBlockState,
val stakingBlocksState: StakingBlockUM?,
val notifications: ImmutableList<TokenDetailsNotification>,
val expressTxsToDisplay: PersistentList<ExpressTransactionStateUM>,
val expressTxs: PersistentList<ExpressTransactionStateUM>,
val pullToRefreshConfig: PullToRefreshConfig,
val bottomSheetConfig: TangemBottomSheetConfig?,
val isBalanceHidden: Boolean,
val isMarketPriceAvailable: Boolean,
)

View file

@ -63,10 +63,7 @@ internal class TokenDetailsSkeletonStateConverter(
marketPriceBlockState = MarketPriceBlockState.Loading(value.symbol),
stakingBlocksState = StakingBlockUM.Loading(iconState).takeIf { isSupportedInMobileApp },
notifications = persistentListOf(),
expressTxs = persistentListOf(),
expressTxsToDisplay = persistentListOf(),
pullToRefreshConfig = createPullToRefresh(),
bottomSheetConfig = null,
isBalanceHidden = true,
isMarketPriceAvailable = value.id.rawCurrencyId != null,
)

View file

@ -123,13 +123,6 @@ internal class TokenDetailsStateFactory(
return refreshStateConverter.convert(false)
}
fun getStateWithClosedBottomSheet(): TokenDetailsState {
val state = currentStateProvider()
return state.copy(
bottomSheetConfig = state.bottomSheetConfig?.copy(isShown = false),
)
}
fun getStateWithUpdatedHidden(isBalanceHidden: Boolean): TokenDetailsState {
val currentState = currentStateProvider()

View file

@ -1,254 +0,0 @@
package com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.express
import arrow.core.getOrElse
import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.datasource.local.swap.ExpressAnalyticsStatus
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.AccountId
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.quote.QuoteStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.quotes.QuotesRepository
import com.tangem.domain.tokens.model.analytics.TokenExchangeAnalyticsEvent
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.feature.swap.domain.SwapTransactionRepository
import com.tangem.feature.swap.domain.api.SwapRepository
import com.tangem.feature.swap.domain.models.domain.*
import com.tangem.feature.tokendetails.presentation.tokendetails.model.ExpressTransactionsClickIntents
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState
import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExchangeUM
import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.TokenDetailsSwapTransactionsStateConverter
import com.tangem.utils.Provider
import com.tangem.utils.logging.TangemLogger
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.map
import kotlin.coroutines.cancellation.CancellationException
@Suppress("LongParameterList")
internal class TokenDetailsExchangeStatusFactory @AssistedInject constructor(
private val swapTransactionRepository: SwapTransactionRepository,
private val swapRepository: SwapRepository,
private val quotesRepository: QuotesRepository,
private val getAccountCurrencyStatusUseCase: GetAccountCurrencyStatusUseCase,
private val manageCryptoCurrenciesUseCase: ManageCryptoCurrenciesUseCase,
private val swapTransactionStatusStore: SwapTransactionStatusStore,
private val analyticsEventsHandler: AnalyticsEventHandler,
private val getUserWalletUseCase: GetUserWalletUseCase,
@Assisted private val clickIntents: ExpressTransactionsClickIntents,
@Assisted private val appCurrencyProvider: Provider<AppCurrency>,
@Assisted private val currentStateProvider: Provider<TokenDetailsState>,
@Assisted private val userWallet: UserWallet,
@Assisted private val cryptoCurrency: CryptoCurrency,
) {
private val swapTransactionsStateConverter by lazy {
TokenDetailsSwapTransactionsStateConverter(
clickIntents = clickIntents,
cryptoCurrency = cryptoCurrency,
appCurrencyProvider = appCurrencyProvider,
analyticsEventsHandler = analyticsEventsHandler,
)
}
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()
.orEmpty()
getExchangeStatusState(
savedTransactions = savedTransactions,
quoteStatuses = quotes,
)
}
}
suspend fun removeTransactionOnBottomSheetClosed(isForceDispose: Boolean = false) {
val state = currentStateProvider()
val bottomSheetConfig = state.bottomSheetConfig?.content as? ExpressStatusBottomSheetConfig ?: return
val selectedTx = bottomSheetConfig.value as? ExchangeUM ?: return
val shouldDispose = selectedTx.activeStatus?.isAutoDisposable == true || isForceDispose
if (shouldDispose) {
swapTransactionRepository.removeTransaction(
userWalletId = userWallet.walletId,
txId = selectedTx.info.txId,
)
}
}
suspend fun updateSwapTxStatus(swapTx: ExchangeUM): ExchangeUM {
return if (swapTx.activeStatus?.isTerminal == true) {
swapTx
} else {
val statusModel = getExchangeStatus(swapTx.info.txId, swapTx.provider, swapTx.fromUserWalletId)
if (statusModel != null) {
swapTransactionsStateConverter.updateTxStatus(
tx = swapTx,
statusModel = statusModel,
)
} else {
swapTx
}
}
}
private suspend fun getExchangeStatus(
txId: String,
provider: SwapProvider,
fromUserWalletId: UserWalletId,
): ExchangeStatusModel? {
val fromUserWallet = getUserWalletUseCase(fromUserWalletId).getOrElse { error ->
TangemLogger.e("Couldn't find userWallet: $error")
return null
}
return swapRepository.getExchangeStatus(
userWallet = fromUserWallet,
userWalletId = fromUserWalletId,
txId = txId,
).fold(
ifLeft = { null },
ifRight = { statusModel ->
sendStatusUpdateAnalytics(statusModel, provider)
val accountId = getAccountCurrencyStatusUseCase.invokeSync(
userWalletId = fromUserWalletId,
currency = cryptoCurrency,
)
.map { it.account.accountId }
.getOrNull()
val refundTokenCurrency = if (accountId != null) {
addRefundCurrencyIfNeeded(
accountId = accountId,
status = statusModel,
type = provider.type,
)
} else {
TangemLogger.e("Account ID is null, cannot add refund currency ${cryptoCurrency.id}")
null
}
swapTransactionRepository.storeTransactionState(
txId = txId,
status = statusModel,
accountWithCurrency = if (refundTokenCurrency != null) {
Pair(accountId, refundTokenCurrency)
} else {
null
},
)
statusModel.copy(refundCurrency = refundTokenCurrency)
},
)
}
private suspend fun sendStatusUpdateAnalytics(statusModel: ExchangeStatusModel, provider: SwapProvider) {
val txId = statusModel.txId ?: return
val status = toAnalyticStatus(statusModel.status) ?: return
val savedStatus = swapTransactionStatusStore.getTransactionStatus(txId)
if (savedStatus != status) {
analyticsEventsHandler.send(
TokenExchangeAnalyticsEvent.CexTxStatusChanged(cryptoCurrency.symbol, status.value, provider.name),
)
swapTransactionStatusStore.setTransactionStatus(txId, status)
}
}
private suspend fun addRefundCurrencyIfNeeded(
accountId: AccountId,
status: ExchangeStatusModel?,
type: ExchangeProviderType,
): CryptoCurrency? {
status ?: return null
if (type != ExchangeProviderType.DEX_BRIDGE) return null
val refundNetwork = status.refundNetwork
val refundContractAddress = status.refundContractAddress
if (refundNetwork == null || refundContractAddress == null) return null
return manageCryptoCurrenciesUseCase.add(
accountId = accountId,
contractAddress = refundContractAddress,
networkId = refundNetwork,
)
.onLeft { TangemLogger.e("Error", it) }
.getOrNull()
}
private fun getExchangeStatusState(
savedTransactions: List<SavedSwapTransactionListModel>?,
quoteStatuses: Set<QuoteStatus>,
): PersistentList<ExchangeUM> {
if (savedTransactions == null) {
return persistentListOf()
}
return swapTransactionsStateConverter.convert(
savedTransactions = savedTransactions,
quoteStatuses = quoteStatuses,
)
}
private fun toAnalyticStatus(status: ExchangeStatus?): ExpressAnalyticsStatus? {
return when (status) {
ExchangeStatus.New,
ExchangeStatus.Waiting,
ExchangeStatus.Sending,
ExchangeStatus.Confirming,
ExchangeStatus.Exchanging,
-> ExpressAnalyticsStatus.InProgress
ExchangeStatus.WaitingTxHash -> ExpressAnalyticsStatus.WaitingTxHash
ExchangeStatus.Verifying -> ExpressAnalyticsStatus.KYC
ExchangeStatus.Failed -> ExpressAnalyticsStatus.Fail
ExchangeStatus.TxFailed -> ExpressAnalyticsStatus.FailTx
ExchangeStatus.Finished -> ExpressAnalyticsStatus.Done
ExchangeStatus.Refunded -> ExpressAnalyticsStatus.Refunded
ExchangeStatus.Cancelled -> ExpressAnalyticsStatus.Cancelled
ExchangeStatus.Unknown -> ExpressAnalyticsStatus.Unknown
else -> null
}
}
private suspend fun Set<CryptoCurrency.ID>.getQuotesOrEmpty(): Set<QuoteStatus> {
val rawIds = mapNotNull { it.rawCurrencyId }.toSet()
return try {
quotesRepository.getMultiQuoteSyncOrNull(currenciesIds = rawIds).orEmpty()
} catch (exception: CancellationException) {
throw exception
} catch (ignore: Exception) {
emptySet()
}
}
@AssistedFactory
interface Factory {
fun create(
clickIntents: ExpressTransactionsClickIntents,
appCurrencyProvider: Provider<AppCurrency>,
currentStateProvider: Provider<TokenDetailsState>,
userWallet: UserWallet,
cryptoCurrency: CryptoCurrency,
): TokenDetailsExchangeStatusFactory
}
}

View file

@ -1,217 +0,0 @@
package com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.express
import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig
import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.tokens.model.analytics.TokenExchangeAnalyticsEvent
import com.tangem.domain.tokens.model.analytics.TokenOnrampAnalyticsEvent
import com.tangem.domain.tokens.model.analytics.TokenScreenAnalyticsEvent
import com.tangem.feature.swap.domain.models.domain.ExchangeStatus
import com.tangem.feature.tokendetails.presentation.tokendetails.model.ExpressTransactionsClickIntents
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState
import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.ExchangeStatusNotification
import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExchangeUM
import com.tangem.utils.Provider
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.collections.immutable.PersistentList
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.combine
import kotlinx.coroutines.withContext
@Suppress("LongParameterList")
internal class TokenDetailsExpressStatusFactory @AssistedInject constructor(
@Assisted private val currentStateProvider: Provider<TokenDetailsState>,
@Assisted private val clickIntents: ExpressTransactionsClickIntents,
@Assisted private val cryptoCurrency: CryptoCurrency,
@Assisted appCurrencyProvider: Provider<AppCurrency>,
@Assisted userWallet: UserWallet,
@Assisted cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus?>,
private val dispatchers: CoroutineDispatcherProvider,
private val analyticsEventsHandler: AnalyticsEventHandler,
tokenDetailsOnrampStatusFactory: TokenDetailsOnrampStatusFactory.Factory,
tokenDetailsExchangeStatusFactory: TokenDetailsExchangeStatusFactory.Factory,
) {
private val exchangeStatusFactory by lazy(mode = LazyThreadSafetyMode.NONE) {
tokenDetailsExchangeStatusFactory.create(
clickIntents = clickIntents,
appCurrencyProvider = appCurrencyProvider,
currentStateProvider = currentStateProvider,
userWallet = userWallet,
cryptoCurrency = cryptoCurrency,
)
}
private val onrampStatusFactory by lazy(LazyThreadSafetyMode.NONE) {
tokenDetailsOnrampStatusFactory.create(
currentStateProvider = currentStateProvider,
cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider,
appCurrencyProvider = appCurrencyProvider,
clickIntents = clickIntents,
cryptoCurrency = cryptoCurrency,
userWallet = userWallet,
)
}
fun getExpressStatuses(): Flow<PersistentList<ExpressTransactionStateUM>> = combine(
flow = exchangeStatusFactory(),
flow2 = onrampStatusFactory(),
) { maybeExchange, maybeOnramp ->
persistentListOf(maybeOnramp, maybeExchange)
.flatten()
.sortedByDescending { it.info.timestamp }
.toPersistentList()
}
suspend fun getUpdatedExpressStatuses(expressTxs: PersistentList<ExpressTransactionStateUM>) =
withContext(dispatchers.io) {
expressTxs.map { tx ->
async {
when (tx) {
is ExchangeUM -> exchangeStatusFactory.updateSwapTxStatus(tx)
is ExpressTransactionStateUM.OnrampUM -> onrampStatusFactory.updateOnrmapTxStatus(tx)
else -> null
}
}
}.awaitAll()
.filterNotNull()
.toPersistentList()
}
fun getStateWithUpdatedExpressTxs(
expressTxs: PersistentList<ExpressTransactionStateUM>,
updateBalance: (CryptoCurrency) -> Unit,
): TokenDetailsState {
val state = currentStateProvider()
val config = state.bottomSheetConfig
val expressBottomSheet = config?.content as? ExpressStatusBottomSheetConfig
val currentTx = expressTxs.firstOrNull { it.info.txId == expressBottomSheet?.value?.info?.txId }
if (currentTx is ExchangeUM && currentTx.activeStatus == ExchangeStatus.Finished) {
updateBalance(currentTx.toCryptoCurrency)
}
val expressTxsToDisplay = expressTxs.filterNot { txs ->
when (txs) {
is ExpressTransactionStateUM.OnrampUM -> txs.activeStatus.isHidden
else -> false
}
}.toPersistentList()
return state.copy(
expressTxs = expressTxs,
expressTxsToDisplay = expressTxsToDisplay,
bottomSheetConfig = currentTx?.let(::updateStateWithExpressStatusBottomSheet) ?: config,
)
}
fun getStateWithExpressStatusBottomSheet(expressState: ExpressTransactionStateUM): TokenDetailsState {
val analyticEvents = when (expressState) {
is ExchangeUM -> listOfNotNull(
TokenExchangeAnalyticsEvent.CexTxStatusOpened(
token = cryptoCurrency.symbol,
provider = expressState.provider.name,
),
maybeGetLongTimeExchangeNotificationShowEvent(
expressState = expressState,
currentStateNotification = null,
isBottomSheetShown = true,
),
)
is ExpressTransactionStateUM.OnrampUM -> listOf(
TokenOnrampAnalyticsEvent.OnrampStatusOpened(
tokenSymbol = cryptoCurrency.symbol,
provider = expressState.providerName,
fiatCurrency = expressState.fromCurrencyCode,
),
)
else -> return currentStateProvider()
}
analyticEvents.forEach { analyticsEventsHandler.send(it) }
return currentStateProvider().copy(
bottomSheetConfig = TangemBottomSheetConfig(
isShown = true,
onDismissRequest = clickIntents::onDismissBottomSheet,
content = ExpressStatusBottomSheetConfig(
value = expressState,
),
),
)
}
fun updateStateWithExpressStatusBottomSheet(expressState: ExpressTransactionStateUM): TangemBottomSheetConfig? {
val state = currentStateProvider()
val bottomSheetConfig = state.bottomSheetConfig
val currentConfig = bottomSheetConfig?.content as? ExpressStatusBottomSheetConfig ?: return bottomSheetConfig
maybeGetLongTimeExchangeNotificationShowEvent(
expressState = expressState,
currentStateNotification = (currentConfig.value as? ExchangeUM)?.notification,
isBottomSheetShown = bottomSheetConfig.isShown,
)?.let { analyticsEventsHandler.send(it) }
return bottomSheetConfig.copy(
content = if (currentConfig.value != expressState) {
ExpressStatusBottomSheetConfig(expressState)
} else {
currentConfig
},
)
}
suspend fun removeTransactionOnBottomSheetClosed(
expressState: ExpressTransactionStateUM,
isForceDispose: Boolean = false,
) {
when (expressState) {
is ExchangeUM -> exchangeStatusFactory.removeTransactionOnBottomSheetClosed(isForceDispose)
is ExpressTransactionStateUM.OnrampUM -> onrampStatusFactory.removeTransactionOnBottomSheetClosed(
isForceDispose,
)
}
}
private fun maybeGetLongTimeExchangeNotificationShowEvent(
expressState: ExpressTransactionStateUM,
currentStateNotification: ExchangeStatusNotification?,
isBottomSheetShown: Boolean,
): TokenScreenAnalyticsEvent? {
val newState = expressState as? ExchangeUM
val newStateNotification = newState?.notification
return if (currentStateNotification !is ExchangeStatusNotification.LongTimeExchange &&
newStateNotification is ExchangeStatusNotification.LongTimeExchange &&
isBottomSheetShown
) {
TokenExchangeAnalyticsEvent.LongTimeTransaction(
token = cryptoCurrency.symbol,
provider = newState.provider.name,
)
} else {
null
}
}
@AssistedFactory
interface Factory {
@Suppress("LongParameterList")
fun create(
clickIntents: ExpressTransactionsClickIntents,
appCurrencyProvider: Provider<AppCurrency>,
currentStateProvider: Provider<TokenDetailsState>,
userWallet: UserWallet,
cryptoCurrency: CryptoCurrency,
cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus?>,
): TokenDetailsExpressStatusFactory
}
}

View file

@ -1,165 +0,0 @@
package com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.express
import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig
import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.datasource.local.swap.ExpressAnalyticsStatus
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.onramp.GetOnrampStatusUseCase
import com.tangem.domain.onramp.GetOnrampTransactionsUseCase
import com.tangem.domain.onramp.OnrampRemoveTransactionUseCase
import com.tangem.domain.onramp.OnrampUpdateTransactionStatusUseCase
import com.tangem.domain.onramp.model.OnrampStatus
import com.tangem.domain.onramp.model.OnrampStatus.Status.*
import com.tangem.domain.tokens.model.analytics.TokenOnrampAnalyticsEvent
import com.tangem.feature.tokendetails.presentation.tokendetails.model.ExpressTransactionsClickIntents
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState
import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.TokenDetailsOnrampTransactionStateConverter
import com.tangem.utils.Provider
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import com.tangem.utils.logging.TangemLogger
@Suppress("LongParameterList")
internal class TokenDetailsOnrampStatusFactory @AssistedInject constructor(
private val getOnrampTransactionsUseCase: GetOnrampTransactionsUseCase,
private val getOnrampStatusUseCase: GetOnrampStatusUseCase,
private val onrampRemoveTransactionUseCase: OnrampRemoveTransactionUseCase,
private val onrampUpdateTransactionStatusUseCase: OnrampUpdateTransactionStatusUseCase,
private val analyticsEventHandler: AnalyticsEventHandler,
@Assisted private val currentStateProvider: Provider<TokenDetailsState>,
@Assisted private val cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus?>,
@Assisted private val appCurrencyProvider: Provider<AppCurrency>,
@Assisted private val clickIntents: ExpressTransactionsClickIntents,
@Assisted private val cryptoCurrency: CryptoCurrency,
@Assisted private val userWallet: UserWallet,
) {
private val onrampTransactionStateConverter by lazy(LazyThreadSafetyMode.NONE) {
TokenDetailsOnrampTransactionStateConverter(
clickIntents = clickIntents,
cryptoCurrency = cryptoCurrency,
cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider,
appCurrencyProvider = appCurrencyProvider,
analyticsEventHandler = analyticsEventHandler,
)
}
operator fun invoke(): Flow<List<ExpressTransactionStateUM.OnrampUM>> {
return getOnrampTransactionsUseCase(
userWalletId = userWallet.walletId,
cryptoCurrencyId = cryptoCurrency.id,
).map { maybeTransaction ->
maybeTransaction.fold(
ifRight = { onrampTxs ->
val transactions = onrampTransactionStateConverter.convertList(onrampTxs)
transactions.clearHiddenTerminal()
transactions
},
ifLeft = { persistentListOf() },
)
}
}
suspend fun removeTransactionOnBottomSheetClosed(isForceDispose: Boolean) {
val state = currentStateProvider()
val bottomSheetConfig = state.bottomSheetConfig?.content as? ExpressStatusBottomSheetConfig ?: return
val selectedTx = bottomSheetConfig.value as? ExpressTransactionStateUM.OnrampUM ?: return
if (selectedTx.activeStatus.isAutoDisposable || isForceDispose) {
onrampRemoveTransactionUseCase(txId = selectedTx.info.txId)
}
}
suspend fun updateOnrmapTxStatus(onrampTx: ExpressTransactionStateUM.OnrampUM): ExpressTransactionStateUM.OnrampUM {
return if (onrampTx.activeStatus.isTerminal) {
onrampTx
} else {
getOnrampStatusUseCase(userWallet = userWallet, onrampTx.info.txId).fold(
ifLeft = { error ->
TangemLogger.e("Couldn't update onramp status. $error")
onrampTx
},
ifRight = { statusModel ->
sendStatusUpdateAnalytics(onrampTx, statusModel)
onrampTx.copy(
activeStatus = statusModel.status,
info = onrampTx.info.copy(
txExternalId = statusModel.externalTxId,
txExternalUrl = statusModel.externalTxUrl,
),
)
},
)
}
}
private suspend fun List<ExpressTransactionStateUM.OnrampUM>.clearHiddenTerminal() {
this.filter { it.activeStatus.isHidden && it.activeStatus.isTerminal }
.forEach { onrampRemoveTransactionUseCase(txId = it.info.txId) }
}
private suspend fun sendStatusUpdateAnalytics(
onrampTx: ExpressTransactionStateUM.OnrampUM,
statusModel: OnrampStatus,
) {
val txId = statusModel.txId
val status = toAnalyticStatus(statusModel.status) ?: return
if (statusModel.status != onrampTx.activeStatus) {
analyticsEventHandler.send(
TokenOnrampAnalyticsEvent.OnrampStatusChanged(
tokenSymbol = cryptoCurrency.symbol,
status = status.name,
provider = onrampTx.providerName,
fiatCurrency = onrampTx.fromCurrencyCode,
),
)
onrampUpdateTransactionStatusUseCase(
txId = txId,
externalTxUrl = statusModel.externalTxUrl.orEmpty(),
externalTxId = statusModel.externalTxId.orEmpty(),
status = statusModel.status,
)
}
}
private fun toAnalyticStatus(status: OnrampStatus.Status?): ExpressAnalyticsStatus? {
return when (status) {
Expired,
Paused,
-> ExpressAnalyticsStatus.Cancelled
Created,
WaitingForPayment,
PaymentProcessing,
Paid,
Sending,
RefundInProgress,
-> ExpressAnalyticsStatus.InProgress
Verifying -> ExpressAnalyticsStatus.KYC
Failed -> ExpressAnalyticsStatus.Fail
Finished -> ExpressAnalyticsStatus.Done
Refunded -> ExpressAnalyticsStatus.Refunded
null -> null
}
}
@AssistedFactory
interface Factory {
fun create(
currentStateProvider: Provider<TokenDetailsState>,
cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus?>,
appCurrencyProvider: Provider<AppCurrency>,
clickIntents: ExpressTransactionsClickIntents,
cryptoCurrency: CryptoCurrency,
userWallet: UserWallet,
): TokenDetailsOnrampStatusFactory
}
}

View file

@ -18,7 +18,6 @@ import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.rememberLazyListState
import com.tangem.common.ui.earn.EarnBlock
import com.tangem.common.ui.notifications.notifications
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@ -31,12 +30,13 @@ import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.tangem.common.ui.account.AccountIconUM
import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM
import com.tangem.common.ui.expressStatus.state.ExpressTransactionsBlockState
import com.tangem.core.ui.components.BottomFade
import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
@ -60,12 +60,14 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDeta
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.TokenDetailsBalanceBlock
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.TokenDetailsBalanceBlockHeight
import com.tangem.features.markets.token.block.TokenMarketBlockComponent
import com.tangem.features.tokendetails.ExpressTransactionsComponent
import com.tangem.features.txhistory.component.TxHistoryComponent
import com.tangem.features.txhistory.entity.TxHistoryItemsUM
import com.tangem.features.txhistory.entity.TxHistoryUM
import com.tangem.features.yield.supply.api.YieldSupplyComponent
import dev.chrisbanes.haze.HazeProgressive
import dev.chrisbanes.haze.HazeTint
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@ -79,8 +81,10 @@ internal fun TokenDetailsScreen(
tokenMarketBlockComponent: TokenMarketBlockComponent?,
yieldSupplyComponent: YieldSupplyComponent,
txHistoryComponent: TxHistoryComponent,
expressTransactionsComponent: ExpressTransactionsComponent,
modifier: Modifier = Modifier,
) {
val expressState by expressTransactionsComponent.state.collectAsStateWithLifecycle()
val statusBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getTop(this).toDp() }
val partialCollapsedHeight = TopBarHeight + statusBarHeight
val expandedHeight = TokenDetailsBalanceBlockHeight + partialCollapsedHeight
@ -95,9 +99,6 @@ internal fun TokenDetailsScreen(
val bottomBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getBottom(this).toDp() }
val fadeFloorHeight = TangemTheme.dimens.size100 + bottomBarHeight
val effectiveBottomPadding = maxOf(partialCollapsedHeight + marketBlockHeight, fadeFloorHeight)
val notificationModifier = Modifier
.fillMaxWidth()
.padding(horizontal = TangemTheme.dimens2.x4)
Box(
modifier = modifier.fillMaxSize(),
@ -124,12 +125,13 @@ internal fun TokenDetailsScreen(
tokenDetailsUM = tokenDetailsUM,
yieldSupplyComponent = yieldSupplyComponent,
txHistoryComponent = txHistoryComponent,
expressTransactionsComponent = expressTransactionsComponent,
expressTransactionsToDisplay = expressState.transactionsToDisplay,
rootBackground = rootBackground,
bottomContentPadding = effectiveBottomPadding,
modifier = Modifier
.fillMaxSize()
.nestedScroll(behavior.nestedScrollConnection),
itemModifier = notificationModifier,
)
},
)
@ -148,6 +150,8 @@ internal fun TokenDetailsScreen(
onHeightChange = { marketBlockHeight = it },
)
}
expressState.bottomSheetSlot?.content()
}
}
@ -207,18 +211,27 @@ private fun BoxScope.TokenDetailsMarketBlockOverlay(
)
}
@Suppress("LongParameterList")
@Composable
private fun TokenDetailsBody(
tokenDetailsUM: TokenDetailsUM,
yieldSupplyComponent: YieldSupplyComponent,
txHistoryComponent: TxHistoryComponent,
expressTransactionsComponent: ExpressTransactionsComponent,
expressTransactionsToDisplay: PersistentList<ExpressTransactionStateUM>,
rootBackground: Color,
bottomContentPadding: Dp,
modifier: Modifier = Modifier,
itemModifier: Modifier = Modifier,
) {
val listState = rememberLazyListState()
val txHistoryState by txHistoryComponent.txHistoryState.collectAsStateWithLifecycle()
val itemModifier = Modifier
.fillMaxWidth()
.padding(horizontal = TangemTheme.dimens2.x4)
val expressTransactionModifier = Modifier
.fillMaxWidth()
.padding(start = TangemTheme.dimens2.x4, end = TangemTheme.dimens2.x4, top = TangemTheme.dimens2.x4)
LazyColumn(
modifier = modifier,
@ -241,6 +254,12 @@ private fun TokenDetailsBody(
item(key = "yield_supply_block") {
yieldSupplyComponent.Content(modifier = itemModifier.padding(vertical = TangemTheme.dimens2.x2))
}
with(expressTransactionsComponent) {
expressTransactionsContent(
state = expressTransactionsToDisplay,
modifier = expressTransactionModifier,
)
}
with(txHistoryComponent) {
txHistoryContent(listState = listState, state = txHistoryState)
}
@ -307,7 +326,28 @@ private fun TokenDetailsScreen_Preview() {
override fun LazyListScope.txHistoryContent(listState: LazyListState, state: TxHistoryItemsUM) = Unit
},
expressTransactionsComponent = PreviewExpressTransactionsComponent,
)
}
}
private val PreviewExpressTransactionsComponent = object : ExpressTransactionsComponent {
override val state: StateFlow<ExpressTransactionsBlockState> = MutableStateFlow(
ExpressTransactionsBlockState(
transactions = persistentListOf(),
transactionsToDisplay = persistentListOf(),
bottomSheetSlot = null,
),
)
override fun LazyListScope.expressTransactionsContentLegacy(
state: PersistentList<ExpressTransactionStateUM>,
modifier: Modifier,
) = Unit
override fun LazyListScope.expressTransactionsContent(
state: PersistentList<ExpressTransactionStateUM>,
modifier: Modifier,
) = Unit
}
// endregion Preview

View file

@ -14,8 +14,8 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig
import com.tangem.common.ui.expressStatus.expressTransactionsItems
import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM
import com.tangem.common.ui.expressStatus.state.ExpressTransactionsBlockState
import com.tangem.core.ui.components.containers.pullToRefresh.TangemPullToRefreshContainer
import com.tangem.core.ui.components.marketprice.MarketPriceBlock
import com.tangem.core.ui.components.marketprice.MarketPriceBlockState
@ -30,13 +30,15 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.state.component
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.TokenDetailsBalanceBlockLegacy
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.express.ExpressStatusBottomSheet
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.staking.TokenStakingBlockLegacy
import com.tangem.features.markets.token.block.TokenMarketBlockComponent
import com.tangem.features.tokendetails.ExpressTransactionsComponent
import com.tangem.features.txhistory.component.TxHistoryComponent
import com.tangem.features.txhistory.entity.TxHistoryItemsUM
import com.tangem.features.txhistory.entity.TxHistoryUM
import com.tangem.features.yield.supply.api.YieldSupplyComponent
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@ -48,6 +50,7 @@ internal fun TokenDetailsScreenLegacy(
tokenMarketBlockComponent: TokenMarketBlockComponent?,
txHistoryComponent: TxHistoryComponent,
yieldSupplyComponent: YieldSupplyComponent,
expressTransactionsComponent: ExpressTransactionsComponent,
) {
val bottomBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getBottom(this).toDp() }
@ -58,6 +61,7 @@ internal fun TokenDetailsScreenLegacy(
) { scaffoldPaddings ->
val listState = rememberLazyListState()
val txHistoryComponentState by txHistoryComponent.legacyTxHistoryState.collectAsStateWithLifecycle()
val expressState by expressTransactionsComponent.state.collectAsStateWithLifecycle()
val betweenItemsPadding = TangemTheme.dimens.spacing12
val horizontalPadding = TangemTheme.dimens.spacing16
val itemModifier = Modifier
@ -147,10 +151,12 @@ internal fun TokenDetailsScreenLegacy(
yieldSupplyComponent.Content(modifier = itemModifier)
}
expressTransactionsItems(
expressTxs = state.expressTxsToDisplay,
modifier = itemModifier,
)
with(expressTransactionsComponent) {
expressTransactionsContentLegacy(
state = expressState.transactionsToDisplay,
modifier = itemModifier,
)
}
with(txHistoryComponent) {
txHistoryContentLegacy(listState = listState, state = txHistoryComponentState)
@ -158,11 +164,7 @@ internal fun TokenDetailsScreenLegacy(
}
}
state.bottomSheetConfig?.let { config ->
if (config.content is ExpressStatusBottomSheetConfig) {
ExpressStatusBottomSheet(config = config)
}
}
expressState.bottomSheetSlot?.content()
}
}
@ -195,10 +197,31 @@ private fun TokenDetailsScreenPreview(
override fun Content(modifier: Modifier) {
}
},
expressTransactionsComponent = PreviewExpressTransactionsComponent,
)
}
}
private val PreviewExpressTransactionsComponent = object : ExpressTransactionsComponent {
override val state: StateFlow<ExpressTransactionsBlockState> = MutableStateFlow(
ExpressTransactionsBlockState(
transactions = persistentListOf(),
transactionsToDisplay = persistentListOf(),
bottomSheetSlot = null,
),
)
override fun LazyListScope.expressTransactionsContentLegacy(
state: PersistentList<ExpressTransactionStateUM>,
modifier: Modifier,
) = Unit
override fun LazyListScope.expressTransactionsContent(
state: PersistentList<ExpressTransactionStateUM>,
modifier: Modifier,
) = Unit
}
private class TokenDetailsScreenParameterProvider : CollectionPreviewParameterProvider<TokenDetailsState>(
collection = listOf(
TokenDetailsPreviewData.tokenDetailsState_1,

View file

@ -48,7 +48,7 @@ import com.tangem.common.ui.bottomsheet.chooseaddress.ChooseAddressBottomSheet
import com.tangem.common.ui.bottomsheet.chooseaddress.ChooseAddressBottomSheetConfig
import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheet
import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig
import com.tangem.common.ui.expressStatus.expressTransactionsItems
import com.tangem.common.ui.expressStatus.expressTransactionsItemsLegacy
import com.tangem.core.ui.components.atoms.handComposableComponentHeight
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheetDraggableHeaderLegacy
@ -236,7 +236,7 @@ private fun WalletContent(
marketPriceBlock(state = marketPriceBlockState, modifier = itemModifier)
}
if (walletState is WalletState.SingleCurrency.Content) {
expressTransactionsItems(
expressTransactionsItemsLegacy(
expressTxs = walletState.expressTxsToDisplay,
modifier = itemModifier,
)