fix(editor): IMPL_13 - Fix gradient regression & drag-flicker
Bug A1: Gradient never showed because maxLines capped lineCount, making lineCount > maxLines impossible. Fixed by always setting maxLines = Int.MAX_VALUE and using hasVisualOverflow + heightIn(max) for clipped overflow detection. Bug A2: derivedStateOf captured stale val from Detekt refactor (commit1da1a63). Replaced with direct computation. Bug B1: animateItem() on dragged item conflicted with manual offset (from IMPL_017 commit900dad7). Fixed with conditional Modifier: if (!isDragging) Modifier.animateItem() else Modifier. Bug B2: Item size could change during drag. Added size snapshot in DragDropListState.onDragStart for stable endOffset calculation. Files changed: - ChecklistItemRow.kt: hasVisualOverflow, direct lineCount check, maxLines=Int.MAX_VALUE always, heightIn(max=collapsedHeightDp) - NoteEditorScreen.kt: conditional animateItem on isDragging - DragDropListState.kt: draggingItemSize snapshot for stable drag
This commit is contained in:
@@ -36,6 +36,9 @@ class DragDropListState(
|
||||
|
||||
private var draggingItemDraggedDelta by mutableFloatStateOf(0f)
|
||||
private var draggingItemInitialOffset by mutableFloatStateOf(0f)
|
||||
// 🆕 v1.8.1: Item-Größe beim Drag-Start fixieren
|
||||
// Verhindert dass Höhenänderungen die Swap-Erkennung destabilisieren
|
||||
private var draggingItemSize by mutableStateOf(0)
|
||||
private var overscrollJob by mutableStateOf<Job?>(null)
|
||||
|
||||
val draggingItemOffset: Float
|
||||
@@ -49,7 +52,9 @@ class DragDropListState(
|
||||
|
||||
fun onDragStart(offset: Offset, itemIndex: Int) {
|
||||
draggingItemIndex = itemIndex
|
||||
draggingItemInitialOffset = draggingItemLayoutInfo?.offset?.toFloat() ?: 0f
|
||||
val info = draggingItemLayoutInfo
|
||||
draggingItemInitialOffset = info?.offset?.toFloat() ?: 0f
|
||||
draggingItemSize = info?.size ?: 0
|
||||
draggingItemDraggedDelta = 0f
|
||||
}
|
||||
|
||||
@@ -57,6 +62,7 @@ class DragDropListState(
|
||||
draggingItemDraggedDelta = 0f
|
||||
draggingItemIndex = null
|
||||
draggingItemInitialOffset = 0f
|
||||
draggingItemSize = 0
|
||||
overscrollJob?.cancel()
|
||||
}
|
||||
|
||||
@@ -65,7 +71,8 @@ class DragDropListState(
|
||||
|
||||
val draggingItem = draggingItemLayoutInfo ?: return
|
||||
val startOffset = draggingItem.offset + draggingItemOffset
|
||||
val endOffset = startOffset + draggingItem.size
|
||||
// 🆕 v1.8.1: Fixierte Item-Größe für stabile Swap-Erkennung
|
||||
val endOffset = startOffset + draggingItemSize
|
||||
|
||||
// 🆕 v1.8.0: IMPL_023b — Straddle-Target-Center + Adjazenz-Filter
|
||||
// Statt den Mittelpunkt des gezogenen Items zu prüfen ("liegt mein Zentrum im Target?"),
|
||||
|
||||
@@ -370,7 +370,9 @@ private fun ChecklistEditor(
|
||||
key = { _, item -> item.id }
|
||||
) { index, item ->
|
||||
// 🆕 v1.8.0 (IMPL_017): Separator vor dem ersten Checked-Item
|
||||
if (showSeparator && index == uncheckedCount) {
|
||||
// 🆕 v1.8.1: Separator während Drag ausblenden — verhindert Flächenveränderung
|
||||
// am Separator-Item die Swap-Erkennung destabilisiert
|
||||
if (showSeparator && index == uncheckedCount && dragDropState.draggingItemIndex == null) {
|
||||
CheckedItemsSeparator(checkedCount = checkedCount)
|
||||
}
|
||||
|
||||
@@ -409,7 +411,10 @@ private fun ChecklistEditor(
|
||||
// 🆕 v1.8.0: IMPL_023 - Drag nur auf Handle
|
||||
dragModifier = Modifier.dragContainer(dragDropState, index),
|
||||
modifier = Modifier
|
||||
.animateItem() // 🆕 v1.8.0 (IMPL_017): LazyColumn Item-Animation
|
||||
// 🆕 v1.8.1: animateItem NUR für nicht-gedraggte Items
|
||||
// Bei gedraggten Items kämpft animateItem (Layout-Animation)
|
||||
// gegen den manuellen offset (Finger-Position) → Flackern
|
||||
.then(if (!isDragging) Modifier.animateItem() else Modifier)
|
||||
.offset {
|
||||
IntOffset(
|
||||
0,
|
||||
|
||||
@@ -24,7 +24,6 @@ import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
@@ -92,17 +91,11 @@ fun ChecklistItemRow(
|
||||
// 🆕 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 by remember {
|
||||
derivedStateOf { showGradient && scrollState.value > 0 }
|
||||
}
|
||||
val showBottomGradient by remember {
|
||||
derivedStateOf { showGradient && scrollState.value < scrollState.maxValue }
|
||||
}
|
||||
// 🆕 v1.8.1: Gradient-Sichtbarkeit direkt berechnet (kein derivedStateOf)
|
||||
// derivedStateOf mit remember{} fängt showGradient als stale val — nie aktualisiert.
|
||||
val showGradient = hasOverflow && collapsedHeightDp != null && !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) {
|
||||
@@ -173,7 +166,7 @@ fun ChecklistItemRow(
|
||||
Box(modifier = Modifier.weight(1f)) {
|
||||
// Scrollbarer Wrapper: begrenzt Höhe auf ~5 Zeilen wenn collapsed
|
||||
Box(
|
||||
modifier = if (!isFocused && useScrollClipping) {
|
||||
modifier = if (!isFocused && hasOverflow && collapsedHeightDp != null) {
|
||||
Modifier
|
||||
.heightIn(max = collapsedHeightDp!!)
|
||||
.verticalScroll(scrollState)
|
||||
@@ -216,11 +209,13 @@ fun ChecklistItemRow(
|
||||
onNext = { onAddNewItem() }
|
||||
),
|
||||
singleLine = false,
|
||||
// maxLines nur als Fallback bis collapsedHeight berechnet ist
|
||||
maxLines = if (isFocused || useScrollClipping) Int.MAX_VALUE else COLLAPSED_MAX_LINES,
|
||||
// 🆕 v1.8.1: maxLines IMMER Int.MAX_VALUE — keine Oszillation möglich
|
||||
// Höhenbegrenzung erfolgt ausschließlich über heightIn-Modifier oben.
|
||||
// Vorher: maxLines=5 → lineCount gedeckelt → Overflow nie erkannt → Deadlock
|
||||
maxLines = Int.MAX_VALUE,
|
||||
cursorBrush = SolidColor(MaterialTheme.colorScheme.primary),
|
||||
onTextLayout = { textLayoutResult ->
|
||||
// 🆕 v1.8.0: Overflow erkennen - ABER NUR wenn kein Drag aktiv ist
|
||||
// 🆕 v1.8.1: lineCount ist jetzt akkurat (maxLines=MAX_VALUE deckelt nicht)
|
||||
if (!isAnyItemDragging) {
|
||||
val overflow = textLayoutResult.lineCount > COLLAPSED_MAX_LINES
|
||||
hasOverflow = overflow
|
||||
@@ -230,6 +225,10 @@ fun ChecklistItemRow(
|
||||
textLayoutResult.getLineBottom(COLLAPSED_MAX_LINES - 1).toDp()
|
||||
}
|
||||
}
|
||||
// Reset wenn Text gekürzt wird
|
||||
if (!overflow) {
|
||||
collapsedHeightDp = null
|
||||
}
|
||||
}
|
||||
},
|
||||
decorationBox = { innerTextField ->
|
||||
|
||||
Reference in New Issue
Block a user