Calculate drag handle bounds using the container width instead of the screen width.

This commit is contained in:
jeffrey-signal
2026-01-07 10:55:09 -05:00
parent 6459ef5b66
commit e3b569ca5b
5 changed files with 49 additions and 48 deletions

View File

@@ -31,8 +31,6 @@ import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.painterResource
@@ -60,7 +58,6 @@ import org.signal.core.util.toInt
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.compose.ComposeFragment
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.util.ViewUtil
import org.thoughtcrime.securesms.util.navigation.safeNavigate
/**
@@ -127,8 +124,6 @@ fun FoldersScreen(
onDeleteDismissed: () -> Unit = {},
onDragAndDropEvent: (DragAndDropEvent) -> Unit = {}
) {
val screenWidth = LocalConfiguration.current.screenWidthDp.dp
val isRtl = ViewUtil.isRtl(LocalContext.current)
val listState = rememberLazyListState()
val dragDropState = rememberDragDropState(listState, includeHeader = true, includeFooter = true, onEvent = onDragAndDropEvent)
@@ -153,8 +148,7 @@ fun FoldersScreen(
LazyColumn(
modifier = Modifier.dragContainer(
dragDropState = dragDropState,
leftDpOffset = if (isRtl) 0.dp else screenWidth - 48.dp,
rightDpOffset = if (isRtl) 48.dp else screenWidth
dragHandleWidth = 56.dp
),
state = listState
) {
@@ -220,6 +214,7 @@ fun FoldersScreen(
onAdd = { onAdd(chatFolder) }
)
}
ChatFolderRecord.FolderType.INDIVIDUAL -> {
val title: String = stringResource(R.string.ChatFoldersFragment__one_on_one_chats)
FolderRow(
@@ -229,6 +224,7 @@ fun FoldersScreen(
onAdd = { onAdd(chatFolder) }
)
}
ChatFolderRecord.FolderType.GROUP -> {
val title: String = stringResource(R.string.ChatFoldersFragment__groups)
FolderRow(
@@ -238,9 +234,11 @@ fun FoldersScreen(
onAdd = { onAdd(chatFolder) }
)
}
ChatFolderRecord.FolderType.ALL -> {
error("All chats should not be suggested")
}
ChatFolderRecord.FolderType.CUSTOM -> {
error("Custom folders should not be suggested")
}

View File

@@ -40,8 +40,6 @@ import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
@@ -154,8 +152,6 @@ private fun CreatePollScreen(
var focusedOption by remember { mutableStateOf(-1) }
// Drag and drop
val screenWidth = LocalConfiguration.current.screenWidthDp.dp
val isRtl = ViewUtil.isRtl(LocalContext.current)
val listState = rememberLazyListState()
val dragDropState = rememberDragDropState(listState, includeHeader = true, includeFooter = true, onEvent = { event ->
when (event) {
@@ -164,6 +160,7 @@ private fun CreatePollScreen(
options[event.fromIndex] = options[event.toIndex]
options[event.toIndex] = oldIndex
}
is DragAndDropEvent.OnItemDrop, is DragAndDropEvent.OnDragCancel -> Unit
}
})
@@ -208,8 +205,7 @@ private fun CreatePollScreen(
.imePadding()
.dragContainer(
dragDropState = dragDropState,
leftDpOffset = if (isRtl) 0.dp else screenWidth - 56.dp,
rightDpOffset = if (isRtl) 56.dp else screenWidth
dragHandleWidth = 56.dp
),
state = listState
) {

View File

@@ -49,17 +49,14 @@ import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource
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 androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.collectAsStateWithLifecycle
@@ -484,8 +481,6 @@ private fun InstalledStickersContent(
val listState = rememberLazyListState()
val dragDropState = rememberDragDropState(lazyListState = listState, includeHeader = true, includeFooter = false, onEvent = callbacks::onDragAndDropEvent)
val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl
val screenWidth = LocalConfiguration.current.screenWidthDp.dp
val density = LocalDensity.current
val haptics = LocalHapticFeedback.current
@@ -503,8 +498,7 @@ private fun InstalledStickersContent(
.fillMaxHeight()
.dragContainer(
dragDropState = dragDropState,
leftDpOffset = if (isRtl) 0.dp else screenWidth - 56.dp,
rightDpOffset = if (isRtl) 56.dp else screenWidth
dragHandleWidth = 56.dp
)
) {
item(key = "installed_section_header") {

View File

@@ -23,7 +23,9 @@ import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.PointerInputChange
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.zIndex
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.Channel
@@ -161,14 +163,11 @@ internal constructor(
}
draggingItemIndex = targetItem.index
} else {
val overscroll =
when {
draggingItemDraggedDelta > 0 ->
(endOffset - state.layoutInfo.viewportEndOffset).coerceAtLeast(0f)
draggingItemDraggedDelta < 0 ->
(startOffset - state.layoutInfo.viewportStartOffset).coerceAtMost(0f)
else -> 0f
}
val overscroll = when {
draggingItemDraggedDelta > 0 -> (endOffset - state.layoutInfo.viewportEndOffset).coerceAtLeast(0f)
draggingItemDraggedDelta < 0 -> (startOffset - state.layoutInfo.viewportStartOffset).coerceAtMost(0f)
else -> 0f
}
if (overscroll != 0f) {
scrollChannel.trySend(overscroll)
}
@@ -198,21 +197,34 @@ sealed interface DragAndDropEvent {
data object OnDragCancel : DragAndDropEvent
}
/**
* Enables drag-to-reorder functionality within a container.
*
* @param dragDropState The state managing the drag operation.
* @param dragHandleWidth Width of the draggable area (positioned at the end of the container).
*/
@Composable
fun Modifier.dragContainer(
dragDropState: DragDropState,
leftDpOffset: Dp,
rightDpOffset: Dp
dragHandleWidth: Dp
): Modifier {
return pointerInput(dragDropState) {
val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl
return pointerInput(dragDropState, dragHandleWidth, isRtl) {
val containerWidthPx = size.width.toFloat()
val handleWidthPx = dragHandleWidth.toPx()
val dragHandleXRange = if (isRtl) {
0f..handleWidthPx
} else {
(containerWidthPx - handleWidthPx)..containerWidthPx
}
detectDragGestures(
onDrag = { change, offset ->
dragDropState.onDrag(offset = offset, change = change)
},
dragHandleXRange = dragHandleXRange,
onDrag = { change, offset -> dragDropState.onDrag(offset = offset, change = change) },
onDragStart = { offset -> dragDropState.onDragStart(offset) },
onDragEnd = { dragDropState.onDragEnd() },
onDragCancel = { dragDropState.onDragCancel() },
leftDpOffset = leftDpOffset,
rightDpOffset = rightDpOffset
onDragCancel = { dragDropState.onDragCancel() }
)
}
}
@@ -227,11 +239,13 @@ fun LazyItemScope.DraggableItem(
val dragging = index == dragDropState.draggingItemIndex
val draggingModifier =
if (dragging) {
Modifier.zIndex(1f).graphicsLayer { translationY = dragDropState.draggingItemOffset }
Modifier
.zIndex(1f)
.graphicsLayer { translationY = dragDropState.draggingItemOffset }
} else if (index == dragDropState.previousIndexOfDraggedItem) {
Modifier.zIndex(1f).graphicsLayer {
translationY = dragDropState.previousItemOffset.value
}
Modifier
.zIndex(1f)
.graphicsLayer { translationY = dragDropState.previousItemOffset.value }
} else {
Modifier.animateItem(fadeInSpec = null, fadeOutSpec = null)
}

View File

@@ -15,8 +15,6 @@ import androidx.compose.ui.input.pointer.changedToUp
import androidx.compose.ui.input.pointer.changedToUpIgnoreConsumed
import androidx.compose.ui.input.pointer.isOutOfBounds
import androidx.compose.ui.input.pointer.positionChange
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastAll
import androidx.compose.ui.util.fastAny
import androidx.compose.ui.util.fastFirstOrNull
@@ -25,21 +23,22 @@ import kotlinx.coroutines.CancellationException
/**
* Modified version of detectDragGesturesAfterLongPress from [androidx.compose.foundation.gestures.DragGestureDetector]
* that allows you to optionally offset the starting and ending position of the draggable area
* that initiates drags when the touch starts within a specified x coordinate range.
*
* @param dragHandleXRange The x coordinate range (in pixels) where drags can be initiated.
*/
suspend fun PointerInputScope.detectDragGestures(
dragHandleXRange: ClosedFloatingPointRange<Float>,
onDragStart: (Offset) -> Unit = { },
onDragEnd: () -> Unit = { },
onDragCancel: () -> Unit = { },
onDrag: (change: PointerInputChange, dragAmount: Offset) -> Unit,
leftDpOffset: Dp = 0.dp,
rightDpOffset: Dp
onDrag: (change: PointerInputChange, dragAmount: Offset) -> Unit
) {
awaitEachGesture {
try {
val down = awaitFirstDown(requireUnconsumed = false)
val drag = awaitLongPressOrCancellation(down.id)
if (drag != null && (drag.position.x > leftDpOffset.toPx()) && (drag.position.x < rightDpOffset.toPx())) {
if (drag != null && down.position.x in dragHandleXRange) {
onDragStart.invoke(drag.position)
if (