Files
timemaster-kiosk/lib/screens/qr_enrollment_scan_screen.dart
T
patrickandClaude Sonnet 5 c5374efbd6 fix: QR-Scan-Fehler sichtbar machen (Diagnose für "An unexpected error occurred")
mobile_scanners Default-errorBuilder zeigt nur die generische Meldung ohne
Details. Eigener errorBuilder zeigt jetzt errorCode + errorDetails.message
an, damit sich das reale Problem auf dem Tablet überhaupt einordnen lässt
(z.B. Kamera-Berechtigung verweigert, Kamera nicht verfügbar, o.ä.).
Zusätzlich Fallback-Button "Zurück – manuell eintragen". v0.1.6+7.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dzdm3EWeNdYFUuXFRJryA1
2026-09-10 13:43:19 +02:00

105 lines
3.8 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
/// Scannt den QR-Code aus dem Portal (KioskDevicesPage → Bearer-Gerät
/// anlegen). Erwartetes Format: {"url": "<api base url>", "token": "<...>"}.
class QrEnrollmentScanScreen extends StatefulWidget {
const QrEnrollmentScanScreen({super.key});
@override
State<QrEnrollmentScanScreen> createState() => _QrEnrollmentScanScreenState();
}
class _QrEnrollmentScanScreenState extends State<QrEnrollmentScanScreen> {
final _controller = MobileScannerController();
bool _handled = false;
String? _error;
@override
void dispose() {
_controller.dispose();
super.dispose();
}
void _onDetect(BarcodeCapture capture) {
if (_handled) return;
final raw = capture.barcodes.firstOrNull?.rawValue;
if (raw == null) return;
try {
final data = jsonDecode(raw) as Map<String, dynamic>;
final url = data['url'] as String?;
final token = data['token'] as String?;
if (url == null || url.isEmpty || token == null || token.isEmpty) {
setState(() => _error = 'QR-Code enthält nicht die erwarteten Felder (url, token).');
return;
}
_handled = true;
Navigator.of(context).pop({'url': url, 'token': token});
} catch (_) {
setState(() => _error = 'QR-Code konnte nicht gelesen werden (kein gültiges Format).');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('QR-Code scannen')),
body: Stack(
children: [
MobileScanner(
controller: _controller,
onDetect: _onDetect,
errorBuilder: (context, error, child) {
// Default-errorBuilder zeigt nur "An unexpected error occurred"
// ohne Details – hier den echten MobileScannerErrorCode +
// Details anzeigen, damit sich das Problem auf dem Gerät
// überhaupt diagnostizieren lässt.
return Container(
color: Colors.black,
padding: const EdgeInsets.all(24),
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.error_outline, color: Colors.red, size: 48),
const SizedBox(height: 16),
Text('Kamera-Fehler: ${error.errorCode}',
style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold)),
const SizedBox(height: 8),
Text('${error.errorDetails?.message ?? "keine Details"}',
style: const TextStyle(color: Colors.white70, fontSize: 12),
textAlign: TextAlign.center),
const SizedBox(height: 24),
OutlinedButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('Zurück – manuell eintragen', style: TextStyle(color: Colors.white)),
),
],
),
),
);
},
),
if (_error != null)
Positioned(
left: 16,
right: 16,
bottom: 32,
child: Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(color: Colors.red[900], borderRadius: BorderRadius.circular(8)),
child: Text(_error!, style: const TextStyle(color: Colors.white), textAlign: TextAlign.center),
),
),
],
),
);
}
}
extension _FirstOrNull<T> on List<T> {
T? get firstOrNull => isEmpty ? null : first;
}