121 Commits
Author SHA1 Message Date
Bot of Thomas Güttlerandguettli f1f7de7b4d feat(undo-log): hyperlink email rows in Undo Log Detail (#474) (#547)
## Summary

- Each email row in the **Undo Log Detail** "Emails" section is now tappable.
- Tapping resolves the email via `EmailRepository.findEmailByMessageId(accountId, messageId)` and navigates to its **current** location, so the link survives the move/snooze that changed its IMAP UID.
- If the email has no Message-ID, or no row matches the lookup (e.g. hard-deleted), a SnackBar explains the situation instead of navigating.

A `chevron_right` trailing icon was added to signal the rows are now navigable.

Closes #474

## Test plan

- [x] New widget test `test/widget/undo_log_detail_screen_test.dart` covers:
  - tap on a row whose lookup hits → navigates to `/accounts/<acc>/mailboxes/<encoded>/emails/<encoded>` with the **current** mailbox/id
  - tap when lookup returns `null` → "Email no longer exists" SnackBar, no navigation
  - tap when the original row has no Message-ID → "no Message-ID" SnackBar, no navigation

Co-authored-by: guettli <guettli@noreply.codeberg.org>
Reviewed-on: https://codeberg.org/guettli/sharedinbox/pulls/547
2026-06-10 13:15:48 +02:00
8ea5237991 fix(detail): auto-dismiss "Load remote images" snack bar (#548)
## Summary

- The "Load remote images" snack bar in single-mail view (and the analogous thread view) never disappeared on its own — the user had to interact with it.
- Flutter's `SnackBar` defaults to `persist: true` whenever an `action` is provided (see `flutter/lib/src/material/snack_bar.dart`: `persist = persist ?? action != null`), which short-circuits the duration-based dismiss timer in `ScaffoldMessengerState.build`:

  ```dart
  _snackBarTimer = Timer(snackBar.duration, () {
    if (snackBar.persist) return;          // <-- here
    hideCurrentSnackBar(reason: SnackBarClosedReason.timeout);
  });
  ```

  So the explicit `duration: 3s` was set, but the "View" action made the snack bar persistent and the timer's callback returned early.
- Pass `persist: false` explicitly on both snack bars so the 3-second timer fires and the snack bar slides away on its own, while the "View" action button still works to navigate to the trusted-senders settings.

## Test plan

- [x] Added widget regression test in `test/widget/email_detail_screen_test.dart` (`Load remote images snack bar auto-dismisses after 3 seconds`).
- [x] Added analogous test in `test/widget/thread_detail_screen_test.dart`.
- [x] `task test-widget` — all 174 widget tests pass.
- [x] `scripts/run_unit_tests.sh` — all 552 unit tests pass.
- [x] `fvm dart analyze --fatal-infos` on changed files — no issues.
- [x] `fvm dart format` — no diffs.
- [ ] Manual: open a single mail with HTML body from an untrusted sender; tap "Load remote images"; verify the snack bar appears, images load, and the snack bar disappears after ~3 seconds while the "View" action button still navigates to `/accounts/trusted-senders` when tapped.

Closes #484

Co-authored-by: Agentloop Bot <agentloop-bot@noreply.codeberg.org>
Reviewed-on: https://codeberg.org/guettli/sharedinbox/pulls/548
2026-06-08 21:59:49 +02:00
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
72f634dd90 fix(tests): remove stale search-toggle test and fix ink_sparkle shader crash
The 'tapping search icon shows search bar' test was stale: the SearchBar is
now permanently visible in AppBar.bottom, so both its assertions held before
any tap. Deleted it; the existing 'SearchBar is always visible in the AppBar'
test already covers the same intent.

Added NoSplash.splashFactory to the widget-test ThemeData to prevent Flutter
from loading the pre-compiled ink_sparkle.frag shader, which was built for an
older SDK version and caused an INVALID_ARGUMENT crash on Flutter 3.44.0.

Closes #486

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 18:19:10 +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
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
Thomas Güttler 59a9ed9109 Implement bug report uploading backend and Flutter client UI (#421) 2026-06-04 22:14:04 +02:00
4ef441ab1b ci: run non-golden widget tests in CI coverage (#416)
This PR includes widget tests (excluding golden tests) in the CI coverage run, ensuring widget layout and UI logic are tested automatically.

Co-authored-by: Thomas Güttler <thomas.guettler@syself.com>
Reviewed-on: https://codeberg.org/guettli/sharedinbox/pulls/416
2026-06-04 19:34:53 +02:00
Thomas SharedInbox 4a07a175b9 remove debug banner on screenshots. 2026-06-04 16:42:42 +02:00
Thomas SharedInbox a82927cae8 create screenshots. 2026-06-04 14:53:50 +02:00
Thomas SharedInbox 771ac691d9 misc. 2026-06-04 13:35:38 +02:00
Thomas Güttler 65ac023622 fix wiget tests. 2026-06-04 12:08:48 +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
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 91083218d4 fix: diff from last deployed SHA to catch all changes since last deploy (#320) (#332) 2026-05-29 17:34:21 +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 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 94b20f50be style: format edit_account_screen_test.dart
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 12:49:29 +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 5925cee4f2 fix: show git hash as clickable link above stacktrace (#201) (#211) 2026-05-24 12:56:27 +02:00
Bot of Thomas Güttler ac0e16adcb feat: about page - sharedinbox.de heading link and git commit row (#199) (#206) 2026-05-24 08:10:07 +02:00
Bot of Thomas Güttler 4f6f1d9437 fix: migrate to Riverpod 3.x and update dependencies (#175) (#190) 2026-05-23 19:50:11 +02:00