feat(report): accomplishment report PDF + shared PDF helpers
- 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>
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
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);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user