Skip to content

fix(chat): Mark voice message as failed when its upload fails - #2740

Open
ToteMeiSter wants to merge 2 commits into
nextcloud:mainfrom
ToteMeiSter:fix/voice-message-stuck-sending
Open

ToteMeiSter wants to merge 2 commits into
nextcloud:mainfrom
ToteMeiSter:fix/voice-message-stuck-sending

Conversation

@ToteMeiSter

Copy link
Copy Markdown
Contributor

When uploading a voice message fails, the message stays in the "sending" state. It can neither be resent nor
deleted until it is marked as failed after 12 hours. This PR marks it as failed right away, so the existing
"Resend" and "Delete" actions are available.

No open issue covers this. Related, closed: #741 (missing error message for a failed voice message upload),
#827 (upload error message).

Root cause

  • BaseChatViewController.shareVoiceMessage() creates a temporary message (isTemporary = true, stored in Realm)
    and calls upload(_:) with its referenceId.
  • When ChatFileUploader.upload(_:) throws, upload(_:) only calls presentUploadError(_:for:).
    The temporary message is neither marked as failed nor removed.
  • "Resend" and "Delete" are only offered for sendingFailed || isOfflineMessage
    (ChatViewController context menu; BaseChatViewController.didPressDelete(for:)). isOfflineMessage is never set
    for voice messages.
  • The only way out is NCChatController.getTemporaryMessages(), which marks temporary messages older than 12 hours
    as failed when the chat is opened.
  • For .uploadFailed (including network errors such as -1005/-1009) the alert says "Unknown error occurred",
    because presentUploadError falls through to default.

Changes

Two commits, BaseChatViewController.swift only (+ a unit test):

  1. fix(chat): Mark voice message as failed when its upload fails
    • In the catch of upload(_:), a new markTemporaryMessageAsFailed(referenceId:) sets sendingFailed = true
      and isOfflineMessage = false:
      • on the stored temporary message (referenceId = %@ AND isTemporary = true), the same way
        NCChatController.sendChatMessage does it after failed retries;
      • on the message shown in the open chat, via the existing modifyMessageWith(referenceId:). Messages that are
        no longer temporary are left untouched.
    • Uploads without a referenceId (contacts) are not affected.
    • didPressResend(for:), voice branch: the message is reset to sending (sendingFailed = false,
      isOfflineMessage = false) and stored again before the new upload. Before, it removed the stored message and
      re-appended the same object, still flagged as failed: the cell showed "failed" during the new upload,
      "Resend" stayed available (a second tap would send the voice message twice), and a second failure was lost
      after reopening the chat. This path was only reachable after 12 hours so far; with this fix it is the main one.
  2. fix(chat): Show the reason when uploading a voice message fails
    • For .uploadFailed with a non-empty description, the alert shows that description instead of
      "Unknown error occurred". This is what ShareConfirmationViewController.message(for:) already does.
      No new strings. The second commit can be dropped if this is not wanted.

Behaviour per error

Error Before After
.uploadFailed (network error, other HTTP status) alert "Unknown error occurred", message stuck in "sending" alert with the error description, message failed
.quotaExceeded (507), .tooManyRequests (429) alert, message stuck same alert, message failed
.attachmentFolderUnavailable alert, message stuck same alert, message failed
.destinationUnavailable no alert (intentional), message stuck still no alert, message failed
.shareFailed alert "Failed to share recording", message stuck same alert, message failed

.shareFailed means the file is already on the server, but posting it into the conversation failed. The message
is still marked as failed:

  • Without it, the message stays stuck for 12 hours and is then marked as failed anyway, with the same consequences.
  • "Resend" uploads the file again under a new name: one more copy in the user's storage, still one chat message.
  • If posting did succeed on the server and only the response was lost, the real message still arrives with the same
    referenceId and replaces the temporary one (NCChatController.storeMessages,
    ChatViewController.appendReceivedMessagesAndComputeTableViewUpdate), regardless of sendingFailed.
    A duplicate is only possible if the user presses "Resend" before that.

Not changed

  • NCChatController.send(_:) also uploads voice messages and only logs a failure. It is only reached from
    NCRoomsManager.resendOfflineMessages, which selects isOfflineMessage = true. That flag is never set for voice
    messages, so this path is not changed.
  • No automatic retry of the upload, no background session.
  • If the app is killed during the upload, the message still stays "sending" until the 12-hour rule marks it failed.
  • A resent message keeps its original timestamp. If it is older than 12 hours and the chat is reopened while the
    new upload is still running, getTemporaryMessages() marks it failed early (existing rule, not changed).

Overlap with #2705

#2705 (draft) changes BaseChatViewController.swift, but not didPressResend(for:), upload(_:) or
presentUploadError(_:for:). Its nearest hunk is didPressDelete(for:), about 100 lines below didPressResend.
A textual conflict is not expected; if #2705 is merged first, a rebase should apply cleanly.
#2705 also changes the context menu Delete condition for grouped files. Single voice messages keep
sendingFailed || isOfflineMessage, so this fix still enables "Delete" for them.

How to test

Not built or run locally (no Xcode available). Build and tests: CI only.

Unit tests in UnitBaseChatViewControllerTest (database part only; the open-chat part needs a loaded table):

  • testMarkTemporaryMessageAsFailed — a temporary message is marked as failed, a received message with the same
    referenceId is not;
  • testMarkTemporaryMessageAsFailedIgnoresReceivedMessage — only a received message exists, it stays unchanged.

Manual, on a device (expected result, not verified):

  1. Open a conversation, start recording a voice message.
  2. Turn on airplane mode, then release to send.
  3. Expected: alert "Upload failed" with the network error text; the message shows as failed, not as sending.
  4. Long-press the message: "Resend" and "Delete" are available.
  5. Turn airplane mode off, press "Resend": the message shows as sending, then is sent. Or press "Delete": the
    message is removed.
  6. Repeat step 5 with airplane mode still on: the message shows as sending, then as failed again; reopening the
    chat still shows it as failed.
  7. Optional: reopen the chat after step 3 — the message is still shown as failed.

SwiftLint (swiftlint lint --quiet, 0.65.1): no new warnings or errors.

AI disclosure

This change was prepared with AI assistance (Claude Code, model claude-opus-5-5): code reading, the fix, the test
and this description. Commits carry Assisted-by: Claude Code:claude-opus-5-5.
The change was reviewed by the author. It was not built or tested on a device (no Xcode available).

🤖 Generated with Claude Code

When uploading a voice message failed, only an alert was shown. The
temporary message stayed in the "sending" state, and as "Resend" and
"Delete" are only offered for failed messages, it could neither be
resent nor removed until it was marked as failed after 12 hours.

Mark the temporary message as failed right away, in the database and
in the open chat, so the existing "Resend" and "Delete" actions work.

When resending, show and store the voice message as sending again.
Before, it was shown as failed during the new upload and was removed
from the database, so a second failure could not be kept.

Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
Assisted-by: Claude Code:claude-opus-5-5
A failed upload of a voice message showed "Unknown error occurred".
Show the description of the upload error instead, as the share view
already does for other files.

Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
Assisted-by: Claude Code:claude-opus-5-5
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Errormessage missing when voicemessage upload fails

1 participant