Updated on 2026-08-14
This commit is contained in:
parent
3c3af4cb34
commit
f0886b4b74
14 changed files with 425 additions and 59 deletions
|
|
@ -6,7 +6,6 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Rect
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.SubcomposeLayout
|
||||
import androidx.compose.ui.text.TextLayoutResult
|
||||
|
|
@ -18,10 +17,12 @@ import androidx.compose.ui.text.style.TextAlign
|
|||
import androidx.compose.ui.text.style.TextDecoration
|
||||
import androidx.compose.ui.unit.Constraints
|
||||
import androidx.compose.ui.unit.TextUnit
|
||||
import com.tangem.core.ui.components.atoms.text.BoundCounter
|
||||
|
||||
/**
|
||||
* https://stackoverflow.com/questions/69083061/how-to-make-middle-ellipsis-in-text-with-jetpack-compose
|
||||
*/
|
||||
@Deprecated("Use EllipsisText with TextEllipsis.Middle ellipsis instead")
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
fun MiddleEllipsisText(
|
||||
|
|
@ -138,38 +139,4 @@ fun MiddleEllipsisText(
|
|||
|
||||
private const val ELLIPSIS_CHARACTERS_COUNT = 3
|
||||
private const val ELLIPSIS_CHARACTER = '.'
|
||||
private val ellipsisText = List(ELLIPSIS_CHARACTERS_COUNT) { ELLIPSIS_CHARACTER }.joinToString(separator = "")
|
||||
|
||||
private class BoundCounter(
|
||||
private val text: String,
|
||||
private val textLayoutResult: TextLayoutResult,
|
||||
private val charPosition: (Int) -> Int,
|
||||
) {
|
||||
var string = ""
|
||||
private set
|
||||
var width = 0f
|
||||
private set
|
||||
|
||||
private var _nextCharWidth: Float? = null
|
||||
private var invalidCharsCount = 0
|
||||
|
||||
fun widthWithNextChar(): Float = width + nextCharWidth()
|
||||
|
||||
private fun nextCharWidth(): Float = _nextCharWidth ?: run {
|
||||
var boundingBox: Rect
|
||||
// invalidCharsCount fixes this bug: https://issuetracker.google.com/issues/197146630
|
||||
invalidCharsCount--
|
||||
do {
|
||||
boundingBox = textLayoutResult
|
||||
.getBoundingBox(charPosition(string.count() + ++invalidCharsCount))
|
||||
} while (boundingBox.right == 0f)
|
||||
_nextCharWidth = boundingBox.width
|
||||
boundingBox.width
|
||||
}
|
||||
|
||||
fun addNextChar() {
|
||||
string += text[charPosition(string.count())]
|
||||
width += nextCharWidth()
|
||||
_nextCharWidth = null
|
||||
}
|
||||
}
|
||||
private val ellipsisText = List(ELLIPSIS_CHARACTERS_COUNT) { ELLIPSIS_CHARACTER }.joinToString(separator = "")
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
package com.tangem.core.ui.components.atoms.text
|
||||
|
||||
import androidx.compose.ui.geometry.Rect
|
||||
import androidx.compose.ui.text.TextLayoutResult
|
||||
|
||||
internal class BoundCounter(
|
||||
private val text: String,
|
||||
private val textLayoutResult: TextLayoutResult,
|
||||
private val charPosition: (Int) -> Int,
|
||||
) {
|
||||
var string = ""
|
||||
private set
|
||||
var width = 0f
|
||||
private set
|
||||
|
||||
private var _nextCharWidth: Float? = null
|
||||
private var invalidCharsCount = 0
|
||||
|
||||
fun widthWithNextChar(): Float = width + nextCharWidth()
|
||||
|
||||
private fun nextCharWidth(): Float = _nextCharWidth ?: run {
|
||||
var boundingBox: Rect
|
||||
// invalidCharsCount fixes this bug: https://issuetracker.google.com/issues/197146630
|
||||
invalidCharsCount--
|
||||
do {
|
||||
boundingBox = textLayoutResult
|
||||
.getBoundingBox(charPosition(string.count() + ++invalidCharsCount))
|
||||
} while (boundingBox.right == 0f)
|
||||
_nextCharWidth = boundingBox.width
|
||||
boundingBox.width
|
||||
}
|
||||
|
||||
fun addNextChar() {
|
||||
string += text[charPosition(string.count())]
|
||||
width += nextCharWidth()
|
||||
_nextCharWidth = null
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,242 @@
|
|||
package com.tangem.core.ui.components.atoms.text
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.material.LocalTextStyle
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.SubcomposeLayout
|
||||
import androidx.compose.ui.text.TextLayoutResult
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextDecoration
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import androidx.compose.ui.unit.Constraints
|
||||
import androidx.compose.ui.unit.TextUnit
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
sealed class TextEllipsis {
|
||||
|
||||
object Middle : TextEllipsis()
|
||||
|
||||
object End : TextEllipsis()
|
||||
|
||||
data class OffsetEnd(
|
||||
val offsetEnd: Int = 0,
|
||||
val hasSeparator: Boolean = true,
|
||||
) : TextEllipsis()
|
||||
}
|
||||
|
||||
/**
|
||||
* https://stackoverflow.com/questions/69083061/how-to-make-middle-ellipsis-in-text-with-jetpack-compose
|
||||
*
|
||||
* Customized Text with ellipsis. Ellipsis can be placed in: Middle, End or OffsetEnd (OffsetEnd with separator).
|
||||
*
|
||||
* * OffsetEnd can be useful to display big amounts with currency symbol. OffsetEnd 0 is equal to End.
|
||||
*/
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
fun EllipsisText(
|
||||
text: String,
|
||||
modifier: Modifier = Modifier,
|
||||
color: Color = Color.Unspecified,
|
||||
fontSize: TextUnit = TextUnit.Unspecified,
|
||||
fontStyle: FontStyle? = null,
|
||||
fontWeight: FontWeight? = null,
|
||||
fontFamily: FontFamily? = null,
|
||||
letterSpacing: TextUnit = TextUnit.Unspecified,
|
||||
textDecoration: TextDecoration? = null,
|
||||
textAlign: TextAlign? = null,
|
||||
lineHeight: TextUnit = TextUnit.Unspecified,
|
||||
softWrap: Boolean = true,
|
||||
onTextLayout: (TextLayoutResult) -> Unit = {},
|
||||
style: TextStyle = LocalTextStyle.current,
|
||||
ellipsis: TextEllipsis = TextEllipsis.End,
|
||||
) {
|
||||
val ellipsisText = remember(text) {
|
||||
if (ellipsis is TextEllipsis.OffsetEnd && ellipsis.hasSeparator) {
|
||||
ELLIPSIS_TEXT_WITH_SEPARATOR
|
||||
} else {
|
||||
ELLIPSIS_TEXT
|
||||
}
|
||||
}
|
||||
|
||||
// some letters, like "r", will have less width when placed right before "."
|
||||
// adding a space to prevent such case
|
||||
val layoutText = remember(text) { "$text $ellipsisText" }
|
||||
val textLayoutResultState = remember(layoutText) {
|
||||
mutableStateOf<TextLayoutResult?>(null)
|
||||
}
|
||||
SubcomposeLayout(modifier) { constraints ->
|
||||
// result is ignored - we only need to fill our textLayoutResult
|
||||
subcompose("measure") {
|
||||
Text(
|
||||
text = layoutText,
|
||||
color = color,
|
||||
fontSize = fontSize,
|
||||
fontStyle = fontStyle,
|
||||
fontWeight = fontWeight,
|
||||
fontFamily = fontFamily,
|
||||
letterSpacing = letterSpacing,
|
||||
textDecoration = textDecoration,
|
||||
textAlign = textAlign,
|
||||
lineHeight = lineHeight,
|
||||
softWrap = softWrap,
|
||||
maxLines = 1,
|
||||
onTextLayout = { textLayoutResultState.value = it },
|
||||
style = style,
|
||||
)
|
||||
}.first().measure(Constraints())
|
||||
// to allow smart cast
|
||||
val textLayoutResult = textLayoutResultState.value
|
||||
?: // shouldn't happen - onTextLayout is called before subcompose finishes
|
||||
return@SubcomposeLayout layout(0, 0) {}
|
||||
val placeable = subcompose("visible") {
|
||||
val finalText = remember(text, textLayoutResult, constraints.maxWidth) {
|
||||
if (
|
||||
text.isEmpty() ||
|
||||
textLayoutResult.getBoundingBox(text.indices.last).right <= constraints.maxWidth
|
||||
) {
|
||||
// text not including ellipsis fits on the first line.
|
||||
return@remember text
|
||||
}
|
||||
|
||||
var ellipsisWidth = 0f
|
||||
layoutText.indices.toList()
|
||||
.takeLast(ellipsisText.length)
|
||||
.forEach widthLet@{
|
||||
ellipsisWidth += textLayoutResult.getBoundingBox(it).width
|
||||
}
|
||||
|
||||
val availableWidth = constraints.maxWidth - ellipsisWidth
|
||||
val startCounter = BoundCounter(text, textLayoutResult) { it }
|
||||
val endCounter = BoundCounter(text, textLayoutResult) { text.indices.last - it }
|
||||
|
||||
when (ellipsis) {
|
||||
TextEllipsis.Middle -> {
|
||||
middleEllipsisText(
|
||||
availableWidth,
|
||||
startCounter,
|
||||
endCounter,
|
||||
)
|
||||
}
|
||||
TextEllipsis.End -> {
|
||||
offsetEndEllipsisText(
|
||||
availableWidth = availableWidth,
|
||||
startCounter = startCounter,
|
||||
endCounter = endCounter,
|
||||
)
|
||||
}
|
||||
is TextEllipsis.OffsetEnd -> {
|
||||
offsetEndEllipsisText(
|
||||
availableWidth = availableWidth,
|
||||
startCounter = startCounter,
|
||||
endCounter = endCounter,
|
||||
offsetEnd = ellipsis.offsetEnd,
|
||||
withSeparator = ellipsis.hasSeparator,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Text(
|
||||
text = finalText,
|
||||
color = color,
|
||||
fontSize = fontSize,
|
||||
fontStyle = fontStyle,
|
||||
fontWeight = fontWeight,
|
||||
fontFamily = fontFamily,
|
||||
letterSpacing = letterSpacing,
|
||||
textDecoration = textDecoration,
|
||||
textAlign = textAlign,
|
||||
lineHeight = lineHeight,
|
||||
softWrap = softWrap,
|
||||
onTextLayout = onTextLayout,
|
||||
style = style,
|
||||
)
|
||||
}[0].measure(constraints)
|
||||
layout(placeable.width, placeable.height) {
|
||||
placeable.place(0, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private const val ELLIPSIS_SEPARATOR = " "
|
||||
private const val ELLIPSIS_TEXT = "..."
|
||||
private const val ELLIPSIS_TEXT_WITH_SEPARATOR = ELLIPSIS_TEXT.plus(ELLIPSIS_SEPARATOR)
|
||||
|
||||
private fun middleEllipsisText(availableWidth: Float, startCounter: BoundCounter, endCounter: BoundCounter): String {
|
||||
while (availableWidth - startCounter.width - endCounter.width > 0) {
|
||||
val possibleEndWidth = endCounter.widthWithNextChar()
|
||||
if (
|
||||
startCounter.width >= possibleEndWidth &&
|
||||
availableWidth - startCounter.width - possibleEndWidth >= 0
|
||||
) {
|
||||
endCounter.addNextChar()
|
||||
} else if (availableWidth - startCounter.widthWithNextChar() - endCounter.width >= 0) {
|
||||
startCounter.addNextChar()
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return startCounter.string.trimEnd() + ELLIPSIS_TEXT + endCounter.string.reversed().trimStart()
|
||||
}
|
||||
|
||||
private fun offsetEndEllipsisText(
|
||||
availableWidth: Float,
|
||||
startCounter: BoundCounter,
|
||||
endCounter: BoundCounter,
|
||||
offsetEnd: Int = 0,
|
||||
withSeparator: Boolean = false,
|
||||
): String {
|
||||
while (availableWidth - startCounter.width - endCounter.width > 0) {
|
||||
val possibleEndWidth = endCounter.widthWithNextChar()
|
||||
if (
|
||||
offsetEnd > endCounter.string.length &&
|
||||
availableWidth - startCounter.width - possibleEndWidth >= 0
|
||||
) {
|
||||
endCounter.addNextChar()
|
||||
} else if (availableWidth - startCounter.widthWithNextChar() - endCounter.width >= 0) {
|
||||
startCounter.addNextChar()
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
val ellipsis = if (withSeparator) ELLIPSIS_TEXT_WITH_SEPARATOR else ELLIPSIS_TEXT
|
||||
|
||||
return startCounter.string.trimEnd() + ellipsis + endCounter.string.reversed().trimStart()
|
||||
}
|
||||
|
||||
//region Preview
|
||||
@Preview(widthDp = 200)
|
||||
@Composable
|
||||
private fun EllipsisTexPreview(@PreviewParameter(EllipsisTexPreviewParameterProvider::class) ellipsis: TextEllipsis) {
|
||||
TangemTheme {
|
||||
EllipsisText(
|
||||
text = "11111111111111111111111111111111111111111111111111 END",
|
||||
ellipsis = ellipsis,
|
||||
modifier = Modifier
|
||||
.background(TangemTheme.colors.background.primary)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class EllipsisTexPreviewParameterProvider : PreviewParameterProvider<TextEllipsis> {
|
||||
override val values: Sequence<TextEllipsis>
|
||||
get() = sequenceOf(
|
||||
TextEllipsis.Middle,
|
||||
TextEllipsis.End,
|
||||
TextEllipsis.OffsetEnd("TEXT".length),
|
||||
TextEllipsis.OffsetEnd("TEXT".length, false),
|
||||
)
|
||||
}
|
||||
//endregion
|
||||
|
|
@ -3,13 +3,13 @@ package com.tangem.core.ui.components.inputrow
|
|||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.atoms.text.EllipsisText
|
||||
import com.tangem.core.ui.components.currency.tokenicon.TokenIcon
|
||||
import com.tangem.core.ui.components.currency.tokenicon.TokenIconState
|
||||
import com.tangem.core.ui.components.inputrow.inner.DividerContainer
|
||||
|
|
@ -56,6 +56,7 @@ fun InputRowApprox(
|
|||
iconState = leftIcon,
|
||||
title = leftTitle,
|
||||
subtitle = leftSubtitle,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_approx_24),
|
||||
|
|
@ -71,6 +72,7 @@ fun InputRowApprox(
|
|||
iconState = rightIcon,
|
||||
title = rightTitle,
|
||||
subtitle = rightSubtitle,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -97,12 +99,12 @@ private fun InputRowApproxItem(
|
|||
start = TangemTheme.dimens.spacing12,
|
||||
),
|
||||
) {
|
||||
Text(
|
||||
EllipsisText(
|
||||
text = title.resolveReference(),
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
Text(
|
||||
EllipsisText(
|
||||
text = subtitle.resolveReference(),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
|
|
@ -123,7 +125,7 @@ private fun InputRowApproxPreview_Light() {
|
|||
leftTitle = TextReference.Str("Left title"),
|
||||
leftSubtitle = TextReference.Str("Left subtitle"),
|
||||
rightIcon = TokenIconState.Loading,
|
||||
rightTitle = TextReference.Str("Right title"),
|
||||
rightTitle = TextReference.Str("Right title Right title Right title Right title Right title"),
|
||||
rightSubtitle = TextReference.Str("Right subtitle"),
|
||||
modifier = Modifier
|
||||
.background(TangemTheme.colors.background.action),
|
||||
|
|
@ -137,7 +139,7 @@ private fun InputRowApproxPreview_Dark() {
|
|||
TangemTheme(isDark = true) {
|
||||
InputRowApprox(
|
||||
leftIcon = TokenIconState.Loading,
|
||||
leftTitle = TextReference.Str("Left title"),
|
||||
leftTitle = TextReference.Str("Left title Left title Left title Left title Left title"),
|
||||
leftSubtitle = TextReference.Str("Left subtitle"),
|
||||
rightIcon = TokenIconState.Loading,
|
||||
rightTitle = TextReference.Str("Right title"),
|
||||
|
|
|
|||
|
|
@ -20,4 +20,9 @@ internal class MockQuotesRepository(
|
|||
override suspend fun getQuotesSync(currenciesIds: Set<CryptoCurrency.ID>, refresh: Boolean): Set<Quote> {
|
||||
return getQuotesUpdates(currenciesIds).first()
|
||||
}
|
||||
|
||||
override suspend fun getQuoteSync(currencyId: CryptoCurrency.ID): Quote {
|
||||
return quotes.map { it.getOrElse { e -> throw e } }.first()
|
||||
.first { it.rawCurrencyId == currencyId.rawCurrencyId }
|
||||
}
|
||||
}
|
||||
|
|
@ -101,6 +101,7 @@ internal object TokenDetailsPreviewData {
|
|||
),
|
||||
dialogConfig = null,
|
||||
pendingTxs = persistentListOf(),
|
||||
swapTxs = persistentListOf(),
|
||||
pullToRefreshConfig = pullToRefreshConfig,
|
||||
bottomSheetConfig = null,
|
||||
isBalanceHidden = false,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue