android: detekt caught a callback that never reached the cards
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 3m18s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 5m46s
Desktop (Tauri) / Update manifest (push) Successful in 4s
Android / Kotlin + Rust (APK) (push) Successful in 8m39s

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.
This commit is contained in:
2026-08-24 08:35:52 -04:00
parent 1a66d9c3a8
commit 315c5f19e6
3 changed files with 34 additions and 14 deletions
@@ -141,7 +141,12 @@ fun BoardScreen(
when { when {
state.loading -> LoadingBoard() state.loading -> LoadingBoard()
state.notes.isEmpty() -> EmptyBoard(state) 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 // `PullToRefreshBox` would be less code, but it takes no
// `enabled`, so the modifier and the indicator are wired by // `enabled`, so the modifier and the indicator are wired by
@@ -324,6 +329,7 @@ private fun DrawerRow(
private fun NoteBoard( private fun NoteBoard(
notes: List<Note>, notes: List<Note>,
onOpenNote: (Note) -> Unit, onOpenNote: (Note) -> Unit,
onToggleItem: (Note, Int, Boolean) -> Unit,
) { ) {
LazyVerticalStaggeredGrid( LazyVerticalStaggeredGrid(
columns = StaggeredGridCells.Fixed(BOARD_COLUMNS), columns = StaggeredGridCells.Fixed(BOARD_COLUMNS),
@@ -109,7 +109,11 @@ private fun NoteBody(
val index = itemAtLine[n] val index = itemAtLine[n]
val item = index?.let { note.items.getOrNull(it) } val item = index?.let { note.items.getOrNull(it) }
when { 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 // Kept as a gap rather than dropped: it is the paragraph break
// somebody typed, and the card reads as a wall without it. // somebody typed, and the card reads as a wall without it.
line.isBlank() -> Spacer(Modifier.height(4.dp)) line.isBlank() -> Spacer(Modifier.height(4.dp))
@@ -396,21 +396,31 @@ private fun continueChecklist(
new: TextFieldValue, new: TextFieldValue,
): TextFieldValue { ): TextFieldValue {
val caret = new.selection.start val caret = new.selection.start
if (new.selection.length != 0) return new val typedNewline =
if (new.text.length != old.text.length + 1) return new new.selection.length == 0 &&
if (caret == 0 || new.text[caret - 1] != '\n') return new 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) return when {
val lineStart = before.lastIndexOf('\n') + 1 marker == null -> new
val marker = checklistContinuation(before.substring(lineStart)) ?: return new
if (marker.isEmpty()) {
// An empty item: take the marker line away rather than adding another. // An empty item: take the marker line away rather than adding another.
val text = new.text.take(lineStart) + new.text.substring(caret) marker.isEmpty() ->
return TextFieldValue(text, TextRange(lineStart)) 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 = "- [ ] " private const val CHECKLIST_MARKER = "- [ ] "