feat(v1.8.0): IMPL_023b Drag & Drop Flicker Fix - Straddle-Target-Center Detection
- Swap detection: Changed from midpoint check to straddle-target-center detection * Old: Checks if midpoint of dragged item lies within target * New: Checks if dragged item spans the midpoint of target * Prevents oscillation when items have different sizes - Adjacency filter: Only adjacent items (index ± 1) as swap candidates * Prevents item jumps during fast drag * Reduces recalculation of visibleItemsInfo - Race-condition fix for scroll + move * draggingItemIndex update moved after onMove() in coroutine block * Prevents inconsistent state between index update and layout change Affected files: - DragDropListState.kt: onDrag() method (~10 lines changed)
This commit is contained in:
@@ -9,6 +9,7 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableFloatStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
@@ -19,9 +20,11 @@ import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* FOSS Drag & Drop State für LazyList
|
||||
*
|
||||
*
|
||||
* Native Compose-Implementierung ohne externe Dependencies
|
||||
* v1.5.0: NoteEditor Redesign
|
||||
* v1.8.0: IMPL_023 - Drag & Drop Fix (pointerInput key + Handle-only drag)
|
||||
* v1.8.0: IMPL_023b - Flicker-Fix (Straddle-Target-Center-Erkennung statt Mittelpunkt)
|
||||
*/
|
||||
class DragDropListState(
|
||||
private val state: LazyListState,
|
||||
@@ -64,11 +67,17 @@ class DragDropListState(
|
||||
val startOffset = draggingItem.offset + draggingItemOffset
|
||||
val endOffset = startOffset + draggingItem.size
|
||||
|
||||
val middleOffset = startOffset + (endOffset - startOffset) / 2f
|
||||
|
||||
val targetItem = state.layoutInfo.visibleItemsInfo.find { item ->
|
||||
middleOffset.toInt() in item.offset..item.offsetEnd &&
|
||||
draggingItem.index != item.index
|
||||
// 🆕 v1.8.0: IMPL_023b — Straddle-Target-Center + Adjazenz-Filter
|
||||
// Statt den Mittelpunkt des gezogenen Items zu prüfen ("liegt mein Zentrum im Target?"),
|
||||
// wird geprüft ob das gezogene Item den MITTELPUNKT des Targets überspannt.
|
||||
// Dies verhindert Oszillation bei Items unterschiedlicher Größe.
|
||||
// Zusätzlich: Nur adjazente Items (Index ± 1) als Swap-Kandidaten.
|
||||
val targetItem = state.layoutInfo.visibleItemsInfo.firstOrNull { item ->
|
||||
(item.index == draggingItem.index - 1 || item.index == draggingItem.index + 1) &&
|
||||
run {
|
||||
val targetCenter = item.offset + item.size / 2
|
||||
startOffset < targetCenter && endOffset > targetCenter
|
||||
}
|
||||
}
|
||||
|
||||
if (targetItem != null) {
|
||||
@@ -84,12 +93,13 @@ class DragDropListState(
|
||||
scope.launch {
|
||||
state.scrollToItem(scrollToIndex, state.firstVisibleItemScrollOffset)
|
||||
onMove(draggingItem.index, targetItem.index)
|
||||
// 🆕 v1.8.0: IMPL_023b — Index-Update NACH dem Move (verhindert Race-Condition)
|
||||
draggingItemIndex = targetItem.index
|
||||
}
|
||||
} else {
|
||||
onMove(draggingItem.index, targetItem.index)
|
||||
draggingItemIndex = targetItem.index
|
||||
}
|
||||
|
||||
draggingItemIndex = targetItem.index
|
||||
} else {
|
||||
val overscroll = when {
|
||||
draggingItemDraggedDelta > 0 ->
|
||||
@@ -130,14 +140,16 @@ fun rememberDragDropListState(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Modifier.dragContainer(
|
||||
dragDropState: DragDropListState,
|
||||
itemIndex: Int
|
||||
): Modifier {
|
||||
return this.pointerInput(dragDropState) {
|
||||
val currentIndex = rememberUpdatedState(itemIndex) // 🆕 v1.8.0: rememberUpdatedState statt Key
|
||||
return this.pointerInput(dragDropState) { // Nur dragDropState als Key - verhindert Gesture-Restart
|
||||
detectDragGesturesAfterLongPress(
|
||||
onDragStart = { offset ->
|
||||
dragDropState.onDragStart(offset, itemIndex)
|
||||
dragDropState.onDragStart(offset, currentIndex.value) // Aktuellen Wert lesen
|
||||
},
|
||||
onDragEnd = {
|
||||
dragDropState.onDragInterrupted()
|
||||
|
||||
@@ -50,6 +50,7 @@ import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.zIndex
|
||||
import dev.dettmer.simplenotes.R
|
||||
import dev.dettmer.simplenotes.models.NoteType
|
||||
import dev.dettmer.simplenotes.ui.editor.components.ChecklistItemRow
|
||||
@@ -351,14 +352,17 @@ private fun ChecklistEditor(
|
||||
onDelete = { onDelete(item.id) },
|
||||
onAddNewItem = { onAddNewItemAfter(item.id) },
|
||||
requestFocus = shouldFocus,
|
||||
isDragging = isDragging, // 🆕 v1.8.0: IMPL_023 - Drag state übergeben
|
||||
isAnyItemDragging = dragDropState.draggingItemIndex != null, // 🆕 v1.8.0: IMPL_023 - Gradient während Drag ausblenden
|
||||
dragModifier = Modifier.dragContainer(dragDropState, index), // 🆕 v1.8.0: IMPL_023 - Drag nur auf Handle
|
||||
modifier = Modifier
|
||||
.dragContainer(dragDropState, index)
|
||||
.offset {
|
||||
IntOffset(
|
||||
0,
|
||||
if (isDragging) dragDropState.draggingItemOffset.roundToInt() else 0
|
||||
)
|
||||
}
|
||||
.zIndex(if (isDragging) 10f else 0f) // 🆕 v1.8.0: IMPL_023 - Gedraggtes Item liegt über anderen
|
||||
.shadow(elevation, shape = RoundedCornerShape(8.dp))
|
||||
.background(
|
||||
color = MaterialTheme.colorScheme.surface,
|
||||
|
||||
@@ -4,12 +4,15 @@ import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.text.BasicTextField
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material.icons.filled.DragHandle
|
||||
@@ -32,6 +35,7 @@ import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.focus.onFocusChanged
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.TextRange
|
||||
@@ -39,6 +43,7 @@ import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.text.style.TextDecoration
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import dev.dettmer.simplenotes.R
|
||||
import dev.dettmer.simplenotes.ui.editor.ChecklistItemState
|
||||
@@ -48,6 +53,7 @@ import dev.dettmer.simplenotes.ui.editor.ChecklistItemState
|
||||
*
|
||||
* v1.5.0: Jetpack Compose NoteEditor Redesign
|
||||
* v1.8.0: Long text UX improvements (gradient fade, auto-expand on focus)
|
||||
* v1.8.0: IMPL_023 - Enlarged drag handle (48dp touch target) + drag modifier
|
||||
*/
|
||||
@Composable
|
||||
fun ChecklistItemRow(
|
||||
@@ -57,12 +63,16 @@ fun ChecklistItemRow(
|
||||
onDelete: () -> Unit,
|
||||
onAddNewItem: () -> Unit,
|
||||
requestFocus: Boolean = false,
|
||||
isDragging: Boolean = false, // 🆕 v1.8.0: IMPL_023 - Drag state
|
||||
isAnyItemDragging: Boolean = false, // 🆕 v1.8.0: IMPL_023 - Hide gradient during any drag
|
||||
dragModifier: Modifier = Modifier, // 🆕 v1.8.0: IMPL_023 - Drag modifier for handle
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
val keyboardController = LocalSoftwareKeyboardController.current
|
||||
val density = LocalDensity.current
|
||||
var textFieldValue by remember(item.id) {
|
||||
mutableStateOf(TextFieldValue(text = item.text, selection = TextRange(item.text.length)))
|
||||
mutableStateOf(TextFieldValue(text = item.text, selection = TextRange(0)))
|
||||
}
|
||||
|
||||
// 🆕 v1.8.0: Focus-State tracken für Expand/Collapse
|
||||
@@ -71,9 +81,20 @@ fun ChecklistItemRow(
|
||||
// 🆕 v1.8.0: Overflow erkennen (Text länger als maxLines)
|
||||
var hasOverflow by remember { mutableStateOf(false) }
|
||||
|
||||
// 🆕 v1.8.0: Dynamische maxLines basierend auf Focus
|
||||
val currentMaxLines = if (isFocused) Int.MAX_VALUE else COLLAPSED_MAX_LINES
|
||||
|
||||
// 🆕 v1.8.0: Höhe für collapsed-Ansicht (aus TextLayout berechnet)
|
||||
var collapsedHeightDp by remember { mutableStateOf<Dp?>(null) }
|
||||
|
||||
// 🆕 v1.8.0: ScrollState für dynamischen Gradient
|
||||
val scrollState = rememberScrollState()
|
||||
|
||||
// 🆕 v1.8.0: Scroll-basierter Ansatz aktiv wenn Höhe berechnet wurde
|
||||
val useScrollClipping = hasOverflow && collapsedHeightDp != null
|
||||
|
||||
// 🆕 v1.8.0: Dynamische Gradient-Sichtbarkeit basierend auf Scroll-Position
|
||||
val showGradient = useScrollClipping && !isFocused && !isAnyItemDragging
|
||||
val showTopGradient = showGradient && scrollState.value > 0
|
||||
val showBottomGradient = showGradient && scrollState.value < scrollState.maxValue
|
||||
|
||||
// v1.5.0: Auto-focus AND show keyboard when requestFocus is true (new items)
|
||||
LaunchedEffect(requestFocus) {
|
||||
if (requestFocus) {
|
||||
@@ -81,121 +102,163 @@ fun ChecklistItemRow(
|
||||
keyboardController?.show()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 🆕 v1.8.0: Cursor ans Ende setzen wenn fokussiert (für Bearbeitung)
|
||||
LaunchedEffect(isFocused) {
|
||||
if (isFocused && textFieldValue.selection.start == 0) {
|
||||
textFieldValue = textFieldValue.copy(
|
||||
selection = TextRange(textFieldValue.text.length)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Update text field when external state changes
|
||||
LaunchedEffect(item.text) {
|
||||
if (textFieldValue.text != item.text) {
|
||||
textFieldValue = TextFieldValue(
|
||||
text = item.text,
|
||||
selection = TextRange(item.text.length)
|
||||
selection = if (isFocused) TextRange(item.text.length) else TextRange(0)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val alpha = if (item.isChecked) 0.6f else 1.0f
|
||||
val textDecoration = if (item.isChecked) TextDecoration.LineThrough else TextDecoration.None
|
||||
|
||||
|
||||
@Suppress("MagicNumber") // UI padding values are self-explanatory
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 8.dp, vertical = 4.dp),
|
||||
verticalAlignment = Alignment.Top // 🆕 v1.8.0: Top statt CenterVertically für lange Texte
|
||||
.padding(end = 8.dp, top = 4.dp, bottom = 4.dp), // 🆕 v1.8.0: IMPL_023 - links kein Padding (Handle hat eigene Fläche)
|
||||
verticalAlignment = if (hasOverflow) Alignment.Top else Alignment.CenterVertically // 🆕 v1.8.0: Dynamisch
|
||||
) {
|
||||
// Drag Handle
|
||||
Icon(
|
||||
imageVector = Icons.Default.DragHandle,
|
||||
contentDescription = stringResource(R.string.drag_to_reorder),
|
||||
modifier = Modifier
|
||||
.size(24.dp)
|
||||
.padding(top = 12.dp) // 🆕 v1.8.0: Visuell am oberen Rand ausrichten
|
||||
.alpha(0.5f),
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
|
||||
// 🆕 v1.8.0: IMPL_023 - Vergrößerter Drag Handle (48dp Touch-Target)
|
||||
Box(
|
||||
modifier = dragModifier
|
||||
.size(48.dp) // Material Design minimum touch target
|
||||
.alpha(if (isDragging) 1.0f else 0.6f), // Visual feedback beim Drag
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.DragHandle,
|
||||
contentDescription = stringResource(R.string.drag_to_reorder),
|
||||
modifier = Modifier.size(28.dp), // Icon größer als vorher (24dp → 28dp)
|
||||
tint = if (isDragging) {
|
||||
MaterialTheme.colorScheme.primary // Primary color während Drag
|
||||
} else {
|
||||
MaterialTheme.colorScheme.onSurfaceVariant
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// Checkbox
|
||||
Checkbox(
|
||||
checked = item.isChecked,
|
||||
onCheckedChange = onCheckedChange,
|
||||
modifier = Modifier.alpha(alpha)
|
||||
)
|
||||
|
||||
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
|
||||
// 🆕 v1.8.0: Text Input mit Overflow-Gradient
|
||||
// 🆕 v1.8.0: Text Input mit dynamischem Overflow-Gradient
|
||||
Box(modifier = Modifier.weight(1f)) {
|
||||
BasicTextField(
|
||||
value = textFieldValue,
|
||||
onValueChange = { newValue ->
|
||||
// Check for newline (Enter key)
|
||||
if (newValue.text.contains("\n")) {
|
||||
val cleanText = newValue.text.replace("\n", "")
|
||||
textFieldValue = TextFieldValue(
|
||||
text = cleanText,
|
||||
selection = TextRange(cleanText.length)
|
||||
)
|
||||
onTextChange(cleanText)
|
||||
onAddNewItem()
|
||||
} else {
|
||||
textFieldValue = newValue
|
||||
onTextChange(newValue.text)
|
||||
}
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.focusRequester(focusRequester)
|
||||
.onFocusChanged { focusState ->
|
||||
isFocused = focusState.isFocused
|
||||
}
|
||||
.alpha(alpha),
|
||||
textStyle = LocalTextStyle.current.copy(
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
textDecoration = textDecoration
|
||||
),
|
||||
keyboardOptions = KeyboardOptions(
|
||||
imeAction = ImeAction.Next
|
||||
),
|
||||
keyboardActions = KeyboardActions(
|
||||
onNext = { onAddNewItem() }
|
||||
),
|
||||
singleLine = false,
|
||||
maxLines = currentMaxLines, // 🆕 v1.8.0: Dynamisch
|
||||
cursorBrush = SolidColor(MaterialTheme.colorScheme.primary),
|
||||
onTextLayout = { textLayoutResult ->
|
||||
// 🆕 v1.8.0: Overflow erkennen
|
||||
hasOverflow = textLayoutResult.hasVisualOverflow ||
|
||||
textLayoutResult.lineCount > COLLAPSED_MAX_LINES
|
||||
},
|
||||
decorationBox = { innerTextField ->
|
||||
Box {
|
||||
if (textFieldValue.text.isEmpty()) {
|
||||
Text(
|
||||
text = stringResource(R.string.item_placeholder),
|
||||
style = LocalTextStyle.current.copy(
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.5f)
|
||||
)
|
||||
)
|
||||
}
|
||||
innerTextField()
|
||||
}
|
||||
// Scrollbarer Wrapper: begrenzt Höhe auf ~5 Zeilen wenn collapsed
|
||||
Box(
|
||||
modifier = if (!isFocused && useScrollClipping) {
|
||||
Modifier
|
||||
.heightIn(max = collapsedHeightDp!!)
|
||||
.verticalScroll(scrollState)
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
)
|
||||
) {
|
||||
BasicTextField(
|
||||
value = textFieldValue,
|
||||
onValueChange = { newValue ->
|
||||
// Check for newline (Enter key)
|
||||
if (newValue.text.contains("\n")) {
|
||||
val cleanText = newValue.text.replace("\n", "")
|
||||
textFieldValue = TextFieldValue(
|
||||
text = cleanText,
|
||||
selection = TextRange(cleanText.length)
|
||||
)
|
||||
onTextChange(cleanText)
|
||||
onAddNewItem()
|
||||
} else {
|
||||
textFieldValue = newValue
|
||||
onTextChange(newValue.text)
|
||||
}
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.focusRequester(focusRequester)
|
||||
.onFocusChanged { focusState ->
|
||||
isFocused = focusState.isFocused
|
||||
}
|
||||
.alpha(alpha),
|
||||
textStyle = LocalTextStyle.current.copy(
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
textDecoration = textDecoration
|
||||
),
|
||||
keyboardOptions = KeyboardOptions(
|
||||
imeAction = ImeAction.Next
|
||||
),
|
||||
keyboardActions = KeyboardActions(
|
||||
onNext = { onAddNewItem() }
|
||||
),
|
||||
singleLine = false,
|
||||
// maxLines nur als Fallback bis collapsedHeight berechnet ist
|
||||
maxLines = if (isFocused || useScrollClipping) Int.MAX_VALUE else COLLAPSED_MAX_LINES,
|
||||
cursorBrush = SolidColor(MaterialTheme.colorScheme.primary),
|
||||
onTextLayout = { textLayoutResult ->
|
||||
// 🆕 v1.8.0: Overflow erkennen - ABER NUR wenn kein Drag aktiv ist
|
||||
if (!isAnyItemDragging) {
|
||||
val overflow = textLayoutResult.lineCount > COLLAPSED_MAX_LINES
|
||||
hasOverflow = overflow
|
||||
// Höhe der ersten 5 Zeilen berechnen (einmalig)
|
||||
if (overflow && collapsedHeightDp == null) {
|
||||
collapsedHeightDp = with(density) {
|
||||
textLayoutResult.getLineBottom(COLLAPSED_MAX_LINES - 1).toDp()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
decorationBox = { innerTextField ->
|
||||
Box {
|
||||
if (textFieldValue.text.isEmpty()) {
|
||||
Text(
|
||||
text = stringResource(R.string.item_placeholder),
|
||||
style = LocalTextStyle.current.copy(
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.5f)
|
||||
)
|
||||
)
|
||||
}
|
||||
innerTextField()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// 🆕 v1.8.0: Gradient-Fade Overlay wenn Text überläuft
|
||||
// Zeige nur Gradient oben, da man am unteren Rand startet und nach oben scrollt
|
||||
if (hasOverflow && !isFocused) {
|
||||
// Gradient oben (zeigt: es gibt Text oberhalb der sichtbaren Zeilen)
|
||||
// 🆕 v1.8.0: Dynamischer Gradient basierend auf Scroll-Position
|
||||
// Oben: sichtbar wenn nach unten gescrollt (Text oberhalb versteckt)
|
||||
if (showTopGradient) {
|
||||
OverflowGradient(
|
||||
modifier = Modifier.align(Alignment.TopCenter),
|
||||
isTopGradient = true
|
||||
)
|
||||
}
|
||||
|
||||
// Unten: sichtbar wenn noch Text unterhalb vorhanden
|
||||
if (showBottomGradient) {
|
||||
OverflowGradient(
|
||||
modifier = Modifier.align(Alignment.BottomCenter),
|
||||
isTopGradient = false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
|
||||
|
||||
// Delete Button
|
||||
IconButton(
|
||||
onClick = onDelete,
|
||||
@@ -232,7 +295,9 @@ private fun ChecklistItemRowShortTextPreview() {
|
||||
onTextChange = {},
|
||||
onCheckedChange = {},
|
||||
onDelete = {},
|
||||
onAddNewItem = {}
|
||||
onAddNewItem = {},
|
||||
isDragging = false,
|
||||
dragModifier = Modifier
|
||||
)
|
||||
}
|
||||
|
||||
@@ -253,7 +318,9 @@ private fun ChecklistItemRowLongTextPreview() {
|
||||
onTextChange = {},
|
||||
onCheckedChange = {},
|
||||
onDelete = {},
|
||||
onAddNewItem = {}
|
||||
onAddNewItem = {},
|
||||
isDragging = false,
|
||||
dragModifier = Modifier
|
||||
)
|
||||
}
|
||||
|
||||
@@ -269,6 +336,27 @@ private fun ChecklistItemRowCheckedPreview() {
|
||||
onTextChange = {},
|
||||
onCheckedChange = {},
|
||||
onDelete = {},
|
||||
onAddNewItem = {}
|
||||
onAddNewItem = {},
|
||||
isDragging = false,
|
||||
dragModifier = Modifier
|
||||
)
|
||||
}
|
||||
|
||||
// 🆕 v1.8.0: IMPL_023 - Preview for dragging state
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun ChecklistItemRowDraggingPreview() {
|
||||
ChecklistItemRow(
|
||||
item = ChecklistItemState(
|
||||
id = "preview-4",
|
||||
text = "Wird gerade verschoben - Handle ist highlighted",
|
||||
isChecked = false
|
||||
),
|
||||
onTextChange = {},
|
||||
onCheckedChange = {},
|
||||
onDelete = {},
|
||||
onAddNewItem = {},
|
||||
isDragging = true,
|
||||
dragModifier = Modifier
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user