Updated on 2026-08-14

This commit is contained in:
Tangem 2025-04-04 11:33:22 +05:00
parent e895d15e8a
commit d53f4d0346
5 changed files with 354 additions and 52 deletions

View file

@ -30,7 +30,7 @@ class InformationBlockContentScope(val scope: BoxScope) : BoxScope by scope
@Composable
fun InformationBlock(
title: @Composable BoxScope.() -> Unit,
title: (@Composable BoxScope.() -> Unit)?,
modifier: Modifier = Modifier,
contentHorizontalPadding: Dp = TangemTheme.dimens.spacing12,
shape: Shape = TangemTheme.shapes.roundedCornersXMedium,
@ -43,7 +43,9 @@ fun InformationBlock(
.background(color = TangemTheme.colors.background.action),
horizontalAlignment = Alignment.Start,
) {
NetworkTitle(title = title, action = action)
if (title != null) {
NetworkTitle(title = title, action = action)
}
if (content != null) {
Box(

View file

@ -7,29 +7,31 @@ import kotlinx.collections.immutable.ImmutableList
data class NFTAssetUM(
val name: String,
val salePrice: SalePrice,
val description: String?,
val rarity: Rarity?,
val media: Media?,
val media: Media,
val topInfo: TopInfo,
val traits: ImmutableList<BlockItem>,
val baseInfoItems: ImmutableList<BlockItem>,
) {
data class Media(
val mimetype: String?,
val url: String,
)
data class Rarity(
val rank: String,
val label: String,
)
@Immutable
sealed class TopInfo {
data object Empty : TopInfo()
data class Content(
val title: TextReference?,
val salePrice: SalePrice,
val description: String?,
val rarity: Rarity,
) : TopInfo()
}
data class BlockItem(
val title: TextReference,
val titleTextEllipsis: TextEllipsis = TextEllipsis.End,
val value: String,
val valueTextEllipsis: TextEllipsis = TextEllipsis.End,
)
@Immutable
sealed class Media {
data object Empty : Media()
data class Content(
val mimetype: String?,
val url: String,
) : Media()
}
@Immutable
sealed class SalePrice {
@ -37,4 +39,21 @@ data class NFTAssetUM(
data object Empty : SalePrice()
data class Content(val value: String, val fiatValue: String) : SalePrice()
}
@Immutable
sealed class Rarity {
data object Empty : Rarity()
data class Content(
val rank: String,
val label: String,
val showDivider: Boolean,
) : Rarity()
}
data class BlockItem(
val title: TextReference,
val titleTextEllipsis: TextEllipsis = TextEllipsis.End,
val value: String,
val valueTextEllipsis: TextEllipsis = TextEllipsis.End,
)
}

View file

@ -28,10 +28,8 @@ internal class NFTDetailsModel @Inject constructor(
value = NFTDetailsUM(
nftAsset = NFTAssetUM(
name = "",
salePrice = NFTAssetUM.SalePrice.Empty,
description = null,
rarity = null,
media = null,
media = NFTAssetUM.Media.Empty,
topInfo = NFTAssetUM.TopInfo.Empty,
traits = persistentListOf(),
baseInfoItems = persistentListOf(),
),

View file

@ -0,0 +1,281 @@
package com.tangem.features.nft.details.ui
import android.content.res.Configuration
import androidx.compose.animation.*
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.TextShimmer
import com.tangem.core.ui.components.atoms.text.EllipsisText
import com.tangem.core.ui.components.block.information.InformationBlock
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.features.nft.details.entity.NFTAssetUM
import com.tangem.features.nft.impl.R
@Composable
internal fun NFTDetailsInfoGroup(
state: NFTAssetUM.TopInfo,
onReadMoreClick: () -> Unit,
modifier: Modifier = Modifier,
) {
when (state) {
is NFTAssetUM.TopInfo.Empty -> Unit
is NFTAssetUM.TopInfo.Content -> {
InformationBlock(
modifier = modifier,
title = if (state.title != null) {
{ NFTDetailsGroupTitle(state.title) }
} else {
null
},
contentHorizontalPadding = 0.dp,
) {
Column(
modifier = Modifier,
) {
SalePrice(state.salePrice)
Description(state.description, onReadMoreClick)
Rarity(state.rarity)
}
}
}
}
}
@Composable
private fun SalePrice(state: NFTAssetUM.SalePrice, modifier: Modifier = Modifier) {
AnimatedVisibility(
modifier = Modifier.fillMaxWidth().animateContentSize(),
visible = state !is NFTAssetUM.SalePrice.Empty,
enter = fadeIn(),
exit = fadeOut(),
) {
when (state) {
is NFTAssetUM.SalePrice.Empty -> Unit
is NFTAssetUM.SalePrice.Loading -> {
Column(
modifier = modifier
.padding(
start = TangemTheme.dimens.spacing12,
end = TangemTheme.dimens.spacing12,
bottom = TangemTheme.dimens.spacing12,
),
) {
TextShimmer(
modifier = Modifier
.width(TangemTheme.dimens.size158),
style = TangemTheme.typography.head,
textSizeHeight = true,
)
TextShimmer(
modifier = Modifier
.padding(top = TangemTheme.dimens.spacing4)
.width(TangemTheme.dimens.size90),
style = TangemTheme.typography.caption2,
textSizeHeight = true,
)
}
}
is NFTAssetUM.SalePrice.Content -> {
Column(
modifier = modifier
.padding(
start = TangemTheme.dimens.spacing12,
end = TangemTheme.dimens.spacing12,
bottom = TangemTheme.dimens.spacing12,
),
verticalArrangement = Arrangement.SpaceAround,
) {
Text(
text = state.value,
style = TangemTheme.typography.head,
color = TangemTheme.colors.text.primary1,
)
Text(
modifier = Modifier
.padding(top = TangemTheme.dimens.spacing4),
text = state.fiatValue,
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
)
}
}
}
}
}
@Composable
private fun Description(description: String?, onReadMoreClick: () -> Unit, modifier: Modifier = Modifier) {
if (!description.isNullOrEmpty()) {
// TODO configure right way to display 3 rows and "Read more" label
EllipsisText(
modifier = modifier
.clickable { onReadMoreClick() }
.padding(
horizontal = TangemTheme.dimens.spacing12,
vertical = TangemTheme.dimens.spacing8,
),
text = description,
style = TangemTheme.typography.body2.copy(
color = TangemTheme.colors.text.tertiary,
),
)
}
}
@Composable
private fun Rarity(state: NFTAssetUM.Rarity, modifier: Modifier = Modifier) {
when (state) {
is NFTAssetUM.Rarity.Empty -> Unit
is NFTAssetUM.Rarity.Content -> {
if (state.showDivider) {
Divider(
modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing12),
)
}
Row(
modifier = modifier
.fillMaxWidth()
.padding(
horizontal = TangemTheme.dimens.spacing6,
vertical = TangemTheme.dimens.spacing8,
),
) {
NFTDetailsGroupBlock(
modifier = Modifier
.weight(1f)
.padding(
horizontal = TangemTheme.dimens.spacing6,
vertical = TangemTheme.dimens.spacing4,
),
title = resourceReference(R.string.nft_details_rarity_label),
value = stringReference(state.label),
)
NFTDetailsGroupBlock(
modifier = Modifier
.weight(1f)
.padding(
horizontal = TangemTheme.dimens.spacing6,
vertical = TangemTheme.dimens.spacing4,
),
title = resourceReference(R.string.nft_details_rarity_rank),
value = stringReference(state.rank),
)
}
}
}
}
@Composable
private fun Divider(modifier: Modifier = Modifier) {
Box(
modifier = modifier
.fillMaxWidth()
.height(TangemTheme.dimens.size8),
contentAlignment = Alignment.Center,
) {
Box(
modifier = Modifier
.fillMaxWidth()
.height(TangemTheme.dimens.size1)
.background(TangemTheme.colors.stroke.primary),
)
}
}
@Preview(widthDp = 360)
@Preview(widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun Preview_NFTDetailsInfoBlock(@PreviewParameter(NFTAssetInfoProvider::class) state: NFTAssetUM.TopInfo) {
TangemThemePreview {
NFTDetailsInfoGroup(
state = state,
onReadMoreClick = { },
)
}
}
@Suppress("LongMethod", "MagicNumber")
private class NFTAssetInfoProvider : CollectionPreviewParameterProvider<NFTAssetUM.TopInfo>(
collection = listOf(
NFTAssetUM.TopInfo.Content(
title = resourceReference(R.string.nft_details_last_sale_price),
salePrice = NFTAssetUM.SalePrice.Content(value = "0,012 ETH", fiatValue = "32.34$"),
description = "Base edition by Piux. An illustration of Crypto Robot #7804".repeat(3),
rarity = NFTAssetUM.Rarity.Content(
rank = "Top 1% rarity",
label = "115.28",
showDivider = true,
),
),
NFTAssetUM.TopInfo.Content(
title = resourceReference(R.string.nft_details_last_sale_price),
salePrice = NFTAssetUM.SalePrice.Loading,
description = "Base edition by Piux. An illustration of Crypto Robot #7804".repeat(3),
rarity = NFTAssetUM.Rarity.Content(
rank = "Top 1% rarity",
label = "115.28",
showDivider = true,
),
),
NFTAssetUM.TopInfo.Content(
title = resourceReference(R.string.nft_details_last_sale_price),
salePrice = NFTAssetUM.SalePrice.Content(value = "0,012 ETH", fiatValue = "32.34$"),
description = null,
rarity = NFTAssetUM.Rarity.Content(
rank = "Top 1% rarity",
label = "115.28",
showDivider = true,
),
),
NFTAssetUM.TopInfo.Content(
title = resourceReference(R.string.nft_details_last_sale_price),
salePrice = NFTAssetUM.SalePrice.Content(value = "0,012 ETH", fiatValue = "32.34$"),
description = "Base edition by Piux. An illustration of Crypto Robot #7804".repeat(3),
rarity = NFTAssetUM.Rarity.Empty,
),
NFTAssetUM.TopInfo.Content(
title = resourceReference(R.string.nft_details_last_sale_price),
salePrice = NFTAssetUM.SalePrice.Content(value = "0,012 ETH", fiatValue = "32.34$"),
description = null,
rarity = NFTAssetUM.Rarity.Empty,
),
NFTAssetUM.TopInfo.Content(
title = null,
salePrice = NFTAssetUM.SalePrice.Empty,
description = "Base edition by Piux. An illustration of Crypto Robot #7804".repeat(3),
rarity = NFTAssetUM.Rarity.Content(
rank = "Top 1% rarity",
label = "115.28",
showDivider = true,
),
),
NFTAssetUM.TopInfo.Content(
title = null,
salePrice = NFTAssetUM.SalePrice.Empty,
description = null,
rarity = NFTAssetUM.Rarity.Content(
rank = "Top 1% rarity",
label = "115.28",
showDivider = false,
),
),
NFTAssetUM.TopInfo.Content(
title = null,
salePrice = NFTAssetUM.SalePrice.Empty,
description = "Base edition by Piux. An illustration of Crypto Robot #7804".repeat(3),
rarity = NFTAssetUM.Rarity.Empty,
),
),
)

View file

@ -26,30 +26,32 @@ import com.tangem.features.nft.details.entity.NFTAssetUM
import com.tangem.features.nft.impl.R
@Composable
internal fun NFTDetailsLogo(state: NFTAssetUM.Media?, modifier: Modifier = Modifier) {
if (state == null) {
Placeholder(
modifier = modifier
.fillMaxWidth(),
)
} else {
SubcomposeAsyncImage(
modifier = modifier
.fillMaxWidth()
.clip(TangemTheme.shapes.roundedCornersXMedium),
model = ImageRequest.Builder(LocalContext.current)
.data(state.url)
.crossfade(true)
.build(),
loading = {
RectangleShimmer(radius = TangemTheme.dimens.radius16)
},
error = {
Placeholder()
},
contentScale = ContentScale.Crop,
contentDescription = null,
)
internal fun NFTDetailsLogo(state: NFTAssetUM.Media, modifier: Modifier = Modifier) {
when (state) {
is NFTAssetUM.Media.Empty -> {
Placeholder(
modifier = modifier.fillMaxWidth(),
)
}
is NFTAssetUM.Media.Content -> {
SubcomposeAsyncImage(
modifier = modifier
.fillMaxWidth()
.clip(TangemTheme.shapes.roundedCornersXMedium),
model = ImageRequest.Builder(LocalContext.current)
.data(state.url)
.crossfade(true)
.build(),
loading = {
RectangleShimmer(radius = TangemTheme.dimens.radius16)
},
error = {
Placeholder()
},
contentScale = ContentScale.Crop,
contentDescription = null,
)
}
}
}
@ -72,7 +74,7 @@ private fun Placeholder(modifier: Modifier = Modifier) {
@Preview(widthDp = 360)
@Preview(widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun Preview_NFTDetailsInfoBlock(@PreviewParameter(NFTAssetMediaProvider::class) state: NFTAssetUM.Media?) {
private fun Preview_NFTDetailsInfoBlock(@PreviewParameter(NFTAssetMediaProvider::class) state: NFTAssetUM.Media) {
TangemThemePreview {
NFTDetailsLogo(
state = state,
@ -82,10 +84,10 @@ private fun Preview_NFTDetailsInfoBlock(@PreviewParameter(NFTAssetMediaProvider:
}
}
private class NFTAssetMediaProvider : CollectionPreviewParameterProvider<NFTAssetUM.Media?>(
private class NFTAssetMediaProvider : CollectionPreviewParameterProvider<NFTAssetUM.Media>(
collection = listOf(
null,
NFTAssetUM.Media(
NFTAssetUM.Media.Empty,
NFTAssetUM.Media.Content(
mimetype = "image/jpeg",
url = "img1",
),