feat: long-press multi-select, aggressive prefetch, and download indicators

Add a long-press contextual action bar to the mailbox: Archive, Delete,
Spam, Move, Select All, and (single-selection only) Reply, Reply All, and
Forward. Reply All/Spam/Delete are confirmed first; deleting from Trash or
Spam warns that it is permanent.

- Delete moves to Trash and Spam moves to the Spam folder; only deletes
  already in Trash/Spam do a permanent IMAP expunge. Archive/Spam/Move
  resolve the destination per account so the unified inbox works.
- Reply/Reply All/Forward force a fresh server fetch of the original
  (recipients + body via BODY.PEEK), build a quoted draft, and open compose;
  a spinner shows during the fetch and they error via snackbar if offline.

Add a top-level "Message downloading" setting (Always fetch all / Fetch all
on Wi-Fi / Always on-demand; default Always) that aggressively pre-caches
full bodies and all attachment bytes during sync (outside the sync lock,
without marking mail read), into a persistent per-part attachment cache so
messages and attachments open instantly and offline.

Show an "available offline" mark on cached list rows and a per-attachment
"downloaded" check in the reader.

Extract a Syncer interface (MailSyncer implements it) so the mailbox can be
driven end-to-end in tests.

Tests: 66 unit + 27 instrumented, all passing on the API 37 emulator.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-30 17:20:22 -05:00
co-authored by Claude Opus 4.8
parent d090f721e6
commit 96111b40df
33 changed files with 2214 additions and 85 deletions
+32
View File
@@ -33,6 +33,29 @@
<string name="mailbox_welcome_title">Welcome to LibreMail</string>
<string name="mailbox_welcome_subtitle">Add an account to start reading your mail.</string>
<!-- Mailbox multi-select contextual action bar -->
<string name="cab_selected_count">%1$d selected</string>
<string name="cab_close">Clear selection</string>
<string name="action_archive">Archive</string>
<string name="action_delete">Delete</string>
<string name="action_spam">Spam</string>
<string name="action_move">Move</string>
<string name="action_reply">Reply</string>
<string name="action_reply_all">Reply all</string>
<string name="action_forward">Forward</string>
<string name="action_select_all">Select all</string>
<string name="action_more">More options</string>
<string name="move_picker_title">Move to</string>
<string name="cancel">Cancel</string>
<string name="confirm_spam_title">Move to Spam?</string>
<string name="confirm_spam_text">Move %1$d message(s) to the Spam folder?</string>
<string name="confirm_trash_title">Move to Trash?</string>
<string name="confirm_trash_text">Move %1$d message(s) to Trash?</string>
<string name="confirm_delete_title">Delete permanently?</string>
<string name="confirm_delete_text">This permanently deletes %1$d message(s) and can\'t be undone.</string>
<string name="confirm_reply_all_title">Reply to all?</string>
<string name="confirm_reply_all_text">Reply to everyone included on this message?</string>
<!-- Folder navigation drawer -->
<string name="drawer_open">Show folders</string>
<string name="drawer_switch_account">Switch account</string>
@@ -74,6 +97,8 @@
<!-- Reader -->
<string name="reader_reply">Reply</string>
<string name="attachments_title">Attachments</string>
<string name="attachment_downloaded">Downloaded</string>
<string name="message_available_offline">Available offline</string>
<string name="attachment_download_failed">Couldn\'t download %1$s</string>
<string name="attachment_no_app">No app can open this file</string>
@@ -105,6 +130,13 @@
<!-- Settings -->
<string name="settings_accounts">Accounts</string>
<string name="settings_downloading">Message downloading</string>
<string name="fetch_always">Always fetch all</string>
<string name="fetch_always_summary">Download full messages and attachments as they sync, on any connection.</string>
<string name="fetch_wifi">Fetch all on Wi-Fi</string>
<string name="fetch_wifi_summary">Download full messages and attachments only on Wi-Fi or other unmetered networks; otherwise fetch them when you open a message.</string>
<string name="fetch_on_demand">Always on-demand</string>
<string name="fetch_on_demand_summary">Sync only message headers; download a message\'s content when you open it.</string>
<string name="settings_notifications">Notifications</string>
<string name="settings_new_mail">New-mail notifications</string>
<string name="settings_new_mail_summary">Show a notification when new mail arrives</string>