From 315c5f19e6f6f7f9680906871c1b4bd8e3553e0c Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Mon, 24 Aug 2026 08:35:52 -0400 Subject: [PATCH] android: detekt caught a callback that never reached the cards MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Not a style finding. `onToggleItem` was added to BoardScreen's signature and read inside NoteBoard — which is a separate top-level composable, not a nested one, so the two were never connected. detekt reported it as an unused parameter; the compiler would have called it an unresolved reference. Neither had run: Kotlin is compiled at the "Unit tests" step, which is gated behind detekt, so nothing in this lane had type-checked the Android changes yet. Threaded properly now, which is what makes ticking a box from the board actually work rather than merely appear to. Two more from reading it again with that in mind: * `when { item != null -> … onToggleItem(index, …) }` would not have compiled. Kotlin does not infer that a non-null item implies a non-null index, so the index stayed `Int?` against an `Int` parameter. Both are in the condition now. * continueChecklist had six returns against detekt's limit of two. Collapsed to one `when`, with the two intermediate values guarded on `typedNewline` — `caret - 1` is only a real index once it is known to be the newline just typed. --- .../fabledsword/thoughtsync/ui/BoardScreen.kt | 8 ++++- .../fabledsword/thoughtsync/ui/NoteCard.kt | 6 +++- .../thoughtsync/ui/NoteEditorScreen.kt | 34 ++++++++++++------- 3 files changed, 34 insertions(+), 14 deletions(-) diff --git a/android/app/src/main/java/com/fabledsword/thoughtsync/ui/BoardScreen.kt b/android/app/src/main/java/com/fabledsword/thoughtsync/ui/BoardScreen.kt index ed226ee..0c3fe20 100644 --- a/android/app/src/main/java/com/fabledsword/thoughtsync/ui/BoardScreen.kt +++ b/android/app/src/main/java/com/fabledsword/thoughtsync/ui/BoardScreen.kt @@ -141,7 +141,12 @@ fun BoardScreen( when { state.loading -> LoadingBoard() state.notes.isEmpty() -> EmptyBoard(state) - else -> NoteBoard(notes = state.notes, onOpenNote = onOpenNote) + else -> + NoteBoard( + notes = state.notes, + onOpenNote = onOpenNote, + onToggleItem = onToggleItem, + ) } // `PullToRefreshBox` would be less code, but it takes no // `enabled`, so the modifier and the indicator are wired by @@ -324,6 +329,7 @@ private fun DrawerRow( private fun NoteBoard( notes: List, onOpenNote: (Note) -> Unit, + onToggleItem: (Note, Int, Boolean) -> Unit, ) { LazyVerticalStaggeredGrid( columns = StaggeredGridCells.Fixed(BOARD_COLUMNS), diff --git a/android/app/src/main/java/com/fabledsword/thoughtsync/ui/NoteCard.kt b/android/app/src/main/java/com/fabledsword/thoughtsync/ui/NoteCard.kt index f87a10b..3b92d66 100644 --- a/android/app/src/main/java/com/fabledsword/thoughtsync/ui/NoteCard.kt +++ b/android/app/src/main/java/com/fabledsword/thoughtsync/ui/NoteCard.kt @@ -109,7 +109,11 @@ private fun NoteBody( val index = itemAtLine[n] val item = index?.let { note.items.getOrNull(it) } when { - item != null -> ChecklistRow(item) { onToggleItem(index, !item.checked) } + // Both in the condition, not just `item`: Kotlin will not infer that a + // non-null item implies a non-null index, and `onToggleItem` needs the + // index smart-cast to act on it. + index != null && item != null -> + ChecklistRow(item) { onToggleItem(index, !item.checked) } // Kept as a gap rather than dropped: it is the paragraph break // somebody typed, and the card reads as a wall without it. line.isBlank() -> Spacer(Modifier.height(4.dp)) diff --git a/android/app/src/main/java/com/fabledsword/thoughtsync/ui/NoteEditorScreen.kt b/android/app/src/main/java/com/fabledsword/thoughtsync/ui/NoteEditorScreen.kt index f1e6d74..4c39129 100644 --- a/android/app/src/main/java/com/fabledsword/thoughtsync/ui/NoteEditorScreen.kt +++ b/android/app/src/main/java/com/fabledsword/thoughtsync/ui/NoteEditorScreen.kt @@ -396,21 +396,31 @@ private fun continueChecklist( new: TextFieldValue, ): TextFieldValue { val caret = new.selection.start - if (new.selection.length != 0) return new - if (new.text.length != old.text.length + 1) return new - if (caret == 0 || new.text[caret - 1] != '\n') return new + val typedNewline = + new.selection.length == 0 && + new.text.length == old.text.length + 1 && + caret > 0 && + new.text[caret - 1] == '\n' + // Both guarded on `typedNewline`, because caret - 1 is only a real index once it + // is known to be the newline that was just typed. + val lineStart = if (typedNewline) new.text.take(caret - 1).lastIndexOf('\n') + 1 else 0 + val marker = + if (typedNewline) checklistContinuation(new.text.substring(lineStart, caret - 1)) else null - val before = new.text.take(caret - 1) - val lineStart = before.lastIndexOf('\n') + 1 - val marker = checklistContinuation(before.substring(lineStart)) ?: return new - - if (marker.isEmpty()) { + return when { + marker == null -> new // An empty item: take the marker line away rather than adding another. - val text = new.text.take(lineStart) + new.text.substring(caret) - return TextFieldValue(text, TextRange(lineStart)) + marker.isEmpty() -> + TextFieldValue( + new.text.take(lineStart) + new.text.substring(caret), + TextRange(lineStart), + ) + else -> + TextFieldValue( + new.text.take(caret) + marker + new.text.substring(caret), + TextRange(caret + marker.length), + ) } - val text = new.text.take(caret) + marker + new.text.substring(caret) - return TextFieldValue(text, TextRange(caret + marker.length)) } private const val CHECKLIST_MARKER = "- [ ] "