android: detekt — check the link before loading shares, and NoteAccess gets its own file
Android / Build, or is the channel already serving this? (push) Successful in 4s
CI & Build / Python lint (push) Successful in 3s
CI & Build / Build now, or wait for Android? (push) Successful in 3s
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 3s
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 13s
CI & Build / integration (push) Successful in 1m6s
CI & Build / Build & push image (push) Skipped
Android / Kotlin + Rust (APK) (push) Successful in 7m27s

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-07 15:52:21 -04:00
co-authored by Claude Opus 5.5
parent 2e7db21b21
commit 5e8c6dc7bf
3 changed files with 38 additions and 33 deletions
@@ -0,0 +1,28 @@
package com.fabledsword.inkwell.ui
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.
*/
enum class NoteAccess { OWNER, EDIT, VIEW }
val Note.access: NoteAccess
get() =
when (permission) {
"edit" -> NoteAccess.EDIT
"view" -> NoteAccess.VIEW
else -> NoteAccess.OWNER
}
/** Nothing changes here: the note 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. */
val Note.ownerControlsHere: Boolean
get() = !readOnlyHere && access == NoteAccess.OWNER
@@ -36,31 +36,6 @@ import com.fabledsword.inkwell.core.Member
import com.fabledsword.inkwell.core.Note import com.fabledsword.inkwell.core.Note
import com.fabledsword.inkwell.core.NoteShare import com.fabledsword.inkwell.core.NoteShare
/**
* 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.
*/
enum class NoteAccess { OWNER, EDIT, VIEW }
val Note.access: NoteAccess
get() =
when (permission) {
"edit" -> NoteAccess.EDIT
"view" -> NoteAccess.VIEW
else -> NoteAccess.OWNER
}
/** Nothing changes here: the note 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. */
val Note.ownerControlsHere: Boolean
get() = !readOnlyHere && access == NoteAccess.OWNER
/** "Shared by Robin · view only" over someone else's note; nothing over our own. */ /** "Shared by Robin · view only" over someone else's note; nothing over our own. */
@Composable @Composable
fun SharedByLine(note: Note) { fun SharedByLine(note: Note) {
@@ -10,7 +10,9 @@ import com.fabledsword.inkwell.core.CoreException
import com.fabledsword.inkwell.core.Inkwell import com.fabledsword.inkwell.core.Inkwell
import com.fabledsword.inkwell.core.Member import com.fabledsword.inkwell.core.Member
import com.fabledsword.inkwell.core.NoteShare import com.fabledsword.inkwell.core.NoteShare
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
/** Everything the Share sheet renders from. [noteId] null means the sheet is closed. */ /** Everything the Share sheet renders from. [noteId] null means the sheet is closed. */
data class ShareState( data class ShareState(
@@ -54,19 +56,19 @@ class ShareViewModel(
fun open(noteId: String) { fun open(noteId: String) {
state = ShareState(noteId = noteId, loading = true) state = ShareState(noteId = noteId, loading = true)
viewModelScope.launch { viewModelScope.launch {
// Unlinked is not a failure: it is the phone working as intended, so the
// sheet says what sharing needs rather than showing an error.
state = state =
try { try {
val members = core.shareDirectory() if (!withContext(Dispatchers.IO) { core.syncStatus().linked }) {
val shares = core.noteShares(noteId)
state.copy(loading = false, members = members, shares = shares)
} catch (e: CoreException) {
// Unlinked is not a failure: it is the phone working as intended,
// and the sheet says what sharing needs instead of an error.
if (e is CoreException.NotLinked) {
state.copy(loading = false, needsServer = true) state.copy(loading = false, needsServer = true)
} else { } else {
state.copy(loading = false, loadError = e.describeShareFailure()) val members = core.shareDirectory()
val shares = core.noteShares(noteId)
state.copy(loading = false, members = members, shares = shares)
} }
} catch (e: CoreException) {
state.copy(loading = false, loadError = e.describeShareFailure())
} }
} }
} }