Updated on 2026-08-14
This commit is contained in:
commit
9abedff8b5
80 changed files with 1567 additions and 3658 deletions
|
|
@ -22,6 +22,7 @@ dependencies {
|
|||
implementation(deps.compose.ui.tooling)
|
||||
implementation(deps.compose.shimmer)
|
||||
implementation(deps.compose.accompanist.systemUiController)
|
||||
implementation(deps.compose.reorderable)
|
||||
|
||||
/** Other libraries */
|
||||
implementation(deps.kotlin.immutable.collections)
|
||||
|
|
|
|||
|
|
@ -7,10 +7,7 @@ import com.tangem.feature.wallet.presentation.common.state.TokenItemState.TokenO
|
|||
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.WalletContentItemState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletTopBarConfig
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.*
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import java.util.UUID
|
||||
|
|
@ -108,21 +105,22 @@ internal object WalletPreviewData {
|
|||
private const val tokensSize = 3
|
||||
val draggableItems = List(networksSize) { it }
|
||||
.flatMap { index ->
|
||||
val n = index + 1
|
||||
val lastNetworkIndex = networksSize - 1
|
||||
val networkNumber = index + 1
|
||||
|
||||
val group = DraggableItem.GroupHeader(
|
||||
id = "group_$n",
|
||||
networkName = "$n",
|
||||
id = "group_$networkNumber",
|
||||
networkName = "$networkNumber",
|
||||
)
|
||||
|
||||
val tokens: MutableList<DraggableItem.Token> = mutableListOf()
|
||||
repeat(times = tokensSize) { i ->
|
||||
val nt = i + 1
|
||||
val tokenNumber = i + 1
|
||||
tokens.add(
|
||||
DraggableItem.Token(
|
||||
tokenItemState = tokenItemDragState.copy(
|
||||
id = "${group.id}_token_$nt",
|
||||
name = "Token $nt",
|
||||
id = "${group.id}_token_$tokenNumber",
|
||||
name = "Token $tokenNumber",
|
||||
networkIconResId = R.drawable.img_eth_22.takeIf { i != 0 },
|
||||
),
|
||||
groupId = group.id,
|
||||
|
|
@ -130,9 +128,14 @@ internal object WalletPreviewData {
|
|||
)
|
||||
}
|
||||
|
||||
val divider = DraggableItem.GroupPlaceholder(id = "divider_$networkNumber")
|
||||
|
||||
buildList {
|
||||
add(group)
|
||||
addAll(tokens)
|
||||
if (index != lastNetworkIndex) {
|
||||
add(divider)
|
||||
}
|
||||
}
|
||||
}
|
||||
.toPersistentList()
|
||||
|
|
@ -149,6 +152,12 @@ internal object WalletPreviewData {
|
|||
onSortByBalanceClick = {},
|
||||
onGroupByNetworkClick = {},
|
||||
),
|
||||
dragConfig = OrganizeTokensStateHolder.DragConfig(
|
||||
onItemDragged = { _, _ -> },
|
||||
onDragStart = {},
|
||||
canDragItemOver = { _, _ -> false },
|
||||
onItemDragEnd = {},
|
||||
),
|
||||
actions = OrganizeTokensStateHolder.ActionsConfig(
|
||||
onApplyClick = {},
|
||||
onCancelClick = {},
|
||||
|
|
@ -161,6 +170,14 @@ internal object WalletPreviewData {
|
|||
),
|
||||
)
|
||||
|
||||
val manageButtons = persistentListOf(
|
||||
WalletManageButton.Buy(onClick = {}),
|
||||
WalletManageButton.Send(onClick = {}),
|
||||
WalletManageButton.Receive(onClick = {}),
|
||||
WalletManageButton.Exchange(onClick = {}),
|
||||
WalletManageButton.CopyAddress(onClick = {}),
|
||||
)
|
||||
|
||||
val multicurrencyWalletScreenState = WalletStateHolder.MultiCurrencyContent(
|
||||
onBackClick = {},
|
||||
topBarConfig = walletTopBarConfig,
|
||||
|
|
@ -220,6 +237,12 @@ internal object WalletPreviewData {
|
|||
),
|
||||
),
|
||||
),
|
||||
notifications = persistentListOf(
|
||||
WalletNotification.UnreachableNetworks,
|
||||
WalletNotification.LikeTangemApp(onClick = {}),
|
||||
WalletNotification.NeedToBackup(onClick = {}),
|
||||
WalletNotification.ScanCard(onClick = {}),
|
||||
),
|
||||
onOrganizeTokensClick = {},
|
||||
)
|
||||
|
||||
|
|
@ -252,5 +275,12 @@ internal object WalletPreviewData {
|
|||
),
|
||||
),
|
||||
),
|
||||
notifications = persistentListOf(
|
||||
WalletNotification.UnreachableNetworks,
|
||||
WalletNotification.LikeTangemApp(onClick = {}),
|
||||
WalletNotification.NeedToBackup(onClick = {}),
|
||||
WalletNotification.ScanCard(onClick = {}),
|
||||
),
|
||||
buttons = manageButtons,
|
||||
)
|
||||
}
|
||||
|
|
@ -14,6 +14,8 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
|||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
import org.burnoutcrew.reorderable.ReorderableLazyListState
|
||||
import org.burnoutcrew.reorderable.detectReorder
|
||||
|
||||
@Composable
|
||||
internal fun NetworkGroupItem(networkName: String, modifier: Modifier = Modifier) {
|
||||
|
|
@ -24,16 +26,33 @@ internal fun NetworkGroupItem(networkName: String, modifier: Modifier = Modifier
|
|||
}
|
||||
|
||||
@Composable
|
||||
internal fun DraggableNetworkGroupItem(networkName: String, modifier: Modifier = Modifier) {
|
||||
internal fun DraggableNetworkGroupItem(
|
||||
networkName: String,
|
||||
modifier: Modifier = Modifier,
|
||||
reorderableTokenListState: ReorderableLazyListState? = null,
|
||||
) {
|
||||
InternalNetworkGroupItem(
|
||||
modifier = modifier,
|
||||
networkName = networkName,
|
||||
endIcon = {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_group_drop_24),
|
||||
tint = TangemTheme.colors.icon.informative,
|
||||
contentDescription = null,
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(TangemTheme.dimens.size32)
|
||||
.let {
|
||||
if (reorderableTokenListState != null) {
|
||||
it.detectReorder(reorderableTokenListState)
|
||||
} else {
|
||||
it
|
||||
}
|
||||
},
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_group_drop_24),
|
||||
tint = TangemTheme.colors.icon.informative,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,8 @@ import com.tangem.feature.wallet.impl.R
|
|||
import com.tangem.feature.wallet.presentation.common.WalletPreviewData
|
||||
import com.tangem.feature.wallet.presentation.common.state.TokenItemState
|
||||
import com.tangem.feature.wallet.presentation.common.state.TokenItemState.TokenOptionsState
|
||||
import org.burnoutcrew.reorderable.ReorderableLazyListState
|
||||
import org.burnoutcrew.reorderable.detectReorder
|
||||
|
||||
private const val DOTS = "•••"
|
||||
val TOKEN_ITEM_HEIGHT: Dp
|
||||
|
|
@ -46,7 +48,7 @@ internal fun TokenItem(state: TokenItemState, modifier: Modifier = Modifier) {
|
|||
when (state) {
|
||||
is TokenItemState.Content -> ContentTokenItem(state, modifier)
|
||||
is TokenItemState.Loading -> LoadingTokenItem(modifier)
|
||||
is TokenItemState.Draggable -> DraggableTokenItem(state, modifier)
|
||||
is TokenItemState.Draggable -> DraggableTokenItem(state, modifier, reorderableTokenListState = null)
|
||||
is TokenItemState.Unreachable -> UnreachableTokenItem(state, modifier)
|
||||
}
|
||||
}
|
||||
|
|
@ -71,7 +73,11 @@ private fun ContentTokenItem(content: TokenItemState.Content, modifier: Modifier
|
|||
}
|
||||
|
||||
@Composable
|
||||
internal fun DraggableTokenItem(state: TokenItemState.Draggable, modifier: Modifier = Modifier) {
|
||||
internal fun DraggableTokenItem(
|
||||
state: TokenItemState.Draggable,
|
||||
modifier: Modifier = Modifier,
|
||||
reorderableTokenListState: ReorderableLazyListState? = null,
|
||||
) {
|
||||
InternalTokenItem(
|
||||
modifier = modifier,
|
||||
name = state.name,
|
||||
|
|
@ -81,13 +87,25 @@ internal fun DraggableTokenItem(state: TokenItemState.Draggable, modifier: Modif
|
|||
amount = state.fiatAmount,
|
||||
hasPending = false,
|
||||
options = { ref ->
|
||||
Icon(
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.constrainAsOptionsItem(scope = this, ref),
|
||||
painter = painterResource(id = R.drawable.ic_drag_24),
|
||||
tint = TangemTheme.colors.icon.informative,
|
||||
contentDescription = null,
|
||||
)
|
||||
.size(TangemTheme.dimens.size32)
|
||||
.constrainAsOptionsItem(scope = this, ref)
|
||||
.let {
|
||||
if (reorderableTokenListState != null) {
|
||||
it.detectReorder(reorderableTokenListState)
|
||||
} else {
|
||||
it
|
||||
}
|
||||
},
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_drag_24),
|
||||
tint = TangemTheme.colors.icon.informative,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,10 +9,7 @@ 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.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.composed
|
||||
|
|
@ -24,14 +21,19 @@ 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.components.buttons.actions.ActionConfig
|
||||
import com.tangem.core.ui.components.buttons.actions.BackgroundActionButton
|
||||
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.component.DraggableNetworkGroupItem
|
||||
import com.tangem.feature.wallet.presentation.common.component.DraggableTokenItem
|
||||
import org.burnoutcrew.reorderable.ReorderableItem
|
||||
import org.burnoutcrew.reorderable.ReorderableLazyListState
|
||||
import org.burnoutcrew.reorderable.rememberReorderableLazyListState
|
||||
import org.burnoutcrew.reorderable.reorderable
|
||||
|
||||
@Composable
|
||||
internal fun OrganizeTokensScreen(state: OrganizeTokensStateHolder, modifier: Modifier = Modifier) {
|
||||
|
|
@ -47,6 +49,7 @@ internal fun OrganizeTokensScreen(state: OrganizeTokensStateHolder, modifier: Mo
|
|||
modifier = Modifier.padding(paddingValues),
|
||||
listState = tokensListState,
|
||||
state = state.itemsState,
|
||||
dragConfig = state.dragConfig,
|
||||
)
|
||||
},
|
||||
floatingActionButtonPosition = FabPosition.Center,
|
||||
|
|
@ -57,28 +60,56 @@ internal fun OrganizeTokensScreen(state: OrganizeTokensStateHolder, modifier: Mo
|
|||
)
|
||||
}
|
||||
|
||||
// TODO: Fix list animations
|
||||
@Composable
|
||||
private fun TokenList(listState: LazyListState, state: OrganizeTokensListState, modifier: Modifier = Modifier) {
|
||||
private fun TokenList(
|
||||
listState: LazyListState,
|
||||
state: OrganizeTokensListState,
|
||||
dragConfig: OrganizeTokensStateHolder.DragConfig,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Box(modifier = modifier) {
|
||||
val reorderableListState = rememberReorderableLazyListState(
|
||||
onMove = dragConfig.onItemDragged,
|
||||
listState = listState,
|
||||
canDragOver = dragConfig.canDragItemOver,
|
||||
onDragEnd = { _, _ -> dragConfig.onItemDragEnd() },
|
||||
)
|
||||
val items = state.items
|
||||
val lastItemIndex = items.lastIndex
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.reorderable(reorderableListState)
|
||||
.align(Alignment.TopCenter)
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16),
|
||||
state = listState,
|
||||
state = reorderableListState.listState,
|
||||
contentPadding = PaddingValues(
|
||||
top = TangemTheme.dimens.spacing12,
|
||||
bottom = TangemTheme.dimens.spacing92,
|
||||
),
|
||||
) {
|
||||
itemsIndexed(
|
||||
items = state.items,
|
||||
items = items,
|
||||
key = { _, item -> item.id },
|
||||
) { index, item ->
|
||||
|
||||
val onDragStart = remember(item) {
|
||||
{ dragConfig.onDragStart(item) }
|
||||
}
|
||||
|
||||
DraggableItem(
|
||||
item = item,
|
||||
index = index,
|
||||
lastItemIndex = state.items.lastIndex,
|
||||
lastItemIndex = lastItemIndex,
|
||||
reorderableState = reorderableListState,
|
||||
onDragStart = onDragStart,
|
||||
)
|
||||
|
||||
if (item is DraggableItem.GroupPlaceholder) {
|
||||
// This item should be displayed in the list but remain invisible
|
||||
Box(modifier = Modifier.fillMaxWidth())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -87,19 +118,39 @@ private fun TokenList(listState: LazyListState, state: OrganizeTokensListState,
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun DraggableItem(index: Int, item: DraggableItem, lastItemIndex: Int) {
|
||||
val itemModifier = Modifier
|
||||
.clipFirstAndLastItems(index, lastItemIndex)
|
||||
private fun LazyItemScope.DraggableItem(
|
||||
index: Int,
|
||||
item: DraggableItem,
|
||||
lastItemIndex: Int,
|
||||
reorderableState: ReorderableLazyListState,
|
||||
onDragStart: () -> Unit,
|
||||
) {
|
||||
ReorderableItem(
|
||||
reorderableState = reorderableState,
|
||||
index = index,
|
||||
key = item.id,
|
||||
) { isDragging ->
|
||||
|
||||
when (item) {
|
||||
is DraggableItem.GroupHeader -> DraggableNetworkGroupItem(
|
||||
modifier = itemModifier,
|
||||
networkName = item.networkName,
|
||||
)
|
||||
is DraggableItem.Token -> DraggableTokenItem(
|
||||
modifier = itemModifier,
|
||||
state = item.tokenItemState,
|
||||
)
|
||||
if (isDragging) {
|
||||
onDragStart()
|
||||
}
|
||||
|
||||
val itemModifier = Modifier
|
||||
.clipFirstLastAndDraggingItems(index, lastItemIndex, isDragging)
|
||||
|
||||
when (item) {
|
||||
is DraggableItem.GroupHeader -> DraggableNetworkGroupItem(
|
||||
modifier = itemModifier,
|
||||
networkName = item.networkName,
|
||||
reorderableTokenListState = reorderableState,
|
||||
)
|
||||
is DraggableItem.Token -> DraggableTokenItem(
|
||||
modifier = itemModifier,
|
||||
state = item.tokenItemState,
|
||||
reorderableTokenListState = reorderableState,
|
||||
)
|
||||
is DraggableItem.GroupPlaceholder -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -164,16 +215,20 @@ private fun TopBar(
|
|||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
|
||||
) {
|
||||
BackgroundActionButton(
|
||||
config = ActionConfig(
|
||||
text = stringResource(id = R.string.organize_tokens_sort_by_balance),
|
||||
iconResId = R.drawable.ic_sort_24,
|
||||
onClick = config.onSortByBalanceClick,
|
||||
),
|
||||
modifier = Modifier.weight(1f),
|
||||
text = stringResource(id = R.string.organize_tokens_sort_by_balance),
|
||||
iconResId = R.drawable.ic_sort_24,
|
||||
onClick = config.onSortByBalanceClick,
|
||||
)
|
||||
BackgroundActionButton(
|
||||
config = ActionConfig(
|
||||
text = stringResource(id = R.string.organize_tokens_group),
|
||||
iconResId = R.drawable.ic_group_24,
|
||||
onClick = config.onGroupByNetworkClick,
|
||||
),
|
||||
modifier = Modifier.weight(1f),
|
||||
text = stringResource(id = R.string.organize_tokens_group),
|
||||
iconResId = R.drawable.ic_group_24,
|
||||
onClick = config.onGroupByNetworkClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -201,29 +256,36 @@ private fun Actions(config: OrganizeTokensStateHolder.ActionsConfig, modifier: M
|
|||
}
|
||||
}
|
||||
|
||||
private fun Modifier.clipFirstAndLastItems(index: Int, lastItemIndex: Int): Modifier = composed {
|
||||
when (index) {
|
||||
0 -> {
|
||||
this
|
||||
.clip(
|
||||
private fun Modifier.clipFirstLastAndDraggingItems(index: Int, lastItemIndex: Int, isDragging: Boolean): Modifier =
|
||||
composed {
|
||||
when {
|
||||
isDragging -> {
|
||||
val elevation by animateDpAsState(
|
||||
targetValue = TangemTheme.dimens.elevation12,
|
||||
label = "dragging_item_shadow_elevation",
|
||||
)
|
||||
|
||||
this.shadow(elevation, shape = TangemTheme.shapes.roundedCornersXMedium)
|
||||
}
|
||||
index == 0 -> {
|
||||
this.clip(
|
||||
RoundedCornerShape(
|
||||
topStart = TangemTheme.dimens.radius16,
|
||||
topEnd = TangemTheme.dimens.radius16,
|
||||
),
|
||||
)
|
||||
}
|
||||
lastItemIndex -> {
|
||||
this
|
||||
.clip(
|
||||
}
|
||||
index == lastItemIndex -> {
|
||||
this.clip(
|
||||
RoundedCornerShape(
|
||||
bottomStart = TangemTheme.dimens.radius16,
|
||||
bottomEnd = TangemTheme.dimens.radius16,
|
||||
),
|
||||
)
|
||||
}
|
||||
else -> this
|
||||
}
|
||||
else -> this
|
||||
}
|
||||
}
|
||||
|
||||
// region Preview
|
||||
|
||||
|
|
|
|||
|
|
@ -2,12 +2,14 @@ package com.tangem.feature.wallet.presentation.organizetokens
|
|||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.feature.wallet.presentation.common.state.TokenItemState
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.PersistentList
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import org.burnoutcrew.reorderable.ItemPosition
|
||||
|
||||
internal data class OrganizeTokensStateHolder(
|
||||
val header: HeaderConfig,
|
||||
val itemsState: OrganizeTokensListState,
|
||||
val dragConfig: DragConfig,
|
||||
val actions: ActionsConfig,
|
||||
) {
|
||||
|
||||
|
|
@ -20,44 +22,79 @@ internal data class OrganizeTokensStateHolder(
|
|||
val onApplyClick: () -> Unit,
|
||||
val onCancelClick: () -> Unit,
|
||||
)
|
||||
|
||||
data class DragConfig(
|
||||
val onItemDragged: (from: ItemPosition, to: ItemPosition) -> Unit,
|
||||
val canDragItemOver: (dragOver: ItemPosition, dragging: ItemPosition) -> Boolean,
|
||||
val onItemDragEnd: () -> Unit,
|
||||
val onDragStart: (item: DraggableItem) -> Unit,
|
||||
)
|
||||
}
|
||||
|
||||
@Immutable
|
||||
internal sealed class OrganizeTokensListState {
|
||||
abstract val items: ImmutableList<DraggableItem>
|
||||
internal sealed interface OrganizeTokensListState {
|
||||
val items: PersistentList<DraggableItem>
|
||||
|
||||
data class GroupedByNetwork(
|
||||
override val items: ImmutableList<DraggableItem>,
|
||||
) : OrganizeTokensListState()
|
||||
override val items: PersistentList<DraggableItem>,
|
||||
) : OrganizeTokensListState
|
||||
|
||||
data class Ungrouped(
|
||||
override val items: ImmutableList<DraggableItem.Token>,
|
||||
) : OrganizeTokensListState()
|
||||
override val items: PersistentList<DraggableItem.Token>,
|
||||
) : OrganizeTokensListState
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
inline fun updateItems(update: (List<DraggableItem>) -> List<DraggableItem>): OrganizeTokensListState {
|
||||
fun updateItems(update: (PersistentList<DraggableItem>) -> List<DraggableItem>): OrganizeTokensListState {
|
||||
val updatedItems = update(this.items).toPersistentList()
|
||||
|
||||
return when (this) {
|
||||
is GroupedByNetwork -> this.copy(items = updatedItems)
|
||||
is Ungrouped -> this.copy(items = updatedItems as ImmutableList<DraggableItem.Token>)
|
||||
is Ungrouped -> this.copy(items = updatedItems as PersistentList<DraggableItem.Token>)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class for the DND list items
|
||||
*
|
||||
* @property id ID of the item
|
||||
* */
|
||||
@Immutable
|
||||
internal sealed interface DraggableItem {
|
||||
val id: String
|
||||
|
||||
/**
|
||||
* Item for network group header.
|
||||
*
|
||||
* @property id ID of the network group
|
||||
* @property networkName network group name
|
||||
* */
|
||||
data class GroupHeader(
|
||||
override val id: String,
|
||||
val networkName: String,
|
||||
) : DraggableItem
|
||||
|
||||
/**
|
||||
* Item for token.
|
||||
*
|
||||
* @property tokenItemState state of the token item
|
||||
* @property groupId ID of the network group which contains this token
|
||||
* @property id ID of the token
|
||||
* */
|
||||
data class Token(
|
||||
val tokenItemState: TokenItemState.Draggable,
|
||||
val groupId: String,
|
||||
) : DraggableItem {
|
||||
override val id: String = tokenItemState.id
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper item used to detect possible positions where a network group can be placed.
|
||||
* Used only on [OrganizeTokensListState.GroupedByNetwork] and placed between network groups.
|
||||
*
|
||||
* @property id ID of the placeholder
|
||||
* */
|
||||
data class GroupPlaceholder(
|
||||
override val id: String,
|
||||
) : DraggableItem
|
||||
}
|
||||
|
|
@ -5,10 +5,16 @@ 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.DragConfig
|
||||
import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensStateHolder.HeaderConfig
|
||||
import com.tangem.feature.wallet.presentation.organizetokens.utils.checkCanMoveHeaderOver
|
||||
import com.tangem.feature.wallet.presentation.organizetokens.utils.checkCanMoveTokenOver
|
||||
import com.tangem.feature.wallet.presentation.organizetokens.utils.findItemsToMove
|
||||
import com.tangem.feature.wallet.presentation.organizetokens.utils.moveItem
|
||||
import com.tangem.feature.wallet.presentation.router.InnerWalletRouter
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import org.burnoutcrew.reorderable.ItemPosition
|
||||
import javax.inject.Inject
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
|
|
@ -16,6 +22,10 @@ import kotlin.properties.Delegates
|
|||
@HiltViewModel
|
||||
internal class OrganizeTokensViewModel @Inject constructor() : ViewModel() {
|
||||
|
||||
// TODO: Move to domain
|
||||
@Volatile
|
||||
private var groupIdToTokens: Map<String, List<DraggableItem.Token>>? = null
|
||||
|
||||
var router: InnerWalletRouter by Delegates.notNull()
|
||||
|
||||
var uiState: OrganizeTokensStateHolder by mutableStateOf(getInitialState())
|
||||
|
|
@ -25,6 +35,12 @@ internal class OrganizeTokensViewModel @Inject constructor() : ViewModel() {
|
|||
itemsState = OrganizeTokensListState.Ungrouped(
|
||||
items = WalletPreviewData.draggableTokens,
|
||||
),
|
||||
dragConfig = DragConfig(
|
||||
onItemDragged = this::moveItem,
|
||||
canDragItemOver = this::checkCanMoveItemOver,
|
||||
onItemDragEnd = this::expandGroups,
|
||||
onDragStart = this::collapseGroup,
|
||||
),
|
||||
header = HeaderConfig(
|
||||
onSortByBalanceClick = { /* no-op */ },
|
||||
onGroupByNetworkClick = this::toggleTokensByNetworkGrouping,
|
||||
|
|
@ -43,4 +59,69 @@ internal class OrganizeTokensViewModel @Inject constructor() : ViewModel() {
|
|||
|
||||
uiState = uiState.copy(itemsState = newListState)
|
||||
}
|
||||
|
||||
private fun checkCanMoveItemOver(moveOverItemPosition: ItemPosition, movedItemPosition: ItemPosition): Boolean {
|
||||
val items = (uiState.itemsState as? OrganizeTokensListState.GroupedByNetwork)
|
||||
?.items
|
||||
?: return true // If ungrouped then item can be moved anywhere
|
||||
|
||||
val (moveOverItem, movedItem) = items.findItemsToMove(moveOverItemPosition.key, movedItemPosition.key)
|
||||
|
||||
if (moveOverItem == null || movedItem == null) {
|
||||
return false
|
||||
}
|
||||
|
||||
return when (movedItem) {
|
||||
is DraggableItem.GroupHeader -> checkCanMoveHeaderOver(moveOverItemPosition, moveOverItem, items.lastIndex)
|
||||
is DraggableItem.Token -> checkCanMoveTokenOver(movedItem, moveOverItem)
|
||||
is DraggableItem.GroupPlaceholder -> false
|
||||
}
|
||||
}
|
||||
|
||||
private fun collapseGroup(item: DraggableItem) {
|
||||
if (!groupIdToTokens.isNullOrEmpty() || item is DraggableItem.Token) return
|
||||
|
||||
val itemsState = uiState.itemsState as? OrganizeTokensListState.GroupedByNetwork ?: return
|
||||
groupIdToTokens = itemsState.items
|
||||
.asSequence()
|
||||
.filterIsInstance<DraggableItem.Token>()
|
||||
.groupBy(DraggableItem.Token::groupId)
|
||||
|
||||
uiState = uiState.copy(
|
||||
itemsState = itemsState.updateItems { items ->
|
||||
items.filterNot { it is DraggableItem.Token && it.groupId == item.id }
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun expandGroups() {
|
||||
if (groupIdToTokens.isNullOrEmpty()) return
|
||||
|
||||
val itemsState = uiState.itemsState as? OrganizeTokensListState.GroupedByNetwork ?: return
|
||||
val currentGroups = itemsState.items.filterIsInstance<DraggableItem.GroupHeader>()
|
||||
val newItems = currentGroups
|
||||
.flatMapIndexed { index, group ->
|
||||
mutableListOf<DraggableItem>(group)
|
||||
.also { it.addAll(groupIdToTokens?.get(group.id).orEmpty()) }
|
||||
.also {
|
||||
if (index != currentGroups.lastIndex) {
|
||||
it.add(DraggableItem.GroupPlaceholder(id = "group_divider_$index"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uiState = uiState.copy(
|
||||
itemsState = itemsState.updateItems { newItems },
|
||||
)
|
||||
|
||||
groupIdToTokens = null
|
||||
}
|
||||
|
||||
private fun moveItem(from: ItemPosition, to: ItemPosition) {
|
||||
uiState = uiState.copy(
|
||||
itemsState = uiState.itemsState.updateItems {
|
||||
it.moveItem(from.index, to.index)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
package com.tangem.feature.wallet.presentation.organizetokens.utils
|
||||
|
||||
import com.tangem.feature.wallet.presentation.organizetokens.DraggableItem
|
||||
import kotlinx.collections.immutable.PersistentList
|
||||
import org.burnoutcrew.reorderable.ItemPosition
|
||||
|
||||
internal fun List<DraggableItem>.findItemsToMove(
|
||||
moveOverItemKey: Any?,
|
||||
movedItemKey: Any?,
|
||||
): Pair<DraggableItem?, DraggableItem?> {
|
||||
var moveOverItem: DraggableItem? = null
|
||||
var movedItem: DraggableItem? = null
|
||||
|
||||
for (item in this) {
|
||||
if (item.id == moveOverItemKey) {
|
||||
moveOverItem = item
|
||||
}
|
||||
if (item.id == movedItemKey) {
|
||||
movedItem = item
|
||||
}
|
||||
if (moveOverItem != null && movedItem != null) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return Pair(moveOverItem, movedItem)
|
||||
}
|
||||
|
||||
internal fun checkCanMoveHeaderOver(
|
||||
moveOverItemPosition: ItemPosition,
|
||||
moveOverItem: DraggableItem,
|
||||
lastItemIndex: Int,
|
||||
): Boolean {
|
||||
return when {
|
||||
moveOverItemPosition.index == 0 -> true
|
||||
moveOverItemPosition.index == lastItemIndex -> true
|
||||
moveOverItem is DraggableItem.GroupPlaceholder -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
internal fun checkCanMoveTokenOver(item: DraggableItem.Token, moveOverItem: DraggableItem): Boolean {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
internal fun PersistentList<DraggableItem>.moveItem(fromIndex: Int, toIndex: Int): PersistentList<DraggableItem> {
|
||||
val fromItem = this[fromIndex]
|
||||
return this
|
||||
.removeAt(fromIndex)
|
||||
.add(toIndex, fromItem)
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state
|
||||
|
||||
import com.tangem.core.ui.components.buttons.actions.ActionConfig
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
|
||||
/**
|
||||
* Wallet manage button state
|
||||
*
|
||||
* @param config action config
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
sealed class WalletManageButton(val config: ActionConfig) {
|
||||
|
||||
/**
|
||||
* Buy
|
||||
*
|
||||
* @param onClick lambda be invoked when manage button is clicked
|
||||
*/
|
||||
data class Buy(val onClick: () -> Unit) : WalletManageButton(
|
||||
config = ActionConfig(
|
||||
text = "Buy",
|
||||
iconResId = R.drawable.ic_plus_24,
|
||||
onClick = onClick,
|
||||
),
|
||||
)
|
||||
|
||||
/**
|
||||
* Send
|
||||
*
|
||||
* @param onClick lambda be invoked when manage button is clicked
|
||||
*/
|
||||
data class Send(val onClick: () -> Unit) : WalletManageButton(
|
||||
config = ActionConfig(
|
||||
text = "Send",
|
||||
iconResId = R.drawable.ic_arrow_up_24,
|
||||
onClick = onClick,
|
||||
),
|
||||
)
|
||||
|
||||
/**
|
||||
* Receive
|
||||
*
|
||||
* @param onClick lambda be invoked when manage button is clicked
|
||||
*/
|
||||
data class Receive(val onClick: () -> Unit) : WalletManageButton(
|
||||
config = ActionConfig(
|
||||
text = "Receive",
|
||||
iconResId = R.drawable.ic_arrow_down_24,
|
||||
onClick = onClick,
|
||||
),
|
||||
)
|
||||
|
||||
/**
|
||||
* Exchange
|
||||
*
|
||||
* @param onClick lambda be invoked when manage button is clicked
|
||||
*/
|
||||
data class Exchange(val onClick: () -> Unit) : WalletManageButton(
|
||||
config = ActionConfig(
|
||||
text = "Exchange",
|
||||
iconResId = R.drawable.ic_exchange_vertical_24,
|
||||
onClick = onClick,
|
||||
),
|
||||
)
|
||||
|
||||
/**
|
||||
* Copy address
|
||||
*
|
||||
* @param onClick lambda be invoked when manage button is clicked
|
||||
*/
|
||||
data class CopyAddress(val onClick: () -> Unit) : WalletManageButton(
|
||||
config = ActionConfig(
|
||||
text = "Copy address",
|
||||
iconResId = R.drawable.ic_copy_24,
|
||||
onClick = onClick,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state
|
||||
|
||||
import androidx.compose.foundation.horizontalScroll
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.key
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import com.tangem.core.ui.components.buttons.actions.ActionButton
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.feature.wallet.presentation.common.WalletPreviewData
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
/**
|
||||
* Wallet manage buttons
|
||||
*
|
||||
* @param buttons manage buttons
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
internal fun WalletManageButtons(buttons: ImmutableList<WalletManageButton>) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.horizontalScroll(state = rememberScrollState())
|
||||
.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing8),
|
||||
) {
|
||||
buttons.forEach { button ->
|
||||
key(button.config.text) {
|
||||
ActionButton(config = button.config)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Preview_WalletManageButtons_Light(
|
||||
@PreviewParameter(WalletManageButtonProvider::class) buttons: ImmutableList<WalletManageButton>,
|
||||
) {
|
||||
TangemTheme(isDark = false) {
|
||||
WalletManageButtons(buttons = buttons)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Preview_WalletManageButtons_Dark(
|
||||
@PreviewParameter(WalletManageButtonProvider::class) buttons: ImmutableList<WalletManageButton>,
|
||||
) {
|
||||
TangemTheme(isDark = true) {
|
||||
WalletManageButtons(buttons = buttons)
|
||||
}
|
||||
}
|
||||
|
||||
private class WalletManageButtonProvider : CollectionPreviewParameterProvider<ImmutableList<WalletManageButton>>(
|
||||
collection = listOf(WalletPreviewData.manageButtons),
|
||||
)
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state
|
||||
|
||||
import com.tangem.core.ui.components.notifications.NotificationState
|
||||
import com.tangem.core.ui.res.TangemColorPalette
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
|
||||
/**
|
||||
* Wallet notification component state
|
||||
*
|
||||
* @property state state
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
sealed class WalletNotification(open val state: NotificationState) {
|
||||
|
||||
/**
|
||||
* "Backup the card" notification
|
||||
*
|
||||
* @property onClick lambda be invoked when notification is clicked
|
||||
*/
|
||||
data class NeedToBackup(val onClick: () -> Unit) : WalletNotification(
|
||||
state = NotificationState.Action(
|
||||
title = "Backup your card",
|
||||
iconResId = R.drawable.ic_alert_circle_24,
|
||||
onClick = onClick,
|
||||
tint = TangemColorPalette.Amaranth,
|
||||
),
|
||||
)
|
||||
|
||||
/** "Unreachable networks" notification */
|
||||
object UnreachableNetworks : WalletNotification(
|
||||
state = NotificationState.Simple(
|
||||
title = "Some networks are unreachable",
|
||||
iconResId = R.drawable.img_attention_20,
|
||||
tint = null,
|
||||
),
|
||||
)
|
||||
|
||||
/**
|
||||
* "Like Tangem App" notification
|
||||
*
|
||||
* @property onClick lambda be invoked when notification is clicked
|
||||
*/
|
||||
data class LikeTangemApp(val onClick: () -> Unit) : WalletNotification(
|
||||
state = NotificationState.Action(
|
||||
title = "Like Tangem App?",
|
||||
iconResId = R.drawable.ic_star_24,
|
||||
onClick = onClick,
|
||||
tint = TangemColorPalette.Tangerine,
|
||||
),
|
||||
)
|
||||
|
||||
/**
|
||||
* "Scan the card" notification
|
||||
*
|
||||
* @property onClick lambda be invoked when notification is clicked
|
||||
*/
|
||||
data class ScanCard(val onClick: () -> Unit) : WalletNotification(
|
||||
state = NotificationState.Action(
|
||||
title = "Scan your card to continue",
|
||||
iconResId = R.drawable.ic_tangem_24,
|
||||
onClick = onClick,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ import kotlinx.collections.immutable.ImmutableList
|
|||
* @property selectedWallet selected wallet
|
||||
* @property wallets list of wallets states
|
||||
* @property contentItems content items
|
||||
* @property notifications notifications
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
|
|
@ -19,16 +20,19 @@ internal sealed class WalletStateHolder(
|
|||
open val selectedWallet: WalletCardState,
|
||||
open val wallets: ImmutableList<WalletCardState>,
|
||||
open val contentItems: ImmutableList<WalletContentItemState>,
|
||||
open val notifications: ImmutableList<WalletNotification>,
|
||||
) {
|
||||
|
||||
/**
|
||||
* Multi currency wallet content state
|
||||
*
|
||||
* @property onBackClick lambda be invoked when back button is clicked
|
||||
* @property topBarConfig top bar config
|
||||
* @property selectedWallet selected wallet
|
||||
* @property wallets list of wallets states
|
||||
* @property contentItems content items
|
||||
* @property onBackClick lambda be invoked when back button is clicked
|
||||
* @property topBarConfig top bar config
|
||||
* @property selectedWallet selected wallet
|
||||
* @property wallets list of wallets states
|
||||
* @property contentItems content items
|
||||
* @property notifications notifications
|
||||
* @property onOrganizeTokensClick lambda be invoked when organize tokens button is clicked
|
||||
*/
|
||||
data class MultiCurrencyContent(
|
||||
override val onBackClick: () -> Unit,
|
||||
|
|
@ -36,8 +40,9 @@ internal sealed class WalletStateHolder(
|
|||
override val selectedWallet: WalletCardState,
|
||||
override val wallets: ImmutableList<WalletCardState>,
|
||||
override val contentItems: ImmutableList<WalletContentItemState.MultiCurrencyItem>,
|
||||
override val notifications: ImmutableList<WalletNotification>,
|
||||
val onOrganizeTokensClick: () -> Unit,
|
||||
) : WalletStateHolder(onBackClick, topBarConfig, selectedWallet, wallets, contentItems)
|
||||
) : WalletStateHolder(onBackClick, topBarConfig, selectedWallet, wallets, contentItems, notifications)
|
||||
|
||||
/**
|
||||
* Single currency wallet content state
|
||||
|
|
@ -47,6 +52,8 @@ internal sealed class WalletStateHolder(
|
|||
* @property selectedWallet selected wallet
|
||||
* @property wallets list of wallets states
|
||||
* @property contentItems content items
|
||||
* @property notifications notifications
|
||||
* @property buttons manage buttons
|
||||
*/
|
||||
data class SingleCurrencyContent(
|
||||
override val onBackClick: () -> Unit,
|
||||
|
|
@ -54,5 +61,7 @@ internal sealed class WalletStateHolder(
|
|||
override val selectedWallet: WalletCardState,
|
||||
override val wallets: ImmutableList<WalletCardState>,
|
||||
override val contentItems: ImmutableList<WalletContentItemState.SingleCurrencyItem>,
|
||||
) : WalletStateHolder(onBackClick, topBarConfig, selectedWallet, wallets, contentItems)
|
||||
override val notifications: ImmutableList<WalletNotification>,
|
||||
val buttons: ImmutableList<WalletManageButton>,
|
||||
) : WalletStateHolder(onBackClick, topBarConfig, selectedWallet, wallets, contentItems, notifications)
|
||||
}
|
||||
|
|
@ -1,28 +1,26 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.ui
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
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.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
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.RoundedActionButton
|
||||
import com.tangem.core.ui.components.buttons.actions.ActionConfig
|
||||
import com.tangem.core.ui.components.buttons.actions.RoundedActionButton
|
||||
import com.tangem.core.ui.components.notifications.Notification
|
||||
import com.tangem.core.ui.components.transactions.Transaction
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
|
|
@ -30,9 +28,11 @@ import com.tangem.feature.wallet.presentation.common.WalletPreviewData
|
|||
import com.tangem.feature.wallet.presentation.common.component.NetworkGroupItem
|
||||
import com.tangem.feature.wallet.presentation.common.component.TokenItem
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletContentItemState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletManageButtons
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder
|
||||
import com.tangem.feature.wallet.presentation.wallet.ui.components.WalletCardsList
|
||||
import com.tangem.feature.wallet.presentation.wallet.ui.components.WalletTopBar
|
||||
import com.tangem.feature.wallet.presentation.wallet.ui.decorations.walletContentItemDecoration
|
||||
|
||||
/**
|
||||
* Wallet screen
|
||||
|
|
@ -49,15 +49,29 @@ internal fun WalletScreen(state: WalletStateHolder, modifier: Modifier = Modifie
|
|||
topBar = { WalletTopBar(config = state.topBarConfig) },
|
||||
containerColor = TangemTheme.colors.background.secondary,
|
||||
) { scaffoldPaddings ->
|
||||
val lastContentItemIndex = remember(state.contentItems) { state.contentItems.lastIndex }
|
||||
|
||||
LazyColumn(
|
||||
modifier = modifier
|
||||
.padding(scaffoldPaddings)
|
||||
.padding(paddingValues = scaffoldPaddings)
|
||||
.fillMaxSize(),
|
||||
contentPadding = PaddingValues(
|
||||
horizontal = TangemTheme.dimens.spacing16,
|
||||
vertical = TangemTheme.dimens.spacing8,
|
||||
),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
item { WalletCardsList(wallets = state.wallets) }
|
||||
item {
|
||||
WalletCardsList(
|
||||
wallets = state.wallets,
|
||||
modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing12),
|
||||
)
|
||||
}
|
||||
|
||||
if (state is WalletStateHolder.SingleCurrencyContent) {
|
||||
item { WalletManageButtons(buttons = state.buttons) }
|
||||
}
|
||||
|
||||
items(items = state.notifications, itemContent = { Notification(state = it.state) })
|
||||
|
||||
itemsIndexed(
|
||||
items = state.contentItems,
|
||||
|
|
@ -70,36 +84,19 @@ internal fun WalletScreen(state: WalletStateHolder, modifier: Modifier = Modifie
|
|||
is WalletContentItemState.SingleCurrencyItem.Transaction -> index
|
||||
}
|
||||
},
|
||||
) { index, item ->
|
||||
val itemModifier = Modifier
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16)
|
||||
.clipFirstAndLastItems(index, lastContentItemIndex)
|
||||
|
||||
when (item) {
|
||||
is WalletContentItemState.MultiCurrencyItem.NetworkGroupTitle -> {
|
||||
NetworkGroupItem(networkName = item.networkName, modifier = itemModifier)
|
||||
}
|
||||
is WalletContentItemState.MultiCurrencyItem.Token -> {
|
||||
TokenItem(state = item.state, modifier = itemModifier)
|
||||
}
|
||||
is WalletContentItemState.SingleCurrencyItem.Title -> {
|
||||
SingleCurrencyTitle(config = item, modifier = itemModifier)
|
||||
}
|
||||
is WalletContentItemState.SingleCurrencyItem.TransactionGroupTitle -> {
|
||||
TransactionGroupTitle(config = item, modifier = itemModifier)
|
||||
}
|
||||
is WalletContentItemState.SingleCurrencyItem.Transaction -> {
|
||||
Transaction(state = item.state, modifier = itemModifier)
|
||||
}
|
||||
}
|
||||
}
|
||||
itemContent = { index, item ->
|
||||
ContentItem(currentIndex = index, lastIndex = state.contentItems.lastIndex, item = item)
|
||||
},
|
||||
)
|
||||
|
||||
if (state is WalletStateHolder.MultiCurrencyContent) {
|
||||
item {
|
||||
RoundedActionButton(
|
||||
text = stringResource(id = R.string.organize_tokens_title),
|
||||
iconResId = R.drawable.ic_filter_24,
|
||||
onClick = state.onOrganizeTokensClick,
|
||||
config = ActionConfig(
|
||||
text = stringResource(id = R.string.organize_tokens_title),
|
||||
iconResId = R.drawable.ic_filter_24,
|
||||
onClick = state.onOrganizeTokensClick,
|
||||
),
|
||||
modifier = Modifier.padding(top = TangemTheme.dimens.spacing14),
|
||||
)
|
||||
}
|
||||
|
|
@ -108,6 +105,29 @@ internal fun WalletScreen(state: WalletStateHolder, modifier: Modifier = Modifie
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ContentItem(currentIndex: Int, lastIndex: Int, item: WalletContentItemState) {
|
||||
val itemModifier = Modifier.walletContentItemDecoration(currentIndex, lastIndex)
|
||||
|
||||
when (item) {
|
||||
is WalletContentItemState.MultiCurrencyItem.NetworkGroupTitle -> {
|
||||
NetworkGroupItem(networkName = item.networkName, modifier = itemModifier)
|
||||
}
|
||||
is WalletContentItemState.MultiCurrencyItem.Token -> {
|
||||
TokenItem(state = item.state, modifier = itemModifier)
|
||||
}
|
||||
is WalletContentItemState.SingleCurrencyItem.Title -> {
|
||||
SingleCurrencyTitle(config = item, modifier = itemModifier)
|
||||
}
|
||||
is WalletContentItemState.SingleCurrencyItem.TransactionGroupTitle -> {
|
||||
TransactionGroupTitle(config = item, modifier = itemModifier)
|
||||
}
|
||||
is WalletContentItemState.SingleCurrencyItem.Transaction -> {
|
||||
Transaction(state = item.state, modifier = itemModifier)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SingleCurrencyTitle(
|
||||
config: WalletContentItemState.SingleCurrencyItem.Title,
|
||||
|
|
@ -166,31 +186,6 @@ private fun TransactionGroupTitle(
|
|||
)
|
||||
}
|
||||
|
||||
private fun Modifier.clipFirstAndLastItems(index: Int, lastItemIndex: Int): Modifier = composed {
|
||||
when (index) {
|
||||
0 -> {
|
||||
this
|
||||
.padding(top = TangemTheme.dimens.spacing14)
|
||||
.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
|
||||
|
|
|
|||
|
|
@ -23,21 +23,21 @@ import kotlinx.collections.immutable.persistentListOf
|
|||
/**
|
||||
* Wallets list
|
||||
*
|
||||
* @param wallets list of wallet state
|
||||
* @param wallets list of wallet state
|
||||
* @param modifier modifier
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
internal fun WalletCardsList(wallets: ImmutableList<WalletCardState>) {
|
||||
internal fun WalletCardsList(wallets: ImmutableList<WalletCardState>, modifier: Modifier = Modifier) {
|
||||
val horizontalCardPadding = TangemTheme.dimens.spacing16
|
||||
val itemWidth = LocalConfiguration.current.screenWidthDp.dp - horizontalCardPadding * 2
|
||||
|
||||
val lazyListState = rememberLazyListState()
|
||||
LazyRow(
|
||||
modifier = Modifier.background(color = TangemTheme.colors.background.secondary),
|
||||
modifier = modifier.background(color = TangemTheme.colors.background.secondary),
|
||||
state = lazyListState,
|
||||
contentPadding = PaddingValues(horizontal = TangemTheme.dimens.spacing16),
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
|
||||
flingBehavior = rememberSnapFlingBehavior(lazyListState = lazyListState),
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.ui.decorations
|
||||
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.composed
|
||||
import androidx.compose.ui.draw.clip
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal fun Modifier.walletContentItemDecoration(currentIndex: Int, lastItemIndex: Int): Modifier = composed {
|
||||
when (currentIndex) {
|
||||
0 -> {
|
||||
this
|
||||
.padding(top = TangemTheme.dimens.spacing14)
|
||||
.clip(
|
||||
RoundedCornerShape(
|
||||
topStart = TangemTheme.dimens.radius16,
|
||||
topEnd = TangemTheme.dimens.radius16,
|
||||
),
|
||||
)
|
||||
}
|
||||
lastItemIndex -> {
|
||||
this
|
||||
.clip(
|
||||
RoundedCornerShape(
|
||||
bottomStart = TangemTheme.dimens.radius16,
|
||||
bottomEnd = TangemTheme.dimens.radius16,
|
||||
),
|
||||
)
|
||||
}
|
||||
else -> this
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue