From e8f2ed85176a7c7dbe08faf4aa2207b200282182 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 1 Apr 2025 16:21:50 +0500 Subject: [PATCH] Updated on 2026-08-14 --- .../tangem/tap/di/domain/NFTDomainModule.kt | 48 +++++---- .../tangem/tap/routing/utils/ChildFactory.kt | 14 +++ .../com/tangem/common/routing/AppRoute.kt | 5 + core/res/src/main/res/values/strings.xml | 3 + .../tangem/data/nft/DefaultNFTRepository.kt | 2 + domain/nft/build.gradle.kts | 2 + .../nft/FilterNFTAvailableNetworksUseCase.kt | 17 +++ .../nft/GetNFTAvailableNetworksUseCase.kt | 22 ++++ .../domain/nft/repository/NFTRepository.kt | 2 + .../nft/component/NFTReceiveComponent.kt | 14 +++ .../tangem/features/nft/NFTFeatureModule.kt | 18 +++- .../collections/model/NFTCollectionsModel.kt | 10 +- .../nft/receive/DefaultNFTReceiveComponent.kt | 37 +++++++ .../receive/entity/NFTReceiveNetworksUM.kt | 9 -- .../nft/receive/entity/NFTReceiveUM.kt | 23 ++++ .../transformer/ToggleSearchBarTransformer.kt | 13 +++ .../transformer/UpdateDataStateTransformer.kt | 34 ++++++ .../UpdateSearchQueryTransformer.kt | 13 +++ .../nft/receive/model/NFTReceiveModel.kt | 100 ++++++++++++++++++ .../features/nft/receive/ui/NFTReceive.kt | 66 ++++++++++++ ...tworks.kt => NFTReceiveNetworksContent.kt} | 74 +++++-------- .../nft/receive/ui/NFTReceiveNetworksEmpty.kt | 18 ++++ 22 files changed, 463 insertions(+), 81 deletions(-) create mode 100644 domain/nft/src/main/kotlin/com/tangem/domain/nft/FilterNFTAvailableNetworksUseCase.kt create mode 100644 domain/nft/src/main/kotlin/com/tangem/domain/nft/GetNFTAvailableNetworksUseCase.kt create mode 100644 features/nft/api/src/main/kotlin/com/tangem/features/nft/component/NFTReceiveComponent.kt create mode 100644 features/nft/impl/src/main/kotlin/com/tangem/features/nft/receive/DefaultNFTReceiveComponent.kt delete mode 100644 features/nft/impl/src/main/kotlin/com/tangem/features/nft/receive/entity/NFTReceiveNetworksUM.kt create mode 100644 features/nft/impl/src/main/kotlin/com/tangem/features/nft/receive/entity/NFTReceiveUM.kt create mode 100644 features/nft/impl/src/main/kotlin/com/tangem/features/nft/receive/entity/transformer/ToggleSearchBarTransformer.kt create mode 100644 features/nft/impl/src/main/kotlin/com/tangem/features/nft/receive/entity/transformer/UpdateDataStateTransformer.kt create mode 100644 features/nft/impl/src/main/kotlin/com/tangem/features/nft/receive/entity/transformer/UpdateSearchQueryTransformer.kt create mode 100644 features/nft/impl/src/main/kotlin/com/tangem/features/nft/receive/model/NFTReceiveModel.kt create mode 100644 features/nft/impl/src/main/kotlin/com/tangem/features/nft/receive/ui/NFTReceive.kt rename features/nft/impl/src/main/kotlin/com/tangem/features/nft/receive/ui/{NFTReceiveNetworks.kt => NFTReceiveNetworksContent.kt} (59%) create mode 100644 features/nft/impl/src/main/kotlin/com/tangem/features/nft/receive/ui/NFTReceiveNetworksEmpty.kt diff --git a/app/src/main/java/com/tangem/tap/di/domain/NFTDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/NFTDomainModule.kt index f9972a842d..b9cfccbc58 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/NFTDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/NFTDomainModule.kt @@ -1,10 +1,9 @@ package com.tangem.tap.di.domain -import com.tangem.domain.nft.FetchNFTCollectionAssetsUseCase -import com.tangem.domain.nft.FetchNFTCollectionsUseCase -import com.tangem.domain.nft.GetNFTCollectionsUseCase +import com.tangem.domain.nft.* import com.tangem.domain.nft.repository.NFTRepository import com.tangem.domain.tokens.repository.CurrenciesRepository +import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -20,30 +19,43 @@ internal object NFTDomainModule { fun providesGetNFTCollectionsUseCase( currenciesRepository: CurrenciesRepository, nftRepository: NFTRepository, - ): GetNFTCollectionsUseCase { - return GetNFTCollectionsUseCase( - currenciesRepository = currenciesRepository, - nftRepository = nftRepository, - ) - } + ): GetNFTCollectionsUseCase = GetNFTCollectionsUseCase( + currenciesRepository = currenciesRepository, + nftRepository = nftRepository, + ) @Provides @Singleton fun providesFetchNFTCollectionsUseCase( currenciesRepository: CurrenciesRepository, nftRepository: NFTRepository, - ): FetchNFTCollectionsUseCase { - return FetchNFTCollectionsUseCase( - currenciesRepository = currenciesRepository, - nftRepository = nftRepository, - ) - } + ): FetchNFTCollectionsUseCase = FetchNFTCollectionsUseCase( + currenciesRepository = currenciesRepository, + nftRepository = nftRepository, + ) @Provides @Singleton - fun providesFetchNFTCollectionAssetsUseCase(nftRepository: NFTRepository): FetchNFTCollectionAssetsUseCase { - return FetchNFTCollectionAssetsUseCase( + fun providesFetchNFTCollectionAssetsUseCase(nftRepository: NFTRepository): FetchNFTCollectionAssetsUseCase = + FetchNFTCollectionAssetsUseCase( nftRepository = nftRepository, ) - } + + @Provides + @Singleton + fun providesGetNFTAvailableNetworksUseCase( + nftRepository: NFTRepository, + currenciesRepository: CurrenciesRepository, + ): GetNFTAvailableNetworksUseCase = GetNFTAvailableNetworksUseCase( + currenciesRepository = currenciesRepository, + nftRepository = nftRepository, + ) + + @Provides + @Singleton + fun providesFilterNFTAvailableNetworksUseCase( + dispatchers: CoroutineDispatcherProvider, + ): FilterNFTAvailableNetworksUseCase = FilterNFTAvailableNetworksUseCase( + dispatchers = dispatchers, + ) } \ No newline at end of file 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 b68f419f3f..dd951b0a53 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.NFTReceiveComponent import com.tangem.features.onboarding.v2.entry.OnboardingEntryComponent import com.tangem.features.onramp.component.* import com.tangem.features.pushnotifications.api.PushNotificationsComponent @@ -82,6 +83,7 @@ internal class ChildFactory @Inject constructor( private val sendFeatureToggles: SendFeatureToggles, private val redesignedWalletConnectComponentFactory: RedisegnedWalletConnectComponent.Factory, private val nftCollectionsComponentFactory: NFTCollectionsComponent.Factory, + private val nftReceiveComponentFactory: NFTReceiveComponent.Factory, private val testerRouter: TesterRouter, private val routingFeatureToggles: RoutingFeatureToggles, private val walletConnectFeatureToggles: WalletConnectFeatureToggles, @@ -405,6 +407,12 @@ internal class ChildFactory @Inject constructor( params = NFTCollectionsComponent.Params(userWalletId = route.userWalletId), componentFactory = nftCollectionsComponentFactory, ) + is AppRoute.NFTReceive -> + createComponentChild( + context = context, + params = NFTReceiveComponent.Params(userWalletId = route.userWalletId), + componentFactory = nftReceiveComponentFactory, + ) is AppRoute.OnboardingNote, is AppRoute.SaveWallet, is AppRoute.OnboardingOther, @@ -743,6 +751,12 @@ internal class ChildFactory @Inject constructor( params = NFTCollectionsComponent.Params(userWalletId = route.userWalletId), componentFactory = nftCollectionsComponentFactory, ) + is AppRoute.NFTReceive -> + route.asComponentChild( + contextProvider = contextProvider(route, contextFactory), + params = NFTReceiveComponent.Params(userWalletId = route.userWalletId), + componentFactory = nftReceiveComponentFactory, + ) } // endregion } 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 60e5d50f16..2a77d9fd2b 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 @@ -282,4 +282,9 @@ sealed class AppRoute(val path: String) : Route { data class NFTCollections( val userWalletId: UserWalletId, ) : AppRoute(path = "/nft_collections/${userWalletId.stringValue}") + + @Serializable + data class NFTReceive( + val userWalletId: UserWalletId, + ) : AppRoute(path = "/nft_receive/${userWalletId.stringValue}") } \ No newline at end of file diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 9966613b6c..22cf05d7e5 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -510,6 +510,9 @@ NFT collections Some data may not load Temporary loading problems + No results. Please try another request. + Receive NFT + To My wallet %1$d NFTs in %2$d collection Tap here to receive first NFT NFT collections diff --git a/data/nft/src/main/kotlin/com/tangem/data/nft/DefaultNFTRepository.kt b/data/nft/src/main/kotlin/com/tangem/data/nft/DefaultNFTRepository.kt index ebf03a5f42..197249ec85 100644 --- a/data/nft/src/main/kotlin/com/tangem/data/nft/DefaultNFTRepository.kt +++ b/data/nft/src/main/kotlin/com/tangem/data/nft/DefaultNFTRepository.kt @@ -153,6 +153,8 @@ internal class DefaultNFTRepository @Inject constructor( }.saveIn(getCollectionJobHolder(collectionId)).join() } + override suspend fun isNFTSupported(network: Network): Boolean = network.canHandleNFTs() + private suspend fun refreshSalePrice( userWalletId: UserWalletId, network: Network, diff --git a/domain/nft/build.gradle.kts b/domain/nft/build.gradle.kts index 2159e615a7..b5ce316d58 100644 --- a/domain/nft/build.gradle.kts +++ b/domain/nft/build.gradle.kts @@ -13,6 +13,8 @@ dependencies { implementation(deps.arrow.core) implementation(deps.kotlin.coroutines) + implementation(projects.core.utils) + implementation(projects.domain.core) implementation(projects.domain.models) implementation(projects.domain.nft.models) diff --git a/domain/nft/src/main/kotlin/com/tangem/domain/nft/FilterNFTAvailableNetworksUseCase.kt b/domain/nft/src/main/kotlin/com/tangem/domain/nft/FilterNFTAvailableNetworksUseCase.kt new file mode 100644 index 0000000000..3308232789 --- /dev/null +++ b/domain/nft/src/main/kotlin/com/tangem/domain/nft/FilterNFTAvailableNetworksUseCase.kt @@ -0,0 +1,17 @@ +package com.tangem.domain.nft + +import com.tangem.domain.tokens.model.Network +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.withContext + +class FilterNFTAvailableNetworksUseCase( + private val dispatchers: CoroutineDispatcherProvider, +) { + suspend operator fun invoke(networks: List, searchQuery: String): List = + withContext(dispatchers.default) { + networks.filter { + it.name.contains(searchQuery, ignoreCase = true) || + it.currencySymbol.contains(searchQuery, ignoreCase = true) + } + } +} \ No newline at end of file diff --git a/domain/nft/src/main/kotlin/com/tangem/domain/nft/GetNFTAvailableNetworksUseCase.kt b/domain/nft/src/main/kotlin/com/tangem/domain/nft/GetNFTAvailableNetworksUseCase.kt new file mode 100644 index 0000000000..203f12e012 --- /dev/null +++ b/domain/nft/src/main/kotlin/com/tangem/domain/nft/GetNFTAvailableNetworksUseCase.kt @@ -0,0 +1,22 @@ +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> = currenciesRepository + .getWalletCurrenciesUpdates(userWalletId) + .map { cryptoCurrencies -> + cryptoCurrencies + .map { cryptoCurrency -> cryptoCurrency.network } + .distinct() + .filter { nftRepository.isNFTSupported(it) } + } +} \ No newline at end of file diff --git a/domain/nft/src/main/kotlin/com/tangem/domain/nft/repository/NFTRepository.kt b/domain/nft/src/main/kotlin/com/tangem/domain/nft/repository/NFTRepository.kt index 7ab39c2bff..5d811e9a39 100644 --- a/domain/nft/src/main/kotlin/com/tangem/domain/nft/repository/NFTRepository.kt +++ b/domain/nft/src/main/kotlin/com/tangem/domain/nft/repository/NFTRepository.kt @@ -12,4 +12,6 @@ interface NFTRepository { suspend fun refreshCollections(userWalletId: UserWalletId, networks: List) suspend fun refreshAssets(userWalletId: UserWalletId, network: Network, collectionId: NFTCollection.Identifier) + + suspend fun isNFTSupported(network: Network): Boolean } \ No newline at end of file diff --git a/features/nft/api/src/main/kotlin/com/tangem/features/nft/component/NFTReceiveComponent.kt b/features/nft/api/src/main/kotlin/com/tangem/features/nft/component/NFTReceiveComponent.kt new file mode 100644 index 0000000000..9184afa340 --- /dev/null +++ b/features/nft/api/src/main/kotlin/com/tangem/features/nft/component/NFTReceiveComponent.kt @@ -0,0 +1,14 @@ +package com.tangem.features.nft.component + +import com.tangem.core.decompose.factory.ComponentFactory +import com.tangem.core.ui.decompose.ComposableContentComponent +import com.tangem.domain.wallets.models.UserWalletId + +interface NFTReceiveComponent : ComposableContentComponent { + + data class Params( + val userWalletId: UserWalletId, + ) + + 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 09b06ecd70..91a6bbd3d5 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,6 +5,9 @@ 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.NFTReceiveComponent +import com.tangem.features.nft.receive.DefaultNFTReceiveComponent +import com.tangem.features.nft.receive.model.NFTReceiveModel import dagger.Binds import dagger.Module import dagger.Provides @@ -30,10 +33,21 @@ internal object NFTFeatureModule { internal interface NFTFeatureModuleBinds { @Binds @Singleton - fun bindComponentFactory(impl: DefaultNFTCollectionsComponent.Factory): NFTCollectionsComponent.Factory + fun bindNFTCollectionsComponentFactory( + impl: DefaultNFTCollectionsComponent.Factory, + ): NFTCollectionsComponent.Factory @Binds @IntoMap @ClassKey(NFTCollectionsModel::class) - fun bindModel(model: NFTCollectionsModel): Model + fun bindNFTCollectionsModel(model: NFTCollectionsModel): Model + + @Binds + @Singleton + fun bindNFTReceiveComponentFactory(impl: DefaultNFTReceiveComponent.Factory): NFTReceiveComponent.Factory + + @Binds + @IntoMap + @ClassKey(NFTReceiveModel::class) + fun bindNFTReceiveModel(model: NFTReceiveModel): Model } \ No newline at end of file diff --git a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/collections/model/NFTCollectionsModel.kt b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/collections/model/NFTCollectionsModel.kt index 5a206e8c95..aa8431196e 100644 --- a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/collections/model/NFTCollectionsModel.kt +++ b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/collections/model/NFTCollectionsModel.kt @@ -1,5 +1,6 @@ package com.tangem.features.nft.collections.model +import com.tangem.common.routing.AppRoute import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer @@ -11,7 +12,8 @@ import com.tangem.domain.nft.FetchNFTCollectionAssetsUseCase import com.tangem.domain.nft.GetNFTCollectionsUseCase import com.tangem.domain.nft.models.NFTAsset import com.tangem.domain.nft.models.NFTCollection -import com.tangem.features.nft.collections.entity.* +import com.tangem.features.nft.collections.entity.NFTCollectionsStateUM +import com.tangem.features.nft.collections.entity.NFTCollectionsUM import com.tangem.features.nft.collections.entity.transformer.ChangeCollectionExpandedStateTransformer import com.tangem.features.nft.collections.entity.transformer.ToggleSearchBarTransformer import com.tangem.features.nft.collections.entity.transformer.UpdateDataStateTransformer @@ -119,7 +121,11 @@ internal class NFTCollectionsModel @Inject constructor( } private fun onReceiveClick() { - // TODO move to receive + router.push( + AppRoute.NFTReceive( + userWalletId = params.userWalletId, + ), + ) } private fun navigateBack() { diff --git a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/receive/DefaultNFTReceiveComponent.kt b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/receive/DefaultNFTReceiveComponent.kt new file mode 100644 index 0000000000..f9651f18e3 --- /dev/null +++ b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/receive/DefaultNFTReceiveComponent.kt @@ -0,0 +1,37 @@ +package com.tangem.features.nft.receive + +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.NFTReceiveComponent +import com.tangem.features.nft.receive.model.NFTReceiveModel +import com.tangem.features.nft.receive.ui.NFTReceive +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +internal class DefaultNFTReceiveComponent @AssistedInject constructor( + @Assisted context: AppComponentContext, + @Assisted private val params: NFTReceiveComponent.Params, +) : NFTReceiveComponent, AppComponentContext by context { + + private val model: NFTReceiveModel = getOrCreateModel(params) + + @Composable + override fun Content(modifier: Modifier) { + val state by model.state.collectAsStateWithLifecycle() + + NFTReceive(state, modifier) + } + + @AssistedFactory + interface Factory : NFTReceiveComponent.Factory { + override fun create( + context: AppComponentContext, + params: NFTReceiveComponent.Params, + ): DefaultNFTReceiveComponent + } +} \ No newline at end of file diff --git a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/receive/entity/NFTReceiveNetworksUM.kt b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/receive/entity/NFTReceiveNetworksUM.kt deleted file mode 100644 index 91abfb75ef..0000000000 --- a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/receive/entity/NFTReceiveNetworksUM.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.tangem.features.nft.receive.entity - -import com.tangem.core.ui.components.fields.entity.SearchBarUM -import kotlinx.collections.immutable.ImmutableList - -internal data class NFTReceiveNetworksUM( - val searchBar: SearchBarUM, - val networks: ImmutableList, -) \ No newline at end of file diff --git a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/receive/entity/NFTReceiveUM.kt b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/receive/entity/NFTReceiveUM.kt new file mode 100644 index 0000000000..0764b2f074 --- /dev/null +++ b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/receive/entity/NFTReceiveUM.kt @@ -0,0 +1,23 @@ +package com.tangem.features.nft.receive.entity + +import androidx.compose.runtime.Immutable +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.components.fields.entity.SearchBarUM +import kotlinx.collections.immutable.ImmutableList + +internal data class NFTReceiveUM( + val onBackClick: () -> Unit, + val search: SearchBarUM, + val networks: Networks, + val bottomSheetConfig: TangemBottomSheetConfig?, +) { + + @Immutable + sealed class Networks { + data object Empty : Networks() + + data class Content( + val items: ImmutableList, + ) : Networks() + } +} \ No newline at end of file diff --git a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/receive/entity/transformer/ToggleSearchBarTransformer.kt b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/receive/entity/transformer/ToggleSearchBarTransformer.kt new file mode 100644 index 0000000000..16b51e66a9 --- /dev/null +++ b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/receive/entity/transformer/ToggleSearchBarTransformer.kt @@ -0,0 +1,13 @@ +package com.tangem.features.nft.receive.entity.transformer + +import com.tangem.features.nft.receive.entity.NFTReceiveUM +import com.tangem.utils.transformer.Transformer + +internal class ToggleSearchBarTransformer(private val isActive: Boolean) : Transformer { + + override fun transform(prevState: NFTReceiveUM): NFTReceiveUM = prevState.copy( + search = prevState.search.copy( + isActive = isActive, + ), + ) +} \ No newline at end of file diff --git a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/receive/entity/transformer/UpdateDataStateTransformer.kt b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/receive/entity/transformer/UpdateDataStateTransformer.kt new file mode 100644 index 0000000000..560bf018f7 --- /dev/null +++ b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/receive/entity/transformer/UpdateDataStateTransformer.kt @@ -0,0 +1,34 @@ +package com.tangem.features.nft.receive.entity.transformer + +import com.tangem.core.ui.extensions.getActiveIconRes +import com.tangem.domain.tokens.model.Network +import com.tangem.features.nft.receive.entity.NFTNetworkUM +import com.tangem.features.nft.receive.entity.NFTReceiveUM +import com.tangem.utils.transformer.Transformer +import kotlinx.collections.immutable.toPersistentList + +internal class UpdateDataStateTransformer( + private val networks: List, + private val onNetworkClick: (Network) -> Unit, +) : Transformer { + + override fun transform(prevState: NFTReceiveUM): NFTReceiveUM = prevState.copy( + networks = when { + networks.isEmpty() -> NFTReceiveUM.Networks.Empty + else -> NFTReceiveUM.Networks.Content( + items = networks + .map { it.transform() } + .toPersistentList(), + ) + }, + ) + + private fun Network.transform(): NFTNetworkUM = NFTNetworkUM( + id = id.value, + iconRes = getActiveIconRes(id.value), + name = name, + onItemClick = { + onNetworkClick(this) + }, + ) +} \ No newline at end of file diff --git a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/receive/entity/transformer/UpdateSearchQueryTransformer.kt b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/receive/entity/transformer/UpdateSearchQueryTransformer.kt new file mode 100644 index 0000000000..6e2d305b3d --- /dev/null +++ b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/receive/entity/transformer/UpdateSearchQueryTransformer.kt @@ -0,0 +1,13 @@ +package com.tangem.features.nft.receive.entity.transformer + +import com.tangem.features.nft.receive.entity.NFTReceiveUM +import com.tangem.utils.transformer.Transformer + +internal class UpdateSearchQueryTransformer(private val newQuery: String) : Transformer { + + override fun transform(prevState: NFTReceiveUM): NFTReceiveUM = prevState.copy( + search = prevState.search.copy( + query = newQuery, + ), + ) +} \ No newline at end of file diff --git a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/receive/model/NFTReceiveModel.kt b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/receive/model/NFTReceiveModel.kt new file mode 100644 index 0000000000..2e1b50b3c7 --- /dev/null +++ b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/receive/model/NFTReceiveModel.kt @@ -0,0 +1,100 @@ +package com.tangem.features.nft.receive.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.core.ui.components.fields.InputManager +import com.tangem.core.ui.components.fields.entity.SearchBarUM +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.domain.nft.FilterNFTAvailableNetworksUseCase +import com.tangem.domain.nft.GetNFTAvailableNetworksUseCase +import com.tangem.domain.tokens.model.Network +import com.tangem.features.nft.component.NFTReceiveComponent +import com.tangem.features.nft.impl.R +import com.tangem.features.nft.receive.entity.NFTReceiveUM +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.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.collections.immutable.persistentListOf +import kotlinx.coroutines.flow.* +import kotlinx.coroutines.launch +import javax.inject.Inject + +@ModelScoped +internal class NFTReceiveModel @Inject constructor( + override val dispatchers: CoroutineDispatcherProvider, + private val router: Router, + private val searchManager: InputManager, + private val getNFTAvailableNetworksUseCase: GetNFTAvailableNetworksUseCase, + private val filterNFTAvailableNetworksUseCase: FilterNFTAvailableNetworksUseCase, + paramsContainer: ParamsContainer, +) : Model() { + + val state: StateFlow get() = _state + + private val _state = MutableStateFlow( + value = NFTReceiveUM( + onBackClick = ::navigateBack, + search = getInitialSearchBar(), + networks = NFTReceiveUM.Networks.Content(persistentListOf()), + bottomSheetConfig = null, + ), + ) + + private val params: NFTReceiveComponent.Params = paramsContainer.require() + + init { + subscribeToNFTAvailableNetworks() + } + + private fun subscribeToNFTAvailableNetworks() { + combine( + flow = getNFTAvailableNetworksUseCase(params.userWalletId), + flow2 = searchManager.query.distinctUntilChanged(), + ) { networks, query -> + filterNFTAvailableNetworksUseCase(networks, query) + } + .onEach { filteredNetworks -> + _state.update { + UpdateDataStateTransformer( + networks = filteredNetworks, + onNetworkClick = ::onNetworkClick, + ).transform(it) + } + } + .launchIn(modelScope) + } + + private fun onSearchQueryChange(newQuery: String) { + modelScope.launch { + _state.update { UpdateSearchQueryTransformer(newQuery).transform(it) } + + searchManager.update(newQuery) + } + } + + private fun getInitialSearchBar(): SearchBarUM = SearchBarUM( + placeholderText = resourceReference(R.string.common_search), + query = "", + isActive = false, + onQueryChange = ::onSearchQueryChange, + onActiveChange = ::toggleSearchBar, + ) + + private fun toggleSearchBar(isActive: Boolean) { + _state.update { + ToggleSearchBarTransformer(isActive).transform(it) + } + } + + @Suppress("UnusedPrivateMember") + private fun onNetworkClick(network: Network) { + // TODO show receive bottom sheet + } + + private fun navigateBack() { + router.pop() + } +} \ No newline at end of file diff --git a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/receive/ui/NFTReceive.kt b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/receive/ui/NFTReceive.kt new file mode 100644 index 0000000000..326d77da63 --- /dev/null +++ b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/receive/ui/NFTReceive.kt @@ -0,0 +1,66 @@ +package com.tangem.features.nft.receive.ui + +import androidx.activity.compose.BackHandler +import androidx.compose.animation.AnimatedContent +import androidx.compose.foundation.layout.* +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.tangem.core.ui.components.appbar.TangemTopAppBar +import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM +import com.tangem.core.ui.components.fields.SearchBar +import com.tangem.core.ui.components.fields.TangemSearchBarDefaults +import com.tangem.core.ui.extensions.stringResourceSafe +import com.tangem.core.ui.res.TangemTheme +import com.tangem.features.nft.impl.R +import com.tangem.features.nft.receive.entity.NFTReceiveUM + +@Composable +internal fun NFTReceive(state: NFTReceiveUM, modifier: Modifier = Modifier) { + BackHandler(onBack = state.onBackClick) + + Scaffold( + modifier = modifier, + containerColor = TangemTheme.colors.background.secondary, + topBar = { + TangemTopAppBar( + modifier = Modifier.statusBarsPadding(), + startButton = TopAppBarButtonUM( + iconRes = R.drawable.ic_close_24, + onIconClicked = state.onBackClick, + ), + title = stringResourceSafe(id = R.string.nft_receive_title), + subtitle = stringResourceSafe(id = R.string.nft_receive_subtitle), + ) + }, + content = { innerPadding -> + Column( + modifier = Modifier + .padding(innerPadding) + .fillMaxSize(), + ) { + SearchBar( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = TangemTheme.dimens.spacing16), + state = state.search, + colors = TangemSearchBarDefaults.secondaryTextFieldColors, + ) + + val contentModifier = Modifier + .padding(TangemTheme.dimens.spacing16) + + 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) + } + } + } + }, + ) +} \ No newline at end of file diff --git a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/receive/ui/NFTReceiveNetworks.kt b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/receive/ui/NFTReceiveNetworksContent.kt similarity index 59% rename from features/nft/impl/src/main/kotlin/com/tangem/features/nft/receive/ui/NFTReceiveNetworks.kt rename to features/nft/impl/src/main/kotlin/com/tangem/features/nft/receive/ui/NFTReceiveNetworksContent.kt index e51fb169bb..23fab1ef15 100644 --- a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/receive/ui/NFTReceiveNetworks.kt +++ b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/receive/ui/NFTReceiveNetworksContent.kt @@ -1,9 +1,8 @@ package com.tangem.features.nft.receive.ui import android.content.res.Configuration +import androidx.compose.animation.animateContentSize import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn @@ -14,57 +13,39 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.tooling.preview.Preview -import com.tangem.core.ui.components.fields.SearchBar -import com.tangem.core.ui.components.fields.entity.SearchBarUM -import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.stringResourceSafe 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 -import com.tangem.features.nft.receive.entity.NFTReceiveNetworksUM +import com.tangem.features.nft.receive.entity.NFTReceiveUM import kotlinx.collections.immutable.persistentListOf @Composable -internal fun NFTReceiveNetworks(state: NFTReceiveNetworksUM, modifier: Modifier = Modifier) { +internal fun NFTReceiveNetworksContent(state: NFTReceiveUM.Networks.Content, modifier: Modifier = Modifier) { val listState = rememberLazyListState() - Column( + LazyColumn( modifier = modifier - .fillMaxSize() - .background(TangemTheme.colors.background.secondary) - .padding( - start = TangemTheme.dimens.spacing16, - end = TangemTheme.dimens.spacing16, - bottom = TangemTheme.dimens.spacing16, - ), + .fillMaxWidth() + .clip(TangemTheme.shapes.roundedCornersXMedium) + .background(TangemTheme.colors.background.primary) + .animateContentSize(), + state = listState, ) { - SearchBar( - state = state.searchBar, - // TODO fix colors after merging [REDACTED_TASK_KEY] - ) - - LazyColumn( - modifier = Modifier - .fillMaxWidth() - .padding( - vertical = TangemTheme.dimens.spacing16, - ) - .clip(TangemTheme.shapes.roundedCornersXMedium) - .background(TangemTheme.colors.background.primary), - state = listState, + item( + key = "title", ) { - item( - key = "title", - ) { - Title() - } - items( - items = state.networks, - key = NFTNetworkUM::id, - ) { network -> - NFTNetwork(state = network) - } + Title() + } + items( + items = state.items, + key = NFTNetworkUM::id, + ) { network -> + NFTNetwork( + state = network, + modifier = Modifier.animateItem(), + ) } } } @@ -91,16 +72,9 @@ private fun Title(modifier: Modifier = Modifier) { @Composable private fun Preview_NFTReceiveNetworksUM() { TangemThemePreview { - NFTReceiveNetworks( - state = NFTReceiveNetworksUM( - searchBar = SearchBarUM( - placeholderText = resourceReference(R.string.common_search), - query = "", - onQueryChange = { }, - isActive = false, - onActiveChange = { }, - ), - networks = persistentListOf( + NFTReceiveNetworksContent( + state = NFTReceiveUM.Networks.Content( + items = persistentListOf( NFTNetworkUM( id = "item1", name = "Ethereum", diff --git a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/receive/ui/NFTReceiveNetworksEmpty.kt b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/receive/ui/NFTReceiveNetworksEmpty.kt new file mode 100644 index 0000000000..cebaa5a91d --- /dev/null +++ b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/receive/ui/NFTReceiveNetworksEmpty.kt @@ -0,0 +1,18 @@ +package com.tangem.features.nft.receive.ui + +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.tangem.core.ui.extensions.stringResourceSafe +import com.tangem.core.ui.res.TangemTheme +import com.tangem.features.nft.impl.R + +@Composable +internal fun NFTReceiveNetworksEmpty(modifier: Modifier = Modifier) { + Text( + modifier = modifier, + text = stringResourceSafe(id = R.string.nft_empty_search), + style = TangemTheme.typography.body2, + color = TangemTheme.colors.text.tertiary, + ) +} \ No newline at end of file