From c8d9480cf988cb655b45c0c7fb1712d73362b3b8 Mon Sep 17 00:00:00 2001 From: patrick Date: Sat, 12 Sep 2026 22:23:31 +0200 Subject: [PATCH] =?UTF-8?q?perf(mangel):=20Foto-Kompression=20vor=20Upload?= =?UTF-8?q?=20in=20beiden=20Clients=20erg=C3=A4nzt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PWA lud Mangel-Fotos bislang komplett unkomprimiert hoch (rohes File aus capture-Input) - Canvas-basierte Verkleinerung/Neukodierung (max. 1600px, JPEG q0.7) vor dem Enqueue ergänzt. Flutter-Client nutzte nur imageQuality (reine JPEG-Kompression ohne Auflösungs-Downscale) - maxWidth/maxHeight ergänzt. Beides zahlt sich bei schlechter Feld-Verbindung aus. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01VDMvneg6wKfXdXvTYNd3dN --- DEVLOG.md | 204 ++++++++++++++++++ .../lib/screens/mangel_melden_screen.dart | 12 +- frontend/src/offline/fotoKomprimierung.ts | 34 +++ frontend/src/pages/MangelListePage.tsx | 6 +- 4 files changed, 254 insertions(+), 2 deletions(-) create mode 100644 frontend/src/offline/fotoKomprimierung.ts diff --git a/DEVLOG.md b/DEVLOG.md index 949cee9..6e1f95a 100644 --- a/DEVLOG.md +++ b/DEVLOG.md @@ -14795,3 +14795,207 @@ Keine Commits in dieser Session. - frontend/DEVLOG.md | 387 ++++++++++++ --- +## 2026-09-12 22:01 – 22:11 (9m) +**Beschreibung:** Claude Code Session +**Projekt:** asb-material + +### Commits +- ecca012 fix(flutter): Sync-Abbruch-Bug in Offline-Queues behoben, API-URL konfigurierbar + +### Geänderte Dateien +- DEVLOG.md | 144 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +- arbeitskacheln/15_mobile.md | 12 +++++++ +- flutter_app/lib/main.dart | 12 ++++--- +- flutter_app/lib/offline/mangel_queue.dart | 14 +++++++- +- flutter_app/lib/offline/queue.dart | 20 ++++++++++- +- flutter_app/test/mangel_queue_test.dart | 143 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +- flutter_app/test/offline_queue_test.dart | 6 ++++ + +--- +## 2026-09-12 22:12 – 22:12 (0m) +**Beschreibung:** Claude Code Session +**Projekt:** asb-material + +### Commits +Keine Commits in dieser Session. + +### Geänderte Dateien +- DEVLOG.md | 144 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +- arbeitskacheln/15_mobile.md | 12 +++++++ +- flutter_app/lib/main.dart | 12 ++++--- +- flutter_app/lib/offline/mangel_queue.dart | 14 +++++++- +- flutter_app/lib/offline/queue.dart | 20 ++++++++++- +- flutter_app/test/mangel_queue_test.dart | 143 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +- flutter_app/test/offline_queue_test.dart | 6 ++++ + +--- +## 2026-09-12 22:14 – 22:15 (0m) +**Beschreibung:** Claude Code Session +**Projekt:** asb-material + +### Commits +Keine Commits in dieser Session. + +### Geänderte Dateien +- DEVLOG.md | 144 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +- arbeitskacheln/15_mobile.md | 12 +++++++ +- flutter_app/lib/main.dart | 12 ++++--- +- flutter_app/lib/offline/mangel_queue.dart | 14 +++++++- +- flutter_app/lib/offline/queue.dart | 20 ++++++++++- +- flutter_app/test/mangel_queue_test.dart | 143 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +- flutter_app/test/offline_queue_test.dart | 6 ++++ + +--- +## 2026-09-12 22:15 – 22:15 (0m) +**Beschreibung:** Claude Code Session +**Projekt:** asb-material + +### Commits +Keine Commits in dieser Session. + +### Geänderte Dateien +- DEVLOG.md | 144 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +- arbeitskacheln/15_mobile.md | 12 +++++++ +- flutter_app/lib/main.dart | 12 ++++--- +- flutter_app/lib/offline/mangel_queue.dart | 14 +++++++- +- flutter_app/lib/offline/queue.dart | 20 ++++++++++- +- flutter_app/test/mangel_queue_test.dart | 143 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +- flutter_app/test/offline_queue_test.dart | 6 ++++ + +--- +## 2026-09-12 22:15 – 22:15 (0m) +**Beschreibung:** Claude Code Session +**Projekt:** asb-material + +### Commits +Keine Commits in dieser Session. + +### Geänderte Dateien +- DEVLOG.md | 144 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +- arbeitskacheln/15_mobile.md | 12 +++++++ +- flutter_app/lib/main.dart | 12 ++++--- +- flutter_app/lib/offline/mangel_queue.dart | 14 +++++++- +- flutter_app/lib/offline/queue.dart | 20 ++++++++++- +- flutter_app/test/mangel_queue_test.dart | 143 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +- flutter_app/test/offline_queue_test.dart | 6 ++++ + +--- +## 2026-09-12 22:16 – 22:17 (0m) +**Beschreibung:** Claude Code Session +**Projekt:** asb-material + +### Commits +Keine Commits in dieser Session. + +### Geänderte Dateien +- DEVLOG.md | 144 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +- arbeitskacheln/15_mobile.md | 12 +++++++ +- flutter_app/lib/main.dart | 12 ++++--- +- flutter_app/lib/offline/mangel_queue.dart | 14 +++++++- +- flutter_app/lib/offline/queue.dart | 20 ++++++++++- +- flutter_app/test/mangel_queue_test.dart | 143 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +- flutter_app/test/offline_queue_test.dart | 6 ++++ + +--- +## 2026-09-12 22:17 – 22:17 (0m) +**Beschreibung:** Claude Code Session +**Projekt:** asb-material + +### Commits +Keine Commits in dieser Session. + +### Geänderte Dateien +- DEVLOG.md | 144 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +- arbeitskacheln/15_mobile.md | 12 +++++++ +- flutter_app/lib/main.dart | 12 ++++--- +- flutter_app/lib/offline/mangel_queue.dart | 14 +++++++- +- flutter_app/lib/offline/queue.dart | 20 ++++++++++- +- flutter_app/test/mangel_queue_test.dart | 143 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +- flutter_app/test/offline_queue_test.dart | 6 ++++ + +--- +## 2026-09-12 22:18 – 22:18 (0m) +**Beschreibung:** Claude Code Session +**Projekt:** asb-material + +### Commits +Keine Commits in dieser Session. + +### Geänderte Dateien +- DEVLOG.md | 144 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +- arbeitskacheln/15_mobile.md | 12 +++++++ +- flutter_app/lib/main.dart | 12 ++++--- +- flutter_app/lib/offline/mangel_queue.dart | 14 +++++++- +- flutter_app/lib/offline/queue.dart | 20 ++++++++++- +- flutter_app/test/mangel_queue_test.dart | 143 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +- flutter_app/test/offline_queue_test.dart | 6 ++++ + +--- +## 2026-09-12 22:19 – 22:19 (0m) +**Beschreibung:** Claude Code Session +**Projekt:** asb-material + +### Commits +Keine Commits in dieser Session. + +### Geänderte Dateien +- DEVLOG.md | 144 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +- arbeitskacheln/15_mobile.md | 12 +++++++ +- flutter_app/lib/main.dart | 12 ++++--- +- flutter_app/lib/offline/mangel_queue.dart | 14 +++++++- +- flutter_app/lib/offline/queue.dart | 20 ++++++++++- +- flutter_app/test/mangel_queue_test.dart | 143 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +- flutter_app/test/offline_queue_test.dart | 6 ++++ + +--- +## 2026-09-12 22:20 – 22:20 (0m) +**Beschreibung:** Claude Code Session +**Projekt:** asb-material + +### Commits +Keine Commits in dieser Session. + +### Geänderte Dateien +- DEVLOG.md | 144 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +- arbeitskacheln/15_mobile.md | 12 +++++++ +- flutter_app/lib/main.dart | 12 ++++--- +- flutter_app/lib/offline/mangel_queue.dart | 14 +++++++- +- flutter_app/lib/offline/queue.dart | 20 ++++++++++- +- flutter_app/test/mangel_queue_test.dart | 143 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +- flutter_app/test/offline_queue_test.dart | 6 ++++ + +--- +## 2026-09-12 22:21 – 22:21 (0m) +**Beschreibung:** Claude Code Session +**Projekt:** asb-material + +### Commits +Keine Commits in dieser Session. + +### Geänderte Dateien +- DEVLOG.md | 144 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +- arbeitskacheln/15_mobile.md | 12 +++++++ +- flutter_app/lib/main.dart | 12 ++++--- +- flutter_app/lib/offline/mangel_queue.dart | 14 +++++++- +- flutter_app/lib/offline/queue.dart | 20 ++++++++++- +- flutter_app/test/mangel_queue_test.dart | 143 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +- flutter_app/test/offline_queue_test.dart | 6 ++++ + +--- +## 2026-09-12 22:21 – 22:21 (0m) +**Beschreibung:** Claude Code Session +**Projekt:** asb-material + +### Commits +Keine Commits in dieser Session. + +### Geänderte Dateien +- DEVLOG.md | 144 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +- arbeitskacheln/15_mobile.md | 12 +++++++ +- flutter_app/lib/main.dart | 12 ++++--- +- flutter_app/lib/offline/mangel_queue.dart | 14 +++++++- +- flutter_app/lib/offline/queue.dart | 20 ++++++++++- +- flutter_app/test/mangel_queue_test.dart | 143 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +- flutter_app/test/offline_queue_test.dart | 6 ++++ + +--- diff --git a/flutter_app/lib/screens/mangel_melden_screen.dart b/flutter_app/lib/screens/mangel_melden_screen.dart index 5e85c2a..0a741fa 100644 --- a/flutter_app/lib/screens/mangel_melden_screen.dart +++ b/flutter_app/lib/screens/mangel_melden_screen.dart @@ -61,7 +61,17 @@ class _MangelMeldenScreenState extends State { } Future _fotoAufnehmen() async { - final bild = await ImagePicker().pickImage(source: ImageSource.camera, imageQuality: 70); + // Fund (Foto-Kompression-Check): `imageQuality` allein komprimiert nur die + // JPEG-Qualität, verkleinert aber NICHT die Auflösung - ein 12MP-Kamerafoto + // bleibt trotzdem mehrere MB groß. `maxWidth`/`maxHeight` skaliert zusätzlich + // herunter, was bei schlechter Feld-Verbindung den eigentlichen Unterschied + // macht (1600px reicht für einen Mangel-Nachweis völlig aus). + final bild = await ImagePicker().pickImage( + source: ImageSource.camera, + imageQuality: 70, + maxWidth: 1600, + maxHeight: 1600, + ); if (bild == null || !mounted) return; setState(() => _foto = File(bild.path)); } diff --git a/frontend/src/offline/fotoKomprimierung.ts b/frontend/src/offline/fotoKomprimierung.ts new file mode 100644 index 0000000..2fd5e0f --- /dev/null +++ b/frontend/src/offline/fotoKomprimierung.ts @@ -0,0 +1,34 @@ +// Fund (Foto-Kompression-Check, Flutter-Pendant hat imageQuality+maxWidth/ +// maxHeight): die PWA lud Mangel-Fotos bislang komplett unkomprimiert hoch +// (rohes File-Objekt aus ) - ein 12MP-Kamerafoto +// sind mehrere MB, die im Feld über schlechte Verbindung übertragen werden +// müssen. Canvas-basierte Verkleinerung/Neukodierung vor dem Enqueue schließt +// diese Lücke, ohne eine zusätzliche Bibliothek zu brauchen. +const MAX_ABMESSUNG = 1600; +const JPEG_QUALITAET = 0.7; + +export async function komprimiereFoto(datei: File): Promise { + if (!datei.type.startsWith("image/")) return datei; + + const bitmap = await createImageBitmap(datei).catch(() => null); + if (!bitmap) return datei; // z.B. HEIC ohne Browser-Decoder-Unterstützung + + const skalierung = Math.min(1, MAX_ABMESSUNG / Math.max(bitmap.width, bitmap.height)); + const breite = Math.round(bitmap.width * skalierung); + const hoehe = Math.round(bitmap.height * skalierung); + + const canvas = document.createElement("canvas"); + canvas.width = breite; + canvas.height = hoehe; + const kontext = canvas.getContext("2d"); + if (!kontext) return datei; + kontext.drawImage(bitmap, 0, 0, breite, hoehe); + + const blob = await new Promise((resolve) => + canvas.toBlob(resolve, "image/jpeg", JPEG_QUALITAET) + ); + if (!blob || blob.size >= datei.size) return datei; // kein Gewinn, Original behalten + + const name = datei.name.replace(/\.[^.]+$/, "") + ".jpg"; + return new File([blob], name, { type: "image/jpeg" }); +} diff --git a/frontend/src/pages/MangelListePage.tsx b/frontend/src/pages/MangelListePage.tsx index 1cb6c20..dde6bae 100644 --- a/frontend/src/pages/MangelListePage.tsx +++ b/frontend/src/pages/MangelListePage.tsx @@ -4,6 +4,7 @@ import { useSearchParams } from "react-router-dom"; import { apiRequest } from "../api/client"; import { useAuth } from "../auth/AuthContext"; import { DokumentePanel } from "../components/DokumentePanel"; +import { komprimiereFoto } from "../offline/fotoKomprimierung"; import * as mangelQueue from "../offline/mangelQueue"; import type { MangelQueueItem } from "../offline/mangelQueue"; import { useOnlineStatus } from "../offline/useOnlineStatus"; @@ -173,7 +174,10 @@ export function MangelListePage() { type="file" accept="image/*" capture="environment" - onChange={(e) => setFoto(e.target.files?.[0] ?? null)} + onChange={async (e) => { + const datei = e.target.files?.[0] ?? null; + setFoto(datei ? await komprimiereFoto(datei) : null); + }} />