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:
2026-09-28 18:55:45 +08:00
parent 87014d1656
commit 167e208ffa
4 changed files with 579 additions and 0 deletions
+81
View File
@@ -0,0 +1,81 @@
import 'package:flutter/services.dart' show rootBundle;
import 'package:pdf/pdf.dart' as pdf;
import 'package:pdf/widgets.dart' as pw;
/// Shared brand assets and letterhead widget for CRMC PDF documents.
class PdfBrand {
final pw.Font regular;
final pw.Font bold;
final pw.ImageProvider logo;
const PdfBrand({
required this.regular,
required this.bold,
required this.logo,
});
/// Load fonts and logo from the Flutter asset bundle.
static Future<PdfBrand> load() async {
final logoData = await rootBundle.load('assets/crmc_logo.png');
final logo = pw.MemoryImage(logoData.buffer.asUint8List());
final regular = pw.Font.ttf(
await rootBundle.load('assets/fonts/Roboto-Regular.ttf'),
);
final bold = pw.Font.ttf(
await rootBundle.load('assets/fonts/Roboto-Bold.ttf'),
);
return PdfBrand(regular: regular, bold: bold, logo: logo);
}
/// Standard CRMC letterhead: 60×60 logo + four-line organisation text.
pw.Widget letterhead() {
return pw.Row(
mainAxisAlignment: pw.MainAxisAlignment.center,
crossAxisAlignment: pw.CrossAxisAlignment.center,
children: [
pw.Image(logo, width: 60, height: 60),
pw.SizedBox(width: 16),
pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.center,
children: [
pw.Text(
'Republic of the Philippines',
style: pw.TextStyle(font: regular, fontSize: 9),
),
pw.Text(
'Department of Health',
style: pw.TextStyle(font: regular, fontSize: 9),
),
pw.Text(
'Cotabato Regional and Medical Center',
style: pw.TextStyle(font: regular, fontSize: 9),
),
pw.SizedBox(height: 3),
pw.Text(
'Integrated Hospital Operations and Management Program (IHOMP)',
style: pw.TextStyle(font: bold, fontSize: 9),
),
],
),
],
);
}
/// Convenience: create a ThemeData from these fonts.
pw.ThemeData theme() => pw.ThemeData.withFont(base: regular, bold: bold);
/// Footer widget: right-aligned "Page x of y".
pw.Widget pageFooter(pw.Context ctx) => pw.Align(
alignment: pw.Alignment.centerRight,
child: pw.Text(
'Page ${ctx.pageNumber} of ${ctx.pagesCount}',
style: pw.TextStyle(
font: regular,
fontSize: 8,
color: pdf.PdfColors.grey600,
),
),
);
}