fix(#433): address Gemini review (BuildContext async-gap + web download)

- LogExport.shareLogs/shareFile now resolve ScaffoldMessenger and l10n strings
  off the context BEFORE the flush await, and _exportFile takes them as values
  (no BuildContext use across the async gap; avoids a deactivated-ancestor crash
  on Back-during-flush).
- Web download: append the anchor to the document before click() (Firefox needs
  a connected anchor) and revoke the object URL on a delay (synchronous revoke
  can abort the download in Safari/iOS).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
pull/436/head
Strycher 2 months ago
parent 9aa83da995
commit 26e0cb3211

@ -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<void> _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) {

@ -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(
<JSAny>[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<void>.delayed(
const Duration(seconds: 10),
() => web.URL.revokeObjectURL(url),
),
);
}

Loading…
Cancel
Save

Powered by TurnKey Linux.