android: pin and archive a note someone shared with you
CI & Build / Python lint (push) Successful in 2s
CI & Build / Build now, or wait for Android? (push) Successful in 2s
Android / Build, or is the channel already serving this? (push) Successful in 2s
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 2s
CI & Build / Web typecheck and unit tests (push) Successful in 9s
CI & Build / Python tests (push) Successful in 11s
CI & Build / integration (push) Successful in 1m24s
CI & Build / Build & push image (push) Skipped
Desktop (Tauri) / Web tests, clippy, Rust tests and rustfmt (push) Failing after 1m30s
Desktop (Tauri) / Tauri desktop (Linux) (push) Skipped
Desktop (Tauri) / Windows installer (cross-compiled) (push) Skipped
Desktop (Tauri) / Update manifest (push) Skipped
Android / Kotlin + Rust (APK) (push) Successful in 7m17s

The card's long-press menu and the editor's overflow now open on shared notes
with Pin and Archive; Labels, Share and Move to trash stay the owner's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-07 17:13:58 -04:00
co-authored by Claude Opus 5.5
parent 89cd1c76bb
commit 4f5459cb94
3 changed files with 28 additions and 18 deletions
@@ -85,8 +85,9 @@ fun EditorTopBar(
onConfirmDelete: () -> Unit,
onAction: (EditorAction) -> Unit,
) {
// Someone else's note (#5175): its text is all that may change here, so the bar
// keeps the checklist button and nothing that is the owner's to do.
// Someone else's note (#5175): its text, at `edit`, and this account's own pin
// and archive (#5176) are all that may change here, so the bar keeps the
// checklist button and an overflow of those two, and nothing that is the owner's.
val owner = access == NoteAccess.OWNER
val dark = isSystemInDarkTheme()
TopAppBar(
@@ -133,10 +134,10 @@ fun EditorTopBar(
)
}
}
if (owner) {
if (owner || !note.trashed) {
OverflowMenu(
note = note,
readOnly = readOnly,
owner = owner,
onPicker = onPicker,
onShare = onShare,
onConfirmDelete = onConfirmDelete,
@@ -278,7 +279,7 @@ private fun savedLabel(
@Composable
private fun OverflowMenu(
note: Note,
readOnly: Boolean,
owner: Boolean,
onPicker: (Picker) -> Unit,
onShare: () -> Unit,
onConfirmDelete: () -> Unit,
@@ -291,7 +292,7 @@ private fun OverflowMenu(
Icon(Icons.Filled.MoreVert, contentDescription = stringResource(R.string.editor_more))
}
DropdownMenu(expanded = open, onDismissRequest = close) {
if (readOnly) {
if (note.trashed) {
MenuItem(R.string.editor_restore, close) { onAction(EditorAction.Restore) }
MenuItem(R.string.editor_delete_forever, close, onConfirmDelete)
} else {
@@ -299,19 +300,23 @@ private fun OverflowMenu(
if (note.pinned) R.string.editor_unpin else R.string.editor_pin,
close,
) { onAction(EditorAction.SetPinned(!note.pinned)) }
if (owner) {
MenuItem(R.string.editor_labels, close) { onPicker(Picker.LABELS) }
}
// Not on a draft: the server has nothing to share until it is saved.
if (note.id != DRAFT_ID) {
if (owner && note.id != DRAFT_ID) {
MenuItem(R.string.editor_share, close, onShare)
}
MenuItem(
if (note.archived) R.string.editor_unarchive else R.string.editor_archive,
close,
) { onAction(EditorAction.SetArchived(!note.archived)) }
if (owner) {
MenuItem(R.string.editor_trash, close) { onAction(EditorAction.Trash) }
}
}
}
}
}
/**
@@ -5,9 +5,10 @@ import com.fabledsword.inkwell.core.Note
/**
* How this account holds a note (#5175), read from the core's `permission`.
*
* Someone else's note at [EDIT] is theirs to have its TEXT changed here and nothing
* else; at [VIEW] nothing changes here at all. The core refuses the rest anyway —
* this only keeps the editor from offering what would be refused.
* Someone else's note at [EDIT] may have its TEXT changed here; at [VIEW] it may
* not. Either way its pin and archive are this account's own (#5176). The core
* refuses the rest anyway — this only keeps the editor from offering what would be
* refused.
*/
enum class NoteAccess { OWNER, EDIT, VIEW }
@@ -19,10 +20,10 @@ val Note.access: NoteAccess
else -> NoteAccess.OWNER
}
/** Nothing changes here: the note is in the trash, or shared with us to view. */
/** Its content can't change here: it is in the trash, or shared with us to view. */
val Note.readOnlyHere: Boolean
get() = trashed || access == NoteAccess.VIEW
/** The owner's own controls apply: tags, reminder, files, previews, the menu. */
/** The owner's own controls apply: tags, reminder, files, previews, share, trash. */
val Note.ownerControlsHere: Boolean
get() = !readOnlyHere && access == NoteAccess.OWNER
@@ -229,9 +229,11 @@ private fun NoteMenu(
onAction: (EditorAction) -> Unit,
onConfirmDelete: () -> Unit,
) {
// Every item here is the owner's to do, so someone else's note has no menu at all
// rather than an empty one (#5175).
DropdownMenu(expanded = expanded && note.access == NoteAccess.OWNER, onDismissRequest = onDismiss) {
// Pin and archive are this account's own on someone else's note too (#5176);
// trash is the owner's. A note shared with us never reaches our Trash, so the
// trashed set is only ever the owner's.
val owner = note.access == NoteAccess.OWNER
DropdownMenu(expanded = expanded && (owner || !note.trashed), onDismissRequest = onDismiss) {
if (note.trashed) {
MenuItem(R.string.editor_restore, onDismiss) { onAction(EditorAction.Restore) }
MenuItem(R.string.editor_delete_forever, onDismiss, onConfirmDelete)
@@ -244,9 +246,11 @@ private fun NoteMenu(
if (note.archived) R.string.editor_unarchive else R.string.editor_archive,
onDismiss,
) { onAction(EditorAction.SetArchived(!note.archived)) }
if (owner) {
MenuItem(R.string.editor_trash, onDismiss) { onAction(EditorAction.Trash) }
}
}
}
}
/**