diff --git a/android/app/src/main/java/com/fabledsword/inkwell/ui/BoardScreen.kt b/android/app/src/main/java/com/fabledsword/inkwell/ui/BoardScreen.kt index 9a6fee5..143d46a 100644 --- a/android/app/src/main/java/com/fabledsword/inkwell/ui/BoardScreen.kt +++ b/android/app/src/main/java/com/fabledsword/inkwell/ui/BoardScreen.kt @@ -195,10 +195,7 @@ fun BoardScreen( onMenu = { scope.launch { drawerState.open() } }, ) - // The main board only, and not while searching, as on the web: a - // search spans everything, so a filter row over its hits would be a - // control that does nothing. - if (state.destination == Destination.Notes && !state.searching) { + if (state.filterable) { FilterRow( filters = state.filters, onOpen = { filtering = true }, @@ -257,13 +254,7 @@ fun BoardScreen( onToggleItem = onToggleItem, onNoteAction = onCardAction, onConfirmDelete = { confirmingDelete = it }, - // The whole board in one order, as on the web: not a - // search, another view, or a filtered subset, whose - // order would be renumbered against notes it can't see. - reorderable = - state.destination == Destination.Notes && - !state.searching && - state.filters.count == 0, + reorderable = state.reorderable, onReorder = onReorder, ) } diff --git a/android/app/src/main/java/com/fabledsword/inkwell/ui/BoardViewModel.kt b/android/app/src/main/java/com/fabledsword/inkwell/ui/BoardViewModel.kt index 955adf4..8ea766a 100644 --- a/android/app/src/main/java/com/fabledsword/inkwell/ui/BoardViewModel.kt +++ b/android/app/src/main/java/com/fabledsword/inkwell/ui/BoardViewModel.kt @@ -85,6 +85,18 @@ data class BoardState( ) { /** Search overrides the destination while there is a query to run. */ val searching: Boolean get() = query.isNotBlank() + + /** + * The main board only, and not while searching, as on the web: a search spans + * everything, so a filter over its hits would be a control that does nothing. + */ + val filterable: Boolean get() = destination == Destination.Notes && !searching + + /** + * The whole board in one order, as on the web: not a search, another view, or a + * filtered subset, whose order would be renumbered against notes it can't see. + */ + val reorderable: Boolean get() = filterable && filters.count == 0 } /** diff --git a/android/app/src/main/java/com/fabledsword/inkwell/ui/NoteCard.kt b/android/app/src/main/java/com/fabledsword/inkwell/ui/NoteCard.kt index 3c0e32c..827d459 100644 --- a/android/app/src/main/java/com/fabledsword/inkwell/ui/NoteCard.kt +++ b/android/app/src/main/java/com/fabledsword/inkwell/ui/NoteCard.kt @@ -108,75 +108,7 @@ fun NoteCard( .border(1.dp, if (dark) CARD_EDGE_DARK else CARD_EDGE_LIGHT, RoundedCornerShape(CARD_RADIUS)) .padding(12.dp), ) { - // TAGS FIRST, on a row of their own above the body: a board is scanned, and - // what a note is about should be the first thing the eye lands on. Not beside - // the body, whose first line is the note's name. - // - // Only labels whose text is not still in the note (`via_tag` is false). A tag - // left in prose is tinted in place instead (see [tintTags]), so this row holds - // what the body cannot say: a tag lifted off its own line, or one added by hand. - val chips = note.labels.filterNot { it.viaTag } - if (chips.isNotEmpty()) { - LabelChips(labels = chips) - Spacer(Modifier.height(8.dp)) - } - - // A note that is NOTHING but a URL renders as its preview and nothing - // else — printing the raw address under a card that already says where it - // goes is saying the same thing twice, badly. Until the unfurl lands, or - // if it never does, `preview` is null and the body falls through to - // NoteBody, which shows the URL. Never a blank card. - val lonePreview = remember(note.body, note.previews) { loneUrlPreview(note) } - - // Body then checklist, in order — a note can carry both (M13 step 2). The - // first line of the body IS the note's name, at the same weight as the rest of - // it (M13 steps 3 and 4). - if (lonePreview != null) { - LinkPreviewCard(preview = lonePreview, compact = false) - } else if (note.body.isNotBlank()) { - // A note shared with us to view has inert boxes (#5175): ticking one is - // changing its text, which is not ours to do. - NoteBody( - note = note, - onToggleItem = if (note.access == NoteAccess.VIEW) INERT_TOGGLE else onToggleItem, - ) - } - - // Links mentioned INSIDE a note: a compact strip at the foot of the card, - // under the note's own words rather than stacked on top of them. Putting - // them above would set a stranger's headline where the note's first line - // should be — the web learned that in M13 and moved them down. - if (!isLoneUrl(note) && note.previews.isNotEmpty()) { - Spacer(Modifier.height(8.dp)) - note.previews.forEach { preview -> - LinkPreviewCard(preview = preview, compact = true) - Spacer(Modifier.height(4.dp)) - } - } - - // Under the words and the links, like the web's card: a photo is often what a - // note is ABOUT, but its first line is still its name. - if (note.attachments.isNotEmpty()) { - CardAttachments(attachments = note.attachments) - } - - // A note with nothing in it still has to occupy the board legibly — otherwise - // it reads as a rendering bug. - if (note.body.isBlank() && note.previews.isEmpty() && note.attachments.isEmpty()) { - Text( - text = stringResource(R.string.board_empty_note), - style = MaterialTheme.typography.bodyMedium, - fontStyle = FontStyle.Italic, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) - } - - note.remindAt?.let { at -> - Spacer(Modifier.height(8.dp)) - ReminderChip(instant = at, recurrence = note.recurrence) - } - - SharedChip(note) + CardContents(note = note, onToggleItem = onToggleItem) } NoteMenu( @@ -189,6 +121,83 @@ fun NoteCard( } } +/** What a card shows, top to bottom: tags, body, links, attachments, reminder, sharing. */ +@Composable +private fun CardContents( + note: Note, + onToggleItem: (Int, Boolean) -> Unit, +) { + // TAGS FIRST, on a row of their own above the body: a board is scanned, and + // what a note is about should be the first thing the eye lands on. Not beside + // the body, whose first line is the note's name. + // + // Only labels whose text is not still in the note (`via_tag` is false). A tag + // left in prose is tinted in place instead (see [tintTags]), so this row holds + // what the body cannot say: a tag lifted off its own line, or one added by hand. + val chips = note.labels.filterNot { it.viaTag } + if (chips.isNotEmpty()) { + LabelChips(labels = chips) + Spacer(Modifier.height(8.dp)) + } + + // A note that is NOTHING but a URL renders as its preview and nothing + // else — printing the raw address under a card that already says where it + // goes is saying the same thing twice, badly. Until the unfurl lands, or + // if it never does, `preview` is null and the body falls through to + // NoteBody, which shows the URL. Never a blank card. + val lonePreview = remember(note.body, note.previews) { loneUrlPreview(note) } + + // Body then checklist, in order — a note can carry both (M13 step 2). The + // first line of the body IS the note's name, at the same weight as the rest of + // it (M13 steps 3 and 4). + if (lonePreview != null) { + LinkPreviewCard(preview = lonePreview, compact = false) + } else if (note.body.isNotBlank()) { + // A note shared with us to view has inert boxes (#5175): ticking one is + // changing its text, which is not ours to do. + NoteBody( + note = note, + onToggleItem = if (note.access == NoteAccess.VIEW) INERT_TOGGLE else onToggleItem, + ) + } + + // Links mentioned INSIDE a note: a compact strip at the foot of the card, + // under the note's own words rather than stacked on top of them. Putting + // them above would set a stranger's headline where the note's first line + // should be — the web learned that in M13 and moved them down. + if (!isLoneUrl(note) && note.previews.isNotEmpty()) { + Spacer(Modifier.height(8.dp)) + note.previews.forEach { preview -> + LinkPreviewCard(preview = preview, compact = true) + Spacer(Modifier.height(4.dp)) + } + } + + // Under the words and the links, like the web's card: a photo is often what a + // note is ABOUT, but its first line is still its name. + if (note.attachments.isNotEmpty()) { + CardAttachments(attachments = note.attachments) + } + + // A note with nothing in it still has to occupy the board legibly — otherwise + // it reads as a rendering bug. + if (note.body.isBlank() && note.previews.isEmpty() && note.attachments.isEmpty()) { + Text( + text = stringResource(R.string.board_empty_note), + style = MaterialTheme.typography.bodyMedium, + fontStyle = FontStyle.Italic, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + + note.remindAt?.let { at -> + Spacer(Modifier.height(8.dp)) + ReminderChip(instant = at, recurrence = note.recurrence) + } + + SharedChip(note) +} + /** * What you can do to a note without opening it, on a long press. *