From 25dc4f27e59aa71aee5eb1580a2ab573d581e723 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 7 Sep 2023 18:40:38 +0300 Subject: [PATCH] Updated on 2026-08-14 --- .../ui/components/buttons/actions/Actions.kt | 5 +- .../tokens/model/CryptoCurrencyStatus.kt | 18 ++- .../presentation/common/WalletPreviewData.kt | 9 +- .../common/component/TokenItem.kt | 47 ++++-- .../component/token/TokenCryptoInfoBlock.kt | 3 +- .../common/state/TokenItemState.kt | 6 +- .../CryptoCurrencyToIconStateConverter.kt | 24 +-- .../organizetokens/OrganizeTokensScreen.kt | 145 ++++++++++-------- .../OrganizeTokensStateHolder.kt | 2 +- .../organizetokens/OrganizeTokensViewModel.kt | 19 ++- .../organizetokens/model/DraggableItem.kt | 9 +- .../model/OrganizeTokensListState.kt | 2 +- .../model/OrganizeTokensState.kt | 2 +- .../utils/CryptoCurrenciesIdsResolver.kt | 2 +- .../utils/common/DraggableItemOperations.kt | 4 +- .../utils/common/DraggableItemsOperations.kt | 57 ++++++- .../OrganiseTokensListStateOperations.kt | 3 +- .../CryptoCurrencyToDraggableItemConverter.kt | 11 +- .../NetworkGroupToDraggableItemsConverter.kt | 2 +- .../utils/dnd/DragAndDropAdapter.kt | 63 +++----- .../utils/dnd/DraggableGroupsOperations.kt | 61 +------- .../state/components/WalletTokensListState.kt | 36 ++++- .../factory/WalletRefreshStateConverter.kt | 17 +- .../state/factory/WalletStateFactory.kt | 5 +- .../presentation/wallet/ui/WalletScreen.kt | 15 +- .../MultiCurrencyOrganizeButton.kt | 22 ++- .../multicurrency/OrganizeTokensButton.kt | 29 ---- ...ryptoCurrencyStatusToTokenItemConverter.kt | 4 +- .../wallet/utils/TokenListErrorConverter.kt | 2 +- .../utils/TokenListToContentItemsConverter.kt | 56 ++++--- 30 files changed, 364 insertions(+), 316 deletions(-) delete mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/OrganizeTokensButton.kt diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/actions/Actions.kt b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/actions/Actions.kt index 2167ad385c..02cf8f05ba 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/actions/Actions.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/actions/Actions.kt @@ -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", diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/CryptoCurrencyStatus.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/CryptoCurrencyStatus.kt index 1ccd0e1e61..5f4f6aa6d0 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/CryptoCurrencyStatus.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/CryptoCurrencyStatus.kt @@ -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, 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, 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, override val networkAddress: NetworkAddress?, - ) : Status() + ) : Status(isError = false) } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt index de4226ceca..7562581a91 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt @@ -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, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/TokenItem.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/TokenItem.kt index 18cc14cbe1..14b4a2065a 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/TokenItem.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/TokenItem.kt @@ -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 diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/token/TokenCryptoInfoBlock.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/token/TokenCryptoInfoBlock.kt index 40740ab8c7..f7dd28c4b6 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/token/TokenCryptoInfoBlock.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/token/TokenCryptoInfoBlock.kt @@ -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 }, ) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/TokenItemState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/TokenItemState.kt index 16e58f8a87..a5da0a39f9 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/TokenItemState.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/TokenItemState.kt @@ -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() /** diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/utils/CryptoCurrencyToIconStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/utils/CryptoCurrencyToIconStateConverter.kt index 6f8c7c9b05..33d33a28ac 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/utils/CryptoCurrencyToIconStateConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/utils/CryptoCurrencyToIconStateConverter.kt @@ -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 { +internal class CryptoCurrencyToIconStateConverter : Converter { - 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 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 diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensStateHolder.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensStateHolder.kt index ec8c8655c2..4552ef0050 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensStateHolder.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensStateHolder.kt @@ -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, ), diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensViewModel.kt index 5c24a976b3..e490a4a9b6 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensViewModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensViewModel.kt @@ -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, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/model/DraggableItem.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/model/DraggableItem.kt index 5d367c0436..3424a965b6 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/model/DraggableItem.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/model/DraggableItem.kt @@ -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) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/model/OrganizeTokensListState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/model/OrganizeTokensListState.kt index 3f87cab410..5d16b5035c 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/model/OrganizeTokensListState.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/model/OrganizeTokensListState.kt @@ -13,7 +13,7 @@ internal sealed class OrganizeTokensListState { ) : OrganizeTokensListState() data class Ungrouped( - override val items: PersistentList, + override val items: PersistentList, ) : OrganizeTokensListState() object Empty : OrganizeTokensListState() { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/model/OrganizeTokensState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/model/OrganizeTokensState.kt index 860c4c5b24..a1ae0ff965 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/model/OrganizeTokensState.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/model/OrganizeTokensState.kt @@ -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, ) } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/utils/CryptoCurrenciesIdsResolver.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/utils/CryptoCurrenciesIdsResolver.kt index 71690752fb..5c7d1de901 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/utils/CryptoCurrenciesIdsResolver.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/utils/CryptoCurrenciesIdsResolver.kt @@ -11,7 +11,7 @@ internal class CryptoCurrenciesIdsResolver { val draggableTokens = when (listState) { is OrganizeTokensListState.Empty -> return emptyList() is OrganizeTokensListState.GroupedByNetwork -> listState.items.filterIsInstance() - is OrganizeTokensListState.Ungrouped -> listState.items + is OrganizeTokensListState.Ungrouped -> listState.items.filterIsInstance() } val currenciesStatuses = when (tokenList) { is TokenList.GroupedByNetwork -> tokenList.groups.flatMap { it.currencies } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/utils/common/DraggableItemOperations.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/utils/common/DraggableItemOperations.kt index acfcac13b7..1db8f4356d 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/utils/common/DraggableItemOperations.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/utils/common/DraggableItemOperations.kt @@ -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()}") } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/utils/common/DraggableItemsOperations.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/utils/common/DraggableItemsOperations.kt index fee7cb1033..7a0beac577 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/utils/common/DraggableItemsOperations.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/utils/common/DraggableItemsOperations.kt @@ -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.uniteItems(): List { - 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.uniteItems(): List { .updateRoundingMode(mode) .updateShadowVisibility(show = false) } +} + +internal fun List.divideMovingItem(movingItem: DraggableItem): List { + 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.prepareItems(): List { + val firstPlaceholderId = "initial_placeholder" + val items = this + + return mutableListOf().apply { + add(DraggableItem.Placeholder(firstPlaceholderId)) + + val itemsWithoutFirstPlaceholder = if (items.firstOrNull()?.id == firstPlaceholderId) { + items.drop(n = 1) + } else { + items + } + + addAll(itemsWithoutFirstPlaceholder) + } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/utils/common/OrganiseTokensListStateOperations.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/utils/common/OrganiseTokensListStateOperations.kt index 4c041f01fe..3b6cf65d8d 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/utils/common/OrganiseTokensListStateOperations.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/utils/common/OrganiseTokensListStateOperations.kt @@ -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) -> List, ): 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) + is OrganizeTokensListState.Ungrouped -> copy(items = updatedItems) is OrganizeTokensListState.Empty -> this } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/utils/converter/items/CryptoCurrencyToDraggableItemConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/utils/converter/items/CryptoCurrencyToDraggableItemConverter.kt index 9bad7941b4..8dad69b926 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/utils/converter/items/CryptoCurrencyToDraggableItemConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/utils/converter/items/CryptoCurrencyToDraggableItemConverter.kt @@ -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)) + }, ) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/utils/converter/items/NetworkGroupToDraggableItemsConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/utils/converter/items/NetworkGroupToDraggableItemsConverter.kt index ddd09e1da9..e57076e83f 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/utils/converter/items/NetworkGroupToDraggableItemsConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/utils/converter/items/NetworkGroupToDraggableItemsConverter.kt @@ -36,6 +36,6 @@ internal class NetworkGroupToDraggableItemsConverter( ) private fun createTokens(group: NetworkGroup): List { - return itemConverter.convertList(group.currencies.toList()) + return itemConverter.convertList(group.currencies) } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/utils/dnd/DragAndDropAdapter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/utils/dnd/DragAndDropAdapter.kt index c73798c77e..45dcb02cee 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/utils/dnd/DragAndDropAdapter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/utils/dnd/DragAndDropAdapter.kt @@ -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, - 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, - movingItem: DraggableItem.Token, - ): List { - return items.map { token -> - token - .updateRoundingMode(DraggableItem.RoundingMode.All(showGap = true)) - .updateShadowVisibility(show = token.id == movingItem.id) - } as List - } - - private companion object { - const val FINISH_DRAGGING_DELAY_MILLIS = 200L - } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/utils/dnd/DraggableGroupsOperations.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/utils/dnd/DraggableGroupsOperations.kt index d28133195a..3018d416a5 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/utils/dnd/DraggableGroupsOperations.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/utils/dnd/DraggableGroupsOperations.kt @@ -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): List { @@ -45,62 +46,4 @@ internal class DraggableGroupsOperations { return expandedGroups } - - fun divideGroups(items: List, movingItem: DraggableItem): List { - 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) - } - } - } - } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/components/WalletTokensListState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/components/WalletTokensListState.kt index 8b973054f2..0cb0188b10 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/components/WalletTokensListState.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/components/WalletTokensListState.kt @@ -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, - 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, - 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 { /** diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletRefreshStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletRefreshStateConverter.kt index a7d44c70d8..2344194b89 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletRefreshStateConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletRefreshStateConverter.kt @@ -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, - private val intents: WalletClickIntents, ) : Converter { 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 } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletStateFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletStateFactory.kt index ad2916e9b1..4a282a682c 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletStateFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletStateFactory.kt @@ -105,10 +105,7 @@ internal class WalletStateFactory( } private val refreshStateConverter by lazy { - WalletRefreshStateConverter( - currentStateProvider = currentStateProvider, - intents = clickIntents, - ) + WalletRefreshStateConverter(currentStateProvider) } private val cryptoCurrencyActionsConverter by lazy { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt index 10f4dbd9e4..1e02c0f412 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt @@ -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, + ) } } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyOrganizeButton.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyOrganizeButton.kt index 210bfd423c..8b981deeb7 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyOrganizeButton.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyOrganizeButton.kt @@ -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, + ), + ) } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/OrganizeTokensButton.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/OrganizeTokensButton.kt deleted file mode 100644 index 53aeef026b..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/OrganizeTokensButton.kt +++ /dev/null @@ -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, - ) -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/CryptoCurrencyStatusToTokenItemConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/CryptoCurrencyStatusToTokenItemConverter.kt index a2e85d00a2..1e073b5ffd 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/CryptoCurrencyStatusToTokenItemConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/CryptoCurrencyStatusToTokenItemConverter.kt @@ -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 { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListErrorConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListErrorConverter.kt index 82709f83a7..100ea1b8bf 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListErrorConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListErrorConverter.kt @@ -19,7 +19,7 @@ internal class TokenListErrorConverter( state.copy( tokensListState = WalletTokensListState.Content( items = persistentListOf(), - onOrganizeTokensClick = null, + organizeTokensButton = WalletTokensListState.OrganizeTokensButtonState.Hidden, ), ) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListToContentItemsConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListToContentItemsConverter.kt index ca50986a37..cb11331df1 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListToContentItemsConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListToContentItemsConverter.kt @@ -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.addGroup(group: NetworkGroup): List { 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 + } + } } \ No newline at end of file