Updated on 2026-08-14
This commit is contained in:
parent
e895d15e8a
commit
d53f4d0346
5 changed files with 354 additions and 52 deletions
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
@ -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(),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
@ -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",
|
||||
),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue