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(), }; } }