Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6137202811 | ||
|
|
a723380560 |
@@ -1,5 +1,6 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_html/flutter_html.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
@@ -17,6 +18,14 @@ import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
final _dateFmt = DateFormat('EEE, MMM d yyyy, HH:mm');
|
||||
|
||||
void _openLink(String? url, Map<String, String> attrs, dynamic _) {
|
||||
if (url == null) return;
|
||||
final uri = Uri.tryParse(url);
|
||||
if (uri != null) {
|
||||
unawaited(launchUrl(uri, mode: LaunchMode.externalApplication));
|
||||
}
|
||||
}
|
||||
|
||||
class EmailDetailScreen extends ConsumerStatefulWidget {
|
||||
const EmailDetailScreen({super.key, required this.emailId});
|
||||
final String emailId;
|
||||
@@ -60,20 +69,27 @@ class _EmailDetailScreenState extends ConsumerState<EmailDetailScreen> {
|
||||
tooltip: 'Reply',
|
||||
onPressed: header == null
|
||||
? null
|
||||
: () => _reply(context, header, body, replyAll: false),
|
||||
: () {
|
||||
unawaited(_reply(context, header, body, replyAll: false));
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.reply_all),
|
||||
tooltip: 'Reply all',
|
||||
onPressed: header == null
|
||||
? null
|
||||
: () => _reply(context, header, body, replyAll: true),
|
||||
: () {
|
||||
unawaited(_reply(context, header, body, replyAll: true));
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.forward),
|
||||
tooltip: 'Forward',
|
||||
onPressed:
|
||||
header == null ? null : () => _forward(context, header, body),
|
||||
onPressed: header == null
|
||||
? null
|
||||
: () {
|
||||
unawaited(_forward(context, header, body));
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.mark_email_unread_outlined),
|
||||
@@ -263,26 +279,31 @@ class _EmailDetailScreenState extends ConsumerState<EmailDetailScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
String _quotedBody(Email header, EmailBody? body) {
|
||||
Future<String> _quotedBody(Email header, EmailBody? body) async {
|
||||
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 rawText = body?.textBody;
|
||||
final text = (rawText != null && rawText.isNotEmpty)
|
||||
? rawText
|
||||
: await compute(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(
|
||||
Future<void> _reply(
|
||||
BuildContext context,
|
||||
Email header,
|
||||
EmailBody? body, {
|
||||
required bool replyAll,
|
||||
}) {
|
||||
}) async {
|
||||
final to = header.from.isNotEmpty ? header.from.first.email : '';
|
||||
final subject = (header.subject?.startsWith('Re:') ?? false)
|
||||
? header.subject!
|
||||
: 'Re: ${header.subject ?? ''}';
|
||||
final cc = replyAll ? header.to.map((a) => a.email).join(', ') : '';
|
||||
final quoted = await _quotedBody(header, body);
|
||||
if (!context.mounted) return;
|
||||
unawaited(
|
||||
context.push(
|
||||
'/compose',
|
||||
@@ -290,23 +311,29 @@ class _EmailDetailScreenState extends ConsumerState<EmailDetailScreen> {
|
||||
'replyToEmailId': widget.emailId,
|
||||
'prefillTo': to,
|
||||
'prefillSubject': subject,
|
||||
'prefillBody': _quotedBody(header, body),
|
||||
'prefillBody': quoted,
|
||||
if (cc.isNotEmpty) 'prefillCc': cc,
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _forward(BuildContext context, Email header, EmailBody? body) {
|
||||
Future<void> _forward(
|
||||
BuildContext context,
|
||||
Email header,
|
||||
EmailBody? body,
|
||||
) async {
|
||||
final subject = (header.subject?.startsWith('Fwd:') ?? false)
|
||||
? header.subject!
|
||||
: 'Fwd: ${header.subject ?? ''}';
|
||||
final quoted = await _quotedBody(header, body);
|
||||
if (!context.mounted) return;
|
||||
unawaited(
|
||||
context.push(
|
||||
'/compose',
|
||||
extra: {
|
||||
'prefillSubject': subject,
|
||||
'prefillBody': _quotedBody(header, body),
|
||||
'prefillBody': quoted,
|
||||
},
|
||||
),
|
||||
);
|
||||
@@ -534,7 +561,11 @@ class _SafeHtmlState extends State<_SafeHtml> {
|
||||
(_) => ErrorWidget.builder = prev,
|
||||
);
|
||||
|
||||
return Html(data: widget.data, extensions: widget.extensions);
|
||||
return Html(
|
||||
data: widget.data,
|
||||
extensions: widget.extensions,
|
||||
onLinkTap: _openLink,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import 'package:sharedinbox/core/models/email.dart';
|
||||
import 'package:sharedinbox/core/models/undo_action.dart';
|
||||
import 'package:sharedinbox/core/utils/html_utils.dart';
|
||||
import 'package:sharedinbox/di.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
final _dateFmt = DateFormat('EEE, MMM d, HH:mm');
|
||||
|
||||
@@ -168,6 +169,18 @@ class _EmailMessageCardState extends ConsumerState<_EmailMessageCard> {
|
||||
extensions: [
|
||||
if (!_loadRemoteImages) _BlockRemoteImagesExtension(),
|
||||
],
|
||||
onLinkTap: (url, _, __) {
|
||||
if (url == null) return;
|
||||
final uri = Uri.tryParse(url);
|
||||
if (uri != null) {
|
||||
unawaited(
|
||||
launchUrl(
|
||||
uri,
|
||||
mode: LaunchMode.externalApplication,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
] else
|
||||
SelectableText(
|
||||
|
||||
Reference in New Issue
Block a user