From c500f4530770b605e4de2768ab720a7943ec24fa Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 8 Aug 2024 18:53:25 +0300 Subject: [PATCH] Updated on 2026-08-14 --- .../tap/di/domain/MarketsDomainModule.kt | 7 + .../tangem/common/ui/charts/MarketChart.kt | 16 +- .../ui/charts/downsample/LTThreeBuckets.kt | 2 +- .../api/markets/TangemTechMarketsApi.kt | 1 + .../response/TokenMarketInfoResponse.kt | 38 ++--- .../bottomsheets/TangemBottomSheet.kt | 11 +- .../tangem/core/ui/res/TangemAnimations.kt | 11 +- .../core/ui/utils/BigDecimalFormatter.kt | 94 ++++++++---- .../ui/utils/BigDecimalFormatterCompat.kt | 27 +++- .../com/tangem/core/ui/utils/ScrollUtils.kt | 15 ++ .../com/tangem/utils/SupportedLanguages.kt | 35 +++++ .../markets/DefaultMarketsTokenRepository.kt | 7 +- .../converters/TokenMarketInfoConverter.kt | 16 +- domain/markets/build.gradle.kts | 1 + .../tangem/domain/markets/TokenMarketInfo.kt | 32 ++-- .../markets/GetTokenMarketInfoUseCase.kt | 2 + .../repositories/MarketsTokenRepository.kt | 2 +- features/markets/impl/build.gradle.kts | 1 + .../impl/model/MarketsTokenDetailsModel.kt | 136 ++++++++++++++++- .../model/converters/DescriptionConverter.kt | 41 +++++ .../model/converters/InsightsConverter.kt | 131 ++++++++++++++++ .../impl/model/converters/LinksConverter.kt | 49 ++++++ .../impl/model/converters/MetricsConverter.kt | 135 +++++++++++++++++ .../converters/PricePerformanceConverter.kt | 58 +++++++ .../converters/SecurityScoreConverter.kt | 35 +++++ .../converters/TokenMarketInfoConverter.kt | 34 +++++ .../impl/ui/MarketsTokenDetailsContent.kt | 85 +++++++---- .../details/impl/ui/components/Description.kt | 112 ++++++++++++++ .../impl/ui/components/InfoBottomSheet.kt | 47 ++++++ .../details/impl/ui/components/InfoPoint.kt | 21 +-- .../impl/ui/components/InsightsBlock.kt | 4 +- .../details/impl/ui/components/LinksBlock.kt | 20 +-- .../ui/components/MarketTokenDetailsChart.kt | 7 +- .../impl/ui/components/MetricsBlock.kt | 4 +- .../ui/components/PricePerformanceBlock.kt | 36 ++--- .../impl/ui/components/SecurityScoreBlock.kt | 2 +- .../ui/components/TokenMarketDetailsBody.kt | 142 ++++++++++++++++++ .../details/impl/ui/entity/InsightsUM.kt | 11 -- .../markets/details/impl/ui/entity/LinksUM.kt | 22 --- .../impl/ui/state/InfoBottomSheetContent.kt | 9 ++ .../impl/ui/{entity => state}/InfoPointUM.kt | 4 +- .../details/impl/ui/state/InsightsUM.kt | 9 ++ .../markets/details/impl/ui/state/LinksUM.kt | 19 +++ .../MarketsTokenDetailsUM.kt | 35 ++++- .../impl/ui/{entity => state}/MetricsUM.kt | 4 +- .../{entity => state}/PricePerformanceUM.kt | 5 +- .../ui/{entity => state}/SecurityScoreUM.kt | 4 +- .../tokenlist/impl/model/MarketsListModel.kt | 13 +- .../converters/MarketsTokenItemConverter.kt | 9 +- .../MarketsListBatchFlowManager.kt | 6 +- .../statemanager/MarketsListUMStateManager.kt | 10 +- .../markets/tokenlist/impl/ui/MarketsList.kt | 8 +- .../impl/ui/components/MarketsListItem.kt | 2 +- .../ui/components/MarketsListLazyColumn.kt | 17 +-- .../MarketsListSortByBottomSheet.kt | 4 +- .../MarketChartListItemPreviewDataProvider.kt | 2 +- .../ui/{entity => state}/MarketsListItemUM.kt | 2 +- .../ui/{entity => state}/MarketsListUM.kt | 2 +- .../SortByBottomSheetContentUM.kt | 2 +- 59 files changed, 1338 insertions(+), 278 deletions(-) create mode 100644 core/ui/src/main/java/com/tangem/core/ui/utils/ScrollUtils.kt create mode 100644 core/utils/src/main/java/com/tangem/utils/SupportedLanguages.kt create mode 100644 features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/DescriptionConverter.kt create mode 100644 features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/InsightsConverter.kt create mode 100644 features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/LinksConverter.kt create mode 100644 features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/MetricsConverter.kt create mode 100644 features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/PricePerformanceConverter.kt create mode 100644 features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/SecurityScoreConverter.kt create mode 100644 features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/TokenMarketInfoConverter.kt create mode 100644 features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/Description.kt create mode 100644 features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/InfoBottomSheet.kt create mode 100644 features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/TokenMarketDetailsBody.kt delete mode 100644 features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/entity/InsightsUM.kt delete mode 100644 features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/entity/LinksUM.kt create mode 100644 features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/state/InfoBottomSheetContent.kt rename features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/{entity => state}/InfoPointUM.kt (62%) create mode 100644 features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/state/InsightsUM.kt create mode 100644 features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/state/LinksUM.kt rename features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/{entity => state}/MarketsTokenDetailsUM.kt (64%) rename features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/{entity => state}/MetricsUM.kt (54%) rename features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/{entity => state}/PricePerformanceUM.kt (70%) rename features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/{entity => state}/SecurityScoreUM.kt (63%) rename features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/{entity => state}/MarketsListItemUM.kt (94%) rename features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/{entity => state}/MarketsListUM.kt (97%) rename features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/{entity => state}/SortByBottomSheetContentUM.kt (79%) diff --git a/app/src/main/java/com/tangem/tap/di/domain/MarketsDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/MarketsDomainModule.kt index f07561bf15..e96df425a1 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/MarketsDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/MarketsDomainModule.kt @@ -1,6 +1,7 @@ package com.tangem.tap.di.domain import com.tangem.domain.markets.GetMarketsTokenListFlowUseCase +import com.tangem.domain.markets.GetTokenMarketInfoUseCase import com.tangem.domain.markets.GetTokenPriceChartUseCase import com.tangem.domain.markets.repositories.MarketsTokenRepository import dagger.Module @@ -26,4 +27,10 @@ object MarketsDomainModule { fun provideGetTokenPriceChartUseCase(marketsTokenRepository: MarketsTokenRepository): GetTokenPriceChartUseCase { return GetTokenPriceChartUseCase(marketsTokenRepository = marketsTokenRepository) } + + @Provides + @Singleton + fun provideGetTokenMarketInfoUseCase(marketsTokenRepository: MarketsTokenRepository): GetTokenMarketInfoUseCase { + return GetTokenMarketInfoUseCase(marketsTokenRepository = marketsTokenRepository) + } } \ No newline at end of file diff --git a/common/ui-charts/src/main/kotlin/com/tangem/common/ui/charts/MarketChart.kt b/common/ui-charts/src/main/kotlin/com/tangem/common/ui/charts/MarketChart.kt index 4e43d74718..917b2eab45 100644 --- a/common/ui-charts/src/main/kotlin/com/tangem/common/ui/charts/MarketChart.kt +++ b/common/ui-charts/src/main/kotlin/com/tangem/common/ui/charts/MarketChart.kt @@ -10,6 +10,7 @@ import androidx.compose.material3.Button import androidx.compose.material3.Text import androidx.compose.runtime.* import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.platform.LocalDensity @@ -20,6 +21,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.resolveAsTypeface import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.patrykandpatrick.vico.compose.cartesian.CartesianChartHost import com.patrykandpatrick.vico.compose.cartesian.axis.rememberAxisGuidelineComponent @@ -71,7 +73,6 @@ private const val GUIDELINES_COUNT = 3 * @param splitChartSegmentColor The color of the grayed by marker chart segment. * @param backgroundSplitChartSegmentColorAlpha The alpha of the background the [splitChartSegmentColor] * @param backgroundColorAlpha The alpha of the background color of the chart. - * @param noChartContent A composable function that defines the content to be displayed when there is no data to display. */ @Composable fun MarketChart( @@ -108,7 +109,7 @@ fun MarketChart( // we need to calculate what the overall height should be in order to get the correct height of the graph val bottomAxisHeight = with(LocalDensity.current) { - TangemTheme.typography.caption2.fontSize.toPx().toInt() + TangemTheme.dimens.spacing26.toPx().toInt() + getMarketChartBottomAxisHeight().toPx().toInt() } CartesianChartHost( @@ -120,6 +121,10 @@ fun MarketChart( } else { 0 } + } + // Sometimes the chart is not drawn correctly (ex. in LazyLayout), so we need to force the redraw + .drawBehind { + state.markerFraction }, chart = chart, modelProducer = state.modelProducer, @@ -129,6 +134,13 @@ fun MarketChart( ) } +@Composable +fun getMarketChartBottomAxisHeight(): Dp { + return with(LocalDensity.current) { + TangemTheme.typography.caption2.fontSize.toDp() + TangemTheme.dimens.spacing26 + } +} + @Composable private fun rememberMarketVisibilityListener( canvasWidth: Int, diff --git a/common/ui-charts/src/main/kotlin/com/tangem/common/ui/charts/downsample/LTThreeBuckets.kt b/common/ui-charts/src/main/kotlin/com/tangem/common/ui/charts/downsample/LTThreeBuckets.kt index 59bf621547..21578b9cc1 100644 --- a/common/ui-charts/src/main/kotlin/com/tangem/common/ui/charts/downsample/LTThreeBuckets.kt +++ b/common/ui-charts/src/main/kotlin/com/tangem/common/ui/charts/downsample/LTThreeBuckets.kt @@ -238,7 +238,7 @@ private data class Area( } } -inline fun List.fastForEach(action: (T) -> Unit) { +private inline fun List.fastForEach(action: (T) -> Unit) { for (index in indices) { val item = get(index) action(item) diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/markets/TangemTechMarketsApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/markets/TangemTechMarketsApi.kt index 7362229f4a..c1cc2d5702 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/markets/TangemTechMarketsApi.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/markets/TangemTechMarketsApi.kt @@ -24,6 +24,7 @@ interface TangemTechMarketsApi { suspend fun getCoinMarketData( @Path("coin_id") coinId: String, @Query("currency") currency: String, + @Query("language") language: String, ): ApiResponse @GET("coins/{coin_id}/history") diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/markets/models/response/TokenMarketInfoResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/markets/models/response/TokenMarketInfoResponse.kt index 13b5e0bdee..ef485e1829 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/markets/models/response/TokenMarketInfoResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/markets/models/response/TokenMarketInfoResponse.kt @@ -21,7 +21,7 @@ data class TokenMarketInfoResponse( @Json(name = "full_description") val fullDescription: String?, @Json(name = "insights") - val insights: List?, + val insights: Insights?, @Json(name = "metrics") val metrics: Metrics?, @Json(name = "links") @@ -32,33 +32,33 @@ data class TokenMarketInfoResponse( data class PriceChangePercentage( @Json(name = "24h") - val day: Int?, + val day: BigDecimal?, @Json(name = "1w") - val week: Int?, + val week: BigDecimal?, @Json(name = "1m") - val month: Int?, + val month: BigDecimal?, @Json(name = "3m") - val threeMonths: Int?, + val threeMonths: BigDecimal?, @Json(name = "6m") - val sixMonths: Int?, + val sixMonths: BigDecimal?, @Json(name = "1y") - val year: Int?, + val year: BigDecimal?, @Json(name = "all_time") - val allTime: Int?, + val allTime: BigDecimal?, ) data class Network( @Json(name = "network_id") val networkId: String, @Json(name = "exchangeable") - val exchangeable: Boolean, + val exchangeable: Boolean = false, @Json(name = "contract_address") - val contractAddress: String, + val contractAddress: String?, @Json(name = "decimal_count") - val decimalCount: Int, + val decimalCount: Int?, ) - data class Insight( + data class Insights( @Json(name = "holders_change") val holdersChange: Change?, @Json(name = "liquidity_change") @@ -71,11 +71,11 @@ data class TokenMarketInfoResponse( data class Change( @Json(name = "24h") - val day: Int?, + val day: BigDecimal?, @Json(name = "1w") - val week: Int?, + val week: BigDecimal?, @Json(name = "1m") - val month: Int?, + val month: BigDecimal?, ) data class Metrics( @@ -123,9 +123,9 @@ data class TokenMarketInfoResponse( ) data class Range( - @Json(name = "low") - val low: Int?, - @Json(name = "high") - val high: Int?, + @Json(name = "low_price") + val low: BigDecimal?, + @Json(name = "high_price") + val high: BigDecimal?, ) } \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/TangemBottomSheet.kt b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/TangemBottomSheet.kt index 388e6b69c2..6ef3e53d1f 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/TangemBottomSheet.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/TangemBottomSheet.kt @@ -30,6 +30,7 @@ inline fun TangemBottomSheet( titleAction: TopAppBarButtonUM? = null, containerColor: Color = TangemTheme.colors.background.primary, addBottomInsets: Boolean = true, + skipPartiallyExpanded: Boolean = true, crossinline content: @Composable ColumnScope.(T) -> Unit, ) { TangemBottomSheet( @@ -37,6 +38,7 @@ inline fun TangemBottomSheet( containerColor = containerColor, addBottomInsets = addBottomInsets, title = { TangemBottomSheetTitle(title = titleText, endButton = titleAction) }, + skipPartiallyExpanded = skipPartiallyExpanded, content = content, ) } @@ -49,6 +51,7 @@ inline fun TangemBottomSheet( config: TangemBottomSheetConfig, containerColor: Color = TangemTheme.colors.background.primary, addBottomInsets: Boolean = true, + skipPartiallyExpanded: Boolean = true, crossinline title: @Composable BoxScope.(T) -> Unit = {}, crossinline content: @Composable ColumnScope.(T) -> Unit, ) { @@ -61,6 +64,7 @@ inline fun TangemBottomSheet( addBottomInsets = addBottomInsets, title = title, content = content, + skipPartiallyExpanded = skipPartiallyExpanded, ) } else { DefaultBottomSheet( @@ -69,6 +73,7 @@ inline fun TangemBottomSheet( addBottomInsets = addBottomInsets, title = title, content = content, + skipPartiallyExpanded = skipPartiallyExpanded, ) } } @@ -79,11 +84,12 @@ inline fun DefaultBottomSheet( config: TangemBottomSheetConfig, containerColor: Color, addBottomInsets: Boolean, + skipPartiallyExpanded: Boolean = true, crossinline title: @Composable (BoxScope.(T) -> Unit), crossinline content: @Composable (ColumnScope.(T) -> Unit), ) { var isVisible by remember { mutableStateOf(value = config.isShow) } - val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) + val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = skipPartiallyExpanded) if (isVisible && config.content is T) { BasicBottomSheet( @@ -111,6 +117,7 @@ inline fun PreviewBottomSheet( config: TangemBottomSheetConfig, containerColor: Color, addBottomInsets: Boolean, + skipPartiallyExpanded: Boolean = true, crossinline title: @Composable (BoxScope.(T) -> Unit), crossinline content: @Composable (ColumnScope.(T) -> Unit), ) { @@ -118,7 +125,7 @@ inline fun PreviewBottomSheet( modifier = Modifier.width(360.dp), config = config, sheetState = SheetState( - skipPartiallyExpanded = true, + skipPartiallyExpanded = skipPartiallyExpanded, initialValue = Expanded, density = LocalDensity.current, ), diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemAnimations.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemAnimations.kt index b735f815a7..2e81db1003 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/res/TangemAnimations.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemAnimations.kt @@ -1,10 +1,7 @@ package com.tangem.core.ui.res import androidx.compose.animation.* -import androidx.compose.animation.core.Spring -import androidx.compose.animation.core.animateFloatAsState -import androidx.compose.animation.core.spring -import androidx.compose.animation.core.tween +import androidx.compose.animation.core.* import androidx.compose.runtime.* @Immutable @@ -24,10 +21,6 @@ object TangemAnimations { @Immutable object TransitionSpecs { - - @Stable - val textChange: ContentTransform = - fadeIn(animationSpec = spring(dampingRatio = Spring.DampingRatioNoBouncy)) togetherWith - fadeOut(animationSpec = spring(dampingRatio = Spring.DampingRatioNoBouncy)) + // TODO add more transition specs } } \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt b/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt index e5d94f85b7..40e51a085c 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt @@ -117,6 +117,7 @@ object BigDecimalFormatter { fiatAmount: BigDecimal?, fiatCurrencyCode: String, fiatCurrencySymbol: String, + decimals: Int = FIAT_MARKET_DEFAULT_DIGITS, locale: Locale = Locale.getDefault(), ): String { if (fiatAmount == null) return EMPTY_BALANCE_SIGN @@ -124,8 +125,8 @@ object BigDecimalFormatter { val formatterCurrency = getCurrency(fiatCurrencyCode) val formatter = NumberFormat.getCurrencyInstance(locale).apply { currency = formatterCurrency - maximumFractionDigits = FIAT_MARKET_DEFAULT_DIGITS - minimumFractionDigits = FIAT_MARKET_DEFAULT_DIGITS + maximumFractionDigits = decimals + minimumFractionDigits = decimals roundingMode = RoundingMode.HALF_UP } @@ -250,40 +251,27 @@ object BigDecimalFormatter { /** * "123456.6" -> "$123.457K" * "12345.6" -> "$123.046K" + * Negative amount is not supported + * @param threeDigitsMethod if true, will format the amount always with 3 significant digits + * @param scale the number of digits to the right of the decimal point */ @Suppress("MagicNumber") - fun formatCompactAmount( - amount: BigDecimal, + fun formatCompactFiatAmount( + amount: BigDecimal?, fiatCurrencyCode: String, fiatCurrencySymbol: String, + threeDigitsMethod: Boolean = false, + scale: Int = 0, locale: Locale = Locale.getDefault(), ): String { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { - return BigDecimalFormatterCompat.formatCompactAmountNoLocaleContext( - amount = amount, - fiatCurrencyCode = fiatCurrencyCode, - fiatCurrencySymbol = fiatCurrencySymbol, - locale = locale, - ) - } + if (amount == null) return EMPTY_BALANCE_SIGN - val scaledAmount = amount.setScale(0, RoundingMode.HALF_UP) - val digitsCount = scaledAmount.longValueExact().toString().count() - val digitsToFormat = 6 - when (digitsCount % 3) { - 0 -> 0 - 1 -> 2 - else -> 1 - } - - val formatter = CompactDecimalFormat.getInstance( - locale, - CompactDecimalFormat.CompactStyle.SHORT, - ).apply { - minimumSignificantDigits = 4 - maximumSignificantDigits = digitsToFormat - } - - val rawAmount = formatter.format(amount.setScale(0, RoundingMode.HALF_UP)) + val rawAmount = formatCompactAmount( + amount = amount, + locale = locale, + threeDigitsMethod = threeDigitsMethod, + scale = scale, + ) return addCurrencySymbolToStringAmount( amount = rawAmount, @@ -293,5 +281,53 @@ object BigDecimalFormatter { ) } + /** + * "123456.6" -> "123.457K" + * "12345.6" -> "123.046K" + * Negative amount is not supported + * @param threeDigitsMethod if true, will format the amount always with 3 significant digits + * @param scale the number of digits to the right of the decimal point + */ + @Suppress("MagicNumber") + fun formatCompactAmount( + amount: BigDecimal, + locale: Locale = Locale.getDefault(), + threeDigitsMethod: Boolean = false, + scale: Int = 0, + ): String { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { + return BigDecimalFormatterCompat.formatCompactAmountNoLocaleContext(amount = amount) + } + + if (threeDigitsMethod) { + val scaledAmount = amount.setScale(scale, RoundingMode.HALF_UP) + val digitsCount = scaledAmount.longValueExact().toString().count() + val digitsToFormat = 6 - when (digitsCount % 3) { + 0 -> 0 + 1 -> 2 + else -> 1 + } + + val formatter = CompactDecimalFormat.getInstance( + locale, + CompactDecimalFormat.CompactStyle.SHORT, + ).apply { + minimumSignificantDigits = 4 + maximumSignificantDigits = digitsToFormat + } + + return formatter.format(amount.setScale(scale, RoundingMode.HALF_UP)) + } else { + val value = amount.setScale(scale, RoundingMode.HALF_UP) + + val formatter = CompactDecimalFormat.getInstance( + locale, + CompactDecimalFormat.CompactStyle.SHORT, + ) + + return formatter.format(value) + } + } + private fun BigDecimal.isLessThanThreshold() = this > BigDecimal.ZERO && this < FIAT_FORMAT_THRESHOLD } \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatterCompat.kt b/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatterCompat.kt index 82aab46559..9218188bef 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatterCompat.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatterCompat.kt @@ -7,16 +7,32 @@ import java.util.Locale internal object BigDecimalFormatterCompat { /** - * Formats value as [BigDecimalFormatter.formatCompactAmount] does using only "T","B","M","K" suffixes + * Formats value as [BigDecimalFormatter.formatCompactFiatAmount] does using only "T","B","M","K" suffixes * Used for < API24 compatibility */ @Suppress("MagicNumber", "UnnecessaryParentheses") - fun formatCompactAmountNoLocaleContext( + fun formatCompactFiatAmountNoLocaleContext( amount: BigDecimal, fiatCurrencyCode: String, fiatCurrencySymbol: String, locale: Locale = Locale.getDefault(), ): String { + val formatted = formatCompactAmountNoLocaleContext(amount) + + return BigDecimalFormatter.addCurrencySymbolToStringAmount( + amount = formatted, + fiatCurrencyCode = fiatCurrencyCode, + fiatCurrencySymbol = fiatCurrencySymbol, + locale = locale, + ) + } + + /** + * Formats value as [BigDecimalFormatter.formatCompactAmount] does using only "T","B","M","K" suffixes + * Used for < API24 compatibility + */ + @Suppress("MagicNumber", "UnnecessaryParentheses") + fun formatCompactAmountNoLocaleContext(amount: BigDecimal): String { val value = amount.setScale(0, RoundingMode.HALF_UP).longValueExact() val formatted = when { @@ -42,11 +58,6 @@ internal object BigDecimalFormatterCompat { else -> return value.toString() } - return BigDecimalFormatter.addCurrencySymbolToStringAmount( - amount = formatted, - fiatCurrencyCode = fiatCurrencyCode, - fiatCurrencySymbol = fiatCurrencySymbol, - locale = locale, - ) + return formatted } } \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/utils/ScrollUtils.kt b/core/ui/src/main/java/com/tangem/core/ui/utils/ScrollUtils.kt new file mode 100644 index 0000000000..3cef274d3a --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/utils/ScrollUtils.kt @@ -0,0 +1,15 @@ +package com.tangem.core.ui.utils + +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.input.nestedscroll.NestedScrollConnection +import androidx.compose.ui.input.nestedscroll.NestedScrollSource +import androidx.compose.ui.input.nestedscroll.nestedScroll + +fun Modifier.disableNestedScroll(): Modifier = nestedScroll(DisableParentConnection) + +private object DisableParentConnection : NestedScrollConnection { + override fun onPostScroll(consumed: Offset, available: Offset, source: NestedScrollSource): Offset { + return available.copy(x = 0f) + } +} \ No newline at end of file diff --git a/core/utils/src/main/java/com/tangem/utils/SupportedLanguages.kt b/core/utils/src/main/java/com/tangem/utils/SupportedLanguages.kt new file mode 100644 index 0000000000..48b2788b37 --- /dev/null +++ b/core/utils/src/main/java/com/tangem/utils/SupportedLanguages.kt @@ -0,0 +1,35 @@ +package com.tangem.utils + +import java.util.Locale + +object SupportedLanguages { + const val ENGLISH = "en" + const val RUSSIAN = "ru" + const val GERMAN = "de" + const val FRANCH = "fr" + const val ITALIAN = "it" + const val JAPANESE = "ja" + const val UKRAINIAN = "uk" + const val CHINESE = "uk" + + val supportedLangugeCodes = listOf( + ENGLISH, + RUSSIAN, + GERMAN, + FRANCH, + ITALIAN, + JAPANESE, + UKRAINIAN, + CHINESE, + ) + + fun getCurrentSupportedLanguageCode(): String { + val locale = Locale.getDefault() + + return if (supportedLangugeCodes.contains(locale.language)) { + locale.language + } else { + ENGLISH + } + } +} \ No newline at end of file diff --git a/data/markets/src/main/java/com/tangem/data/markets/DefaultMarketsTokenRepository.kt b/data/markets/src/main/java/com/tangem/data/markets/DefaultMarketsTokenRepository.kt index 6bd59980f7..e670fce633 100644 --- a/data/markets/src/main/java/com/tangem/data/markets/DefaultMarketsTokenRepository.kt +++ b/data/markets/src/main/java/com/tangem/data/markets/DefaultMarketsTokenRepository.kt @@ -108,10 +108,15 @@ internal class DefaultMarketsTokenRepository( return tokenChartConverter.convert(interval, response.getOrThrow()) } - override suspend fun getTokenInfo(fiatCurrencyCode: String, tokenId: String): TokenMarketInfo { + override suspend fun getTokenInfo( + fiatCurrencyCode: String, + tokenId: String, + languageCode: String, + ): TokenMarketInfo { val response = marketsApi.getCoinMarketData( currency = fiatCurrencyCode, coinId = tokenId, + language = languageCode, ) return TokenMarketInfoConverter().convert(response.getOrThrow()) diff --git a/data/markets/src/main/java/com/tangem/data/markets/converters/TokenMarketInfoConverter.kt b/data/markets/src/main/java/com/tangem/data/markets/converters/TokenMarketInfoConverter.kt index 524d27debd..8693393e68 100644 --- a/data/markets/src/main/java/com/tangem/data/markets/converters/TokenMarketInfoConverter.kt +++ b/data/markets/src/main/java/com/tangem/data/markets/converters/TokenMarketInfoConverter.kt @@ -50,15 +50,13 @@ internal class TokenMarketInfoConverter : Converter.convert(): List { - return map { - TokenMarketInfo.Insight( - holdersChange = it.holdersChange?.convert(), - liquidityChange = it.liquidityChange?.convert(), - buyPressureChange = it.buyPressureChange?.convert(), - experiencedBuyerChange = it.experiencedBuyerChange?.convert(), - ) - } + private fun TokenMarketInfoResponse.Insights.convert(): TokenMarketInfo.Insights { + return TokenMarketInfo.Insights( + holdersChange = holdersChange?.convert(), + liquidityChange = liquidityChange?.convert(), + buyPressureChange = buyPressureChange?.convert(), + experiencedBuyerChange = experiencedBuyerChange?.convert(), + ) } private fun TokenMarketInfoResponse.Change.convert(): TokenMarketInfo.Change { diff --git a/domain/markets/build.gradle.kts b/domain/markets/build.gradle.kts index 8545d1c3b3..885998c263 100644 --- a/domain/markets/build.gradle.kts +++ b/domain/markets/build.gradle.kts @@ -18,4 +18,5 @@ dependencies { implementation(deps.kotlin.serialization) implementation(projects.domain.tokens.models) + implementation(projects.core.utils) } \ No newline at end of file diff --git a/domain/markets/models/src/main/kotlin/com/tangem/domain/markets/TokenMarketInfo.kt b/domain/markets/models/src/main/kotlin/com/tangem/domain/markets/TokenMarketInfo.kt index 1ad9daf777..e1cf490632 100644 --- a/domain/markets/models/src/main/kotlin/com/tangem/domain/markets/TokenMarketInfo.kt +++ b/domain/markets/models/src/main/kotlin/com/tangem/domain/markets/TokenMarketInfo.kt @@ -11,29 +11,29 @@ data class TokenMarketInfo( val networks: List?, val shortDescription: String?, val fullDescription: String?, - val insights: List?, + val insights: Insights?, val metrics: Metrics?, val links: Links?, val pricePerformance: PricePerformance?, ) { data class PriceChangePercentage( - val day: Int?, - val week: Int?, - val month: Int?, - val threeMonths: Int?, - val sixMonths: Int?, - val year: Int?, - val allTime: Int?, + val day: BigDecimal?, + val week: BigDecimal?, + val month: BigDecimal?, + val threeMonths: BigDecimal?, + val sixMonths: BigDecimal?, + val year: BigDecimal?, + val allTime: BigDecimal?, ) data class Network( val networkId: String, val exchangeable: Boolean, - val contractAddress: String, - val decimalCount: Int, + val contractAddress: String?, + val decimalCount: Int?, ) - data class Insight( + data class Insights( val holdersChange: Change?, val liquidityChange: Change?, val buyPressureChange: Change?, @@ -41,9 +41,9 @@ data class TokenMarketInfo( ) data class Change( - val day: Int?, - val week: Int?, - val month: Int?, + val day: BigDecimal?, + val week: BigDecimal?, + val month: BigDecimal?, ) data class Metrics( @@ -75,7 +75,7 @@ data class TokenMarketInfo( ) data class Range( - val low: Int?, - val high: Int?, + val low: BigDecimal?, + val high: BigDecimal?, ) } \ No newline at end of file diff --git a/domain/markets/src/main/java/com/tangem/domain/markets/GetTokenMarketInfoUseCase.kt b/domain/markets/src/main/java/com/tangem/domain/markets/GetTokenMarketInfoUseCase.kt index f6b2ecf317..c7935cb4e0 100644 --- a/domain/markets/src/main/java/com/tangem/domain/markets/GetTokenMarketInfoUseCase.kt +++ b/domain/markets/src/main/java/com/tangem/domain/markets/GetTokenMarketInfoUseCase.kt @@ -3,6 +3,7 @@ package com.tangem.domain.markets import arrow.core.Either import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.markets.repositories.MarketsTokenRepository +import com.tangem.utils.SupportedLanguages class GetTokenMarketInfoUseCase( private val marketsTokenRepository: MarketsTokenRepository, @@ -13,6 +14,7 @@ class GetTokenMarketInfoUseCase( marketsTokenRepository.getTokenInfo( fiatCurrencyCode = appCurrency.code, tokenId = tokenId, + languageCode = SupportedLanguages.getCurrentSupportedLanguageCode(), ) }.mapLeft {} } diff --git a/domain/markets/src/main/java/com/tangem/domain/markets/repositories/MarketsTokenRepository.kt b/domain/markets/src/main/java/com/tangem/domain/markets/repositories/MarketsTokenRepository.kt index 0fd36a20fa..e6e3a23e03 100644 --- a/domain/markets/src/main/java/com/tangem/domain/markets/repositories/MarketsTokenRepository.kt +++ b/domain/markets/src/main/java/com/tangem/domain/markets/repositories/MarketsTokenRepository.kt @@ -12,5 +12,5 @@ interface MarketsTokenRepository { suspend fun getChart(fiatCurrencyCode: String, interval: PriceChangeInterval, tokenId: String): TokenChart - suspend fun getTokenInfo(fiatCurrencyCode: String, tokenId: String): TokenMarketInfo + suspend fun getTokenInfo(fiatCurrencyCode: String, tokenId: String, languageCode: String): TokenMarketInfo } \ No newline at end of file diff --git a/features/markets/impl/build.gradle.kts b/features/markets/impl/build.gradle.kts index f2becec495..7d8b7d0667 100644 --- a/features/markets/impl/build.gradle.kts +++ b/features/markets/impl/build.gradle.kts @@ -14,6 +14,7 @@ android { dependencies { /* Project - API */ api(projects.features.markets.api) + implementation(projects.core.navigation) /* Domain */ implementation(projects.domain.markets) diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/MarketsTokenDetailsModel.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/MarketsTokenDetailsModel.kt index f1814f03df..2359614915 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/MarketsTokenDetailsModel.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/MarketsTokenDetailsModel.kt @@ -5,6 +5,9 @@ import arrow.core.getOrElse import com.tangem.common.ui.charts.state.* import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.core.navigation.url.UrlOpener +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent import com.tangem.core.ui.components.marketprice.PriceChangeType import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.wrappedList @@ -13,11 +16,16 @@ import com.tangem.core.ui.utils.DateTimeFormatters import com.tangem.core.ui.utils.toTimeFormat import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.markets.GetTokenMarketInfoUseCase import com.tangem.domain.markets.GetTokenPriceChartUseCase import com.tangem.domain.markets.PriceChangeInterval import com.tangem.features.markets.details.api.MarketsTokenDetailsComponent -import com.tangem.features.markets.details.impl.ui.entity.MarketsTokenDetailsUM +import com.tangem.features.markets.details.impl.model.converters.DescriptionConverter +import com.tangem.features.markets.details.impl.model.converters.TokenMarketInfoConverter +import com.tangem.features.markets.details.impl.ui.state.InfoBottomSheetContent +import com.tangem.features.markets.details.impl.ui.state.MarketsTokenDetailsUM import com.tangem.features.markets.impl.R +import com.tangem.utils.Provider import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.JobHolder import com.tangem.utils.coroutines.saveIn @@ -28,12 +36,15 @@ import java.math.BigDecimal import java.math.RoundingMode import javax.inject.Inject +@Suppress("LargeClass") @Stable internal class MarketsTokenDetailsModel @Inject constructor( paramsContainer: ParamsContainer, override val dispatchers: CoroutineDispatcherProvider, getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val getTokenPriceChartUseCase: GetTokenPriceChartUseCase, + private val getTokenMarketInfoUseCase: GetTokenMarketInfoUseCase, + private val urlOpener: UrlOpener, ) : Model() { val params = paramsContainer.require() @@ -47,6 +58,21 @@ internal class MarketsTokenDetailsModel @Inject constructor( initialValue = params.appCurrency, ) + private val infoConverter = TokenMarketInfoConverter( + appCurrency = Provider { currentAppCurrency.value }, + onInfoClick = { + showInfoBottomSheet(it) + }, + onLinkClick = { + urlOpener.openUrl(it.url) + }, + ) + private val descriptionConverter = DescriptionConverter( + onReadModeClicked = { + showInfoBottomSheet(it) + }, + ) + private val chartDataProducer = MarketChartDataProducer.build(dispatcher = dispatchers.default) { chartData = MarketChartData.NoData.Loading @@ -89,19 +115,39 @@ internal class MarketsTokenDetailsModel @Inject constructor( chartState = MarketsTokenDetailsUM.ChartState( dataProducer = chartDataProducer, chartLook = MarketChartLook(), - onLoadRetryClick = {}, + onLoadRetryClick = ::onLoadRetryClicked, status = MarketsTokenDetailsUM.ChartState.Status.LOADING, onMarkerPointSelected = ::onMarkerPointSelected, ), selectedInterval = PriceChangeInterval.H24, onSelectedIntervalChange = ::onSelectedIntervalChange, + body = MarketsTokenDetailsUM.Body.Loading, + infoBottomSheet = TangemBottomSheetConfig( + isShow = false, + onDismissRequest = {}, + content = TangemBottomSheetConfigContent.Empty, + ), ), ) private val loadChartJobHolder = JobHolder() init { - loadChart(PriceChangeInterval.H24) + // reload screen if currency changed + modelScope.launch { + currentAppCurrency + .filter { it != params.appCurrency } + .collectLatest { + initialLoad() + } + } + + initialLoad() + } + + private fun initialLoad() { + loadChart(state.value.selectedInterval) + loadInfo() } private fun onSelectedIntervalChange(interval: PriceChangeInterval) { @@ -175,12 +221,60 @@ internal class MarketsTokenDetailsModel @Inject constructor( chartState = it.chartState.copy( status = MarketsTokenDetailsUM.ChartState.Status.ERROR, ), + body = if (it.body is MarketsTokenDetailsUM.Body.Error) { + MarketsTokenDetailsUM.Body.Nothing + } else { + it.body + }, ) } } }.saveIn(loadChartJobHolder) } + private fun loadInfo() { + state.update { + it.copy( + body = MarketsTokenDetailsUM.Body.Loading, + ) + } + + modelScope.launch { + val tokenMarketInfo = getTokenMarketInfoUseCase( + appCurrency = currentAppCurrency.value, + tokenId = params.token.id, + ) + + tokenMarketInfo.fold( + ifRight = { result -> + state.update { + it.copy( + body = MarketsTokenDetailsUM.Body.Content( + description = descriptionConverter.convert(result), + infoBlocks = infoConverter.convert(result), + ), + ) + } + }, + ifLeft = { + state.update { + if (it.chartState.status == MarketsTokenDetailsUM.ChartState.Status.DATA) { + it.copy( + body = MarketsTokenDetailsUM.Body.Error( + onLoadRetryClick = ::onLoadRetryClicked, + ), + ) + } else { + it.copy( + body = MarketsTokenDetailsUM.Body.Nothing, + ) + } + } + }, + ) + } + } + private fun getFormatterByInterval(interval: PriceChangeInterval): (BigDecimal) -> String { return when (interval) { PriceChangeInterval.H24 -> { value: BigDecimal -> @@ -251,4 +345,40 @@ internal class MarketsTokenDetailsModel @Inject constructor( MarketChartLook.Type.Falling } } + + private fun showInfoBottomSheet(content: InfoBottomSheetContent) { + state.update { stateToUpdate -> + stateToUpdate.copy( + infoBottomSheet = stateToUpdate.infoBottomSheet.copy( + isShow = true, + onDismissRequest = ::hideInfoBottomSheet, + content = content, + ), + ) + } + } + + private fun hideInfoBottomSheet() { + state.update { stateToUpdate -> + stateToUpdate.copy( + infoBottomSheet = stateToUpdate.infoBottomSheet.copy( + isShow = false, + ), + ) + } + } + + private fun onLoadRetryClicked() { + val currentState = state.value + + if (currentState.chartState.status == MarketsTokenDetailsUM.ChartState.Status.ERROR) { + loadChart(currentState.selectedInterval) + } + + if (currentState.body is MarketsTokenDetailsUM.Body.Error || + currentState.body is MarketsTokenDetailsUM.Body.Nothing + ) { + loadInfo() + } + } } \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/DescriptionConverter.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/DescriptionConverter.kt new file mode 100644 index 0000000000..40d76daa99 --- /dev/null +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/DescriptionConverter.kt @@ -0,0 +1,41 @@ +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.extensions.wrappedList +import com.tangem.domain.markets.TokenMarketInfo +import com.tangem.features.markets.details.impl.ui.state.InfoBottomSheetContent +import com.tangem.features.markets.details.impl.ui.state.MarketsTokenDetailsUM +import com.tangem.features.markets.impl.R +import com.tangem.utils.converter.Converter + +@Stable +internal class DescriptionConverter( + val onReadModeClicked: (InfoBottomSheetContent) -> Unit, +) : Converter { + + override fun convert(value: TokenMarketInfo): MarketsTokenDetailsUM.Description? { + return value.shortDescription?.let { desc -> + MarketsTokenDetailsUM.Description( + shortDescription = stringReference(desc), + fullDescription = value.fullDescription?.let { fullDescription -> + stringReference(fullDescription) + }, + onReadMoreClick = { + onReadModeClicked( + InfoBottomSheetContent( + title = resourceReference( + R.string.markets_token_details_about_token_title, + wrappedList( + value.name, + ), + ), + body = stringReference(value.fullDescription ?: ""), + ), + ) + }, + ) + } + } +} \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/InsightsConverter.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/InsightsConverter.kt new file mode 100644 index 0000000000..e90f915acd --- /dev/null +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/InsightsConverter.kt @@ -0,0 +1,131 @@ +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.utils.BigDecimalFormatter +import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.markets.TokenMarketInfo +import com.tangem.features.markets.details.impl.ui.state.InfoBottomSheetContent +import com.tangem.features.markets.details.impl.ui.state.InfoPointUM +import com.tangem.features.markets.details.impl.ui.state.InsightsUM +import com.tangem.features.markets.impl.R +import com.tangem.utils.Provider +import com.tangem.utils.StringsSigns +import com.tangem.utils.converter.Converter +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf +import java.math.BigDecimal + +@Stable +internal class InsightsConverter( + private val appCurrency: Provider, + private val onInfoClick: (InfoBottomSheetContent) -> Unit, +) : Converter { + + override fun convert(value: TokenMarketInfo.Insights): InsightsUM { + return with(value) { + InsightsUM( + h24Info = createInfoPointList( + experiencedBuyerChange = experiencedBuyerChange?.day, + holdersChange = holdersChange?.day, + liquidityChange = liquidityChange?.day, + buyPressureChange = buyPressureChange?.day, + ), + weekInfo = createInfoPointList( + experiencedBuyerChange = experiencedBuyerChange?.week, + holdersChange = holdersChange?.week, + liquidityChange = liquidityChange?.week, + buyPressureChange = buyPressureChange?.week, + ), + monthInfo = createInfoPointList( + experiencedBuyerChange = experiencedBuyerChange?.month, + holdersChange = holdersChange?.month, + liquidityChange = liquidityChange?.month, + buyPressureChange = buyPressureChange?.month, + ), + ) + } + } + + private fun createInfoPointList( + experiencedBuyerChange: BigDecimal?, + holdersChange: BigDecimal?, + liquidityChange: BigDecimal?, + buyPressureChange: BigDecimal?, + ): ImmutableList { + return persistentListOf( + InfoPointUM( + title = resourceReference(R.string.markets_token_details_experienced_buyers), + value = experiencedBuyerChange.convertChange(), + onInfoClick = { + onInfoClick( + InfoBottomSheetContent( + title = resourceReference(R.string.markets_token_details_experienced_buyers), + body = resourceReference(R.string.markets_token_details_experienced_buyers_description), + ), + ) + }, + ), + InfoPointUM( + title = resourceReference(R.string.markets_token_details_buy_pressure), + value = buyPressureChange.convertChange(isFiatValue = true), + onInfoClick = { + onInfoClick( + InfoBottomSheetContent( + title = resourceReference(R.string.markets_token_details_buy_pressure), + body = resourceReference(R.string.markets_token_details_buy_pressure_description), + ), + ) + }, + ), + InfoPointUM( + title = resourceReference(R.string.markets_token_details_holders), + value = holdersChange.convertChange(), + onInfoClick = { + onInfoClick( + InfoBottomSheetContent( + title = resourceReference(R.string.markets_token_details_holders), + body = resourceReference(R.string.markets_token_details_holders_description), + ), + ) + }, + ), + InfoPointUM( + title = resourceReference(R.string.markets_token_details_liquidity), + value = liquidityChange.convertChange(), + onInfoClick = { + onInfoClick( + InfoBottomSheetContent( + title = resourceReference(R.string.markets_token_details_liquidity), + body = resourceReference(R.string.markets_token_details_liquidity_description), + ), + ) + }, + ), + ) + } + + private fun BigDecimal?.convertChange(isFiatValue: Boolean = false): String { + if (this == null) return StringsSigns.DASH_SIGN + + val value = if (isFiatValue) { + val currency = appCurrency() + BigDecimalFormatter.formatCompactFiatAmount( + amount = this.abs(), + fiatCurrencyCode = currency.code, + fiatCurrencySymbol = currency.symbol, + ) + } else { + BigDecimalFormatter.formatCompactAmount(amount = this.abs()) + } + + val spacing = if (isFiatValue) " " else "" + + return when { + this > BigDecimal.ZERO -> StringsSigns.PLUS + spacing + value + this < BigDecimal.ZERO -> StringsSigns.MINUS + spacing + value + this == BigDecimal.ZERO -> value + else -> StringsSigns.DASH_SIGN + } + } +} \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/LinksConverter.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/LinksConverter.kt new file mode 100644 index 0000000000..1b571e168f --- /dev/null +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/LinksConverter.kt @@ -0,0 +1,49 @@ +package com.tangem.features.markets.details.impl.model.converters + +import androidx.compose.runtime.Stable +import com.tangem.core.ui.extensions.stringReference +import com.tangem.domain.markets.TokenMarketInfo +import com.tangem.features.markets.details.impl.ui.state.LinksUM +import com.tangem.features.markets.impl.R +import com.tangem.utils.converter.Converter +import kotlinx.collections.immutable.toImmutableList + +@Stable +internal class LinksConverter( + private val onLinkClick: (LinksUM.Link) -> Unit, +) : Converter { + + override fun convert(value: TokenMarketInfo.Links): LinksUM { + return LinksUM( + officialLinks = value.officialLinks?.map { it.convert() }.orEmpty().toImmutableList(), + social = value.social?.map { it.convert() }.orEmpty().toImmutableList(), + repository = value.repository?.map { it.convert() }.orEmpty().toImmutableList(), + blockchainSite = value.blockchainSite?.map { it.convert() }.orEmpty().toImmutableList(), + onLinkClick = onLinkClick, + ) + } + + private fun TokenMarketInfo.Link.convert(): LinksUM.Link { + return LinksUM.Link( + title = stringReference(title), + iconRes = getIconById(id), + url = link, + ) + } + + private fun getIconById(id: String?): Int { + return when (id) { + "linkedin" -> R.drawable.ic_linkedin_24 + "discord" -> R.drawable.ic_discord_24 + "youtube" -> R.drawable.ic_youtube_24 + "telegram" -> R.drawable.ic_telegram_24 + "github" -> R.drawable.ic_github_24 + "twitter" -> R.drawable.ic_twitter_24 + "facebook" -> R.drawable.ic_facebook_24 + "reddit" -> R.drawable.ic_reddit_24 + "instagram" -> R.drawable.ic_instagram_24 + "whitepaper" -> R.drawable.ic_doc_24 + else -> R.drawable.ic_arrow_top_right_24 + } + } +} \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/MetricsConverter.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/MetricsConverter.kt new file mode 100644 index 0000000000..4bf9f1e111 --- /dev/null +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/MetricsConverter.kt @@ -0,0 +1,135 @@ +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.utils.BigDecimalFormatter +import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.markets.TokenMarketInfo +import com.tangem.features.markets.details.impl.ui.state.InfoBottomSheetContent +import com.tangem.features.markets.details.impl.ui.state.InfoPointUM +import com.tangem.features.markets.details.impl.ui.state.MetricsUM +import com.tangem.features.markets.impl.R +import com.tangem.utils.Provider +import com.tangem.utils.StringsSigns +import com.tangem.utils.converter.Converter +import kotlinx.collections.immutable.persistentListOf +import java.math.BigDecimal +import java.math.RoundingMode +import java.text.NumberFormat +import java.util.Locale + +@Stable +internal class MetricsConverter( + private val appCurrency: Provider, + private val onInfoClick: (InfoBottomSheetContent) -> Unit, +) : Converter { + + @Suppress("LongMethod") + override fun convert(value: TokenMarketInfo.Metrics): MetricsUM { + return with(value) { + MetricsUM( + metrics = persistentListOf( + InfoPointUM( + title = resourceReference(R.string.markets_token_details_market_capitalization), + value = marketCap.formatAmount(), + onInfoClick = { + onInfoClick( + InfoBottomSheetContent( + title = resourceReference(R.string.markets_token_details_market_capitalization), + body = resourceReference( + R.string.markets_token_details_market_capitalization_description, + ), + ), + ) + }, + ), + InfoPointUM( + title = resourceReference(R.string.markets_token_details_market_rating), + value = marketRating?.toString() ?: StringsSigns.DASH_SIGN, + onInfoClick = { + onInfoClick( + InfoBottomSheetContent( + title = resourceReference(R.string.markets_token_details_market_rating), + body = resourceReference(R.string.markets_token_details_market_rating_description), + ), + ) + }, + ), + InfoPointUM( + title = resourceReference(R.string.markets_token_details_trading_volume), + value = volume24h.formatAmount(), + onInfoClick = { + onInfoClick( + InfoBottomSheetContent( + title = resourceReference(R.string.markets_token_details_trading_volume), + body = resourceReference( + R.string.markets_token_details_trading_volume_24h_description, + ), + ), + ) + }, + ), + InfoPointUM( + title = resourceReference(R.string.markets_token_details_fully_diluted_valuation), + value = fullyDilutedValuation.formatAmount(), + onInfoClick = { + onInfoClick( + InfoBottomSheetContent( + title = resourceReference(R.string.markets_token_details_fully_diluted_valuation), + body = resourceReference( + R.string.markets_token_details_fully_diluted_valuation_description, + ), + ), + ) + }, + ), + InfoPointUM( + title = resourceReference(R.string.markets_token_details_circulating_supply), + value = circulatingSupply.formatAmount(crypto = true), + onInfoClick = { + onInfoClick( + InfoBottomSheetContent( + title = resourceReference(R.string.markets_token_details_circulating_supply), + body = resourceReference( + R.string.markets_token_details_circulating_supply_description, + ), + ), + ) + }, + ), + InfoPointUM( + title = resourceReference(R.string.markets_token_details_total_supply), + value = totalSupply.formatAmount(crypto = true), + onInfoClick = { + onInfoClick( + InfoBottomSheetContent( + title = resourceReference(R.string.markets_token_details_total_supply), + body = resourceReference(R.string.markets_token_details_total_supply_description), + ), + ) + }, + ), + ), + ) + } + } + + private fun BigDecimal?.formatAmount(crypto: Boolean = false): String { + return if (crypto) { + val formatter = NumberFormat.getNumberInstance(Locale.getDefault()).apply { + maximumFractionDigits = 0 + isGroupingUsed = true + roundingMode = RoundingMode.HALF_UP + } + formatter.format(this) + } else { + val currency = appCurrency() + BigDecimalFormatter.formatFiatAmount( + fiatAmount = this, + fiatCurrencyCode = currency.code, + fiatCurrencySymbol = currency.symbol, + decimals = 0, + ) + } + } +} \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/PricePerformanceConverter.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/PricePerformanceConverter.kt new file mode 100644 index 0000000000..b08b518ba0 --- /dev/null +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/PricePerformanceConverter.kt @@ -0,0 +1,58 @@ +package com.tangem.features.markets.details.impl.model.converters + +import androidx.compose.runtime.Stable +import com.tangem.core.ui.utils.BigDecimalFormatter +import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.markets.TokenMarketInfo +import com.tangem.features.markets.details.impl.ui.state.PricePerformanceUM +import com.tangem.utils.Provider +import com.tangem.utils.StringsSigns +import com.tangem.utils.converter.Converter +import java.math.BigDecimal +import java.math.RoundingMode + +@Stable +internal class PricePerformanceConverter( + private val appCurrency: Provider, +) : Converter { + + override fun convert(value: TokenMarketInfo.PricePerformance): PricePerformanceUM { + return PricePerformanceUM( + h24 = value.day.convert(), + month = value.month.convert(), + all = value.allTime.convert(), + ) + } + + private fun TokenMarketInfo.Range?.convert(): PricePerformanceUM.Value { + if (this == null) { + return PricePerformanceUM.Value( + low = StringsSigns.DASH_SIGN, + high = StringsSigns.DASH_SIGN, + indicatorFraction = 0f, + ) + } + + return PricePerformanceUM.Value( + low = low.convert(), + high = high.convert(), + indicatorFraction = calculateFraction(), + ) + } + + private fun BigDecimal?.convert(): String { + val currency = appCurrency() + return BigDecimalFormatter.formatCompactFiatAmount( + amount = this, + fiatCurrencyCode = currency.code, + fiatCurrencySymbol = currency.symbol, + ) + } + + private fun TokenMarketInfo.Range.calculateFraction(): Float { + if (low == null || high == null || low == BigDecimal.ZERO) return 0f + return (high!! - low!!).divide(low!!, RoundingMode.HALF_UP) + .setScale(2, RoundingMode.HALF_UP) + .toFloat().coerceAtMost(1f) + } +} \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/SecurityScoreConverter.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/SecurityScoreConverter.kt new file mode 100644 index 0000000000..302aeb7258 --- /dev/null +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/SecurityScoreConverter.kt @@ -0,0 +1,35 @@ +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.features.markets.details.impl.ui.state.InfoBottomSheetContent +import com.tangem.features.markets.details.impl.ui.state.SecurityScoreUM +import com.tangem.features.markets.impl.R +import com.tangem.utils.converter.Converter + +// TODO implement when backend is ready +@Stable +internal class SecurityScoreConverter( + private val onInfoClick: (InfoBottomSheetContent) -> Unit, +) : Converter { + + override fun convert(value: Unit): SecurityScoreUM { + return with(value) { + SecurityScoreUM( + score = 4.7f, + description = "Based on 3 ratings", + onInfoClick = { + onInfoClick( + InfoBottomSheetContent( + title = resourceReference(R.string.markets_token_details_security_score), + body = stringReference("markets_token_details_security_score_description"), + // FIXME + // resourceReference(R.string.markets_token_details_security_score_description) + ), + ) + }, + ) + } + } +} \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/TokenMarketInfoConverter.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/TokenMarketInfoConverter.kt new file mode 100644 index 0000000000..f03cbff9c4 --- /dev/null +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/TokenMarketInfoConverter.kt @@ -0,0 +1,34 @@ +package com.tangem.features.markets.details.impl.model.converters + +import androidx.compose.runtime.Stable +import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.markets.TokenMarketInfo +import com.tangem.features.markets.details.impl.ui.state.InfoBottomSheetContent +import com.tangem.features.markets.details.impl.ui.state.LinksUM +import com.tangem.features.markets.details.impl.ui.state.MarketsTokenDetailsUM +import com.tangem.utils.Provider +import com.tangem.utils.converter.Converter + +@Stable +internal class TokenMarketInfoConverter( + appCurrency: Provider, + onInfoClick: (InfoBottomSheetContent) -> Unit, + onLinkClick: (LinksUM.Link) -> Unit, +) : Converter { + + private val insightsConverter = InsightsConverter(appCurrency = appCurrency, onInfoClick = onInfoClick) + private val securityScoreConverter = SecurityScoreConverter(onInfoClick = onInfoClick) + private val metricsConverter = MetricsConverter(appCurrency = appCurrency, onInfoClick = onInfoClick) + private val pricePerformanceConverter = PricePerformanceConverter(appCurrency = appCurrency) + private val linksConverter = LinksConverter(onLinkClick = onLinkClick) + + override fun convert(value: TokenMarketInfo): MarketsTokenDetailsUM.InformationBlocks { + return MarketsTokenDetailsUM.InformationBlocks( + insights = value.insights?.let { insightsConverter.convert(it) }, + securityScore = securityScoreConverter.convert(Unit), + metrics = value.metrics?.let { metricsConverter.convert(it) }, + pricePerformance = value.pricePerformance?.let { pricePerformanceConverter.convert(it) }, + links = value.links?.let { linksConverter.convert(it) }, + ) + } +} \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/MarketsTokenDetailsContent.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/MarketsTokenDetailsContent.kt index ead8e056f4..7f8dfd5def 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/MarketsTokenDetailsContent.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/MarketsTokenDetailsContent.kt @@ -1,6 +1,8 @@ package com.tangem.features.markets.details.impl.ui +import androidx.compose.foundation.background import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -12,12 +14,11 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import com.tangem.common.ui.charts.state.MarketChartDataProducer import com.tangem.common.ui.charts.state.MarketChartLook -import com.tangem.core.ui.components.SpacerH16 -import com.tangem.core.ui.components.SpacerH32 -import com.tangem.core.ui.components.SpacerH4 -import com.tangem.core.ui.components.SpacerW4 +import com.tangem.core.ui.components.* import com.tangem.core.ui.components.appbar.TangemTopAppBar import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent import com.tangem.core.ui.components.buttons.segmentedbutton.SegmentedButtons import com.tangem.core.ui.components.currency.icon.CoinIcon import com.tangem.core.ui.components.marketprice.PriceChangeInPercent @@ -29,9 +30,12 @@ import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.res.LocalMainBottomSheetColor import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.core.ui.utils.disableNestedScroll import com.tangem.domain.markets.PriceChangeInterval +import com.tangem.features.markets.details.impl.ui.components.InfoBottomSheet import com.tangem.features.markets.details.impl.ui.components.MarketTokenDetailsChart -import com.tangem.features.markets.details.impl.ui.entity.MarketsTokenDetailsUM +import com.tangem.features.markets.details.impl.ui.components.tokenMarketDetailsBody +import com.tangem.features.markets.details.impl.ui.state.MarketsTokenDetailsUM import com.tangem.features.markets.impl.R import kotlinx.collections.immutable.persistentListOf @@ -44,11 +48,13 @@ internal fun MarketsTokenDetailsContent( modifier: Modifier = Modifier, ) { Content( + modifier = modifier, state = state, onBackClick = onBackClick, onHeaderSizeChange = onHeaderSizeChange, - modifier = modifier, ) + + InfoBottomSheet(config = state.infoBottomSheet) } @Suppress("UnusedPrivateMember") @@ -61,6 +67,7 @@ private fun Content( ) { val backgroundColor = LocalMainBottomSheetColor.current.value val density = LocalDensity.current + val bottomBarHeight = with(density) { WindowInsets.systemBars.getBottom(this).toDp() } Column( modifier = modifier @@ -78,31 +85,46 @@ private fun Content( title = state.tokenName, startButton = TopAppBarButtonUM.Back(onBackClick), ) + SpacerH4() - Header( - state = state, - modifier = Modifier - .padding(horizontal = TangemTheme.dimens.spacing16) - .fillMaxWidth(), - ) + LazyColumn( + modifier = Modifier.disableNestedScroll(), + contentPadding = PaddingValues(bottom = bottomBarHeight), + ) { + item("header") { + Header( + state = state, + modifier = Modifier + .padding(horizontal = TangemTheme.dimens.spacing16) + .fillMaxWidth(), + ) + } + item { SpacerH16() } + item("intervalSelector") { + IntervalSelector( + trendInterval = state.selectedInterval, + onIntervalClick = state.onSelectedIntervalChange, + modifier = Modifier + .padding(horizontal = TangemTheme.dimens.spacing16) + .fillMaxWidth(), + ) + } + item { SpacerH32() } + item( + contentType = "chart", + ) { + MarketTokenDetailsChart( + modifier = Modifier.fillMaxWidth(), + state = state.chartState, + ) + } + item { SpacerH16() } - SpacerH16() - - IntervalSelector( - trendInterval = state.selectedInterval, - onIntervalClick = state.onSelectedIntervalChange, - modifier = Modifier - .padding(horizontal = TangemTheme.dimens.spacing16) - .fillMaxWidth(), - ) - - SpacerH32() - - MarketTokenDetailsChart( - modifier = Modifier.fillMaxWidth(), - state = state.chartState, - ) + tokenMarketDetailsBody( + state = state.body, + ) + } } } @@ -199,6 +221,7 @@ fun PriceChangeInterval.getText(): TextReference { private fun Preview() { TangemThemePreview { Content( + modifier = Modifier.background(TangemTheme.colors.background.tertiary), state = MarketsTokenDetailsUM( tokenName = "Token Name", priceText = "Price", @@ -215,6 +238,12 @@ private fun Preview() { ), selectedInterval = PriceChangeInterval.H24, onSelectedIntervalChange = { }, + body = MarketsTokenDetailsUM.Body.Loading, + infoBottomSheet = TangemBottomSheetConfig( + isShow = false, + onDismissRequest = {}, + content = TangemBottomSheetConfigContent.Empty, + ), ), onHeaderSizeChange = {}, onBackClick = {}, diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/Description.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/Description.kt new file mode 100644 index 0000000000..e895b421f5 --- /dev/null +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/Description.kt @@ -0,0 +1,112 @@ +package com.tangem.features.markets.details.impl.ui.components + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.text.ClickableText +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.withStyle +import androidx.compose.ui.tooling.preview.Preview +import com.tangem.core.ui.components.TextShimmer +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.core.ui.utils.PreviewShimmerContainer +import com.tangem.features.markets.impl.R + +@Composable +internal fun Description( + description: TextReference, + hasFullDescription: Boolean, + onReadMoreClick: () -> Unit, + modifier: Modifier = Modifier, +) { + if (hasFullDescription) { + val text = buildAnnotatedString { + withStyle(SpanStyle(color = TangemTheme.colors.text.secondary)) { + append(description.resolveReference()) + } + withStyle(SpanStyle(color = TangemTheme.colors.text.accent)) { + append(" " + stringResource(R.string.common_read_more)) + } + } + + ClickableText( + modifier = modifier, + text = text, + style = TangemTheme.typography.body2, + ) { + text.spanStyles.getOrNull(1)?.let { spanStyle -> + if (it in spanStyle.start..spanStyle.end) { + onReadMoreClick() + } + } + } + } else { + Text( + modifier = modifier, + text = description.resolveReference(), + style = TangemTheme.typography.body2, + color = TangemTheme.colors.text.secondary, + ) + } +} + +@Composable +internal fun DescriptionPlaceholder(modifier: Modifier = Modifier) { + Column( + modifier = modifier, + ) { + TextShimmer( + modifier = Modifier.fillMaxWidth(), + style = TangemTheme.typography.body2, + textSizeHeight = true, + ) + TextShimmer( + modifier = Modifier.fillMaxWidth(), + style = TangemTheme.typography.body2, + textSizeHeight = true, + ) + TextShimmer( + modifier = Modifier.fillMaxWidth(fraction = 0.8f), + style = TangemTheme.typography.body2, + textSizeHeight = true, + ) + } +} + +@Preview +@Composable +private fun ContentPreview() { + TangemThemePreview { + Description( + description = stringReference( + "XRP (XRP) is a cryptocurrency launched in January 2009, where the first " + + "genesis block was mined on 9th January 2009", + ), + hasFullDescription = true, + onReadMoreClick = {}, + ) + } +} + +@Preview +@Composable +private fun PreviewPlaceholder() { + TangemThemePreview { + PreviewShimmerContainer( + actualContent = { + ContentPreview() + }, + shimmerContent = { + DescriptionPlaceholder() + }, + ) + } +} \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/InfoBottomSheet.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/InfoBottomSheet.kt new file mode 100644 index 0000000000..0d44a41f91 --- /dev/null +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/InfoBottomSheet.kt @@ -0,0 +1,47 @@ +package com.tangem.features.markets.details.impl.ui.components + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.systemBars +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalDensity +import com.tangem.core.ui.components.SpacerH +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetTitle +import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.res.TangemTheme +import com.tangem.features.markets.details.impl.ui.state.InfoBottomSheetContent + +@Composable +internal fun InfoBottomSheet(config: TangemBottomSheetConfig) { + val bottomBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getBottom(this).toDp() } + + TangemBottomSheet( + config = config, + skipPartiallyExpanded = false, + addBottomInsets = false, + title = { + TangemBottomSheetTitle(title = it.title) + }, + content = { + Column( + modifier = Modifier + .verticalScroll(rememberScrollState()) + .padding(horizontal = TangemTheme.dimens.spacing28), + ) { + Text( + text = it.body.resolveReference(), + style = TangemTheme.typography.body2, + color = TangemTheme.colors.text.secondary, + ) + SpacerH(bottomBarHeight) + } + }, + ) +} \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/InfoPoint.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/InfoPoint.kt index 5be2fb74a5..09d8317df5 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/InfoPoint.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/InfoPoint.kt @@ -1,7 +1,6 @@ package com.tangem.features.markets.details.impl.ui.components import android.content.res.Configuration -import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.material3.Text @@ -15,11 +14,10 @@ import com.tangem.core.ui.components.TextShimmer import com.tangem.core.ui.components.text.TooltipText import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.extensions.stringReference -import com.tangem.core.ui.res.TangemAnimations import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.utils.PreviewShimmerContainer -import com.tangem.features.markets.details.impl.ui.entity.InfoPointUM +import com.tangem.features.markets.details.impl.ui.state.InfoPointUM @Composable internal fun InfoPoint(infoPointUM: InfoPointUM, modifier: Modifier = Modifier) { @@ -42,18 +40,11 @@ internal fun InfoPoint(infoPointUM: InfoPointUM, modifier: Modifier = Modifier) overflow = TextOverflow.Ellipsis, ) } - AnimatedContent( - modifier = Modifier, - targetState = infoPointUM.value, - transitionSpec = { TangemAnimations.transitionSpecs.textChange }, - label = "insight block", - ) { - Text( - text = it, - style = TangemTheme.typography.body1, - color = TangemTheme.colors.text.primary1, - ) - } + Text( + text = infoPointUM.value, + style = TangemTheme.typography.body1, + color = TangemTheme.colors.text.primary1, + ) } } diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/InsightsBlock.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/InsightsBlock.kt index a8cd4bf110..3cd88bc115 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/InsightsBlock.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/InsightsBlock.kt @@ -20,8 +20,8 @@ import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.utils.PreviewShimmerContainer import com.tangem.domain.markets.PriceChangeInterval -import com.tangem.features.markets.details.impl.ui.entity.InfoPointUM -import com.tangem.features.markets.details.impl.ui.entity.InsightsUM +import com.tangem.features.markets.details.impl.ui.state.InfoPointUM +import com.tangem.features.markets.details.impl.ui.state.InsightsUM import com.tangem.features.markets.details.impl.ui.getText import com.tangem.features.markets.impl.R import kotlinx.collections.immutable.persistentListOf diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/LinksBlock.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/LinksBlock.kt index 2d968b1327..ca7e4d4390 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/LinksBlock.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/LinksBlock.kt @@ -9,7 +9,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.tangem.common.ui.charts.downsample.fastForEach +import androidx.compose.ui.util.fastForEach import com.tangem.core.ui.components.SmallButtonShimmer import com.tangem.core.ui.components.TextShimmer import com.tangem.core.ui.components.block.information.InformationBlock @@ -21,7 +21,7 @@ import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.utils.PreviewShimmerContainer -import com.tangem.features.markets.details.impl.ui.entity.LinksUM +import com.tangem.features.markets.details.impl.ui.state.LinksUM import com.tangem.features.markets.impl.R import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf @@ -69,8 +69,8 @@ internal fun LinksBlock(state: LinksUM, modifier: Modifier = Modifier) { @OptIn(ExperimentalLayoutApi::class) @Composable private fun SubBlock( - links: ImmutableList, - onLinkClick: (LinksUM.LinkUM) -> Unit, + links: ImmutableList, + onLinkClick: (LinksUM.Link) -> Unit, modifier: Modifier = Modifier, lastBlock: Boolean = false, title: String = "Official links", @@ -166,36 +166,36 @@ private fun ContentPreview() { LinksBlock( state = LinksUM( officialLinks = persistentListOf( - LinksUM.LinkUM( + LinksUM.Link( title = stringReference("Website"), iconRes = R.drawable.ic_plus_24, url = "https://tangem.com", ), - LinksUM.LinkUM( + LinksUM.Link( title = stringReference("Website"), iconRes = R.drawable.ic_plus_24, url = "https://tangem.com", ), - LinksUM.LinkUM( + LinksUM.Link( title = stringReference("Website"), iconRes = R.drawable.ic_plus_24, url = "https://tangem.com", ), ), social = persistentListOf( - LinksUM.LinkUM( + LinksUM.Link( title = stringReference("Twitter"), iconRes = R.drawable.ic_plus_24, url = "https://tangem.com", ), - LinksUM.LinkUM( + LinksUM.Link( title = stringReference("Facebook"), iconRes = R.drawable.ic_plus_24, url = "https://tangem.com", ), ), repository = persistentListOf( - LinksUM.LinkUM( + LinksUM.Link( title = stringReference("Github"), iconRes = R.drawable.ic_plus_24, url = "https://tangem.com", diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/MarketTokenDetailsChart.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/MarketTokenDetailsChart.kt index ee9ce1d5b3..b19876af06 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/MarketTokenDetailsChart.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/MarketTokenDetailsChart.kt @@ -10,11 +10,12 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawBehind import com.tangem.common.ui.charts.MarketChart +import com.tangem.common.ui.charts.getMarketChartBottomAxisHeight import com.tangem.common.ui.charts.state.MarketChartLook import com.tangem.common.ui.charts.state.rememberMarketChartState import com.tangem.core.ui.res.LocalMainBottomSheetColor import com.tangem.core.ui.res.TangemTheme -import com.tangem.features.markets.details.impl.ui.entity.MarketsTokenDetailsUM +import com.tangem.features.markets.details.impl.ui.state.MarketsTokenDetailsUM import com.tangem.features.markets.tokenlist.impl.ui.components.UnableToLoadData @Composable @@ -34,6 +35,7 @@ internal fun MarketTokenDetailsChart(state: MarketsTokenDetailsUM.ChartState, mo ) val backgroundColor = LocalMainBottomSheetColor.current.value + val bottomChartAxisHeight = getMarketChartBottomAxisHeight() Box(modifier) { MarketChart( @@ -45,7 +47,8 @@ internal fun MarketTokenDetailsChart(state: MarketsTokenDetailsUM.ChartState, mo Box( Modifier .drawBehind { drawRect(backgroundColor) } - .matchParentSize(), + .matchParentSize() + .padding(bottom = bottomChartAxisHeight), ) { when (state.status) { MarketsTokenDetailsUM.ChartState.Status.LOADING -> { diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/MetricsBlock.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/MetricsBlock.kt index 20308e8e72..4825c2ab32 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/MetricsBlock.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/MetricsBlock.kt @@ -17,8 +17,8 @@ import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.utils.PreviewShimmerContainer -import com.tangem.features.markets.details.impl.ui.entity.InfoPointUM -import com.tangem.features.markets.details.impl.ui.entity.MetricsUM +import com.tangem.features.markets.details.impl.ui.state.InfoPointUM +import com.tangem.features.markets.details.impl.ui.state.MetricsUM import com.tangem.features.markets.impl.R import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/PricePerformanceBlock.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/PricePerformanceBlock.kt index 799200dc99..cb3098ca60 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/PricePerformanceBlock.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/PricePerformanceBlock.kt @@ -26,7 +26,7 @@ import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.utils.PreviewShimmerContainer import com.tangem.domain.markets.PriceChangeInterval -import com.tangem.features.markets.details.impl.ui.entity.PricePerformanceUM +import com.tangem.features.markets.details.impl.ui.state.PricePerformanceUM import com.tangem.features.markets.details.impl.ui.getText import com.tangem.features.markets.impl.R import kotlinx.collections.immutable.persistentListOf @@ -127,30 +127,18 @@ private fun Content(state: PricePerformanceUM.Value, modifier: Modifier = Modifi modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, ) { - AnimatedContent( - targetState = state.low, - transitionSpec = { TangemAnimations.transitionSpecs.textChange }, - label = "Low price", - ) { - Text( - text = it, - style = TangemTheme.typography.body1, - color = TangemTheme.colors.text.primary1, - ) - } + Text( + text = state.low, + style = TangemTheme.typography.body1, + color = TangemTheme.colors.text.primary1, + ) SpacerW8() - AnimatedContent( - targetState = state.high, - transitionSpec = { TangemAnimations.transitionSpecs.textChange }, - label = "High price", - ) { - Text( - text = it, - style = TangemTheme.typography.body1, - color = TangemTheme.colors.text.primary1, - textAlign = TextAlign.End, - ) - } + Text( + text = state.high, + style = TangemTheme.typography.body1, + color = TangemTheme.colors.text.primary1, + textAlign = TextAlign.End, + ) } } } diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/SecurityScoreBlock.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/SecurityScoreBlock.kt index 319a08de34..66a75660ef 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/SecurityScoreBlock.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/SecurityScoreBlock.kt @@ -27,7 +27,7 @@ import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.utils.PreviewShimmerContainer -import com.tangem.features.markets.details.impl.ui.entity.SecurityScoreUM +import com.tangem.features.markets.details.impl.ui.state.SecurityScoreUM import com.tangem.features.markets.impl.R import kotlin.math.round diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/TokenMarketDetailsBody.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/TokenMarketDetailsBody.kt new file mode 100644 index 0000000000..40cc2fc2f5 --- /dev/null +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/TokenMarketDetailsBody.kt @@ -0,0 +1,142 @@ +package com.tangem.features.markets.details.impl.ui.components + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyListScope +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import com.tangem.core.ui.res.TangemTheme +import com.tangem.features.markets.details.impl.ui.state.MarketsTokenDetailsUM +import com.tangem.features.markets.tokenlist.impl.ui.components.UnableToLoadData + +internal fun LazyListScope.tokenMarketDetailsBody(state: MarketsTokenDetailsUM.Body) { + when (state) { + MarketsTokenDetailsUM.Body.Loading -> { + loading() + } + is MarketsTokenDetailsUM.Body.Content -> { + if (state.description != null) { + description(state.description) + } + + infoBlocksList(state.infoBlocks) + } + is MarketsTokenDetailsUM.Body.Error -> { + error(state) + } + MarketsTokenDetailsUM.Body.Nothing -> { + // Do nothing + } + } +} + +private fun LazyListScope.error(state: MarketsTokenDetailsUM.Body.Error) { + item("body-error") { + Box(Modifier.fillMaxWidth()) { + UnableToLoadData( + modifier = Modifier + .align(Alignment.Center) + .padding( + horizontal = TangemTheme.dimens.spacing16, + vertical = TangemTheme.dimens.spacing40, + ), + onRetryClick = state.onLoadRetryClick, + ) + } + } +} + +private fun LazyListScope.description(description: MarketsTokenDetailsUM.Description) { + item("description") { + Description( + modifier = Modifier.blockPaddings(), + description = description.shortDescription, + hasFullDescription = description.fullDescription != null, + onReadMoreClick = description.onReadMoreClick, + ) + } +} + +internal fun LazyListScope.infoBlocksList(state: MarketsTokenDetailsUM.InformationBlocks) { + if (state.insights != null) { + item("insights") { + InsightsBlock( + modifier = Modifier.blockPaddings(), + state = state.insights, + ) + } + } + + if (state.securityScore != null) { + item("securityScore") { + SecurityScoreBlock( + modifier = Modifier.blockPaddings(), + state = state.securityScore, + ) + } + } + + if (state.metrics != null) { + item("metrics") { + MetricsBlock( + modifier = Modifier.blockPaddings(), + state = state.metrics, + ) + } + } + + if (state.pricePerformance != null) { + item("pricePerformance") { + PricePerformanceBlock( + modifier = Modifier.blockPaddings(), + state = state.pricePerformance, + ) + } + } + + if (state.links != null) { + item("links") { + LinksBlock( + modifier = Modifier.blockPaddings(), + state = state.links, + ) + } + } +} + +private fun LazyListScope.loading() { + item("description-loading") { + DescriptionPlaceholder(modifier = Modifier.blockPaddings()) + } + + item("insights-loading") { + InsightsBlockPlaceholder(modifier = Modifier.blockPaddings()) + } + + item("securityScore-loading") { + SecurityScorePlaceHolder(modifier = Modifier.blockPaddings()) + } + + item("metrics-loading") { + MetricsBlockPlaceholder(modifier = Modifier.blockPaddings()) + } + + item("pricePerformance-loading") { + PricePerformanceBlockPlaceholder(modifier = Modifier.blockPaddings()) + } + + item("links-loading") { + LinksBlockPlaceholder(modifier = Modifier.blockPaddings()) + } +} + +@Composable +private fun Modifier.blockPaddings(): Modifier { + return this.padding( + start = TangemTheme.dimens.spacing16, + end = TangemTheme.dimens.spacing16, + bottom = TangemTheme.dimens.spacing12, + ) +} \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/entity/InsightsUM.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/entity/InsightsUM.kt deleted file mode 100644 index 9c240b9214..0000000000 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/entity/InsightsUM.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.tangem.features.markets.details.impl.ui.entity - -import androidx.compose.runtime.Immutable -import kotlinx.collections.immutable.PersistentList - -@Immutable -internal data class InsightsUM( - val h24Info: PersistentList, - val weekInfo: PersistentList, - val monthInfo: PersistentList, -) \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/entity/LinksUM.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/entity/LinksUM.kt deleted file mode 100644 index e22571424f..0000000000 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/entity/LinksUM.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.tangem.features.markets.details.impl.ui.entity - -import androidx.annotation.DrawableRes -import androidx.compose.runtime.Immutable -import com.tangem.core.ui.extensions.TextReference -import kotlinx.collections.immutable.PersistentList - -@Immutable -internal data class LinksUM( - val officialLinks: PersistentList, - val social: PersistentList, - val repository: PersistentList, - val blockchainSite: PersistentList, - val onLinkClick: (LinkUM) -> Unit, -) { - @Immutable - data class LinkUM( - @DrawableRes val iconRes: Int, - val title: TextReference, - val url: String, - ) -} \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/state/InfoBottomSheetContent.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/state/InfoBottomSheetContent.kt new file mode 100644 index 0000000000..858fc017c5 --- /dev/null +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/state/InfoBottomSheetContent.kt @@ -0,0 +1,9 @@ +package com.tangem.features.markets.details.impl.ui.state + +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent +import com.tangem.core.ui.extensions.TextReference + +internal data class InfoBottomSheetContent( + val title: TextReference, + val body: TextReference, +) : TangemBottomSheetConfigContent \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/entity/InfoPointUM.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/state/InfoPointUM.kt similarity index 62% rename from features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/entity/InfoPointUM.kt rename to features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/state/InfoPointUM.kt index 44816c5ab1..5ef1b1ad91 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/entity/InfoPointUM.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/state/InfoPointUM.kt @@ -1,9 +1,7 @@ -package com.tangem.features.markets.details.impl.ui.entity +package com.tangem.features.markets.details.impl.ui.state -import androidx.compose.runtime.Immutable import com.tangem.core.ui.extensions.TextReference -@Immutable internal data class InfoPointUM( val title: TextReference, val value: String, diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/state/InsightsUM.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/state/InsightsUM.kt new file mode 100644 index 0000000000..f7d33a8ddc --- /dev/null +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/state/InsightsUM.kt @@ -0,0 +1,9 @@ +package com.tangem.features.markets.details.impl.ui.state + +import kotlinx.collections.immutable.ImmutableList + +internal data class InsightsUM( + val h24Info: ImmutableList, + val weekInfo: ImmutableList, + val monthInfo: ImmutableList, +) \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/state/LinksUM.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/state/LinksUM.kt new file mode 100644 index 0000000000..d1a6d13860 --- /dev/null +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/state/LinksUM.kt @@ -0,0 +1,19 @@ +package com.tangem.features.markets.details.impl.ui.state + +import androidx.annotation.DrawableRes +import com.tangem.core.ui.extensions.TextReference +import kotlinx.collections.immutable.ImmutableList + +internal data class LinksUM( + val officialLinks: ImmutableList, + val social: ImmutableList, + val repository: ImmutableList, + val blockchainSite: ImmutableList, + val onLinkClick: (Link) -> Unit, +) { + data class Link( + @DrawableRes val iconRes: Int, + val title: TextReference, + val url: String, + ) +} \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/entity/MarketsTokenDetailsUM.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/state/MarketsTokenDetailsUM.kt similarity index 64% rename from features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/entity/MarketsTokenDetailsUM.kt rename to features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/state/MarketsTokenDetailsUM.kt index 713d890bfb..42f7c3e9e0 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/entity/MarketsTokenDetailsUM.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/state/MarketsTokenDetailsUM.kt @@ -1,14 +1,14 @@ -package com.tangem.features.markets.details.impl.ui.entity +package com.tangem.features.markets.details.impl.ui.state import androidx.compose.runtime.Immutable import com.tangem.common.ui.charts.state.MarketChartDataProducer import com.tangem.common.ui.charts.state.MarketChartLook +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.components.marketprice.PriceChangeType import com.tangem.core.ui.extensions.TextReference import com.tangem.domain.markets.PriceChangeInterval import java.math.BigDecimal -@Immutable internal data class MarketsTokenDetailsUM( val tokenName: String, val priceText: String, @@ -19,10 +19,10 @@ internal data class MarketsTokenDetailsUM( val selectedInterval: PriceChangeInterval, val chartState: ChartState, val onSelectedIntervalChange: (PriceChangeInterval) -> Unit, - // val info : Information TODO [REDACTED_TASK_KEY] + val infoBottomSheet: TangemBottomSheetConfig, + val body: Body, ) { - @Immutable data class ChartState( val status: Status, val dataProducer: MarketChartDataProducer, @@ -30,18 +30,39 @@ internal data class MarketsTokenDetailsUM( val onLoadRetryClick: () -> Unit, val onMarkerPointSelected: (time: BigDecimal?, price: BigDecimal?) -> Unit, ) { - @Immutable enum class Status { LOADING, ERROR, DATA } } - @Immutable - data class Information( + data class InformationBlocks( val insights: InsightsUM?, val securityScore: SecurityScoreUM?, val metrics: MetricsUM?, val pricePerformance: PricePerformanceUM?, val links: LinksUM?, ) + + @Immutable + sealed interface Body { + + data class Error( + val onLoadRetryClick: () -> Unit, + ) : Body + + data object Loading : Body + + data class Content( + val description: Description?, + val infoBlocks: InformationBlocks, + ) : Body + + data object Nothing : Body + } + + data class Description( + val shortDescription: TextReference, + val fullDescription: TextReference?, + val onReadMoreClick: () -> Unit, + ) } \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/entity/MetricsUM.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/state/MetricsUM.kt similarity index 54% rename from features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/entity/MetricsUM.kt rename to features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/state/MetricsUM.kt index 14f5a9fb86..b1982093d4 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/entity/MetricsUM.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/state/MetricsUM.kt @@ -1,9 +1,7 @@ -package com.tangem.features.markets.details.impl.ui.entity +package com.tangem.features.markets.details.impl.ui.state -import androidx.compose.runtime.Immutable import kotlinx.collections.immutable.PersistentList -@Immutable internal data class MetricsUM( val metrics: PersistentList, ) \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/entity/PricePerformanceUM.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/state/PricePerformanceUM.kt similarity index 70% rename from features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/entity/PricePerformanceUM.kt rename to features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/state/PricePerformanceUM.kt index 194354a363..e56e10312e 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/entity/PricePerformanceUM.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/state/PricePerformanceUM.kt @@ -1,15 +1,12 @@ -package com.tangem.features.markets.details.impl.ui.entity +package com.tangem.features.markets.details.impl.ui.state import androidx.annotation.FloatRange -import androidx.compose.runtime.Immutable -@Immutable internal data class PricePerformanceUM( val h24: Value, val month: Value, val all: Value, ) { - @Immutable data class Value( val low: String, val high: String, diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/entity/SecurityScoreUM.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/state/SecurityScoreUM.kt similarity index 63% rename from features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/entity/SecurityScoreUM.kt rename to features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/state/SecurityScoreUM.kt index 4afa252dba..35f35d4b9c 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/entity/SecurityScoreUM.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/state/SecurityScoreUM.kt @@ -1,9 +1,7 @@ -package com.tangem.features.markets.details.impl.ui.entity +package com.tangem.features.markets.details.impl.ui.state import androidx.annotation.FloatRange -import androidx.compose.runtime.Immutable -@Immutable internal data class SecurityScoreUM( @FloatRange(from = 0.0, to = 5.0) val score: Float, val description: String, diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/MarketsListModel.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/MarketsListModel.kt index 4e4d85ea7f..167b03fd6c 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/MarketsListModel.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/MarketsListModel.kt @@ -11,9 +11,9 @@ import com.tangem.domain.markets.TokenMarket import com.tangem.features.markets.component.BottomSheetState import com.tangem.features.markets.tokenlist.impl.model.statemanager.MarketsListUMStateManager import com.tangem.features.markets.tokenlist.impl.model.statemanager.MarketsListBatchFlowManager -import com.tangem.features.markets.tokenlist.impl.ui.entity.ListUM -import com.tangem.features.markets.tokenlist.impl.ui.entity.MarketsListItemUM -import com.tangem.features.markets.tokenlist.impl.ui.entity.SortByTypeUM +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.SortByTypeUM import com.tangem.utils.Provider import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.JobHolder @@ -200,10 +200,12 @@ internal class MarketsListModel @Inject constructor( modelScope.launch { marketsListUMStateManager.searchQueryFlow - .filter { it.isNotEmpty() } .debounce(timeoutMillis = SEARCH_QUERY_DEBOUNCE_MILLIS) .distinctUntilChanged() - .filter { activeListManager == searchMarketsListManager } + .onEach { + if (it.isEmpty()) searchMarketsListManager.clearStateAndStopAllActions() + } + .filter { it.isNotEmpty() && activeListManager == searchMarketsListManager } .collectLatest { searchMarketsListManager.reload(searchText = it) } @@ -229,6 +231,7 @@ internal class MarketsListModel @Inject constructor( } } } + private fun CoroutineScope.loadQuotesWithTimer(timeMillis: Long) { launch { while (true) { diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/converters/MarketsTokenItemConverter.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/converters/MarketsTokenItemConverter.kt index f19fc26c78..590aae7f3f 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/converters/MarketsTokenItemConverter.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/converters/MarketsTokenItemConverter.kt @@ -7,8 +7,8 @@ import com.tangem.core.ui.components.marketprice.PriceChangeType import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.markets.TokenMarket -import com.tangem.features.markets.tokenlist.impl.ui.entity.MarketsListItemUM -import com.tangem.features.markets.tokenlist.impl.ui.entity.MarketsListUM.TrendInterval +import com.tangem.features.markets.tokenlist.impl.ui.state.MarketsListItemUM +import com.tangem.features.markets.tokenlist.impl.ui.state.MarketsListUM.TrendInterval import com.tangem.utils.converter.Converter import kotlinx.collections.immutable.toImmutableList import java.math.BigDecimal @@ -70,10 +70,11 @@ internal class MarketsTokenItemConverter( private fun TokenMarket.getMarketCap(): String? { val value = marketCap?.takeIf { marketCap != BigDecimal.ZERO } ?: return null - return BigDecimalFormatter.formatCompactAmount( - value, + return BigDecimalFormatter.formatCompactFiatAmount( + amount = value, fiatCurrencyCode = appCurrency.code, fiatCurrencySymbol = appCurrency.symbol, + threeDigitsMethod = true, ) } diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/statemanager/MarketsListBatchFlowManager.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/statemanager/MarketsListBatchFlowManager.kt index d84184d89a..bc5dcaed36 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/statemanager/MarketsListBatchFlowManager.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/statemanager/MarketsListBatchFlowManager.kt @@ -6,9 +6,9 @@ import com.tangem.features.markets.tokenlist.impl.model.converters.MarketsTokenI import com.tangem.features.markets.tokenlist.impl.model.utils.logAction import com.tangem.features.markets.tokenlist.impl.model.utils.logStatus import com.tangem.features.markets.tokenlist.impl.model.utils.logUpdateResults -import com.tangem.features.markets.tokenlist.impl.ui.entity.MarketsListItemUM -import com.tangem.features.markets.tokenlist.impl.ui.entity.MarketsListUM.TrendInterval -import com.tangem.features.markets.tokenlist.impl.ui.entity.SortByTypeUM +import com.tangem.features.markets.tokenlist.impl.ui.state.MarketsListItemUM +import com.tangem.features.markets.tokenlist.impl.ui.state.MarketsListUM.TrendInterval +import com.tangem.features.markets.tokenlist.impl.ui.state.SortByTypeUM import com.tangem.pagination.Batch import com.tangem.pagination.BatchAction import com.tangem.pagination.BatchFetchResult diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/statemanager/MarketsListUMStateManager.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/statemanager/MarketsListUMStateManager.kt index fabc2afbfd..806d5434cb 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/statemanager/MarketsListUMStateManager.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/statemanager/MarketsListUMStateManager.kt @@ -7,11 +7,11 @@ import com.tangem.core.ui.event.consumedEvent import com.tangem.core.ui.event.triggeredEvent import com.tangem.core.ui.extensions.resourceReference import com.tangem.features.markets.impl.R -import com.tangem.features.markets.tokenlist.impl.ui.entity.SortByBottomSheetContentUM -import com.tangem.features.markets.tokenlist.impl.ui.entity.ListUM -import com.tangem.features.markets.tokenlist.impl.ui.entity.MarketsListItemUM -import com.tangem.features.markets.tokenlist.impl.ui.entity.MarketsListUM -import com.tangem.features.markets.tokenlist.impl.ui.entity.SortByTypeUM +import com.tangem.features.markets.tokenlist.impl.ui.state.SortByBottomSheetContentUM +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 kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/MarketsList.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/MarketsList.kt index 56f0361cff..6dcb360962 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/MarketsList.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/MarketsList.kt @@ -38,10 +38,10 @@ import com.tangem.features.markets.component.BottomSheetState import com.tangem.features.markets.impl.R import com.tangem.features.markets.tokenlist.impl.ui.components.MarketsListLazyColumn import com.tangem.features.markets.tokenlist.impl.ui.components.MarketsListSortByBottomSheet -import com.tangem.features.markets.tokenlist.impl.ui.entity.ListUM -import com.tangem.features.markets.tokenlist.impl.ui.entity.MarketsListUM -import com.tangem.features.markets.tokenlist.impl.ui.entity.SortByBottomSheetContentUM -import com.tangem.features.markets.tokenlist.impl.ui.entity.SortByTypeUM +import com.tangem.features.markets.tokenlist.impl.ui.state.ListUM +import com.tangem.features.markets.tokenlist.impl.ui.state.MarketsListUM +import com.tangem.features.markets.tokenlist.impl.ui.state.SortByBottomSheetContentUM +import com.tangem.features.markets.tokenlist.impl.ui.state.SortByTypeUM import com.tangem.features.markets.tokenlist.impl.ui.preview.MarketChartListItemPreviewDataProvider import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/components/MarketsListItem.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/components/MarketsListItem.kt index 14873a4e8d..7658893fb7 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/components/MarketsListItem.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/components/MarketsListItem.kt @@ -46,7 +46,7 @@ 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.tokenlist.impl.ui.entity.MarketsListItemUM +import com.tangem.features.markets.tokenlist.impl.ui.state.MarketsListItemUM import com.tangem.features.markets.tokenlist.impl.ui.preview.MarketChartListItemPreviewDataProvider import com.tangem.utils.StringsSigns.MINUS import kotlinx.coroutines.launch diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/components/MarketsListLazyColumn.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/components/MarketsListLazyColumn.kt index 57aa98cc35..0da161d62a 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/components/MarketsListLazyColumn.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/components/MarketsListLazyColumn.kt @@ -9,10 +9,6 @@ import androidx.compose.material3.Text import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.input.nestedscroll.NestedScrollConnection -import androidx.compose.ui.input.nestedscroll.NestedScrollSource -import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.stringResource import com.tangem.core.ui.components.buttons.SecondarySmallButton @@ -20,8 +16,9 @@ import com.tangem.core.ui.components.buttons.SmallButtonConfig import com.tangem.core.ui.event.EventEffect import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.utils.disableNestedScroll import com.tangem.features.markets.impl.R -import com.tangem.features.markets.tokenlist.impl.ui.entity.ListUM +import com.tangem.features.markets.tokenlist.impl.ui.state.ListUM import kotlinx.coroutines.launch private const val LOAD_NEXT_PAGE_ON_END_INDEX = 50 @@ -54,7 +51,7 @@ internal fun MarketsListLazyColumn( if (state is ListUM.Loading) { LazyColumn( - modifier = Modifier.nestedScroll(DisableParentConnection), + modifier = Modifier.disableNestedScroll(), state = rememberLazyListState(), contentPadding = PaddingValues(bottom = bottomBarHeight), userScrollEnabled = false, @@ -65,7 +62,7 @@ internal fun MarketsListLazyColumn( } } else { LazyColumn( - modifier = modifier.nestedScroll(DisableParentConnection), + modifier = modifier.disableNestedScroll(), state = lazyListState, contentPadding = PaddingValues(bottom = bottomBarHeight), userScrollEnabled = true, @@ -221,10 +218,4 @@ fun InfiniteListHandler(listState: LazyListState, onLoadMore: () -> Boolean, buf emitted = onLoadMore() } } -} - -private object DisableParentConnection : NestedScrollConnection { - override fun onPostScroll(consumed: Offset, available: Offset, source: NestedScrollSource): Offset { - return available.copy(x = 0f) - } } \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/components/MarketsListSortByBottomSheet.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/components/MarketsListSortByBottomSheet.kt index 67fe12a9c3..b5c0966102 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/components/MarketsListSortByBottomSheet.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/components/MarketsListSortByBottomSheet.kt @@ -19,8 +19,8 @@ import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.features.markets.impl.R -import com.tangem.features.markets.tokenlist.impl.ui.entity.SortByBottomSheetContentUM -import com.tangem.features.markets.tokenlist.impl.ui.entity.SortByTypeUM +import com.tangem.features.markets.tokenlist.impl.ui.state.SortByBottomSheetContentUM +import com.tangem.features.markets.tokenlist.impl.ui.state.SortByTypeUM @Composable fun MarketsListSortByBottomSheet(config: TangemBottomSheetConfig) { diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/preview/MarketChartListItemPreviewDataProvider.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/preview/MarketChartListItemPreviewDataProvider.kt index 5cc524cd87..41d1596f68 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/preview/MarketChartListItemPreviewDataProvider.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/preview/MarketChartListItemPreviewDataProvider.kt @@ -4,7 +4,7 @@ package com.tangem.features.markets.tokenlist.impl.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.tokenlist.impl.ui.entity.MarketsListItemUM +import com.tangem.features.markets.tokenlist.impl.ui.state.MarketsListItemUM import kotlinx.collections.immutable.persistentListOf internal class MarketChartListItemPreviewDataProvider : CollectionPreviewParameterProvider( diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/entity/MarketsListItemUM.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/state/MarketsListItemUM.kt similarity index 94% rename from features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/entity/MarketsListItemUM.kt rename to features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/state/MarketsListItemUM.kt index 3dedf931c4..020dd5b37a 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/entity/MarketsListItemUM.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/state/MarketsListItemUM.kt @@ -1,4 +1,4 @@ -package com.tangem.features.markets.tokenlist.impl.ui.entity +package com.tangem.features.markets.tokenlist.impl.ui.state import androidx.compose.runtime.Immutable import com.tangem.common.ui.charts.state.MarketChartLook diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/entity/MarketsListUM.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/state/MarketsListUM.kt similarity index 97% rename from features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/entity/MarketsListUM.kt rename to features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/state/MarketsListUM.kt index 39d29633d4..7906cc1fb9 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/entity/MarketsListUM.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/state/MarketsListUM.kt @@ -1,4 +1,4 @@ -package com.tangem.features.markets.tokenlist.impl.ui.entity +package com.tangem.features.markets.tokenlist.impl.ui.state import androidx.compose.runtime.Immutable import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/entity/SortByBottomSheetContentUM.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/state/SortByBottomSheetContentUM.kt similarity index 79% rename from features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/entity/SortByBottomSheetContentUM.kt rename to features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/state/SortByBottomSheetContentUM.kt index fc3354f4b9..f3b4e9682b 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/entity/SortByBottomSheetContentUM.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/state/SortByBottomSheetContentUM.kt @@ -1,4 +1,4 @@ -package com.tangem.features.markets.tokenlist.impl.ui.entity +package com.tangem.features.markets.tokenlist.impl.ui.state import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent