android: bring BoardScreen and NoteCard back under detekt's complexity limit
CI & Build / Python lint (push) Successful in 2s
CI & Build / Build now, or wait for Android? (push) Successful in 3s
Android / Build, or is the channel already serving this? (push) Successful in 3s
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 2s
Desktop (Tauri) / Web tests, clippy, Rust tests and rustfmt (push) Skipped
Desktop (Tauri) / Tauri desktop (Linux) (push) Skipped
Desktop (Tauri) / Windows installer (cross-compiled) (push) Skipped
Desktop (Tauri) / Update manifest (push) Skipped
CI & Build / Web typecheck and unit tests (push) Successful in 10s
CI & Build / Python tests (push) Successful in 12s
Android / Core and FFI clippy and tests (push) Successful in 26s
CI & Build / integration (push) Successful in 1m7s
CI & Build / Build & push image (push) Skipped
Android / Kotlin + Rust (APK) (push) Successful in 9m31s
Android / Build the server image (push) Successful in 1s
CI & Build / Python lint (push) Successful in 2s
CI & Build / Build now, or wait for Android? (push) Successful in 3s
Android / Build, or is the channel already serving this? (push) Successful in 3s
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 2s
Desktop (Tauri) / Web tests, clippy, Rust tests and rustfmt (push) Skipped
Desktop (Tauri) / Tauri desktop (Linux) (push) Skipped
Desktop (Tauri) / Windows installer (cross-compiled) (push) Skipped
Desktop (Tauri) / Update manifest (push) Skipped
CI & Build / Web typecheck and unit tests (push) Successful in 10s
CI & Build / Python tests (push) Successful in 12s
Android / Core and FFI clippy and tests (push) Successful in 26s
CI & Build / integration (push) Successful in 1m7s
CI & Build / Build & push image (push) Skipped
Android / Kotlin + Rust (APK) (push) Successful in 9m31s
Android / Build the server image (push) Successful in 1s
Run 8693's detekt failed both on CyclomaticComplexMethod (17 and 15 against 15) after the filters and drag-to-reorder landed. - BoardState now carries `filterable` and `reorderable`, so the board's rules for when the filter row shows and when a card can be carried live with the state they read instead of as boolean chains in the screen. - NoteCard's contents (tags, body, links, attachments, reminder, sharing) move to their own CardContents composable, leaving NoteCard the gestures, the frame and the menu. No behaviour change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -195,10 +195,7 @@ fun BoardScreen(
|
|||||||
onMenu = { scope.launch { drawerState.open() } },
|
onMenu = { scope.launch { drawerState.open() } },
|
||||||
)
|
)
|
||||||
|
|
||||||
// The main board only, and not while searching, as on the web: a
|
if (state.filterable) {
|
||||||
// search spans everything, so a filter row over its hits would be a
|
|
||||||
// control that does nothing.
|
|
||||||
if (state.destination == Destination.Notes && !state.searching) {
|
|
||||||
FilterRow(
|
FilterRow(
|
||||||
filters = state.filters,
|
filters = state.filters,
|
||||||
onOpen = { filtering = true },
|
onOpen = { filtering = true },
|
||||||
@@ -257,13 +254,7 @@ fun BoardScreen(
|
|||||||
onToggleItem = onToggleItem,
|
onToggleItem = onToggleItem,
|
||||||
onNoteAction = onCardAction,
|
onNoteAction = onCardAction,
|
||||||
onConfirmDelete = { confirmingDelete = it },
|
onConfirmDelete = { confirmingDelete = it },
|
||||||
// The whole board in one order, as on the web: not a
|
reorderable = state.reorderable,
|
||||||
// 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,
|
|
||||||
onReorder = onReorder,
|
onReorder = onReorder,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,6 +85,18 @@ data class BoardState(
|
|||||||
) {
|
) {
|
||||||
/** Search overrides the destination while there is a query to run. */
|
/** Search overrides the destination while there is a query to run. */
|
||||||
val searching: Boolean get() = query.isNotBlank()
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -108,75 +108,7 @@ fun NoteCard(
|
|||||||
.border(1.dp, if (dark) CARD_EDGE_DARK else CARD_EDGE_LIGHT, RoundedCornerShape(CARD_RADIUS))
|
.border(1.dp, if (dark) CARD_EDGE_DARK else CARD_EDGE_LIGHT, RoundedCornerShape(CARD_RADIUS))
|
||||||
.padding(12.dp),
|
.padding(12.dp),
|
||||||
) {
|
) {
|
||||||
// TAGS FIRST, on a row of their own above the body: a board is scanned, and
|
CardContents(note = note, onToggleItem = onToggleItem)
|
||||||
// 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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NoteMenu(
|
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.
|
* What you can do to a note without opening it, on a long press.
|
||||||
*
|
*
|
||||||
|
|||||||
Reference in New Issue
Block a user