From dd035f8a1518fef2c8865bc0a58f9e771c828f76 Mon Sep 17 00:00:00 2001 From: Pierre HUBERT Date: Mon, 14 Jul 2025 16:03:45 +0200 Subject: [PATCH] Fix PDF rendering on my smartphone --- .../lib/widgets/expense_editor.dart | 13 +-- moneymgr_mobile/lib/widgets/pdf_viewer.dart | 104 ++++++++++++++++++ moneymgr_mobile/pubspec.lock | 16 +-- moneymgr_mobile/pubspec.yaml | 4 +- 4 files changed, 116 insertions(+), 21 deletions(-) create mode 100644 moneymgr_mobile/lib/widgets/pdf_viewer.dart diff --git a/moneymgr_mobile/lib/widgets/expense_editor.dart b/moneymgr_mobile/lib/widgets/expense_editor.dart index b6e65fd..3a76f33 100644 --- a/moneymgr_mobile/lib/widgets/expense_editor.dart +++ b/moneymgr_mobile/lib/widgets/expense_editor.dart @@ -1,16 +1,14 @@ import 'dart:typed_data'; -import 'package:alert_dialog/alert_dialog.dart'; import 'package:confirm_dialog/confirm_dialog.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:flutter_pdfview/flutter_pdfview.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:logging/logging.dart'; import 'package:moneymgr_mobile/services/storage/expenses.dart'; import 'package:moneymgr_mobile/services/storage/prefs.dart'; import 'package:moneymgr_mobile/utils/extensions.dart'; import 'package:moneymgr_mobile/utils/time_utils.dart'; +import 'package:moneymgr_mobile/widgets/pdf_viewer.dart'; class ExpenseEditor extends HookConsumerWidget { final Uint8List file; @@ -88,14 +86,7 @@ class ExpenseEditor extends HookConsumerWidget { children: [ // Expense preview Expanded( - child: PDFView( - pdfData: file, - onError: (e) { - Logger.root.warning("Failed to render PDF $e"); - alert(context, content: Text("Failed to render PDF $e")); - }, - fitPolicy: FitPolicy.BOTH, - ), + child: PDFViewer(pdfBytes: file, fit: BoxFit.contain), ), SizedBox(height: 10), diff --git a/moneymgr_mobile/lib/widgets/pdf_viewer.dart b/moneymgr_mobile/lib/widgets/pdf_viewer.dart new file mode 100644 index 0000000..bcc458c --- /dev/null +++ b/moneymgr_mobile/lib/widgets/pdf_viewer.dart @@ -0,0 +1,104 @@ +import 'dart:io'; +import 'dart:math'; +import 'dart:typed_data'; + +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:path/path.dart' as p; +import 'package:path_provider/path_provider.dart'; +import 'package:pdf_image_renderer/pdf_image_renderer.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'pdf_viewer.g.dart'; + +@riverpod +Future _renderPdf( + Ref ref, { + String? path, + Uint8List? pdfBytes, +}) async { + assert(path != null || pdfBytes != null); + + // Create temporary file if required + var isTemp = false; + if (path == null) { + path = p.join( + (await getTemporaryDirectory()).absolute.path, + "render-${Random().nextInt(10000).toString()}+.pdf", + ); + + await File(path).writeAsBytes(pdfBytes!); + isTemp = true; + } + + try { + final pdf = PdfImageRenderer(path: path); + await pdf.open(); + await pdf.openPage(pageIndex: 0); + + // get the render size after the page is loaded + final size = await pdf.getPageSize(pageIndex: 0); + + // get the actual image of the page + final img = await pdf.renderPage( + pageIndex: 0, + x: 0, + y: 0, + width: size.width, + // you can pass a custom size here to crop the image + height: size.height, + // you can pass a custom size here to crop the image + scale: 1, + // increase the scale for better quality (e.g. for zooming) + background: Colors.white, + ); + + // close the page again + await pdf.closePage(pageIndex: 0); + + // close the PDF after rendering the page + pdf.close(); + + return img!; + } finally { + if (isTemp) { + await File(path).delete(); + } + } +} + +class PDFViewer extends ConsumerWidget { + final String? pdfPath; + final Uint8List? pdfBytes; + final double? width; + final double? height; + final BoxFit? fit; + + const PDFViewer({ + super.key, + this.pdfPath, + this.pdfBytes, + this.width, + this.height, + this.fit, + }) : assert(pdfPath != null || pdfBytes != null); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final provider = ref.watch( + _renderPdfProvider(path: pdfPath, pdfBytes: pdfBytes), + ); + + // Perform a switch-case on the result to handle loading/error states + return switch (provider) { + AsyncData(:final value) => Image( + image: MemoryImage(value), + width: width, + height: height, + fit: fit, + ), + AsyncError(:final error) => Text('PDF error: $error'), + _ => const CircularProgressIndicator(), + }; + } +} diff --git a/moneymgr_mobile/pubspec.lock b/moneymgr_mobile/pubspec.lock index a3f6975..3e030a4 100644 --- a/moneymgr_mobile/pubspec.lock +++ b/moneymgr_mobile/pubspec.lock @@ -382,14 +382,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.4.6" - flutter_pdfview: - dependency: "direct main" - description: - name: flutter_pdfview - sha256: c402ad1f51ba8ea73b9fb04c003ca0a9286118ba5ac9787ee2aa58956b3fcf8a - url: "https://pub.dev" - source: hosted - version: "1.4.1+1" flutter_riverpod: dependency: transitive description: @@ -728,6 +720,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.0" + pdf_image_renderer: + dependency: "direct main" + description: + name: pdf_image_renderer + sha256: "0ec76118b14663f17f9b6a8c29ec59cb1b82e466a3c16fbb2ed9f1b613fc41b7" + url: "https://pub.dev" + source: hosted + version: "1.0.1" petitparser: dependency: transitive description: diff --git a/moneymgr_mobile/pubspec.yaml b/moneymgr_mobile/pubspec.yaml index 906ed78..6f61c51 100644 --- a/moneymgr_mobile/pubspec.yaml +++ b/moneymgr_mobile/pubspec.yaml @@ -89,8 +89,8 @@ dependencies: path_provider: ^2.1.5 path: ^1.9.1 - # PDF viewer - flutter_pdfview: ^1.4.1+1 + # PDF renderer + pdf_image_renderer: ^1.0.1 dev_dependencies: flutter_test: