Updated on 2026-08-14
This commit is contained in:
parent
c300a5eaf3
commit
d99c4e1405
15 changed files with 316 additions and 56 deletions
|
|
@ -1,6 +1,9 @@
|
|||
package com.tangem.features.nft.collections.entity
|
||||
|
||||
import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig
|
||||
|
||||
internal data class NFTCollectionsStateUM(
|
||||
val onBackClick: () -> Unit,
|
||||
val pullToRefreshConfig: PullToRefreshConfig,
|
||||
val content: NFTCollectionsUM,
|
||||
)
|
||||
|
|
@ -12,6 +12,7 @@ internal sealed class NFTCollectionsUM {
|
|||
) : NFTCollectionsUM()
|
||||
|
||||
data class Loading(
|
||||
val search: SearchBarUM,
|
||||
val onReceiveClick: () -> Unit,
|
||||
) : NFTCollectionsUM()
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
package com.tangem.features.nft.collections.entity.transformer
|
||||
|
||||
import com.tangem.features.nft.collections.entity.NFTCollectionsStateUM
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
|
||||
internal class ChangeRefreshingStateTransformer(
|
||||
private val isRefreshing: Boolean,
|
||||
) : Transformer<NFTCollectionsStateUM> {
|
||||
|
||||
override fun transform(prevState: NFTCollectionsStateUM): NFTCollectionsStateUM = prevState.copy(
|
||||
pullToRefreshConfig = prevState.pullToRefreshConfig.copy(
|
||||
isRefreshing = isRefreshing,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import com.tangem.core.ui.components.fields.entity.SearchBarUM
|
|||
import com.tangem.core.ui.components.notifications.NotificationConfig
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.getActiveIconRes
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.domain.nft.models.*
|
||||
import com.tangem.features.nft.collections.entity.*
|
||||
|
|
@ -24,8 +25,8 @@ internal class UpdateDataStateTransformer(
|
|||
private val initialSearchBarFactory: () -> SearchBarUM,
|
||||
) : Transformer<NFTCollectionsStateUM> {
|
||||
|
||||
override fun transform(prevState: NFTCollectionsStateUM): NFTCollectionsStateUM = prevState.copy(
|
||||
content = when {
|
||||
override fun transform(prevState: NFTCollectionsStateUM): NFTCollectionsStateUM {
|
||||
val content = when {
|
||||
nftCollections.allCollectionsFailed() ->
|
||||
NFTCollectionsUM.Failed(onRetryClick, onReceiveClick)
|
||||
nftCollections.anyCollectionFailed() && nftCollections.allLoadedCollectionsEmpty() ->
|
||||
|
|
@ -33,7 +34,16 @@ internal class UpdateDataStateTransformer(
|
|||
nftCollections.allCollectionsLoaded() && nftCollections.allCollectionsEmpty() ->
|
||||
NFTCollectionsUM.Empty(onReceiveClick)
|
||||
!nftCollections.allCollectionsLoaded() && nftCollections.allCollectionsEmpty() ->
|
||||
NFTCollectionsUM.Loading(onReceiveClick)
|
||||
NFTCollectionsUM.Loading(
|
||||
onReceiveClick = onReceiveClick,
|
||||
search = SearchBarUM(
|
||||
placeholderText = resourceReference(R.string.common_search),
|
||||
query = "",
|
||||
isActive = false,
|
||||
onQueryChange = { },
|
||||
onActiveChange = { },
|
||||
),
|
||||
)
|
||||
else -> {
|
||||
NFTCollectionsUM.Content(
|
||||
search = if (prevState.content is NFTCollectionsUM.Content) {
|
||||
|
|
@ -54,8 +64,17 @@ internal class UpdateDataStateTransformer(
|
|||
onReceiveClick = onReceiveClick,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
return prevState.copy(
|
||||
content = content,
|
||||
pullToRefreshConfig = when (content) {
|
||||
is NFTCollectionsUM.Loading -> prevState.pullToRefreshConfig.copy(
|
||||
isRefreshing = false,
|
||||
)
|
||||
else -> prevState.pullToRefreshConfig
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun List<NFTCollection>.transform(
|
||||
state: NFTCollectionsStateUM,
|
||||
|
|
|
|||
|
|
@ -3,15 +3,18 @@ package com.tangem.features.nft.collections.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.ui.components.containers.pullToRefresh.PullToRefreshConfig
|
||||
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.FetchNFTCollectionAssetsUseCase
|
||||
import com.tangem.domain.nft.GetNFTCollectionsUseCase
|
||||
import com.tangem.domain.nft.RefreshAllNFTUseCase
|
||||
import com.tangem.domain.nft.models.NFTCollection
|
||||
import com.tangem.features.nft.collections.NFTCollectionsComponent
|
||||
import com.tangem.features.nft.collections.entity.NFTCollectionsStateUM
|
||||
import com.tangem.features.nft.collections.entity.NFTCollectionsUM
|
||||
import com.tangem.features.nft.collections.entity.transformer.*
|
||||
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
|
||||
|
|
@ -29,6 +32,7 @@ internal class NFTCollectionsModel @Inject constructor(
|
|||
private val searchManager: InputManager,
|
||||
private val getNFTCollectionsUseCase: GetNFTCollectionsUseCase,
|
||||
private val fetchNFTCollectionAssetsUseCase: FetchNFTCollectionAssetsUseCase,
|
||||
private val refreshAllNFTUseCase: RefreshAllNFTUseCase,
|
||||
paramsContainer: ParamsContainer,
|
||||
) : Model() {
|
||||
|
||||
|
|
@ -37,7 +41,20 @@ internal class NFTCollectionsModel @Inject constructor(
|
|||
private val _state = MutableStateFlow(
|
||||
value = NFTCollectionsStateUM(
|
||||
onBackClick = params.onBackClick,
|
||||
content = NFTCollectionsUM.Loading(params.onBackClick),
|
||||
content = NFTCollectionsUM.Loading(
|
||||
search = SearchBarUM(
|
||||
placeholderText = resourceReference(R.string.common_search),
|
||||
query = "",
|
||||
isActive = false,
|
||||
onQueryChange = { },
|
||||
onActiveChange = { },
|
||||
),
|
||||
onReceiveClick = params.onReceiveClick,
|
||||
),
|
||||
pullToRefreshConfig = PullToRefreshConfig(
|
||||
isRefreshing = false,
|
||||
onRefresh = { onRefresh() },
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -59,7 +76,7 @@ internal class NFTCollectionsModel @Inject constructor(
|
|||
onReceiveClick = {
|
||||
params.onReceiveClick()
|
||||
},
|
||||
onRetryClick = ::onRetryClick,
|
||||
onRetryClick = ::onRefresh,
|
||||
onExpandCollectionClick = ::onExpandCollectionClick,
|
||||
onRetryAssetsClick = ::onRetryAssetsClick,
|
||||
onAssetClick = { asset, collectionName ->
|
||||
|
|
@ -69,9 +86,21 @@ internal class NFTCollectionsModel @Inject constructor(
|
|||
).transform(it)
|
||||
}
|
||||
}
|
||||
.onStart { onRefresh() }
|
||||
.launchIn(modelScope)
|
||||
}
|
||||
|
||||
private fun onRefresh() {
|
||||
modelScope.launch {
|
||||
_state.update { ChangeRefreshingStateTransformer(true).transform(it) }
|
||||
try {
|
||||
refreshAllNFTUseCase(params.userWalletId)
|
||||
} finally {
|
||||
_state.update { ChangeRefreshingStateTransformer(false).transform(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onSearchQueryChange(newQuery: String) {
|
||||
modelScope.launch {
|
||||
_state.update { UpdateSearchQueryTransformer(newQuery).transform(it) }
|
||||
|
|
@ -111,10 +140,6 @@ internal class NFTCollectionsModel @Inject constructor(
|
|||
loadCollectionAssets(collection)
|
||||
}
|
||||
|
||||
private fun onRetryClick() {
|
||||
// TODO refresh all
|
||||
}
|
||||
|
||||
private fun loadCollectionAssets(collection: NFTCollection) {
|
||||
modelScope.launch {
|
||||
fetchNFTCollectionAssetsUseCase(
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
package com.tangem.features.nft.collections.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.AppBarWithBackButton
|
||||
import com.tangem.core.ui.components.containers.pullToRefresh.TangemPullToRefreshContainer
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.nft.collections.entity.NFTCollectionsStateUM
|
||||
|
|
@ -29,19 +29,17 @@ internal fun NFTCollections(state: NFTCollectionsStateUM, modifier: Modifier = M
|
|||
)
|
||||
},
|
||||
content = { innerPadding ->
|
||||
AnimatedContent(
|
||||
targetState = state.content,
|
||||
contentKey = { it::class },
|
||||
label = "NFT Collections",
|
||||
) {
|
||||
val contentModifier = Modifier
|
||||
TangemPullToRefreshContainer(
|
||||
config = state.pullToRefreshConfig,
|
||||
modifier = Modifier
|
||||
.padding(innerPadding)
|
||||
.fillMaxSize()
|
||||
when (val content = it) {
|
||||
is NFTCollectionsUM.Content -> NFTCollectionsContent(content, contentModifier)
|
||||
is NFTCollectionsUM.Empty -> NFTCollectionsEmpty(content, contentModifier)
|
||||
is NFTCollectionsUM.Failed -> NFTCollectionsFailed(content, contentModifier)
|
||||
is NFTCollectionsUM.Loading -> Unit
|
||||
.fillMaxSize(),
|
||||
) {
|
||||
when (val content = state.content) {
|
||||
is NFTCollectionsUM.Content -> NFTCollectionsContent(content)
|
||||
is NFTCollectionsUM.Empty -> NFTCollectionsEmpty(content)
|
||||
is NFTCollectionsUM.Failed -> NFTCollectionsFailed(content)
|
||||
is NFTCollectionsUM.Loading -> NFTCollectionsLoading(content)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ package com.tangem.features.nft.collections.ui
|
|||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
|
|
@ -23,6 +25,7 @@ internal fun NFTCollectionsEmpty(state: NFTCollectionsUM.Empty, modifier: Modifi
|
|||
Box(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(TangemTheme.dimens.spacing16),
|
||||
) {
|
||||
Column(
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import androidx.compose.foundation.layout.Box
|
|||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
|
|
@ -22,6 +24,7 @@ internal fun NFTCollectionsFailed(state: NFTCollectionsUM.Failed, modifier: Modi
|
|||
Box(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(TangemTheme.dimens.spacing16),
|
||||
) {
|
||||
UnableToLoadData(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,142 @@
|
|||
package com.tangem.features.nft.collections.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
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.tooling.preview.Preview
|
||||
import com.tangem.core.ui.components.PrimaryButton
|
||||
import com.tangem.core.ui.components.TextShimmer
|
||||
import com.tangem.core.ui.components.fields.SearchBar
|
||||
import com.tangem.core.ui.components.fields.TangemSearchBarDefaults
|
||||
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.collections.entity.NFTCollectionsUM
|
||||
import com.tangem.features.nft.impl.R
|
||||
|
||||
private const val SHIMMER_ITEMS_COUNT = 5
|
||||
|
||||
@Composable
|
||||
internal fun NFTCollectionsLoading(state: NFTCollectionsUM.Loading, modifier: Modifier = Modifier) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(TangemTheme.dimens.spacing16),
|
||||
) {
|
||||
Column {
|
||||
SearchBar(
|
||||
state = state.search,
|
||||
colors = TangemSearchBarDefaults.secondaryTextFieldColors,
|
||||
enabled = false,
|
||||
)
|
||||
Card(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(
|
||||
top = TangemTheme.dimens.spacing16,
|
||||
),
|
||||
shape = RoundedCornerShape(TangemTheme.dimens.radius16),
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = TangemTheme.colors.background.primary,
|
||||
contentColor = TangemTheme.colors.text.primary1,
|
||||
disabledContainerColor = TangemTheme.colors.background.primary,
|
||||
disabledContentColor = TangemTheme.colors.text.primary1,
|
||||
),
|
||||
) {
|
||||
Column {
|
||||
repeat(SHIMMER_ITEMS_COUNT) {
|
||||
CollectionPlaceholder()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
PrimaryButton(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.align(Alignment.BottomCenter),
|
||||
text = stringResourceSafe(R.string.nft_collections_receive),
|
||||
onClick = state.onReceiveClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CollectionPlaceholder(modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.padding(
|
||||
horizontal = TangemTheme.dimens.spacing12,
|
||||
vertical = TangemTheme.dimens.spacing16,
|
||||
),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(TangemTheme.dimens.size36)
|
||||
.clip(RoundedCornerShape(TangemTheme.dimens.radius8))
|
||||
.background(TangemTheme.colors.field.primary),
|
||||
)
|
||||
}
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
horizontal = TangemTheme.dimens.spacing12,
|
||||
)
|
||||
.weight(1f),
|
||||
horizontalAlignment = Alignment.Start,
|
||||
) {
|
||||
TextShimmer(
|
||||
modifier = Modifier.width(TangemTheme.dimens.size110),
|
||||
style = TangemTheme.typography.caption2,
|
||||
textSizeHeight = true,
|
||||
)
|
||||
TextShimmer(
|
||||
modifier = Modifier
|
||||
.width(TangemTheme.dimens.size80)
|
||||
.padding(top = TangemTheme.dimens.spacing4),
|
||||
style = TangemTheme.typography.caption2,
|
||||
textSizeHeight = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 360, showBackground = true)
|
||||
@Preview(widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview_NFTCollectionsLoading() {
|
||||
TangemThemePreview {
|
||||
NFTCollectionsLoading(
|
||||
state = NFTCollectionsUM.Loading(
|
||||
search = SearchBarUM(
|
||||
placeholderText = resourceReference(R.string.common_search),
|
||||
query = "",
|
||||
isActive = false,
|
||||
onQueryChange = { },
|
||||
onActiveChange = { },
|
||||
),
|
||||
onReceiveClick = { },
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue