Updated on 2026-08-14

This commit is contained in:
Tangem 2024-08-14 12:52:27 +03:00
parent c6d333dbeb
commit 244f9bc61d
19 changed files with 224 additions and 77 deletions

View file

@ -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

View file

@ -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) {

View file

@ -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

View file

@ -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
}
}

View file

@ -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

View file

@ -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 = {

View file

@ -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 = {},
),
)
}

View file

@ -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,

View file

@ -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
}
}

View file

@ -6,4 +6,5 @@ internal data class InsightsUM(
val h24Info: ImmutableList<InfoPointUM>,
val weekInfo: ImmutableList<InfoPointUM>,
val monthInfo: ImmutableList<InfoPointUM>,
val onInfoClick: () -> Unit,
)

View file

@ -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() },

View file

@ -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,

View file

@ -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,
)
}
}

View file

@ -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