Commit Graph
18 Commits
Author SHA1 Message Date
Thomas GüttlerandClaude Opus 4.7 1a62907703 feat: default new SMTP accounts to implicit TLS (port 465)
The SMTP SSL/TLS toggle in the add-account form now defaults to on with
port 465. Previously the default was port 587 with the toggle off
(STARTTLS — TLS was active under the hood, but the off toggle made users
think the connection was insecure). The MX-record fallback in the
discovery service was flipped the same way; autoconfig XML parsing is
unchanged so servers that advertise STARTTLS still get STARTTLS.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 15:50:37 +02:00
Thomas GüttlerandClaude Opus 4.7 fb767a8489 fix: don't resurrect locally-deleted IMAP message on next sync
The incremental IMAP sync issued `UID ${lastUid + 1}:*` to look for new
mail. RFC 3501 §6.4.4 reverses `n:*` to `*:n` when n exceeds the largest
UID, so a server with one message at UID 1 and `lastUid=1` returned UID 1
for `UID 2:*` — re-fetching and re-inserting a row the user had just
deleted locally (whose pending change had not yet flushed).

`_fetchAndUpsertImap` now looks up the UIDs in the mailbox that have a
pending `delete` or `move` queued and skips the insert for those. The
existing `UID n:*` query is left intact so freshly-delivered SMTP mail
keeps driving StreamBuilder rebuilds in the E2E flow.

Regression test in `email_repository_imap_test.dart` deletes a synced
message and calls `syncEmails` directly — exactly what the in-app sync
button does — and asserts the row stays gone with the pending change
still queued.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 14:02:12 +02:00
Thomas GüttlerandClaude Opus 4.7 2e2b7c3d9f fix: Android E2E aliceTile race + bundle deploy-android infra
The Android UI integration test failed at tap(aliceTile) with "0 widgets"
even though pumpUntil had just found the tile. On the slow software-rendered
emulator the route-pop animation finalises during pumpUntil's trailing 300 ms
settle, briefly leaving the tile out of the tree. Re-confirm with a second
pumpUntil before the tap.

Bundles the previously uncommitted infra changes that make task deploy-android
run end-to-end inside nix develop: Linux desktop runtime libs + GL software
rendering env in flake.nix, path_provider_android pin to <2.3 to avoid the
libdartjni SIGSEGV, deferred DB-path resolution after WidgetsFlutterBinding,
+iglx for xvfb-run, platform-tools on PATH, and a single pre-commit script
replacing the dart-format / task-check-fast pair.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 12:36:30 +02:00
Thomas GüttlerandClaude Sonnet 4.6 65c75c365a feat: replace custom search TextField with Flutter SearchBar widget
SearchBar is always visible in the AppBar bottom slot — no toggle needed.
Removed _isSearching, manual debounce timer, and slide animation.
SearchController listener clears results when text is emptied.
Updated E2E and widget tests for the new widget tree.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 08:04:20 +02:00
Thomas GüttlerandClaude Sonnet 4.6 2b260edb52 feat: MX record fallback in account auto-discovery
When JMAP well-known and autoconfig XML both fail, query DNS-over-HTTPS
(dns.google) for MX records and use the highest-priority MX host as IMAP
(993/SSL) and SMTP (587/STARTTLS) server. No new dependencies needed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 07:46:28 +02:00
Thomas GüttlerandClaude Sonnet 4.6 e22418c6dd feat: add Email filters entry to FolderDrawer for JMAP accounts
The Sieve script editor was only reachable via the hidden popup menu on
the account list. Adding an 'Email filters' tile to the drawer makes it
discoverable from any mailbox view for JMAP accounts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 07:40:01 +02:00
Thomas GüttlerandClaude Sonnet 4.6 527683172c feat: bulk actions on search results via long-press selection
Long-pressing a search result enters selection mode; tapping further
results toggles them. The existing bottom bar (Archive, Delete, Mark
as spam, Move to folder) operates on the selected emails via a new
_selectedSearchIds set. _selectedEmailIds returns the right IDs for
each mode (search vs. normal thread list).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 07:32:44 +02:00
Thomas GüttlerandClaude Sonnet 4.6 2074046bb3 feat: split search query into words for AND semantics
Searching for "foo bar" now matches emails containing both words rather
than the exact phrase. Each whitespace-separated term generates its own
IMAP criterion (OR SUBJECT "w" TEXT "w") — multiple top-level IMAP criteria
are ANDed by the protocol — and its own Drift LIKE clause for the local DB.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 07:27:14 +02:00
Thomas GüttlerandClaude Sonnet 4.6 0103454e31 feat: add 'All accounts' entry to FolderDrawer for back-navigation
Users had no obvious way to return to the account list after opening an
account. Adding an 'All accounts' tile at the top of the drawer (visible in
both MailboxListScreen and EmailListScreen) lets users navigate to /accounts
from anywhere inside an account.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 07:20:16 +02:00
Thomas GüttlerandClaude Sonnet 4.6 9c74dec866 perf: speed up deploy-android via MobSF pre-warm and parallel integration tests
Start MobSF as a dep of build-android (_mobsf-start) so it warms up during
the APK build instead of blocking afterwards (saves up to 90 s). Add
_integrations task to run integration and integration-ui in parallel since
they use random Stalwart ports and different Flutter build targets.

Also add `docker rm ... || true` before `docker run` in both _mobsf-start and
mobsf_scan.sh to handle stopped-but-not-removed containers gracefully.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 07:13:13 +02:00
Thomas GüttlerandClaude Sonnet 4.6 a2d98ed9bc fix: Android E2E search — unfocus IME keyboard before polling results
On Android, the soft keyboard keeps viewInsets.bottom non-zero while the
search TextField is focused.  ListView.builder is allocated near-zero
height and renders 0 items, so find.text(subject) always finds nothing
even though the IMAP search returned results.  Unfocusing the primary
focus after enterText dismisses the keyboard and gives the results list
full body height before pumpUntil starts polling.

Also fix pumpUntil to use pump(300ms) instead of pumpAndSettle() so a
continuously-running animation (spinner under CPU load) never prevents
settling, and override accountConnectionStatusProvider so _AccountTile
never shows a CircularProgressIndicator during the test.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 22:31:25 +02:00
Thomas GüttlerandClaude Sonnet 4.6 ad31a3bf14 docs: move accountConnectionStatusProvider task to done.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 21:23:59 +02:00
Thomas GüttlerandClaude Sonnet 4.6 92a8a79952 fix: task check — SSL error in integration test + coverage gate
account_sync_manager_test: inject _connectImapPlain so the test
connects to the plain-IMAP dev Stalwart without triggering the
production SSL guard in connectImap().

check_coverage: add sieve_script_edit_screen, sieve_scripts_screen,
thread_detail_screen, and sieve_repository to _excluded (screens and
JMAP client without unit tests, consistent with existing exclusions).

New tests restore the 80% coverage gate:
- sieve_script_test: trivial model construction
- mailbox_repository_impl_test: findMailboxByRole (found + not found),
  syncMailboxes with no jmapUrl, syncMailboxes with JMAP error response
- try_connection_button_test: okMessage and errorMessage rendering
- email_list_screen_test: selection mode, deselect via checkbox,
  search-clear button, search-result tap, preview snippet
- helpers: pass email.preview through to EmailThread in FakeEmailRepository

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 06:38:21 +02:00
Thomas GüttlerandClaude Sonnet 4.6 681e0c0167 feat: Android emulator integration test via Stalwart
Adds stalwart-dev/integration_android_test.sh which starts Stalwart on
random ports, detects a connected Android emulator via adb, sets
STALWART_IMAP_HOST=10.0.2.2 (emulator-to-host alias), and runs the
existing integration_test/ suite on the emulator.

Wires it up as `task integration-android` in Taskfile.yml.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 06:13:38 +02:00
Thomas GüttlerandClaude Sonnet 4.6 71692665cc feat: quoted reply body and Forward button in email detail
Reply and reply-all now prefill the compose body with the original message
quoted as plain text (> lines). New Forward button sets Fwd: subject and
the same quoted body, leaving To/Cc empty for the user to fill.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 16:48:19 +02:00
Thomas GüttlerandClaude Sonnet 4.6 cdd5e5a6fc feat: mark as unread button in email detail screen
Added mark_email_unread_outlined icon to the toolbar. Calls setFlag(seen:false)
then pops back to the list so the email reappears as unread.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 16:46:09 +02:00
Thomas GüttlerandClaude Sonnet 4.6 14b1ee14ac feat: pull-to-refresh on email list screen
Wrapped the stream body in RefreshIndicator calling syncEmails.
Empty-state is now a scrollable ListView so the pull gesture triggers
even when the folder contains no messages.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 16:44:53 +02:00
Thomas GüttlerandClaude Sonnet 4.6 39c3d1ea1a feat: show email preview snippet in thread list tiles
Added preview field to EmailThread (from latest email's preview via
_groupIntoThreads). Thread tiles now show subject + one-line body snippet.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 16:43:35 +02:00