Files
MoneyMgr/moneymgr_mobile/lib/widgets/pdf_viewer.dart
Pierre HUBERT dd035f8a15
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Fix PDF rendering on my smartphone
2025-07-14 16:03:45 +02:00

105 lines
2.5 KiB
Dart

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