import 'dart:io'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:getx_scaffold/getx_scaffold.dart'; import 'package:image_picker/image_picker.dart'; import 'package:ln_jq_app/common/model/base_model.dart'; import 'package:ln_jq_app/common/model/vehicle_info.dart'; import 'package:ln_jq_app/storage_service.dart'; import 'package:mobile_scanner/mobile_scanner.dart'; import 'package:permission_handler/permission_handler.dart'; class QrCodeController extends GetxController with BaseControllerMixin, GetSingleTickerProviderStateMixin { @override String get builderId => 'qrcode'; // --- Animation --- late final AnimationController animationController; late final Animation scanAnimation; // --- 使用 MobileScanner 的控制器 --- final MobileScannerController scannerController = MobileScannerController(); final RxBool isFlashOn = false.obs; final RxBool isProcessingResult = false.obs; @override void onInit() { super.onInit(); requestPermission(); animationController = AnimationController( duration: const Duration(milliseconds: 2500), vsync: this, ); scanAnimation = Tween(begin: 0, end: 1).animate(animationController); animationController.repeat(reverse: false); } /// MobileScanner 的 onDetect 回调方法 void onDetect(BarcodeCapture capture) { if (isProcessingResult.value) return; final Barcode? barcode = capture.barcodes.firstOrNull; if (barcode?.rawValue != null && barcode!.rawValue!.isNotEmpty) { isProcessingResult.value = true; scannerController.stop(); animationController.stop(); print("相机识别到的内容: ${barcode.rawValue!}"); renderResult(barcode.rawValue!); } } /// 恢复扫描状态 void resumeScanner() { isProcessingResult.value = false; try { scannerController.start(); } catch (e) { print("无法重启相机: $e"); } animationController.repeat(reverse: false); } /// 从相册选择图片并扫描二维码 void scanFromGallery() async { try { final XFile? imageFile = await ImagePicker().pickImage(source: ImageSource.gallery); if (imageFile == null) { return; } scannerController.stop(); animationController.stop(); showLoading("正在识别..."); final BarcodeCapture? capture = await scannerController.analyzeImage(imageFile.path); dismissLoading(); final Barcode? firstBarcode = capture?.barcodes.firstOrNull; if (firstBarcode?.rawValue != null && firstBarcode!.rawValue!.isNotEmpty) { final String scanResult = firstBarcode.rawValue!; print("相册识别到的内容: $scanResult"); renderResult(scanResult); } else { showErrorToast('未识别到二维码'); resumeScanner(); } } catch (e, stackTrace) { dismissLoading(); showErrorToast('从相册选择失败,请稍后重试'); print("scanFromGallery Error: $e\n$stackTrace"); resumeScanner(); } } /// 切换闪光灯 void toggleFlash() async { try { await scannerController.toggleTorch(); final currentTorchState = scannerController.value.torchState; isFlashOn.value = currentTorchState == TorchState.on; } catch (e) { print("切换闪光灯失败: $e"); showErrorToast("无法打开闪光灯"); } } /// 翻转相机 void flipCamera() async { await scannerController.switchCamera(); } /// 请求相机权限 void requestPermission() async { var status = await Permission.camera.request(); if (!status.isGranted) { showErrorToast('请授予相机权限以使用扫描功能'); Get.back(); } } void requestPhotoPermission() async { var status = await Permission.photos.request(); if (status.isGranted) { scanFromGallery(); } else if (status.isPermanentlyDenied) { openAppSettings(); } else { showErrorToast('需要相册权限才能从相册中选择图片'); } } /// 处理扫描结果 void renderResult(String resultStr, {plateNumber}) async { showLoading("正在获取车辆信息..."); try { final Map requestData = { "code": resultStr, "phone": StorageService.to.phone, }; if (plateNumber != null && plateNumber.isNotEmpty) { requestData['plateNumber'] = plateNumber; } var responseData = await HttpService.to.post( "appointment/truck/bindTruck", data: requestData, ); if (responseData == null) { dismissLoading(); showToast('无法获取车辆信息,请检查网络或稍后重试'); resumeScanner(); return; } var result = BaseModel.fromJson(responseData.data); if (result.code != 0) { showToast(result.error); dismissLoading(); resumeScanner(); // 绑定失败也要恢复扫描 return; } if (result.data == null) { dismissLoading(); showBindDialog(resultStr); return; } final vehicle = VehicleInfo.fromJson(result.data as Map); await StorageService.to.saveVehicleInfo(vehicle); dismissLoading(); Get.back(result: true); } on DioException catch (_) { showErrorToast("网络请求失败,请稍后重试"); resumeScanner(); } catch (e, _) { showErrorToast("处理失败,请稍后重舍"); resumeScanner(); } finally { if (Get.isDialogOpen ?? false) { dismissLoading(); } } } /// 显示绑定确认对话框 void showBindDialog(String resultStr) { final TextEditingController plateNumberController = TextEditingController(); // 使用 showConfirmDialog,它有 onCancel 回调 DialogX.to.showConfirmDialog( title: '请输入车牌号', barrierDismissible: false, content: TextField( controller: plateNumberController, autofocus: false, decoration: const InputDecoration( hintText: '请输入完整的车牌号', border: OutlineInputBorder(), contentPadding: EdgeInsets.symmetric(horizontal: 12.0), ), ), confirmText: '确认绑定', cancelText: '取消', // showConfirmDialog 有 cancelText onConfirm: () { final String plateNumber = plateNumberController.text.trim(); if (plateNumber.isEmpty) { showToast("请输入车牌号"); // 返回 false 可以阻止弹窗关闭,让用户继续输入 return false; } renderResult(resultStr, plateNumber: plateNumber); //关闭弹窗 return true; }, onCancel: () { // 如果用户点击取消,恢复扫描 resumeScanner(); }, ); } @override void onClose() { scannerController.dispose(); animationController.dispose(); super.onClose(); } }