import 'package:chicken_farm/core/utils/logger.dart'; import 'package:flutter/material.dart'; import 'package:mobile_scanner/mobile_scanner.dart'; import 'package:chicken_farm/core/utils/toast.dart'; import 'package:audioplayers/audioplayers.dart'; typedef OnScanCallback = void Function(String scannedContent); typedef QRCodeParser = Future Function(String rawContent); class QRScannerComponent extends StatefulWidget { final String? startWithString; final OnScanCallback onScanCallback; final String? invalidQRMessage; final QRCodeParser? qrCodeParser; const QRScannerComponent({ super.key, this.startWithString, required this.onScanCallback, this.invalidQRMessage, this.qrCodeParser, }); @override State createState() => _QRScannerComponentState(); } class _QRScannerComponentState extends State { MobileScannerController? _cameraController; bool _isScanning = false; bool _isTorchOn = false; final AudioPlayer _audioPlayer = AudioPlayer(); @override void initState() { super.initState(); _cameraController = MobileScannerController(); } @override void dispose() { _cameraController?.dispose(); _audioPlayer.dispose(); super.dispose(); } void _handleBarcodeScan(BarcodeCapture barcode) async { // 如果正在扫描或冷却中,则忽略 logger.d('扫描二维码: $barcode'); if (_isScanning) return; _isScanning = true; final code = barcode.barcodes.first.rawValue; // 播放提示音 await _audioPlayer.play(AssetSource('sounds/beep.mp3')); if (code == null) { _resetScanningState('无法识别二维码'); return; } _processQRCode(code); } void _processQRCode(String code) async { // 如果设置了起始字符串,则检查是否匹配 if (widget.startWithString != null && !code.startsWith(widget.startWithString!)) { final errorMessage = widget.invalidQRMessage ?? '非法二维码'; _resetScanningState(errorMessage); return; } if (widget.qrCodeParser != null) { try { final parseCode = await widget.qrCodeParser!(code); if (parseCode == null || parseCode.isEmpty) { // 解析失败,显示错误并重置状态 _resetScanningState('二维码解析失败'); return; } code = parseCode; } catch (e) { // 解析过程中出现异常 _resetScanningState('二维码解析异常: $e'); return; } } widget.onScanCallback(code); _resetScanningState(); } void _resetScanningState([String? errorMessage]) { if (errorMessage != null) { ToastUtil.errorAlert(errorMessage); } Future.delayed(const Duration(seconds: 1), () { if (mounted) { _isScanning = false; } }); } void _toggleTorch() async { if (_cameraController != null) { await _cameraController!.toggleTorch(); setState(() { _isTorchOn = !_isTorchOn; }); } } @override Widget build(BuildContext context) { const scanBoxSize = 250.0; return Scaffold( body: SafeArea( child: LayoutBuilder( builder: (context, constraints) { var cw = constraints.maxWidth; var ch = constraints.maxHeight; var w = cw / 2 - scanBoxSize / 2; var h = ch / 2 - scanBoxSize / 2; var scanRefc = Rect.fromLTWH(w, h, scanBoxSize, scanBoxSize); return Stack( children: [ MobileScanner( controller: _cameraController, onDetect: _handleBarcodeScan, scanWindow: scanRefc, ), Align( alignment: Alignment.center, child: Container( width: scanBoxSize, height: scanBoxSize, decoration: BoxDecoration( border: Border( left: BorderSide(color: Colors.white, width: 2), right: BorderSide(color: Colors.white, width: 2), top: BorderSide(color: Colors.white, width: 2), bottom: BorderSide(color: Colors.white, width: 2), ), ), ), ), // 顶部黑色遮罩 Positioned( top: 0, left: 0, right: 0, bottom: h + scanBoxSize, child: Container(color: Colors.black.withValues(alpha: 0.7)), ), // 底部黑色遮罩 Positioned( top: h + scanBoxSize, left: 0, right: 0, bottom: 0, child: Container(color: Colors.black.withValues(alpha: 0.7)), ), // 左侧黑色遮罩 Positioned( top: h, left: 0, width: w, height: scanBoxSize, child: Container(color: Colors.black.withValues(alpha: 0.7)), ), // 右侧黑色遮罩 Positioned( top: h, right: 0, width: w, height: scanBoxSize, child: Container(color: Colors.black.withValues(alpha: 0.7)), ), //底部手电筒按钮 Positioned( top: h + scanBoxSize + 40, left: 0, right: 0, child: Center( child: IconButton( icon: Icon( _isTorchOn ? Icons.flashlight_on : Icons.flashlight_off, color: Colors.white, size: 36, ), onPressed: _toggleTorch, ), ), ), ], ); }, ), ), ); } }