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:
@@ -2,10 +2,11 @@ import 'package:flutter/material.dart';
|
||||
|
||||
import '../api/api_client.dart';
|
||||
import 'kontroll_screen.dart';
|
||||
import 'scanner_screen.dart';
|
||||
|
||||
/// Prompt 11 Screen 1: Standort/Objekt wählen. Kamera-Scan (Karte 10) ist Roadmap -
|
||||
/// das Suchfeld nimmt schon jetzt einen gescannten Code entgegen, sobald ein
|
||||
/// Scanner-Plugin ergänzt wird.
|
||||
/// Prompt 11 Screen 1: Standort/Objekt wählen. Objekt-Code (Karte 10) →
|
||||
/// Direktsprung ins Kontroll-Screen, sonst füllt der Scan dasselbe Suchfeld wie
|
||||
/// manuelle Eingabe (Kamera fällt bei Etikettschaden auf Tippen zurück).
|
||||
class ObjektListScreen extends StatefulWidget {
|
||||
const ObjektListScreen({super.key, required this.apiClient});
|
||||
final ApiClient apiClient;
|
||||
@@ -18,6 +19,7 @@ class _ObjektListScreenState extends State<ObjektListScreen> {
|
||||
List<dynamic> _objekte = [];
|
||||
String _suche = '';
|
||||
String? _fehler;
|
||||
final _sucheController = TextEditingController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -25,6 +27,12 @@ class _ObjektListScreenState extends State<ObjektListScreen> {
|
||||
_laden();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_sucheController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _laden() async {
|
||||
try {
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
final gefiltert = _objekte.where((o) {
|
||||
@@ -49,9 +84,21 @@ class _ObjektListScreenState extends State<ObjektListScreen> {
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: TextField(
|
||||
decoration: const InputDecoration(labelText: 'Suche oder Code scannen…'),
|
||||
onChanged: (wert) => setState(() => _suche = wert),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _sucheController,
|
||||
decoration: const InputDecoration(labelText: 'Suche oder Code…'),
|
||||
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)),
|
||||
|
||||
@@ -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),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user