diff --git a/lib/utils/log_export.dart b/lib/utils/log_export.dart index 7bb0b15..271da1f 100644 --- a/lib/utils/log_export.dart +++ b/lib/utils/log_export.dart @@ -57,13 +57,18 @@ class LogExport { ); return; } + // Resolve everything off `context` BEFORE the async gap so nothing touches a + // possibly-deactivated widget after the await. + final messenger = ScaffoldMessenger.of(context); + final l10n = context.l10n; final file = await FileLogService.instance.flushAndGetActiveFile(); - if (!context.mounted) return; await _exportFile( - context, + messenger, file: file, fileName: _defaultFileName, - subject: context.l10n.debugLog_shareSubject, + subject: l10n.debugLog_shareSubject, + unavailableMessage: l10n.debugLog_logUnavailable, + savedMessage: l10n.debugLog_logSaved, ); } @@ -75,25 +80,28 @@ class LogExport { String? subject, String? fileName, }) async { + final messenger = ScaffoldMessenger.of(context); + final l10n = context.l10n; await _exportFile( - context, + messenger, file: file, fileName: fileName ?? file.uri.pathSegments.last, - subject: subject ?? context.l10n.debugLog_shareSubject, + subject: subject ?? l10n.debugLog_shareSubject, + unavailableMessage: l10n.debugLog_logUnavailable, + savedMessage: l10n.debugLog_logSaved, ); } static Future _exportFile( - BuildContext context, { + ScaffoldMessengerState messenger, { required File? file, required String fileName, required String subject, + required String unavailableMessage, + required String savedMessage, }) async { - final messenger = ScaffoldMessenger.of(context); - final unavailable = context.l10n.debugLog_logUnavailable; - final savedMessage = context.l10n.debugLog_logSaved; if (file == null) { - messenger.showSnackBar(SnackBar(content: Text(unavailable))); + messenger.showSnackBar(SnackBar(content: Text(unavailableMessage))); return; } if (_isMobile) { diff --git a/lib/utils/log_web_download_web.dart b/lib/utils/log_web_download_web.dart index 8d0146a..6b31c3f 100644 --- a/lib/utils/log_web_download_web.dart +++ b/lib/utils/log_web_download_web.dart @@ -1,9 +1,14 @@ +import 'dart:async'; import 'dart:js_interop'; import 'package:web/web.dart' as web; /// Trigger a browser download of [content] (UTF-8 text) as [fileName] by /// creating an in-memory Blob and clicking a transient anchor. #433 +/// +/// The anchor is appended to the document before clicking (Firefox ignores +/// `click()` on a disconnected anchor) and the object URL is revoked on a delay +/// (revoking synchronously can abort the download in Safari/iOS). void downloadTextFile(String fileName, String content) { final blob = web.Blob( [content.toJS].toJS, @@ -13,6 +18,13 @@ void downloadTextFile(String fileName, String content) { final anchor = web.document.createElement('a') as web.HTMLAnchorElement ..href = url ..download = fileName; + web.document.body?.appendChild(anchor); anchor.click(); - web.URL.revokeObjectURL(url); + anchor.remove(); + unawaited( + Future.delayed( + const Duration(seconds: 10), + () => web.URL.revokeObjectURL(url), + ), + ); }