Bring rich composition to LibreMail (issues #36, #37, #38, and #23).
#36 HTML editor + toolbar
- New pure, JVM-testable rich-text model (`richtext/`): RichTextContent with
inline styles + links and block markers ("• ", "N. ", "> "), serializing to a
narrow email-safe HTML subset and back (fromHtml is a faithful inverse).
- Rich editor in ComposeScreen with a bold/italic/underline, bulleted/numbered
list, block-quote, and link toolbar, backed by AnnotatedString. Unformatted
text stays plaintext-only (null HTML) so it feels unchanged and is accessible.
- #23: rounded corners on the compose fields/body via MaterialTheme.shapes.
#37 multipart/alternative + reply/forward quoting
- SmtpSender builds multipart/alternative (text/plain + text/html), nested in
multipart/mixed when there are attachments; GraphSender sends HTML content.
- HtmlToText produces a readable text/plain fallback; ReplyBuilder quotes HTML
originals as clean blockquotes (tags stripped) without corruption.
- HTML body persists/restores through drafts and the outbox (new nullable
bodyHtml columns; Room v9->v10 migration + schema).
#38 signatures
- New signatures table (multiple per account, one default) + repository/DAO;
migration backfills the existing per-account signature as the default.
- Rich signatures reuse the #36 editor; a Signatures management screen (list,
add/edit/delete, set default) is linked from per-account settings.
- The account's default signature auto-inserts on new compose / reply / forward
(honoring the enable toggle), placed above the quote, and stays editable.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a per-account settings area (reached by tapping an account in
Settings), starting with signatures and notifications.
- Storage: new Room `account_settings` table (schema v9, MIGRATION_8_9)
with a cascading foreign key to `accounts`; AccountSettings model and
AccountSettingsRepository (a missing row resolves to defaults).
- Signatures: plain-text per-account signature (RFC 3676 "-- "
delimiter) auto-inserted below new messages and reply/forward drafts,
and swapped when the From-account changes.
- Notifications: one notification channel + channel group per account so
Android manages sound/vibration/importance (deep-linked from the app)
and the shade bundles per account, plus an in-app per-account on/off
gate. minSdk 29 >= API 26, so channels are always available (no
pre-channel fallback needed).
- UI: per-account settings screen (signature field/toggle, notification
toggle, system deep-link, remove account); shared settings components.
Verified: JVM unit tests, lintVitalRelease (NewApi), and the full
instrumented suite (30/30) on the API 37 emulator.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Folder view: a left navigation drawer lists each account's IMAP folders;
tapping one browses and caches that folder's mail. IMAP UIDs are unique only
within a folder, so message identity, the fetch/read/flag/delete paths, sync,
and the Room cache all became folder-aware (id = "accountId:folder:uid"; new
`folder` column; schema v7->v8). Standard folders (Inbox/Sent/Drafts/Spam/Trash/
Archive) surface with friendly names + icons via RFC 6154 SPECIAL-USE attributes
with a case-insensitive name fallback; the multi-account drawer adds an account
switcher and a unified "All Inboxes". INBOX stays the only auto-synced,
IDLE-watched, notifying folder; other folders sync on demand.
Lower minSdk 33 -> 29 for a rolling ~7-year Android support window; guard the
API-33 POST_NOTIFICATIONS runtime request accordingly.
Tests and CI:
- Bump espresso-core 3.6.1 -> 3.7.0 so Compose UI tests run on API 37
(3.6.1's InputManagerEventInjectionStrategy reflects a removed hidden method).
- New coverage across layers: FolderRoleTest, ImapClientTest folder cases,
MailboxViewModelTest, MailRepositoryImplTest folder routing, a FolderDrawer
Compose UI test, and LibreMailDatabaseTest folder DAO/reconcile tests.
- Gradle Managed Devices + a CI E2E matrix over every API 29-36; a single
"CI passed" gate job fans in all jobs and is required by branch protection.
- Non-blocking, custom-provisioned API 37 (preview) E2E job with image caching.
- Build + unit-test jobs run on arm64 (ubuntu-24.04-arm); emulators stay x86_64.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Addresses findings from a full-repo review across the mail, sync, persistence,
auth, and UI layers.
Send / outbox:
- Stop the Graph->SMTP fallback from duplicating a message when a Graph send may
already have been accepted; leave indeterminate sends queued for the user.
- Parse RFC822 display-name recipients on the Graph path.
- Preserve attachment order (staged in indexed subdirectories).
Data safety (schema v7):
- Disable cloud/device backup of the Keystore-encrypted credential DB.
- Add the missing v1->v2 migration and drop the destructive migration fallback.
- Normalize the messages.isHtml default and add an attachments->messages
ON DELETE CASCADE foreign key (no more orphaned attachment rows).
Concurrency:
- Serialize syncing and per-account OAuth token refresh; cache tokens by expiry.
- Synchronize Android Keystore key creation.
- Make a sync's persist+notify non-cancellable so an IDLE renewal can't drop it.
Notifications / UI:
- Per-message notifications under a group + summary instead of one overwriting id.
- Wire the "load remote images" and "allow STARTTLS" settings.
- Harden the reader WebView (scheme allowlist + user gesture; no reload on
recomposition); refresh headers without reverting optimistic read/star flags.
- Persist draft attachments; keep server-search hits out of the inbox; encode
the reader navigation argument.
Build / test:
- Export Room schemas; support a real release keystore; add unit/db tests.
- Remove dead Gmail account-setup code left after the sign-in removal.
Verified: debug + release (R8) + androidTest compile; unit tests pass;
MIGRATION_6_7 matches the generated v7 schema.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>