Updated on 2026-08-14

This commit is contained in:
Tangem 2025-08-21 17:11:54 +02:00
parent 33fd5a927a
commit b8f3d5315f
20 changed files with 607 additions and 118 deletions

View file

@ -22,6 +22,7 @@ import com.tangem.features.nft.details.info.NFTDetailsInfoComponent
import com.tangem.features.nft.entity.NFTSendSuccessListener
import com.tangem.features.nft.receive.NFTReceiveComponent
import com.tangem.features.nft.traits.NFTAssetTraitsComponent
import com.tangem.features.tokenreceive.TokenReceiveComponent
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@ -35,6 +36,7 @@ internal class DefaultNFTComponent @AssistedInject constructor(
@Assisted private val params: NFTComponent.Params,
private val nftDetailsInfoComponentFactory: NFTDetailsInfoComponent.Factory,
nftSendSuccessListener: NFTSendSuccessListener,
private val tokenReceiveComponentFactory: TokenReceiveComponent.Factory,
) : NFTComponent, AppComponentContext by appComponentContext {
private val stackNavigation = StackNavigation<NFTRoute>()
@ -134,6 +136,7 @@ internal class DefaultNFTComponent @AssistedInject constructor(
walletName = params.walletName,
onBackClick = ::onChildBack,
),
tokenReceiveComponentFactory = tokenReceiveComponentFactory,
)
private fun getDetailsComponent(

View file

@ -4,29 +4,58 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.arkivanov.decompose.ComponentContext
import com.arkivanov.decompose.extensions.compose.subscribeAsState
import com.arkivanov.decompose.router.slot.childSlot
import com.arkivanov.decompose.router.slot.dismiss
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.context.childByContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.domain.models.TokenReceiveConfig
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.features.nft.receive.model.NFTReceiveModel
import com.tangem.features.nft.receive.ui.NFTReceive
import com.tangem.features.tokenreceive.TokenReceiveComponent
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
internal class NFTReceiveComponent @AssistedInject constructor(
@Assisted context: AppComponentContext,
@Assisted private val params: Params,
private val tokenReceiveComponentFactory: TokenReceiveComponent.Factory,
) : ComposableContentComponent, AppComponentContext by context {
private val model: NFTReceiveModel = getOrCreateModel(params)
private val bottomSheetSlot = childSlot(
source = model.bottomSheetNavigation,
serializer = TokenReceiveConfig.serializer(),
handleBackButton = false,
childFactory = ::bottomSheetChild,
)
@Composable
override fun Content(modifier: Modifier) {
val state by model.state.collectAsStateWithLifecycle()
val bottomSheet by bottomSheetSlot.subscribeAsState()
NFTReceive(state, modifier)
bottomSheet.child?.instance?.BottomSheet()
}
private fun bottomSheetChild(
config: TokenReceiveConfig,
componentContext: ComponentContext,
): ComposableBottomSheetComponent = tokenReceiveComponentFactory.create(
context = childByContext(componentContext),
params = TokenReceiveComponent.Params(
config = config,
onDismiss = model.bottomSheetNavigation::dismiss,
),
)
data class Params(
val userWalletId: UserWalletId,
val walletName: String,

View file

@ -1,5 +1,7 @@
package com.tangem.features.nft.receive.model
import com.arkivanov.decompose.router.slot.SlotNavigation
import com.arkivanov.decompose.router.slot.activate
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
@ -12,12 +14,20 @@ import com.tangem.core.ui.components.fields.entity.SearchBarUM
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.message.DialogMessage
import com.tangem.domain.models.Asset
import com.tangem.domain.models.ReceiveAddressModel
import com.tangem.domain.models.TokenReceiveConfig
import com.tangem.domain.models.TokenReceiveNotification
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.network.NetworkAddress
import com.tangem.domain.models.network.NetworkStatus
import com.tangem.domain.nft.FilterNFTAvailableNetworksUseCase
import com.tangem.domain.nft.GetNFTCurrencyUseCase
import com.tangem.domain.nft.GetNFTNetworkStatusUseCase
import com.tangem.domain.nft.GetNFTNetworksUseCase
import com.tangem.domain.nft.analytics.NFTAnalyticsEvent
import com.tangem.domain.tokens.GetViewedTokenReceiveWarningUseCase
import com.tangem.domain.transaction.usecase.GetEnsNameUseCase
import com.tangem.features.nft.impl.R
import com.tangem.features.nft.receive.NFTReceiveComponent
import com.tangem.features.nft.receive.entity.NFTReceiveUM
@ -25,6 +35,8 @@ import com.tangem.features.nft.receive.entity.transformer.ShowReceiveBottomSheet
import com.tangem.features.nft.receive.entity.transformer.ToggleSearchBarTransformer
import com.tangem.features.nft.receive.entity.transformer.UpdateDataStateTransformer
import com.tangem.features.nft.receive.entity.transformer.UpdateSearchQueryTransformer
import com.tangem.features.tokenreceive.TokenReceiveFeatureToggle
import com.tangem.lib.crypto.BlockchainUtils
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.flow.*
@ -43,11 +55,17 @@ internal class NFTReceiveModel @Inject constructor(
private val shareManager: ShareManager,
private val analyticsEventHandler: AnalyticsEventHandler,
private val messageSender: UiMessageSender,
private val tokenReceiveFeatureToggle: TokenReceiveFeatureToggle,
private val getViewedTokenReceiveWarningUseCase: GetViewedTokenReceiveWarningUseCase,
private val getEnsNameUseCase: GetEnsNameUseCase,
private val getNFTCurrencyUseCase: GetNFTCurrencyUseCase,
paramsContainer: ParamsContainer,
) : Model() {
private val params: NFTReceiveComponent.Params = paramsContainer.require()
val bottomSheetNavigation: SlotNavigation<TokenReceiveConfig> = SlotNavigation()
private val _state = MutableStateFlow(
value = NFTReceiveUM(
onBackClick = params.onBackClick,
@ -138,14 +156,23 @@ internal class NFTReceiveModel @Inject constructor(
when (val value = networkStatus.value) {
is NetworkStatus.Verified -> {
_state.update {
ShowReceiveBottomSheetTransformer(
network = network,
networkAddress = value.address,
onDismissBottomSheet = ::onReceiveBottomSheetDismiss,
onCopyClick = { text -> onCopyClick(text, network) },
onShareClick = { text -> onShareClick(text, network) },
).transform(it)
if (tokenReceiveFeatureToggle.isNewTokenReceiveEnabled) {
bottomSheetNavigation.activate(
configuration = configureReceiveAddresses(
addresses = value.address,
network = network,
),
)
} else {
_state.update {
ShowReceiveBottomSheetTransformer(
network = network,
networkAddress = value.address,
onDismissBottomSheet = ::onReceiveBottomSheetDismiss,
onCopyClick = { text -> onCopyClick(text, network) },
onShareClick = { text -> onShareClick(text, network) },
).transform(it)
}
}
}
is NetworkStatus.MissedDerivation,
@ -166,4 +193,56 @@ internal class NFTReceiveModel @Inject constructor(
analyticsEventHandler.send(NFTAnalyticsEvent.Receive.ShareAddress(network.name))
shareManager.shareText(text = text)
}
private suspend fun configureReceiveAddresses(addresses: NetworkAddress, network: Network): TokenReceiveConfig {
val cryptoCurrency = getNFTCurrencyUseCase.invoke(network)
val ensName = getEnsNameUseCase.invoke(
userWalletId = params.userWalletId,
network = network,
address = addresses.defaultAddress.value,
)
val receiveAddresses = buildList {
ensName?.let { ens ->
add(
ReceiveAddressModel(
nameService = ReceiveAddressModel.NameService.Ens,
value = ens,
displayName = ens,
),
)
}
addresses.availableAddresses.map { address ->
add(
ReceiveAddressModel(
nameService = ReceiveAddressModel.NameService.Default,
value = address.value,
displayName = cryptoCurrency.symbol,
),
)
}
}
val notifications = buildList {
if (BlockchainUtils.isSolana(network.rawId)) {
add(
TokenReceiveNotification(
title = R.string.nft_receive_unsupported_types,
subtitle = R.string.nft_receive_unsupported_types_description,
),
)
}
}
return TokenReceiveConfig(
shouldShowWarning = Asset.NFT.name !in getViewedTokenReceiveWarningUseCase(),
cryptoCurrency = cryptoCurrency,
userWalletId = params.userWalletId,
showMemoDisclaimer = false,
receiveAddress = receiveAddresses,
tokenReceiveNotification = notifications,
asset = Asset.NFT,
)
}
}