feat(editor): IMPL_05 - Auto-scroll on line wrap in checklist editor
Changes: - ChecklistItemRow.kt: Import mutableIntStateOf - ChecklistItemRow.kt: Add onHeightChanged callback parameter - ChecklistItemRow.kt: Track lastLineCount state for line wrap detection - ChecklistItemRow.kt: Detect line count increase in onTextLayout and trigger callback - NoteEditorScreen.kt: Add scrollToItemIndex state in ChecklistEditor - NoteEditorScreen.kt: Add LaunchedEffect for auto-scroll on height change - NoteEditorScreen.kt: Pass onHeightChanged callback to ChecklistItemRow When typing in a checklist item at the bottom of the list, the editor now automatically scrolls to keep the cursor visible when text wraps to a new line.
This commit is contained in:
@@ -338,6 +338,7 @@ private fun LazyItemScope.DraggableChecklistItem(
|
||||
onDelete: (String) -> Unit,
|
||||
onAddNewItemAfter: (String) -> Unit,
|
||||
onFocusHandled: () -> Unit,
|
||||
onHeightChanged: () -> Unit, // 🆕 v1.8.1 (IMPL_05)
|
||||
) {
|
||||
val isDragging = dragDropState.draggingItemIndex == visualIndex
|
||||
val elevation by animateDpAsState(
|
||||
@@ -363,6 +364,7 @@ private fun LazyItemScope.DraggableChecklistItem(
|
||||
isDragging = isDragging,
|
||||
isAnyItemDragging = dragDropState.draggingItemIndex != null,
|
||||
dragModifier = Modifier.dragContainer(dragDropState, visualIndex),
|
||||
onHeightChanged = onHeightChanged, // 🆕 v1.8.1 (IMPL_05)
|
||||
modifier = Modifier
|
||||
.then(if (!isDragging) Modifier.animateItem() else Modifier)
|
||||
.offset {
|
||||
@@ -404,6 +406,9 @@ private fun ChecklistEditor(
|
||||
onMove = onMove
|
||||
)
|
||||
|
||||
// 🆕 v1.8.1 (IMPL_05): Auto-Scroll bei Zeilenumbruch
|
||||
var scrollToItemIndex by remember { mutableStateOf<Int?>(null) }
|
||||
|
||||
// 🆕 v1.8.0 (IMPL_017 + IMPL_020): Separator nur bei MANUAL und UNCHECKED_FIRST anzeigen
|
||||
val uncheckedCount = items.count { !it.isChecked }
|
||||
val checkedCount = items.count { it.isChecked }
|
||||
@@ -418,6 +423,21 @@ private fun ChecklistEditor(
|
||||
dragDropState.separatorVisualIndex = separatorVisualIndex
|
||||
}
|
||||
|
||||
// 🆕 v1.8.1 (IMPL_05): Auto-Scroll wenn ein Item durch Zeilenumbruch wächst
|
||||
LaunchedEffect(scrollToItemIndex) {
|
||||
scrollToItemIndex?.let { index ->
|
||||
delay(50) // Warten bis Layout-Pass abgeschlossen
|
||||
val lastVisibleIndex = listState.layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0
|
||||
if (index >= lastVisibleIndex - 1) {
|
||||
listState.animateScrollToItem(
|
||||
index = minOf(index + 1, items.size + if (showSeparator) 1 else 0),
|
||||
scrollOffset = 0
|
||||
)
|
||||
}
|
||||
scrollToItemIndex = null
|
||||
}
|
||||
}
|
||||
|
||||
LazyColumn(
|
||||
state = listState,
|
||||
modifier = Modifier.weight(1f),
|
||||
@@ -438,7 +458,8 @@ private fun ChecklistEditor(
|
||||
onCheckedChange = onCheckedChange,
|
||||
onDelete = onDelete,
|
||||
onAddNewItemAfter = onAddNewItemAfter,
|
||||
onFocusHandled = onFocusHandled
|
||||
onFocusHandled = onFocusHandled,
|
||||
onHeightChanged = { scrollToItemIndex = index } // 🆕 v1.8.1 (IMPL_05)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -466,7 +487,8 @@ private fun ChecklistEditor(
|
||||
onCheckedChange = onCheckedChange,
|
||||
onDelete = onDelete,
|
||||
onAddNewItemAfter = onAddNewItemAfter,
|
||||
onFocusHandled = onFocusHandled
|
||||
onFocusHandled = onFocusHandled,
|
||||
onHeightChanged = { scrollToItemIndex = visualIndex } // 🆕 v1.8.1 (IMPL_05)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
@@ -70,6 +71,7 @@ fun ChecklistItemRow(
|
||||
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
|
||||
onHeightChanged: (() -> Unit)? = null, // 🆕 v1.8.1: IMPL_05 - Auto-scroll callback
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
@@ -91,6 +93,9 @@ fun ChecklistItemRow(
|
||||
// 🆕 v1.8.0: ScrollState für dynamischen Gradient
|
||||
val scrollState = rememberScrollState()
|
||||
|
||||
// 🆕 v1.8.1: IMPL_05 - Letzte Zeilenanzahl tracken für Auto-Scroll
|
||||
var lastLineCount by remember { mutableIntStateOf(0) }
|
||||
|
||||
// 🆕 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
|
||||
@@ -216,8 +221,9 @@ fun ChecklistItemRow(
|
||||
cursorBrush = SolidColor(MaterialTheme.colorScheme.primary),
|
||||
onTextLayout = { textLayoutResult ->
|
||||
// 🆕 v1.8.1: lineCount ist jetzt akkurat (maxLines=MAX_VALUE deckelt nicht)
|
||||
val lineCount = textLayoutResult.lineCount
|
||||
if (!isAnyItemDragging) {
|
||||
val overflow = textLayoutResult.lineCount > COLLAPSED_MAX_LINES
|
||||
val overflow = lineCount > COLLAPSED_MAX_LINES
|
||||
hasOverflow = overflow
|
||||
// Höhe der ersten 5 Zeilen berechnen (einmalig)
|
||||
if (overflow && collapsedHeightDp == null) {
|
||||
@@ -230,6 +236,11 @@ fun ChecklistItemRow(
|
||||
collapsedHeightDp = null
|
||||
}
|
||||
}
|
||||
// 🆕 v1.8.1 (IMPL_05): Höhenänderung bei Zeilenumbruch melden
|
||||
if (isFocused && lineCount > lastLineCount && lastLineCount > 0) {
|
||||
onHeightChanged?.invoke()
|
||||
}
|
||||
lastLineCount = lineCount
|
||||
},
|
||||
decorationBox = { innerTextField ->
|
||||
Box {
|
||||
|
||||
Reference in New Issue
Block a user