From 39c3d1ea1a92972130b7bc86c0d5da50eb182957 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=BCttler?= Date: Fri, 24 Apr 2026 16:43:35 +0200 Subject: [PATCH] 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 --- NEXT.md | 19 ------- DONE.md => done.md | 8 ++- lib/core/models/email.dart | 2 + .../repositories/email_repository_impl.dart | 1 + lib/ui/screens/email_list_screen.dart | 23 ++++++-- next.md | 55 +++++++++++++++++++ 6 files changed, 84 insertions(+), 24 deletions(-) delete mode 100644 NEXT.md rename DONE.md => done.md (91%) create mode 100644 next.md diff --git a/NEXT.md b/NEXT.md deleted file mode 100644 index abd9276..0000000 --- a/NEXT.md +++ /dev/null @@ -1,19 +0,0 @@ -# Next - -## Introduction - -Do one thing, ask if unsure first! - -Then implement. - -Then run `task check`. - -Then move task to DONE.md - -Check if all files are staged. - -Git repo should not contain unknown files. - -Then commit. - -## Tasks diff --git a/DONE.md b/done.md similarity index 91% rename from DONE.md rename to done.md index d1af92d..ae9ed73 100644 --- a/DONE.md +++ b/done.md @@ -2,10 +2,16 @@ This file contains tasks which got implemented. -Tasks get moved from NEXT.md to DONE.md +Tasks get moved from next.md to done.md ## Tasks +## Show email preview snippet in list + +Added `preview` field to `EmailThread` (populated from the latest email in +`_groupIntoThreads`). Thread tiles now show subject + a one-line preview +snippet in the subtitle. + ## Extract TryConnectionButton widget shared by account screens Extracted `lib/ui/widgets/try_connection_button.dart` — a stateless widget diff --git a/lib/core/models/email.dart b/lib/core/models/email.dart index a850659..fe07c7e 100644 --- a/lib/core/models/email.dart +++ b/lib/core/models/email.dart @@ -52,6 +52,7 @@ class EmailThread { final bool hasUnread; final bool isFlagged; final String latestEmailId; + final String? preview; final String accountId; final String mailboxPath; @@ -67,6 +68,7 @@ class EmailThread { required this.hasUnread, required this.isFlagged, required this.latestEmailId, + this.preview, required this.emailIds, required this.accountId, required this.mailboxPath, diff --git a/lib/data/repositories/email_repository_impl.dart b/lib/data/repositories/email_repository_impl.dart index f1cb9b9..5e46aa1 100644 --- a/lib/data/repositories/email_repository_impl.dart +++ b/lib/data/repositories/email_repository_impl.dart @@ -110,6 +110,7 @@ class EmailRepositoryImpl implements EmailRepository { hasUnread: threadEmails.any((e) => !e.isSeen), isFlagged: threadEmails.any((e) => e.isFlagged), latestEmailId: latest.id, + preview: latest.preview, emailIds: threadEmails.map((e) => e.id).toList(), accountId: latest.accountId, mailboxPath: latest.mailboxPath, diff --git a/lib/ui/screens/email_list_screen.dart b/lib/ui/screens/email_list_screen.dart index bad4775..80fdb90 100644 --- a/lib/ui/screens/email_list_screen.dart +++ b/lib/ui/screens/email_list_screen.dart @@ -379,10 +379,25 @@ class _EmailListScreenState extends ConsumerState { ), ], ), - subtitle: Text( - t.subject ?? '(no subject)', - maxLines: 1, - overflow: TextOverflow.ellipsis, + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + t.subject ?? '(no subject)', + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: t.hasUnread + ? const TextStyle(fontWeight: FontWeight.bold) + : null, + ), + if (t.preview != null && t.preview!.isNotEmpty) + Text( + t.preview!, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: Theme.of(ctx).textTheme.bodySmall, + ), + ], ), selected: isSelected, trailing: _selecting diff --git a/next.md b/next.md new file mode 100644 index 0000000..f5966e9 --- /dev/null +++ b/next.md @@ -0,0 +1,55 @@ +# Next + +## Introduction + +Do one thing, ask if unsure first! + +Then implement. + +Then run `task check`. + +Then move task to done.md + +Check if all files are staged. + +Git repo should not contain unknown files. + +Then commit. + +## Tasks + +## Pull-to-refresh on email list + +`EmailListScreen` has a manual sync icon button but no swipe-to-refresh gesture. +Wrap the `StreamBuilder` result body in a `RefreshIndicator` that calls the +same sync trigger as the icon button. + +File: `lib/ui/screens/email_list_screen.dart` + +## Mark as unread button in email detail + +`EmailRepository.setFlag(emailId, seen: false)` already exists. Add a +"Mark as unread" icon button (or overflow menu item) in `EmailDetailScreen` +that calls `setFlag(seen: false)` then pops the screen so the message +reappears as unread in the list. + +File: `lib/ui/screens/email_detail_screen.dart` + +## Quote original message in reply, and add Forward button + +When replying, `prefillBody` is never set so compose opens with an empty body. +Set `prefillBody` to the original message formatted as a plain-text quote: + +```text +\n\n— On , wrote:\n> line1\n> line2… +``` + +For Forward, add a third icon button (Icons.forward) next to reply/reply-all: + +- subject: `Fwd: ` +- To/Cc: empty (user fills in) +- body: same quoted original text + +The body snapshot is already available in `_buildBody()` context. + +File: `lib/ui/screens/email_detail_screen.dart`