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
130 lines
3.9 KiB
Dart
130 lines
3.9 KiB
Dart
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. 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;
|
|
|
|
@override
|
|
State<ObjektListScreen> createState() => _ObjektListScreenState();
|
|
}
|
|
|
|
class _ObjektListScreenState extends State<ObjektListScreen> {
|
|
List<dynamic> _objekte = [];
|
|
String _suche = '';
|
|
String? _fehler;
|
|
final _sucheController = TextEditingController();
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_laden();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_sucheController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
Future<void> _laden() async {
|
|
try {
|
|
final ergebnis = await widget.apiClient.request('/objekte');
|
|
setState(() => _objekte = ergebnis as List<dynamic>);
|
|
} catch (_) {
|
|
setState(() => _fehler = 'Objektliste konnte nicht geladen werden.');
|
|
}
|
|
}
|
|
|
|
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) {
|
|
final name = (o['name'] as String).toLowerCase();
|
|
final code = (o['code'] as String).toLowerCase();
|
|
final suche = _suche.toLowerCase();
|
|
return name.contains(suche) || code.contains(suche);
|
|
}).toList();
|
|
|
|
return Scaffold(
|
|
appBar: AppBar(title: const Text('Objekte')),
|
|
body: Column(
|
|
children: [
|
|
Padding(
|
|
padding: const EdgeInsets.all(12),
|
|
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)),
|
|
Expanded(
|
|
child: ListView.builder(
|
|
itemCount: gefiltert.length,
|
|
itemBuilder: (context, index) {
|
|
final objekt = gefiltert[index];
|
|
return ListTile(
|
|
title: Text(objekt['name'] as String),
|
|
subtitle: Text(objekt['code'] as String),
|
|
onTap: () => Navigator.of(context).push(
|
|
MaterialPageRoute(
|
|
builder: (_) => KontrollScreen(
|
|
apiClient: widget.apiClient,
|
|
objektId: objekt['id'] as int,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|