From 71692665cc3b93e84f4330ad10de18d9c59d2227 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=BCttler?= Date: Fri, 24 Apr 2026 16:48:19 +0200 Subject: [PATCH] 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 --- done.md | 6 ++++ lib/ui/screens/email_detail_screen.dart | 43 +++++++++++++++++++++++-- next.md | 19 ----------- 3 files changed, 46 insertions(+), 22 deletions(-) diff --git a/done.md b/done.md index 64dcc30..f876568 100644 --- a/done.md +++ b/done.md @@ -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. diff --git a/lib/ui/screens/email_detail_screen.dart b/lib/ui/screens/email_detail_screen.dart index 22b5739..2738f70 100644 --- a/lib/ui/screens/email_detail_screen.dart +++ b/lib/ui/screens/email_detail_screen.dart @@ -64,14 +64,21 @@ class _EmailDetailScreenState extends ConsumerState { 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 { ); } - 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 { '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 _moveTo(BuildContext context, Email header) async { final mailboxRepo = ref.read(mailboxRepositoryProvider); final mailboxes = diff --git a/next.md b/next.md index 4e79a50..5ff77df 100644 --- a/next.md +++ b/next.md @@ -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 , 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`