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