Karte 10: Kamera-Scan in Flutter-App (Objekt-Code, Scan-First-Einstieg)
ScannerScreen auf mobile_scanner, Treffer springt direkt ins KontrollScreen, sonst Fallback ins bestehende Suchfeld. Kamera-Berechtigung (Android Manifest/ iOS Info.plist) im README dokumentiert, da android/ios-Ordner generiert und nicht im Repo sind. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CVgbozhYmuEhiEJHffRXCV
This commit is contained in:
@@ -2591,3 +2591,19 @@ Keine Commits in dieser Session.
|
|||||||
- frontend/src/pages/admin/ObjektSection.tsx | 58 +++++++++++++++++++++++++++++-
|
- frontend/src/pages/admin/ObjektSection.tsx | 58 +++++++++++++++++++++++++++++-
|
||||||
|
|
||||||
---
|
---
|
||||||
|
## 2026-09-04 17:39 – 17:39 (0m)
|
||||||
|
**Beschreibung:** Claude Code Session
|
||||||
|
**Projekt:** frontend
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
- 6cf28cc Karte 10: Kamera-Scan im Frontend (Objekt-Code, Scan-First-Einstieg)
|
||||||
|
|
||||||
|
### Geänderte Dateien
|
||||||
|
- DEVLOG.md | 13 ++++++
|
||||||
|
- frontend/DEVLOG.md | 13 ++++++
|
||||||
|
- frontend/package.json | 1 +
|
||||||
|
- frontend/src/components/BarcodeScanner.tsx | 64 ++++++++++++++++++++++++++++++
|
||||||
|
- frontend/src/pages/ObjektListPage.tsx | 48 ++++++++++++++++------
|
||||||
|
- frontend/src/styles/global.css | 24 +++++++++++
|
||||||
|
|
||||||
|
---
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# flutter_app – Dev Log
|
||||||
|
|
||||||
|
## 2026-09-04 17:42 – 17:43 (1m)
|
||||||
|
**Beschreibung:** Claude Code Session
|
||||||
|
**Projekt:** asb-material
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
Keine Commits in dieser Session.
|
||||||
|
|
||||||
|
### Geänderte Dateien
|
||||||
|
- DEVLOG.md | 13 ++++++
|
||||||
|
- frontend/DEVLOG.md | 13 ++++++
|
||||||
|
- frontend/package.json | 1 +
|
||||||
|
- frontend/src/components/BarcodeScanner.tsx | 64 ++++++++++++++++++++++++++++++
|
||||||
|
- frontend/src/pages/ObjektListPage.tsx | 48 ++++++++++++++++------
|
||||||
|
- frontend/src/styles/global.css | 24 +++++++++++
|
||||||
|
|
||||||
|
---
|
||||||
+17
-1
@@ -27,6 +27,23 @@ flutter build apk
|
|||||||
(`192.168.1.238`) gesetzt. Für Produktivbetrieb auf `--dart-define` umstellen
|
(`192.168.1.238`) gesetzt. Für Produktivbetrieb auf `--dart-define` umstellen
|
||||||
oder zumindest vor jedem Build prüfen/anpassen.
|
oder zumindest vor jedem Build prüfen/anpassen.
|
||||||
|
|
||||||
|
## Kamera-Berechtigung (Kamera-Scan, Karte 10)
|
||||||
|
|
||||||
|
`mobile_scanner` braucht die Kamera-Berechtigung im generierten `android/`-Ordner
|
||||||
|
(existiert erst nach `flutter create .`, siehe oben) - nach dem Erstlauf einmalig
|
||||||
|
in `android/app/src/main/AndroidManifest.xml` ergänzen, oberhalb von `<application>`:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
|
```
|
||||||
|
|
||||||
|
Für iOS entsprechend in `ios/Runner/Info.plist`:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<key>NSCameraUsageDescription</key>
|
||||||
|
<string>Wird zum Scannen von Objekt-/Geräte-Codes benötigt.</string>
|
||||||
|
```
|
||||||
|
|
||||||
## Bewusst noch nicht umgesetzt (Unterschied zur React-PWA)
|
## Bewusst noch nicht umgesetzt (Unterschied zur React-PWA)
|
||||||
|
|
||||||
- **Keine Offline-Queue**: anders als die PWA (`frontend/src/offline/queue.ts`,
|
- **Keine Offline-Queue**: anders als die PWA (`frontend/src/offline/queue.ts`,
|
||||||
@@ -35,6 +52,5 @@ oder zumindest vor jedem Build prüfen/anpassen.
|
|||||||
Zwischenspeicherung/Sync bei Reconnect. Nachziehen, sobald die App über das
|
Zwischenspeicherung/Sync bei Reconnect. Nachziehen, sobald die App über das
|
||||||
Grundgerüst hinauswächst - gleiches Muster wie in der PWA übertragbar
|
Grundgerüst hinauswächst - gleiches Muster wie in der PWA übertragbar
|
||||||
(z. B. `sqflite` oder `hive` statt IndexedDB).
|
(z. B. `sqflite` oder `hive` statt IndexedDB).
|
||||||
- Kein QR-/Barcode-Kamera-Scan (Karte 10, Roadmap).
|
|
||||||
- Keine Tests (Flutter-Widget-/Integrationstests) - Sprintplan sieht Testphase 4
|
- Keine Tests (Flutter-Widget-/Integrationstests) - Sprintplan sieht Testphase 4
|
||||||
primär für die PWA vor (Playwright); Android-Testabdeckung ist noch offen.
|
primär für die PWA vor (Playwright); Android-Testabdeckung ist noch offen.
|
||||||
|
|||||||
@@ -2,10 +2,11 @@ import 'package:flutter/material.dart';
|
|||||||
|
|
||||||
import '../api/api_client.dart';
|
import '../api/api_client.dart';
|
||||||
import 'kontroll_screen.dart';
|
import 'kontroll_screen.dart';
|
||||||
|
import 'scanner_screen.dart';
|
||||||
|
|
||||||
/// Prompt 11 Screen 1: Standort/Objekt wählen. Kamera-Scan (Karte 10) ist Roadmap -
|
/// Prompt 11 Screen 1: Standort/Objekt wählen. Objekt-Code (Karte 10) →
|
||||||
/// das Suchfeld nimmt schon jetzt einen gescannten Code entgegen, sobald ein
|
/// Direktsprung ins Kontroll-Screen, sonst füllt der Scan dasselbe Suchfeld wie
|
||||||
/// Scanner-Plugin ergänzt wird.
|
/// manuelle Eingabe (Kamera fällt bei Etikettschaden auf Tippen zurück).
|
||||||
class ObjektListScreen extends StatefulWidget {
|
class ObjektListScreen extends StatefulWidget {
|
||||||
const ObjektListScreen({super.key, required this.apiClient});
|
const ObjektListScreen({super.key, required this.apiClient});
|
||||||
final ApiClient apiClient;
|
final ApiClient apiClient;
|
||||||
@@ -18,6 +19,7 @@ class _ObjektListScreenState extends State<ObjektListScreen> {
|
|||||||
List<dynamic> _objekte = [];
|
List<dynamic> _objekte = [];
|
||||||
String _suche = '';
|
String _suche = '';
|
||||||
String? _fehler;
|
String? _fehler;
|
||||||
|
final _sucheController = TextEditingController();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -25,6 +27,12 @@ class _ObjektListScreenState extends State<ObjektListScreen> {
|
|||||||
_laden();
|
_laden();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_sucheController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _laden() async {
|
Future<void> _laden() async {
|
||||||
try {
|
try {
|
||||||
final ergebnis = await widget.apiClient.request('/objekte');
|
final ergebnis = await widget.apiClient.request('/objekte');
|
||||||
@@ -34,6 +42,33 @@ class _ObjektListScreenState extends State<ObjektListScreen> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _scannen() async {
|
||||||
|
final code = await Navigator.of(context).push<String>(
|
||||||
|
MaterialPageRoute(builder: (_) => const ScannerScreen()),
|
||||||
|
);
|
||||||
|
if (code == null || !mounted) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
final objekt = await widget.apiClient.request('/objekte/code/$code');
|
||||||
|
if (!mounted) return;
|
||||||
|
Navigator.of(context).push(
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (_) => KontrollScreen(
|
||||||
|
apiClient: widget.apiClient,
|
||||||
|
objektId: objekt['id'] as int,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} on ApiException catch (e) {
|
||||||
|
if (e.status == 404) {
|
||||||
|
_sucheController.text = code;
|
||||||
|
setState(() => _suche = code);
|
||||||
|
} else {
|
||||||
|
setState(() => _fehler = 'Scan konnte nicht verarbeitet werden.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final gefiltert = _objekte.where((o) {
|
final gefiltert = _objekte.where((o) {
|
||||||
@@ -49,11 +84,23 @@ class _ObjektListScreenState extends State<ObjektListScreen> {
|
|||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
child: TextField(
|
child: TextField(
|
||||||
decoration: const InputDecoration(labelText: 'Suche oder Code scannen…'),
|
controller: _sucheController,
|
||||||
|
decoration: const InputDecoration(labelText: 'Suche oder Code…'),
|
||||||
onChanged: (wert) => setState(() => _suche = wert),
|
onChanged: (wert) => setState(() => _suche = wert),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.qr_code_scanner),
|
||||||
|
tooltip: 'Scannen',
|
||||||
|
onPressed: _scannen,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
if (_fehler != null) Text(_fehler!, style: const TextStyle(color: Colors.red)),
|
if (_fehler != null) Text(_fehler!, style: const TextStyle(color: Colors.red)),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:mobile_scanner/mobile_scanner.dart';
|
||||||
|
|
||||||
|
/// Kamera-Scan (Karte 10): Vollbild-Scanner, gibt den ersten erkannten
|
||||||
|
/// Code128-Wert per Navigator.pop zurück. Kein Auto-Close bei fehlgeschlagenem
|
||||||
|
/// Decode-Versuch nötig - mobile_scanner meldet nur echte Treffer.
|
||||||
|
class ScannerScreen extends StatefulWidget {
|
||||||
|
const ScannerScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ScannerScreen> createState() => _ScannerScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ScannerScreenState extends State<ScannerScreen> {
|
||||||
|
bool _bereitsErkannt = false;
|
||||||
|
|
||||||
|
void _onDetect(BarcodeCapture capture) {
|
||||||
|
if (_bereitsErkannt) return;
|
||||||
|
if (capture.barcodes.isEmpty) return;
|
||||||
|
final wert = capture.barcodes.first.rawValue;
|
||||||
|
if (wert == null || wert.isEmpty) return;
|
||||||
|
_bereitsErkannt = true;
|
||||||
|
Navigator.of(context).pop(wert);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(title: const Text('Code scannen')),
|
||||||
|
body: MobileScanner(onDetect: _onDetect),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ dependencies:
|
|||||||
sdk: flutter
|
sdk: flutter
|
||||||
http: ^1.2.2
|
http: ^1.2.2
|
||||||
shared_preferences: ^2.3.2
|
shared_preferences: ^2.3.2
|
||||||
|
mobile_scanner: ^5.2.3
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
Reference in New Issue
Block a user