167e208ffa
- lib/utils/pdf/pdf_letterhead.dart: PdfBrand with load(), letterhead(), theme(), and pageFooter() shared across CRMC PDF documents - lib/widgets/pdf_preview_dialog.dart: showPdfPreviewDialog() with pdfrx PdfViewer.data preview, Print (Printing.layoutPdf) and Download (Printing.sharePdf) buttons - lib/screens/programmer_tasks/report/accomplishment_report_pdf.dart: buildAccomplishmentReportPdf() — A4 MultiPage with letterhead, info box, accomplishment table, footnote, summary, signatures, page footer - test/accomplishment_report_pdf_test.dart: 2 passing tests Co-Authored-By: claude-flow <ruv@ruv.net>
125 lines
3.7 KiB
Dart
125 lines
3.7 KiB
Dart
import 'dart:typed_data';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:pdfrx/pdfrx.dart';
|
|
import 'package:printing/printing.dart';
|
|
|
|
/// Show a PDF preview dialog with Print and Download buttons.
|
|
///
|
|
/// [buildBytes] is called once and cached; the result is shared between the
|
|
/// preview viewer and the action buttons.
|
|
Future<void> showPdfPreviewDialog(
|
|
BuildContext context, {
|
|
required String title,
|
|
required String filename,
|
|
required Future<Uint8List> Function() buildBytes,
|
|
}) {
|
|
return showDialog<void>(
|
|
context: context,
|
|
builder: (_) => _PdfPreviewDialog(
|
|
title: title,
|
|
filename: filename,
|
|
buildBytes: buildBytes,
|
|
),
|
|
);
|
|
}
|
|
|
|
class _PdfPreviewDialog extends StatefulWidget {
|
|
const _PdfPreviewDialog({
|
|
required this.title,
|
|
required this.filename,
|
|
required this.buildBytes,
|
|
});
|
|
|
|
final String title;
|
|
final String filename;
|
|
final Future<Uint8List> Function() buildBytes;
|
|
|
|
@override
|
|
State<_PdfPreviewDialog> createState() => _PdfPreviewDialogState();
|
|
}
|
|
|
|
class _PdfPreviewDialogState extends State<_PdfPreviewDialog> {
|
|
late final Future<Uint8List> _future;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_future = widget.buildBytes();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return AlertDialog(
|
|
contentPadding: EdgeInsets.zero,
|
|
content: SizedBox(
|
|
width: 800,
|
|
height: 900,
|
|
child: Column(
|
|
children: [
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
|
child: Row(
|
|
children: [
|
|
Expanded(
|
|
child: Text(
|
|
widget.title,
|
|
style: const TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
),
|
|
IconButton(
|
|
tooltip: 'Print',
|
|
icon: const Icon(Icons.print),
|
|
onPressed: () async {
|
|
final bytes = await _future;
|
|
await Printing.layoutPdf(
|
|
onLayout: (_) async => bytes,
|
|
name: widget.filename,
|
|
);
|
|
},
|
|
),
|
|
IconButton(
|
|
tooltip: 'Download',
|
|
icon: const Icon(Icons.download),
|
|
onPressed: () async {
|
|
final bytes = await _future;
|
|
await Printing.sharePdf(
|
|
bytes: bytes,
|
|
filename: widget.filename,
|
|
);
|
|
},
|
|
),
|
|
IconButton(
|
|
tooltip: 'Close',
|
|
icon: const Icon(Icons.close),
|
|
onPressed: () => Navigator.of(context).pop(),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const Divider(height: 1),
|
|
Expanded(
|
|
child: FutureBuilder<Uint8List>(
|
|
future: _future,
|
|
builder: (context, snapshot) {
|
|
if (snapshot.connectionState == ConnectionState.waiting) {
|
|
return const Center(child: CircularProgressIndicator());
|
|
}
|
|
if (snapshot.hasError) {
|
|
return Center(child: Text(snapshot.error.toString()));
|
|
}
|
|
final data = snapshot.data;
|
|
if (data == null) return const SizedBox.shrink();
|
|
return PdfViewer.data(data, sourceName: widget.filename);
|
|
},
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|