import 'package:flutter/foundation.dart'; import 'package:http/http.dart' as http; import 'package:image/image.dart' as img; import 'package:intl/intl.dart'; import 'package:pdf/pdf.dart'; import 'package:pdf/widgets.dart' as pw; import 'package:printing/printing.dart'; import '../models/post_item.dart'; class PdfService { static Future _fetchImage(String url) async { if (url.isEmpty) return null; try { final response = await http.get(Uri.parse(url)).timeout(const Duration(seconds: 8)); if (response.statusCode == 200) { final Uint8List bytes = response.bodyBytes; // Optionally resize image to save PDF memory final decoded = img.decodeImage(bytes); if (decoded != null) { final resized = img.copyResize(decoded, width: 300); final encoded = Uint8List.fromList(img.encodeJpg(resized, quality: 80)); return pw.MemoryImage(encoded); } return pw.MemoryImage(bytes); } } catch (e) { if (kDebugMode) { debugPrint('Failed to load image for PDF: $e'); } } return null; } /// Print single post item as PDF static Future printSinglePost(PostItem item) async { final pdf = pw.Document(); final formattedDate = DateFormat('dd.MM.yyyy HH:mm').format(item.createdDate); final List urlsToFetch = item.imageUrls.isNotEmpty ? item.imageUrls : (item.imageUrl.isNotEmpty ? [item.imageUrl] : []); final results = await Future.wait(urlsToFetch.map((url) => _fetchImage(url))); final imageProviders = results.whereType().toList(); pdf.addPage( pw.MultiPage( pageFormat: PdfPageFormat.a4, margin: const pw.EdgeInsets.all(32), build: (context) => [ pw.Header( level: 0, child: pw.Row( mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, children: [ pw.Text( 'Marktgemeinde Oggau', style: pw.TextStyle( fontSize: 18, fontWeight: pw.FontWeight.bold, color: PdfColors.blue800, ), ), pw.Text( formattedDate, style: const pw.TextStyle(fontSize: 12, color: PdfColors.grey700), ), ], ), ), pw.SizedBox(height: 12), pw.Text( item.title, style: pw.TextStyle(fontSize: 22, fontWeight: pw.FontWeight.bold), ), if (item.status != null && item.status!.isNotEmpty) ...[ pw.SizedBox(height: 6), pw.Row( children: [ pw.Text( 'Status: ', style: pw.TextStyle( fontSize: 12, fontWeight: pw.FontWeight.bold, color: PdfColors.grey800, ), ), pw.Container( padding: const pw.EdgeInsets.symmetric(horizontal: 8, vertical: 3), decoration: pw.BoxDecoration( color: item.status == 'Erledigt' ? const PdfColor.fromInt(0xFFE8F5E9) : (item.status == 'In Bearbeitung' ? const PdfColor.fromInt(0xFFE3F2FD) : const PdfColor.fromInt(0xFFFFF3E0)), borderRadius: const pw.BorderRadius.all(pw.Radius.circular(4)), border: pw.Border.all( color: item.status == 'Erledigt' ? const PdfColor.fromInt(0xFF81C784) : (item.status == 'In Bearbeitung' ? const PdfColor.fromInt(0xFF64B5F6) : const PdfColor.fromInt(0xFFFFB74D)), ), ), child: pw.Text( item.status!, style: pw.TextStyle( fontSize: 11, fontWeight: pw.FontWeight.bold, color: item.status == 'Erledigt' ? const PdfColor.fromInt(0xFF2E7D32) : (item.status == 'In Bearbeitung' ? const PdfColor.fromInt(0xFF1565C0) : const PdfColor.fromInt(0xFFE65100)), ), ), ), ], ), ], if (item.street != null && item.street!.isNotEmpty) ...[ pw.SizedBox(height: 6), pw.Text( 'Örtlichkeit / Straße: ${item.street}', style: pw.TextStyle(fontSize: 13, fontWeight: pw.FontWeight.bold), ), ], if (item.category != null && item.category!.isNotEmpty) ...[ pw.SizedBox(height: 4), pw.Text( 'Kategorie: ${item.category}', style: const pw.TextStyle(fontSize: 12, color: PdfColors.grey800), ), ], pw.Divider(thickness: 1, color: PdfColors.grey400), pw.SizedBox(height: 10), if (imageProviders.isNotEmpty) ...[ if (imageProviders.length == 1) pw.Center( child: pw.Container( height: 220, child: pw.Image(imageProviders.first, fit: pw.BoxFit.contain), ), ) else pw.Row( children: [ for (int i = 0; i < imageProviders.length; i++) ...[ if (i > 0) pw.SizedBox(width: 12), pw.Expanded( child: pw.Container( height: 180, child: pw.Image(imageProviders[i], fit: pw.BoxFit.contain), ), ), ], ], ), pw.SizedBox(height: 14), ], pw.Text( 'Beschreibung:', style: pw.TextStyle(fontSize: 14, fontWeight: pw.FontWeight.bold), ), pw.SizedBox(height: 6), pw.Paragraph( text: item.description, style: const pw.TextStyle(fontSize: 12, lineSpacing: 1.5), ), ], ), ); final cleanTitle = item.title.replaceAll(RegExp(r'[\\/:*?"<>|]'), '_'); await Printing.sharePdf( bytes: await pdf.save(), filename: '$cleanTitle.pdf', ); } /// Print a list of selected items in table format for Bauhof / Gemeinde static Future printSelectedItemsTable({ required List items, required String title, }) async { final pdf = pw.Document(); // Preload all images in parallel final Map imageMap = {}; await Future.wait(items.map((item) async { final url = item.imageUrls.isNotEmpty ? item.imageUrls.first : item.imageUrl; if (url.isNotEmpty) { imageMap[item.id] = await _fetchImage(url); } })); pdf.addPage( pw.MultiPage( pageFormat: PdfPageFormat.a4.landscape, margin: const pw.EdgeInsets.all(24), header: (context) => pw.Container( alignment: pw.Alignment.centerLeft, margin: const pw.EdgeInsets.only(bottom: 12), padding: const pw.EdgeInsets.only(bottom: 6), decoration: const pw.BoxDecoration( border: pw.Border( bottom: pw.BorderSide(color: PdfColors.blue800, width: 2)), ), child: pw.Row( mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, children: [ pw.Text( 'Oggauer Gemeindetrommler - $title', style: pw.TextStyle( fontSize: 16, fontWeight: pw.FontWeight.bold, color: PdfColors.blue800, ), ), pw.Text( 'Erstellt am: ${DateFormat('dd.MM.yyyy HH:mm').format(DateTime.now())}', style: const pw.TextStyle(fontSize: 10, color: PdfColors.grey700), ), ], ), ), build: (context) => [ pw.TableHelper.fromTextArray( border: pw.TableBorder.all(color: PdfColors.grey400, width: 0.5), headerStyle: pw.TextStyle(fontWeight: pw.FontWeight.bold, fontSize: 10), headerDecoration: const pw.BoxDecoration(color: PdfColors.grey200), cellHeight: 65, cellAlignments: { 0: pw.Alignment.centerLeft, 1: pw.Alignment.centerLeft, 2: pw.Alignment.centerLeft, 3: pw.Alignment.centerLeft, 4: pw.Alignment.center, }, headers: [ 'Datum', 'Örtlichkeit / Kat.', 'Titel & Mangel', 'Status', 'Foto' ], data: items.map((item) { final formattedDate = DateFormat('dd.MM.yyyy').format(item.createdDate); final locInfo = '${item.street ?? "Keine Angabe"}\n(${item.category ?? "-"})'; final descSnippet = item.description.length > 120 ? '${item.description.substring(0, 120)}...' : item.description; final imageCount = item.imageUrls.length; final imageLabel = imageMap[item.id] != null ? (imageCount > 1 ? '$imageCount Fotos' : 'Foto vorhanden') : '-'; return [ formattedDate, locInfo, '${item.title}\n$descSnippet', item.status ?? 'Gemeldet', imageLabel, ]; }).toList(), ), ], ), ); final filename = '${title.toLowerCase().replaceAll(' ', '_')}_tabelle.pdf'; await Printing.sharePdf(bytes: await pdf.save(), filename: filename); } }