Updated on 2026-08-14

This commit is contained in:
Tangem 2023-09-07 18:40:38 +03:00
parent 65d9b4818b
commit 25dc4f27e5
30 changed files with 364 additions and 316 deletions

View file

@ -20,7 +20,6 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import com.tangem.core.ui.R
import com.tangem.core.ui.components.SpacerW8
import com.tangem.core.ui.components.buttons.common.*
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
@ -99,7 +98,7 @@ private fun Button(
val iconTint by animateColorAsState(
targetValue = when {
!config.enabled -> TangemTheme.colors.icon.informative
config.dimContent -> TangemTheme.colors.icon.secondary
config.dimContent -> TangemTheme.colors.icon.informative
else -> TangemTheme.colors.icon.primary1
},
label = "Update tint color",
@ -117,7 +116,7 @@ private fun Button(
val textColor by animateColorAsState(
targetValue = when {
!config.enabled -> TangemTheme.colors.text.disabled
config.dimContent -> TangemTheme.colors.text.secondary
config.dimContent -> TangemTheme.colors.text.tertiary
else -> TangemTheme.colors.text.primary1
},
label = "Update text color",

View file

@ -21,8 +21,10 @@ data class CryptoCurrencyStatus(
/**
* Represents the various states a token can have, encapsulating different information based on the state.
*
* @property isError Indicates whether this status represents an error status.
*/
sealed class Status {
sealed class Status(val isError: Boolean) {
/** The amount of the token. */
open val amount: BigDecimal? = null
@ -47,16 +49,16 @@ data class CryptoCurrencyStatus(
}
/** Represents the Loading state of a token, typically while fetching its details. */
object Loading : Status()
object Loading : Status(isError = false)
/** Represents a state where the token is not reachable. */
object Unreachable : Status()
object Unreachable : Status(isError = true)
/** Represents a state where the token's derivation is missed. */
object MissedDerivation : Status()
object MissedDerivation : Status(isError = true)
/** Represents a state where there is no account associated with the token. */
object NoAccount : Status()
object NoAccount : Status(isError = false)
/**
* Represents a Loaded state of a token with complete information.
@ -77,7 +79,7 @@ data class CryptoCurrencyStatus(
override val hasCurrentNetworkTransactions: Boolean,
override val pendingTransactions: Set<TxHistoryItem>,
override val networkAddress: NetworkAddress?,
) : Status()
) : Status(isError = false)
/**
* Represents a Custom state of a token, typically used for user-defined tokens.
@ -98,7 +100,7 @@ data class CryptoCurrencyStatus(
override val hasCurrentNetworkTransactions: Boolean,
override val pendingTransactions: Set<TxHistoryItem>,
override val networkAddress: NetworkAddress?,
) : Status()
) : Status(isError = false)
/**
* Represents a state where the token is available, but there is no current quote available for it.
@ -113,5 +115,5 @@ data class CryptoCurrencyStatus(
override val hasCurrentNetworkTransactions: Boolean,
override val pendingTransactions: Set<TxHistoryItem>,
override val networkAddress: NetworkAddress?,
) : Status()
) : Status(isError = false)
}

View file

@ -8,6 +8,7 @@ import com.tangem.core.ui.components.transactions.state.TransactionState
import com.tangem.core.ui.components.transactions.state.TxHistoryState
import com.tangem.core.ui.event.consumed
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.TangemColorPalette
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.feature.wallet.presentation.common.state.TokenItemState
@ -164,7 +165,7 @@ internal object WalletPreviewData {
id = UUID.randomUUID().toString(),
icon = tokenIconState,
name = "Polygon",
fiatAmount = "3 172,14 $",
info = stringReference(value = "3 172,14 $"),
)
}
@ -232,7 +233,7 @@ internal object WalletPreviewData {
)
}
val divider = DraggableItem.GroupPlaceholder(id = "divider_$networkNumber")
val divider = DraggableItem.Placeholder(id = "divider_$networkNumber")
buildList {
add(group)
@ -267,7 +268,7 @@ internal object WalletPreviewData {
),
dndConfig = OrganizeTokensState.DragAndDropConfig(
onItemDragged = { _, _ -> },
onDragStart = {},
onItemDragStart = {},
canDragItemOver = { _, _ -> false },
onItemDragEnd = {},
),
@ -365,7 +366,7 @@ internal object WalletPreviewData {
),
),
),
onOrganizeTokensClick = {},
organizeTokensButton = WalletTokensListState.OrganizeTokensButtonState.Visible(isEnabled = true, {}),
),
pullToRefreshConfig = WalletPullToRefreshConfig(
isRefreshing = false,

View file

@ -3,11 +3,15 @@ package com.tangem.feature.wallet.presentation.common.component
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.tooling.preview.Preview
@ -25,27 +29,13 @@ import com.tangem.feature.wallet.presentation.common.component.token.icon.TokenI
import com.tangem.feature.wallet.presentation.common.state.TokenItemState
import org.burnoutcrew.reorderable.ReorderableLazyListState
@OptIn(ExperimentalFoundationApi::class)
@Composable
internal fun TokenItem(
state: TokenItemState,
modifier: Modifier = Modifier,
reorderableTokenListState: ReorderableLazyListState? = null,
) {
val hapticFeedback = LocalHapticFeedback.current
val containerModifier: Modifier = remember(state) {
when (state) {
is TokenItemState.Content -> modifier.combinedClickable(
onClick = state.onItemClick,
onLongClick = {
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
state.onItemLongClick()
},
)
else -> modifier
}
}
BaseContainer(modifier = containerModifier) {
BaseContainer(modifier = modifier.tokenClickable(state)) {
val (iconRef, cryptoInfoRef, fiatInfoRef) = createRefs()
TokenIcon(
@ -108,6 +98,31 @@ private fun Modifier.constrainAsOptionsItem(scope: ConstraintLayoutScope, ref: C
}
}
@OptIn(ExperimentalFoundationApi::class)
private fun Modifier.tokenClickable(state: TokenItemState): Modifier = composed {
when (state) {
is TokenItemState.Content -> {
val hapticFeedback = LocalHapticFeedback.current
val onLongClick = remember(state) {
{
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
state.onItemLongClick()
}
}
this.combinedClickable(
onClick = state.onItemClick,
onLongClick = onLongClick,
)
}
is TokenItemState.Draggable,
is TokenItemState.Unreachable,
is TokenItemState.Loading,
is TokenItemState.Locked,
-> this
}
}
// region preview
@Preview
@Composable

View file

@ -10,6 +10,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.res.painterResource
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemTypography
import com.tangem.feature.wallet.impl.R
@ -40,7 +41,7 @@ private fun ContentBlock(state: TokenItemState.ContentState, modifier: Modifier
AmountText(
amount = when (state) {
is TokenItemState.Content -> if (state.tokenOptions is TokenOptionsState.Hidden) DOTS else state.amount
is TokenItemState.Draggable -> state.fiatAmount
is TokenItemState.Draggable -> state.info.resolveReference()
is TokenItemState.Unreachable -> null
},
)

View file

@ -4,6 +4,7 @@ import androidx.annotation.DrawableRes
import androidx.compose.runtime.Immutable
import androidx.compose.ui.graphics.Color
import com.tangem.core.ui.components.marketprice.PriceChangeConfig
import com.tangem.core.ui.extensions.TextReference
/** Token item state */
@Immutable
@ -19,6 +20,7 @@ internal sealed interface TokenItemState {
data class Locked(override val id: String) : TokenItemState
/** Content state */
@Immutable
sealed class ContentState : TokenItemState {
abstract val icon: IconState
@ -54,13 +56,13 @@ internal sealed interface TokenItemState {
* @property id unique id
* @property icon token icon state
* @property name token name
* @property fiatAmount fiat amount of token
* @property info token info (e.g. fiat balance or status)
*/
data class Draggable(
override val id: String,
override val icon: IconState,
override val name: String,
val fiatAmount: String,
val info: TextReference,
) : ContentState()
/**

View file

@ -3,28 +3,32 @@ package com.tangem.feature.wallet.presentation.common.utils
import com.tangem.core.ui.extensions.getTintForTokenIcon
import com.tangem.core.ui.extensions.networkIconResId
import com.tangem.core.ui.extensions.tryGetBackgroundForTokenIcon
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.models.CryptoCurrency
import com.tangem.feature.wallet.presentation.common.state.TokenItemState
import com.tangem.utils.converter.Converter
internal class CryptoCurrencyToIconStateConverter : Converter<CryptoCurrency, TokenItemState.IconState> {
internal class CryptoCurrencyToIconStateConverter : Converter<CryptoCurrencyStatus, TokenItemState.IconState> {
override fun convert(value: CryptoCurrency): TokenItemState.IconState {
return when (value) {
is CryptoCurrency.Coin -> getIconStateForCoin(value)
is CryptoCurrency.Token -> getIconStateForToken(value)
override fun convert(value: CryptoCurrencyStatus): TokenItemState.IconState {
return when (val currency = value.currency) {
is CryptoCurrency.Coin -> getIconStateForCoin(currency, value.value.isError)
is CryptoCurrency.Token -> getIconStateForToken(currency, value.value.isError)
}
}
private fun getIconStateForCoin(coin: CryptoCurrency.Coin): TokenItemState.IconState.CoinIcon {
private fun getIconStateForCoin(
coin: CryptoCurrency.Coin,
isUnreachable: Boolean,
): TokenItemState.IconState.CoinIcon {
return TokenItemState.IconState.CoinIcon(
url = coin.iconUrl,
fallbackResId = coin.networkIconResId,
isGrayscale = coin.network.isTestnet,
isGrayscale = coin.network.isTestnet || isUnreachable,
)
}
private fun getIconStateForToken(token: CryptoCurrency.Token): TokenItemState.IconState {
private fun getIconStateForToken(token: CryptoCurrency.Token, isErrorStatus: Boolean): TokenItemState.IconState {
val background = token.tryGetBackgroundForTokenIcon()
val tint = getTintForTokenIcon(background)
@ -33,13 +37,13 @@ internal class CryptoCurrencyToIconStateConverter : Converter<CryptoCurrency, To
tint = tint,
background = background,
networkBadgeIconResId = token.networkIconResId,
isGrayscale = token.network.isTestnet,
isGrayscale = token.network.isTestnet || isErrorStatus,
)
} else {
TokenItemState.IconState.TokenIcon(
url = token.iconUrl,
networkBadgeIconResId = token.networkIconResId,
isGrayscale = token.network.isTestnet,
isGrayscale = token.network.isTestnet || isErrorStatus,
fallbackTint = tint,
fallbackBackground = background,
)

View file

@ -1,10 +1,7 @@
package com.tangem.feature.wallet.presentation.organizetokens
import androidx.activity.compose.BackHandler
import androidx.compose.animation.core.LinearOutSlowInEasing
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.*
@ -21,10 +18,13 @@ import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.PrimaryButton
import com.tangem.core.ui.components.SecondaryButton
import com.tangem.core.ui.components.buttons.actions.ActionButtonConfig
@ -96,27 +96,28 @@ private fun TokenList(
canDragOver = dndConfig.canDragItemOver,
onDragEnd = onDragEnd,
)
val items = state.items
val listContentPadding = PaddingValues(
top = TangemTheme.dimens.spacing12,
bottom = TangemTheme.dimens.spacing92,
start = TangemTheme.dimens.spacing16,
end = TangemTheme.dimens.spacing16,
)
LazyColumn(
modifier = Modifier
.reorderable(reorderableListState)
.align(Alignment.TopCenter)
.padding(horizontal = TangemTheme.dimens.spacing16)
.fillMaxSize(),
.reorderable(reorderableListState),
state = reorderableListState.listState,
contentPadding = PaddingValues(
top = TangemTheme.dimens.spacing12,
bottom = TangemTheme.dimens.spacing92,
),
contentPadding = listContentPadding,
) {
itemsIndexed(
items = items,
items = state.items,
key = { _, item -> item.id },
) { index, item ->
val onDragStart = remember(item) {
{ dndConfig.onDragStart(item) }
{ dndConfig.onItemDragStart(item) }
}
DraggableItem(
@ -132,7 +133,6 @@ private fun TokenList(
}
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun LazyItemScope.DraggableItem(
index: Int,
@ -140,15 +140,18 @@ private fun LazyItemScope.DraggableItem(
reorderableState: ReorderableLazyListState,
onDragStart: () -> Unit,
) {
var isDragging by remember {
mutableStateOf(value = false)
}
val itemModifier = Modifier.applyShapeAndShadow(item.roundingMode, item.showShadow)
ReorderableItem(
defaultDraggingModifier = Modifier.animateItemPlacement(
animationSpec = tween(easing = LinearOutSlowInEasing),
),
state = reorderableState,
reorderableState = reorderableState,
index = index,
key = item.id,
) { isDragging ->
val itemModifier = Modifier.applyShapeAndShadow(item.roundingMode, item.showShadow)
) { isItemDragging ->
isDragging = isItemDragging
when (item) {
is DraggableItem.GroupHeader -> DraggableNetworkGroupItem(
@ -162,10 +165,12 @@ private fun LazyItemScope.DraggableItem(
reorderableTokenListState = reorderableState,
)
// Should be presented in the list but remain invisible
is DraggableItem.GroupPlaceholder -> Box(modifier = Modifier.fillMaxWidth())
is DraggableItem.Placeholder -> Box(modifier = Modifier.fillMaxWidth())
}
}
LaunchedEffect(isDragging) {
DisposableEffect(isDragging) {
onDispose {
if (isDragging) {
onDragStart()
}
@ -288,57 +293,71 @@ private fun Actions(config: OrganizeTokensState.ActionsConfig, modifier: Modifie
}
}
private fun Modifier.applyShapeAndShadow(roundingMode: DraggableItem.RoundingMode, showShadow: Boolean): Modifier =
composed {
private fun Modifier.applyShapeAndShadow(roundingMode: DraggableItem.RoundingMode, showShadow: Boolean): Modifier {
return composed {
val radius by animateDpAsState(
targetValue = if (roundingMode !is DraggableItem.RoundingMode.None) {
TangemTheme.dimens.radius16
} else {
TangemTheme.dimens.radius0
targetValue = when (roundingMode) {
is DraggableItem.RoundingMode.None -> TangemTheme.dimens.radius0
is DraggableItem.RoundingMode.All -> TangemTheme.dimens.radius12
is DraggableItem.RoundingMode.Bottom,
is DraggableItem.RoundingMode.Top,
-> TangemTheme.dimens.radius16
},
label = "item_shape_radius",
)
val shape = when (roundingMode) {
is DraggableItem.RoundingMode.None -> RectangleShape
is DraggableItem.RoundingMode.Top -> RoundedCornerShape(
topStart = radius,
topEnd = radius,
)
is DraggableItem.RoundingMode.Bottom -> RoundedCornerShape(
bottomStart = radius,
bottomEnd = radius,
)
is DraggableItem.RoundingMode.All -> RoundedCornerShape(
size = radius,
)
}
val paddingValue = TangemTheme.dimens.spacing4
val padding = if (roundingMode.showGap) {
when (roundingMode) {
is DraggableItem.RoundingMode.None -> null
is DraggableItem.RoundingMode.All -> PaddingValues(vertical = paddingValue)
is DraggableItem.RoundingMode.Top -> PaddingValues(top = paddingValue)
is DraggableItem.RoundingMode.Bottom -> PaddingValues(bottom = paddingValue)
}
} else {
null
}
val elevation by animateDpAsState(
targetValue = if (showShadow) {
TangemTheme.dimens.elevation8
} else {
TangemTheme.dimens.elevation0
},
label = "item_elevation",
)
this
.let {
if (padding != null) {
it.padding(padding)
} else {
it
}
}
.padding(paddingValues = getItemGap(roundingMode))
.shadow(
elevation = if (showShadow) TangemTheme.dimens.elevation12 else TangemTheme.dimens.elevation0,
shape = shape,
elevation = elevation,
shape = getItemShape(roundingMode, radius),
clip = true,
)
}
}
@Composable
@ReadOnlyComposable
private fun getItemGap(roundingMode: DraggableItem.RoundingMode): PaddingValues {
val paddingValue = TangemTheme.dimens.spacing4
return if (roundingMode.showGap) {
when (roundingMode) {
is DraggableItem.RoundingMode.None -> PaddingValues(all = 0.dp)
is DraggableItem.RoundingMode.All -> PaddingValues(vertical = paddingValue)
is DraggableItem.RoundingMode.Top -> PaddingValues(top = paddingValue)
is DraggableItem.RoundingMode.Bottom -> PaddingValues(bottom = paddingValue)
}
} else {
PaddingValues(all = 0.dp)
}
}
@Stable
private fun getItemShape(roundingMode: DraggableItem.RoundingMode, radius: Dp): Shape {
return when (roundingMode) {
is DraggableItem.RoundingMode.None -> RectangleShape
is DraggableItem.RoundingMode.Top -> RoundedCornerShape(
topStart = radius,
topEnd = radius,
)
is DraggableItem.RoundingMode.Bottom -> RoundedCornerShape(
bottomStart = radius,
bottomEnd = radius,
)
is DraggableItem.RoundingMode.All -> RoundedCornerShape(
size = radius,
)
}
}
// region Preview

View file

@ -110,7 +110,7 @@ internal class OrganizeTokensStateHolder(
),
dndConfig = OrganizeTokensState.DragAndDropConfig(
onItemDragged = dragAndDropIntents::onItemDragged,
onDragStart = dragAndDropIntents::onItemDraggingStart,
onItemDragStart = dragAndDropIntents::onItemDraggingStart,
onItemDragEnd = dragAndDropIntents::onItemDraggingEnd,
canDragItemOver = dragAndDropIntents::canDragItemOver,
),

View file

@ -20,13 +20,14 @@ import com.tangem.feature.wallet.presentation.organizetokens.utils.common.disabl
import com.tangem.feature.wallet.presentation.organizetokens.utils.dnd.DragAndDropAdapter
import com.tangem.feature.wallet.presentation.router.InnerWalletRouter
import com.tangem.feature.wallet.presentation.router.WalletRoute
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import javax.inject.Inject
@Suppress("LongParameterList")
@HiltViewModel
internal class OrganizeTokensViewModel @Inject constructor(
private val getTokenListUseCase: GetTokenListUseCase,
@ -34,6 +35,7 @@ internal class OrganizeTokensViewModel @Inject constructor(
private val toggleTokenListSortingUseCase: ToggleTokenListSortingUseCase,
private val applyTokenListSortingUseCase: ApplyTokenListSortingUseCase,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val dispatchers: CoroutineDispatcherProvider,
savedStateHandle: SavedStateHandle,
) : ViewModel(), OrganizeTokensIntents {
@ -43,7 +45,6 @@ internal class OrganizeTokensViewModel @Inject constructor(
private val dragAndDropAdapter = DragAndDropAdapter(
listStateProvider = Provider { uiState.value.itemsState },
scope = viewModelScope,
)
private val stateHolder = OrganizeTokensStateHolder(
@ -72,7 +73,7 @@ internal class OrganizeTokensViewModel @Inject constructor(
}
override fun onSortClick() {
viewModelScope.launch(Dispatchers.Default) {
viewModelScope.launch(dispatchers.default) {
val list = tokenList ?: return@launch
toggleTokenListSortingUseCase(list).fold(
@ -86,7 +87,7 @@ internal class OrganizeTokensViewModel @Inject constructor(
}
override fun onGroupClick() {
viewModelScope.launch(Dispatchers.Default) {
viewModelScope.launch(dispatchers.default) {
val list = tokenList ?: return@launch
toggleTokenListGroupingUseCase(list).fold(
@ -100,7 +101,7 @@ internal class OrganizeTokensViewModel @Inject constructor(
}
override fun onApplyClick() {
viewModelScope.launch(Dispatchers.Default) {
viewModelScope.launch(dispatchers.default) {
stateHolder.updateStateToDisplayProgress()
val listState = uiState.value.itemsState
@ -117,7 +118,9 @@ internal class OrganizeTokensViewModel @Inject constructor(
ifLeft = stateHolder::updateStateWithError,
ifRight = {
stateHolder.updateStateToHideProgress()
withContext(Dispatchers.Main) { router.popBackStack() }
withContext(
dispatchers.main,
) { router.popBackStack() }
},
)
}
@ -128,9 +131,9 @@ internal class OrganizeTokensViewModel @Inject constructor(
}
private fun bootstrapTokenList() {
viewModelScope.launch(Dispatchers.Default) {
viewModelScope.launch(dispatchers.default) {
val maybeTokenList = getTokenListUseCase(userWalletId)
.first { it.getOrNull()?.totalFiatBalance is TokenList.FiatBalance.Loaded }
.first { it.getOrNull()?.totalFiatBalance !is TokenList.FiatBalance.Loading }
maybeTokenList.fold(
ifLeft = stateHolder::updateStateWithError,

View file

@ -51,12 +51,11 @@ internal sealed class DraggableItem {
}
/**
* Helper item used to detect possible positions where a network group can be placed.
* Used only on [OrganizeTokensListState.GroupedByNetwork] and placed between network groups.
* Helper item used to detect possible positions where a draggable item can be placed.
*
* @property id ID of the placeholder
* */
data class GroupPlaceholder(
data class Placeholder(
override val id: String,
) : DraggableItem() {
override val showShadow: Boolean = false
@ -109,7 +108,7 @@ internal sealed class DraggableItem {
* @return updated [DraggableItem]
* */
fun updateRoundingMode(mode: RoundingMode): DraggableItem = when (this) {
is GroupPlaceholder -> this
is Placeholder -> this
is GroupHeader -> this.copy(roundingMode = mode)
is Token -> this.copy(roundingMode = mode)
}
@ -122,7 +121,7 @@ internal sealed class DraggableItem {
* @return updated [DraggableItem]
* */
fun updateShadowVisibility(show: Boolean): DraggableItem = when (this) {
is GroupPlaceholder -> this
is Placeholder -> this
is GroupHeader -> this.copy(showShadow = show)
is Token -> this.copy(showShadow = show)
}

View file

@ -13,7 +13,7 @@ internal sealed class OrganizeTokensListState {
) : OrganizeTokensListState()
data class Ungrouped(
override val items: PersistentList<DraggableItem.Token>,
override val items: PersistentList<DraggableItem>,
) : OrganizeTokensListState()
object Empty : OrganizeTokensListState() {

View file

@ -33,6 +33,6 @@ internal data class OrganizeTokensState(
val onItemDragged: (ItemPosition, ItemPosition) -> Unit,
val canDragItemOver: (ItemPosition, ItemPosition) -> Boolean,
val onItemDragEnd: () -> Unit,
val onDragStart: (DraggableItem) -> Unit,
val onItemDragStart: (DraggableItem) -> Unit,
)
}

View file

@ -11,7 +11,7 @@ internal class CryptoCurrenciesIdsResolver {
val draggableTokens = when (listState) {
is OrganizeTokensListState.Empty -> return emptyList()
is OrganizeTokensListState.GroupedByNetwork -> listState.items.filterIsInstance<DraggableItem.Token>()
is OrganizeTokensListState.Ungrouped -> listState.items
is OrganizeTokensListState.Ungrouped -> listState.items.filterIsInstance<DraggableItem.Token>()
}
val currenciesStatuses = when (tokenList) {
is TokenList.GroupedByNetwork -> tokenList.groups.flatMap { it.currencies }

View file

@ -2,6 +2,6 @@ package com.tangem.feature.wallet.presentation.organizetokens.utils.common
import com.tangem.feature.wallet.presentation.organizetokens.model.DraggableItem
internal fun getGroupPlaceholder(index: Int): DraggableItem.GroupPlaceholder {
return DraggableItem.GroupPlaceholder(id = "placeholder_${index.inc()}")
internal fun getGroupPlaceholder(index: Int): DraggableItem.Placeholder {
return DraggableItem.Placeholder(id = "placeholder_${index.inc()}")
}

View file

@ -3,20 +3,22 @@ package com.tangem.feature.wallet.presentation.organizetokens.utils.common
import com.tangem.feature.wallet.presentation.organizetokens.model.DraggableItem
internal fun List<DraggableItem>.uniteItems(): List<DraggableItem> {
val lastItemIndex = this.lastIndex
val items = prepareItems()
val lastItemIndex = items.lastIndex
return this.mapIndexed { index, item ->
return prepareItems().mapIndexed { index, item ->
val mode = when (index) {
0 -> DraggableItem.RoundingMode.Top()
// 1 index is used because the first item is always a placeholder, check `prepareItems()` function
1 -> DraggableItem.RoundingMode.Top()
lastItemIndex -> DraggableItem.RoundingMode.Bottom()
else -> when (item) {
is DraggableItem.Placeholder -> DraggableItem.RoundingMode.None
is DraggableItem.GroupHeader -> DraggableItem.RoundingMode.Top(showGap = true)
is DraggableItem.Token -> if (this[index + 1] is DraggableItem.GroupPlaceholder) {
is DraggableItem.Token -> if (items[index + 1] is DraggableItem.Placeholder) {
DraggableItem.RoundingMode.Bottom(showGap = true)
} else {
DraggableItem.RoundingMode.None
}
is DraggableItem.GroupPlaceholder -> DraggableItem.RoundingMode.None
}
}
@ -24,4 +26,49 @@ internal fun List<DraggableItem>.uniteItems(): List<DraggableItem> {
.updateRoundingMode(mode)
.updateShadowVisibility(show = false)
}
}
internal fun List<DraggableItem>.divideMovingItem(movingItem: DraggableItem): List<DraggableItem> {
val mutableList = this.toMutableList()
val listIterator = mutableList.listIterator()
while (listIterator.hasNext()) {
val item = listIterator.next()
if (item.id == movingItem.id) {
val dividedItem = movingItem
.updateRoundingMode(DraggableItem.RoundingMode.All())
.updateShadowVisibility(show = true)
listIterator.set(dividedItem)
break
}
}
return mutableList
}
/**
* !!! Workaround !!!
*
* We need to add a [DraggableItem.Placeholder] (since it's not draggable) as the first item of the list, because the
* [DND library](https://github.com/aclassen/ComposeReorderable) glitches when a user tries to drag the first item.
*
* @since 07.09.2023
* */
private fun List<DraggableItem>.prepareItems(): List<DraggableItem> {
val firstPlaceholderId = "initial_placeholder"
val items = this
return mutableListOf<DraggableItem>().apply {
add(DraggableItem.Placeholder(firstPlaceholderId))
val itemsWithoutFirstPlaceholder = if (items.firstOrNull()?.id == firstPlaceholderId) {
items.drop(n = 1)
} else {
items
}
addAll(itemsWithoutFirstPlaceholder)
}
}

View file

@ -5,7 +5,6 @@ import com.tangem.feature.wallet.presentation.organizetokens.model.OrganizeToken
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.toPersistentList
@Suppress("UNCHECKED_CAST")
internal inline fun OrganizeTokensListState.updateItems(
update: (PersistentList<DraggableItem>) -> List<DraggableItem>,
): OrganizeTokensListState {
@ -13,7 +12,7 @@ internal inline fun OrganizeTokensListState.updateItems(
return when (this) {
is OrganizeTokensListState.GroupedByNetwork -> copy(items = updatedItems)
is OrganizeTokensListState.Ungrouped -> copy(items = updatedItems as PersistentList<DraggableItem.Token>)
is OrganizeTokensListState.Ungrouped -> copy(items = updatedItems)
is OrganizeTokensListState.Empty -> this
}
}

View file

@ -1,9 +1,12 @@
package com.tangem.feature.wallet.presentation.organizetokens.utils.converter.items
import com.tangem.common.Provider
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.common.state.TokenItemState
import com.tangem.feature.wallet.presentation.common.utils.CryptoCurrencyToIconStateConverter
import com.tangem.feature.wallet.presentation.organizetokens.model.DraggableItem
@ -45,9 +48,13 @@ internal class CryptoCurrencyToDraggableItemConverter(
return TokenItemState.Draggable(
id = getTokenItemId(currency.id),
icon = iconStateConverter.convert(currency),
icon = iconStateConverter.convert(currencyStatus),
name = currency.name,
fiatAmount = getFormattedFiatAmount(currencyStatus, appCurrency),
info = if (currencyStatus.value.isError) {
resourceReference(id = R.string.common_unreachable)
} else {
stringReference(getFormattedFiatAmount(currencyStatus, appCurrency))
},
)
}

View file

@ -36,6 +36,6 @@ internal class NetworkGroupToDraggableItemsConverter(
)
private fun createTokens(group: NetworkGroup): List<DraggableItem.Token> {
return itemConverter.convertList(group.currencies.toList())
return itemConverter.convertList(group.currencies)
}
}

View file

@ -4,21 +4,17 @@ import com.tangem.common.Provider
import com.tangem.feature.wallet.presentation.organizetokens.DragAndDropIntents
import com.tangem.feature.wallet.presentation.organizetokens.model.DraggableItem
import com.tangem.feature.wallet.presentation.organizetokens.model.OrganizeTokensListState
import com.tangem.feature.wallet.presentation.organizetokens.utils.common.divideMovingItem
import com.tangem.feature.wallet.presentation.organizetokens.utils.common.uniteItems
import com.tangem.feature.wallet.presentation.organizetokens.utils.common.updateItems
import kotlinx.collections.immutable.mutate
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.launch
import org.burnoutcrew.reorderable.ItemPosition
internal class DragAndDropAdapter(
private val listStateProvider: Provider<OrganizeTokensListState>,
private val scope: CoroutineScope,
) : DragAndDropIntents {
private val draggableGroupsOperations = DraggableGroupsOperations()
@ -37,9 +33,12 @@ internal class DragAndDropAdapter(
get() = listStateFlowInternal
override fun canDragItemOver(dragOver: ItemPosition, dragging: ItemPosition): Boolean {
val items = (currentListState as? OrganizeTokensListState.GroupedByNetwork)
?.items
?: return true // If ungrouped then item can be moved anywhere
val items = when (val listState = currentListState) {
is OrganizeTokensListState.GroupedByNetwork -> listState.items
is OrganizeTokensListState.Empty,
is OrganizeTokensListState.Ungrouped,
-> return true // If ungrouped then item can be moved anywhere
}
val (dragOverItem, draggingItem) = findItemsToMove(
items = items,
@ -54,7 +53,7 @@ internal class DragAndDropAdapter(
return when (draggingItem) {
is DraggableItem.GroupHeader -> checkCanMoveHeaderOver(dragOver, dragOverItem, items.lastIndex)
is DraggableItem.Token -> checkCanMoveTokenOver(draggingItem, dragOverItem)
is DraggableItem.GroupPlaceholder -> false
is DraggableItem.Placeholder -> false
}
}
@ -64,11 +63,11 @@ internal class DragAndDropAdapter(
updateListState {
when (item) {
is DraggableItem.GroupPlaceholder -> items
is DraggableItem.Placeholder -> items
is DraggableItem.GroupHeader -> draggableGroupsOperations.collapseGroup(items, item)
is DraggableItem.Token -> when (this) {
is OrganizeTokensListState.GroupedByNetwork -> draggableGroupsOperations.divideGroups(items, item)
is OrganizeTokensListState.Ungrouped -> divideTokens(items, item)
is OrganizeTokensListState.GroupedByNetwork -> items.divideMovingItem(item)
is OrganizeTokensListState.Ungrouped -> items.divideMovingItem(item)
is OrganizeTokensListState.Empty -> items
}
}
@ -76,21 +75,17 @@ internal class DragAndDropAdapter(
}
override fun onItemDraggingEnd() {
scope.launch(Dispatchers.IO) {
val draggingItem = currentDraggingItem ?: return@launch
val draggingItem = currentDraggingItem ?: return
delay(FINISH_DRAGGING_DELAY_MILLIS)
updateListState {
when (draggingItem) {
is DraggableItem.GroupHeader -> draggableGroupsOperations.expandGroups(items)
is DraggableItem.Token -> items.uniteItems()
is DraggableItem.GroupPlaceholder -> items
}
updateListState {
when (draggingItem) {
is DraggableItem.GroupHeader -> draggableGroupsOperations.expandGroups(items)
is DraggableItem.Token -> items.uniteItems()
is DraggableItem.Placeholder -> items
}
currentDraggingItem = null
}
currentDraggingItem = null
}
override fun onItemDragged(from: ItemPosition, to: ItemPosition) = updateListState {
@ -137,7 +132,7 @@ internal class DragAndDropAdapter(
return when {
moveOverItemPosition.index == 0 -> true
moveOverItemPosition.index == lastItemIndex -> true
moveOverItem is DraggableItem.GroupPlaceholder -> true
moveOverItem is DraggableItem.Placeholder -> true
else -> false
}
}
@ -147,23 +142,7 @@ internal class DragAndDropAdapter(
return when (moveOverItem) {
is DraggableItem.GroupHeader -> false // Token item can not be moved to group item
is DraggableItem.Token -> item.groupId == moveOverItem.groupId // Token item can not be moved over its group
is DraggableItem.GroupPlaceholder -> false
is DraggableItem.Placeholder -> false
}
}
@Suppress("UNCHECKED_CAST") // Erased type
private fun divideTokens(
items: List<DraggableItem.Token>,
movingItem: DraggableItem.Token,
): List<DraggableItem.Token> {
return items.map { token ->
token
.updateRoundingMode(DraggableItem.RoundingMode.All(showGap = true))
.updateShadowVisibility(show = token.id == movingItem.id)
} as List<DraggableItem.Token>
}
private companion object {
const val FINISH_DRAGGING_DELAY_MILLIS = 200L
}
}

View file

@ -1,6 +1,7 @@
package com.tangem.feature.wallet.presentation.organizetokens.utils.dnd
import com.tangem.feature.wallet.presentation.organizetokens.model.DraggableItem
import com.tangem.feature.wallet.presentation.organizetokens.utils.common.divideMovingItem
import com.tangem.feature.wallet.presentation.organizetokens.utils.common.getGroupPlaceholder
import com.tangem.feature.wallet.presentation.organizetokens.utils.common.uniteItems
@ -20,7 +21,7 @@ internal class DraggableGroupsOperations {
it is DraggableItem.Token && it.groupId == movingGroup.id
}
return divideGroups(itemsWithoutGroupTokens, movingGroup)
return itemsWithoutGroupTokens.divideMovingItem(movingGroup)
}
fun expandGroups(items: List<DraggableItem>): List<DraggableItem> {
@ -45,62 +46,4 @@ internal class DraggableGroupsOperations {
return expandedGroups
}
fun divideGroups(items: List<DraggableItem>, movingItem: DraggableItem): List<DraggableItem> {
val lastItemIndex = items.lastIndex
return items.mapIndexed { index, item ->
when {
// Case when current item is the moving item
item.id == movingItem.id -> {
item
.updateRoundingMode(DraggableItem.RoundingMode.All(showGap = true))
.updateShadowVisibility(show = true)
}
// Case when moving item is a token and current item is the group of the moving token
movingItem is DraggableItem.Token && item.id == movingItem.groupId -> {
item
.updateRoundingMode(DraggableItem.RoundingMode.All(showGap = true))
.updateShadowVisibility(show = true)
}
// Case when both moving item and current item are tokens and belong to the same group
movingItem is DraggableItem.Token &&
item is DraggableItem.Token && item.groupId == movingItem.groupId -> {
item
.updateRoundingMode(DraggableItem.RoundingMode.All(showGap = true))
.updateShadowVisibility(show = false)
}
// Case when current item is the first item in the list
index == 0 -> {
item
.updateRoundingMode(DraggableItem.RoundingMode.Top())
.updateShadowVisibility(show = false)
}
// Case when current item is the last item in the list
index == lastItemIndex -> {
item
.updateRoundingMode(DraggableItem.RoundingMode.Bottom())
.updateShadowVisibility(show = false)
}
// Case when previous item is a GroupPlaceholder
items[index - 1] is DraggableItem.GroupPlaceholder -> {
item
.updateRoundingMode(DraggableItem.RoundingMode.Top(showGap = true))
.updateShadowVisibility(show = false)
}
// Case when next item is a GroupPlaceholder
items[index + 1] is DraggableItem.GroupPlaceholder -> {
item
.updateRoundingMode(DraggableItem.RoundingMode.Bottom(showGap = true))
.updateShadowVisibility(show = false)
}
// Default case when none of the above conditions are met
else -> {
item
.updateRoundingMode(DraggableItem.RoundingMode.None)
.updateShadowVisibility(show = false)
}
}
}
}
}

View file

@ -5,6 +5,7 @@ import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.common.state.TokenItemState
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import javax.annotation.concurrent.Immutable
/**
* Wallet tokens list state
@ -20,11 +21,10 @@ internal sealed class WalletTokensListState {
* Wallet content token list state
*
* @property items content items
* @property onOrganizeTokensClick lambda be invoked when organize tokens button is clicked
*/
sealed class ContentState(
open val items: ImmutableList<TokensListItemState>,
open val onOrganizeTokensClick: (() -> Unit)?,
open val organizeTokensButton: OrganizeTokensButtonState,
) : WalletTokensListState()
/**
@ -37,18 +37,18 @@ internal sealed class WalletTokensListState {
TokensListItemState.Token(state = TokenItemState.Loading(id = FIRST_LOADING_TOKEN_ID)),
TokensListItemState.Token(state = TokenItemState.Loading(id = SECOND_LOADING_TOKEN_ID)),
),
) : ContentState(items = items, onOrganizeTokensClick = null)
) : ContentState(items = items, organizeTokensButton = OrganizeTokensButtonState.Hidden)
/**
* Content state
*
* @property items content items
* @property onOrganizeTokensClick lambda be invoked when organize tokens button is clicked
* @property organizeTokensButton represents the state of the 'Organize Tokens' button
*/
data class Content(
override val items: ImmutableList<TokensListItemState>,
override val onOrganizeTokensClick: (() -> Unit)?,
) : ContentState(items, onOrganizeTokensClick)
override val organizeTokensButton: OrganizeTokensButtonState,
) : ContentState(items, organizeTokensButton)
/** Locked content state */
object Locked : ContentState(
@ -56,10 +56,32 @@ internal sealed class WalletTokensListState {
TokensListItemState.NetworkGroupTitle(value = TextReference.Res(id = R.string.main_tokens)),
TokensListItemState.Token(state = TokenItemState.Locked(id = LOCKED_TOKEN_ID)),
),
onOrganizeTokensClick = null,
organizeTokensButton = OrganizeTokensButtonState.Hidden,
)
/**
* Represents the state of the 'Organize Tokens' button.
*/
@Immutable
sealed class OrganizeTokensButtonState {
/** Represents the state where the 'Organize Tokens' button is hidden. */
object Hidden : OrganizeTokensButtonState()
/**
* Represents the state where the 'Organize Tokens' button is visible.
*
* @property isEnabled Indicates if the button is enabled or not.
* @property onClick Callback to be executed when the button is clicked.
*/
data class Visible(
val isEnabled: Boolean,
val onClick: () -> Unit,
) : OrganizeTokensButtonState()
}
/** Tokens list item state */
@Immutable
sealed interface TokensListItemState {
/**

View file

@ -7,14 +7,12 @@ import com.tangem.feature.wallet.presentation.wallet.state.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletManageButton
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletPullToRefreshConfig
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletTokensListState
import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletClickIntents
import com.tangem.utils.converter.Converter
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.mutate
internal class WalletRefreshStateConverter(
private val currentStateProvider: Provider<WalletState>,
private val intents: WalletClickIntents,
) : Converter<Boolean, WalletState> {
override fun convert(value: Boolean): WalletState {
@ -77,16 +75,21 @@ internal class WalletRefreshStateConverter(
}
private fun WalletMultiCurrencyState.updateTokenListState(isRefreshing: Boolean): WalletTokensListState {
return when (val state = tokensListState) {
return when (val listState = tokensListState) {
is WalletTokensListState.Content -> {
val onOrganizeTokensClick = if (isRefreshing) null else intents::onOrganizeTokensClick
state.copy(onOrganizeTokensClick = onOrganizeTokensClick)
when (listState.organizeTokensButton) {
is WalletTokensListState.OrganizeTokensButtonState.Hidden -> listState
is WalletTokensListState.OrganizeTokensButtonState.Visible -> listState.copy(
organizeTokensButton = listState.organizeTokensButton.copy(
isEnabled = !isRefreshing,
),
)
}
}
is WalletTokensListState.Locked,
is WalletTokensListState.Loading,
is WalletTokensListState.Empty,
-> state
-> listState
}
}

View file

@ -105,10 +105,7 @@ internal class WalletStateFactory(
}
private val refreshStateConverter by lazy {
WalletRefreshStateConverter(
currentStateProvider = currentStateProvider,
intents = clickIntents,
)
WalletRefreshStateConverter(currentStateProvider)
}
private val cryptoCurrencyActionsConverter by lazy {

View file

@ -26,10 +26,11 @@ import com.tangem.feature.wallet.presentation.wallet.state.WalletMultiCurrencySt
import com.tangem.feature.wallet.presentation.wallet.state.WalletSingleCurrencyState
import com.tangem.feature.wallet.presentation.wallet.state.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletTokensListState
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletTokensListState.OrganizeTokensButtonState
import com.tangem.feature.wallet.presentation.wallet.ui.components.TokenActionsBottomSheet
import com.tangem.feature.wallet.presentation.wallet.ui.components.WalletsList
import com.tangem.feature.wallet.presentation.wallet.ui.components.common.*
import com.tangem.feature.wallet.presentation.wallet.ui.components.multicurrency.organizeButton
import com.tangem.feature.wallet.presentation.wallet.ui.components.multicurrency.organizeTokensButton
import com.tangem.feature.wallet.presentation.wallet.ui.components.singlecurrency.controlButtons
import com.tangem.feature.wallet.presentation.wallet.ui.components.singlecurrency.marketPriceBlock
import com.tangem.feature.wallet.presentation.wallet.ui.utils.changeWalletAnimator
@ -110,9 +111,15 @@ private fun WalletContent(state: WalletState.ContentState) {
contentItems(state = state, txHistoryItems = txHistoryItems, modifier = movableItemModifier)
if (state is WalletMultiCurrencyState) {
val tokensListState = state.tokensListState
if (tokensListState is WalletTokensListState.ContentState) {
organizeButton(onClick = tokensListState.onOrganizeTokensClick, modifier = itemModifier)
val contentTokenListState = state.tokensListState as? WalletTokensListState.ContentState
val organizeTokensButton = contentTokenListState?.organizeTokensButton
if (organizeTokensButton is OrganizeTokensButtonState.Visible) {
organizeTokensButton(
modifier = itemModifier,
isEnabled = organizeTokensButton.isEnabled,
onClick = organizeTokensButton.onClick,
)
}
}
}

View file

@ -1,8 +1,11 @@
package com.tangem.feature.wallet.presentation.wallet.ui.components.multicurrency
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.ui.Modifier
import com.tangem.core.ui.components.buttons.actions.ActionButtonConfig
import com.tangem.core.ui.components.buttons.actions.RoundedActionButton
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.feature.wallet.impl.R
private const val ORGANIZE_BUTTON_CONTENT_TYPE = "OrganizeTokensButton"
@ -14,9 +17,20 @@ private const val ORGANIZE_BUTTON_CONTENT_TYPE = "OrganizeTokensButton"
*
[REDACTED_AUTHOR]
*/
@OptIn(ExperimentalFoundationApi::class)
internal fun LazyListScope.organizeButton(onClick: (() -> Unit)?, modifier: Modifier = Modifier) {
internal fun LazyListScope.organizeTokensButton(
isEnabled: Boolean,
onClick: () -> Unit,
modifier: Modifier = Modifier,
) {
item(key = ORGANIZE_BUTTON_CONTENT_TYPE, contentType = ORGANIZE_BUTTON_CONTENT_TYPE) {
OrganizeTokensButton(onClick = onClick, modifier = modifier.animateItemPlacement())
RoundedActionButton(
modifier = modifier,
config = ActionButtonConfig(
text = resourceReference(id = R.string.organize_tokens_title),
iconResId = R.drawable.ic_filter_24,
onClick = onClick,
enabled = isEnabled,
),
)
}
}

View file

@ -1,29 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.ui.components.multicurrency
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.tangem.core.ui.components.buttons.actions.ActionButtonConfig
import com.tangem.core.ui.components.buttons.actions.RoundedActionButton
import com.tangem.core.ui.extensions.TextReference
import com.tangem.feature.wallet.impl.R
/**
* Organize tokens button
*
* @param onClick callback, if null button is disabled
* @param modifier modifier
*
[REDACTED_AUTHOR]
*/
@Composable
internal fun OrganizeTokensButton(onClick: (() -> Unit)?, modifier: Modifier = Modifier) {
RoundedActionButton(
config = ActionButtonConfig(
text = TextReference.Res(id = R.string.organize_tokens_title),
iconResId = R.drawable.ic_filter_24,
onClick = onClick ?: {},
enabled = onClick != null,
),
modifier = modifier,
)
}

View file

@ -38,7 +38,7 @@ internal class CryptoCurrencyStatusToTokenItemConverter(
return TokenItemState.Content(
id = currency.id.value,
name = currency.name,
icon = iconStateConverter.convert(currency),
icon = iconStateConverter.convert(value = this),
amount = getFormattedAmount(),
hasPending = value.hasCurrentNetworkTransactions,
tokenOptions = if (isWalletContentHidden) {
@ -70,7 +70,7 @@ internal class CryptoCurrencyStatusToTokenItemConverter(
private fun CryptoCurrencyStatus.mapToUnreachableTokenItemState() = TokenItemState.Unreachable(
id = currency.id.value,
name = currency.name,
icon = iconStateConverter.convert(currency),
icon = iconStateConverter.convert(value = this),
)
private fun CryptoCurrencyStatus.getPriceChangeConfig(): PriceChangeConfig {

View file

@ -19,7 +19,7 @@ internal class TokenListErrorConverter(
state.copy(
tokensListState = WalletTokensListState.Content(
items = persistentListOf(),
onOrganizeTokensClick = null,
organizeTokensButton = WalletTokensListState.OrganizeTokensButtonState.Hidden,
),
)
}

View file

@ -7,8 +7,8 @@ import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.NetworkGroup
import com.tangem.domain.tokens.model.TokenList
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletTokensListState
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletTokensListState.OrganizeTokensButtonState
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletTokensListState.TokensListItemState
import com.tangem.feature.wallet.presentation.wallet.utils.LoadingItemsProvider.getLoadingMultiCurrencyTokens
import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletClickIntents
import com.tangem.utils.converter.Converter
import kotlinx.collections.immutable.PersistentList
@ -28,26 +28,15 @@ internal class TokenListToContentItemsConverter(
)
override fun convert(value: TokenList): WalletTokensListState {
val isEmptyList = when (value) {
is TokenList.GroupedByNetwork -> value.groups.isEmpty()
is TokenList.NotInitialized -> false
is TokenList.Ungrouped -> value.currencies.isEmpty()
}
return if (isEmptyList) {
WalletTokensListState.Empty
} else {
WalletTokensListState.Content(
items = when (value) {
is TokenList.GroupedByNetwork -> value.mapToMultiCurrencyItems()
is TokenList.Ungrouped -> value.mapToMultiCurrencyItems()
is TokenList.NotInitialized -> getLoadingMultiCurrencyTokens()
},
onOrganizeTokensClick = if (value.totalFiatBalance is TokenList.FiatBalance.Loaded) {
clickIntents::onOrganizeTokensClick
} else {
null
},
return when (value) {
is TokenList.NotInitialized -> WalletTokensListState.Loading()
is TokenList.GroupedByNetwork -> WalletTokensListState.Content(
items = value.mapToMultiCurrencyItems(),
organizeTokensButton = value.mapToOrganizeTokensButtonState(),
)
is TokenList.Ungrouped -> WalletTokensListState.Content(
items = value.mapToMultiCurrencyItems(),
organizeTokensButton = value.mapToOrganizeTokensButtonState(),
)
}
}
@ -64,6 +53,20 @@ internal class TokenListToContentItemsConverter(
}
}
private fun TokenList.GroupedByNetwork.mapToOrganizeTokensButtonState(): OrganizeTokensButtonState {
return getOrganizeTokensButtonState(
isLoading = totalFiatBalance is TokenList.FiatBalance.Loading,
currenciesSize = groups.flatMap(NetworkGroup::currencies).size,
)
}
private fun TokenList.Ungrouped.mapToOrganizeTokensButtonState(): OrganizeTokensButtonState {
return getOrganizeTokensButtonState(
isLoading = totalFiatBalance is TokenList.FiatBalance.Loading,
currenciesSize = currencies.size,
)
}
private fun MutableList<TokensListItemState>.addGroup(group: NetworkGroup): List<TokensListItemState> {
this.add(TokensListItemState.NetworkGroupTitle(TextReference.Str(group.network.name)))
@ -81,4 +84,15 @@ internal class TokenListToContentItemsConverter(
return this
}
private fun getOrganizeTokensButtonState(isLoading: Boolean, currenciesSize: Int): OrganizeTokensButtonState {
return if (currenciesSize > 1) {
OrganizeTokensButtonState.Visible(
isEnabled = !isLoading,
onClick = clickIntents::onOrganizeTokensClick,
)
} else {
OrganizeTokensButtonState.Hidden
}
}
}