From 99745c2ee4232a84a2279f6e5003dcdd4bff4cb6 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 6 Jun 2023 14:25:38 +0300 Subject: [PATCH] Updated on 2026-08-14 --- core/res/src/main/res/values-ru/strings.xml | 4 + core/res/src/main/res/values/strings.xml | 4 + core/ui/src/main/res/drawable/ic_group_24.xml | 9 + core/ui/src/main/res/drawable/ic_sort_24.xml | 9 + .../wallet/presentation/WalletFragment.kt | 13 +- .../presentation/common/WalletPreviewData.kt | 102 +++++--- .../common/component/NetworkGroupItem.kt | 67 +++-- .../common/component/TokenItem.kt | 11 +- .../common/state/NetworkGroupState.kt | 4 +- .../common/state/TokenListState.kt | 16 -- .../organizetokens/OrganizeTokensScreen.kt | 229 +++++++++++++++--- .../OrganizeTokensStateHolder.kt | 49 +++- .../organizetokens/OrganizeTokensViewModel.kt | 26 +- .../router/DefaultWalletRouter.kt | 29 +-- 14 files changed, 442 insertions(+), 130 deletions(-) create mode 100644 core/ui/src/main/res/drawable/ic_group_24.xml create mode 100644 core/ui/src/main/res/drawable/ic_sort_24.xml delete mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/TokenListState.kt diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 2baace2c4d..636d609568 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -46,6 +46,7 @@ Чат Принять Добавить + Применить Внимание Баланс: %s биометрическую аутентификацию @@ -255,6 +256,9 @@ Восстановление кода доступа Идентичные карты Код доступа + Группировка + По балансу + Сортировка токенов Участвовать Не удалось загрузить информацию по реферальной программе. Пожалуйста, попробуйте позже. Не удалось загрузить информацию по реферальной программе. Код ошибки: %s. Пожалуйста, попробуйте позже. diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index ea3c2c3c93..af04c2fd30 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -44,6 +44,7 @@ Support Accept Add + Apply Attention Balance: %s biometric authentication @@ -253,6 +254,9 @@ Access code restore Identical cards Access code + Group + By balance + Organize tokens Participate Failed to load the information about the referral program. Please try again later. Failed to load the information about the referral program. Error code: %s. Please try again later. diff --git a/core/ui/src/main/res/drawable/ic_group_24.xml b/core/ui/src/main/res/drawable/ic_group_24.xml new file mode 100644 index 0000000000..0f5f5f4487 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_group_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/core/ui/src/main/res/drawable/ic_sort_24.xml b/core/ui/src/main/res/drawable/ic_sort_24.xml new file mode 100644 index 0000000000..538a24d8e0 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_sort_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/WalletFragment.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/WalletFragment.kt index 1bc2cd04fd..d62c79855f 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/WalletFragment.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/WalletFragment.kt @@ -8,6 +8,8 @@ import android.view.ViewGroup import androidx.compose.ui.platform.ComposeView import androidx.core.view.WindowCompat import androidx.fragment.app.Fragment +import com.tangem.core.ui.components.SystemBarsEffect +import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.wallet.impl.R import com.tangem.feature.wallet.presentation.router.InnerWalletRouter import com.tangem.features.wallet.navigation.WalletRouter @@ -41,8 +43,15 @@ internal class WalletFragment : Fragment() { return ComposeView(inflater.context).apply { setContent { - isTransitionGroup = true - _walletRouter.Initialize() + TangemTheme { + val systemBarsColor = TangemTheme.colors.background.secondary + SystemBarsEffect { + setSystemBarsColor(systemBarsColor) + } + + isTransitionGroup = true + _walletRouter.Initialize() + } } } } 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 40817e750e..cdd8d70132 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 @@ -4,11 +4,13 @@ import com.tangem.core.ui.R import com.tangem.feature.wallet.presentation.common.state.NetworkGroupState import com.tangem.feature.wallet.presentation.common.state.TokenItemState import com.tangem.feature.wallet.presentation.common.state.TokenItemState.TokenOptionsState -import com.tangem.feature.wallet.presentation.common.state.TokenListState +import com.tangem.feature.wallet.presentation.organizetokens.DraggableItem +import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensListState import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensStateHolder import com.tangem.feature.wallet.presentation.wallet.state.WalletCardState import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toPersistentList import java.util.UUID internal object WalletPreviewData { @@ -107,42 +109,6 @@ internal object WalletPreviewData { val loadingTokenItemState = TokenItemState.Loading(id = UUID.randomUUID().toString()) - val organizeTokensState = OrganizeTokensStateHolder( - tokens = TokenListState.GroupedByNetwork( - groups = persistentListOf(), - ), - header = OrganizeTokensStateHolder.HeaderConfig( - onSortByBalanceClick = {}, - onGroupByNetworkClick = {}, - ), - actions = OrganizeTokensStateHolder.ActionsConfig( - onApplyClick = {}, - onCancelClick = {}, - ), - ) - - private val draggableTokenList = persistentListOf( - tokenItemDragState.copy( - id = "token_1", - name = "Ethereum", - tokenIconResId = R.drawable.img_eth_22, - networkIconResId = null, - fiatAmount = "3 172,14 $", - ), - tokenItemDragState.copy( - id = "token_2", - networkIconResId = R.drawable.img_eth_22, - fiatAmount = "803,65 $", - ), - tokenItemDragState.copy( - id = "token_3", - name = "USDT", - tokenIconResId = R.drawable.img_arbitrum_22, - networkIconResId = R.drawable.img_eth_22, - fiatAmount = "88,01 $", - ), - ) - val networkGroup = NetworkGroupState.Content( id = UUID.randomUUID().toString(), networkName = "Ethereum", @@ -170,8 +136,66 @@ internal object WalletPreviewData { ) val draggableNetworkGroup = NetworkGroupState.Draggable( - id = UUID.randomUUID().toString(), + id = "group_1", networkName = "Ethereum", - tokens = draggableTokenList, + ) + + private const val networksSize = 10 + private const val tokensSize = 3 + val draggableItems = List(networksSize) { it } + .flatMap { index -> + val n = index + 1 + + val group = DraggableItem.GroupHeader( + groupState = NetworkGroupState.Draggable( + id = "group_$n", + networkName = "$n", + ), + ) + + val tokens: MutableList = mutableListOf() + repeat(times = tokensSize) { i -> + val nt = i + 1 + tokens.add( + DraggableItem.Token( + tokenItemState = tokenItemDragState.copy( + id = "${group.id}_token_$nt", + name = "Token $nt", + networkIconResId = R.drawable.img_eth_22.takeIf { i != 0 }, + ), + groupId = group.id, + ), + ) + } + + buildList { + add(group) + addAll(tokens) + } + } + .toPersistentList() + + val draggableTokens = draggableItems + .filterIsInstance() + .toPersistentList() + + val groupedOrganizeTokensState = OrganizeTokensStateHolder( + itemsState = OrganizeTokensListState.GroupedByNetwork( + items = draggableItems, + ), + header = OrganizeTokensStateHolder.HeaderConfig( + onSortByBalanceClick = {}, + onGroupByNetworkClick = {}, + ), + actions = OrganizeTokensStateHolder.ActionsConfig( + onApplyClick = {}, + onCancelClick = {}, + ), + ) + + val organizeTokensState = groupedOrganizeTokensState.copy( + itemsState = OrganizeTokensListState.Ungrouped( + items = draggableTokens, + ), ) } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/NetworkGroupItem.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/NetworkGroupItem.kt index ee2d924e85..70e1eeb242 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/NetworkGroupItem.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/NetworkGroupItem.kt @@ -19,7 +19,42 @@ import com.tangem.feature.wallet.presentation.common.WalletPreviewData import com.tangem.feature.wallet.presentation.common.state.NetworkGroupState @Composable -internal fun NetworkGroupItem(state: NetworkGroupState, modifier: Modifier = Modifier) { +internal fun NetworkGroupItem(state: NetworkGroupState.Content, modifier: Modifier = Modifier) { + InternalNetworkGroupItem( + modifier = modifier, + networkName = state.networkName, + tokens = { + state.tokens.forEach { token -> + key(token.id) { + TokenItem(state = token) + } + } + }, + ) +} + +@Composable +internal fun DraggableNetworkGroupItem(state: NetworkGroupState.Draggable, modifier: Modifier = Modifier) { + InternalNetworkGroupItem( + modifier = modifier, + networkName = state.networkName, + endIcon = { + Icon( + painter = painterResource(id = R.drawable.ic_group_drop_24), + tint = TangemTheme.colors.icon.informative, + contentDescription = null, + ) + }, + ) +} + +@Composable +private fun InternalNetworkGroupItem( + networkName: String, + modifier: Modifier = Modifier, + tokens: @Composable ColumnScope.() -> Unit = {}, + endIcon: @Composable RowScope.() -> Unit = {}, +) { Column(modifier = modifier) { Row( modifier = Modifier @@ -31,34 +66,30 @@ internal fun NetworkGroupItem(state: NetworkGroupState, modifier: Modifier = Mod horizontalArrangement = Arrangement.SpaceBetween, ) { Text( - text = stringResource(id = R.string.wallet_network_group_title, state.networkName), + text = stringResource(id = R.string.wallet_network_group_title, networkName), style = TangemTheme.typography.subtitle2, color = TangemTheme.colors.text.tertiary, ) - if (state is NetworkGroupState.Draggable) { - Icon( - painter = painterResource(id = R.drawable.ic_group_drop_24), - tint = TangemTheme.colors.icon.informative, - contentDescription = null, - ) - } - } - Column { - state.tokens.forEach { token -> - key(token.id) { - TokenItem(state = token) - } - } + endIcon() } + tokens() } } // region Preview +@Composable +private fun NetworkGroupItemSample(group: NetworkGroupState) { + when (group) { + is NetworkGroupState.Content -> NetworkGroupItem(group) + is NetworkGroupState.Draggable -> DraggableNetworkGroupItem(group) + } +} + @Preview(showBackground = true, widthDp = 360) @Composable private fun NetworkGroupItemPreview_Light(@PreviewParameter(NetworkGroupProvider::class) group: NetworkGroupState) { TangemTheme { - NetworkGroupItem(group) + NetworkGroupItemSample(group) } } @@ -66,7 +97,7 @@ private fun NetworkGroupItemPreview_Light(@PreviewParameter(NetworkGroupProvider @Composable private fun NetworkGroupItemPreview_Dark(@PreviewParameter(NetworkGroupProvider::class) group: NetworkGroupState) { TangemTheme(isDark = true) { - NetworkGroupItem(group) + NetworkGroupItemSample(group) } } 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 78c5b5f322..b2cb67a6d1 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 @@ -9,6 +9,7 @@ import androidx.compose.material3.Icon import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.runtime.Stable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -18,6 +19,7 @@ import androidx.compose.ui.res.painterResource 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 androidx.constraintlayout.compose.ConstrainedLayoutReference import androidx.constraintlayout.compose.ConstraintLayout @@ -34,6 +36,10 @@ import com.tangem.feature.wallet.presentation.common.state.TokenItemState import com.tangem.feature.wallet.presentation.common.state.TokenItemState.TokenOptionsState private const val DOTS = "•••" +val TOKEN_ITEM_HEIGHT: Dp + @Composable + @ReadOnlyComposable + get() = TangemTheme.dimens.size68 @Composable internal fun TokenItem(state: TokenItemState, modifier: Modifier = Modifier) { @@ -76,7 +82,8 @@ internal fun DraggableTokenItem(state: TokenItemState.Draggable, modifier: Modif hasPending = false, options = { ref -> Icon( - modifier = Modifier.constrainAsOptionsItem(scope = this, ref), + modifier = Modifier + .constrainAsOptionsItem(scope = this, ref), painter = painterResource(id = R.drawable.ic_drag_24), tint = TangemTheme.colors.icon.informative, contentDescription = null, @@ -251,7 +258,7 @@ private fun BaseSurface( content: @Composable () -> Unit, ) { Surface( - modifier = modifier.defaultMinSize(minHeight = TangemTheme.dimens.size68), + modifier = modifier.defaultMinSize(minHeight = TOKEN_ITEM_HEIGHT), color = TangemTheme.colors.background.primary, onClick = onClick ?: {}, enabled = onClick != null, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/NetworkGroupState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/NetworkGroupState.kt index c34fdd126d..854730e663 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/NetworkGroupState.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/NetworkGroupState.kt @@ -7,17 +7,15 @@ import kotlinx.collections.immutable.ImmutableList internal sealed interface NetworkGroupState { val id: String val networkName: String - val tokens: ImmutableList data class Draggable( override val id: String, override val networkName: String, - override val tokens: ImmutableList, ) : NetworkGroupState data class Content( override val id: String, override val networkName: String, - override val tokens: ImmutableList, + val tokens: ImmutableList, ) : NetworkGroupState } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/TokenListState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/TokenListState.kt deleted file mode 100644 index fe3b7fba46..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/TokenListState.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.tangem.feature.wallet.presentation.common.state - -import androidx.compose.runtime.Immutable -import kotlinx.collections.immutable.ImmutableList - -@Immutable -internal sealed interface TokenListState { - - data class GroupedByNetwork( - val groups: ImmutableList, - ) : TokenListState - - data class Ungrouped( - val tokens: ImmutableList, - ) : TokenListState -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensScreen.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensScreen.kt index e626eb0029..cea7c1211a 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensScreen.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensScreen.kt @@ -1,88 +1,255 @@ -// TODO: Remove after components implementation -@file:Suppress("UNUSED_PARAMETER") - package com.tangem.feature.wallet.presentation.organizetokens +import androidx.compose.animation.core.animateDpAsState +import androidx.compose.foundation.background import androidx.compose.foundation.layout.* -import androidx.compose.material3.* +import androidx.compose.foundation.lazy.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.AppBarDefaults +import androidx.compose.material3.FabPosition +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.composed +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +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 com.tangem.core.ui.components.BackgroundActionButton +import com.tangem.core.ui.components.PrimaryButton +import com.tangem.core.ui.components.SecondaryButton import com.tangem.core.ui.res.TangemTheme +import com.tangem.feature.wallet.impl.R import com.tangem.feature.wallet.presentation.common.WalletPreviewData -import com.tangem.feature.wallet.presentation.common.state.TokenListState +import com.tangem.feature.wallet.presentation.common.component.DraggableNetworkGroupItem +import com.tangem.feature.wallet.presentation.common.component.DraggableTokenItem @Composable internal fun OrganizeTokensScreen(state: OrganizeTokensStateHolder) { + val tokensListState = rememberLazyListState() + Scaffold( topBar = { - TopBar(state.header) + TopBar(state.header, tokensListState) }, content = { paddingValues -> TokenList( - state = state.tokens, modifier = Modifier.padding(paddingValues), + listState = tokensListState, + state = state.itemsState, ) }, floatingActionButtonPosition = FabPosition.Center, floatingActionButton = { Actions(state.actions) }, - contentColor = TangemTheme.colors.background.primary, + containerColor = TangemTheme.colors.background.secondary, ) } @Composable -private fun TokenList(state: TokenListState, modifier: Modifier = Modifier) { - when (state) { - is TokenListState.GroupedByNetwork -> { - /* [REDACTED_TODO_COMMENT] */ - } - is TokenListState.Ungrouped -> { - /* [REDACTED_TODO_COMMENT] */ +private fun TokenList(listState: LazyListState, state: OrganizeTokensListState, modifier: Modifier = Modifier) { + Box(modifier = modifier) { + LazyColumn( + modifier = Modifier + .align(Alignment.TopCenter) + .padding(horizontal = TangemTheme.dimens.spacing16), + state = listState, + contentPadding = PaddingValues( + top = TangemTheme.dimens.spacing12, + bottom = TangemTheme.dimens.spacing92, + ), + ) { + itemsIndexed( + items = state.items, + key = { _, item -> item.id }, + ) { index, item -> + DraggableItem( + item = item, + index = index, + lastItemIndex = state.items.lastIndex, + ) + } } + + BottomGradient(modifier = Modifier.align(Alignment.BottomCenter)) } } -@OptIn(ExperimentalMaterial3Api::class) @Composable -private fun TopBar(config: OrganizeTokensStateHolder.HeaderConfig, modifier: Modifier = Modifier) { - TopAppBar( - title = { +private fun DraggableItem(index: Int, item: DraggableItem, lastItemIndex: Int) { + val itemModifier = Modifier + .clipFirstAndLastItems(index, lastItemIndex) + + when (item) { + is DraggableItem.GroupHeader -> DraggableNetworkGroupItem( + modifier = itemModifier, + state = item.groupState, + ) + is DraggableItem.Token -> DraggableTokenItem( + modifier = itemModifier, + state = item.tokenItemState, + ) + } +} + +@Composable +private fun BottomGradient(modifier: Modifier = Modifier) { + Box( + modifier = modifier + .fillMaxWidth() + .height(TangemTheme.dimens.size116) + .background( + brush = Brush.verticalGradient( + colors = listOf( + Color.Transparent, + TangemTheme.colors.background.secondary, + ), + ), + ), + ) +} + +@Composable +private fun TopBar( + config: OrganizeTokensStateHolder.HeaderConfig, + tokensListState: LazyListState, + modifier: Modifier = Modifier, +) { + val isElevationEnabled by remember { + derivedStateOf { + tokensListState.firstVisibleItemScrollOffset > 0 + } + } + val elevation by animateDpAsState( + targetValue = if (isElevationEnabled) AppBarDefaults.TopAppBarElevation else TangemTheme.dimens.elevation0, + label = "top_bar_shadow_elevation", + ) + + Column( + modifier = modifier + .shadow(elevation) + .background(TangemTheme.colors.background.secondary) + .padding(horizontal = TangemTheme.dimens.spacing16) + .fillMaxWidth(), + ) { + Box( + modifier = Modifier + .fillMaxWidth() + .heightIn(min = TangemTheme.dimens.size56), + contentAlignment = Alignment.CenterStart, + ) { Text( - text = "Organize tokens", // TODO: Move to resources + text = stringResource(id = R.string.organize_tokens_title), style = TangemTheme.typography.subtitle1, color = TangemTheme.colors.text.primary1, maxLines = 1, ) - }, - colors = TopAppBarDefaults.topAppBarColors( - containerColor = TangemTheme.colors.background.secondary, - titleContentColor = TangemTheme.colors.text.primary1, - ), - ) + } + Row( + modifier = Modifier + .fillMaxWidth() + .heightIn(min = TangemTheme.dimens.size56), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8), + ) { + BackgroundActionButton( + modifier = Modifier.weight(1f), + text = stringResource(id = R.string.organize_tokens_sort_by_balance), + iconResId = R.drawable.ic_sort_24, + onClick = config.onSortByBalanceClick, + ) + BackgroundActionButton( + modifier = Modifier.weight(1f), + text = stringResource(id = R.string.organize_tokens_group), + iconResId = R.drawable.ic_group_24, + onClick = config.onGroupByNetworkClick, + ) + } + } } @Composable private fun Actions(config: OrganizeTokensStateHolder.ActionsConfig, modifier: Modifier = Modifier) { - // TODO: Implement list apply and cancel actions + Row( + modifier = modifier + .padding(horizontal = TangemTheme.dimens.spacing16) + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12), + ) { + SecondaryButton( + modifier = Modifier.weight(1f), + text = stringResource(id = R.string.common_cancel), + onClick = config.onCancelClick, + ) + PrimaryButton( + modifier = Modifier.weight(1f), + text = stringResource(id = R.string.common_apply), + onClick = config.onApplyClick, + ) + } +} + +private fun Modifier.clipFirstAndLastItems(index: Int, lastItemIndex: Int): Modifier = composed { + when (index) { + 0 -> { + this + .clip( + RoundedCornerShape( + topStart = TangemTheme.dimens.radius16, + topEnd = TangemTheme.dimens.radius16, + ), + ) + } + lastItemIndex -> { + this + .clip( + RoundedCornerShape( + bottomStart = TangemTheme.dimens.radius16, + bottomEnd = TangemTheme.dimens.radius16, + ), + ) + } + else -> this + } } // region Preview @Preview(showBackground = true, widthDp = 360) @Composable -private fun OrganizeTokensScreenPreview_Light() { +private fun OrganizeTokensScreenPreview_Light( + @PreviewParameter(OrganizeTokensStateProvider::class) state: OrganizeTokensStateHolder, +) { TangemTheme { - OrganizeTokensScreen(state = WalletPreviewData.organizeTokensState) + OrganizeTokensScreen(state) } } @Preview(showBackground = true, widthDp = 360) @Composable -private fun OrganizeTokensScreenPreview_Dark() { +private fun OrganizeTokensScreenPreview_Dark( + @PreviewParameter(OrganizeTokensStateProvider::class) state: OrganizeTokensStateHolder, +) { TangemTheme(isDark = true) { - OrganizeTokensScreen(state = WalletPreviewData.organizeTokensState) + OrganizeTokensScreen(state) } } + +private class OrganizeTokensStateProvider : CollectionPreviewParameterProvider( + collection = listOf( + WalletPreviewData.organizeTokensState, + WalletPreviewData.groupedOrganizeTokensState, + ), +) // endregion Preview \ No newline at end of file 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 b9a8e797cb..9e76b66248 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 @@ -1,10 +1,14 @@ package com.tangem.feature.wallet.presentation.organizetokens -import com.tangem.feature.wallet.presentation.common.state.TokenListState +import androidx.compose.runtime.Immutable +import com.tangem.feature.wallet.presentation.common.state.NetworkGroupState +import com.tangem.feature.wallet.presentation.common.state.TokenItemState +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toPersistentList internal data class OrganizeTokensStateHolder( - val tokens: TokenListState, val header: HeaderConfig, + val itemsState: OrganizeTokensListState, val actions: ActionsConfig, ) { @@ -17,4 +21,45 @@ internal data class OrganizeTokensStateHolder( val onApplyClick: () -> Unit, val onCancelClick: () -> Unit, ) +} + +@Immutable +internal sealed class OrganizeTokensListState { + abstract val items: ImmutableList + + data class GroupedByNetwork( + override val items: ImmutableList, + ) : OrganizeTokensListState() + + data class Ungrouped( + override val items: ImmutableList, + ) : OrganizeTokensListState() + + @Suppress("UNCHECKED_CAST") + inline fun updateItems(update: (List) -> List): OrganizeTokensListState { + val updatedItems = update(this.items).toPersistentList() + + return when (this) { + is GroupedByNetwork -> this.copy(items = updatedItems) + is Ungrouped -> this.copy(items = updatedItems as ImmutableList) + } + } +} + +@Immutable +internal sealed interface DraggableItem { + val id: String + + data class GroupHeader( + val groupState: NetworkGroupState.Draggable, + ) : DraggableItem { + override val id: String = groupState.id + } + + data class Token( + val tokenItemState: TokenItemState.Draggable, + val groupId: String, + ) : DraggableItem { + override val id: String = tokenItemState.id + } } \ No newline at end of file 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 16f726eab1..cbdd25e47e 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 @@ -5,11 +5,14 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import com.tangem.feature.wallet.presentation.common.WalletPreviewData +import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensStateHolder.HeaderConfig import com.tangem.feature.wallet.presentation.router.InnerWalletRouter import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.collections.immutable.toPersistentList import javax.inject.Inject import kotlin.properties.Delegates +// FIXME: Implemented with preview data @HiltViewModel internal class OrganizeTokensViewModel @Inject constructor() : ViewModel() { @@ -18,5 +21,26 @@ internal class OrganizeTokensViewModel @Inject constructor() : ViewModel() { var uiState: OrganizeTokensStateHolder by mutableStateOf(getInitialState()) private set - private fun getInitialState(): OrganizeTokensStateHolder = WalletPreviewData.organizeTokensState + private fun getInitialState(): OrganizeTokensStateHolder = WalletPreviewData.organizeTokensState.copy( + itemsState = OrganizeTokensListState.Ungrouped( + items = WalletPreviewData.draggableTokens, + ), + header = HeaderConfig( + onSortByBalanceClick = { /* no-op */ }, + onGroupByNetworkClick = this::toggleTokensByNetworkGrouping, + ), + ) + + private fun toggleTokensByNetworkGrouping() { + val newListState = when (val itemsState = uiState.itemsState) { + is OrganizeTokensListState.GroupedByNetwork -> OrganizeTokensListState.Ungrouped( + items = itemsState.items.filterIsInstance().toPersistentList(), + ) + is OrganizeTokensListState.Ungrouped -> OrganizeTokensListState.GroupedByNetwork( + items = WalletPreviewData.draggableItems, + ) + } + + uiState = uiState.copy(itemsState = newListState) + } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt index f2768ed116..b06a719e1f 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt @@ -8,7 +8,6 @@ import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController -import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.wallet.presentation.WalletFragment import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensScreen import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensViewModel @@ -25,24 +24,22 @@ internal class DefaultWalletRouter : InnerWalletRouter { @Composable override fun Initialize() { - TangemTheme { - NavHost( - navController = rememberNavController().apply { navController = this }, - startDestination = WalletScreens.WALLET.name, - ) { - composable(WalletScreens.WALLET.name) { - val viewModel = hiltViewModel().apply { router = this@DefaultWalletRouter } - WalletScreen(state = viewModel.uiState) - } + NavHost( + navController = rememberNavController().apply { navController = this }, + startDestination = WalletScreens.WALLET.name, + ) { + composable(WalletScreens.WALLET.name) { + val viewModel = hiltViewModel().apply { router = this@DefaultWalletRouter } + WalletScreen(state = viewModel.uiState) + } - composable(WalletScreens.ORGANIZE_TOKENS.name) { - BackHandler(onBack = ::popBackStack) + composable(WalletScreens.ORGANIZE_TOKENS.name) { + BackHandler(onBack = ::popBackStack) - val viewModel: OrganizeTokensViewModel = hiltViewModel() - .apply { router = this@DefaultWalletRouter } + val viewModel: OrganizeTokensViewModel = hiltViewModel() + .apply { router = this@DefaultWalletRouter } - OrganizeTokensScreen(state = viewModel.uiState) - } + OrganizeTokensScreen(state = viewModel.uiState) } } }