Merge branch 'fix/chrome_save' into feat/mobile

This commit is contained in:
insleker 2025-09-24 11:43:52 +08:00
commit 9e0ae1dcfe
7 changed files with 38 additions and 17 deletions

View File

@ -182,6 +182,7 @@ class DocumentStateNotifier extends StateNotifier<Document> {
); );
if (result != null) return result; if (result != null) return result;
} catch (_) { } catch (_) {
debugPrint('Warning: export in isolate failed');
// Fall back to main-isolate export if isolate fails (e.g., engine limitations). // Fall back to main-isolate export if isolate fails (e.g., engine limitations).
} }

View File

@ -6,6 +6,7 @@ import 'package:image/image.dart' as img;
import 'package:pdf/widgets.dart' as pw; import 'package:pdf/widgets.dart' as pw;
import 'package:pdf/pdf.dart' as pdf; import 'package:pdf/pdf.dart' as pdf;
import 'package:pdfrx_engine/pdfrx_engine.dart' as engine; import 'package:pdfrx_engine/pdfrx_engine.dart' as engine;
import 'package:pdfrx/pdfrx.dart' show pdfrxFlutterInitialize;
import '../../domain/models/model.dart'; import '../../domain/models/model.dart';
import '../../utils/rotation_utils.dart' as rot; import '../../utils/rotation_utils.dart' as rot;
import '../../utils/background_removal.dart' as br; import '../../utils/background_removal.dart' as br;
@ -104,15 +105,14 @@ class ExportService {
} }
// Initialize engine (safe to call multiple times) // Initialize engine (safe to call multiple times)
try { pdfrxFlutterInitialize();
await engine.pdfrxInitialize();
} catch (_) {}
// Open source document from memory; if not supported, write temp file // Open source document from memory; if not supported, write temp file
engine.PdfDocument? doc; engine.PdfDocument? doc;
try { try {
doc = await engine.PdfDocument.openData(srcBytes); doc = await engine.PdfDocument.openData(srcBytes);
} catch (_) { } catch (_) {
debugPrint('Warning: pdfrx openData failed');
final tmp = File( final tmp = File(
'${Directory.systemTemp.path}/pdfrx_src_${DateTime.now().millisecondsSinceEpoch}.pdf', '${Directory.systemTemp.path}/pdfrx_src_${DateTime.now().millisecondsSinceEpoch}.pdf',
); );
@ -120,7 +120,9 @@ class ExportService {
doc = await engine.PdfDocument.openFile(tmp.path); doc = await engine.PdfDocument.openFile(tmp.path);
try { try {
tmp.deleteSync(); tmp.deleteSync();
} catch (_) {} } catch (_) {
debugPrint('Warning: temp file delete failed');
}
} }
// doc is guaranteed to be assigned by either openData or openFile above // doc is guaranteed to be assigned by either openData or openFile above
@ -221,6 +223,7 @@ class ExportService {
final bytes = await out.save(); final bytes = await out.save();
doc.dispose(); doc.dispose();
debugPrint('exportSignedPdfFromBytes succeeded');
return bytes; return bytes;
} }
@ -233,6 +236,7 @@ class ExportService {
await file.writeAsBytes(bytes, flush: true); await file.writeAsBytes(bytes, flush: true);
return true; return true;
} catch (_) { } catch (_) {
debugPrint('Error: saveBytesToFile failed');
return false; return false;
} }
} }

View File

@ -221,6 +221,8 @@ class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
if (out != null) { if (out != null) {
ok = await downloadBytes(out, filename: suggested); ok = await downloadBytes(out, filename: suggested);
savedPath = suggested; savedPath = suggested;
} else {
debugPrint('_saveSignedPdf: export to bytes failed');
} }
} }
if (!kIsWeb) { if (!kIsWeb) {
@ -235,7 +237,6 @@ class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
), ),
), ),
); );
// ignore: avoid_print
debugPrint('_saveSignedPdf: SnackBar shown ok=' + ok.toString()); debugPrint('_saveSignedPdf: SnackBar shown ok=' + ok.toString());
} else { } else {
messenger.showSnackBar( messenger.showSnackBar(

View File

@ -1,11 +1,17 @@
import 'dart:typed_data'; import 'package:flutter/foundation.dart';
import 'download_stub.dart' if (dart.library.html) 'download_web.dart' as impl; // On modern Flutter Web (Wasm GC, e.g., Chromium), dart:html is not available.
// Use js_interop capability to select the web implementation that relies on
// package:web instead of dart:html.
import 'download_stub.dart'
if (dart.library.js_interop) 'download_web.dart'
as impl;
/// Initiates a platform-appropriate download/save operation. /// Initiates a platform-appropriate download/save operation.
/// ///
/// On Web: triggers a browser download with the provided filename. /// On Web: triggers a browser download with the provided filename.
/// On non-Web: returns false (no-op). Use your existing IO save flow instead. /// On non-Web: returns false (no-op). Use your existing IO save flow instead.
Future<bool> downloadBytes(Uint8List bytes, {required String filename}) { Future<bool> downloadBytes(Uint8List bytes, {required String filename}) {
debugPrint('downloadBytes: initiating download');
return impl.downloadBytes(bytes, filename: filename); return impl.downloadBytes(bytes, filename: filename);
} }

View File

@ -1,6 +1,9 @@
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:flutter/widgets.dart';
Future<bool> downloadBytes(Uint8List bytes, {required String filename}) async { Future<bool> downloadBytes(Uint8List bytes, {required String filename}) async {
// Not supported on non-web. Return false so caller can fallback to file save. // Not supported on non-web. Return false so caller can fallback to file save.
debugPrint('downloadBytes: not supported on this platform');
return false; return false;
} }

View File

@ -1,23 +1,28 @@
// ignore_for_file: deprecated_member_use // Implementation for Web using package:web to support Wasm GC (Chromium)
// ignore: avoid_web_libraries_in_flutter // without importing dart:html directly.
import 'dart:html' as html; import 'dart:convert';
import 'dart:typed_data'; import 'package:flutter/foundation.dart';
import 'package:web/web.dart' as web;
Future<bool> downloadBytes(Uint8List bytes, {required String filename}) async { Future<bool> downloadBytes(Uint8List bytes, {required String filename}) async {
try { try {
final blob = html.Blob([bytes], 'application/pdf'); // Use a data URL to avoid Blob/typed array interop issues under Wasm GC.
final url = html.Url.createObjectUrlFromBlob(blob); final url = 'data:application/pdf;base64,${base64Encode(bytes)}';
// Create an anchor element and trigger a click to download
final anchor = final anchor =
html.document.createElement('a') as html.AnchorElement web.HTMLAnchorElement()
..href = url ..href = url
..download = filename ..download = filename
..style.display = 'none'; ..style.display = 'none';
html.document.body?.children.add(anchor);
web.document.body?.append(anchor);
anchor.click(); anchor.click();
anchor.remove(); anchor.remove();
html.Url.revokeObjectUrl(url);
return true; return true;
} catch (_) { } catch (e, st) {
debugPrint('Error: downloadBytes failed: $e\n$st');
return false; return false;
} }
} }

View File

@ -62,6 +62,7 @@ dependencies:
responsive_framework: ^1.5.1 responsive_framework: ^1.5.1
# disable_web_context_menu: ^1.1.0 # disable_web_context_menu: ^1.1.0
# ml_linalg: ^13.12.6 # ml_linalg: ^13.12.6
web: ^1.1.1
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: