From 3e4b1bd07e765cb43326a4c5188c7d3573531ba6 Mon Sep 17 00:00:00 2001 From: inventory69 Date: Wed, 11 Feb 2026 10:42:08 +0100 Subject: [PATCH] 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. --- .../simplenotes/ui/editor/NoteEditorScreen.kt | 26 +++++++++++++++++-- .../ui/editor/components/ChecklistItemRow.kt | 13 +++++++++- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/android/app/src/main/java/dev/dettmer/simplenotes/ui/editor/NoteEditorScreen.kt b/android/app/src/main/java/dev/dettmer/simplenotes/ui/editor/NoteEditorScreen.kt index dfe1cf5..3a4b966 100644 --- a/android/app/src/main/java/dev/dettmer/simplenotes/ui/editor/NoteEditorScreen.kt +++ b/android/app/src/main/java/dev/dettmer/simplenotes/ui/editor/NoteEditorScreen.kt @@ -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(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) ) } } diff --git a/android/app/src/main/java/dev/dettmer/simplenotes/ui/editor/components/ChecklistItemRow.kt b/android/app/src/main/java/dev/dettmer/simplenotes/ui/editor/components/ChecklistItemRow.kt index 1c86102..575f976 100644 --- a/android/app/src/main/java/dev/dettmer/simplenotes/ui/editor/components/ChecklistItemRow.kt +++ b/android/app/src/main/java/dev/dettmer/simplenotes/ui/editor/components/ChecklistItemRow.kt @@ -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 {