diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml
index 68238443cf..16dff9ff2e 100644
--- a/core/res/src/main/res/values/strings.xml
+++ b/core/res/src/main/res/values/strings.xml
@@ -495,6 +495,10 @@
Pull this up or tap the search bar to add tokens directly from the market
Add tokens
NFC is not available on your device
+ NFT collections
+ Tap here to receive first NFT
+ %1$d NFTs in %2$d collection
+ Unable to load the data
Set up a single access code to protect all your devices.
Protect
Set an individual access code for each card or ring later.
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/nft/WalletNFTItem.kt b/core/ui/src/main/java/com/tangem/core/ui/components/nft/WalletNFTItem.kt
new file mode 100644
index 0000000000..9bbb13232a
--- /dev/null
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/nft/WalletNFTItem.kt
@@ -0,0 +1,433 @@
+package com.tangem.core.ui.components.nft
+
+import android.content.res.Configuration
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Card
+import androidx.compose.material3.CardDefaults
+import androidx.compose.material3.Icon
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.res.painterResource
+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 androidx.compose.ui.zIndex
+import coil.compose.SubcomposeAsyncImage
+import com.tangem.core.ui.R
+import com.tangem.core.ui.components.RectangleShimmer
+import com.tangem.core.ui.components.TextShimmer
+import com.tangem.core.ui.components.nft.state.WalletNFTItemUM
+import com.tangem.core.ui.components.nft.state.WalletNFTItemUM.Content.CollectionPreview
+import com.tangem.core.ui.components.text.applyBladeBrush
+import com.tangem.core.ui.extensions.stringResourceSafe
+import com.tangem.core.ui.res.TangemTheme
+import com.tangem.core.ui.res.TangemThemePreview
+import kotlinx.collections.immutable.ImmutableList
+import kotlinx.collections.immutable.persistentListOf
+
+@Composable
+fun WalletNFTItem(state: WalletNFTItemUM, onClick: () -> Unit) {
+ when (state) {
+ is WalletNFTItemUM.Hidden -> {}
+ is WalletNFTItemUM.Empty -> WalletNFTItemEmpty(
+ onClick = onClick,
+ )
+ is WalletNFTItemUM.Failed -> WalletNFTItemFailed()
+ is WalletNFTItemUM.Loading -> WalletNFTItemLoading()
+
+ is WalletNFTItemUM.Content -> WalletNFTItemContent(
+ previews = state.previews,
+ collectionsCount = state.collectionsCount,
+ assetsCount = state.assetsCount,
+ isFlickering = state.isFlickering,
+ onClick = onClick,
+ )
+ }
+}
+
+@Composable
+fun WalletNFTItemEmpty(onClick: () -> Unit) {
+ RowContentContainer(
+ icon = {
+ Image(
+ modifier = Modifier,
+ painter = painterResource(R.drawable.ic_nft_empty_36),
+ contentDescription = null,
+ )
+ },
+ text = {
+ Text(
+ text = stringResourceSafe(R.string.nft_wallet_title),
+ style = TangemTheme.typography.subtitle2,
+ color = TangemTheme.colors.text.primary1,
+ )
+ Text(
+ text = stringResourceSafe(R.string.nft_wallet_receive_nft),
+ style = TangemTheme.typography.caption2,
+ color = TangemTheme.colors.text.tertiary,
+ )
+ },
+ action = {
+ ChevronArrow()
+ },
+ onClick = onClick,
+ )
+}
+
+@Composable
+fun WalletNFTItemContent(
+ onClick: () -> Unit,
+ previews: ImmutableList,
+ collectionsCount: Int,
+ assetsCount: Int,
+ isFlickering: Boolean,
+) {
+ RowContentContainer(
+ icon = {
+ CollectionsPreviews(previews)
+ },
+ text = {
+ Text(
+ text = stringResourceSafe(R.string.nft_wallet_title),
+ style = TangemTheme.typography.subtitle2.applyBladeBrush(
+ isEnabled = isFlickering,
+ textColor = TangemTheme.colors.text.primary1,
+ ),
+ )
+ Text(
+ text = stringResourceSafe(
+ id = R.string.nft_wallet_count,
+ assetsCount,
+ collectionsCount,
+ ),
+ style = TangemTheme.typography.caption2.applyBladeBrush(
+ isEnabled = isFlickering,
+ textColor = TangemTheme.colors.text.tertiary,
+ ),
+ )
+ },
+ action = {
+ ChevronArrow()
+ },
+ onClick = onClick,
+ )
+}
+
+@Composable
+fun WalletNFTItemFailed() {
+ RowContentContainer(
+ icon = {
+ CollectionsPreviewsPlaceholder()
+ },
+ text = {
+ Text(
+ text = stringResourceSafe(R.string.nft_wallet_title),
+ style = TangemTheme.typography.body2,
+ color = TangemTheme.colors.text.tertiary,
+ )
+ Text(
+ text = stringResourceSafe(R.string.nft_wallet_unable_to_load),
+ style = TangemTheme.typography.caption2,
+ color = TangemTheme.colors.text.tertiary,
+ )
+ },
+ )
+}
+
+@Composable
+fun WalletNFTItemLoading() {
+ RowContentContainer(
+ icon = {
+ CollectionsPreviewsPlaceholder()
+ },
+ text = {
+ TextShimmer(
+ modifier = Modifier.width(TangemTheme.dimens.size110),
+ style = TangemTheme.typography.caption2,
+ textSizeHeight = true,
+ )
+ TextShimmer(
+ modifier = Modifier
+ .width(TangemTheme.dimens.size80)
+ .padding(top = TangemTheme.dimens.spacing4),
+ style = TangemTheme.typography.caption2,
+ textSizeHeight = true,
+ )
+ },
+ )
+}
+
+@Composable
+private fun CollectionsPreviewsPlaceholder() {
+ RectangleShimmer(
+ modifier = Modifier
+ .size(TangemTheme.dimens.size36),
+ radius = TangemTheme.dimens.radius8,
+ )
+}
+
+@Composable
+@Suppress("MagicNumber")
+private fun BoxScope.CollectionsPreviews(urls: ImmutableList) {
+ val modifiers = when (urls.size) {
+ 1 -> previews1Modifiers()
+ 2 -> previews2Modifiers()
+ 3 -> previews3Modifiers()
+ else -> previews4Modifiers()
+ }
+ Box(
+ modifier = Modifier
+ .size(TangemTheme.dimens.size36),
+ ) {
+ urls.take(modifiers.size).forEachIndexed { index, s ->
+ val modifier = modifiers[index]
+ when (s) {
+ is CollectionPreview.Image -> {
+ SubcomposeAsyncImage(
+ modifier = modifier,
+ model = s,
+ loading = {
+ RectangleShimmer(radius = 0.dp)
+ },
+ contentDescription = null,
+ )
+ }
+ is CollectionPreview.More -> {
+ Image(
+ modifier = modifier
+ .background(TangemTheme.colors.stroke.primary),
+ painter = painterResource(R.drawable.ic_nft_preview_more_16),
+ contentDescription = null,
+ )
+ }
+ }
+ }
+ }
+}
+
+@Composable
+private fun BoxScope.previews1Modifiers(): List = listOf(
+ // single image
+ Modifier
+ .size(TangemTheme.dimens.size36)
+ .clip(RoundedCornerShape(TangemTheme.dimens.radius6)),
+)
+
+@Composable
+private fun BoxScope.previews2Modifiers(): List = listOf(
+ Modifier
+ .size(TangemTheme.dimens.size22)
+ .padding(
+ top = TangemTheme.dimens.spacing1,
+ )
+ .clip(RoundedCornerShape(TangemTheme.dimens.radius6))
+ .align(Alignment.TopStart),
+ Modifier
+ .zIndex(1f)
+ .clip(RoundedCornerShape(TangemTheme.dimens.radius6))
+ .background(TangemTheme.colors.background.primary)
+ .padding(
+ top = TangemTheme.dimens.spacing2,
+ start = TangemTheme.dimens.spacing2,
+ )
+ .size(TangemTheme.dimens.size22)
+ .clip(RoundedCornerShape(TangemTheme.dimens.radius6))
+ .align(Alignment.BottomEnd),
+)
+
+@Composable
+private fun BoxScope.previews3Modifiers(): List = listOf(
+ Modifier
+ .padding(
+ top = TangemTheme.dimens.spacing1,
+ bottom = TangemTheme.dimens.spacing1,
+ )
+ .size(TangemTheme.dimens.size16)
+ .clip(RoundedCornerShape(5.dp))
+ .align(Alignment.TopStart),
+ Modifier
+ .zIndex(1f)
+ .padding(
+ top = TangemTheme.dimens.radius4,
+ )
+ .clip(RoundedCornerShape(TangemTheme.dimens.radius6))
+ .background(TangemTheme.colors.background.primary)
+ .padding(
+ start = TangemTheme.dimens.spacing2,
+ top = TangemTheme.dimens.spacing2,
+ end = TangemTheme.dimens.spacing1,
+ bottom = TangemTheme.dimens.spacing2,
+ )
+ .size(TangemTheme.dimens.size18)
+ .clip(RoundedCornerShape(TangemTheme.dimens.radius6))
+ .align(Alignment.TopEnd),
+ Modifier
+ .padding(
+ start = TangemTheme.dimens.spacing6,
+ bottom = TangemTheme.dimens.spacing1,
+ )
+ .clip(RoundedCornerShape(TangemTheme.dimens.radius4))
+ .size(TangemTheme.dimens.size14)
+ .align(Alignment.BottomStart),
+)
+
+@Composable
+private fun BoxScope.previews4Modifiers(): List = listOf(
+ Modifier
+ .clip(RoundedCornerShape(TangemTheme.dimens.radius4))
+ .size(TangemTheme.dimens.size16)
+ .align(Alignment.TopStart),
+ Modifier
+ .clip(RoundedCornerShape(TangemTheme.dimens.radius4))
+ .size(TangemTheme.dimens.size16)
+ .align(Alignment.TopEnd),
+ Modifier
+ .clip(RoundedCornerShape(TangemTheme.dimens.radius4))
+ .size(TangemTheme.dimens.size16)
+ .align(Alignment.BottomStart),
+ Modifier
+ .clip(RoundedCornerShape(TangemTheme.dimens.radius4))
+ .size(TangemTheme.dimens.size16)
+ .align(Alignment.BottomEnd),
+)
+
+@Composable
+private fun ChevronArrow() {
+ Icon(
+ modifier = Modifier,
+ painter = painterResource(R.drawable.ic_chevron_right_24),
+ contentDescription = null,
+ tint = TangemTheme.colors.icon.informative,
+ )
+}
+
+@Composable
+private fun RowContentContainer(
+ modifier: Modifier = Modifier,
+ onClick: () -> Unit = {},
+ icon: @Composable BoxScope.() -> Unit,
+ text: @Composable ColumnScope.() -> Unit,
+ action: @Composable BoxScope.() -> Unit = {},
+) {
+ Card(
+ modifier = modifier.fillMaxWidth(),
+ shape = RoundedCornerShape(TangemTheme.dimens.radius16),
+ colors = CardDefaults.cardColors(
+ containerColor = TangemTheme.colors.background.primary,
+ contentColor = TangemTheme.colors.text.primary1,
+ disabledContainerColor = TangemTheme.colors.background.primary,
+ disabledContentColor = TangemTheme.colors.text.primary1,
+ ),
+ onClick = onClick,
+ enabled = true,
+ ) {
+ Row(
+ modifier = Modifier
+ .padding(
+ horizontal = TangemTheme.dimens.spacing12,
+ vertical = TangemTheme.dimens.spacing16,
+ ),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ Row(
+ modifier = modifier.fillMaxWidth(),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ Box(
+ contentAlignment = Alignment.Center,
+ content = icon,
+ )
+ Column(
+ modifier = Modifier
+ .padding(
+ horizontal = TangemTheme.dimens.spacing12,
+ )
+ .weight(1f),
+ horizontalAlignment = Alignment.Start,
+ content = text,
+ )
+ Box(
+ modifier = Modifier.heightIn(min = TangemTheme.dimens.size24),
+ contentAlignment = Alignment.CenterEnd,
+ content = action,
+ )
+ }
+ }
+ }
+}
+
+@Preview(widthDp = 360)
+@Preview(widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
+@Composable
+private fun Preview_WalletNFTItem_InLight(@PreviewParameter(WalletNFTItemProvider::class) state: WalletNFTItemUM) {
+ TangemThemePreview {
+ WalletNFTItem(
+ state = state,
+ onClick = {},
+ )
+ }
+}
+
+private class WalletNFTItemProvider : CollectionPreviewParameterProvider(
+ collection = listOf(
+ WalletNFTItemUM.Empty,
+ WalletNFTItemUM.Loading,
+ WalletNFTItemUM.Failed,
+ WalletNFTItemUM.Content(
+ previews = persistentListOf(
+ CollectionPreview.Image("img1"),
+ ),
+ assetsCount = 125,
+ collectionsCount = 11,
+ isFlickering = true,
+ ),
+ WalletNFTItemUM.Content(
+ previews = persistentListOf(
+ CollectionPreview.Image("img1"),
+ CollectionPreview.Image("img2"),
+ ),
+ assetsCount = 125,
+ collectionsCount = 11,
+ isFlickering = false,
+ ),
+ WalletNFTItemUM.Content(
+ previews = persistentListOf(
+ CollectionPreview.Image("img1"),
+ CollectionPreview.Image("img2"),
+ CollectionPreview.Image("img3"),
+ ),
+ assetsCount = 125,
+ collectionsCount = 11,
+ isFlickering = false,
+ ),
+ WalletNFTItemUM.Content(
+ previews = persistentListOf(
+ CollectionPreview.Image("img1"),
+ CollectionPreview.Image("img2"),
+ CollectionPreview.Image("img3"),
+ CollectionPreview.Image("img4"),
+ ),
+ assetsCount = 125,
+ collectionsCount = 11,
+ isFlickering = false,
+ ),
+ WalletNFTItemUM.Content(
+ previews = persistentListOf(
+ CollectionPreview.Image("img1"),
+ CollectionPreview.Image("img2"),
+ CollectionPreview.Image("img3"),
+ CollectionPreview.More,
+ ),
+ assetsCount = 125,
+ collectionsCount = 11,
+ isFlickering = true,
+ ),
+ ),
+)
\ No newline at end of file
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/nft/state/WalletNFTItemUM.kt b/core/ui/src/main/java/com/tangem/core/ui/components/nft/state/WalletNFTItemUM.kt
new file mode 100644
index 0000000000..458fd52e53
--- /dev/null
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/nft/state/WalletNFTItemUM.kt
@@ -0,0 +1,29 @@
+package com.tangem.core.ui.components.nft.state
+
+import androidx.compose.runtime.Immutable
+import kotlinx.collections.immutable.ImmutableList
+
+@Immutable
+sealed class WalletNFTItemUM {
+
+ data object Hidden : WalletNFTItemUM()
+
+ data object Loading : WalletNFTItemUM()
+
+ data object Empty : WalletNFTItemUM()
+
+ data object Failed : WalletNFTItemUM()
+
+ data class Content(
+ val previews: ImmutableList,
+ val collectionsCount: Int,
+ val assetsCount: Int,
+ val isFlickering: Boolean,
+ ) : WalletNFTItemUM() {
+
+ sealed class CollectionPreview {
+ data class Image(val url: String) : CollectionPreview()
+ data object More : CollectionPreview()
+ }
+ }
+}
\ No newline at end of file
diff --git a/core/ui/src/main/res/drawable/ic_nft_empty_36.xml b/core/ui/src/main/res/drawable/ic_nft_empty_36.xml
new file mode 100644
index 0000000000..b246aaf6e1
--- /dev/null
+++ b/core/ui/src/main/res/drawable/ic_nft_empty_36.xml
@@ -0,0 +1,13 @@
+
+
+
+
diff --git a/core/ui/src/main/res/drawable/ic_nft_preview_more_16.xml b/core/ui/src/main/res/drawable/ic_nft_preview_more_16.xml
new file mode 100644
index 0000000000..e7a9118384
--- /dev/null
+++ b/core/ui/src/main/res/drawable/ic_nft_preview_more_16.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
diff --git a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/model/WalletSettingsModel.kt b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/model/WalletSettingsModel.kt
index cbf8bafda7..764f57f277 100644
--- a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/model/WalletSettingsModel.kt
+++ b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/model/WalletSettingsModel.kt
@@ -106,9 +106,7 @@ internal class WalletSettingsModel @Inject constructor(
renameWallet = { openRenameWalletDialog(userWallet, dialogNavigation) },
isNFTFeatureEnabled = isNFTFeatureEnabled,
isNFTEnabled = isNFTEnabled,
- onCheckedNFTChange = { isChecked ->
- onCheckedNFTChange(isChecked)
- },
+ onCheckedNFTChange = ::onCheckedNFTChange,
forgetWallet = {
val message = DialogMessage(
message = resourceReference(R.string.user_wallet_list_delete_prompt),