From c1d4bf79cbf304a7a8cb343f671093c3c88902cd Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 2 Apr 2025 14:20:37 +0500 Subject: [PATCH] Updated on 2026-08-14 --- .../tangem/tap/routing/utils/ChildFactory.kt | 14 +++++++ common/routing/build.gradle.kts | 1 + .../com/tangem/common/routing/AppRoute.kt | 7 ++++ domain/networks/.gitignore | 1 + domain/nft/models/build.gradle.kts | 5 ++- .../com/tangem/domain/nft/models/NFTAsset.kt | 23 ++++++++++-- .../tangem/domain/nft/models/NFTCollection.kt | 16 ++++++++ .../tangem/domain/nft/models/NFTSalePrice.kt | 10 ++++- features/nft/api/build.gradle.kts | 1 + .../nft/component/NFTDetailsComponent.kt | 16 ++++++++ .../tangem/features/nft/NFTFeatureModule.kt | 12 ++++++ .../nft/details/DefaultNFTDetailsComponent.kt | 37 +++++++++++++++++++ .../nft/details/entity/NFTDetailsUM.kt | 5 +++ .../nft/details/model/NFTDetailsModel.kt | 34 +++++++++++++++++ .../features/nft/details/ui/NFTDetails.kt | 12 ++++++ 15 files changed, 188 insertions(+), 6 deletions(-) create mode 100644 domain/networks/.gitignore create mode 100644 features/nft/api/src/main/kotlin/com/tangem/features/nft/component/NFTDetailsComponent.kt create mode 100644 features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/DefaultNFTDetailsComponent.kt create mode 100644 features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/entity/NFTDetailsUM.kt create mode 100644 features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/model/NFTDetailsModel.kt create mode 100644 features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/ui/NFTDetails.kt diff --git a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt index 7d2a6727f7..3c0ac636bb 100644 --- a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt +++ b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt @@ -13,6 +13,7 @@ import com.tangem.features.managetokens.component.ManageTokensComponent import com.tangem.features.managetokens.component.ManageTokensSource import com.tangem.features.markets.details.MarketsTokenDetailsComponent import com.tangem.features.nft.component.NFTCollectionsComponent +import com.tangem.features.nft.component.NFTDetailsComponent import com.tangem.features.nft.component.NFTReceiveComponent import com.tangem.features.onboarding.v2.entry.OnboardingEntryComponent import com.tangem.features.onramp.component.* @@ -84,6 +85,7 @@ internal class ChildFactory @Inject constructor( private val redesignedWalletConnectComponentFactory: RedisegnedWalletConnectComponent.Factory, private val nftCollectionsComponentFactory: NFTCollectionsComponent.Factory, private val nftReceiveComponentFactory: NFTReceiveComponent.Factory, + private val nftDetailsComponentFactory: NFTDetailsComponent.Factory, private val testerRouter: TesterRouter, private val routingFeatureToggles: RoutingFeatureToggles, private val walletConnectFeatureToggles: WalletConnectFeatureToggles, @@ -415,6 +417,12 @@ internal class ChildFactory @Inject constructor( params = NFTReceiveComponent.Params(userWalletId = route.userWalletId), componentFactory = nftReceiveComponentFactory, ) + is AppRoute.NFTDetails -> + createComponentChild( + context = context, + params = NFTDetailsComponent.Params(userWalletId = route.userWalletId, nftAsset = route.nftAsset), + componentFactory = nftDetailsComponentFactory, + ) is AppRoute.OnboardingNote, is AppRoute.SaveWallet, is AppRoute.OnboardingOther, @@ -761,6 +769,12 @@ internal class ChildFactory @Inject constructor( params = NFTReceiveComponent.Params(userWalletId = route.userWalletId), componentFactory = nftReceiveComponentFactory, ) + is AppRoute.NFTDetails -> + route.asComponentChild( + contextProvider = contextProvider(route, contextFactory), + params = NFTDetailsComponent.Params(userWalletId = route.userWalletId, nftAsset = route.nftAsset), + componentFactory = nftDetailsComponentFactory, + ) } // endregion } diff --git a/common/routing/build.gradle.kts b/common/routing/build.gradle.kts index 4fd88cfb46..285041a42a 100644 --- a/common/routing/build.gradle.kts +++ b/common/routing/build.gradle.kts @@ -22,6 +22,7 @@ dependencies { implementation(projects.domain.markets.models) implementation(projects.domain.onramp.models) implementation(projects.domain.appCurrency.models) + implementation(projects.domain.nft.models) /* Libs - Other */ api(deps.kotlin.serialization) diff --git a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt index 5e2f642f8b..cc4bda0a08 100644 --- a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt +++ b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt @@ -8,6 +8,7 @@ import com.tangem.core.decompose.navigation.Route import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.markets.TokenMarketParams import com.tangem.domain.models.scan.ScanResponse +import com.tangem.domain.nft.models.NFTAsset import com.tangem.domain.onramp.model.OnrampSource import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.wallets.models.UserWalletId @@ -288,4 +289,10 @@ sealed class AppRoute(val path: String) : Route { data class NFTReceive( val userWalletId: UserWalletId, ) : AppRoute(path = "/nft_receive/${userWalletId.stringValue}") + + @Serializable + data class NFTDetails( + val userWalletId: UserWalletId, + val nftAsset: NFTAsset, + ) : AppRoute(path = "/nft_details/${userWalletId.stringValue}/${nftAsset.collectionId}/${nftAsset.id.stringValue}") } \ No newline at end of file diff --git a/domain/networks/.gitignore b/domain/networks/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/domain/networks/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/domain/nft/models/build.gradle.kts b/domain/nft/models/build.gradle.kts index 92208c0a6b..ce4aec98eb 100644 --- a/domain/nft/models/build.gradle.kts +++ b/domain/nft/models/build.gradle.kts @@ -8,7 +8,10 @@ dependencies { implementation(deps.arrow.core) implementation(deps.kotlin.coroutines) + implementation(projects.domain.core) implementation(projects.domain.models) - implementation(projects.domain.tokens.models) + + /* Utils */ + implementation(deps.kotlin.serialization) } \ No newline at end of file diff --git a/domain/nft/models/src/main/kotlin/com/tangem/domain/nft/models/NFTAsset.kt b/domain/nft/models/src/main/kotlin/com/tangem/domain/nft/models/NFTAsset.kt index 761db519d4..3e2558f928 100644 --- a/domain/nft/models/src/main/kotlin/com/tangem/domain/nft/models/NFTAsset.kt +++ b/domain/nft/models/src/main/kotlin/com/tangem/domain/nft/models/NFTAsset.kt @@ -2,7 +2,9 @@ package com.tangem.domain.nft.models import com.tangem.domain.models.StatusSource import com.tangem.domain.tokens.model.Network +import kotlinx.serialization.Serializable +@Serializable data class NFTAsset( val id: Identifier, val collectionId: NFTCollection.Identifier, @@ -18,29 +20,44 @@ data class NFTAsset( val source: StatusSource, ) { + @Serializable data class Media( val mimetype: String?, val url: String, ) + @Serializable data class Rarity( val rank: String, val label: String, ) + @Serializable data class Trait( val name: String, val value: String, ) + @Serializable sealed class Identifier { + abstract val stringValue: String + + @Serializable data class EVM( val tokenId: String, val tokenAddress: String, - ) : Identifier() + ) : Identifier() { + override val stringValue: String = "${tokenAddress}_$tokenId" + } - data class TON(val tokenAddress: String) : Identifier() + @Serializable + data class TON(val tokenAddress: String) : Identifier() { + override val stringValue: String = tokenAddress + } - data object Unknown : Identifier() + @Serializable + data object Unknown : Identifier() { + override val stringValue: String = "" + } } } \ No newline at end of file diff --git a/domain/nft/models/src/main/kotlin/com/tangem/domain/nft/models/NFTCollection.kt b/domain/nft/models/src/main/kotlin/com/tangem/domain/nft/models/NFTCollection.kt index 58b5aea439..d75d39c6de 100644 --- a/domain/nft/models/src/main/kotlin/com/tangem/domain/nft/models/NFTCollection.kt +++ b/domain/nft/models/src/main/kotlin/com/tangem/domain/nft/models/NFTCollection.kt @@ -2,7 +2,9 @@ package com.tangem.domain.nft.models import com.tangem.domain.models.StatusSource import com.tangem.domain.tokens.model.Network +import kotlinx.serialization.Serializable +@Serializable data class NFTCollection( val id: Identifier, val network: Network, @@ -12,19 +14,33 @@ data class NFTCollection( val count: Int, val assets: Assets, ) { + @Serializable sealed class Assets { + @Serializable data object Empty : Assets() + + @Serializable data object Loading : Assets() + + @Serializable data object Failed : Assets() + + @Serializable data class Value( val items: List, val source: StatusSource, ) : Assets() } + @Serializable sealed class Identifier { + @Serializable data class EVM(val tokenAddress: String) : Identifier() + + @Serializable data class TON(val contractAddress: String?) : Identifier() + + @Serializable data object Unknown : Identifier() } } \ No newline at end of file diff --git a/domain/nft/models/src/main/kotlin/com/tangem/domain/nft/models/NFTSalePrice.kt b/domain/nft/models/src/main/kotlin/com/tangem/domain/nft/models/NFTSalePrice.kt index 26b9c2e17f..52cc43d072 100644 --- a/domain/nft/models/src/main/kotlin/com/tangem/domain/nft/models/NFTSalePrice.kt +++ b/domain/nft/models/src/main/kotlin/com/tangem/domain/nft/models/NFTSalePrice.kt @@ -1,25 +1,31 @@ package com.tangem.domain.nft.models -import java.math.BigDecimal +import com.tangem.domain.core.serialization.SerializedBigDecimal +import kotlinx.serialization.Serializable +@Serializable sealed class NFTSalePrice { abstract val assetId: NFTAsset.Identifier + @Serializable data class Empty( override val assetId: NFTAsset.Identifier, ) : NFTSalePrice() + @Serializable data class Loading( override val assetId: NFTAsset.Identifier, ) : NFTSalePrice() + @Serializable data class Error( override val assetId: NFTAsset.Identifier, ) : NFTSalePrice() + @Serializable data class Value( override val assetId: NFTAsset.Identifier, - val value: BigDecimal, + val value: SerializedBigDecimal, val symbol: String, ) : NFTSalePrice() } \ No newline at end of file diff --git a/features/nft/api/build.gradle.kts b/features/nft/api/build.gradle.kts index eb681efb3a..d9217b0692 100644 --- a/features/nft/api/build.gradle.kts +++ b/features/nft/api/build.gradle.kts @@ -12,6 +12,7 @@ dependencies { /* Project - Domain */ implementation(projects.domain.models) + implementation(projects.domain.nft.models) implementation(projects.domain.wallets.models) /* Project - Core */ diff --git a/features/nft/api/src/main/kotlin/com/tangem/features/nft/component/NFTDetailsComponent.kt b/features/nft/api/src/main/kotlin/com/tangem/features/nft/component/NFTDetailsComponent.kt new file mode 100644 index 0000000000..0f43e3a58f --- /dev/null +++ b/features/nft/api/src/main/kotlin/com/tangem/features/nft/component/NFTDetailsComponent.kt @@ -0,0 +1,16 @@ +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 NFTDetailsComponent : ComposableContentComponent { + + data class Params( + val userWalletId: UserWalletId, + val nftAsset: NFTAsset, + ) + + interface Factory : ComponentFactory +} \ No newline at end of file diff --git a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/NFTFeatureModule.kt b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/NFTFeatureModule.kt index 91a6bbd3d5..75f083f217 100644 --- a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/NFTFeatureModule.kt +++ b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/NFTFeatureModule.kt @@ -5,7 +5,10 @@ import com.tangem.core.decompose.model.Model import com.tangem.features.nft.collections.DefaultNFTCollectionsComponent import com.tangem.features.nft.collections.model.NFTCollectionsModel import com.tangem.features.nft.component.NFTCollectionsComponent +import com.tangem.features.nft.component.NFTDetailsComponent import com.tangem.features.nft.component.NFTReceiveComponent +import com.tangem.features.nft.details.DefaultNFTDetailsComponent +import com.tangem.features.nft.details.model.NFTDetailsModel import com.tangem.features.nft.receive.DefaultNFTReceiveComponent import com.tangem.features.nft.receive.model.NFTReceiveModel import dagger.Binds @@ -50,4 +53,13 @@ internal interface NFTFeatureModuleBinds { @IntoMap @ClassKey(NFTReceiveModel::class) fun bindNFTReceiveModel(model: NFTReceiveModel): Model + + @Binds + @Singleton + fun bindNFTDetailsComponentFactory(impl: DefaultNFTDetailsComponent.Factory): NFTDetailsComponent.Factory + + @Binds + @IntoMap + @ClassKey(NFTDetailsModel::class) + fun bindNFTDetailsModel(model: NFTDetailsModel): Model } \ No newline at end of file diff --git a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/DefaultNFTDetailsComponent.kt b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/DefaultNFTDetailsComponent.kt new file mode 100644 index 0000000000..bb220d9f9f --- /dev/null +++ b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/DefaultNFTDetailsComponent.kt @@ -0,0 +1,37 @@ +package com.tangem.features.nft.details + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.model.getOrCreateModel +import com.tangem.features.nft.component.NFTDetailsComponent +import com.tangem.features.nft.details.model.NFTDetailsModel +import com.tangem.features.nft.details.ui.NFTDetails +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +internal class DefaultNFTDetailsComponent @AssistedInject constructor( + @Assisted context: AppComponentContext, + @Assisted private val params: NFTDetailsComponent.Params, +) : NFTDetailsComponent, AppComponentContext by context { + + private val model: NFTDetailsModel = getOrCreateModel(params) + + @Composable + override fun Content(modifier: Modifier) { + val state by model.state.collectAsStateWithLifecycle() + + NFTDetails(state, modifier) + } + + @AssistedFactory + interface Factory : NFTDetailsComponent.Factory { + override fun create( + context: AppComponentContext, + params: NFTDetailsComponent.Params, + ): DefaultNFTDetailsComponent + } +} \ No newline at end of file diff --git a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/entity/NFTDetailsUM.kt b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/entity/NFTDetailsUM.kt new file mode 100644 index 0000000000..d024abdcff --- /dev/null +++ b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/entity/NFTDetailsUM.kt @@ -0,0 +1,5 @@ +package com.tangem.features.nft.details.entity + +internal data class NFTDetailsUM( + val onBackClick: () -> Unit, +) \ No newline at end of file diff --git a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/model/NFTDetailsModel.kt b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/model/NFTDetailsModel.kt new file mode 100644 index 0000000000..5c7e9d20e4 --- /dev/null +++ b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/model/NFTDetailsModel.kt @@ -0,0 +1,34 @@ +package com.tangem.features.nft.details.model + +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.features.nft.component.NFTDetailsComponent +import com.tangem.features.nft.details.entity.NFTDetailsUM +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.flow.* +import javax.inject.Inject + +@ModelScoped +internal class NFTDetailsModel @Inject constructor( + override val dispatchers: CoroutineDispatcherProvider, + private val router: Router, + paramsContainer: ParamsContainer, +) : Model() { + + val state: StateFlow get() = _state + + private val _state = MutableStateFlow( + value = NFTDetailsUM( + onBackClick = ::navigateBack, + ), + ) + + @Suppress("UnusedPrivateMember") + private val params: NFTDetailsComponent.Params = paramsContainer.require() + + private fun navigateBack() { + router.pop() + } +} \ No newline at end of file diff --git a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/ui/NFTDetails.kt b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/ui/NFTDetails.kt new file mode 100644 index 0000000000..b40e1ab39c --- /dev/null +++ b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/ui/NFTDetails.kt @@ -0,0 +1,12 @@ +package com.tangem.features.nft.details.ui + +import androidx.activity.compose.BackHandler +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.tangem.features.nft.details.entity.NFTDetailsUM + +@Suppress("UnusedPrivateMember") +@Composable +internal fun NFTDetails(state: NFTDetailsUM, modifier: Modifier = Modifier) { + BackHandler(onBack = state.onBackClick) +} \ No newline at end of file