Commit Graph
96 Commits
Author SHA1 Message Date
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
Bot of Thomas Güttler a6c231f293 feat: show git commit link on crash screen (#150) (#170) 2026-05-23 13:45:08 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 f7d021c62a fix: survive MissingPluginException on startup, fix crash report URL (#146)
Two fixes:

1. notification_service.dart: initNotifications() now catches
   MissingPluginException (and any other init failure) so the app no
   longer crashes when flutter_local_notifications is unavailable on
   some Android devices.  _initialized tracks success; showNewMailNotification
   skips the plugin call when it never initialised.

2. crash_screen.dart: "Report Issue on Codeberg" no longer puts the full
   report in the URL query string.  Long stack traces exceeded browser
   URL-length limits and caused "create issue failed".  The URL now
   carries only the pre-filled title; the user copies the full report
   via "Copy to Clipboard" and pastes it in the issue body.

Tests added:
- test/unit/notification_service_test.dart: verifies initNotifications()
  completes without throwing when the plugin channel is unavailable.
- test/widget/crash_screen_test.dart: verifies the Codeberg URL contains
  the title but no &body= parameter.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 13:01:34 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 23cbe4611c fix: resolve startup crash and CrashScreen button crashes (#127)
Two bugs caused the crash-at-startup report:

1. CrashScreen used the widget's build context (above its own MaterialApp)
   for ScaffoldMessenger.of() in button callbacks. When the screen is the
   root widget — the runApp() path after a startup crash — there is no
   ScaffoldMessenger above it, so both 'Copy to Clipboard' and 'Report Issue
   on Codeberg' crashed with a null check error. Fix: wrap Scaffold.body in
   Builder to obtain a context that is a descendant of the Scaffold.

2. path_provider_android 2.2.21 updated to Pigeon 26, which causes a
   channel-error on startup for some Android devices. Pin to <2.2.21
   (resolves to 2.2.20, which uses the stable pre-Pigeon-26 implementation).
   Additionally, make initDatabasePath() catch PlatformException so a
   channel error at the very start of main() no longer hard-crashes the app;
   _openConnection()'s lazy fallback retries after runApp() completes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 11:16:09 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 517f799b99 feat: apply local Sieve rules after sync (#119)
- Add LocalSieveApplied table (schema v32) keyed by (accountId, messageId)
  so each email is processed by Sieve at most once, even across restarts.
- Implement EmailRepository.applySieveRules(): loads the active local Sieve
  script, runs the interpreter against new INBOX emails, and queues pending
  move/delete/flag_seen changes for any matched rules.
- Wire applySieveRules() into both _AccountSync._sync() and
  _JmapAccountSync._sync() after the per-mailbox email sync loop.
- Make _flushPendingChangesImap() treat NONEXISTENT / not-found errors as
  silent no-ops (counts as flushed) so a second device racing on the same
  email does not accumulate retries.
- Add migration test assertions and a dedicated unit test suite covering
  rule matching, deduplication, discard, and multi-email processing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 10:34:21 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 de66081813 feat: save raw email to temp dir and add Share action to SnackBar (#115)
Save the .eml file to the temporary directory (reliable on all
platforms) and display a Share action in the SnackBar so users can
send the file to any app — including the Files app — which properly
registers it with Android's MediaStore and makes it visible in the
recently-used list.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 17:57:31 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 e327b42312 fix: close Raw Email dialog automatically after download (#114)
After a successful download, Navigator.pop is called so the dialog
dismisses without requiring a manual close. Adds a widget test that
verifies this using a fake PathProviderPlatform and IOOverrides so the
entire async chain runs as pure microtasks inside the Flutter test zone.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 13:32:22 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 651110b389 fix: do not show snackbar for stale undo actions on startup (#113)
Actions persisted to the database triggered a snackbar when the app
restarted. Added a 30-second recency check so only actions created in
the current session show the snackbar; added widget tests covering both
cases.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 09:23:49 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 aeb4c5ab41 feat: improve About screen with labeled versions, dark mode, account counts, and bottom buttons (#111)
- Rename "Version" to "App Version"; rename "OS Version" to platform-prefixed label (e.g. "Android Version")
- Link app version to its Codeberg git commit (via GIT_HASH dart-define)
- Add "Dark Mode" yes/no row
- Add IMAP Accounts and JMAP Accounts rows
- Move copy/create-issue actions from AppBar icons to labeled buttons below the table
- Pass GIT_HASH dart-define in Taskfile APK/AAB build commands

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 08:03:13 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 dc8c1cb08d feat: introduce Local Filters / Remote Filters terminology (#109)
- Rename 'Local email filters' → 'Local Filters' and 'Server email
  filters' → 'Remote Filters' in AppBar titles
- Update banner text on each filter page to focus on the current type
  and mention that the other type exists separately
- Add 'Remote Filters' and 'Local Filters' as two distinct drawer
  entries so both types are discoverable from the navigation
- Add widget tests verifying titles and banner text for both pages

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 01:49:11 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 67880929bc feat: rename SharedInbox to sharedinbox.de in UI and website (#108)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 01:33:13 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 04e65d2fba feat: secure account sharing via public-key encryption (#107)
Replace the insecure plaintext QR export/import flow with an
end-to-end-encrypted account-transfer mechanism:

- Receiver generates an ephemeral X25519 key pair (20-minute lifetime,
  stored in the new share_keys DB table at schema v31) and displays it
  as a QR code (sharedinbox.de:pubkey:v1:…).
- Sender scans the public-key QR, selects accounts (or auto-selects
  when only one exists), encrypts them with ECIES (X25519-ECDH +
  HKDF-SHA256 + AES-256-GCM) and displays an encrypted QR
  (sharedinbox.de:encrypted-accounts:v1:…).
- Receiver scans the encrypted QR, decrypts, verifies the 20-minute
  expiry and MAC authentication tag, then imports the accounts.

New screens: AccountReceiveScreen (/accounts/receive) and
AccountSendScreen (/accounts/send), accessible from the account-list
drawer and per-account popup menu respectively.

Remove the old insecure AccountExportScreen and AccountImportScreen.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 01:19:01 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 7fa19dd39a feat(about): add About page with device/app info and issue reporting (#106)
Shows version, platform, OS version, screen resolution, Dart version, and
processor count in a markdown table. Buttons let users copy the info to
clipboard or open a pre-filled Codeberg issue.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 23:50:55 +02:00
Thomas SharedInbox 1fd37cc966 feat(account-menu): move force full sync button from edit screen to account menu (#99)
Add "Force full sync" popup menu item below "Verify sync health" in the
per-account menu on the account list screen, with a confirmation dialog.
Remove the button and handler from the edit account screen.
2026-05-15 21:29:43 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 a38691a760 fix(html-mail): force light color-scheme to prevent black-on-black in dark mode (#98)
HTML emails with black text became unreadable when viewed in dark mode
because the WebView inherited a dark background from the system theme.
Inject `color-scheme: light` CSS + meta tag so the WebView always renders
email content on a white background, regardless of the device theme.

Extracts `buildEmailHtml()` as a `@visibleForTesting` top-level function
and adds unit tests to cover the light-mode enforcement and CSP logic.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 21:01:57 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 1fa4d4911a fix(raw-email): save to Downloads and show size in raw email view (#97)
- Replace getTemporaryDirectory() + OpenFilex.open() with
  getDownloadsDirectory() (fallback to temp) so the .eml file lands in
  the public Downloads folder instead of triggering Android's
  "open with" dialog.
- Show a SnackBar with the saved path after download instead of
  launching a file viewer.
- Display the email size (via fmtSize) at the top of the Raw Email
  dialog, above the scrollable content.
- Add widget test covering the size display in the Raw Email dialog.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 20:39:39 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 9d19bdb81b feat(search): match word prefix, not arbitrary substring (#96)
Searching for "foo" now finds "foobar" (prefix of a word) but not
"blafoo" (suffix). The FTS5 query already used the foo* prefix form;
this commit extends the same semantics to folder-name and address
matching in the search screen, replacing contains() with a
word-boundary regex check. Tests added for all three paths.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 20:20:16 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 a44a2e4834 fix(ux): pop back after deleting the last search result from detail view (#85)
- Filter deleted emails locally in _batchDelete so the pop-back fires
  immediately instead of waiting for the IMAP server to catch up.
- Add _openSearchResultAndRefresh / _refreshSearchAndPopIfEmpty so that
  returning from EmailDetailScreen after deleting the last match also
  pops EmailListScreen back to the caller.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 18:09:35 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 99df6f5fd0 feat(accounts): share account settings via QR code / JSON export (#66)
Add Export account screen (QR code + copy-to-clipboard) and Import
account screen (paste JSON code) so users can transfer IMAP/JMAP
account configuration to another device without re-entering every field.

- Account list popup: "Export account" opens a QR code with a password
  warning and a copy-code button.
- Add Account screen: "Import account" button opens the import flow
  where pasting the exported JSON pre-fills the account and one tap
  saves it with a fresh generated ID.
- New routes: /accounts/:id/export and /accounts/import.
- Widget tests cover export display, import parsing, validation,
  and the happy-path save-and-navigate flow.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 16:53:36 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 122358c9a2 fix(ux): navigate back after deleting all search results (#85)
When the user searches in a mailbox, selects all results, and deletes
them, re-evaluate the search. If no results remain and there is a
previous screen in the navigation stack, pop back to it instead of
clearing the search and showing the regular inbox.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 15:36:05 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 ef3fb72f4e fix(email): populate mimeTree for JMAP accounts in Show Mail Structure (#92)
The JMAP body-fetch path never requested or stored `bodyStructure`, so
`body.mimeTree` was always null for JMAP accounts — causing Show Mail
Structure to show nothing.

Fix: include `bodyStructure` in the JMAP `Email/get` request and convert
it to the same JSON format used by the IMAP path via the new
`_jmapBodyStructureToJson` helper.  The parsed tree is persisted in the
DB and returned from `getEmailBody`, so the cached round-trip also works.

Tests added:
- Unit: JMAP getEmailBody populates mimeTree from bodyStructure and
  survives the cache round-trip; null when bodyStructure is absent.
- Widget: Show Mail Structure dialog displays all MIME parts when
  mimeTree is present; snackbar appears when mimeTree is null.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 14:23:43 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 69e358204d fix(undo): keep undo log entry and fix IMAP UID mismatch after sync (#81)
Two fixes for the UndoLog:

1. Don't delete the original undo log entry when undo is performed.
   The entry stays in the log alongside the new inverse action, so
   the user can retry the undo if it was silently reverted by an
   IMAP sync.

2. Fix IMAP UID mismatch: after an IMAP move is applied on the server
   the email gets a new UID in the destination folder. The undo service
   now looks up the email by its RFC 2822 Message-ID when the original
   row is gone, so the reverse-move pending change carries the correct
   UID and actually succeeds on the server.

Add findEmailByMessageId to EmailRepository interface and impl.
Add a regression test that simulates the UID change scenario.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 10:46:12 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 0ccf7b51fa fix: fetch original RFC822 from server in Show Raw Email (#84)
Instead of reconstructing the message from the local DB, fetch the
original bytes live from IMAP (BODY.PEEK[]) or JMAP (Email/get blobId
→ downloadBlob) so the view shows the true unmodified message.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 09:27:12 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 f96f9216cd feat: replace flutter_html with SecureEmailWebView (#21)
Swap the flutter_html renderer for a webview_flutter-based widget that
enforces strict security by default: scripts blocked via CSP
(script-src 'none'), remote images opt-in, and every link click routed
through a confirmation dialog that bolds the registered domain for
phishing detection.  Links open in the system browser via url_launcher.

On Linux (no webview_flutter platform support) the widget falls back to
plain text extracted via the existing htmlToPlain() utility.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 08:18:42 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 a29d0e93b4 feat(sync): add View log button to sync error banner
When a sync failure banner appears in the email list screen, a new
'View log' button navigates directly to the account's sync log screen
so the user can see the full error details.

Also creates issue #75 for the first-snooze-in-new-account failure.

Closes #13

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 21:35:59 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 02b0fec0b6 feat(compose): autocomplete To/Cc from local address history
Adds RawAutocomplete<EmailAddress> to the To and Cc fields in the
compose screen. As the user types (minimum 2 chars), suggestions are
fetched from the local DB by searching from/to/cc columns of cached
emails. Selecting a suggestion appends it to any existing addresses
already in the field (comma-separated).

New repository method searchAddresses() returns deduplicated
EmailAddress objects matching the query string.

Closes #11

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 21:30:17 +02:00