From 139b4ffdc6cc792d5968c8f8131efd98c7bfe02b Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 1 Jul 2026 19:28:21 +0200 Subject: [PATCH] Updated on 2026-08-14 --- .../core/ui/ds/row/header/TangemHeaderRow.kt | 15 +- .../core/ui/ds/row/internal/TangemRowTail.kt | 20 +-- .../core/ui/ds/row/token/TangemTokenRow.kt | 30 ++-- .../features/feed/ui/search/SearchContent.kt | 2 + .../page/tokenrow/TangemTokenRowStory.kt | 1 - features/wallet/impl/build.gradle.kts | 1 + .../ui/OrganizeTokensContent.kt | 132 ++++++++++-------- .../child/tokenActions/TokenActionContent.kt | 1 - .../multicurrency/MultiCurrencyContent.kt | 3 - 9 files changed, 106 insertions(+), 99 deletions(-) diff --git a/core/ui/src/main/java/com/tangem/core/ui/ds/row/header/TangemHeaderRow.kt b/core/ui/src/main/java/com/tangem/core/ui/ds/row/header/TangemHeaderRow.kt index 2778e5efd8..b256537196 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/ds/row/header/TangemHeaderRow.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/ds/row/header/TangemHeaderRow.kt @@ -27,19 +27,20 @@ import com.tangem.core.ui.extensions.* import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreviewRedesign import com.tangem.core.ui.test.TokenElementsTestTags -import org.burnoutcrew.reorderable.ReorderableLazyListState /** * UI model for header row component * - * @param headerRowUM UI model for the header row - * @param modifier Modifier for the composable + * @param headerRowUM UI model for the header row + * @param modifier Modifier for the composable + * @param dragHandleModifier Modifier applied to the drag handle in the row's tail (e.g. a reorderable + * drag-handle modifier). Defaults to [Modifier] for non-reorderable rows. */ @Composable fun TangemHeaderRow( headerRowUM: TangemHeaderRowUM, modifier: Modifier = Modifier, - reorderableState: ReorderableLazyListState? = null, + dragHandleModifier: Modifier = Modifier, isBalanceHidden: Boolean = false, ) { TangemHeaderRow( @@ -48,7 +49,7 @@ fun TangemHeaderRow( title = headerRowUM.title, subtitle = headerRowUM.subtitle, isBalanceHidden = isBalanceHidden, - reorderableState = reorderableState, + dragHandleModifier = dragHandleModifier, modifier = modifier, ) } @@ -129,7 +130,7 @@ fun TangemHeaderRow( subtitle: TextReference? = null, headTangemIconUM: TangemIconUM? = null, tailUM: TangemRowTailUM = TangemRowTailUM.Empty, - reorderableState: ReorderableLazyListState? = null, + dragHandleModifier: Modifier = Modifier, isEnabled: Boolean = false, onItemClick: (() -> Unit)? = null, ) { @@ -181,7 +182,7 @@ fun TangemHeaderRow( SpacerWMax() TangemRowTail( tangemRowTailUM = tailUM, - reorderableState = reorderableState, + dragHandleModifier = dragHandleModifier, ) } } diff --git a/core/ui/src/main/java/com/tangem/core/ui/ds/row/internal/TangemRowTail.kt b/core/ui/src/main/java/com/tangem/core/ui/ds/row/internal/TangemRowTail.kt index d5f60de779..55fba4a4e0 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/ds/row/internal/TangemRowTail.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/ds/row/internal/TangemRowTail.kt @@ -19,14 +19,12 @@ import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resolveAnnotatedReference import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.test.OrganizeTokensScreenTestTags -import org.burnoutcrew.reorderable.ReorderableLazyListState -import org.burnoutcrew.reorderable.detectReorder @Composable fun TangemRowTail( tangemRowTailUM: TangemRowTailUM, modifier: Modifier = Modifier, - reorderableState: ReorderableLazyListState? = null, + dragHandleModifier: Modifier = Modifier, ) { AnimatedContent( targetState = tangemRowTailUM, @@ -39,7 +37,7 @@ fun TangemRowTail( TangemRowTailUM.Empty -> Unit is TangemRowTailUM.Draggable -> DraggableImage( iconRes = animatedState.iconRes, - reorderableState = reorderableState, + dragHandleModifier = dragHandleModifier, modifier = innerModifier, ) is TangemRowTailUM.Text -> ContentText(text = animatedState.text, modifier = innerModifier) @@ -54,21 +52,11 @@ fun TangemRowTail( } @Composable -private fun DraggableImage( - @DrawableRes iconRes: Int, - reorderableState: ReorderableLazyListState?, - modifier: Modifier = Modifier, -) { +private fun DraggableImage(@DrawableRes iconRes: Int, dragHandleModifier: Modifier, modifier: Modifier = Modifier) { Box( modifier = modifier .size(size = TangemTheme.dimens2.x6) - .then( - other = if (reorderableState != null) { - Modifier.detectReorder(reorderableState) - } else { - Modifier - }, - ) + .then(dragHandleModifier) .testTag(OrganizeTokensScreenTestTags.DRAGGABLE_IMAGE), contentAlignment = Alignment.Center, ) { diff --git a/core/ui/src/main/java/com/tangem/core/ui/ds/row/token/TangemTokenRow.kt b/core/ui/src/main/java/com/tangem/core/ui/ds/row/token/TangemTokenRow.kt index 591d39ad86..30d00c7e4a 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/ds/row/token/TangemTokenRow.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/ds/row/token/TangemTokenRow.kt @@ -21,24 +21,24 @@ import com.tangem.core.ui.extensions.clickableSingle import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreviewRedesign import com.tangem.core.ui.test.TokenElementsTestTags -import org.burnoutcrew.reorderable.ReorderableLazyListState /** * Composable function that represents a Tangem token row in a list. * * [Token Row](https://www.figma.com/design/RU7AIgwHtGdMfy83T5UOoR/Core-Library?node-id=8207-17583&t=k8dyaykorsNocGVq-4) * - * @param tokenRowUM The user model containing the data for the token row. - * @param isBalanceHidden A boolean indicating whether the balance should be hidden. - * @param reorderableState The state of the reorderable lazy list, if applicable. - * @param modifier The modifier to be applied to the row. + * @param tokenRowUM The user model containing the data for the token row. + * @param isBalanceHidden A boolean indicating whether the balance should be hidden. + * @param dragHandleModifier Modifier applied to the drag handle in the row's tail (e.g. a reorderable + * drag-handle modifier). Defaults to [Modifier] for non-reorderable rows. + * @param modifier The modifier to be applied to the row. */ @Composable fun TangemTokenRow( tokenRowUM: TangemTokenRowUM, isBalanceHidden: Boolean, - reorderableState: ReorderableLazyListState?, modifier: Modifier = Modifier, + dragHandleModifier: Modifier = Modifier, ) { TangemRowContainer( content = { @@ -91,7 +91,7 @@ fun TangemTokenRow( TangemRowTail( tangemRowTailUM = tokenRowUM.tailUM, - reorderableState = reorderableState, + dragHandleModifier = dragHandleModifier, modifier = Modifier .layoutId(layoutId = TangemRowLayoutId.TAIL) .testTag(tag = TokenElementsTestTags.TOKEN_NON_FIAT_BLOCK), @@ -116,18 +116,19 @@ fun TangemTokenRow( * [Token Row](https://www.figma.com/design/RU7AIgwHtGdMfy83T5UOoR/Core-Library?node-id=8207-17583&t=k8dyaykorsNocGVq-4) * * @param tokenRowUM The user model containing the data for the token row. - * @param headComponent The composable function representing the head component. - * @param titleComponent The composable function representing the title component. - * @param isBalanceHidden A boolean indicating whether the balance should be hidden. - * @param reorderableState The state of the reorderable lazy list, if applicable. - * @param modifier The modifier to be applied to the row. + * @param headComponent The composable function representing the head component. + * @param titleComponent The composable function representing the title component. + * @param isBalanceHidden A boolean indicating whether the balance should be hidden. + * @param dragHandleModifier Modifier applied to the drag handle in the row's tail (e.g. a reorderable + * drag-handle modifier). Defaults to [Modifier] for non-reorderable rows. + * @param modifier The modifier to be applied to the row. */ @Composable fun TangemTokenRow( tokenRowUM: TangemTokenRowUM, isBalanceHidden: Boolean, - reorderableState: ReorderableLazyListState?, modifier: Modifier = Modifier, + dragHandleModifier: Modifier = Modifier, headComponent: @Composable (Modifier) -> Unit, titleComponent: @Composable (Modifier) -> Unit, ) { @@ -190,7 +191,7 @@ fun TangemTokenRow( TangemRowTail( tangemRowTailUM = tokenRowUM.tailUM, - reorderableState = reorderableState, + dragHandleModifier = dragHandleModifier, modifier = Modifier .layoutId(layoutId = TangemRowLayoutId.TAIL) .testTag(tag = TokenElementsTestTags.TOKEN_NON_FIAT_BLOCK), @@ -210,7 +211,6 @@ private fun TangemTokenRow_Preview( TangemTokenRow( tokenRowUM = tokenRowUM, isBalanceHidden = false, - reorderableState = null, modifier = Modifier.background(TangemTheme.colors2.surface.level1), ) } diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/search/SearchContent.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/search/SearchContent.kt index 2c6f115e59..5245ef407a 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/search/SearchContent.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/search/SearchContent.kt @@ -17,6 +17,7 @@ import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import com.tangem.common.ui.markets.MarketsListItem @@ -405,6 +406,7 @@ private fun ShowTokensUnder100kItem(onShowTokensClick: () -> Unit, modifier: Mod Text( text = stringResourceSafe(R.string.markets_search_see_tokens_under_100k), style = TangemTheme.typography2.subheadlineMedium14, + textAlign = TextAlign.Center, color = TangemTheme.colors2.text.neutral.secondary, ) TangemButton( diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/storybook/page/tokenrow/TangemTokenRowStory.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/storybook/page/tokenrow/TangemTokenRowStory.kt index 5fa78660ba..674dc5a2ba 100644 --- a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/storybook/page/tokenrow/TangemTokenRowStory.kt +++ b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/storybook/page/tokenrow/TangemTokenRowStory.kt @@ -61,7 +61,6 @@ internal fun TangemTokenRowStory(state: TangemTokenRowStory, modifier: Modifier TangemTokenRow( tokenRowUM = um, isBalanceHidden = state.isBalanceHidden, - reorderableState = null, modifier = Modifier.background(TangemTheme.colors2.surface.level1), ) HorizontalDivider( diff --git a/features/wallet/impl/build.gradle.kts b/features/wallet/impl/build.gradle.kts index 9e06c9a21b..ede24c9be3 100644 --- a/features/wallet/impl/build.gradle.kts +++ b/features/wallet/impl/build.gradle.kts @@ -31,6 +31,7 @@ dependencies { implementation(deps.compose.material3) implementation(deps.compose.paging) implementation(deps.compose.reorderable) + implementation(deps.compose.reorderableV2) implementation(deps.compose.shimmer) implementation(deps.compose.ui) implementation(deps.compose.ui.tooling) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/organizetokens/ui/OrganizeTokensContent.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/organizetokens/ui/OrganizeTokensContent.kt index 0c08233a68..b64ccf1e30 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/organizetokens/ui/OrganizeTokensContent.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/organizetokens/ui/OrganizeTokensContent.kt @@ -28,7 +28,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import com.tangem.core.ui.components.SpacerH +import com.tangem.core.ui.components.bottomsheets.LocalTangemBottomSheetContentBottomInset import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent @@ -48,7 +48,6 @@ import com.tangem.core.ui.ds.topbar.TangemTopBarActionContent import com.tangem.core.ui.ds.topbar.TangemTopBarActionUM import com.tangem.core.ui.ds.topbar.TangemTopBarType import com.tangem.core.ui.extensions.resourceReference -import com.tangem.core.ui.reordarable.ReorderableItem import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreviewRedesign import com.tangem.core.ui.test.OrganizeTokensScreenTestTags @@ -63,9 +62,9 @@ import com.tangem.feature.wallet.child.organizetokens.ui.preview.OrganizeTokensP import com.tangem.feature.wallet.impl.R import dev.chrisbanes.haze.rememberHazeState import org.burnoutcrew.reorderable.ItemPosition -import org.burnoutcrew.reorderable.ReorderableLazyListState -import org.burnoutcrew.reorderable.rememberReorderableLazyListState -import org.burnoutcrew.reorderable.reorderable +import sh.calvin.reorderable.ReorderableItem +import sh.calvin.reorderable.ReorderableLazyListState +import sh.calvin.reorderable.rememberReorderableLazyListState @Composable internal fun OrganizeTokensContent( @@ -134,26 +133,27 @@ private fun TokenList( val hapticFeedback = LocalHapticFeedback.current val tokenList = organizeTokensUM.tokenList + + var draggingItem by remember { mutableStateOf(null) } + Box( modifier = modifier.background(TangemTheme.colors2.surface.level2), ) { - val onDragEnd: (Int, Int) -> Unit = remember { - { _, _ -> - dragAndDropIntents.onItemDraggingEnd() - } - } - val reorderableListState = rememberReorderableLazyListState( - onMove = dragAndDropIntents::onItemDragged, - listState = tokensListState, - canDragOver = dragAndDropIntents::canDragItemOver, - onDragEnd = onDragEnd, - ) + val footerInset = LocalTangemBottomSheetContentBottomInset.current - val bottomBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getBottom(this).toDp() } + val reorderableState = rememberReorderableLazyListState( + lazyListState = tokensListState, + scrollThresholdPadding = PaddingValues(bottom = footerInset), + ) { from, to -> + dragAndDropIntents.onItemDragged( + ItemPosition(index = from.index, key = from.key), + ItemPosition(index = to.index, key = to.key), + ) + } val listContentPadding = PaddingValues( top = TangemTheme.dimens2.x1, - bottom = TangemTheme.dimens2.x1 + bottomBarHeight, + bottom = TangemTheme.dimens2.x1 + footerInset, start = TangemTheme.dimens2.x3, end = TangemTheme.dimens2.x3, ) @@ -161,60 +161,82 @@ private fun TokenList( LazyColumn( modifier = Modifier .align(Alignment.TopCenter) - .reorderable(reorderableListState) .testTag(OrganizeTokensScreenTestTags.TOKENS_LAZY_LIST), - state = reorderableListState.listState, + state = tokensListState, contentPadding = listContentPadding, ) { itemsIndexed( items = tokenList, key = { _, item -> item.id }, ) { index, item -> - - val onDragStart = remember(item) { - { - dragAndDropIntents.onItemDraggingStart(item) - hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) - } - } - DraggableItem( index = index, item = item, - reorderableState = reorderableListState, - onDragStart = onDragStart, + reorderableState = reorderableState, + isValidDropTarget = isValidDropTarget( + item = item, + dragging = draggingItem, + isGrouped = organizeTokensUM.isGrouped, + isAccountsMode = organizeTokensUM.isAccountsMode, + ), isBalanceHidden = organizeTokensUM.isBalanceHidden, + onDragStart = { + draggingItem = item + dragAndDropIntents.onItemDraggingStart(item) + hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) + }, + onDragStop = { + draggingItem = null + dragAndDropIntents.onItemDraggingEnd() + }, ) } - - item { - SpacerH(TangemTheme.dimens2.x20) - } } } } +private fun isValidDropTarget( + item: OrganizeRowItemUM, + dragging: OrganizeRowItemUM?, + isGrouped: Boolean, + isAccountsMode: Boolean, +): Boolean { + return when { + dragging == null -> true + item.id == dragging.id -> true + dragging is OrganizeRowItemUM.Network -> + item is OrganizeRowItemUM.Placeholder && item.accountId == dragging.accountId + dragging is OrganizeRowItemUM.Token -> when (item) { + is OrganizeRowItemUM.Token -> when { + isGrouped -> item.groupId == dragging.groupId + isAccountsMode -> item.accountId == dragging.accountId + else -> true + } + else -> false + } + else -> false + } +} + +@Suppress("LongParameterList") @Composable private fun LazyItemScope.DraggableItem( index: Int, item: OrganizeRowItemUM, reorderableState: ReorderableLazyListState, - onDragStart: () -> Unit, + isValidDropTarget: Boolean, isBalanceHidden: Boolean, + onDragStart: () -> Unit, + onDragStop: () -> Unit, ) { - var isDragging by remember { - mutableStateOf(value = false) - } - val itemModifier = Modifier.applyShapeAndShadow(item.roundingModeUM, item.isShowShadow) ReorderableItem( - reorderableState = reorderableState, - index = index, + state = reorderableState, key = item.id, - ) { isItemDragging -> - isDragging = isItemDragging - + // Only same-group/account items are eligible drop targets while dragging (see TokenList). + enabled = isValidDropTarget, + ) { _ -> val modifierWithBackground = itemModifier .background(color = TangemTheme.colors.background.primary) .semantics { lazyListItemPosition = index } @@ -222,7 +244,10 @@ private fun LazyItemScope.DraggableItem( when (item) { is OrganizeRowItemUM.Network -> TangemHeaderRow( modifier = modifierWithBackground.testTag(OrganizeTokensScreenTestTags.GROUP_TITLE_ITEM), - reorderableState = reorderableState, + dragHandleModifier = Modifier.draggableHandle( + onDragStarted = { onDragStart() }, + onDragStopped = { onDragStop() }, + ), headerRowUM = item.headerRowUM, ) is OrganizeRowItemUM.Portfolio -> TangemHeaderRow( @@ -233,21 +258,16 @@ private fun LazyItemScope.DraggableItem( is OrganizeRowItemUM.Token -> OrganizeTokenRow( modifier = modifierWithBackground.testTag(OrganizeTokensScreenTestTags.TOKEN_LIST_ITEM), tokenRowUM = item.tokenRowUM, - reorderableState = reorderableState, + dragHandleModifier = Modifier.draggableHandle( + onDragStarted = { onDragStart() }, + onDragStopped = { onDragStop() }, + ), isBalanceHidden = isBalanceHidden, ) // Should be presented in the list but remain invisible is OrganizeRowItemUM.Placeholder -> Box(modifier = Modifier.fillMaxWidth()) } } - - DisposableEffect(isDragging) { - onDispose { - if (isDragging) { - onDragStart() - } - } - } } @Composable @@ -310,7 +330,7 @@ private fun Modifier.applyShapeAndShadow(roundingMode: RoundingModeUM, showShado private fun OrganizeTokenRow( tokenRowUM: TangemTokenRowUM, isBalanceHidden: Boolean, - reorderableState: ReorderableLazyListState?, + dragHandleModifier: Modifier, modifier: Modifier = Modifier, ) { TangemRowContainer( @@ -345,7 +365,7 @@ private fun OrganizeTokenRow( TangemRowTail( tangemRowTailUM = tokenRowUM.tailUM, - reorderableState = reorderableState, + dragHandleModifier = dragHandleModifier, modifier = Modifier .layoutId(layoutId = TangemRowLayoutId.TAIL) .testTag(tag = TokenElementsTestTags.TOKEN_NON_FIAT_BLOCK), diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/tokenActions/TokenActionContent.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/tokenActions/TokenActionContent.kt index 85efce67c6..f0cd95fb56 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/tokenActions/TokenActionContent.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/tokenActions/TokenActionContent.kt @@ -59,7 +59,6 @@ internal fun TokenActionContent( TangemTokenRow( tokenRowUM = tokenRowUM, isBalanceHidden = isBalanceHidden, - reorderableState = null, modifier = Modifier .padding(horizontal = TangemTheme.dimens2.x3) .clip(RoundedCornerShape(18.dp)) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyContent.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyContent.kt index f0fa031f17..c2a634a11c 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyContent.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyContent.kt @@ -167,7 +167,6 @@ private fun LazyListScope.tokenItem( is TangemTokenRowUM -> TangemTokenRow( tokenRowUM = tokenRowUM, isBalanceHidden = isBalanceHidden, - reorderableState = null, modifier = itemModifier .onGloballyPositioned { position = it.positionOnScreen() } .combinedClickable( @@ -238,7 +237,6 @@ private fun LazyListScope.portfolioItem( is TangemTokenRowUM -> TangemTokenRow( tokenRowUM = tokenRowUM, isBalanceHidden = isBalanceHidden, - reorderableState = null, modifier = itemModifier .onGloballyPositioned { position = it.positionInWindow() @@ -450,7 +448,6 @@ internal fun PortfolioRowItem( headComponent = composables.icon, titleComponent = composables.title, isBalanceHidden = isBalanceHidden, - reorderableState = null, ) } }