From b97ea5faf800f9d31b8a1f9468e029f33853efb6 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 9 Jun 2023 10:42:22 +0300 Subject: [PATCH] Updated on 2026-08-14 --- buildSrc/src/main/java/Dependency.kt | 0 features/wallet/impl/build.gradle.kts | 1 + .../presentation/common/WalletPreviewData.kt | 24 +++- .../common/component/NetworkGroupItem.kt | 31 ++++- .../common/component/TokenItem.kt | 34 +++-- .../organizetokens/OrganizeTokensScreen.kt | 119 +++++++++++++----- .../OrganizeTokensStateHolder.kt | 55 ++++++-- .../organizetokens/OrganizeTokensViewModel.kt | 81 ++++++++++++ .../utils/DraggableItemsOperations.kt | 55 ++++++++ gradle/dependencies.toml | 2 + 10 files changed, 342 insertions(+), 60 deletions(-) delete mode 100644 buildSrc/src/main/java/Dependency.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/utils/DraggableItemsOperations.kt diff --git a/buildSrc/src/main/java/Dependency.kt b/buildSrc/src/main/java/Dependency.kt deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/features/wallet/impl/build.gradle.kts b/features/wallet/impl/build.gradle.kts index c639f23ac8..b8bbf291ab 100644 --- a/features/wallet/impl/build.gradle.kts +++ b/features/wallet/impl/build.gradle.kts @@ -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) 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 2bfde4eaaa..5fcea3bc6c 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 @@ -105,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 = 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, @@ -127,9 +128,14 @@ internal object WalletPreviewData { ) } + val divider = DraggableItem.GroupPlaceholder(id = "divider_$networkNumber") + buildList { add(group) addAll(tokens) + if (index != lastNetworkIndex) { + add(divider) + } } } .toPersistentList() @@ -146,6 +152,12 @@ internal object WalletPreviewData { onSortByBalanceClick = {}, onGroupByNetworkClick = {}, ), + dragConfig = OrganizeTokensStateHolder.DragConfig( + onItemDragged = { _, _ -> }, + onDragStart = {}, + canDragItemOver = { _, _ -> false }, + onItemDragEnd = {}, + ), actions = OrganizeTokensStateHolder.ActionsConfig( onApplyClick = {}, onCancelClick = {}, 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 82e40737c5..ae4d06b0dc 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 @@ -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, + ) + } }, ) } 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 de8daa9cb8..e615c7dedb 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 @@ -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, + ) + } }, ) } 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 fb6be5831b..4f3cda259d 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 @@ -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 @@ -32,6 +29,10 @@ 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 +48,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 +59,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 +117,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 + } } } @@ -201,29 +251,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 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 0e0a07d2e6..693b9ce419 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 @@ -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 +internal sealed interface OrganizeTokensListState { + val items: PersistentList data class GroupedByNetwork( - override val items: ImmutableList, - ) : OrganizeTokensListState() + override val items: PersistentList, + ) : OrganizeTokensListState data class Ungrouped( - override val items: ImmutableList, - ) : OrganizeTokensListState() + override val items: PersistentList, + ) : OrganizeTokensListState @Suppress("UNCHECKED_CAST") - inline fun updateItems(update: (List) -> List): OrganizeTokensListState { + fun updateItems(update: (PersistentList) -> 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) + is Ungrouped -> this.copy(items = updatedItems as PersistentList) } } } +/** + * 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 } \ 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 cbdd25e47e..9334ce7f8b 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,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>? = 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() + .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() + val newItems = currentGroups + .flatMapIndexed { index, group -> + mutableListOf(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) + }, + ) + } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/utils/DraggableItemsOperations.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/utils/DraggableItemsOperations.kt new file mode 100644 index 0000000000..d8f3733dc3 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/utils/DraggableItemsOperations.kt @@ -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.findItemsToMove( + moveOverItemKey: Any?, + movedItemKey: Any?, +): Pair { + 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.moveItem(fromIndex: Int, toIndex: Int): PersistentList { + val fromItem = this[fromIndex] + return this + .removeAt(fromIndex) + .add(toIndex, fromItem) +} \ No newline at end of file diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 177713a612..b61ebffa8d 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -31,6 +31,7 @@ compose-constraint = "1.0.1" compose-navigation = "2.5.3" compose-accompanist = "0.30.1" compose-paging = "1.0.0-alpha18" +compose-reorderable = "0.9.6" # endregion Compose # region Other libraries @@ -147,6 +148,7 @@ compose-accompanist-appCompatTheme = { module = "com.google.accompanist:accompan compose-accompanist-systemUiController = { module = "com.google.accompanist:accompanist-systemuicontroller", version.ref = "compose-accompanist" } compose-accompanist-webView = { module = "com.google.accompanist:accompanist-webview", version.ref = "compose-accompanist" } compose-paging = { module = "androidx.paging:paging-compose", version.ref = "compose-paging" } +compose-reorderable = { module = "org.burnoutcrew.composereorderable:reorderable", version.ref = "compose-reorderable" } # endregion Compose # region Firebase