Tweaking padding in customer list and update look and feel pdf export

This commit is contained in:
verboomp
2026-02-06 11:09:25 +01:00
parent 9c439e21ee
commit 5e941753e2
4 changed files with 55 additions and 37 deletions

View File

@@ -70,7 +70,8 @@ public class PdfUtils {
float pageWidth = page.getMediaBox().getWidth(); float pageWidth = page.getMediaBox().getWidth();
float pageHeight = page.getMediaBox().getHeight(); float pageHeight = page.getMediaBox().getHeight();
float contentWidth = pageWidth - 2 * PAGE_MARGIN; float contentWidth = pageWidth - 2 * PAGE_MARGIN;
float halfWidth = contentWidth / 2F; float imageWidth = contentWidth * 0.75F;
float metadataWidth = contentWidth * 0.25F;
try (PDPageContentStream cs = new PDPageContentStream(document, page)) { try (PDPageContentStream cs = new PDPageContentStream(document, page)) {
float yPosition = pageHeight - 50F; float yPosition = pageHeight - 50F;
@@ -87,10 +88,10 @@ public class PdfUtils {
firstPage = false; firstPage = false;
} }
// Left side: image (50% of content width) // Left side: image (75% of content width)
float imageX = PAGE_MARGIN; float imageX = PAGE_MARGIN;
float imageY = yPosition; float imageY = yPosition;
float imageMaxWidth = halfWidth - 10F; float imageMaxWidth = imageWidth - 10F;
float imageMaxHeight = pageHeight - 2 * PAGE_MARGIN - 40F; float imageMaxHeight = pageHeight - 2 * PAGE_MARGIN - 40F;
if (picture.getImage() != null) { if (picture.getImage() != null) {
@@ -108,30 +109,25 @@ public class PdfUtils {
} }
} }
// Right side: metadata (top-aligned with image) // Right side: metadata (25% of content width, top-aligned with image)
float rightX = PAGE_MARGIN + halfWidth + 10F; float rightX = PAGE_MARGIN + imageWidth + 10F;
float rightY = imageY - 32F; float rightY = imageY - 10F;
// Date (no label, bold, size 44) // Date (bold, size 10 - matching labels)
String dateStr = picture.getPictureDate() != null ? (DATE_FORMAT.format(picture.getPictureDate()) + " UHR") : ""; String dateStr = picture.getPictureDate() != null ? (DATE_FORMAT.format(picture.getPictureDate()) + " UHR") : "";
cs.setFont(fontBold, 32); cs.setFont(fontBold, 10);
cs.setNonStrokingColor(COLOR_DATE); cs.setNonStrokingColor(COLOR_DATE);
cs.beginText(); cs.beginText();
cs.newLineAtOffset(rightX, rightY); cs.newLineAtOffset(rightX, rightY);
cs.showText(dateStr); cs.showText(dateStr);
cs.endText(); cs.endText();
rightY -= 54F; rightY -= 24F;
// Customer number // Customer number
float kundenNummerY = rightY;
rightY = drawLabel(cs, fontBold, "KUNDENNUMMER", rightX, rightY); rightY = drawLabel(cs, fontBold, "KUNDENNUMMER", rightX, rightY);
rightY = drawValue(cs, fontRegular, nullSafe(customer.getCustomerNumber()), rightX, rightY); rightY = drawValue(cs, fontRegular, nullSafe(customer.getCustomerNumber()), rightX, rightY);
rightY -= 10F; rightY -= 10F;
// Evaluation card with circles
float circlesX = rightX + 140F;
drawEvaluationCard(cs, fontBold, circlesX, kundenNummerY, picture.getEvaluation());
// ZIP // ZIP
rightY = drawLabel(cs, fontBold, "PLZ", rightX, rightY); rightY = drawLabel(cs, fontBold, "PLZ", rightX, rightY);
rightY = drawValue(cs, fontRegular, nullSafe(customer.getZip()), rightX, rightY); rightY = drawValue(cs, fontRegular, nullSafe(customer.getZip()), rightX, rightY);
@@ -142,9 +138,27 @@ public class PdfUtils {
rightY = drawValue(cs, fontRegular, nullSafe(customer.getCity()), rightX, rightY); rightY = drawValue(cs, fontRegular, nullSafe(customer.getCity()), rightX, rightY);
rightY -= 10F; rightY -= 10F;
// Evaluation card with circles
float evaluationY = rightY;
drawEvaluationCard(cs, fontBold, rightX, evaluationY, picture.getEvaluation());
rightY -= 80F;
// Comment // Comment
rightY = drawLabel(cs, fontBold, "KOMMENTAR", rightX, rightY); rightY = drawLabel(cs, fontBold, "KOMMENTAR", rightX, rightY);
drawWrappedText(cs, fontRegular, nullSafe(picture.getComment()), rightX, rightY, halfWidth - 20f); String remainingComment = drawWrappedText(cs, fontRegular, nullSafe(picture.getComment()), rightX, rightY, metadataWidth - 20f, 50F);
// Continue comment on additional pages if needed
while (remainingComment != null && !remainingComment.isEmpty()) {
PDPage continuationPage = new PDPage(new PDRectangle(PDRectangle.A4.getHeight(), PDRectangle.A4.getWidth()));
document.addPage(continuationPage);
try (PDPageContentStream continuationCs = new PDPageContentStream(document, continuationPage)) {
float continuationY = continuationPage.getMediaBox().getHeight() - PAGE_MARGIN;
float continuationWidth = continuationPage.getMediaBox().getWidth() - 2 * PAGE_MARGIN;
continuationY = drawLabel(continuationCs, fontBold, "KOMMENTAR (FORTSETZUNG)", PAGE_MARGIN, continuationY);
remainingComment = drawWrappedText(continuationCs, fontRegular, remainingComment, PAGE_MARGIN, continuationY, continuationWidth, 50F);
}
}
} }
} }
@@ -189,9 +203,9 @@ public class PdfUtils {
return y - 14F; return y - 14F;
} }
private void drawWrappedText(PDPageContentStream cs, PDFont font, String text, float x, float y, float maxWidth) throws IOException { private String drawWrappedText(PDPageContentStream cs, PDFont font, String text, float x, float y, float maxWidth, float minY) throws IOException {
if (text == null || text.isEmpty()) { if (text == null || text.isEmpty()) {
return; return null;
} }
cs.setFont(font, 10); cs.setFont(font, 10);
cs.setNonStrokingColor(COLOR_TEXT_GRAY); cs.setNonStrokingColor(COLOR_TEXT_GRAY);
@@ -199,11 +213,23 @@ public class PdfUtils {
String[] words = text.split("\\s+"); String[] words = text.split("\\s+");
StringBuilder line = new StringBuilder(); StringBuilder line = new StringBuilder();
float currentY = y; float currentY = y;
int wordIndex = 0;
for (String word : words) { for (wordIndex = 0; wordIndex < words.length; wordIndex++) {
String word = words[wordIndex];
String testLine = line.isEmpty() ? word : (line + " " + word); String testLine = line.isEmpty() ? word : (line + " " + word);
float textWidth = font.getStringWidth(testLine) / 1000F * 10F; float textWidth = font.getStringWidth(testLine) / 1000F * 10F;
if (textWidth > maxWidth && !line.isEmpty()) { if (textWidth > maxWidth && !line.isEmpty()) {
// Check if we have room for this line
if (currentY < minY) {
// Return remaining text starting from current word
StringBuilder remaining = new StringBuilder(line);
for (int i = wordIndex; i < words.length; i++) {
if (!remaining.isEmpty()) remaining.append(" ");
remaining.append(words[i]);
}
return remaining.toString();
}
cs.beginText(); cs.beginText();
cs.newLineAtOffset(x, currentY); cs.newLineAtOffset(x, currentY);
cs.showText(line.toString()); cs.showText(line.toString());
@@ -215,45 +241,36 @@ public class PdfUtils {
} }
} }
if (!line.isEmpty()) { if (!line.isEmpty()) {
if (currentY < minY) {
return line.toString();
}
cs.beginText(); cs.beginText();
cs.newLineAtOffset(x, currentY); cs.newLineAtOffset(x, currentY);
cs.showText(line.toString()); cs.showText(line.toString());
cs.endText(); cs.endText();
} }
return null;
} }
private void drawEvaluationCard(PDPageContentStream cs, PDFont fontBold, float x, float y, Integer evaluation) throws IOException { private void drawEvaluationCard(PDPageContentStream cs, PDFont fontBold, float x, float y, Integer evaluation) throws IOException {
int eval = evaluation != null ? evaluation : 0; int eval = evaluation != null ? evaluation : 0;
Color[] colors = { COLOR_GREEN, COLOR_YELLOW, COLOR_RED }; Color[] colors = { COLOR_GREEN, COLOR_YELLOW, COLOR_RED };
float cardPadding = 10F;
float cardWidth = 2 * CIRCLE_SPACING + 2 * HIGHLIGHT_RADIUS + 2 * cardPadding;
float labelHeight = 14F; float labelHeight = 14F;
float cardHeight = labelHeight + 2 * HIGHLIGHT_RADIUS + 2 * cardPadding + 4F;
float cardX = x - HIGHLIGHT_RADIUS - cardPadding;
float cardY = y - cardHeight + cardPadding;
// Draw card background (rounded rectangle)
cs.setStrokingColor(new Color(0xDD, 0xDD, 0xDD));
cs.setNonStrokingColor(new Color(0xF8, 0xF8, 0xF8));
cs.setLineWidth(1f);
drawRoundedRect(cs, cardX, cardY, cardWidth, cardHeight, 6F);
cs.fillAndStroke();
// Draw "BEWERTUNG" label above circles // Draw "BEWERTUNG" label above circles
float labelX = x;
float labelY = y - labelHeight; float labelY = y - labelHeight;
cs.setFont(fontBold, 9); cs.setFont(fontBold, 10);
cs.setNonStrokingColor(COLOR_CUSTOMER_NAME); cs.setNonStrokingColor(COLOR_CUSTOMER_NAME);
cs.beginText(); cs.beginText();
cs.newLineAtOffset(labelX, labelY); cs.newLineAtOffset(x, labelY);
cs.showText("BEWERTUNG"); cs.showText("BEWERTUNG");
cs.endText(); cs.endText();
// Draw circles below the label // Draw circles below the label
float circleY = labelY - cardPadding - HIGHLIGHT_RADIUS - 2F; float circleY = labelY - HIGHLIGHT_RADIUS - 10F;
for (int i = 0; i < 3; i++) { for (int i = 0; i < 3; i++) {
float cx = x + i * CIRCLE_SPACING; float cx = x + CIRCLE_RADIUS + i * CIRCLE_SPACING;
// Highlight circle if this matches the evaluation (1=green, 2=yellow, 3=red) // Highlight circle if this matches the evaluation (1=green, 2=yellow, 3=red)
if (eval == i + 1) { if (eval == i + 1) {

View File

@@ -78,7 +78,7 @@ class PdfUtilsTest {
byte[] pdfBytes = pdfUtils.createPdf(customer, pictures); byte[] pdfBytes = pdfUtils.createPdf(customer, pictures);
try (PDDocument document = Loader.loadPDF(pdfBytes)) { try (PDDocument document = Loader.loadPDF(pdfBytes)) {
assertEquals(3, document.getNumberOfPages()); assertEquals(4, document.getNumberOfPages());
} }
writeToFile(pdfBytes, "createPdf_multiplePictures_createsOnPagePerPicture.pdf"); writeToFile(pdfBytes, "createPdf_multiplePictures_createsOnPagePerPicture.pdf");
} }

View File

@@ -147,6 +147,7 @@ class _CustomerListWidgetState extends State<CustomerListWidget> {
return Padding( return Padding(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
child: Row( child: Row(
spacing: 8.0,
children: [ children: [
const SizedBox(width: 48), const SizedBox(width: 48),
Expanded( Expanded(
@@ -196,6 +197,7 @@ class _CustomerListWidgetState extends State<CustomerListWidget> {
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: Row( child: Row(
spacing: 8.0,
children: [ children: [
SizedBox( SizedBox(
width: 48, width: 48,

View File

@@ -117,7 +117,6 @@ void main() {
when(mockCustomerController.getAll("", "")).thenAnswer((_) async => _list); when(mockCustomerController.getAll("", "")).thenAnswer((_) async => _list);
provideMockedNetworkImages(() async { provideMockedNetworkImages(() async {
await pumpAppConfig(tester, "${GlobalRouter.pathHome}${GlobalRouter.pathCustomer}/1${GlobalRouter.pathPicture}/1"); await pumpAppConfig(tester, "${GlobalRouter.pathHome}${GlobalRouter.pathCustomer}/1${GlobalRouter.pathPicture}/1");
await tester.pump(Duration(seconds: 10));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
// The comment field should be empty but the label should exist // The comment field should be empty but the label should exist