fix(raw-email): save to Downloads and show size in raw email view (#97)
- Replace getTemporaryDirectory() + OpenFilex.open() with getDownloadsDirectory() (fallback to temp) so the .eml file lands in the public Downloads folder instead of triggering Android's "open with" dialog. - Show a SnackBar with the saved path after download instead of launching a file viewer. - Display the email size (via fmtSize) at the top of the Raw Email dialog, above the scrollable content. - Add widget test covering the size display in the Raw Email dialog. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
9d19bdb81b
commit
1fa4d4911a
@@ -457,11 +457,29 @@ class _EmailDetailScreenState extends ConsumerState<EmailDetailScreen> {
|
||||
title: const Text('Raw Email'),
|
||||
content: SizedBox(
|
||||
width: double.maxFinite,
|
||||
child: SingleChildScrollView(
|
||||
child: SelectableText(
|
||||
raw,
|
||||
style: const TextStyle(fontFamily: 'monospace', fontSize: 12),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
fmtSize(raw.length),
|
||||
style: Theme.of(ctx).textTheme.bodySmall?.copyWith(
|
||||
color: Theme.of(ctx).colorScheme.outline,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Flexible(
|
||||
child: SingleChildScrollView(
|
||||
child: SelectableText(
|
||||
raw,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'monospace',
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
@@ -496,13 +514,23 @@ class _EmailDetailScreenState extends ConsumerState<EmailDetailScreen> {
|
||||
String raw,
|
||||
) async {
|
||||
try {
|
||||
final dir = await getTemporaryDirectory();
|
||||
Directory dir;
|
||||
try {
|
||||
dir =
|
||||
(await getDownloadsDirectory()) ?? (await getTemporaryDirectory());
|
||||
} catch (_) {
|
||||
dir = await getTemporaryDirectory();
|
||||
}
|
||||
final subject = (header?.subject ?? 'email')
|
||||
.replaceAll(RegExp(r'[^\w\s-]'), '_')
|
||||
.trim();
|
||||
final file = File('${dir.path}/$subject.eml');
|
||||
final filename = '$subject.eml';
|
||||
final file = File('${dir.path}/$filename');
|
||||
await file.writeAsString(raw);
|
||||
await OpenFilex.open(file.path);
|
||||
if (!context.mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Saved $filename to ${dir.path}')),
|
||||
);
|
||||
} catch (e) {
|
||||
if (!context.mounted) return;
|
||||
ScaffoldMessenger.of(
|
||||
|
||||
@@ -143,6 +143,41 @@ void main() {
|
||||
expect(find.text('report.pdf'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('Show Raw Email dialog shows size of email', (tester) async {
|
||||
// 'A' * 2048 → fmtSize(2048) == '2.0 KB'
|
||||
final rawContent = 'A' * 2048;
|
||||
await tester.pumpWidget(
|
||||
buildApp(
|
||||
initialLocation: '/accounts/acc-1/mailboxes/INBOX/emails/acc-1%3A42',
|
||||
overrides: [
|
||||
accountRepositoryProvider.overrideWithValue(
|
||||
FakeAccountRepository([kTestAccount]),
|
||||
),
|
||||
mailboxRepositoryProvider
|
||||
.overrideWithValue(FakeMailboxRepository()),
|
||||
emailRepositoryProvider.overrideWithValue(
|
||||
FakeEmailRepository(
|
||||
emailDetail: testEmail(),
|
||||
emailBody:
|
||||
const EmailBody(emailId: 'acc-1:42', attachments: []),
|
||||
rawRfc822: rawContent,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.byType(PopupMenuButton<String>));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.text('Show Raw Email'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Raw Email'), findsOneWidget);
|
||||
expect(find.text('2.0 KB'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('Show Mail Structure opens dialog with MIME parts', (
|
||||
tester,
|
||||
) async {
|
||||
|
||||
@@ -147,6 +147,7 @@ class FakeEmailRepository implements EmailRepository {
|
||||
final List<Email> _emails;
|
||||
final Email? _emailDetail;
|
||||
final EmailBody _emailBody;
|
||||
final String _rawRfc822;
|
||||
|
||||
final List<Email> _searchResults;
|
||||
|
||||
@@ -155,9 +156,11 @@ class FakeEmailRepository implements EmailRepository {
|
||||
Email? emailDetail,
|
||||
EmailBody? emailBody,
|
||||
List<Email>? searchResults,
|
||||
String rawRfc822 = '',
|
||||
}) : _emails = emails ?? [],
|
||||
_emailDetail = emailDetail,
|
||||
_searchResults = searchResults ?? [],
|
||||
_rawRfc822 = rawRfc822,
|
||||
_emailBody = emailBody ?? const EmailBody(emailId: '', attachments: []);
|
||||
|
||||
@override
|
||||
@@ -261,7 +264,7 @@ class FakeEmailRepository implements EmailRepository {
|
||||
'/tmp/${attachment.filename}';
|
||||
|
||||
@override
|
||||
Future<String> fetchRawRfc822(String emailId) async => '';
|
||||
Future<String> fetchRawRfc822(String emailId) async => _rawRfc822;
|
||||
|
||||
@override
|
||||
Future<List<Email>> searchEmails(
|
||||
|
||||
Reference in New Issue
Block a user