43 Commits
Author SHA1 Message Date
Bot of Thomas Güttler 38f7ada8b5 chore(deps): bump go_router, file_picker, flutter_local_notifications (#532) 2026-06-07 19:45:22 +02:00
57b266a82b fix(lint): move sqlite3 to dependencies, use close() instead of dispose()
- sqlite3 is now imported in lib/ (production code), so it must be a
  regular dependency, not a dev_dependency
- Replace deprecated conn.dispose() with conn.close() in the test

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 00:32:13 +02:00
guettlibotandBot of Thomas Güttler f3e1ca13de chore(deps): update dependency flutter_launcher_icons to ^0.14.0 (#464) 2026-06-06 09:01:21 +02:00
3e2da2bdf8 feat: use icon.svg as app icon for Android and Linux (#459)
Closes #451

## What changed

Replaces the default Flutter blue logo with the project's rainbow-rings `icon.svg` on all supported platforms.

**Android** — all five mipmap densities regenerated (`mdpi` 48px through `xxxhdpi` 192px).

**Linux** — `linux/sharedinbox.png` (512×512) added, installed next to the binary via `CMakeLists.txt`, and set as the GTK window icon via `gtk_window_set_icon_from_file` in `my_application.cc`.

**Tooling** — `icon.png` (1024×1024 source raster) committed; `flutter_launcher_icons` added as dev dep with a `flutter_icons` config block; `task generate-icons` added to `Taskfile.yml` for future regeneration; `librsvg` added to `flake.nix` so `rsvg-convert` is available inside `nix develop`.

## How verified

Icons were generated with Inkscape from `icon.svg` and visually confirmed (rainbow-rings design appears correctly at all sizes). The `playstore/icon.png` was already correct and unchanged.

Co-authored-by: Thomas SharedInbox <sharedinbox@thomas-guettler.de>
Reviewed-on: https://codeberg.org/guettli/sharedinbox/pulls/459
2026-06-06 05:32:29 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 ef3255cd2b fix: set demangleStackTrace to handle async chain stack traces
When zone errors bubble up through Dart's async machinery the stack
trace is in package:stack_trace chain format (with '===== asynchronous
gap =====' separators). Flutter's StackFrame parser asserts on those
lines. FlutterError.demangleStackTrace strips the chain format back to
a plain VM trace before Flutter tries to parse it.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 14:08:08 +02:00
Bot of Thomas Güttler 7e3308cb94 fix: pin intl dependency to ^0.20.2 instead of any (#344) 2026-06-01 21:47:50 +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 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 4f6f1d9437 fix: migrate to Riverpod 3.x and update dependencies (#175) (#190) 2026-05-23 19:50:11 +02:00
826488192d fix: update flutter packages (#148) (#165)
## Summary

- Upgrades 9 direct dependencies and their transitive peers to resolve the CI warning: *"38 packages have newer versions incompatible with dependency constraints"*
- Reduces incompatible-version count from **38 → 21** (the remaining 21 are either deliberately pinned, constrained by transitive dep ceilings, or require a separate riverpod 2→3 migration)
- Adapts two source files to breaking API changes in the upgraded packages:
  - `notification_service.dart`: `flutter_local_notifications` 21.x changed positional args to named params (`initialize(settings:…)`, `show(id:…, title:…, body:…, notificationDetails:…)`)
  - `compose_screen.dart`: `file_picker` 12.x removed `FilePicker.platform` static getter; calls are now `FilePicker.pickFiles()`

## Packages changed

| Package | Before | After |
|---|---|---|
| `go_router` | ^14.8.1 | ^17.2.3 |
| `flutter_local_notifications` | ^18.0.1 | ^21.0.0 |
| `file_picker` | ^8.0.0 | ^12.0.0-beta.4 |
| `mobile_scanner` | ^5.0.0 | ^7.2.0 |
| `package_info_plus` | ^8.0.0 | ^10.1.0 |
| `share_plus` | ^12.0.2 | ^13.1.0 |
| `sqlite3_flutter_libs` | ^0.5.28 | ^0.6.0+eol |
| `flutter_lints` | ^4.0.0 | ^6.0.0 |
| `flutter_secure_storage` | 10.2.0 | 10.3.0 (patch) |

## Test plan

- [x] `flutter analyze` — no issues
- [x] Unit tests (324 passed)
- [x] Widget tests (116 passed)
- [ ] CI full check suite

🤖 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/165
2026-05-23 14:58:54 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 acd9483e8b chore: replace flutter_markdown with flutter_markdown_plus (#147)
flutter_markdown 0.7.7+1 has been discontinued in favour of
flutter_markdown_plus. Switch the dependency and update both import
sites.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 16:44:10 +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 44d6227ba8 chore: track pubspec.lock and pin sqlite3 to ^3.1.5
pubspec.lock was incorrectly gitignored — this is a Flutter app, not a
package, so the lockfile should be committed for reproducible builds.
Without it, CI resolved drift to its minimum (2.20.3) which constrains
sqlite3 to 2.x, causing dart analyze to disagree on whether
Database.close() exists vs the local environment using 3.3.1.

Also pins sqlite3: ^3.1.5 explicitly in pubspec.yaml as belt-and-
suspenders so the constraint is visible without reading the lockfile.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 08:19:14 +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 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 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 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 26a9a5e6f3 feat(crash): add app version and device info to crash reports
Issue reports now include:
- App version (from package_info_plus)
- OS name and version (non-personal, from dart:io Platform)
- Error and stack trace wrapped in triple-backtick code blocks
  so Codeberg renders them as preformatted text

Closes #59

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 20:52:40 +02:00
Bot of Thomas Güttler 6d83a5670d fix: upgrade workmanager to 0.9.0+3 to fix Kotlin 2.x AAB build failure (#38) 2026-05-14 09:03:17 +02:00
Bot of Thomas Güttler 2715c1613f feat(U4): background sync and local notifications for new mail (#28) 2026-05-14 04:06:35 +02:00
Thomas SharedInbox 9815e105d3 fix: improve crash reporting on Codeberg
- Pre-fill Codeberg issue with crash details (title and body).
- Remove unreliable canLaunchUrl check.
- Add SnackBar error handling if launch fails.
- Add https intent to Android queries manifest for better link visibility.
- Add widget test for CrashScreen.
2026-05-10 22:21:09 +02:00
Thomas SharedInbox 3ca71cd4e5 feat: add hyperlinks to Codeberg commits in ChangeLog (Issue #9) 2026-05-10 13:18:35 +02:00
Thomas SharedInbox 082a39b539 fix: restore missing integration_test dependency and bump version to 0.1.1 2026-05-10 11:56:40 +02:00
Thomas SharedInbox 2ecb29d6ee feat: implement in-app ChangeLog from git history 2026-05-10 11:23:39 +02:00
guettlibotandThomas SharedInbox 6d58ee1e00 Fix Issues 1, 2, and 3: Headers, Undo, and Crash Reporting (#6)
## Overview
This PR implements several fixes and enhancements requested in the latest session:

### Fixes
1. **Issue 1: Raw Email Headers**
   - Added database support for raw headers.
   - Added a new Headers tab in the email detail screen with a zebra-colored table display.
2. **Issue 2: Exception on Undo of Delete**
   - Added `toJson` and `fromJson` to `EmailAddress` model to fix serialization during undo.
3. **Issue 3: Crash Reporting**
   - Added a button to the Crash Screen to report issues directly on Codeberg.

### Infrastructure
- Added Nix experimental features check to `Taskfile.yml` to ensure a consistent dev environment.

## Verification
- Manually verified the Headers display on Linux.
- Verified Undo for IMAP and JMAP accounts.
- Verified the Crash Screen button.

Co-authored-by: Thomas SharedInbox <sharedinbox@thomas-guettler.de>
Reviewed-on: https://codeberg.org/guettli/sharedinbox/pulls/6
2026-05-09 18:49:34 +02:00
Thomas Güttler 43e1744614 feat: implement optimized Undo for delete and move actions
- Added UndoService with 10-action history stack.
- Integrated Undo Snackbar into EmailListScreen and EmailDetailScreen.
- Added EmailRepository.cancelPendingChange to optimize undo by removing
  unsynced local mutations.
- Fixed sorting bug in compareMailboxes for unknown roles.
- Increased unit coverage to 83% with new model and utility tests.
- Verified with full test suite (task check).
2026-05-08 11:14:54 +02:00
Thomas GüttlerandClaude Opus 4.7 44d02afc46 feat: render HTML email bodies via flutter_html
Email detail screen now renders the message htmlBody with the flutter_html
widget when present, falling back to SelectableText for plain-text-only mail.
Remote http(s) images are blocked by default to defeat tracking pixels; an
opt-in "Load remote images" button reveals them per-screen.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 20:10:18 +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üttler 7dc6b8959d ... 2026-04-25 07:12:43 +02:00
Thomas GüttlerandQwen-Coder 5984137bdc refactor: use open_filex instead of open_file with xdg-open workaround
- Replace open_file + manual xdg-open with open_filex (maintained fork)
- open_filex has better Linux support built-in
- Removes platform-specific code, cleaner implementation

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-22 10:52:45 +02:00
Thomas GüttlerandQwen-Coder a51c2dad9c feat: attachment open via xdg-open on Linux, mime type detection
- Use xdg-open directly on Linux for opening attachments (fixes 'file type not supported' error)
- Add mime package for comprehensive MIME type detection in compose screen
- Show file size and MIME type for attachments in compose screen
- Add open/preview button for attachments in compose screen

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-22 10:47:11 +02:00
Thomas GüttlerandClaude Sonnet 4.6 2f1924be9c feat: email attachments — send, download and open
- Add file_picker and open_file dependencies
- EmailDraft gains attachmentFilePaths; EmailAttachment gains fetchPartId
- sendEmail attaches files via MessageBuilder.addFile()
- downloadAttachment fetches the specific MIME part from IMAP, caches to
  local filesystem; subsequent calls return the cached file without a
  network round-trip
- ComposeScreen: attach-file button + removable attachment list
- EmailDetailScreen: per-attachment download/open button with spinner
- 3 new unit tests covering send-with-attachment, download, and cache hit

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 17:04:25 +02:00
Thomas GüttlerandClaude Sonnet 4.6 442c3c4087 feat: add-account wizard, edit account, inbox tap, connection status
- Add account wizard: email-first flow with JMAP/IMAP auto-detection
  via well-known URLs; falls back to manual type selection
- Fix JMAP connection probe: GET session URL with Basic auth instead
  of the API endpoint, so 401 reliably signals bad credentials
- Account list tile: tap → open INBOX directly; popup menu for
  all mailboxes / edit / delete (with confirmation dialog)
- Show account type (JMAP/IMAP) and async connection status per tile:
  spinner while checking, green check on success, red error on failure
- Add EditAccountScreen: edit name, password, server settings; runs
  connection test only when password is changed
- Fix GTK window initialisation order so app starts with correct size
- Fix 42 lint issues (avoid_redundant_argument_values,
  unnecessary_non_null_assertion, unawaited_futures)
- 147 tests, 87% coverage, task check green

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 15:13:47 +02:00
Thomas Güttler c7a121e386 stalwart-dev/integration_ui_test.sh working! great! 2026-04-18 12:05:20 +02:00
Thomas Güttler f0d3d9e6a2 task run show a window. Empty, but at least a window. 2026-04-17 22:20:10 +02:00
Thomas Güttler 9ce598d21c task check, working again. 2026-04-17 10:05:31 +02:00
Thomas Güttler c6be26623d better coverage. 2026-04-16 13:44:55 +02:00
Thomas GüttlerandClaude Sonnet 4.6 2f93ae3638 Use upstream enough_mail 2.1.7 from pub.dev
No fork needed — our changes were correct API usage, not workarounds.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 09:38:46 +02:00
Thomas GüttlerandClaude Sonnet 4.6 79ee498879 Switch enough_mail from vendored path to forked git dependency
- pubspec.yaml: path: packages/enough_mail → git: guettli/enough_mail
  pinned to SHA 25320ada (same version as the vendored copy)
- Remove packages/enough_mail/ (170 files, 38k lines) from this repo
- Fix .gitignore: restore comment, add coverage/ (generated by --coverage)
- PLAN.md: document phase 0a

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 09:34:55 +02:00
Thomas GüttlerandClaude Sonnet 4.6 03d35387f7 Linting, tests, README, CI, and code quality improvements
Linting:
- analysis_options.yaml: flutter_lints base + 20 additional rules
  (prefer_single_quotes, avoid_print, cancel_subscriptions,
  unawaited_futures, empty_catches, always_declare_return_types, …)
- unused_import / dead_code treated as errors

pubspec.yaml:
- Remove 6 unused packages: freezed, freezed_annotation, json_annotation,
  json_serializable, riverpod_generator, collection
- Add test: ^1.25.0 for pure-Dart unit tests

Testable utilities (extracted from screen code):
- lib/core/utils/html_utils.dart — htmlToPlain()
- lib/core/utils/format_utils.dart — fmtSize()
- lib/core/utils/logger.dart — thin debugPrint wrapper

Unit tests (test/unit/, no device required):
- html_utils_test.dart — 13 cases covering tags, entities, edge cases
- format_utils_test.dart — B / KB / MB / GB formatting
- email_model_test.dart — EmailAddress JSON roundtrip, toString, EmailDraft
- account_sync_backoff_test.dart — exponential backoff + clamping

Taskfile:
- task test → dart test test/unit/ (fast, no device)
- task test-flutter → flutter test (widget tests)
- task check → analyze + unit tests in parallel

README rewrite:
- Covers user flow (download, add account)
- Developer flow: nix develop, direnv allow, task codegen, task check
- Platform status table (Linux done, others pending)
- Project layout diagram
- Schema-change workflow

CI (.github/workflows/ci.yml):
- analyze-and-test job: flutter analyze + dart test on every push/PR
- integration job: Nix + Stalwart on push to main
- build-linux job: flutter build linux --release

Logging:
- Replace all bare catch (_) {} with log() calls
- Sync backoff errors now print account email + retry delay
- STATUS failures on \Noselect mailboxes logged at debug level
- STARTTLS failures logged before continuing without TLS

.gitignore:
- Add .direnv/, .flutter-plugins-dependencies
- Add all platform generated-plugin wiring files
- Add .env / .env.local
- Add linux/build/

.env.example: documents optional STALWART_PORT overrides

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 08:11:29 +02:00
Thomas GüttlerandClaude Sonnet 4.6 22db4a2dd6 Add Nix flake, .envrc, Taskfile, and Stalwart dev server
- flake.nix: Flutter 3.41.6, Android SDK, Stalwart, GTK3/build
  tools for Linux desktop, go-task
- .envrc: copied from sharedinbox — use flake + dotenv_if_exists
- Taskfile.yml: analyze, test, integration, codegen, run tasks
- stalwart-dev/: IMAP+SMTP dev server reused from sharedinbox
- test/integration/imap_sync_test.dart: login, list mailboxes,
  send via SMTP and receive via IMAP
- pubspec.yaml: add flutter_secure_storage

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 07:40:34 +02:00
Thomas GüttlerandClaude Sonnet 4.6 5ebda521d6 Initial Flutter/Dart port of SharedInbox
IMAP/SMTP email client with offline-first architecture:
sync engine writes to Drift (SQLite), UI reads reactively
from the local DB. enough_mail vendored under packages/.

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