feat: quoted reply body and Forward button in email detail

Reply and reply-all now prefill the compose body with the original message
quoted as plain text (> lines). New Forward button sets Fwd: subject and
the same quoted body, leaving To/Cc empty for the user to fill.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Thomas Güttler
2026-04-24 16:48:19 +02:00
co-authored by Claude Sonnet 4.6
parent cdd5e5a6fc
commit 71692665cc
3 changed files with 46 additions and 22 deletions
+6
View File
@@ -6,6 +6,12 @@ Tasks get moved from next.md to done.md
## Tasks
## Quote original message in reply, and add Forward button
`_reply` now passes `prefillBody` with the original message quoted as plain
text (`> line…`). New `_forward` method and Forward toolbar button added;
sets `Fwd:` subject prefix and prefills the same quoted body with To/Cc empty.
## Mark as unread button in email detail
Added `mark_email_unread_outlined` icon button to `EmailDetailScreen` toolbar.
+40 -3
View File
@@ -64,14 +64,21 @@ class _EmailDetailScreenState extends ConsumerState<EmailDetailScreen> {
tooltip: 'Reply',
onPressed: header == null
? null
: () => _reply(context, header, replyAll: false),
: () => _reply(context, header, body, replyAll: false),
),
IconButton(
icon: const Icon(Icons.reply_all),
tooltip: 'Reply all',
onPressed: header == null
? null
: () => _reply(context, header, replyAll: true),
: () => _reply(context, header, body, replyAll: true),
),
IconButton(
icon: const Icon(Icons.forward),
tooltip: 'Forward',
onPressed: header == null
? null
: () => _forward(context, header, body),
),
IconButton(
icon: const Icon(Icons.mark_email_unread_outlined),
@@ -228,7 +235,21 @@ class _EmailDetailScreenState extends ConsumerState<EmailDetailScreen> {
);
}
void _reply(BuildContext context, Email header, {required bool replyAll}) {
String _quotedBody(Email header, EmailBody? body) {
final date = header.sentAt != null ? _dateFmt.format(header.sentAt!) : '';
final from =
header.from.isNotEmpty ? header.from.first.toString() : '(unknown)';
final text = body?.textBody ?? htmlToPlain(body?.htmlBody ?? '');
final quoted = text.trim().split('\n').map((l) => '> $l').join('\n');
return '\n\n— On $date, $from wrote:\n$quoted';
}
void _reply(
BuildContext context,
Email header,
EmailBody? body, {
required bool replyAll,
}) {
final to = header.from.isNotEmpty ? header.from.first.email : '';
final subject = (header.subject?.startsWith('Re:') ?? false)
? header.subject!
@@ -241,12 +262,28 @@ class _EmailDetailScreenState extends ConsumerState<EmailDetailScreen> {
'replyToEmailId': widget.emailId,
'prefillTo': to,
'prefillSubject': subject,
'prefillBody': _quotedBody(header, body),
if (cc.isNotEmpty) 'prefillCc': cc,
},
),
);
}
void _forward(BuildContext context, Email header, EmailBody? body) {
final subject = (header.subject?.startsWith('Fwd:') ?? false)
? header.subject!
: 'Fwd: ${header.subject ?? ''}';
unawaited(
context.push(
'/compose',
extra: {
'prefillSubject': subject,
'prefillBody': _quotedBody(header, body),
},
),
);
}
Future<void> _moveTo(BuildContext context, Email header) async {
final mailboxRepo = ref.read(mailboxRepositoryProvider);
final mailboxes =
-19
View File
@@ -17,22 +17,3 @@ Git repo should not contain unknown files.
Then commit.
## Tasks
## 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`