Updated on 2026-08-14

This commit is contained in:
Tangem 2025-04-22 17:33:39 +05:00
parent c7151d95a7
commit c227fd1001
18 changed files with 299 additions and 187 deletions

View file

@ -57,7 +57,7 @@ internal object NFTDomainModule {
fun providesGetNFTAvailableNetworksUseCase( fun providesGetNFTAvailableNetworksUseCase(
nftRepository: NFTRepository, nftRepository: NFTRepository,
currenciesRepository: CurrenciesRepository, currenciesRepository: CurrenciesRepository,
): GetNFTAvailableNetworksUseCase = GetNFTAvailableNetworksUseCase( ): GetNFTNetworksUseCase = GetNFTNetworksUseCase(
currenciesRepository = currenciesRepository, currenciesRepository = currenciesRepository,
nftRepository = nftRepository, nftRepository = nftRepository,
) )

View file

@ -546,7 +546,6 @@
<string name="nft_receive_unavailable_asset_warning_message">You haven\'t added this network yet. To start receiving NFTs, add it to the main screen!</string> <string name="nft_receive_unavailable_asset_warning_message">You haven\'t added this network yet. To start receiving NFTs, add it to the main screen!</string>
<string name="nft_receive_unavailable_asset_warning_title">Network not added</string> <string name="nft_receive_unavailable_asset_warning_title">Network not added</string>
<string name="nft_receive_unavailable_section_title">Not Added</string> <string name="nft_receive_unavailable_section_title">Not Added</string>
<string name="nft_send">Send NFT</string>
<string name="nft_traits_title">Traits</string> <string name="nft_traits_title">Traits</string>
<string name="nft_wallet_count">%1$d NFTs in %2$d collection</string> <string name="nft_wallet_count">%1$d NFTs in %2$d collection</string>
<string name="nft_wallet_receive_nft">Tap here to receive first NFT</string> <string name="nft_wallet_receive_nft">Tap here to receive first NFT</string>

View file

@ -42,6 +42,7 @@ fun ChainRow(model: ChainRowUM, modifier: Modifier = Modifier, action: @Composab
} else { } else {
null null
}, },
isEnabled = model.enabled,
accentMainText = true, accentMainText = true,
accentSecondText = false, accentSecondText = false,
) )

View file

@ -9,4 +9,5 @@ data class ChainRowUM(
val type: String, val type: String,
val icon: CurrencyIconState, val icon: CurrencyIconState,
val showCustom: Boolean, val showCustom: Boolean,
val enabled: Boolean = true,
) )

View file

@ -2,7 +2,9 @@ package com.tangem.data.nft
import arrow.core.Either import arrow.core.Either
import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
import com.tangem.blockchainsdk.utils.fromNetworkId import com.tangem.blockchainsdk.utils.fromNetworkId
import com.tangem.data.common.currency.getNetwork
import com.tangem.datasource.local.nft.NFTPersistenceStore import com.tangem.datasource.local.nft.NFTPersistenceStore
import com.tangem.datasource.local.nft.NFTPersistenceStoreFactory import com.tangem.datasource.local.nft.NFTPersistenceStoreFactory
import com.tangem.datasource.local.nft.NFTRuntimeStore import com.tangem.datasource.local.nft.NFTRuntimeStore
@ -10,6 +12,7 @@ import com.tangem.datasource.local.nft.NFTRuntimeStoreFactory
import com.tangem.datasource.local.nft.converter.NFTSdkAssetIdentifierConverter import com.tangem.datasource.local.nft.converter.NFTSdkAssetIdentifierConverter
import com.tangem.datasource.local.nft.converter.NFTSdkCollectionConverter import com.tangem.datasource.local.nft.converter.NFTSdkCollectionConverter
import com.tangem.datasource.local.nft.converter.NFTSdkCollectionIdentifierConverter import com.tangem.datasource.local.nft.converter.NFTSdkCollectionIdentifierConverter
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.models.StatusSource import com.tangem.domain.models.StatusSource
import com.tangem.domain.nft.models.NFTAsset import com.tangem.domain.nft.models.NFTAsset
import com.tangem.domain.nft.models.NFTCollection import com.tangem.domain.nft.models.NFTCollection
@ -37,6 +40,8 @@ internal class DefaultNFTRepository @Inject constructor(
private val nftRuntimeStoreFactory: NFTRuntimeStoreFactory, private val nftRuntimeStoreFactory: NFTRuntimeStoreFactory,
private val walletManagersFacade: WalletManagersFacade, private val walletManagersFacade: WalletManagersFacade,
private val dispatchers: CoroutineDispatcherProvider, private val dispatchers: CoroutineDispatcherProvider,
private val excludedBlockchains: ExcludedBlockchains,
private val userWalletsStore: UserWalletsStore,
) : NFTRepository { ) : NFTRepository {
private val networkJobs = ConcurrentHashMap<Network, JobHolder>() private val networkJobs = ConcurrentHashMap<Network, JobHolder>()
@ -129,6 +134,24 @@ internal class DefaultNFTRepository @Inject constructor(
override suspend fun isNFTSupported(network: Network): Boolean = network.canHandleNFTs() override suspend fun isNFTSupported(network: Network): Boolean = network.canHandleNFTs()
override suspend fun getNFTSupportedNetworks(userWalletId: UserWalletId): List<Network> {
val userWallet = requireNotNull(userWalletsStore.getSyncOrNull(userWalletId)) {
"User wallet [$userWalletId] not found"
}
return Blockchain
.entries
.filter { it.canHandleNFTs() }
.filter { !it.isTestnet() }
.mapNotNull {
getNetwork(
blockchain = it,
extraDerivationPath = null,
scanResponse = userWallet.scanResponse,
excludedBlockchains = excludedBlockchains,
)
}
}
override suspend fun getNFTExploreUrl(network: Network, assetIdentifier: NFTAsset.Identifier): String? = override suspend fun getNFTExploreUrl(network: Network, assetIdentifier: NFTAsset.Identifier): String? =
walletManagersFacade.getNFTExploreUrl( walletManagersFacade.getNFTExploreUrl(
network = network, network = network,

View file

@ -0,0 +1,8 @@
package com.tangem.domain.nft.models
import com.tangem.domain.tokens.model.Network
data class NFTNetworks(
val availableNetworks: List<Network>,
val notAvailableNetworks: List<Network>,
)

View file

@ -1,5 +1,6 @@
package com.tangem.domain.nft package com.tangem.domain.nft
import com.tangem.domain.nft.models.NFTNetworks
import com.tangem.domain.tokens.model.Network import com.tangem.domain.tokens.model.Network
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -7,11 +8,16 @@ import kotlinx.coroutines.withContext
class FilterNFTAvailableNetworksUseCase( class FilterNFTAvailableNetworksUseCase(
private val dispatchers: CoroutineDispatcherProvider, private val dispatchers: CoroutineDispatcherProvider,
) { ) {
suspend operator fun invoke(networks: List<Network>, searchQuery: String): List<Network> = suspend operator fun invoke(networks: NFTNetworks, searchQuery: String): NFTNetworks =
withContext(dispatchers.default) { withContext(dispatchers.default) {
networks.filter { NFTNetworks(
it.name.contains(searchQuery, ignoreCase = true) || availableNetworks = networks.availableNetworks.filter(searchQuery),
it.currencySymbol.contains(searchQuery, ignoreCase = true) notAvailableNetworks = networks.notAvailableNetworks.filter(searchQuery),
} )
} }
private fun List<Network>.filter(searchQuery: String) = filter {
it.name.contains(searchQuery, ignoreCase = true) ||
it.currencySymbol.contains(searchQuery, ignoreCase = true)
}
} }

View file

@ -1,22 +0,0 @@
package com.tangem.domain.nft
import com.tangem.domain.nft.repository.NFTRepository
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
class GetNFTAvailableNetworksUseCase(
private val currenciesRepository: CurrenciesRepository,
private val nftRepository: NFTRepository,
) {
operator fun invoke(userWalletId: UserWalletId): Flow<List<Network>> = currenciesRepository
.getWalletCurrenciesUpdates(userWalletId)
.map { cryptoCurrencies ->
cryptoCurrencies
.map { cryptoCurrency -> cryptoCurrency.network }
.distinct()
.filter { nftRepository.isNFTSupported(it) }
}
}

View file

@ -0,0 +1,32 @@
package com.tangem.domain.nft
import com.tangem.domain.nft.models.NFTNetworks
import com.tangem.domain.nft.repository.NFTRepository
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
class GetNFTNetworksUseCase(
private val currenciesRepository: CurrenciesRepository,
private val nftRepository: NFTRepository,
) {
operator fun invoke(userWalletId: UserWalletId): Flow<NFTNetworks> = currenciesRepository
.getWalletCurrenciesUpdates(userWalletId)
.map { cryptoCurrencies ->
val availableNetworks = cryptoCurrencies
.map { cryptoCurrency -> cryptoCurrency.network }
.filter { nftRepository.isNFTSupported(it) }
.sortedBy { it.name }
val notAvailableNetworks = nftRepository
.getNFTSupportedNetworks(userWalletId)
.filter { supportedNetwork -> availableNetworks.none { it.id == supportedNetwork.id } }
.sortedBy { it.name }
NFTNetworks(
availableNetworks = availableNetworks,
notAvailableNetworks = notAvailableNetworks,
)
}
}

View file

@ -18,5 +18,7 @@ interface NFTRepository {
suspend fun isNFTSupported(network: Network): Boolean suspend fun isNFTSupported(network: Network): Boolean
suspend fun getNFTSupportedNetworks(userWalletId: UserWalletId): List<Network>
suspend fun getNFTExploreUrl(network: Network, assetIdentifier: NFTAsset.Identifier): String? suspend fun getNFTExploreUrl(network: Network, assetIdentifier: NFTAsset.Identifier): String?
} }

View file

@ -1,10 +1,9 @@
package com.tangem.features.nft.receive.entity package com.tangem.features.nft.receive.entity
import androidx.annotation.DrawableRes import com.tangem.core.ui.components.rows.model.ChainRowUM
internal data class NFTNetworkUM( internal data class NFTNetworkUM(
val id: String, val id: String,
@DrawableRes val iconRes: Int, val chainRowUM: ChainRowUM,
val name: String,
val onItemClick: () -> Unit, val onItemClick: () -> Unit,
) )

View file

@ -19,7 +19,8 @@ internal data class NFTReceiveUM(
data object Empty : Networks() data object Empty : Networks()
data class Content( data class Content(
val items: ImmutableList<NFTNetworkUM>, val availableItems: ImmutableList<NFTNetworkUM>,
val notAvailableItems: ImmutableList<NFTNetworkUM>,
) : Networks() ) : Networks()
} }
} }

View file

@ -1,6 +1,9 @@
package com.tangem.features.nft.receive.entity.transformer package com.tangem.features.nft.receive.entity.transformer
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.components.rows.model.ChainRowUM
import com.tangem.core.ui.extensions.getActiveIconRes import com.tangem.core.ui.extensions.getActiveIconRes
import com.tangem.domain.nft.models.NFTNetworks
import com.tangem.domain.tokens.model.Network import com.tangem.domain.tokens.model.Network
import com.tangem.features.nft.receive.entity.NFTNetworkUM import com.tangem.features.nft.receive.entity.NFTNetworkUM
import com.tangem.features.nft.receive.entity.NFTReceiveUM import com.tangem.features.nft.receive.entity.NFTReceiveUM
@ -8,27 +11,44 @@ import com.tangem.utils.transformer.Transformer
import kotlinx.collections.immutable.toPersistentList import kotlinx.collections.immutable.toPersistentList
internal class UpdateDataStateTransformer( internal class UpdateDataStateTransformer(
private val networks: List<Network>, private val networks: NFTNetworks,
private val onNetworkClick: (Network) -> Unit, private val onNetworkClick: (Network, Boolean) -> Unit,
) : Transformer<NFTReceiveUM> { ) : Transformer<NFTReceiveUM> {
override fun transform(prevState: NFTReceiveUM): NFTReceiveUM = prevState.copy( override fun transform(prevState: NFTReceiveUM): NFTReceiveUM = prevState.copy(
networks = when { networks = when {
networks.isEmpty() -> NFTReceiveUM.Networks.Empty networks.availableNetworks.isEmpty() && networks.notAvailableNetworks.isEmpty()
-> NFTReceiveUM.Networks.Empty
else -> NFTReceiveUM.Networks.Content( else -> NFTReceiveUM.Networks.Content(
items = networks availableItems = networks.availableNetworks
.map { it.transform() } .map { it.transform(true) }
.toPersistentList(),
notAvailableItems = networks.notAvailableNetworks
.map { it.transform(false) }
.toPersistentList(), .toPersistentList(),
) )
}, },
) )
private fun Network.transform(): NFTNetworkUM = NFTNetworkUM( private fun Network.transform(enabled: Boolean): NFTNetworkUM {
id = id.value, val custom = derivationPath is Network.DerivationPath.Custom
iconRes = getActiveIconRes(id.value), return NFTNetworkUM(
name = name, id = id.value + derivationPath.value,
onItemClick = { chainRowUM = ChainRowUM(
onNetworkClick(this) name = name,
}, type = "",
) icon = CurrencyIconState.CoinIcon(
url = null,
fallbackResId = getActiveIconRes(id.value),
isGrayscale = !enabled,
showCustomBadge = custom,
),
showCustom = custom,
enabled = enabled,
),
onItemClick = {
onNetworkClick(this, enabled)
},
)
}
} }

View file

@ -4,14 +4,16 @@ import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.navigation.share.ShareManager import com.tangem.core.navigation.share.ShareManager
import com.tangem.core.ui.clipboard.ClipboardManager import com.tangem.core.ui.clipboard.ClipboardManager
import com.tangem.core.ui.components.fields.InputManager import com.tangem.core.ui.components.fields.InputManager
import com.tangem.core.ui.components.fields.entity.SearchBarUM import com.tangem.core.ui.components.fields.entity.SearchBarUM
import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.message.DialogMessage
import com.tangem.domain.nft.FilterNFTAvailableNetworksUseCase import com.tangem.domain.nft.FilterNFTAvailableNetworksUseCase
import com.tangem.domain.nft.GetNFTAvailableNetworksUseCase import com.tangem.domain.nft.GetNFTNetworksUseCase
import com.tangem.domain.nft.GetNFTNetworkStatusUseCase import com.tangem.domain.nft.GetNFTNetworkStatusUseCase
import com.tangem.domain.nft.analytics.NFTAnalyticsEvent import com.tangem.domain.nft.analytics.NFTAnalyticsEvent
import com.tangem.domain.tokens.model.Network import com.tangem.domain.tokens.model.Network
@ -34,12 +36,13 @@ import javax.inject.Inject
internal class NFTReceiveModel @Inject constructor( internal class NFTReceiveModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider, override val dispatchers: CoroutineDispatcherProvider,
private val searchManager: InputManager, private val searchManager: InputManager,
private val getNFTAvailableNetworksUseCase: GetNFTAvailableNetworksUseCase, private val getNFTNetworksUseCase: GetNFTNetworksUseCase,
private val filterNFTAvailableNetworksUseCase: FilterNFTAvailableNetworksUseCase, private val filterNFTAvailableNetworksUseCase: FilterNFTAvailableNetworksUseCase,
private val getNFTNetworkStatusUseCase: GetNFTNetworkStatusUseCase, private val getNFTNetworkStatusUseCase: GetNFTNetworkStatusUseCase,
private val clipboardManager: ClipboardManager, private val clipboardManager: ClipboardManager,
private val shareManager: ShareManager, private val shareManager: ShareManager,
private val analyticsEventHandler: AnalyticsEventHandler, private val analyticsEventHandler: AnalyticsEventHandler,
private val messageSender: UiMessageSender,
paramsContainer: ParamsContainer, paramsContainer: ParamsContainer,
) : Model() { ) : Model() {
@ -53,7 +56,10 @@ internal class NFTReceiveModel @Inject constructor(
formatArgs = wrappedList(params.walletName), formatArgs = wrappedList(params.walletName),
), ),
search = getInitialSearchBar(), search = getInitialSearchBar(),
networks = NFTReceiveUM.Networks.Content(persistentListOf()), networks = NFTReceiveUM.Networks.Content(
availableItems = persistentListOf(),
notAvailableItems = persistentListOf(),
),
bottomSheetConfig = null, bottomSheetConfig = null,
), ),
) )
@ -67,7 +73,7 @@ internal class NFTReceiveModel @Inject constructor(
private fun subscribeToNFTAvailableNetworks() { private fun subscribeToNFTAvailableNetworks() {
combine( combine(
flow = getNFTAvailableNetworksUseCase(params.userWalletId), flow = getNFTNetworksUseCase(params.userWalletId),
flow2 = searchManager.query.distinctUntilChanged(), flow2 = searchManager.query.distinctUntilChanged(),
) { networks, query -> ) { networks, query ->
filterNFTAvailableNetworksUseCase(networks, query) filterNFTAvailableNetworksUseCase(networks, query)
@ -113,31 +119,40 @@ internal class NFTReceiveModel @Inject constructor(
} }
} }
private fun onNetworkClick(network: Network) { private fun onNetworkClick(network: Network, enabled: Boolean) {
modelScope.launch { if (!enabled) {
analyticsEventHandler.send(NFTAnalyticsEvent.Receive.BlockchainChosen(network.name)) val message = DialogMessage(
title = resourceReference(R.string.nft_receive_unavailable_asset_warning_title),
message = resourceReference(R.string.nft_receive_unavailable_asset_warning_message),
)
val networkStatus = getNFTNetworkStatusUseCase.invoke( messageSender.send(message)
userWalletId = params.userWalletId, } else {
network = network, modelScope.launch {
) ?: return@launch analyticsEventHandler.send(NFTAnalyticsEvent.Receive.BlockchainChosen(network.name))
when (val value = networkStatus.value) { val networkStatus = getNFTNetworkStatusUseCase.invoke(
is NetworkStatus.Verified -> { userWalletId = params.userWalletId,
_state.update { network = network,
ShowReceiveBottomSheetTransformer( ) ?: return@launch
network = network,
networkAddress = value.address, when (val value = networkStatus.value) {
onDismissBottomSheet = ::onReceiveBottomSheetDismiss, is NetworkStatus.Verified -> {
onCopyClick = { text -> onCopyClick(text, network) }, _state.update {
onShareClick = { text -> onShareClick(text, network) }, ShowReceiveBottomSheetTransformer(
).transform(it) network = network,
networkAddress = value.address,
onDismissBottomSheet = ::onReceiveBottomSheetDismiss,
onCopyClick = { text -> onCopyClick(text, network) },
onShareClick = { text -> onShareClick(text, network) },
).transform(it)
}
} }
is NetworkStatus.MissedDerivation,
is NetworkStatus.NoAccount,
is NetworkStatus.Unreachable,
-> Unit
} }
is NetworkStatus.MissedDerivation,
is NetworkStatus.NoAccount,
is NetworkStatus.Unreachable,
-> Unit
} }
} }
} }

View file

@ -1,73 +0,0 @@
package com.tangem.features.nft.receive.ui
import android.content.res.Configuration
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
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.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.features.nft.impl.R
import com.tangem.features.nft.receive.entity.NFTNetworkUM
@Composable
internal fun NFTNetwork(state: NFTNetworkUM, modifier: Modifier = Modifier) {
Row(
modifier = modifier
.fillMaxWidth()
.clickable { state.onItemClick() }
.padding(TangemTheme.dimens.spacing12),
verticalAlignment = Alignment.CenterVertically,
) {
Image(
modifier = Modifier
.size(TangemTheme.dimens.size24)
.clip(CircleShape),
painter = painterResource(state.iconRes),
contentDescription = null,
)
Text(
modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing12),
text = state.name,
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.primary1,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
}
@Preview(widthDp = 360, showBackground = true)
@Preview(widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun Preview_NFTNetwork(@PreviewParameter(NFTNetworkProvider::class) state: NFTNetworkUM) {
TangemThemePreview {
NFTNetwork(
state = state,
)
}
}
private class NFTNetworkProvider : CollectionPreviewParameterProvider<NFTNetworkUM>(
collection = listOf(
NFTNetworkUM(
id = "item1",
name = "Nethers #0853",
iconRes = R.drawable.img_eth_22,
onItemClick = { },
),
),
)

View file

@ -1,7 +1,6 @@
package com.tangem.features.nft.receive.ui package com.tangem.features.nft.receive.ui
import androidx.activity.compose.BackHandler import androidx.activity.compose.BackHandler
import androidx.compose.animation.AnimatedContent
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -51,18 +50,9 @@ internal fun NFTReceive(state: NFTReceiveUM, modifier: Modifier = Modifier) {
colors = TangemSearchBarDefaults.secondaryTextFieldColors, colors = TangemSearchBarDefaults.secondaryTextFieldColors,
) )
val contentModifier = Modifier when (val networks = state.networks) {
.padding(TangemTheme.dimens.spacing16) is NFTReceiveUM.Networks.Content -> NFTReceiveNetworksContent(networks)
is NFTReceiveUM.Networks.Empty -> NFTReceiveNetworksEmpty()
AnimatedContent(
targetState = state.networks,
contentKey = { it::class },
label = "NFT Receive",
) {
when (val networks = it) {
is NFTReceiveUM.Networks.Content -> NFTReceiveNetworksContent(networks, contentModifier)
is NFTReceiveUM.Networks.Empty -> NFTReceiveNetworksEmpty(contentModifier)
}
} }
} }
}, },

View file

@ -1,19 +1,29 @@
package com.tangem.features.nft.receive.ui package com.tangem.features.nft.receive.ui
import android.content.res.Configuration import android.content.res.Configuration
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.components.rows.ChainRow
import com.tangem.core.ui.components.rows.model.ChainRowUM
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.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.impl.R import com.tangem.features.nft.impl.R
@ -27,40 +37,86 @@ internal fun NFTReceiveNetworksContent(state: NFTReceiveUM.Networks.Content, mod
LazyColumn( LazyColumn(
modifier = modifier modifier = modifier
.fillMaxWidth() .fillMaxSize()
.clip(TangemTheme.shapes.roundedCornersXMedium) .padding(
.background(TangemTheme.colors.background.primary) vertical = TangemTheme.dimens.spacing8,
.animateContentSize(), ),
state = listState, state = listState,
contentPadding = PaddingValues(
horizontal = TangemTheme.dimens.spacing16,
),
) { ) {
item( if (state.availableItems.isNotEmpty()) {
key = "title", item(
) { key = "available_title",
Title() ) {
Title(resourceReference(R.string.nft_receive_available_section_title))
}
networks(state.availableItems)
} }
items( if (state.notAvailableItems.isNotEmpty()) {
items = state.items, item(
key = NFTNetworkUM::id, key = "not_available_title",
) { network -> ) {
NFTNetwork( Title(resourceReference(R.string.nft_receive_unavailable_section_title))
state = network, }
modifier = Modifier.animateItem(), networks(state.notAvailableItems)
)
} }
} }
} }
private fun LazyListScope.networks(items: List<NFTNetworkUM>) {
itemsIndexed(
items = items,
key = { _, item -> item.id },
) { index, network ->
val isLastItem = index == items.size - 1
ChainRow(
modifier = Modifier
.composed {
if (isLastItem) {
padding(
bottom = TangemTheme.dimens.spacing8,
)
.clip(
RoundedCornerShape(
bottomStart = TangemTheme.dimens.radius16,
bottomEnd = TangemTheme.dimens.radius16,
),
)
} else {
this
}
}
.background(TangemTheme.colors.background.primary)
.clickable { network.onItemClick() },
model = network.chainRowUM,
)
}
}
@Composable @Composable
private fun Title(modifier: Modifier = Modifier) { private fun Title(text: TextReference, modifier: Modifier = Modifier) {
Text( Text(
modifier = modifier modifier = modifier
.fillMaxWidth()
.padding(
top = TangemTheme.dimens.spacing8,
)
.background(
color = TangemTheme.colors.background.primary,
shape = RoundedCornerShape(
topStart = TangemTheme.dimens.radius16,
topEnd = TangemTheme.dimens.radius16,
),
)
.padding( .padding(
start = TangemTheme.dimens.spacing12, start = TangemTheme.dimens.spacing12,
top = TangemTheme.dimens.spacing12, top = TangemTheme.dimens.spacing12,
end = TangemTheme.dimens.spacing12, end = TangemTheme.dimens.spacing12,
bottom = TangemTheme.dimens.spacing4, bottom = TangemTheme.dimens.spacing4,
), ),
text = stringResourceSafe(R.string.nft_receive_choose_network), text = text.resolveReference(),
style = TangemTheme.typography.subtitle2, style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.tertiary, color = TangemTheme.colors.text.tertiary,
) )
@ -74,32 +130,84 @@ private fun Preview_NFTReceiveNetworksUM() {
TangemThemePreview { TangemThemePreview {
NFTReceiveNetworksContent( NFTReceiveNetworksContent(
state = NFTReceiveUM.Networks.Content( state = NFTReceiveUM.Networks.Content(
items = persistentListOf( availableItems = persistentListOf(
NFTNetworkUM( NFTNetworkUM(
id = "item1", id = "item1",
name = "Ethereum", chainRowUM = ChainRowUM(
iconRes = R.drawable.img_eth_22, name = "Ethereum",
type = "",
icon = CurrencyIconState.CoinIcon(
url = null,
fallbackResId = R.drawable.img_eth_22,
isGrayscale = false,
showCustomBadge = false,
),
showCustom = false,
),
onItemClick = { }, onItemClick = { },
), ),
NFTNetworkUM( NFTNetworkUM(
id = "item2", id = "item2",
name = "Polygon", chainRowUM = ChainRowUM(
iconRes = R.drawable.img_polygon_22, name = "Polygon",
type = "",
icon = CurrencyIconState.CoinIcon(
url = null,
fallbackResId = R.drawable.img_polygon_22,
isGrayscale = false,
showCustomBadge = false,
),
showCustom = false,
),
onItemClick = { }, onItemClick = { },
), ),
NFTNetworkUM( NFTNetworkUM(
id = "item3", id = "item3",
name = "BSC", chainRowUM = ChainRowUM(
iconRes = R.drawable.img_bsc_22, name = "BSC",
type = "",
icon = CurrencyIconState.CoinIcon(
url = null,
fallbackResId = R.drawable.img_bsc_22,
isGrayscale = false,
showCustomBadge = false,
),
showCustom = false,
),
onItemClick = { }, onItemClick = { },
), ),
NFTNetworkUM( NFTNetworkUM(
id = "item4", id = "item4",
name = "Avalanche", chainRowUM = ChainRowUM(
iconRes = R.drawable.img_avalanche_22, name = "Avalanche",
type = "",
icon = CurrencyIconState.CoinIcon(
url = null,
fallbackResId = R.drawable.img_avalanche_22,
isGrayscale = false,
showCustomBadge = false,
),
showCustom = false,
),
onItemClick = { },
),
NFTNetworkUM(
id = "item5",
chainRowUM = ChainRowUM(
name = "Avalanche",
type = "",
icon = CurrencyIconState.CoinIcon(
url = null,
fallbackResId = R.drawable.img_avalanche_22,
isGrayscale = false,
showCustomBadge = true,
),
showCustom = true,
),
onItemClick = { }, onItemClick = { },
), ),
), ),
notAvailableItems = persistentListOf(),
), ),
) )
} }

View file

@ -3,6 +3,7 @@ package com.tangem.features.nft.receive.ui
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
@ -16,6 +17,7 @@ internal fun NFTReceiveNetworksEmpty(modifier: Modifier = Modifier) {
Box( Box(
modifier = modifier modifier = modifier
.fillMaxSize() .fillMaxSize()
.padding(TangemTheme.dimens.spacing16)
.background(TangemTheme.colors.background.secondary), .background(TangemTheme.colors.background.secondary),
) { ) {
Text( Text(