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 <noreply@anthropic.com>
This commit is contained in:
Thomas Güttler
2026-04-24 16:43:35 +02:00
co-authored by Claude Sonnet 4.6
parent 02585bad90
commit 39c3d1ea1a
6 changed files with 84 additions and 24 deletions
-19
View File
@@ -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
+7 -1
View File
@@ -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
+2
View File
@@ -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,
@@ -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,
+19 -4
View File
@@ -379,10 +379,25 @@ class _EmailListScreenState extends ConsumerState<EmailListScreen> {
),
],
),
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
+55
View File
@@ -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 <date>, <from> wrote:\n> line1\n> line2…
```
For Forward, add a third icon button (Icons.forward) next to reply/reply-all:
- subject: `Fwd: <original subject>`
- 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`