Updated on 2026-08-14

This commit is contained in:
Tangem 2024-11-03 10:25:56 +04:00
parent bc8242f423
commit edc5a0cf13
11 changed files with 188 additions and 93 deletions

View file

@ -6,6 +6,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.fields.SearchBar
import com.tangem.core.ui.components.token.TokenItem
import com.tangem.core.ui.components.tokenlist.internal.GroupTitleItem
import com.tangem.core.ui.components.tokenlist.internal.NetworkTitleItem
import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
import com.tangem.core.ui.extensions.resolveReference
@ -25,6 +26,9 @@ fun TokenListItem(state: TokensListItemUM, isBalanceHidden: Boolean, modifier: M
is TokensListItemUM.NetworkGroupTitle -> {
NetworkTitleItem(networkName = state.name.resolveReference(), modifier = modifier)
}
is TokensListItemUM.GroupTitle -> {
GroupTitleItem(state, modifier)
}
is TokensListItemUM.Token -> {
TokenItem(
state = state.state,

View file

@ -0,0 +1,31 @@
package com.tangem.core.ui.components.tokenlist.internal
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.tangem.core.ui.components.rows.NetworkTitle
import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
/**
* Group title item
*
* @param state state
* @param modifier modifier
*
[REDACTED_AUTHOR]
*/
@Composable
fun GroupTitleItem(state: TokensListItemUM.GroupTitle, modifier: Modifier = Modifier) {
NetworkTitle(
title = {
Text(
text = state.text.resolveReference(),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.tertiary,
)
},
modifier = modifier,
)
}

View file

@ -29,8 +29,17 @@ sealed interface TokensListItemUM {
* @property id id
* @property name network group name
*/
@Deprecated("Use GroupTitle instead") // TODO: [REDACTED_JIRA]
data class NetworkGroupTitle(override val id: Int, val name: TextReference) : TokensListItemUM
/**
* Group title
*
* @property id id
* @property text title value
*/
data class GroupTitle(override val id: Any, val text: TextReference) : TokensListItemUM
/**
* Token item
*

View file

@ -2,11 +2,12 @@ package com.tangem.features.onramp.selecttoken.ui
import androidx.activity.compose.BackHandler
import androidx.annotation.StringRes
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
@ -16,6 +17,7 @@ import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.onramp.impl.R
import com.tangem.features.onramp.tokenlist.OnrampTokenListComponent
@OptIn(ExperimentalFoundationApi::class)
@Composable
internal fun OnrampSelectToken(
@StringRes titleResId: Int,
@ -25,21 +27,25 @@ internal fun OnrampSelectToken(
) {
BackHandler(onBack = onBackClick)
Column(
LazyColumn(
modifier = modifier
.background(TangemTheme.colors.background.secondary)
.imePadding()
.systemBarsPadding(),
) {
AppBarWithBackButton(
onBackClick = onBackClick,
text = stringResource(id = titleResId),
iconRes = R.drawable.ic_close_24,
)
stickyHeader(key = "header") {
AppBarWithBackButton(
onBackClick = onBackClick,
text = stringResource(id = titleResId),
iconRes = R.drawable.ic_close_24,
containerColor = TangemTheme.colors.background.secondary,
)
}
onrampTokenListComponent.Content(
contentPadding = PaddingValues(vertical = 8.dp, horizontal = 16.dp),
modifier = Modifier,
)
item(key = "token_list", contentType = "token_list") {
onrampTokenListComponent.Content(
modifier = Modifier.padding(vertical = 8.dp, horizontal = 16.dp),
)
}
}
}

View file

@ -15,8 +15,8 @@ internal sealed interface ExchangeCardUM {
/** Title reference */
val titleReference: TextReference
/** Flag that indicates if remove button should be shown */
val hasRemoveButton: Boolean
/** Remove button UI model */
val removeButtonUM: RemoveButtonUM?
/** Token item state */
val tokenItemState: TokenItemState
@ -26,11 +26,9 @@ internal sealed interface ExchangeCardUM {
*
* @property titleReference title reference
*/
data class Empty(
override val titleReference: TextReference,
) : ExchangeCardUM {
data class Empty(override val titleReference: TextReference) : ExchangeCardUM {
override val hasRemoveButton: Boolean = false
override val removeButtonUM: RemoveButtonUM? = null
// TODO: [REDACTED_JIRA]
override val tokenItemState: TokenItemState = TokenItemState.Content(
@ -49,13 +47,14 @@ internal sealed interface ExchangeCardUM {
* Filled
*
* @property titleReference title reference
* @property removeButtonUM remove button UI model
* @property tokenItemState token item state
*/
data class Filled(
override val titleReference: TextReference,
override val removeButtonUM: RemoveButtonUM?,
override val tokenItemState: TokenItemState,
) : ExchangeCardUM {
) : ExchangeCardUM
override val hasRemoveButton: Boolean = true
}
data class RemoveButtonUM(val onClick: () -> Unit)
}

View file

@ -1,13 +1,12 @@
package com.tangem.features.onramp.swap.ui
import android.content.res.Configuration
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Text
import androidx.compose.material3.ripple
import androidx.compose.runtime.Composable
@ -24,6 +23,7 @@ import com.tangem.core.ui.components.marketprice.PriceChangeType
import com.tangem.core.ui.components.rows.NetworkTitle
import com.tangem.core.ui.components.token.TokenItem
import com.tangem.core.ui.components.token.state.TokenItemState
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.res.TangemTheme
@ -51,34 +51,41 @@ internal fun ExchangeCard(state: ExchangeCardUM, modifier: Modifier = Modifier)
),
verticalArrangement = Arrangement.SpaceBetween,
) {
NetworkTitle(
title = {
Text(
text = state.titleReference.resolveReference(),
color = TangemTheme.colors.text.tertiary,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
style = TangemTheme.typography.subtitle2,
)
},
action = {
RemoveButton(hasRemoveButton = state.hasRemoveButton, onClick = {})
},
)
Title(titleReference = state.titleReference, removeButtonUM = state.removeButtonUM)
TokenItem(state = state.tokenItemState, isBalanceHidden = false)
AnimatedContent(targetState = state.tokenItemState, label = "TokenItem's changing ") {
TokenItem(state = it, isBalanceHidden = false)
}
}
}
@Composable
private fun RemoveButton(hasRemoveButton: Boolean, onClick: () -> Unit) {
AnimatedVisibility(visible = hasRemoveButton) {
private fun Title(titleReference: TextReference, removeButtonUM: ExchangeCardUM.RemoveButtonUM?) {
NetworkTitle(
title = {
Text(
text = titleReference.resolveReference(),
color = TangemTheme.colors.text.tertiary,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
style = TangemTheme.typography.subtitle2,
)
},
action = { RemoveButton(state = removeButtonUM) },
)
}
@Composable
private fun RemoveButton(state: ExchangeCardUM.RemoveButtonUM?) {
AnimatedVisibility(visible = state != null) {
state ?: return@AnimatedVisibility
Text(
text = stringResource(id = R.string.manage_tokens_remove),
modifier = Modifier.clickable(
indication = ripple(bounded = false),
interactionSource = remember { MutableInteractionSource() },
onClick = onClick,
onClick = state.onClick,
),
color = TangemTheme.colors.text.accent,
maxLines = 1,
@ -88,22 +95,32 @@ private fun RemoveButton(hasRemoveButton: Boolean, onClick: () -> Unit) {
}
}
@Preview
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun Preview_ExchangeCard(@PreviewParameter(ExchangeCardUMProvider::class) state: ExchangeCardUM) {
TangemThemePreview {
ExchangeCard(state = state)
ExchangeCard(
state = state,
modifier = Modifier
.background(TangemTheme.colors.background.secondary)
.padding(16.dp),
)
}
}
private class ExchangeCardUMProvider : PreviewParameterProvider<ExchangeCardUM> {
override val values: Sequence<ExchangeCardUM> = sequenceOf(
ExchangeCardUM.Empty(
titleReference = resourceReference(id = R.string.swapping_from_title),
),
ExchangeCardUM.Filled(
ExchangeCardUM.Empty(titleReference = resourceReference(id = R.string.swapping_from_title)),
createFilled(removeButtonUM = null),
createFilled(removeButtonUM = ExchangeCardUM.RemoveButtonUM { }),
)
private fun createFilled(removeButtonUM: ExchangeCardUM.RemoveButtonUM?): ExchangeCardUM.Filled {
return ExchangeCardUM.Filled(
titleReference = resourceReference(id = R.string.swapping_from_title),
removeButtonUM = removeButtonUM,
tokenItemState = TokenItemState.Content(
id = "1",
iconState = CurrencyIconState.Locked,
@ -118,6 +135,6 @@ private class ExchangeCardUMProvider : PreviewParameterProvider<ExchangeCardUM>
onItemClick = {},
onItemLongClick = {},
),
),
)
)
}
}

View file

@ -1,6 +1,5 @@
package com.tangem.features.onramp.tokenlist
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
@ -23,10 +22,10 @@ internal class DefaultOnrampTokenListComponent @AssistedInject constructor(
private val model: OnrampTokenListModel = getOrCreateModel(params)
@Composable
override fun Content(contentPadding: PaddingValues, modifier: Modifier) {
override fun Content(modifier: Modifier) {
val state by model.state.collectAsStateWithLifecycle()
TokenList(state = state, contentPadding = contentPadding, modifier = modifier)
TokenList(state = state, modifier = modifier)
}
@AssistedFactory

View file

@ -1,21 +1,16 @@
package com.tangem.features.onramp.tokenlist
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.ui.Modifier
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.components.token.state.TokenItemState
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.features.onramp.entity.OnrampOperation
/** Token list component that present list of token for multi-currency wallet */
@Stable
internal interface OnrampTokenListComponent {
@Composable
fun Content(contentPadding: PaddingValues, modifier: Modifier)
internal interface OnrampTokenListComponent : ComposableContentComponent {
/** Component factory */
interface Factory : ComponentFactory<Params, OnrampTokenListComponent>

View file

@ -133,10 +133,7 @@ internal class UpdateTokenItemsTransformer(
)
}
private fun createGroupTitle(textReference: TextReference): TokensListItemUM.NetworkGroupTitle {
return TokensListItemUM.NetworkGroupTitle(
id = textReference.hashCode(),
name = textReference,
)
private fun createGroupTitle(textReference: TextReference): TokensListItemUM.GroupTitle {
return TokensListItemUM.GroupTitle(id = textReference.hashCode(), text = textReference)
}
}

View file

@ -1,16 +1,17 @@
package com.tangem.features.onramp.tokenlist.ui
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.key
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEachIndexed
import com.tangem.core.ui.components.SpacerH12
import com.tangem.core.ui.components.tokenlist.TokenListItem
import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
@ -24,34 +25,30 @@ import kotlinx.collections.immutable.ImmutableList
/**
* Token list
*
* @param state state
* @param contentPadding content padding
* @param modifier modifier
* @param state state
* @param modifier modifier
*
[REDACTED_AUTHOR]
*/
@Composable
internal fun TokenList(state: TokenListUM, contentPadding: PaddingValues, modifier: Modifier = Modifier) {
LazyColumn(modifier = modifier, contentPadding = contentPadding) {
itemsBlock(items = state.availableItems, isBalanceHidden = state.isBalanceHidden)
internal fun TokenList(state: TokenListUM, modifier: Modifier = Modifier) {
Column(modifier) {
ItemsBlock(items = state.availableItems, isBalanceHidden = state.isBalanceHidden)
item { SpacerH12() }
SpacerH12()
itemsBlock(items = state.unavailableItems, isBalanceHidden = state.isBalanceHidden)
ItemsBlock(items = state.unavailableItems, isBalanceHidden = state.isBalanceHidden)
}
}
private fun LazyListScope.itemsBlock(items: ImmutableList<TokensListItemUM>, isBalanceHidden: Boolean) {
return itemsIndexed(
items = items,
key = { _, item -> item.id },
contentType = { _, item -> item::class.java },
itemContent = { index, item ->
@Composable
private fun ItemsBlock(items: ImmutableList<TokensListItemUM>, isBalanceHidden: Boolean) {
items.fastForEachIndexed { index, item ->
key(item.id) {
TokenListItem(
state = item,
isBalanceHidden = isBalanceHidden,
modifier = Modifier
.animateItem()
.roundedShapeItemDecoration(
currentIndex = index,
lastIndex = items.lastIndex,
@ -59,20 +56,21 @@ private fun LazyListScope.itemsBlock(items: ImmutableList<TokensListItemUM>, isB
backgroundColor = TangemTheme.colors.background.primary,
),
)
},
)
}
}
}
@Preview
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun Preview_TokenList(@PreviewParameter(PreviewTokenListUMProvider::class) state: TokenListUM) {
TangemThemePreview {
TokenList(
state = state,
contentPadding = PaddingValues(all = 16.dp),
modifier = Modifier
.fillMaxWidth()
.background(color = TangemTheme.colors.background.secondary),
.background(color = TangemTheme.colors.background.secondary)
.padding(16.dp),
)
}
}

View file

@ -9,13 +9,24 @@ import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.features.onramp.impl.R
import com.tangem.features.onramp.tokenlist.entity.TokenListUM
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
internal class PreviewTokenListUMProvider : PreviewParameterProvider<TokenListUM> {
override val values: Sequence<TokenListUM> = sequenceOf(
createTokensList(hasSearchBar = false, createDefaultTokenItem()),
createTokensList(hasSearchBar = true, createDefaultTokenItem()),
createTokensList(
hasSearchBar = false,
createHeader(),
createDefaultTokenItem(),
createDefaultTokenItem(),
),
createTokensList(
hasSearchBar = true,
createHeader(),
createDefaultTokenItem(),
createDefaultTokenItem(),
),
)
private fun createTokensList(hasSearchBar: Boolean, vararg items: TokensListItemUM): TokenListUM {
@ -37,11 +48,22 @@ internal class PreviewTokenListUMProvider : PreviewParameterProvider<TokenListUM
addAll(items)
}
.toImmutableList(),
unavailableItems = items.toList().toImmutableList(),
unavailableItems = persistentListOf(
createHeader(),
createUnavailableTokenItem(),
createUnavailableTokenItem(),
),
isBalanceHidden = false,
)
}
private fun createHeader(): TokensListItemUM.GroupTitle {
return TokensListItemUM.GroupTitle(
id = "exchange_tokens_available_tokens_header",
text = resourceReference(R.string.exchange_tokens_available_tokens_header),
)
}
private fun createDefaultTokenItem(): TokensListItemUM.Token {
return TokensListItemUM.Token(
state = TokenItemState.Content(
@ -60,4 +82,22 @@ internal class PreviewTokenListUMProvider : PreviewParameterProvider<TokenListUM
),
)
}
private fun createUnavailableTokenItem(): TokensListItemUM.Token {
return TokensListItemUM.Token(
state = TokenItemState.Content(
id = "2",
iconState = CurrencyIconState.Locked,
titleState = TokenItemState.TitleState.Content(text = "Bitcoin", isAvailable = false),
fiatAmountState = TokenItemState.FiatAmountState.TextContent(
text = "12 368,14 \$",
isAvailable = false,
),
subtitle2State = TokenItemState.Subtitle2State.TextContent(text = "0,35853044 BTC"),
subtitleState = TokenItemState.SubtitleState.TextContent(value = "BTC", isAvailable = false),
onItemClick = {},
onItemLongClick = {},
),
)
}
}