Updated on 2026-08-14

This commit is contained in:
Tangem 2025-04-25 16:55:31 +05:00
parent b00a710ed0
commit c3379c5df9
12 changed files with 188 additions and 13 deletions

View file

@ -538,6 +538,13 @@
<string name="nft_details_token_id">Token ID</string>
<string name="nft_details_token_standard">Token Standard</string>
<string name="nft_details_traits">Traits</string>
<string name="nft_details_info_rarity_label"></string>
<string name="nft_details_info_rarity_rank"></string>
<string name="nft_details_info_token_standard"></string>
<string name="nft_details_info_token_address"></string>
<string name="nft_details_info_contract_address"></string>
<string name="nft_details_info_token_id"></string>
<string name="nft_details_info_chain"></string>
<string name="nft_empty_search">No results. Please try another request.</string>
<string name="nft_no_collection">No collection</string>
<string name="nft_receive_available_section_title">Available</string>

View file

@ -53,6 +53,7 @@ internal class NFTDetailsComponent @AssistedInject constructor(
is NFTDetailsBottomSheetConfig.Info -> nftDetailsInfoComponentFactory.create(
context = childByContext(componentContext),
params = NFTDetailsInfoComponent.Params(
title = config.title,
text = config.text,
onDismiss = {
model.bottomSheetNavigation.dismiss()

View file

@ -56,6 +56,8 @@ data class NFTAssetUM(
val rank: String,
val label: String,
val showDivider: Boolean,
val onRankClick: () -> Unit,
val onLabelClick: () -> Unit,
) : Rarity()
}
@ -64,5 +66,7 @@ data class NFTAssetUM(
val titleTextEllipsis: TextEllipsis = TextEllipsis.End,
val value: String,
val valueTextEllipsis: TextEllipsis = TextEllipsis.End,
val showInfoButton: Boolean,
val onClick: () -> Unit = { },
)
}

View file

@ -7,6 +7,7 @@ import kotlinx.serialization.Serializable
internal sealed interface NFTDetailsBottomSheetConfig {
@Serializable
data class Info(
val title: TextReference,
val text: TextReference,
) : NFTDetailsBottomSheetConfig
}

View file

@ -1,6 +1,7 @@
package com.tangem.features.nft.details.entity.factory
import com.tangem.core.ui.components.atoms.text.TextEllipsis
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.domain.nft.models.NFTAsset
@ -17,6 +18,7 @@ internal class NFTDetailsUMFactory(
private val onSeeAllTraitsClick: () -> Unit,
private val onExploreClick: () -> Unit,
private val onSendClick: () -> Unit,
private val onInfoBlockClick: (title: TextReference, text: TextReference) -> Unit,
) {
fun getInitialState(nftAsset: NFTAsset): NFTDetailsUM = NFTDetailsUM(
@ -57,6 +59,18 @@ internal class NFTDetailsUMFactory(
rank = rarity.rank,
label = rarity.label,
showDivider = hasSalePrice || hasDescription,
onLabelClick = {
onInfoBlockClick(
resourceReference(R.string.nft_details_rarity_label),
resourceReference(R.string.nft_details_info_rarity_label),
)
},
onRankClick = {
onInfoBlockClick(
resourceReference(R.string.nft_details_rarity_rank),
resourceReference(R.string.nft_details_info_rarity_rank),
)
},
)
} else {
NFTAssetUM.Rarity.Empty
@ -68,6 +82,7 @@ internal class NFTDetailsUMFactory(
NFTAssetUM.BlockItem(
title = stringReference(it.name),
value = it.value,
showInfoButton = false,
)
}.toImmutableList(),
showAllTraitsButton = traits.size > MAX_TRAITS_COUNT,
@ -76,24 +91,53 @@ internal class NFTDetailsUMFactory(
private fun NFTAsset.hasSalePrice() = salePrice !is NFTSalePrice.Empty && salePrice !is NFTSalePrice.Error
@Suppress("LongMethod")
private fun NFTAsset.buildBaseInfoItems() = when (val id = id) {
is NFTAsset.Identifier.EVM -> persistentListOf(
NFTAssetUM.BlockItem(
title = resourceReference(R.string.nft_details_token_standard),
value = contractType,
showInfoButton = true,
onClick = {
onInfoBlockClick(
resourceReference(R.string.nft_details_token_standard),
resourceReference(R.string.nft_details_info_token_standard),
)
},
),
NFTAssetUM.BlockItem(
title = resourceReference(R.string.nft_details_contract_address),
value = id.tokenAddress,
valueTextEllipsis = TextEllipsis.Middle,
showInfoButton = true,
onClick = {
onInfoBlockClick(
resourceReference(R.string.nft_details_contract_address),
resourceReference(R.string.nft_details_info_contract_address),
)
},
),
NFTAssetUM.BlockItem(
title = resourceReference(R.string.nft_details_token_id),
value = id.tokenId.toString(),
showInfoButton = true,
onClick = {
onInfoBlockClick(
resourceReference(R.string.nft_details_token_id),
resourceReference(R.string.nft_details_info_token_id),
)
},
),
NFTAssetUM.BlockItem(
title = resourceReference(R.string.nft_details_chain),
value = network.name,
showInfoButton = true,
onClick = {
onInfoBlockClick(
resourceReference(R.string.nft_details_chain),
resourceReference(R.string.nft_details_info_chain),
)
},
),
)
is NFTAsset.Identifier.TON -> persistentListOf(
@ -101,10 +145,24 @@ internal class NFTDetailsUMFactory(
title = resourceReference(R.string.nft_details_token_address),
value = id.tokenAddress,
valueTextEllipsis = TextEllipsis.Middle,
showInfoButton = true,
onClick = {
onInfoBlockClick(
resourceReference(R.string.nft_details_token_address),
resourceReference(R.string.nft_details_info_token_address),
)
},
),
NFTAssetUM.BlockItem(
title = resourceReference(R.string.nft_details_chain),
value = network.name,
showInfoButton = true,
onClick = {
onInfoBlockClick(
resourceReference(R.string.nft_details_chain),
resourceReference(R.string.nft_details_info_chain),
)
},
),
)
is NFTAsset.Identifier.Solana -> persistentListOf(
@ -112,10 +170,24 @@ internal class NFTDetailsUMFactory(
title = resourceReference(R.string.nft_details_token_address),
value = id.tokenAddress,
valueTextEllipsis = TextEllipsis.Middle,
showInfoButton = true,
onClick = {
onInfoBlockClick(
resourceReference(R.string.nft_details_token_address),
resourceReference(R.string.nft_details_info_token_address),
)
},
),
NFTAssetUM.BlockItem(
title = resourceReference(R.string.nft_details_chain),
value = network.name,
showInfoButton = true,
onClick = {
onInfoBlockClick(
resourceReference(R.string.nft_details_chain),
resourceReference(R.string.nft_details_info_chain),
)
},
),
)
is NFTAsset.Identifier.Unknown -> persistentListOf()

View file

@ -33,6 +33,7 @@ internal class DefaultNFTDetailsInfoComponent @AssistedInject constructor(
)
}
NFTInfoBottomSheet(
title = params.title,
config = bottomSheetConfig,
content = {
NFTInfoBottomSheetContent(

View file

@ -7,6 +7,7 @@ import com.tangem.core.ui.extensions.TextReference
internal interface NFTDetailsInfoComponent : ComposableBottomSheetComponent {
data class Params(
val title: TextReference,
val text: TextReference,
val onDismiss: () -> Unit,
)

View file

@ -14,17 +14,19 @@ import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
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.impl.R
@Composable
internal fun NFTInfoBottomSheet(config: TangemBottomSheetConfig, content: @Composable () -> Unit) {
internal fun NFTInfoBottomSheet(
title: TextReference,
config: TangemBottomSheetConfig,
content: @Composable () -> Unit,
) {
TangemBottomSheet<TangemBottomSheetConfigContent.Empty>(
config = config,
titleText = resourceReference(R.string.nft_about_title),
titleText = title,
content = { content() },
)
}

View file

@ -8,15 +8,18 @@ import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.decompose.navigation.Router
import com.tangem.domain.nft.analytics.NFTAnalyticsEvent
import com.tangem.core.navigation.url.UrlOpener
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.domain.nft.GetNFTExploreUrlUseCase
import com.tangem.domain.nft.analytics.NFTAnalyticsEvent
import com.tangem.features.nft.details.NFTDetailsComponent
import com.tangem.features.nft.details.entity.NFTAssetUM
import com.tangem.features.nft.details.entity.NFTDetailsBottomSheetConfig
import com.tangem.features.nft.details.entity.NFTDetailsUM
import com.tangem.features.nft.details.entity.factory.NFTDetailsUMFactory
import com.tangem.features.nft.impl.R
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@ -43,6 +46,7 @@ internal class NFTDetailsModel @Inject constructor(
onSeeAllTraitsClick = { params.onAllTraitsClick() },
onExploreClick = ::onExploreClick,
onSendClick = ::onSendClick,
onInfoBlockClick = ::onInfoBlockClick,
)
private val _state = MutableStateFlow(
@ -50,10 +54,21 @@ internal class NFTDetailsModel @Inject constructor(
)
val bottomSheetNavigation: SlotNavigation<NFTDetailsBottomSheetConfig> = SlotNavigation()
init {
analyticsEventHandler.send(NFTAnalyticsEvent.Details.ScreenOpened(params.nftAsset.network.name))
}
private fun onInfoBlockClick(title: TextReference, text: TextReference) {
analyticsEventHandler.send(NFTAnalyticsEvent.Details.ButtonReadMore)
bottomSheetNavigation.activate(
NFTDetailsBottomSheetConfig.Info(
title = title,
text = text,
),
)
}
private fun onReadMoreClick() {
analyticsEventHandler.send(NFTAnalyticsEvent.Details.ButtonReadMore)
when (val topInfo = _state.value.nftAsset.topInfo) {
@ -61,6 +76,7 @@ internal class NFTDetailsModel @Inject constructor(
is NFTAssetUM.TopInfo.Content -> {
bottomSheetNavigation.activate(
NFTDetailsBottomSheetConfig.Info(
title = resourceReference(R.string.nft_about_title),
text = stringReference(topInfo.description.orEmpty()),
),
)

View file

@ -138,32 +138,40 @@ private class NFTAssetProvider : CollectionPreviewParameterProvider<NFTAssetUM>(
rank = "Top 1% rarity",
label = "115.28",
showDivider = true,
onRankClick = { },
onLabelClick = { },
),
),
traits = persistentListOf(
NFTAssetUM.BlockItem(
title = stringReference("Tier"),
value = "Infinite",
showInfoButton = false,
),
NFTAssetUM.BlockItem(
title = stringReference("Phygital Toy"),
value = "None",
showInfoButton = false,
),
NFTAssetUM.BlockItem(
title = stringReference("Class"),
value = "CYBER",
showInfoButton = false,
),
NFTAssetUM.BlockItem(
title = stringReference("Accessory"),
value = "No accessory",
showInfoButton = false,
),
NFTAssetUM.BlockItem(
title = stringReference("Sneakers"),
value = "Boots",
showInfoButton = false,
),
NFTAssetUM.BlockItem(
title = stringReference("Artist"),
value = "DJ Dragoon",
showInfoButton = false,
),
),
showAllTraitsButton = true,
@ -171,19 +179,23 @@ private class NFTAssetProvider : CollectionPreviewParameterProvider<NFTAssetUM>(
NFTAssetUM.BlockItem(
title = resourceReference(R.string.nft_details_token_standard),
value = "ERC-721",
showInfoButton = true,
),
NFTAssetUM.BlockItem(
title = resourceReference(R.string.nft_details_contract_address),
value = "0x6811f2fgd892ac83f719",
valueTextEllipsis = TextEllipsis.Middle,
showInfoButton = true,
),
NFTAssetUM.BlockItem(
title = resourceReference(R.string.nft_details_token_id),
value = "100200273",
showInfoButton = true,
),
NFTAssetUM.BlockItem(
title = resourceReference(R.string.nft_details_chain),
value = "Ethereum",
showInfoButton = true,
),
),
),

View file

@ -30,6 +30,7 @@ import com.tangem.features.nft.impl.R
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
@Suppress("LongMethod")
@Composable
internal fun NFTDetailsBlocksGroup(
title: TextReference,
@ -68,11 +69,17 @@ internal fun NFTDetailsBlocksGroup(
NFTDetailsGroupBlock(
modifier = Modifier
.weight(1f)
.padding(paddingValues),
.padding(paddingValues)
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = null,
onClick = item1.onClick,
),
title = item1.title,
value = stringReference(item1.value),
titleEllipsis = item1.titleTextEllipsis,
valueEllipsis = item1.valueTextEllipsis,
showInfoButton = item1.showInfoButton,
)
if (item2 == null) {
Box(
@ -84,11 +91,17 @@ internal fun NFTDetailsBlocksGroup(
NFTDetailsGroupBlock(
modifier = Modifier
.weight(1f)
.padding(paddingValues),
.padding(paddingValues)
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = null,
onClick = item2.onClick,
),
title = item2.title,
value = stringReference(item2.value),
titleEllipsis = item2.titleTextEllipsis,
valueEllipsis = item2.valueTextEllipsis,
showInfoButton = item2.showInfoButton,
)
}
}
@ -113,6 +126,7 @@ internal fun NFTDetailsGroupTitle(text: TextReference, modifier: Modifier = Modi
internal fun NFTDetailsGroupBlock(
title: TextReference,
value: TextReference,
showInfoButton: Boolean,
modifier: Modifier = Modifier,
titleEllipsis: TextEllipsis = TextEllipsis.End,
valueEllipsis: TextEllipsis = TextEllipsis.End,
@ -120,12 +134,29 @@ internal fun NFTDetailsGroupBlock(
Column(
modifier = modifier,
) {
EllipsisText(
text = title.resolveReference(),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
ellipsis = titleEllipsis,
)
Row(
modifier = Modifier
.fillMaxWidth(),
) {
EllipsisText(
modifier = Modifier
.weight(1f, fill = false),
text = title.resolveReference(),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
ellipsis = titleEllipsis,
)
if (showInfoButton) {
Icon(
modifier = Modifier
.padding(start = TangemTheme.dimens.spacing4)
.size(TangemTheme.dimens.size16),
painter = painterResource(R.drawable.ic_information_24),
contentDescription = null,
tint = TangemTheme.colors.icon.informative,
)
}
}
EllipsisText(
modifier = Modifier
.padding(top = TangemTheme.dimens.spacing4),
@ -224,22 +255,27 @@ private class NFTAssetBlocksProvider : CollectionPreviewParameterProvider<Immuta
NFTAssetUM.BlockItem(
title = stringReference("Tier"),
value = "Infinite",
showInfoButton = false,
),
NFTAssetUM.BlockItem(
title = stringReference("Phygital Toy"),
value = "None",
showInfoButton = false,
),
NFTAssetUM.BlockItem(
title = stringReference("Class"),
value = "CYBER",
showInfoButton = false,
),
NFTAssetUM.BlockItem(
title = stringReference("Accessory"),
value = "No accessory",
showInfoButton = false,
),
NFTAssetUM.BlockItem(
title = stringReference("Sneakers"),
value = "Boots",
showInfoButton = false,
),
),
),

View file

@ -189,18 +189,30 @@ private fun Rarity(state: NFTAssetUM.Rarity, modifier: Modifier = Modifier) {
.weight(1f)
.padding(
horizontal = TangemTheme.dimens.spacing6,
)
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = null,
onClick = state.onLabelClick,
),
title = resourceReference(R.string.nft_details_rarity_label),
value = stringReference(state.label),
showInfoButton = true,
)
NFTDetailsGroupBlock(
modifier = Modifier
.weight(1f)
.padding(
horizontal = TangemTheme.dimens.spacing6,
)
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = null,
onClick = state.onRankClick,
),
title = resourceReference(R.string.nft_details_rarity_rank),
value = stringReference(state.rank),
showInfoButton = true,
)
}
}
@ -236,6 +248,8 @@ private class NFTAssetInfoProvider : CollectionPreviewParameterProvider<NFTAsset
rank = "Top 1% rarity",
label = "115.28",
showDivider = true,
onLabelClick = { },
onRankClick = { },
),
),
NFTAssetUM.TopInfo.Content(
@ -246,6 +260,8 @@ private class NFTAssetInfoProvider : CollectionPreviewParameterProvider<NFTAsset
rank = "Top 1% rarity",
label = "115.28",
showDivider = true,
onLabelClick = { },
onRankClick = { },
),
),
NFTAssetUM.TopInfo.Content(
@ -262,6 +278,8 @@ private class NFTAssetInfoProvider : CollectionPreviewParameterProvider<NFTAsset
rank = "Top 1% rarity",
label = "115.28",
showDivider = true,
onLabelClick = { },
onRankClick = { },
),
),
NFTAssetUM.TopInfo.Content(
@ -296,6 +314,8 @@ private class NFTAssetInfoProvider : CollectionPreviewParameterProvider<NFTAsset
rank = "Top 1% rarity",
label = "115.28",
showDivider = true,
onLabelClick = { },
onRankClick = { },
),
),
NFTAssetUM.TopInfo.Content(
@ -306,6 +326,8 @@ private class NFTAssetInfoProvider : CollectionPreviewParameterProvider<NFTAsset
rank = "Top 1% rarity",
label = "115.28",
showDivider = false,
onLabelClick = { },
onRankClick = { },
),
),
NFTAssetUM.TopInfo.Content(