Updated on 2026-08-14

This commit is contained in:
Tangem 2025-04-04 19:00:35 +05:00
parent 469c0a4317
commit b83d49cdea
13 changed files with 244 additions and 59 deletions

View file

@ -420,7 +420,11 @@ internal class ChildFactory @Inject constructor(
is AppRoute.NFTDetails -> is AppRoute.NFTDetails ->
createComponentChild( createComponentChild(
context = context, context = context,
params = NFTDetailsComponent.Params(userWalletId = route.userWalletId, nftAsset = route.nftAsset), params = NFTDetailsComponent.Params(
userWalletId = route.userWalletId,
nftAsset = route.nftAsset,
nftCollectionName = route.collectionName,
),
componentFactory = nftDetailsComponentFactory, componentFactory = nftDetailsComponentFactory,
) )
is AppRoute.OnboardingNote, is AppRoute.OnboardingNote,
@ -772,7 +776,11 @@ internal class ChildFactory @Inject constructor(
is AppRoute.NFTDetails -> is AppRoute.NFTDetails ->
route.asComponentChild( route.asComponentChild(
contextProvider = contextProvider(route, contextFactory), contextProvider = contextProvider(route, contextFactory),
params = NFTDetailsComponent.Params(userWalletId = route.userWalletId, nftAsset = route.nftAsset), params = NFTDetailsComponent.Params(
userWalletId = route.userWalletId,
nftAsset = route.nftAsset,
nftCollectionName = route.collectionName,
),
componentFactory = nftDetailsComponentFactory, componentFactory = nftDetailsComponentFactory,
) )
} }

View file

@ -294,5 +294,6 @@ sealed class AppRoute(val path: String) : Route {
data class NFTDetails( data class NFTDetails(
val userWalletId: UserWalletId, val userWalletId: UserWalletId,
val nftAsset: NFTAsset, val nftAsset: NFTAsset,
val collectionName: String,
) : AppRoute(path = "/nft_details/${userWalletId.stringValue}/${nftAsset.collectionId}/${nftAsset.id.stringValue}") ) : AppRoute(path = "/nft_details/${userWalletId.stringValue}/${nftAsset.collectionId}/${nftAsset.id.stringValue}")
} }

View file

@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemTheme
@ -19,12 +20,13 @@ fun CurrencyIconTopBadge(
alpha: Float, alpha: Float,
colorFilter: ColorFilter?, colorFilter: ColorFilter?,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
background: Color = TangemTheme.colors.background.primary,
) { ) {
Box( Box(
modifier = modifier modifier = modifier
.size(TangemTheme.dimens.size18) .size(TangemTheme.dimens.size18)
.background( .background(
color = TangemTheme.colors.background.primary, color = background,
shape = CircleShape, shape = CircleShape,
), ),
) { ) {

View file

@ -0,0 +1,17 @@
package com.tangem.features.nft.component
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.domain.nft.models.NFTAsset
import com.tangem.domain.wallets.models.UserWalletId
interface NFTDetailsBlockComponent : ComposableContentComponent {
data class Params(
val userWalletId: UserWalletId,
val nftAsset: NFTAsset,
val nftCollectionName: String,
)
interface Factory : ComponentFactory<Params, NFTDetailsBlockComponent>
}

View file

@ -10,6 +10,7 @@ interface NFTDetailsComponent : ComposableContentComponent {
data class Params( data class Params(
val userWalletId: UserWalletId, val userWalletId: UserWalletId,
val nftAsset: NFTAsset, val nftAsset: NFTAsset,
val nftCollectionName: String,
) )
interface Factory : ComponentFactory<Params, NFTDetailsComponent> interface Factory : ComponentFactory<Params, NFTDetailsComponent>

View file

@ -20,7 +20,7 @@ internal class UpdateDataStateTransformer(
private val onRetryClick: () -> Unit, private val onRetryClick: () -> Unit,
private val onExpandCollectionClick: (NFTCollection) -> Unit, private val onExpandCollectionClick: (NFTCollection) -> Unit,
private val onRetryAssetsClick: (NFTCollection) -> Unit, private val onRetryAssetsClick: (NFTCollection) -> Unit,
private val onAssetClick: (NFTAsset) -> Unit, private val onAssetClick: (NFTAsset, String) -> Unit,
private val initialSearchBarFactory: () -> SearchBarUM, private val initialSearchBarFactory: () -> SearchBarUM,
) : Transformer<NFTCollectionsStateUM> { ) : Transformer<NFTCollectionsStateUM> {
@ -105,12 +105,12 @@ internal class UpdateDataStateTransformer(
is NFTCollection.Assets.Value -> NFTCollectionAssetsListUM.Content( is NFTCollection.Assets.Value -> NFTCollectionAssetsListUM.Content(
items = assets items = assets
.items .items
.map { it.transform() } .map { it.transform(name.orEmpty()) }
.toPersistentList(), .toPersistentList(),
) )
} }
private fun NFTAsset.transform(): NFTCollectionAssetUM = NFTCollectionAssetUM( private fun NFTAsset.transform(collectionName: String): NFTCollectionAssetUM = NFTCollectionAssetUM(
id = id.toString(), id = id.toString(),
name = name.orEmpty(), name = name.orEmpty(),
imageUrl = media?.url, imageUrl = media?.url,
@ -121,7 +121,7 @@ internal class UpdateDataStateTransformer(
is NFTSalePrice.Value -> NFTSalePriceUM.Content(salePrice.value.toString()) is NFTSalePrice.Value -> NFTSalePriceUM.Content(salePrice.value.toString())
}, },
onItemClick = { onItemClick = {
onAssetClick(this) onAssetClick(this, collectionName)
}, },
) )

View file

@ -115,11 +115,12 @@ internal class NFTCollectionsModel @Inject constructor(
// TODO refresh all // TODO refresh all
} }
private fun onAssetClick(asset: NFTAsset) { private fun onAssetClick(asset: NFTAsset, collectionName: String) {
router.push( router.push(
AppRoute.NFTDetails( AppRoute.NFTDetails(
userWalletId = params.userWalletId, userWalletId = params.userWalletId,
nftAsset = asset, nftAsset = asset,
collectionName = collectionName,
), ),
) )
} }

View file

@ -2,7 +2,6 @@ package com.tangem.features.nft.collections.ui
import android.content.res.Configuration import android.content.res.Configuration
import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
@ -11,25 +10,19 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.rotate import androidx.compose.ui.draw.rotate
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import coil.compose.SubcomposeAsyncImage
import coil.request.ImageRequest
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.currency.icon.CurrencyIconTopBadge
import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.features.nft.collections.entity.NFTCollectionAssetsListUM import com.tangem.features.nft.collections.entity.NFTCollectionAssetsListUM
import com.tangem.features.nft.collections.entity.NFTCollectionUM import com.tangem.features.nft.collections.entity.NFTCollectionUM
import com.tangem.features.nft.common.ui.NFTLogo
import com.tangem.features.nft.impl.R import com.tangem.features.nft.impl.R
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
@ -52,7 +45,11 @@ internal fun NFTCollection(state: NFTCollectionUM, modifier: Modifier = Modifier
), ),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
) { ) {
Logo(state) NFTLogo(
imageUrl = state.logoUrl,
networkIconId = state.networkIconId,
background = TangemTheme.colors.background.primary,
)
Text(state) Text(state)
@ -81,46 +78,6 @@ internal fun NFTCollection(state: NFTCollectionUM, modifier: Modifier = Modifier
} }
} }
@Composable
private fun Logo(state: NFTCollectionUM) {
val networkBadgeOffset = TangemTheme.dimens.spacing6
Box(
modifier = Modifier,
) {
SubcomposeAsyncImage(
modifier = Modifier
.align(Alignment.CenterStart)
.size(TangemTheme.dimens.size36)
.clip(TangemTheme.shapes.roundedCorners8),
model = ImageRequest.Builder(LocalContext.current)
.data(state.logoUrl)
.crossfade(true)
.build(),
loading = {
RectangleShimmer(radius = TangemTheme.dimens.radius8)
},
error = {
Box(
modifier = Modifier
.clip(shape = TangemTheme.shapes.roundedCorners8)
.background(TangemTheme.colors.field.primary),
)
},
contentScale = ContentScale.Crop,
contentDescription = null,
)
CurrencyIconTopBadge(
modifier = Modifier
.offset(x = networkBadgeOffset, y = -networkBadgeOffset)
.align(Alignment.TopEnd),
iconResId = state.networkIconId,
alpha = 1f,
colorFilter = null,
)
}
}
@Composable @Composable
private fun RowScope.Text(state: NFTCollectionUM) { private fun RowScope.Text(state: NFTCollectionUM) {
Column( Column(

View file

@ -0,0 +1,60 @@
package com.tangem.features.nft.common.ui
import androidx.annotation.DrawableRes
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.size
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.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import coil.compose.SubcomposeAsyncImage
import coil.request.ImageRequest
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.currency.icon.CurrencyIconTopBadge
import com.tangem.core.ui.res.TangemTheme
@Composable
internal fun NFTLogo(imageUrl: String?, @DrawableRes networkIconId: Int, background: Color = Color.Transparent) {
val networkBadgeOffset = TangemTheme.dimens.spacing6
Box(
modifier = Modifier,
) {
SubcomposeAsyncImage(
modifier = Modifier
.align(Alignment.CenterStart)
.size(TangemTheme.dimens.size36)
.clip(TangemTheme.shapes.roundedCorners8),
model = ImageRequest.Builder(LocalContext.current)
.data(imageUrl)
.crossfade(true)
.build(),
loading = {
RectangleShimmer(radius = TangemTheme.dimens.radius8)
},
error = {
Box(
modifier = Modifier
.clip(shape = TangemTheme.shapes.roundedCorners8)
.background(TangemTheme.colors.field.primary),
)
},
contentScale = ContentScale.Crop,
contentDescription = null,
)
CurrencyIconTopBadge(
modifier = Modifier
.offset(x = networkBadgeOffset, y = -networkBadgeOffset)
.align(Alignment.TopEnd),
iconResId = networkIconId,
alpha = 1f,
colorFilter = null,
background = background,
)
}
}

View file

@ -0,0 +1,36 @@
package com.tangem.features.nft.details.block
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.ui.extensions.getActiveIconRes
import com.tangem.core.ui.extensions.stringReference
import com.tangem.features.nft.component.NFTDetailsBlockComponent
import com.tangem.features.nft.details.block.ui.NFTDetailsBlock
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
class DefaultNFTDetailsBlockComponent @AssistedInject constructor(
@Assisted context: AppComponentContext,
@Assisted private val params: NFTDetailsBlockComponent.Params,
) : NFTDetailsBlockComponent, AppComponentContext by context {
@Composable
override fun Content(modifier: Modifier) {
NFTDetailsBlock(
assetName = stringReference(params.nftAsset.name.orEmpty()),
collectionName = stringReference(params.nftCollectionName),
assetImage = params.nftAsset.media?.url,
networkIconRes = getActiveIconRes(params.nftAsset.network.id.value),
)
}
@AssistedFactory
interface Factory : NFTDetailsBlockComponent.Factory {
override fun create(
context: AppComponentContext,
params: NFTDetailsBlockComponent.Params,
): DefaultNFTDetailsBlockComponent
}
}

View file

@ -0,0 +1,84 @@
package com.tangem.features.nft.details.block.ui
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
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.tooling.preview.Preview
import androidx.compose.ui.unit.dp
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.features.nft.common.ui.NFTLogo
import com.tangem.features.nft.impl.R
@Composable
internal fun NFTDetailsBlock(
assetName: TextReference,
collectionName: TextReference,
assetImage: String?,
networkIconRes: Int,
) {
Column(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(16.dp))
.background(TangemTheme.colors.background.action)
.padding(12.dp),
verticalArrangement = Arrangement.spacedBy(6.dp),
) {
Text(
text = "NFT Asset",
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.secondary,
)
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(12.dp),
) {
NFTLogo(
assetImage,
networkIconRes,
background = TangemTheme.colors.background.action,
)
Column(
verticalArrangement = Arrangement.spacedBy(2.dp),
) {
Text(
text = assetName.resolveReference(),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.primary1,
)
Text(
text = collectionName.resolveReference(),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
)
}
}
}
}
// region Preview
@Composable
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
private fun NFTDetailsBlock_Preview() {
TangemThemePreview {
NFTDetailsBlock(
assetName = stringReference("NFT Asset Name"),
collectionName = stringReference("NFT Collection"),
assetImage = null,
networkIconRes = R.drawable.img_polygon_22,
)
}
}
// endregion

View file

@ -9,7 +9,8 @@ import com.tangem.features.nft.details.entity.NFTAssetUM
import com.tangem.features.nft.details.entity.NFTDetailsUM import com.tangem.features.nft.details.entity.NFTDetailsUM
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import javax.inject.Inject import javax.inject.Inject
@ModelScoped @ModelScoped
@ -56,6 +57,13 @@ internal class NFTDetailsModel @Inject constructor(
private fun onSendClick() { private fun onSendClick() {
// TODO implement // TODO implement
// router.push(
// AppRoute.NFTSend(
// userWalletId = params.userWalletId,
// nftAsset = params.nftAsset,
// nftCollectionName = params.nftCollectionName,
// ),
// )
} }
private fun navigateBack() { private fun navigateBack() {

View file

@ -1,13 +1,17 @@
package com.tangem.features.nft package com.tangem.features.nft.di
import com.tangem.core.configtoggle.feature.FeatureTogglesManager import com.tangem.core.configtoggle.feature.FeatureTogglesManager
import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.Model
import com.tangem.features.nft.DefaultNFTFeatureToggles
import com.tangem.features.nft.NFTFeatureToggles
import com.tangem.features.nft.collections.DefaultNFTCollectionsComponent import com.tangem.features.nft.collections.DefaultNFTCollectionsComponent
import com.tangem.features.nft.collections.model.NFTCollectionsModel import com.tangem.features.nft.collections.model.NFTCollectionsModel
import com.tangem.features.nft.component.NFTCollectionsComponent import com.tangem.features.nft.component.NFTCollectionsComponent
import com.tangem.features.nft.component.NFTDetailsBlockComponent
import com.tangem.features.nft.component.NFTDetailsComponent import com.tangem.features.nft.component.NFTDetailsComponent
import com.tangem.features.nft.component.NFTReceiveComponent import com.tangem.features.nft.component.NFTReceiveComponent
import com.tangem.features.nft.details.DefaultNFTDetailsComponent import com.tangem.features.nft.details.DefaultNFTDetailsComponent
import com.tangem.features.nft.details.block.DefaultNFTDetailsBlockComponent
import com.tangem.features.nft.details.model.NFTDetailsModel import com.tangem.features.nft.details.model.NFTDetailsModel
import com.tangem.features.nft.receive.DefaultNFTReceiveComponent import com.tangem.features.nft.receive.DefaultNFTReceiveComponent
import com.tangem.features.nft.receive.model.NFTReceiveModel import com.tangem.features.nft.receive.model.NFTReceiveModel
@ -62,4 +66,10 @@ internal interface NFTFeatureModuleBinds {
@IntoMap @IntoMap
@ClassKey(NFTDetailsModel::class) @ClassKey(NFTDetailsModel::class)
fun bindNFTDetailsModel(model: NFTDetailsModel): Model fun bindNFTDetailsModel(model: NFTDetailsModel): Model
@Binds
@Singleton
fun bindNFTDetailsBlockComponentFactory(
impl: DefaultNFTDetailsBlockComponent.Factory,
): NFTDetailsBlockComponent.Factory
} }