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
105 lines
3.8 KiB
Dart
105 lines
3.8 KiB
Dart
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;
|
||
}
|