fix: PDF zoom boundaries, Mängel status display in list view, and swipeable image gallery with PDF export
Build Android APK / Build Release APKs (push) Successful in 14m49s
Build Android APK / Build Release APKs (push) Successful in 14m49s
This commit is contained in:
+106
-18
@@ -35,8 +35,15 @@ class PdfService {
|
||||
/// Print single post item as PDF
|
||||
static Future<void> printSinglePost(PostItem item) async {
|
||||
final pdf = pw.Document();
|
||||
final formattedDate = DateFormat('dd.MM.yyyy HH:mm').format(item.createdDate);
|
||||
final pw.ImageProvider? imageProvider = await _fetchImage(item.imageUrl);
|
||||
final formattedDate =
|
||||
DateFormat('dd.MM.yyyy HH:mm').format(item.createdDate);
|
||||
|
||||
final List<String> 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<pw.ImageProvider>().toList();
|
||||
|
||||
pdf.addPage(
|
||||
pw.MultiPage(
|
||||
@@ -58,7 +65,8 @@ class PdfService {
|
||||
),
|
||||
pw.Text(
|
||||
formattedDate,
|
||||
style: const pw.TextStyle(fontSize: 12, color: PdfColors.grey700),
|
||||
style:
|
||||
const pw.TextStyle(fontSize: 12, color: PdfColors.grey700),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -68,6 +76,53 @@ class PdfService {
|
||||
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(
|
||||
@@ -84,13 +139,29 @@ class PdfService {
|
||||
],
|
||||
pw.Divider(thickness: 1, color: PdfColors.grey400),
|
||||
pw.SizedBox(height: 10),
|
||||
if (imageProvider != null) ...[
|
||||
pw.Center(
|
||||
child: pw.Container(
|
||||
height: 220,
|
||||
child: pw.Image(imageProvider, fit: pw.BoxFit.contain),
|
||||
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(
|
||||
@@ -123,8 +194,10 @@ class PdfService {
|
||||
// Preload all images in parallel
|
||||
final Map<String, pw.ImageProvider?> imageMap = {};
|
||||
await Future.wait(items.map((item) async {
|
||||
if (item.imageUrl.isNotEmpty) {
|
||||
imageMap[item.id] = await _fetchImage(item.imageUrl);
|
||||
final url =
|
||||
item.imageUrls.isNotEmpty ? item.imageUrls.first : item.imageUrl;
|
||||
if (url.isNotEmpty) {
|
||||
imageMap[item.id] = await _fetchImage(url);
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -137,7 +210,8 @@ class PdfService {
|
||||
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)),
|
||||
border: pw.Border(
|
||||
bottom: pw.BorderSide(color: PdfColors.blue800, width: 2)),
|
||||
),
|
||||
child: pw.Row(
|
||||
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
|
||||
@@ -152,7 +226,8 @@ class PdfService {
|
||||
),
|
||||
pw.Text(
|
||||
'Erstellt am: ${DateFormat('dd.MM.yyyy HH:mm').format(DateTime.now())}',
|
||||
style: const pw.TextStyle(fontSize: 10, color: PdfColors.grey700),
|
||||
style:
|
||||
const pw.TextStyle(fontSize: 10, color: PdfColors.grey700),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -160,7 +235,8 @@ class PdfService {
|
||||
build: (context) => [
|
||||
pw.TableHelper.fromTextArray(
|
||||
border: pw.TableBorder.all(color: PdfColors.grey400, width: 0.5),
|
||||
headerStyle: pw.TextStyle(fontWeight: pw.FontWeight.bold, fontSize: 10),
|
||||
headerStyle:
|
||||
pw.TextStyle(fontWeight: pw.FontWeight.bold, fontSize: 10),
|
||||
headerDecoration: const pw.BoxDecoration(color: PdfColors.grey200),
|
||||
cellHeight: 65,
|
||||
cellAlignments: {
|
||||
@@ -170,20 +246,32 @@ class PdfService {
|
||||
3: pw.Alignment.centerLeft,
|
||||
4: pw.Alignment.center,
|
||||
},
|
||||
headers: <String>['Datum', 'Örtlichkeit / Kat.', 'Titel & Mangel', 'Status', 'Foto'],
|
||||
headers: <String>[
|
||||
'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 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',
|
||||
imageMap[item.id] != null ? 'Foto vorhanden' : '-',
|
||||
imageLabel,
|
||||
];
|
||||
}).toList(),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user