feat: save raw email to temp dir and add Share action to SnackBar (#115)

Save the .eml file to the temporary directory (reliable on all
platforms) and display a Share action in the SnackBar so users can
send the file to any app — including the Files app — which properly
registers it with Android's MediaStore and makes it visible in the
recently-used list.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Thomas SharedInbox
2026-05-16 17:57:31 +02:00
co-authored by Claude Sonnet 4.6
parent e327b42312
commit de66081813
3 changed files with 17 additions and 14 deletions
+11 -8
View File
@@ -9,6 +9,7 @@ import 'package:go_router/go_router.dart';
import 'package:intl/intl.dart';
import 'package:open_filex/open_filex.dart';
import 'package:path_provider/path_provider.dart';
import 'package:share_plus/share_plus.dart';
import 'package:sharedinbox/core/models/email.dart';
import 'package:sharedinbox/core/models/undo_action.dart';
@@ -517,13 +518,7 @@ class _EmailDetailScreenState extends ConsumerState<EmailDetailScreen> {
String raw,
) async {
try {
Directory dir;
try {
dir =
(await getDownloadsDirectory()) ?? (await getTemporaryDirectory());
} catch (_) {
dir = await getTemporaryDirectory();
}
final dir = await getTemporaryDirectory();
final subject = (header?.subject ?? 'email')
.replaceAll(RegExp(r'[^\w\s-]'), '_')
.trim();
@@ -532,7 +527,15 @@ class _EmailDetailScreenState extends ConsumerState<EmailDetailScreen> {
await file.writeAsString(raw);
if (!context.mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Saved $filename to ${dir.path}')),
SnackBar(
content: Text('Saved $filename'),
action: SnackBarAction(
label: 'Share',
onPressed: () => SharePlus.instance.share(
ShareParams(files: [XFile(file.path)]),
),
),
),
);
} catch (e) {
if (!context.mounted) return;
+1
View File
@@ -60,6 +60,7 @@ dependencies:
# App version metadata for crash reports
package_info_plus: ^8.0.0
share_plus: ^12.0.2
dev_dependencies:
flutter_test:
+5 -6
View File
@@ -12,14 +12,11 @@ import 'package:sharedinbox/di.dart';
import 'helpers.dart';
// Fake PathProviderPlatform so _downloadRaw resolves getDownloadsDirectory /
// getTemporaryDirectory via pure microtasks instead of calling xdg-user-dir.
// Fake PathProviderPlatform so _downloadRaw resolves getTemporaryDirectory
// via pure microtasks instead of calling xdg-user-dir.
class _FakePathProviderPlatform extends PathProviderPlatform {
@override
Future<String?> getTemporaryPath() async => '/tmp';
@override
Future<String?> getDownloadsPath() async => '/tmp';
}
// IOOverrides subclass that stubs File creation so _downloadRaw completes
@@ -261,7 +258,7 @@ void main() {
});
await tester.tap(find.text('Download'));
// Each pump drains one microtask level: getDownloadsDirectory, then
// Each pump drains one microtask level: getTemporaryDirectory, then
// writeAsString, then _downloadRaw return, then Navigator.pop.
for (var i = 0; i < 10; i++) {
await tester.pump(Duration.zero);
@@ -270,6 +267,8 @@ void main() {
// Dialog must be dismissed after download completes.
expect(find.text('Raw Email'), findsNothing);
// SnackBar with Share action must be visible.
expect(find.text('Share'), findsOneWidget);
});
testWidgets('Show Mail Structure opens dialog with MIME parts', (