Updated on 2026-08-14
This commit is contained in:
parent
6ddf9a25ae
commit
e8f2ed8517
22 changed files with 463 additions and 81 deletions
|
|
@ -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<Params, NFTReceiveComponent>
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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<NFTNetworkUM>,
|
||||
)
|
||||
|
|
@ -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<NFTNetworkUM>,
|
||||
) : Networks()
|
||||
}
|
||||
}
|
||||
|
|
@ -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<NFTReceiveUM> {
|
||||
|
||||
override fun transform(prevState: NFTReceiveUM): NFTReceiveUM = prevState.copy(
|
||||
search = prevState.search.copy(
|
||||
isActive = isActive,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -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<Network>,
|
||||
private val onNetworkClick: (Network) -> Unit,
|
||||
) : Transformer<NFTReceiveUM> {
|
||||
|
||||
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)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -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<NFTReceiveUM> {
|
||||
|
||||
override fun transform(prevState: NFTReceiveUM): NFTReceiveUM = prevState.copy(
|
||||
search = prevState.search.copy(
|
||||
query = newQuery,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -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<NFTReceiveUM> 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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -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",
|
||||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue