Updated on 2026-08-14
This commit is contained in:
parent
c6d333dbeb
commit
244f9bc61d
19 changed files with 224 additions and 77 deletions
|
|
@ -314,6 +314,7 @@ private fun MarketChartPreview(
|
|||
TangemThemePreview {
|
||||
val growingColor = TangemTheme.colors.icon.accent
|
||||
val fallingColor = TangemTheme.colors.icon.warning
|
||||
val neutralColor = TangemTheme.colors.icon.informative
|
||||
|
||||
val chartState = rememberMarketChartState(
|
||||
dataProducer = dataProducer,
|
||||
|
|
@ -324,6 +325,7 @@ private fun MarketChartPreview(
|
|||
when (it) {
|
||||
MarketChartLook.Type.Growing -> growingColor
|
||||
MarketChartLook.Type.Falling -> fallingColor
|
||||
MarketChartLook.Type.Neutral -> neutralColor
|
||||
}
|
||||
},
|
||||
)
|
||||
|
|
@ -382,10 +384,10 @@ private fun MarketChartPreview(
|
|||
dataProducer.runTransaction {
|
||||
updateLook {
|
||||
it.copy(
|
||||
type = if (it.type == MarketChartLook.Type.Growing) {
|
||||
MarketChartLook.Type.Falling
|
||||
} else {
|
||||
MarketChartLook.Type.Growing
|
||||
type = when (it.type) {
|
||||
MarketChartLook.Type.Growing -> MarketChartLook.Type.Falling
|
||||
MarketChartLook.Type.Falling -> MarketChartLook.Type.Neutral
|
||||
MarketChartLook.Type.Neutral -> MarketChartLook.Type.Growing
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ fun MarketChartMini(
|
|||
type: MarketChartLook.Type = MarketChartLook.Type.Growing,
|
||||
growingColor: Color = TangemTheme.colors.icon.accent,
|
||||
fallingColor: Color = TangemTheme.colors.icon.warning,
|
||||
neutralColor: Color = TangemTheme.colors.icon.informative,
|
||||
) {
|
||||
val model = remember(rawData) {
|
||||
CartesianChartModel(LineCartesianLayerModel.build { series(rawData.y) })
|
||||
|
|
@ -44,6 +45,7 @@ fun MarketChartMini(
|
|||
val lineColor = when (type) {
|
||||
MarketChartLook.Type.Growing -> growingColor
|
||||
MarketChartLook.Type.Falling -> fallingColor
|
||||
MarketChartLook.Type.Neutral -> neutralColor
|
||||
}
|
||||
|
||||
val lineSpec = rememberLine(
|
||||
|
|
@ -85,6 +87,8 @@ private fun Preview() {
|
|||
MarketChartMini(rawData = data, type = MarketChartLook.Type.Growing)
|
||||
SpacerH16()
|
||||
MarketChartMini(rawData = data, type = MarketChartLook.Type.Falling)
|
||||
SpacerH16()
|
||||
MarketChartMini(rawData = data, type = MarketChartLook.Type.Neutral)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,5 +24,6 @@ data class MarketChartLook(
|
|||
enum class Type {
|
||||
Growing,
|
||||
Falling,
|
||||
Neutral,
|
||||
}
|
||||
}
|
||||
|
|
@ -24,6 +24,7 @@ fun rememberMarketChartState(
|
|||
when (it) {
|
||||
MarketChartLook.Type.Growing -> Color.Green
|
||||
MarketChartLook.Type.Falling -> Color.Red
|
||||
MarketChartLook.Type.Neutral -> Color.Gray
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -11,12 +11,21 @@ import java.util.Locale
|
|||
object DateTimeFormatters {
|
||||
|
||||
/**
|
||||
* Two SS means, SHORT style for date and time.
|
||||
* If pattern contains "a", it means time is in 12 hour format.
|
||||
* [Documentation](https://www.joda.org/joda-time/apidocs/org/joda/time/format/DateTimeFormat.html)
|
||||
* Determine if the time is in 12-hour format ("10:00 PM") for the current locale.
|
||||
*/
|
||||
val is12HourFormat by lazy {
|
||||
/**
|
||||
* Two SS means, SHORT style for date and time.
|
||||
* If pattern contains "a", it means time is in 12 hour format.
|
||||
* [Documentation](https://www.joda.org/joda-time/apidocs/org/joda/time/format/DateTimeFormat.html)
|
||||
*/
|
||||
DateTimeFormat.patternForStyle("SS", Locale.getDefault()).contains("a")
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: "12:00 PM", "12:00"
|
||||
*/
|
||||
val timeFormatter: DateTimeFormatter by lazy {
|
||||
val is12HourFormat = DateTimeFormat.patternForStyle("SS", Locale.getDefault()).contains("a")
|
||||
if (is12HourFormat) {
|
||||
DateTimeFormatterBuilder()
|
||||
.appendClockhourOfHalfday(1)
|
||||
|
|
@ -36,6 +45,9 @@ object DateTimeFormatters {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: "1 Jun, 2020", "1 Jun, 2020"
|
||||
*/
|
||||
val dateFormatter: DateTimeFormatter by lazy {
|
||||
DateTimeFormatterBuilder()
|
||||
.appendDayOfMonth(1)
|
||||
|
|
@ -47,36 +59,61 @@ object DateTimeFormatters {
|
|||
.withLocale(Locale.getDefault())
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: "31.06.2020", "06/31/2020"
|
||||
*/
|
||||
val dateDDMMYYYY: DateTimeFormatter by lazy {
|
||||
DateTimeFormatterBuilder()
|
||||
.appendPattern("dd.MM.yyyy")
|
||||
.toFormatter()
|
||||
.withLocale(Locale.getDefault())
|
||||
getBestFormatterBySkeleton("dd.MM.yyyy")
|
||||
}
|
||||
|
||||
/**
|
||||
* In API version < 24, there may be some problems with getting the best date and time format pattern.
|
||||
* Example: "Jun 31, 2020", "31 Jun, 2020"
|
||||
*/
|
||||
val dateMMMdd: DateTimeFormatter by lazy {
|
||||
getBestFormatterBySkeleton("dd MMM")
|
||||
getBestFormatterBySkeleton("MMM dd")
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: "2020"
|
||||
*/
|
||||
val dateYYYY: DateTimeFormatter by lazy {
|
||||
getBestFormatterBySkeleton("yyyy")
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: "31.06.2020 12:00", "06/31/2020 12:00", "06/31/2020 12:00 PM"
|
||||
*/
|
||||
val dateTimeFormatter: DateTimeFormatter by lazy {
|
||||
DateTimeFormat.forPattern("dd.MM.yyyy HH:mm")
|
||||
getBestFormatterBySkeleton("dd.MM.yyyy HH:mm")
|
||||
}
|
||||
|
||||
fun formatDate(date: DateTime, formatter: DateTimeFormatter = dateFormatter): String {
|
||||
return formatter.print(date)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the best date and time format pattern for the given skeleton and the current locale.
|
||||
* (In API version < 24, there may be some problems with getting the best date and time format pattern.)
|
||||
*
|
||||
* @param skeleton The skeleton is an alternative to the pattern. The difference is that the pattern rigidly
|
||||
* defines the date/time format, while the skeleton specifies only the date/time components (year, month, day, etc.)
|
||||
* So the order of the components and separators (space, comma, etc.) is not taken into account.
|
||||
* @see [dateYYYY], [dateMMMdd], [dateDDMMYYYY], [dateTimeFormatter]
|
||||
*/
|
||||
fun getBestFormatterBySkeleton(skeleton: String): DateTimeFormatter {
|
||||
val skeletonWithLocale = skeleton.replaceHourLetters()
|
||||
|
||||
return DateTimeFormatterBuilder()
|
||||
.appendPattern(DateFormat.getBestDateTimePattern(Locale.getDefault(), skeleton))
|
||||
.appendPattern(DateFormat.getBestDateTimePattern(Locale.getDefault(), skeletonWithLocale))
|
||||
.toFormatter()
|
||||
.withLocale(Locale.getDefault())
|
||||
}
|
||||
|
||||
private fun String.replaceHourLetters(): String {
|
||||
return if (is12HourFormat) {
|
||||
this.replace('H', 'h').replace('k', 'K')
|
||||
} else {
|
||||
this.replace('h', 'H').replace('K', 'k')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -87,8 +87,27 @@ internal class DefaultMarketsEntryComponent @AssistedInject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
// order of LaunchedEffects is important here
|
||||
|
||||
val activeChild = stackState.value.active.configuration
|
||||
|
||||
LaunchedEffect(activeChild) {
|
||||
when (activeChild) {
|
||||
is MarketsEntryChildFactory.Child.TokenDetails -> {
|
||||
backgroundColor.animateTo(
|
||||
secondary,
|
||||
animationSpec = tween(durationMillis = 500),
|
||||
)
|
||||
}
|
||||
MarketsEntryChildFactory.Child.TokenList -> {
|
||||
backgroundColor.animateTo(
|
||||
primary,
|
||||
animationSpec = tween(durationMillis = 500),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(bottomSheetState.value) {
|
||||
if (activeChild is MarketsEntryChildFactory.Child.TokenDetails) {
|
||||
when (bottomSheetState.value) {
|
||||
|
|
@ -108,23 +127,6 @@ internal class DefaultMarketsEntryComponent @AssistedInject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(activeChild) {
|
||||
when (activeChild) {
|
||||
is MarketsEntryChildFactory.Child.TokenDetails -> {
|
||||
backgroundColor.animateTo(
|
||||
secondary,
|
||||
animationSpec = tween(durationMillis = 500),
|
||||
)
|
||||
}
|
||||
MarketsEntryChildFactory.Child.TokenList -> {
|
||||
backgroundColor.animateTo(
|
||||
primary,
|
||||
animationSpec = tween(durationMillis = 500),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(primary, secondary) {
|
||||
if (backgroundColor.isRunning) return@LaunchedEffect
|
||||
|
||||
|
|
|
|||
|
|
@ -291,6 +291,14 @@ internal class MarketsTokenDetailsModel @Inject constructor(
|
|||
),
|
||||
)
|
||||
}
|
||||
|
||||
chartDataProducer.runTransaction {
|
||||
updateLook {
|
||||
it.copy(
|
||||
type = getChartTypeByPercent(percent),
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
ifLeft = {
|
||||
state.update {
|
||||
|
|
@ -347,6 +355,14 @@ internal class MarketsTokenDetailsModel @Inject constructor(
|
|||
dateTimeText = getDefaultDateTimeString(stateToUpdate.selectedInterval),
|
||||
)
|
||||
}
|
||||
|
||||
chartDataProducer.runTransaction {
|
||||
updateLook {
|
||||
it.copy(
|
||||
type = getChartTypeByPercent(percent),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onSelectedIntervalChange(interval: PriceChangeInterval) {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.features.markets.details.impl.model.converters
|
|||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.markets.TokenMarketInfo
|
||||
|
|
@ -43,6 +44,14 @@ internal class InsightsConverter(
|
|||
liquidityChange = liquidityChange?.month,
|
||||
buyPressureChange = buyPressureChange?.month,
|
||||
),
|
||||
onInfoClick = {
|
||||
onInfoClick(
|
||||
InfoBottomSheetContent(
|
||||
title = resourceReference(R.string.markets_token_details_insights),
|
||||
body = stringReference("//TODO"),
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -57,6 +66,7 @@ internal class InsightsConverter(
|
|||
InfoPointUM(
|
||||
title = resourceReference(R.string.markets_token_details_experienced_buyers),
|
||||
value = experiencedBuyerChange.convertChange(),
|
||||
change = experiencedBuyerChange.changeType(),
|
||||
onInfoClick = {
|
||||
onInfoClick(
|
||||
InfoBottomSheetContent(
|
||||
|
|
@ -69,6 +79,7 @@ internal class InsightsConverter(
|
|||
InfoPointUM(
|
||||
title = resourceReference(R.string.markets_token_details_buy_pressure),
|
||||
value = buyPressureChange.convertChange(isFiatValue = true),
|
||||
change = buyPressureChange.changeType(),
|
||||
onInfoClick = {
|
||||
onInfoClick(
|
||||
InfoBottomSheetContent(
|
||||
|
|
@ -81,6 +92,7 @@ internal class InsightsConverter(
|
|||
InfoPointUM(
|
||||
title = resourceReference(R.string.markets_token_details_holders),
|
||||
value = holdersChange.convertChange(),
|
||||
change = holdersChange.changeType(),
|
||||
onInfoClick = {
|
||||
onInfoClick(
|
||||
InfoBottomSheetContent(
|
||||
|
|
@ -93,6 +105,7 @@ internal class InsightsConverter(
|
|||
InfoPointUM(
|
||||
title = resourceReference(R.string.markets_token_details_liquidity),
|
||||
value = liquidityChange.convertChange(),
|
||||
change = liquidityChange.changeType(),
|
||||
onInfoClick = {
|
||||
onInfoClick(
|
||||
InfoBottomSheetContent(
|
||||
|
|
@ -105,6 +118,15 @@ internal class InsightsConverter(
|
|||
)
|
||||
}
|
||||
|
||||
private fun BigDecimal?.changeType(): InfoPointUM.ChangeType? {
|
||||
return when {
|
||||
this == null -> null
|
||||
this > BigDecimal.ZERO -> InfoPointUM.ChangeType.UP
|
||||
this < BigDecimal.ZERO -> InfoPointUM.ChangeType.DOWN
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun BigDecimal?.convertChange(isFiatValue: Boolean = false): String {
|
||||
if (this == null) return StringsSigns.DASH_SIGN
|
||||
|
||||
|
|
|
|||
|
|
@ -83,16 +83,17 @@ internal fun PriceChangeType.toChartType(): MarketChartLook.Type {
|
|||
return when (this) {
|
||||
PriceChangeType.UP -> MarketChartLook.Type.Growing
|
||||
PriceChangeType.DOWN -> MarketChartLook.Type.Falling
|
||||
PriceChangeType.NEUTRAL -> MarketChartLook.Type.Growing
|
||||
PriceChangeType.NEUTRAL -> MarketChartLook.Type.Neutral
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
internal fun getChartTypeByPercent(percent: BigDecimal?): MarketChartLook.Type {
|
||||
val scaled = percent?.setScale(4, RoundingMode.HALF_UP)
|
||||
return when {
|
||||
percent == null -> return MarketChartLook.Type.Growing
|
||||
percent == BigDecimal.ZERO -> MarketChartLook.Type.Growing
|
||||
percent > BigDecimal.ZERO -> MarketChartLook.Type.Growing
|
||||
percent < BigDecimal.ZERO -> MarketChartLook.Type.Falling
|
||||
else -> MarketChartLook.Type.Growing
|
||||
scaled == null -> return MarketChartLook.Type.Neutral
|
||||
scaled > BigDecimal.ZERO -> MarketChartLook.Type.Growing
|
||||
scaled < BigDecimal.ZERO -> MarketChartLook.Type.Falling
|
||||
else -> MarketChartLook.Type.Neutral
|
||||
}
|
||||
}
|
||||
|
|
@ -18,7 +18,7 @@ internal object MarketsDateTimeFormatters {
|
|||
private const val WEEK_MILLIS = 7L * H24_MILLIS
|
||||
|
||||
private val dateTimeMMMFormatter by lazy {
|
||||
DateTimeFormatters.getBestFormatterBySkeleton("dd MMM hh:mm")
|
||||
DateTimeFormatters.getBestFormatterBySkeleton("dd MMM Hm")
|
||||
}
|
||||
|
||||
private val dateFormatter = DateTimeFormatters.dateDDMMYYYY
|
||||
|
|
|
|||
|
|
@ -3,13 +3,18 @@ package com.tangem.features.markets.details.impl.ui.components
|
|||
import android.content.res.Configuration
|
||||
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.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.SpacerW4
|
||||
import com.tangem.core.ui.components.TextShimmer
|
||||
import com.tangem.core.ui.components.text.TooltipText
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
|
|
@ -40,11 +45,32 @@ internal fun InfoPoint(infoPointUM: InfoPointUM, modifier: Modifier = Modifier)
|
|||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = infoPointUM.value,
|
||||
style = TangemTheme.typography.body1,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
Row {
|
||||
Text(
|
||||
text = infoPointUM.value,
|
||||
style = TangemTheme.typography.body1,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
if (infoPointUM.change != null) {
|
||||
SpacerW4()
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.size(TangemTheme.dimens.size8)
|
||||
.align(Alignment.CenterVertically),
|
||||
imageVector = ImageVector.vectorResource(
|
||||
id = when (infoPointUM.change) {
|
||||
InfoPointUM.ChangeType.UP -> R.drawable.ic_arrow_up_8
|
||||
InfoPointUM.ChangeType.DOWN -> R.drawable.ic_arrow_down_8
|
||||
},
|
||||
),
|
||||
tint = when (infoPointUM.change) {
|
||||
InfoPointUM.ChangeType.UP -> TangemTheme.colors.icon.accent
|
||||
InfoPointUM.ChangeType.DOWN -> TangemTheme.colors.icon.warning
|
||||
},
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -105,6 +131,22 @@ private fun ContentPreview() {
|
|||
onInfoClick = { },
|
||||
),
|
||||
)
|
||||
InfoPoint(
|
||||
infoPointUM = InfoPointUM(
|
||||
title = stringReference("Market Cap"),
|
||||
value = "$1,000,000",
|
||||
change = InfoPointUM.ChangeType.UP,
|
||||
onInfoClick = { },
|
||||
),
|
||||
)
|
||||
InfoPoint(
|
||||
infoPointUM = InfoPointUM(
|
||||
title = stringReference("Market Cap"),
|
||||
value = "$1,000,000",
|
||||
change = InfoPointUM.ChangeType.DOWN,
|
||||
onInfoClick = { },
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -126,6 +168,14 @@ private fun PreviewShimmer() {
|
|||
modifier = Modifier.fillMaxWidth(),
|
||||
withTooltip = true,
|
||||
)
|
||||
InfoPointShimmer(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
withTooltip = true,
|
||||
)
|
||||
InfoPointShimmer(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
withTooltip = true,
|
||||
)
|
||||
}
|
||||
},
|
||||
actualContent = {
|
||||
|
|
|
|||
|
|
@ -7,13 +7,12 @@ import androidx.compose.runtime.*
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.components.RectangleShimmer
|
||||
import com.tangem.core.ui.components.block.information.GridItems
|
||||
import com.tangem.core.ui.components.block.information.InformationBlock
|
||||
import com.tangem.core.ui.components.buttons.segmentedbutton.SegmentedButtons
|
||||
import com.tangem.core.ui.components.text.TooltipText
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
|
@ -34,13 +33,10 @@ internal fun InsightsBlock(state: InsightsUM, modifier: Modifier = Modifier) {
|
|||
InformationBlock(
|
||||
modifier = modifier,
|
||||
title = {
|
||||
Text(
|
||||
modifier = Modifier,
|
||||
text = stringResource(R.string.markets_token_details_insights),
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
TooltipText(
|
||||
text = resourceReference(R.string.markets_token_details_insights),
|
||||
textStyle = TangemTheme.typography.subtitle2,
|
||||
onInfoClick = state.onInfoClick,
|
||||
)
|
||||
},
|
||||
action = {
|
||||
|
|
@ -180,6 +176,7 @@ private fun ContentPreview() {
|
|||
value = "1 000",
|
||||
),
|
||||
),
|
||||
onInfoClick = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import com.tangem.features.markets.tokenlist.impl.ui.components.UnableToLoadData
|
|||
internal fun MarketTokenDetailsChart(state: MarketsTokenDetailsUM.ChartState, modifier: Modifier = Modifier) {
|
||||
val growingColor = TangemTheme.colors.icon.accent
|
||||
val fallingColor = TangemTheme.colors.icon.warning
|
||||
val neutralColor = TangemTheme.colors.icon.informative
|
||||
|
||||
val chartState = rememberMarketChartState(
|
||||
dataProducer = state.dataProducer,
|
||||
|
|
@ -29,6 +30,7 @@ internal fun MarketTokenDetailsChart(state: MarketsTokenDetailsUM.ChartState, mo
|
|||
when (it) {
|
||||
MarketChartLook.Type.Growing -> growingColor
|
||||
MarketChartLook.Type.Falling -> fallingColor
|
||||
MarketChartLook.Type.Neutral -> neutralColor
|
||||
}
|
||||
},
|
||||
onMarkerShown = state.onMarkerPointSelected,
|
||||
|
|
|
|||
|
|
@ -5,5 +5,10 @@ import com.tangem.core.ui.extensions.TextReference
|
|||
internal data class InfoPointUM(
|
||||
val title: TextReference,
|
||||
val value: String,
|
||||
val change: ChangeType? = null,
|
||||
val onInfoClick: (() -> Unit)? = null,
|
||||
)
|
||||
) {
|
||||
enum class ChangeType {
|
||||
UP, DOWN
|
||||
}
|
||||
}
|
||||
|
|
@ -6,4 +6,5 @@ internal data class InsightsUM(
|
|||
val h24Info: ImmutableList<InfoPointUM>,
|
||||
val weekInfo: ImmutableList<InfoPointUM>,
|
||||
val monthInfo: ImmutableList<InfoPointUM>,
|
||||
val onInfoClick: () -> Unit,
|
||||
)
|
||||
|
|
@ -48,6 +48,7 @@ internal class MarketsListModel @Inject constructor(
|
|||
private val visibleItemIds = MutableStateFlow<List<String>>(emptyList())
|
||||
|
||||
private val marketsListUMStateManager = MarketsListUMStateManager(
|
||||
currentVisibleIds = Provider { visibleItemIds.value },
|
||||
onLoadMoreUiItems = { activeListManager.loadMore() },
|
||||
visibleItemsChanged = { visibleItemIds.value = it },
|
||||
onRetryButtonClicked = { activeListManager.reload() },
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import com.tangem.features.markets.tokenlist.impl.ui.state.ListUM
|
|||
import com.tangem.features.markets.tokenlist.impl.ui.state.MarketsListItemUM
|
||||
import com.tangem.features.markets.tokenlist.impl.ui.state.MarketsListUM
|
||||
import com.tangem.features.markets.tokenlist.impl.ui.state.SortByTypeUM
|
||||
import com.tangem.utils.Provider
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
|
@ -19,6 +20,7 @@ import kotlinx.coroutines.flow.*
|
|||
|
||||
@Stable
|
||||
internal class MarketsListUMStateManager(
|
||||
private val currentVisibleIds: Provider<List<String>>,
|
||||
private val onLoadMoreUiItems: () -> Unit,
|
||||
private val visibleItemsChanged: (itemsKeys: List<String>) -> Unit,
|
||||
private val onRetryButtonClicked: () -> Unit,
|
||||
|
|
@ -129,9 +131,11 @@ internal class MarketsListUMStateManager(
|
|||
}
|
||||
}
|
||||
|
||||
val itemsWithFilteredPriceChange = items.filterPriceChangeByVisibility()
|
||||
|
||||
return currentState.copy(
|
||||
list = ListUM.Content(
|
||||
items = items,
|
||||
items = itemsWithFilteredPriceChange,
|
||||
loadMore = onLoadMoreUiItems,
|
||||
visibleIdsChanged = visibleItemsChanged,
|
||||
showUnder100kTokens = isInSearchState.not() || isNextPageInSearch,
|
||||
|
|
@ -157,6 +161,22 @@ internal class MarketsListUMStateManager(
|
|||
)
|
||||
}
|
||||
|
||||
// Show price change animation for visible items only
|
||||
private fun ImmutableList<MarketsListItemUM>.filterPriceChangeByVisibility(): ImmutableList<MarketsListItemUM> {
|
||||
val visibleItemIds = currentVisibleIds()
|
||||
return map {
|
||||
it.copy(
|
||||
price = it.price.copy(
|
||||
changeType = if (visibleItemIds.contains(it.id)) {
|
||||
it.price.changeType
|
||||
} else {
|
||||
null
|
||||
},
|
||||
),
|
||||
)
|
||||
}.toImmutableList()
|
||||
}
|
||||
|
||||
private fun generalContentState(newItems: ImmutableList<MarketsListItemUM>): ListUM.Content {
|
||||
return ListUM.Content(
|
||||
items = newItems,
|
||||
|
|
|
|||
|
|
@ -34,11 +34,11 @@ fun MarketsListItemPlaceholder() {
|
|||
SpacerW12()
|
||||
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Row(
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = TangemTheme.dimens.spacing4),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
contentAlignment = Alignment.CenterStart,
|
||||
) {
|
||||
RectangleShimmer(
|
||||
modifier = Modifier
|
||||
|
|
@ -46,23 +46,15 @@ fun MarketsListItemPlaceholder() {
|
|||
.height(sp12),
|
||||
radius = TangemTheme.dimens.radius3,
|
||||
)
|
||||
SpacerW8()
|
||||
RectangleShimmer(
|
||||
modifier = Modifier
|
||||
.width(TangemTheme.dimens.size70)
|
||||
.height(sp12),
|
||||
radius = TangemTheme.dimens.radius3,
|
||||
)
|
||||
}
|
||||
|
||||
SpacerH(height = TangemTheme.dimens.spacing2)
|
||||
|
||||
Row(
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = TangemTheme.dimens.spacing2),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.Bottom,
|
||||
contentAlignment = Alignment.CenterStart,
|
||||
) {
|
||||
RectangleShimmer(
|
||||
modifier = Modifier
|
||||
|
|
@ -70,12 +62,6 @@ fun MarketsListItemPlaceholder() {
|
|||
.height(sp12),
|
||||
radius = TangemTheme.dimens.radius3,
|
||||
)
|
||||
RectangleShimmer(
|
||||
modifier = Modifier
|
||||
.width(TangemTheme.dimens.size52)
|
||||
.height(sp12),
|
||||
radius = TangemTheme.dimens.radius3,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,10 +20,9 @@ data class MarketsListItemUM(
|
|||
val isUnder100kMarketCap: Boolean = false,
|
||||
) {
|
||||
val chartType: MarketChartLook.Type = when (trendType) {
|
||||
PriceChangeType.UP,
|
||||
PriceChangeType.NEUTRAL,
|
||||
-> MarketChartLook.Type.Growing
|
||||
PriceChangeType.UP -> MarketChartLook.Type.Growing
|
||||
PriceChangeType.DOWN -> MarketChartLook.Type.Falling
|
||||
PriceChangeType.NEUTRAL -> MarketChartLook.Type.Neutral
|
||||
}
|
||||
|
||||
@Immutable
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue