fix(cordn): an attachment that cannot be sent says so

`sendAttachment` ended four ways with a bare `?: return`: no session, no
group, unreadable bytes, and `upload()` returning null. Every one reads as
"nothing to do" and behaves as "the attach button does nothing at all" — the
picker closes, the spinner ends, and no message and no error appear. The
`attachError` banner added earlier could not fire, because nothing threw.

The fourth is the one that matters: `CordnMediaService.upload` returns null
precisely when the account has no Blossom server, which is a setting the
person can change and the only failure here that is entirely actionable.

Each is now a `CordnAttachmentException` carrying a reason, and the catch that
sets the banner also logs — a failed attachment previously left no trace
anywhere, in the UI or in logcat.

Not a reproduction: on the tablet the system picker returned RESULT_CANCELED
on every attempt, so no file ever reached `sendAttachment`. This is the silent
path closed by reading, not a bug observed in the field.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Vitor Pamplona
2026-09-24 09:05:44 -04:00
co-authored by Claude Opus 5
parent 5e89ffd800
commit 4bbd06ce2c
2 changed files with 31 additions and 4 deletions
@@ -360,6 +360,10 @@ private fun CordnGroupChat(
try {
sendAttachment(context, accountViewModel, room, uri)
} catch (e: Exception) {
// A failed attachment left no trace anywhere; the
// banner tells the person, this tells whoever has
// to work out why.
Log.w("CordnGroupChat", "attachment failed in ${room.gid}: ${e.message}", e)
attachError = e.message ?: uploadFailed
} finally {
attaching = false
@@ -897,6 +901,11 @@ private fun MessageBody(
}
}
/** A reason an attachment did not go, in words meant for the person who tried. */
private class CordnAttachmentException(
message: String,
) : Exception(message)
/**
* Encrypts the picked file and sends it as an attachment on an empty message.
*
@@ -910,15 +919,31 @@ private suspend fun sendAttachment(
room: CordnGroupChatroom,
uri: Uri,
) {
val session = accountViewModel.account.cordnRuntime?.sessionOrNull(room.coordinatorPubKey) ?: return
val group = session.manager.group(room.gid) ?: return
// Every step here used to `?: return`, which reads as "nothing to do" and
// behaves as "the attach button does nothing at all": the picker closed,
// the spinner ended, and no message and no error appeared. Measured on the
// tablet — attaching a PNG produced no message, no error and no log line.
// Each one is now a reason a person can act on.
val session =
accountViewModel.account.cordnRuntime?.sessionOrNull(room.coordinatorPubKey)
?: throw CordnAttachmentException(stringRes(context, R.string.cordn_send_no_session))
val group =
session.manager.group(room.gid)
?: throw CordnAttachmentException(stringRes(context, R.string.cordn_send_no_session))
val resolver = context.contentResolver
val mime = resolver.getType(uri) ?: CordnBlobUpload.OPAQUE
val name = uri.lastPathSegment?.substringAfterLast('/') ?: "file"
val bytes = withContext(Dispatchers.IO) { resolver.openInputStream(uri)?.use { it.readBytes() } } ?: return
val bytes =
withContext(Dispatchers.IO) { resolver.openInputStream(uri)?.use { it.readBytes() } }
?: throw CordnAttachmentException(stringRes(context, R.string.cordn_media_unreadable))
val tag = CordnMediaService(accountViewModel.account).upload(bytes, mime, name, context) ?: return
// Null means the account has no Blossom server, which is a setting the
// person can change — the one failure here that is entirely actionable,
// and the one that was hardest to notice.
val tag =
CordnMediaService(accountViewModel.account).upload(bytes, mime, name, context)
?: throw CordnAttachmentException(stringRes(context, R.string.cordn_media_no_server))
// Into the room as well, for the same reason every other send is: an
// attachment of your own echoes back as an Echo and would otherwise be
// invisible to the person who sent it.
+2
View File
@@ -325,6 +325,8 @@
<string name="cordn_send_failed">That did not send.</string>
<string name="cordn_send_no_session">This group\'s coordinator is not open, so nothing can be sent yet.</string>
<string name="cordn_media_download_failed">Could not open that file.</string>
<string name="cordn_media_no_server">No media server is set for this account, so there is nowhere to put the file. Pick one in Settings.</string>
<string name="cordn_media_unreadable">That file could not be read.</string>
<string name="cordn_voice_record">Record a voice note</string>
<string name="cordn_voice_stop">Stop and send</string>
<string name="cordn_voice_play">Play voice note</string>