diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/currency/tokenicon/ContentIcon.kt b/core/ui/src/main/java/com/tangem/core/ui/components/currency/tokenicon/ContentIcon.kt
index cdf96d7b53..1814d2593d 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/currency/tokenicon/ContentIcon.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/currency/tokenicon/ContentIcon.kt
@@ -58,7 +58,7 @@ internal fun ContentIcon(
}
@Composable
-private fun CoinIcon(
+fun CoinIcon(
url: String?,
@DrawableRes fallbackResId: Int,
alpha: Float,
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/marketprice/MarketPriceBlock.kt b/core/ui/src/main/java/com/tangem/core/ui/components/marketprice/MarketPriceBlock.kt
index 606416093a..8ef37d1f7c 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/marketprice/MarketPriceBlock.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/marketprice/MarketPriceBlock.kt
@@ -4,14 +4,12 @@ import android.content.res.Configuration
import androidx.compose.animation.AnimatedContent
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
-import androidx.compose.material.Icon
-import androidx.compose.material.Text
+import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
@@ -20,8 +18,8 @@ import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameter
import androidx.compose.ui.unit.Dp
import com.tangem.core.ui.R
import com.tangem.core.ui.components.RectangleShimmer
-import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.res.TangemTheme
+import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.utils.BigDecimalFormatter
/**
@@ -116,7 +114,10 @@ private fun PriceBlock(state: MarketPriceBlockState, priceWidthDp: Dp) {
) {
Price(price = marketPriceBlockState.price, modifier = priceModifier)
- PriceChangeInPercent(marketPriceBlockState.priceChangeConfig)
+ PriceChangeInPercent(
+ valueInPercent = marketPriceBlockState.priceChangeConfig.valueInPercent,
+ type = marketPriceBlockState.priceChangeConfig.type,
+ )
}
} else {
Price(price = BigDecimalFormatter.EMPTY_BALANCE_SIGN, modifier = priceModifier)
@@ -136,47 +137,6 @@ private fun Price(price: String, modifier: Modifier = Modifier) {
)
}
-@Composable
-private fun PriceChangeInPercent(config: PriceChangeState.Content) {
- AnimatedContent(
- targetState = config.type,
- contentAlignment = Alignment.CenterStart,
- label = "Update price change",
- ) { type ->
- Row(
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing2),
- ) {
- Icon(
- modifier = Modifier.size(TangemTheme.dimens.size8),
- painter = painterResource(
- id = when (type) {
- PriceChangeType.UP -> R.drawable.ic_arrow_up_8
- PriceChangeType.DOWN -> R.drawable.ic_arrow_down_8
- PriceChangeType.NEUTRAL -> R.drawable.ic_elipse_8
- },
- ),
- tint = when (type) {
- PriceChangeType.UP -> TangemTheme.colors.icon.accent
- PriceChangeType.DOWN -> TangemTheme.colors.icon.warning
- PriceChangeType.NEUTRAL -> TangemTheme.colors.icon.inactive
- },
- contentDescription = null,
- )
-
- Text(
- text = config.valueInPercent,
- color = when (type) {
- PriceChangeType.UP -> TangemTheme.colors.text.accent
- PriceChangeType.DOWN -> TangemTheme.colors.text.warning
- PriceChangeType.NEUTRAL -> TangemTheme.colors.text.disabled
- },
- style = TangemTheme.typography.body2,
- )
- }
- }
-}
-
@Composable
private fun LoadingContent() {
Row(
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/marketprice/PriceChangeInPercent.kt b/core/ui/src/main/java/com/tangem/core/ui/components/marketprice/PriceChangeInPercent.kt
new file mode 100644
index 0000000000..d2c55ed576
--- /dev/null
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/marketprice/PriceChangeInPercent.kt
@@ -0,0 +1,96 @@
+package com.tangem.core.ui.components.marketprice
+
+import android.content.res.Configuration
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.size
+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.TextStyle
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.tooling.preview.Preview
+import com.tangem.core.ui.R
+import com.tangem.core.ui.res.TangemTheme
+import com.tangem.core.ui.res.TangemThemePreview
+
+@Composable
+fun PriceChangeInPercent(
+ valueInPercent: String,
+ type: PriceChangeType,
+ modifier: Modifier = Modifier,
+ textStyle: TextStyle = TangemTheme.typography.body2,
+) {
+ Row(
+ modifier = modifier,
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing2),
+ ) {
+ Icon(
+ modifier = Modifier
+ .size(TangemTheme.dimens.size8)
+ .align(Alignment.CenterVertically),
+ imageVector = ImageVector.vectorResource(
+ id = when (type) {
+ PriceChangeType.UP -> R.drawable.ic_arrow_up_8
+ PriceChangeType.DOWN -> R.drawable.ic_arrow_down_8
+ PriceChangeType.NEUTRAL -> R.drawable.ic_elipse_8
+ },
+ ),
+ tint = when (type) {
+ PriceChangeType.UP -> TangemTheme.colors.icon.accent
+ PriceChangeType.DOWN -> TangemTheme.colors.icon.warning
+ PriceChangeType.NEUTRAL -> TangemTheme.colors.icon.inactive
+ },
+ contentDescription = null,
+ )
+
+ Text(
+ text = valueInPercent,
+ color = when (type) {
+ PriceChangeType.UP -> TangemTheme.colors.text.accent
+ PriceChangeType.DOWN -> TangemTheme.colors.text.warning
+ PriceChangeType.NEUTRAL -> TangemTheme.colors.text.disabled
+ },
+ style = textStyle,
+ overflow = TextOverflow.Visible,
+ maxLines = 1,
+ )
+ }
+}
+
+//region Preview
+
+@Preview
+@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
+@Composable
+private fun Preview() {
+ TangemThemePreview {
+ Column {
+ PriceChangeInPercent(
+ valueInPercent = "52.00%",
+ type = PriceChangeType.NEUTRAL,
+ )
+ PriceChangeInPercent(
+ valueInPercent = "52.00%",
+ type = PriceChangeType.UP,
+ )
+ PriceChangeInPercent(
+ valueInPercent = "52.00%",
+ type = PriceChangeType.DOWN,
+ )
+ PriceChangeInPercent(
+ valueInPercent = "52.00%",
+ type = PriceChangeType.DOWN,
+ textStyle = TangemTheme.typography.caption2,
+ )
+ }
+ }
+}
+
+//endregion Preview
\ No newline at end of file
diff --git a/core/ui/src/main/java/com/tangem/core/ui/windowsize/WindowSize.kt b/core/ui/src/main/java/com/tangem/core/ui/windowsize/WindowSize.kt
index a8928b86f8..ed47c74673 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/windowsize/WindowSize.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/windowsize/WindowSize.kt
@@ -46,18 +46,18 @@ data class WindowSize(
fun heightAtMost(value: Dp): Boolean = this.height <= value
private fun getWidthWindowSizeType(windowDp: Dp): WindowSizeType = when {
- windowDp < 300.dp -> WindowSizeType.ExtraSmall
- windowDp < 380.dp -> WindowSizeType.Small
- windowDp < 540.dp -> WindowSizeType.Normal
- windowDp < 700.dp -> WindowSizeType.Large
+ windowDp <= 320.dp -> WindowSizeType.ExtraSmall
+ windowDp <= 360.dp -> WindowSizeType.Small
+ windowDp <= 540.dp -> WindowSizeType.Normal
+ windowDp <= 700.dp -> WindowSizeType.Large
else -> WindowSizeType.ExtraLarge
}
private fun getHeightWindowSizeType(heightDp: Dp): WindowSizeType = when {
- heightDp < 480.dp -> WindowSizeType.ExtraSmall
- heightDp < 640.dp -> WindowSizeType.Small
- heightDp < 860.dp -> WindowSizeType.Normal
- heightDp < 1100.dp -> WindowSizeType.Large
+ heightDp <= 480.dp -> WindowSizeType.ExtraSmall
+ heightDp <= 640.dp -> WindowSizeType.Small
+ heightDp <= 860.dp -> WindowSizeType.Normal
+ heightDp <= 1100.dp -> WindowSizeType.Large
else -> WindowSizeType.ExtraLarge
}
}
diff --git a/core/ui/src/main/res/drawable/ic_plus_mini_28.xml b/core/ui/src/main/res/drawable/ic_plus_mini_28.xml
new file mode 100644
index 0000000000..e6e483128c
--- /dev/null
+++ b/core/ui/src/main/res/drawable/ic_plus_mini_28.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/features/markets/api/.gitignore b/features/markets/api/.gitignore
new file mode 100644
index 0000000000..796b96d1c4
--- /dev/null
+++ b/features/markets/api/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/features/markets/api/build.gradle.kts b/features/markets/api/build.gradle.kts
new file mode 100644
index 0000000000..0248feb86d
--- /dev/null
+++ b/features/markets/api/build.gradle.kts
@@ -0,0 +1,18 @@
+plugins {
+ alias(deps.plugins.android.library)
+ alias(deps.plugins.kotlin.android)
+ id("kotlin-parcelize")
+ id("configuration")
+}
+
+android {
+ namespace = "com.tangem.features.markets.api"
+}
+
+dependencies {
+ implementation(deps.compose.foundation)
+
+ /* Project - Core */
+ implementation(projects.core.decompose)
+ implementation(projects.core.ui)
+}
\ No newline at end of file
diff --git a/features/markets/api/src/main/kotlin/com/tangem/features/markets/BottomSheetState.kt b/features/markets/api/src/main/kotlin/com/tangem/features/markets/BottomSheetState.kt
new file mode 100644
index 0000000000..0084b73528
--- /dev/null
+++ b/features/markets/api/src/main/kotlin/com/tangem/features/markets/BottomSheetState.kt
@@ -0,0 +1,6 @@
+package com.tangem.features.markets
+
+enum class BottomSheetState {
+ EXPANDED,
+ COLLAPSED,
+}
\ No newline at end of file
diff --git a/features/markets/api/src/main/kotlin/com/tangem/features/markets/MarketsListComponent.kt b/features/markets/api/src/main/kotlin/com/tangem/features/markets/MarketsListComponent.kt
new file mode 100644
index 0000000000..e987e0d66e
--- /dev/null
+++ b/features/markets/api/src/main/kotlin/com/tangem/features/markets/MarketsListComponent.kt
@@ -0,0 +1,12 @@
+package com.tangem.features.markets
+
+import androidx.compose.runtime.Stable
+
+@Stable
+interface MarketsListComponent {
+
+ // TODO
+
+ // @Composable
+ // fun BottomSheetContent(state: State, onHeaderSizeChange: (Dp) -> Unit, modifier: Modifier)
+}
\ No newline at end of file
diff --git a/features/markets/impl/.gitignore b/features/markets/impl/.gitignore
new file mode 100644
index 0000000000..796b96d1c4
--- /dev/null
+++ b/features/markets/impl/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/features/markets/impl/build.gradle.kts b/features/markets/impl/build.gradle.kts
new file mode 100644
index 0000000000..80288a27be
--- /dev/null
+++ b/features/markets/impl/build.gradle.kts
@@ -0,0 +1,24 @@
+plugins {
+ alias(deps.plugins.android.library)
+ alias(deps.plugins.kotlin.android)
+ id("configuration")
+}
+
+android {
+ namespace = "com.tangem.features.markets.impl"
+}
+
+dependencies {
+ implementation(deps.compose.coil)
+ implementation(deps.compose.foundation)
+ implementation(deps.compose.material3)
+ implementation(deps.compose.ui)
+ implementation(deps.compose.ui.tooling)
+ implementation(deps.compose.ui.utils)
+
+ implementation(deps.kotlin.immutable.collections)
+
+ implementation(projects.core.ui)
+ implementation(projects.common.ui)
+ implementation(projects.common.uiCharts)
+}
\ No newline at end of file
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/ui/MarketsListItem.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/ui/MarketsListItem.kt
new file mode 100644
index 0000000000..67dd8c574e
--- /dev/null
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/ui/MarketsListItem.kt
@@ -0,0 +1,448 @@
+package com.tangem.features.markets.ui
+
+import android.content.res.Configuration
+import androidx.compose.animation.Animatable
+import androidx.compose.animation.core.*
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.gestures.*
+import androidx.compose.foundation.interaction.*
+import androidx.compose.foundation.layout.*
+import androidx.compose.material.ripple.rememberRipple
+import androidx.compose.material3.Button
+import androidx.compose.material3.Text
+import androidx.compose.runtime.*
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.graphics.RectangleShape
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.res.vectorResource
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.tooling.preview.PreviewParameter
+import androidx.compose.ui.unit.IntOffset
+import com.tangem.common.ui.charts.MarketChartMini
+import com.tangem.common.ui.charts.state.MarketChartLook
+import com.tangem.common.ui.charts.state.MarketChartRawData
+import com.tangem.core.ui.components.*
+import com.tangem.core.ui.components.currency.tokenicon.CoinIcon
+import com.tangem.core.ui.components.marketprice.PriceChangeInPercent
+import com.tangem.core.ui.components.marketprice.PriceChangeType
+import com.tangem.core.ui.res.LocalHapticManager
+import com.tangem.core.ui.res.LocalWindowSize
+import com.tangem.core.ui.res.TangemTheme
+import com.tangem.core.ui.res.TangemThemePreview
+import com.tangem.core.ui.windowsize.WindowSizeType
+import com.tangem.features.markets.impl.R
+import com.tangem.features.markets.ui.models.MarketsListItemModel
+import com.tangem.features.markets.ui.preview.MarketChartListItemPreviewDataProvider
+import kotlinx.coroutines.flow.*
+import kotlinx.coroutines.launch
+import kotlin.math.roundToInt
+import kotlin.random.Random
+
+internal enum class DragValue { Start, End }
+
+const val SWIPE_THRESHOLD_PERCENT = 0.8f
+const val SWIPE_VELOCITY_THRESHOLD = 20f
+
+@Suppress("LongMethod")
+@OptIn(ExperimentalFoundationApi::class)
+@Composable
+fun MarketsListItem(
+ model: MarketsListItemModel,
+ modifier: Modifier = Modifier,
+ onClick: () -> Unit = {},
+ onSwipeToAction: () -> Unit = {},
+) {
+ val actionWidth = TangemTheme.dimens.size68
+ val actionWidthPx = with(LocalDensity.current) { actionWidth.toPx() }
+
+ val hapticManager = LocalHapticManager.current
+
+ val anchors = DraggableAnchors {
+ DragValue.Start at 0f
+ DragValue.End at -actionWidthPx
+ }
+ val state = remember {
+ AnchoredDraggableState(
+ initialValue = DragValue.Start,
+ anchors = anchors,
+ positionalThreshold = { it * (1 - SWIPE_THRESHOLD_PERCENT) },
+ velocityThreshold = { SWIPE_VELOCITY_THRESHOLD },
+ animationSpec = tween(easing = FastOutSlowInEasing),
+ confirmValueChange = { it == DragValue.Start },
+ )
+ }
+ val dragInteractionSource = remember { MutableInteractionSource() }
+ val clickInteractionSource = remember { MutableInteractionSource() }
+ val isInDraggedState by dragInteractionSource.collectIsDraggedAsState()
+
+ LaunchedEffect(Unit) {
+ var actionPerformed = false
+ var releasePerformed = true
+ launch {
+ snapshotFlow { state.offset }
+ .collect {
+ val border = -actionWidthPx * SWIPE_THRESHOLD_PERCENT
+ if (it < border && actionPerformed.not()) {
+ hapticManager.vibrateLong()
+ actionPerformed = true
+ releasePerformed = false
+ }
+
+ if (it > border) {
+ if (releasePerformed.not()) {
+ hapticManager.vibrateShort()
+ releasePerformed = true
+ }
+ actionPerformed = false
+ }
+ }
+ }
+ launch {
+ snapshotFlow { isInDraggedState }
+ .collect {
+ if (it.not() && actionPerformed) {
+ releasePerformed = true
+ onSwipeToAction()
+ }
+ }
+ }
+ }
+
+ Box(
+ modifier = Modifier
+ .height(intrinsicSize = IntrinsicSize.Min)
+ .fillMaxWidth(),
+ ) {
+ Box(
+ modifier = Modifier
+ .align(Alignment.TopEnd)
+ .fillMaxHeight()
+ .offset {
+ IntOffset(
+ x = actionWidthPx.roundToInt() +
+ state
+ .requireOffset()
+ .toInt(),
+ y = 0,
+ )
+ }
+ .width(actionWidth)
+ .background(TangemTheme.colors.control.checked),
+ contentAlignment = Alignment.Center,
+ ) {
+ Image(
+ modifier = Modifier.size(TangemTheme.dimens.size28),
+ imageVector = ImageVector.vectorResource(id = R.drawable.ic_plus_mini_28),
+ colorFilter = ColorFilter.tint(TangemTheme.colors.icon.primary2),
+ contentDescription = null,
+ )
+ }
+
+ Box(
+ modifier = modifier
+ .align(Alignment.CenterStart)
+ .clip(RectangleShape)
+ .offset {
+ IntOffset(
+ x = state
+ .requireOffset()
+ .toInt(),
+ y = 0,
+ )
+ }
+ .anchoredDraggable(
+ state = state,
+ orientation = Orientation.Horizontal,
+ interactionSource = dragInteractionSource,
+ )
+ .clickable(
+ enabled = true,
+ interactionSource = clickInteractionSource,
+ indication = rememberRipple(),
+ onClick = onClick,
+ ),
+ ) {
+ MarketsListItemContent(model = model)
+ }
+ }
+}
+
+@Composable
+private fun MarketsListItemContent(model: MarketsListItemModel, modifier: Modifier = Modifier) {
+ val windowSize = LocalWindowSize.current
+
+ Row(
+ modifier = modifier.padding(
+ horizontal = TangemTheme.dimens.spacing16,
+ vertical = TangemTheme.dimens.spacing15,
+ ),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ CoinIcon(
+ modifier = Modifier.size(TangemTheme.dimens.size36),
+ url = model.iconUrl,
+ alpha = 1f,
+ colorFilter = null,
+ fallbackResId = R.drawable.ic_custom_token_44,
+ )
+
+ SpacerW12()
+
+ Column(modifier = Modifier.weight(1f)) {
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ ) {
+ TokenTitle(
+ modifier = Modifier.weight(1f, fill = false),
+ name = model.name,
+ currencySymbol = model.currencySymbol,
+ )
+ SpacerW8()
+ TokenPriceText(
+ modifier = Modifier.alignByBaseline(),
+ price = model.price.text,
+ priceChangeType = model.price.changeType,
+ )
+ }
+
+ SpacerH(height = TangemTheme.dimens.spacing2)
+
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.Bottom,
+ ) {
+ TokenSubtitle(
+ modifier = Modifier
+ .weight(1f, fill = false)
+ .alignByBaseline(),
+ ratingPosition = model.ratingPosition,
+ marketCap = model.marketCap,
+ )
+ PriceChangeInPercent(
+ modifier = Modifier.alignByBaseline(),
+ textStyle = TangemTheme.typography.caption2,
+ type = model.trendType,
+ valueInPercent = model.trendPercentText,
+ )
+ }
+ }
+
+ if (windowSize.widthAtLeast(WindowSizeType.Small)) {
+ Spacer(Modifier.width(TangemTheme.dimens.spacing10))
+
+ Chart(
+ chartType = model.chartType,
+ chartRawData = model.chardData,
+ )
+ }
+ }
+}
+
+@Composable
+private fun TokenTitle(name: String, currencySymbol: String, modifier: Modifier = Modifier) {
+ Row(modifier = modifier) {
+ Text(
+ modifier = Modifier
+ .weight(1f, fill = false)
+ .alignByBaseline(),
+ text = name,
+ color = TangemTheme.colors.text.primary1,
+ style = TangemTheme.typography.subtitle2,
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis,
+ )
+ SpacerW4()
+ Text(
+ modifier = Modifier.alignByBaseline(),
+ text = currencySymbol,
+ color = TangemTheme.colors.text.tertiary,
+ style = TangemTheme.typography.caption1,
+ maxLines = 1,
+ overflow = TextOverflow.Visible,
+ )
+ }
+}
+
+@Composable
+private fun TokenSubtitle(ratingPosition: String?, marketCap: String?, modifier: Modifier = Modifier) {
+ Row(
+ modifier = modifier,
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ TokenRatingPlace(ratingPosition = ratingPosition)
+ SpacerW4()
+ TokenMarketCapText(text = marketCap ?: "")
+ }
+}
+
+@Composable
+private fun RowScope.TokenRatingPlace(ratingPosition: String?) {
+ Box(
+ modifier = Modifier
+ .alignByBaseline()
+ .heightIn(min = TangemTheme.dimens.size16)
+ .background(
+ color = TangemTheme.colors.field.primary,
+ shape = TangemTheme.shapes.roundedCornersSmall2,
+ )
+ .padding(horizontal = TangemTheme.dimens.size5), // TODO check
+ ) {
+ Text(
+ text = ratingPosition ?: "-",
+ color = TangemTheme.colors.text.tertiary,
+ style = TangemTheme.typography.caption1,
+ maxLines = 1,
+ )
+ }
+}
+
+@Composable
+private fun RowScope.TokenMarketCapText(text: String) {
+ Text(
+ modifier = Modifier.alignByBaseline(),
+ text = text,
+ color = TangemTheme.colors.text.tertiary,
+ style = TangemTheme.typography.caption2,
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis,
+ )
+}
+
+@Composable
+private fun TokenPriceText(price: String, modifier: Modifier = Modifier, priceChangeType: PriceChangeType? = null) {
+ val growColor = TangemTheme.colors.text.accent
+ val fallColor = TangemTheme.colors.text.warning
+ val generalColor = TangemTheme.colors.text.primary1
+
+ val color = remember { Animatable(generalColor) }
+
+ LaunchedEffect(price, growColor, fallColor, generalColor) {
+ snapshotFlow { price }
+ .drop(1)
+ .distinctUntilChanged()
+ .collectLatest {
+ val nextColor = when (priceChangeType) {
+ PriceChangeType.NEUTRAL,
+ PriceChangeType.UP,
+ -> growColor
+ PriceChangeType.DOWN -> fallColor
+ null -> generalColor
+ }
+
+ color.animateTo(nextColor, snap())
+ color.animateTo(generalColor, tween(durationMillis = 500))
+ }
+ }
+
+ Text(
+ modifier = modifier,
+ text = price,
+ color = color.value,
+ maxLines = 1,
+ style = TangemTheme.typography.body2,
+ overflow = TextOverflow.Visible,
+ )
+}
+
+@Composable
+private fun Chart(chartType: MarketChartLook.Type, chartRawData: MarketChartRawData?) {
+ val chartWidth = TangemTheme.dimens.size56
+ Box(
+ modifier = Modifier
+ .padding(vertical = TangemTheme.dimens.spacing2)
+ .size(height = TangemTheme.dimens.size32, width = chartWidth),
+ ) {
+ if (chartRawData != null) {
+ MarketChartMini(
+ rawData = chartRawData,
+ type = chartType,
+ )
+ } else {
+ RectangleShimmer(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(TangemTheme.dimens.size15) // TODO check
+ .align(Alignment.Center),
+ )
+ }
+ }
+}
+
+// region preview
+@Preview(showBackground = true, widthDp = 360, name = "normal")
+@Preview(showBackground = true, widthDp = 360, name = "normal night", uiMode = Configuration.UI_MODE_NIGHT_YES)
+@Preview(showBackground = true, widthDp = 320, name = "small width")
+@Composable
+private fun Preview(@PreviewParameter(MarketChartListItemPreviewDataProvider::class) state: MarketsListItemModel) {
+ TangemThemePreview {
+ var state1 by remember { mutableStateOf(state) }
+ var state2 by remember { mutableStateOf(state) }
+ var prices by remember {
+ mutableStateOf(
+ listOf(
+ 100 to PriceChangeType.NEUTRAL,
+ 200 to PriceChangeType.NEUTRAL,
+ ),
+ )
+ }
+
+ Column(modifier = Modifier.background(TangemTheme.colors.background.primary)) {
+ MarketsListItem(
+ modifier = Modifier,
+ model = state1,
+ )
+ MarketsListItem(
+ modifier = Modifier,
+ model = state2,
+ )
+ Row {
+ Button(
+ onClick = {
+ state1 = state1.copy(
+ trendType = PriceChangeType.entries.random(),
+ )
+ state2 = state2.copy(
+ trendType = PriceChangeType.entries.random(),
+ )
+ },
+ ) { Text(text = "trend") }
+
+ Button(
+ onClick = {
+ prices = prices.map {
+ if (Random.nextBoolean()) {
+ it.first.inc() to PriceChangeType.UP
+ } else {
+ it.first.dec() to PriceChangeType.DOWN
+ }
+ }
+ state1 = state1.copy(
+ price = MarketsListItemModel.Price(
+ text = "0.${prices[0].first}023 $",
+ changeType = prices[0].second,
+ ),
+ )
+ state2 = state2.copy(
+ price = MarketsListItemModel.Price(
+ text = "0.${prices[1].first}023 $",
+ changeType = prices[1].second,
+ ),
+ )
+ },
+ ) { Text(text = "price") }
+ }
+ }
+ }
+}
+
+// endregion preview
\ No newline at end of file
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/ui/models/MarketsListItemModel.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/ui/models/MarketsListItemModel.kt
new file mode 100644
index 0000000000..4c8d21f229
--- /dev/null
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/ui/models/MarketsListItemModel.kt
@@ -0,0 +1,33 @@
+package com.tangem.features.markets.ui.models
+
+import androidx.compose.runtime.Immutable
+import com.tangem.common.ui.charts.state.MarketChartLook
+import com.tangem.common.ui.charts.state.MarketChartRawData
+import com.tangem.core.ui.components.marketprice.PriceChangeType
+
+@Immutable
+data class MarketsListItemModel(
+ val id: String,
+ val name: String,
+ val currencySymbol: String,
+ val iconUrl: String?,
+ val ratingPosition: String?,
+ val marketCap: String?,
+ val price: Price,
+ val trendPercentText: String,
+ val trendType: PriceChangeType,
+ val chardData: MarketChartRawData?,
+) {
+ val chartType: MarketChartLook.Type = when (trendType) {
+ PriceChangeType.UP,
+ PriceChangeType.NEUTRAL,
+ -> MarketChartLook.Type.Growing
+ PriceChangeType.DOWN -> MarketChartLook.Type.Falling
+ }
+
+ @Immutable
+ data class Price(
+ val text: String,
+ val changeType: PriceChangeType = PriceChangeType.NEUTRAL,
+ )
+}
\ No newline at end of file
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/ui/preview/MarketChartListItemPreviewDataProvider.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/ui/preview/MarketChartListItemPreviewDataProvider.kt
new file mode 100644
index 0000000000..6121a35222
--- /dev/null
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/ui/preview/MarketChartListItemPreviewDataProvider.kt
@@ -0,0 +1,94 @@
+@file:Suppress("MagicNumber")
+package com.tangem.features.markets.ui.preview
+
+import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
+import com.tangem.common.ui.charts.state.MarketChartRawData
+import com.tangem.core.ui.components.marketprice.PriceChangeType
+import com.tangem.features.markets.ui.models.MarketsListItemModel
+
+internal class MarketChartListItemPreviewDataProvider : CollectionPreviewParameterProvider(
+ collection = listOf(
+ MarketsListItemModel(
+ id = "1",
+ name = "Bitcoin",
+ currencySymbol = "BTC",
+ iconUrl = "",
+ ratingPosition = "10",
+ marketCap = "$6.233 B",
+ price = MarketsListItemModel.Price(text = "31 285.72$"),
+ trendPercentText = "12.43%",
+ trendType = PriceChangeType.UP,
+ chardData = MarketChartRawData(
+ y = listOf(0.4f, 0.2f, 0.4f, 0.1f, 0.4f, 2f, 5f, 0.1f, 2f, 2f, 3f),
+ ),
+ ),
+ MarketsListItemModel(
+ id = "1",
+ name = "Bitcoin",
+ currencySymbol = "BTC",
+ iconUrl = null,
+ ratingPosition = "10",
+ marketCap = "$6.233 B",
+ price = MarketsListItemModel.Price(text = "31 285.72$"),
+ trendPercentText = "12.43%",
+ trendType = PriceChangeType.NEUTRAL,
+ chardData = null,
+ ),
+ MarketsListItemModel(
+ id = "1",
+ name = "Bitcoin Bitcoin Bitcoin Bitcoin Bitcoin Bitcoin Bitcoin",
+ currencySymbol = "BTC",
+ iconUrl = null,
+ ratingPosition = "10",
+ marketCap = "$6.23348172384781234 B",
+ price = MarketsListItemModel.Price(text = "31 285.72$"),
+ trendPercentText = "12.43%",
+ trendType = PriceChangeType.DOWN,
+ chardData = MarketChartRawData(
+ y = listOf(0.4f, 0.2f, 0.4f, 0.1f, 0.4f, 2f, 5f, 0.1f, 2f, 2f, 3f),
+ ),
+ ),
+ MarketsListItemModel(
+ id = "1",
+ name = "Bitcoin",
+ currencySymbol = "BTC",
+ iconUrl = null,
+ ratingPosition = "10",
+ marketCap = null,
+ price = MarketsListItemModel.Price(text = "31 285.72$"),
+ trendPercentText = "12.43%",
+ trendType = PriceChangeType.UP,
+ chardData = MarketChartRawData(
+ y = listOf(0.4f, 0.2f, 0.4f, 0.1f, 0.4f, 2f, 5f, 0.1f, 2f, 2f, 3f),
+ ),
+ ),
+ MarketsListItemModel(
+ id = "1",
+ name = "Bitcoin",
+ currencySymbol = "BTC",
+ iconUrl = null,
+ ratingPosition = null,
+ marketCap = "$6.233 B",
+ price = MarketsListItemModel.Price(text = "31 285.72$"),
+ trendPercentText = "12.43%",
+ trendType = PriceChangeType.UP,
+ chardData = MarketChartRawData(
+ y = listOf(0.4f, 0.2f, 0.4f, 0.1f, 0.4f, 2f, 5f, 0.1f, 2f, 2f, 3f),
+ ),
+ ),
+ MarketsListItemModel(
+ id = "1",
+ name = "Bitcoin",
+ currencySymbol = "BTC",
+ iconUrl = null,
+ ratingPosition = null,
+ marketCap = null,
+ price = MarketsListItemModel.Price(text = "31 285.72$"),
+ trendPercentText = "12.43%",
+ trendType = PriceChangeType.UP,
+ chardData = MarketChartRawData(
+ y = listOf(0.4f, 0.2f, 0.4f, 0.1f, 0.4f, 2f, 5f, 0.1f, 2f, 2f, 3f),
+ ),
+ ),
+ ),
+)
\ No newline at end of file
diff --git a/features/wallet/impl/build.gradle.kts b/features/wallet/impl/build.gradle.kts
index f9f232db02..bbfc624efd 100644
--- a/features/wallet/impl/build.gradle.kts
+++ b/features/wallet/impl/build.gradle.kts
@@ -90,4 +90,5 @@ dependencies {
implementation(projects.features.manageTokens.api)
implementation(projects.features.details.api)
implementation(projects.features.pushNotifications.api)
+ implementation(projects.features.markets.api)
}
\ No newline at end of file
diff --git a/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/extension/BaseExtensionConfigurations.kt b/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/extension/BaseExtensionConfigurations.kt
index 67de36d6df..1612e170ed 100644
--- a/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/extension/BaseExtensionConfigurations.kt
+++ b/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/extension/BaseExtensionConfigurations.kt
@@ -25,6 +25,7 @@ internal fun BaseExtension.configureCompose(project: Project) {
contains(Regex(pattern = ":presentation\$")) ||
contains(Regex(pattern = ":app\$")) || // TODO: [REDACTED_JIRA]
contains(Regex(pattern = ":features:manage-tokens:api\$")) || // provides Composable function
+ contains(Regex(pattern = ":features:markets:api\$")) || // provides Composable function
contains(Regex(pattern = ":impl\$"))
}
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 14ba5ff49e..3637dd3cc9 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -186,6 +186,9 @@ include(":features:push-notifications:impl")
include(":features:wallet-settings:api")
include(":features:wallet-settings:impl")
+
+include(":features:markets:api")
+include(":features:markets:impl")
// endregion Feature modules
// region Domain modules