Updated on 2026-08-14

This commit is contained in:
Tangem 2023-11-29 20:10:20 +03:00
commit cd2f5bfeed
14 changed files with 425 additions and 59 deletions

View file

@ -101,6 +101,7 @@ internal object TokenDetailsPreviewData {
),
dialogConfig = null,
pendingTxs = persistentListOf(),
swapTxs = persistentListOf(),
pullToRefreshConfig = pullToRefreshConfig,
bottomSheetConfig = null,
isBalanceHidden = false,

View file

@ -2,21 +2,25 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.state
import com.tangem.core.ui.components.currency.tokenicon.TokenIconState
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.components.ExchangeStatusNotifications
import kotlinx.collections.immutable.PersistentList
import java.math.BigDecimal
internal data class SwapTransactionsState(
val txId: String,
val providerId: Int,
val provider: SwapProvider,
val txUrl: String? = null,
val rate: BigDecimal,
val timestamp: Long,
val status: PersistentList<ExchangeStatusState>,
val statuses: PersistentList<ExchangeStatusState>,
val activeStatus: ExchangeStatus?,
val fiatSymbol: String,
val notification: ExchangeStatusNotifications? = null,
val toCryptoAmount: String,
val toCryptoSymbol: String,
val toFiatAmount: String,
val toCurrencyIcon: TokenIconState,
val fromCryptoAmount: String,
val fromCryptoSymbol: String,
val fromFiatAmount: String,
val fromCurrencyIcon: TokenIconState,
val onClick: () -> Unit,
@ -25,7 +29,6 @@ internal data class SwapTransactionsState(
internal class ExchangeStatusState(
val status: ExchangeStatus,
val text: String,
val isActive: Boolean,
val isDone: Boolean,
)

View file

@ -19,6 +19,7 @@ internal data class TokenDetailsState(
val marketPriceBlockState: MarketPriceBlockState,
val notifications: ImmutableList<TokenDetailsNotification>,
val pendingTxs: PersistentList<TransactionState>,
val swapTxs: PersistentList<SwapTransactionsState>,
val txHistoryState: TxHistoryState,
val dialogConfig: TokenDetailsDialogConfig?,
val pullToRefreshConfig: TokenDetailsPullToRefreshConfig,

View file

@ -0,0 +1,38 @@
package com.tangem.feature.tokendetails.presentation.tokendetails.state.components
import androidx.compose.runtime.Immutable
import com.tangem.core.ui.components.notifications.NotificationConfig
import com.tangem.core.ui.extensions.TextReference
import com.tangem.features.tokendetails.impl.R
@Immutable
internal sealed class ExchangeStatusNotifications(val config: NotificationConfig) {
data class NeedVerification(
val onGoToProviderClick: () -> Unit,
) : ExchangeStatusNotifications(
config = NotificationConfig(
title = TextReference.Res(R.string.express_exchange_notification_verification_title),
subtitle = TextReference.Res(R.string.express_exchange_notification_verification_text),
iconResId = R.drawable.ic_alert_triangle_20,
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
text = TextReference.Res(R.string.express_go_to_provider),
onClick = onGoToProviderClick,
),
),
)
data class Failed(
val onGoToProviderClick: () -> Unit,
) : ExchangeStatusNotifications(
config = NotificationConfig(
title = TextReference.Res(R.string.express_exchange_notification_failed_title),
subtitle = TextReference.Res(R.string.express_exchange_notification_failed_text),
iconResId = R.drawable.ic_alert_circle_24,
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
text = TextReference.Res(R.string.express_go_to_provider),
onClick = onGoToProviderClick,
),
),
)
}

View file

@ -45,6 +45,7 @@ internal class TokenDetailsSkeletonStateConverter(
marketPriceBlockState = MarketPriceBlockState.Loading(value.symbol),
notifications = persistentListOf(),
pendingTxs = persistentListOf(),
swapTxs = persistentListOf(),
txHistoryState = TxHistoryState.Content(
contentItems = MutableStateFlow(
value = TxHistoryState.getDefaultLoadingTransactions(clickIntents::onExploreClick),

View file

@ -2,7 +2,9 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.ui
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.ExperimentalMaterialApi
@ -40,6 +42,9 @@ 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
// TODO: Split to blocks [REDACTED_JIRA]
@Suppress("LongMethod")
@ -117,6 +122,11 @@ internal fun TokenDetailsScreen(state: TokenDetailsState) {
)
}
swapTransactionsItems(
state.swapTxs,
itemModifier,
)
txHistoryItems(
state = state.txHistoryState,
isBalanceHidden = state.isBalanceHidden,
@ -141,6 +151,9 @@ internal fun TokenDetailsScreen(state: TokenDetailsState) {
is ChooseAddressBottomSheetConfig -> {
ChooseAddressBottomSheet(config = config)
}
is ExchangeStatusBottomSheetConfig -> {
ExchangeStatusBottomSheet(config = config)
}
}
}

View file

@ -26,7 +26,7 @@ import kotlinx.collections.immutable.PersistentList
@Composable
internal fun ExchangeStatusBlock(
status: PersistentList<ExchangeStatusState>,
statuses: PersistentList<ExchangeStatusState>,
onClick: () -> Unit,
modifier: Modifier = Modifier,
) {
@ -70,10 +70,10 @@ internal fun ExchangeStatusBlock(
}
}
status.forEachIndexed { index, item ->
statuses.forEachIndexed { index, item ->
ExchangeStatusStep(
stepStatus = item,
isLast = index == status.lastIndex,
isLast = index == statuses.lastIndex,
)
}
}
@ -128,7 +128,7 @@ private fun ExchangeStatusStepText(stepStatus: ExchangeStatusState) {
}
Text(
text = stepStatus.text,
text = getStatusText(stepStatus)?.let { stringResource(it) }.orEmpty(),
style = TangemTheme.typography.body2,
color = textColor,
modifier = Modifier
@ -206,4 +206,34 @@ private fun ExchangeStepSeparator() {
shape = CircleShape,
),
)
}
private fun getStatusText(stepStatus: ExchangeStatusState) = when (stepStatus.status) {
ExchangeStatus.Failed -> R.string.express_exchange_status_failed
ExchangeStatus.Verifying -> if (stepStatus.isDone) {
R.string.express_exchange_status_verified
} else {
R.string.express_exchange_status_verifying
}
ExchangeStatus.New, ExchangeStatus.Waiting -> if (stepStatus.isDone) {
R.string.express_exchange_status_received
} else {
R.string.express_exchange_status_receiving
}
ExchangeStatus.Confirming -> if (stepStatus.isDone) {
R.string.express_exchange_status_confirmed
} else {
R.string.express_exchange_status_confirming
}
ExchangeStatus.Exchanging -> if (stepStatus.isDone) {
R.string.express_exchange_status_exchanged
} else {
R.string.express_exchange_status_exchanging
}
ExchangeStatus.Sending -> if (stepStatus.isDone) {
R.string.express_exchange_status_sent
} else {
R.string.express_exchange_status_sending
}
else -> null
}

View file

@ -1,5 +1,6 @@
package com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.exchange
import androidx.compose.animation.AnimatedContent
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
@ -15,6 +16,7 @@ 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.components.notifications.Notification
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.utils.toDateFormat
@ -64,17 +66,25 @@ private fun ExchangeStatusBottomSheetContent(content: ExchangeStatusBottomSheetC
toFiatAmount = TextReference.Str(config.toFiatAmount),
)
SpacerH12()
// todo replace with real provider data
ExchangeProvider(
providerName = TextReference.Str(config.providerId.toString()),
providerType = TextReference.Str("CEX"),
imageUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/express/changenow_512.png",
providerName = TextReference.Str(config.provider.name),
providerType = TextReference.Str(config.provider.type.name),
imageUrl = config.provider.imageLarge,
)
SpacerH12()
ExchangeStatusBlock(
status = config.status,
statuses = config.statuses,
onClick = config.onGoToProviderClick,
)
AnimatedContent(
targetState = config.notification,
label = "Exchange Status Notification Change",
) {
it?.let {
SpacerH12()
Notification(config = it.config)
}
}
SpacerH24()
}
}

View file

@ -17,7 +17,10 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.constraintlayout.compose.ConstraintLayout
import androidx.constraintlayout.compose.Dimension
import androidx.constraintlayout.compose.Visibility
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.tokenicon.TokenIcon
import com.tangem.core.ui.components.currency.tokenicon.TokenIconState
import com.tangem.core.ui.res.TangemTheme
@ -46,11 +49,13 @@ internal fun LazyListScope.swapTransactionsItems(
}
ExchangeStatusItem(
providerName = item.providerId.toString(),
providerName = item.provider.name,
fromTokenIconState = item.fromCurrencyIcon,
toTokenIconState = item.toCurrencyIcon,
fromAmount = item.fromCryptoAmount,
fromSymbol = item.fromCryptoSymbol,
toAmount = item.toCryptoAmount,
toSymbol = item.toCryptoSymbol,
onClick = item.onClick,
infoIconRes = iconRes,
infoIconTint = tint,
@ -67,7 +72,9 @@ private fun ExchangeStatusItem(
fromTokenIconState: TokenIconState,
toTokenIconState: TokenIconState,
fromAmount: String,
fromSymbol: String,
toAmount: String,
toSymbol: String,
onClick: () -> Unit,
modifier: Modifier = Modifier,
@DrawableRes infoIconRes: Int? = null,
@ -104,14 +111,17 @@ private fun ExchangeStatusItem(
bottom.linkTo(parent.bottom)
},
)
Text(
EllipsisText(
text = fromAmount,
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.primary1,
ellipsis = TextEllipsis.OffsetEnd(fromSymbol.length),
modifier = Modifier.constrainAs(fromRef) {
start.linkTo(fromIconRef.end, padding6)
top.linkTo(titleRef.bottom, padding6)
end.linkTo(swapIconRef.start)
bottom.linkTo(parent.bottom)
width = Dimension.fillToConstraints
},
)
Icon(
@ -123,6 +133,7 @@ private fun ExchangeStatusItem(
.constrainAs(swapIconRef) {
start.linkTo(fromRef.end, padding6)
top.linkTo(titleRef.bottom, padding6)
end.linkTo(toIconRef.start)
bottom.linkTo(parent.bottom)
},
)
@ -134,17 +145,21 @@ private fun ExchangeStatusItem(
.constrainAs(toIconRef) {
start.linkTo(swapIconRef.end, padding6)
top.linkTo(titleRef.bottom, padding6)
end.linkTo(toRef.start)
bottom.linkTo(parent.bottom)
},
)
Text(
EllipsisText(
text = toAmount,
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)
end.linkTo(infoIconRef.start, padding6)
bottom.linkTo(parent.bottom)
width = Dimension.fillToConstraints
},
)
Icon(