Commit Graph
183 Commits
Author SHA1 Message Date
Bot of Thomas Güttlerandguettli 41c8196a97 feat(detail): drop AppBar subject, surface Mark as spam icon (#531)
## Summary
- Drop the truncated subject preview from the single-mail AppBar title; the full subject is already shown in the body header.
- Replace the popup-menu entry for **Mark as spam** with a direct `IconButton` (`Icons.report_outlined`) in the AppBar actions so the action is reachable without opening the `⋯` menu.
- Update affected widget tests for the new layout (subject is only in the body header; spam action is now a standalone button rather than a popup item).

Closes #528

## Test plan
- [x] `dart format --output=none --set-exit-if-changed lib test` — 0 changed
- [x] `dart analyze --fatal-infos lib test` — no issues
- [x] `flutter test test/widget/email_detail_screen_test.dart test/widget/email_list_screen_test.dart` — 42/42 passing
- [x] Full widget suite (`flutter test test/widget/`) — 172/172 passing

Reviewed-on: https://codeberg.org/guettli/sharedinbox/pulls/531
2026-06-07 20:05:57 +02:00
Bot of Thomas Güttler 69606ce586 fix: prevent Enter key from re-running a settled search (#479) 2026-06-07 04:38:30 +02:00
Bot of Thomas Güttler 9081b452f3 feat: add structured search with visual filter builder (#469) 2026-06-07 04:38:28 +02:00
Bot of Thomas Güttler b9ccafc709 feat: allow manual entry of glob patterns for trusted image senders (#480) 2026-06-07 04:38:22 +02:00
Bot of Thomas Güttler e2bb299300 fix(ci): exclude chaos_monkey_test from regular CI (#518) 2026-06-07 04:24:10 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 e22322166c feat: linkify #NNN references in ChangeLog to Codeberg issues
Closes #472

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 21:51:13 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 65173d323c feat: switch folder-view search from IMAP to local SQLite FTS5
Closes #501

searchEmails now queries the local email_fts virtual table filtered by
mailbox_path instead of doing a live IMAP SEARCH. This makes folder-view
search work offline and ensures tapped results always open the correct
email (IDs come from the same local DB that getEmail reads from).

Reuses the existing FTS5 infrastructure (_toFtsQuery + the email_fts
content-table join) from searchEmailsGlobal, adding only the
`AND e.mailbox_path = ?` filter.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 20:43:53 +02:00
Bot of Thomas Güttler 4712e768ea fix: prevent Enter key from re-running a settled search (#473) 2026-06-06 18:02:50 +02:00
Bot of Thomas Güttler 7985caa9b4 fix: discard stale search results when a newer query supersedes them (#468) 2026-06-06 10:32:37 +02:00
Bot of Thomas Güttler e28996cf86 feat: track installed versions and annotate ChangeLog with install dates (#457) 2026-06-06 10:31:06 +02:00
d86ce7766c feat: add undo log detail view (#461)
## Summary

- Tapping a row in the Undo Log list opens a new `UndoLogDetailScreen`
- Detail screen shows: account ID, action type (with icon/colour), timestamp, source folder, destination folder (move only), and a list of all emails in the transaction (subject + sender)
- Navigation uses go_router nested route `/accounts/undo-log/:actionId` with `state.extra` to pass the `UndoAction` object
- AppBar has an **Undo** button that calls the existing undo service and pops back

## Also fixed

- `flake.nix`: replaced the broken dagger/nix 0.20.8 Nix wrapper (infinite self-exec loop) with a direct 0.21.4 `fetchurl` derivation; wired `DAGGER_HOST` so the pre-commit `dart-check` hook can reach the running engine
- `pubspec.lock`: bumped `meta` 1.17→1.18 and `test` 1.30→1.31 to match what the CI resolver picks up (eliminates spurious generated-files drift in CI)

## Verification

- `task test` — all 492 unit/widget tests pass
- `dart analyze --fatal-infos` — clean (no warnings or infos)
- Pre-commit hooks (including `dart-check` via Dagger) — all passed on commit

Closes #450

Co-authored-by: Thomas SharedInbox <sharedinbox@thomas-guettler.de>
Reviewed-on: https://codeberg.org/guettli/sharedinbox/pulls/461
2026-06-06 05:43:17 +02:00
Bot of Thomas Güttler 8446b05601 feat: add per-email notes stored on IMAP/JMAP server (#443) 2026-06-05 19:31:35 +02:00
Bot of Thomas Güttler bcece9f0af refactor: unify mail display with shared ThreadTile widget (#445) 2026-06-05 19:06:29 +02:00
Bot of Thomas Güttler 3bd404f0cf feat: add 'Create new folder' option to Move To Folder dialog (#423) 2026-06-05 18:53:36 +02:00
Bot of Thomas Güttler adef2e9f80 feat: unify thread list views via shared EmailThreadTile widget (#431) 2026-06-05 18:11:28 +02:00
Bot of Thomas Güttler 2788a43dda feat: dedicated page for allowed image-sender addresses (#420) 2026-06-05 17:53:48 +02:00
Thomas Güttler 59a9ed9109 Implement bug report uploading backend and Flutter client UI (#421) 2026-06-04 22:14:04 +02:00
cd8c930000 fix: use Builder to get descendant context for Scaffold.of() in bottom nav (#403)
## Summary

Fixes the crash reported in #397: `Scaffold.of() called with a context that does not contain a Scaffold.`

- `Scaffold.of(context)` was called in the `onPressed` of the bottom-nav menu `IconButton` using the widget's own `build` context. That context is the *parent* of the `Scaffold` being returned, so Flutter correctly throws.
- Fix: wrap the `IconButton` in a `Builder`, which provides a child `ctx` that is a proper descendant of the `Scaffold`. `Scaffold.of(ctx)` then resolves correctly.

## Test plan

- [ ] Run app with bottom menu position enabled, tap the hamburger icon — drawer opens without crashing.
- [ ] Run app with top menu position — no regression (bottom nav is not rendered).

Closes #397

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Thomas SharedInbox <sharedinbox@thomas-guettler.de>
Reviewed-on: https://codeberg.org/guettli/sharedinbox/pulls/403
2026-06-04 06:16:24 +02:00
b0354c7423 fix: remove delete confirmation dialog from thread view (#402)
## Summary

- Removes the `AlertDialog` popup that appeared when tapping delete in thread view
- Deletion now happens immediately, matching the behaviour of the single mail view
- The existing `UndoShell` widget already listens for new `UndoAction` pushes and shows a snack bar with an **Undo** button — no extra UI code needed

Closes #398

Co-authored-by: Thomas SharedInbox <sharedinbox@thomas-guettler.de>
Reviewed-on: https://codeberg.org/guettli/sharedinbox/pulls/402
2026-06-04 06:15:55 +02:00
582f6764eb fix: snack bar now auto-dismisses after delete in mail detail view (#401)
## Summary

- When deleting a mail from the single Mail View, \`pushAction()\` was called with \`unawaited\` before \`_navigateTo()\`. This meant the UndoShell snack bar fired *after* navigation had already started, showing the snack bar on the destination scaffold mid-transition — which prevented the snack bar's duration timer from starting correctly.
- Fixed by changing \`unawaited(pushAction(...))\` to \`await pushAction(...)\`. Since Riverpod fires \`ref.listen\` synchronously when state changes, the UndoShell now queues the snack bar on the current stable scaffold *before* \`_navigateTo()\` is called. The snack bar then naturally transfers to the destination scaffold and auto-dismisses after 5 seconds as intended.

Closes #399

## Test plan

- [x] All 338 unit/widget tests pass
- [ ] Manually delete a mail from single Mail View and verify the snack bar appears and auto-dismisses after ~5 seconds
- [ ] Verify the Undo button in the snack bar still works

Co-authored-by: Thomas SharedInbox <sharedinbox@thomas-guettler.de>
Reviewed-on: https://codeberg.org/guettli/sharedinbox/pulls/401
2026-06-04 06:15:37 +02:00
674d402ff9 feat: pre-fetch email bodies for offline access (#400)
Closes #373

## Summary

- **Schema v38**: two new columns on `user_preferences` — `prefetch_mode` (default `wifiOnly`) and `body_cache_limit_mb` (default 100 MB).
- **`BodyCacheService`**: queries for emails that have no cached body, fetches them newest-first in batches of 20, and evicts the oldest cached bodies when the configured size limit is exceeded.
- **Separate WorkManager task** (`si_bg_prefetch`): runs hourly with `NetworkType.unmetered` (Wi-Fi) or `NetworkType.connected` (any) depending on the user's choice. The task is cancelled when prefetch is disabled.
- **App startup**: reads the stored preference from the DB and re-registers the WorkManager task with the correct constraint.
- **Preferences screen**: radio group for prefetch mode (Wi-Fi only / Any network / Disabled) and a dropdown for cache size limit (50 / 100 / 200 / 500 MB).

## What is NOT downloaded

Binary attachments are never fetched — `getEmailBody()` stores only `textBody` and `htmlBody`. The cache size limit + per-run batch cap (20 emails) keep storage bounded even on large mailboxes.

## Test plan

- [x] `task analyze` — no issues
- [x] `task test` — all 492 tests pass (incl. updated migration_test.dart for v38)

Co-authored-by: Thomas SharedInbox <sharedinbox@thomas-guettler.de>
Reviewed-on: https://codeberg.org/guettli/sharedinbox/pulls/400
2026-06-04 06:15:00 +02:00
Bot of Thomas Güttler c1d314a621 feat: combined inbox as the default startup view (#376) (#379) 2026-06-04 02:46:59 +02:00
692fa14d4d feat: remember show images per sender (#378)
## Summary

Closes #377

- Adds a new `ImageTrustedSenders` Drift table (schema v37) that stores email addresses for which remote images are loaded automatically (per device, not per account)
- When the user taps "Load remote images", the sender's address is saved and a 3-second snackbar appears with a "Settings" hyperlink to undo the choice in preferences
- Both `EmailDetailScreen` and `ThreadDetailScreen` check the trusted senders list on open and auto-load images for known senders
- The Preferences screen gains a new "Trusted image senders" section listing all saved senders with individual remove buttons

## Test plan

- [x] `dart run build_runner build` regenerates `database.g.dart` cleanly (schema v37)
- [x] `flutter analyze` — no issues
- [x] Migration test updated: checks `image_trusted_senders` table exists after upgrade and fresh install
- [x] `FakeUserPreferencesRepository` updated with three new interface methods
- [x] All 490 unit + widget tests pass (1 pre-existing golden test failure unrelated to this change)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Thomas SharedInbox <sharedinbox@thomas-guettler.de>
Reviewed-on: https://codeberg.org/guettli/sharedinbox/pulls/378
2026-06-04 01:41:50 +02:00
87244de7da feat: group email headers in full-screen dialog (#374)
Closes #372

## What changed

- **New widget** `lib/ui/widgets/email_headers_dialog.dart`: full-screen header browser that organises headers into collapsible groups:
  - **Headers** — all standard headers (expanded by default)
  - **List- Headers** — all `List-*` headers grouped together (expanded)
  - **Received** — all `Received` headers, **collapsed by default**; shows the inter-hop duration between consecutive entries and highlights delays in colour (green < 30 s, orange < 5 min, red >= 5 min)
  - **ARC- Headers** — all `ARC-*` headers (above X-, expanded)
  - **X-Prefix Headers** — X- headers split by their second component (e.g. `X-Google-*` → "X-Google Headers"), sorted alphabetically, at the very bottom

- **`email_detail_screen.dart`**: `_showHeaders` now uses `EmailHeadersDialog`; `_showStructure` converted from `AlertDialog` to `Dialog.fullscreen()` — satisfying "Make popup windows full screen."

- **`scripts/check_coverage.dart`**: new widget file added to the `_excluded` set (UI widgets are covered by integration tests, not unit tests).

## Verified

`task check` passes (analyze: no issues, 491 unit tests pass, coverage >= 80 %).

Co-authored-by: Thomas SharedInbox <sharedinbox@thomas-guettler.de>
Reviewed-on: https://codeberg.org/guettli/sharedinbox/pulls/374
2026-06-03 22:14:14 +02:00
Thomas Güttler 8ea8d71f42 fix: format, analyze-fix and update mocks 2026-06-02 17:10:16 +02:00
Thomas Güttler 1e2d1b6063 chore: migrate to SOPS and SSH for Dagger engine access 2026-06-02 11:10:29 +02:00
Bot of Thomas Güttler 264ce7e349 fix: guard against empty IMAP fetch message list (#346) 2026-06-01 21:48:21 +02:00
Bot of Thomas Güttler b3f5ad4110 fix: add try-catch to _measureHeight() in secure_email_webview.dart (#345) 2026-06-01 21:47:53 +02:00
Bot of Thomas Güttler 05d00bdf09 fix: move overflow actions into popup menu so three-dot menu is always visible (#312) (#323) 2026-05-28 07:19:11 +02:00
Bot of Thomas Güttler c45775be92 fix: move sync health report to own row below each account (#311) (#322) 2026-05-28 06:53:11 +02:00
Bot of Thomas Güttler f0f210e5ab feat: configurable next action after single mail view (#300) (#308) 2026-05-27 23:33:14 +02:00
Bot of Thomas Güttler 41550eb4b5 feat: configurable menu bar position for mailbox view (#298) (#303) 2026-05-27 22:07:12 +02:00
Bot of Thomas Güttler 633fc5d9da fix: show full discrepancy details in account list (#296) (#301) 2026-05-27 21:20:19 +02:00
Bot of Thomas Güttler 14f64cd2a5 feat: show URL tooltip on long-press of unsubscribe chip (#294) (#295) 2026-05-27 21:02:30 +02:00
Bot of Thomas Güttler e2b08e07b7 fix: prevent HTML email content from being cut off (#288) (#292) 2026-05-27 19:52:14 +02:00
Bot of Thomas Güttler c0dd13be5d feat: align single and multi-mail actions, add archive (#287) (#291) 2026-05-27 19:36:13 +02:00
Bot of Thomas Güttler 4e32984ecc fix: prompt to create or pick folder when archive is missing (#286) (#290) 2026-05-27 19:06:37 +02:00
Bot of Thomas Güttler f57a8c502d feat: syncLog add Copy button, stack trace, isPermanent (#266) (#269) 2026-05-26 07:55:07 +02:00
Bot of Thomas Güttler 8709e9f38d feat: add Locale, Text Scale, DB Schema Version, Device Model to About page (#258) (#263) 2026-05-25 22:18:09 +02:00
Bot of Thomas Güttler 7997ff0980 feat: Reply All dialog on Reply button, add Mark as Spam (#260) (#261) 2026-05-25 21:51:08 +02:00
Bot of Thomas Güttler a7783d46cf fix: disable Save button when no password available; fix changelog fetch-depth (#246, #229) (#248) 2026-05-25 14:47:25 +02:00
Bot of Thomas Güttler 3868c160d3 fix: disable Try connection button when no password is available (#235) (#247) 2026-05-25 14:30:13 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 885906b204 fix: show password required error instead of crashing when no stored password (#235)
During _load(), check whether a password exists in secure storage and track the result
in _hasStoredPassword. The password field validator now requires user input when no
password is stored, so _tryConnection() fails fast at form validation instead of
throwing an unhandled StateError.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 12:49:29 +02:00
Bot of Thomas Güttler e03c7708ba feat: show app version as link on crash screen and in MD report (#236) (#245) 2026-05-25 11:40:53 +02:00
Bot of Thomas Güttler 09c90c244b fix: load changelog via DefaultAssetBundle for testability (#214) (#225) 2026-05-24 17:50:10 +02:00
Bot of Thomas Güttler 357ed9af31 fix: about page version unknown and link crash on Android (#213) (#224) 2026-05-24 17:20:09 +02:00
Bot of Thomas Güttler e7ff9243c9 feat: add build mode, Dart version, timestamp to crash report (#205) (#222) 2026-05-24 16:10:09 +02:00
Bot of Thomas Güttler d51e67ddcc fix: probe scanner method channel to detect MissingPluginException (#204) (#221) 2026-05-24 15:55:08 +02:00
Bot of Thomas Güttler 43068509d2 fix: show live countdown with seconds on receive account screen (#203) (#220) 2026-05-24 15:15:12 +02:00
Bot of Thomas Güttler 50ae7df8a3 fix: fall back to text input when mobile_scanner plugin is unavailable (#202) (#219) 2026-05-24 14:55:07 +02:00