218 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			Dart
		
	
	
			
		
		
	
	
			218 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			Dart
		
	
	
| import 'dart:async';
 | |
| import 'package:camera/camera.dart';
 | |
| import 'package:flutter/material.dart';
 | |
| import 'package:fluttertoast/fluttertoast.dart';
 | |
| import 'package:qhd_prevention/customWidget/custom_button.dart';
 | |
| import 'package:qhd_prevention/customWidget/toast_util.dart';
 | |
| import 'package:qhd_prevention/pages/my_appbar.dart';
 | |
| import 'package:qhd_prevention/tools/tools.dart';
 | |
| import '../../../http/ApiService.dart';
 | |
| 
 | |
| /// 人脸识别模式
 | |
| enum FaceMode { auto, manual }
 | |
| 
 | |
| class FaceRecognitionPage extends StatefulWidget {
 | |
|   final String studentId;
 | |
|   final FaceMode mode;
 | |
| 
 | |
|   const FaceRecognitionPage({
 | |
|     Key? key,
 | |
|     required this.studentId,
 | |
|     this.mode = FaceMode.auto,
 | |
|   }) : super(key: key);
 | |
| 
 | |
|   @override
 | |
|   _FaceRecognitionPageState createState() => _FaceRecognitionPageState();
 | |
| }
 | |
| 
 | |
| class _FaceRecognitionPageState extends State<FaceRecognitionPage> {
 | |
|   CameraController? _cameraController;
 | |
|   Timer? _timer;
 | |
|   int _attempts = 0;
 | |
| 
 | |
|   static const int _maxAttempts = 8;
 | |
|   static const Duration _interval = Duration(seconds: 2);
 | |
| 
 | |
|   bool get _isManualMode => widget.mode == FaceMode.manual;
 | |
| 
 | |
|   @override
 | |
|   void initState() {
 | |
|     super.initState();
 | |
|     _initCamera();
 | |
|   }
 | |
| 
 | |
|   @override
 | |
|   void dispose() {
 | |
|     _timer?.cancel();
 | |
|     _cameraController?.dispose();
 | |
|     super.dispose();
 | |
|   }
 | |
| 
 | |
|   Future<void> _initCamera() async {
 | |
|     final cams = await availableCameras();
 | |
|     final front = cams.firstWhere((c) => c.lensDirection == CameraLensDirection.front);
 | |
|     _cameraController = CameraController(front, ResolutionPreset.medium, enableAudio: false);
 | |
|     await _cameraController!.initialize();
 | |
|     if (!mounted) return;
 | |
|     setState(() {});
 | |
| 
 | |
|     if (!_isManualMode) {
 | |
|       _timer = Timer.periodic(_interval, (_) => _captureAndUpload());
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /// 显示加载弹窗
 | |
|   void _showLoading() {
 | |
|     showDialog(
 | |
|       context: context,
 | |
|       barrierDismissible: false,
 | |
|       builder: (_) => const Center(child: CircularProgressIndicator()),
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   /// 隐藏弹窗
 | |
|   void _hideLoading() {
 | |
|     if (Navigator.canPop(context)) Navigator.pop(context);
 | |
|   }
 | |
| /// 定时上传
 | |
|   Future<void> _captureAndUpload() async {
 | |
|     if (!_isManualMode) {
 | |
|       if (_attempts >= _maxAttempts) return _onTimeout();
 | |
|       _attempts++;
 | |
|       try {
 | |
|         final pic = await _cameraController!.takePicture();
 | |
|         final res = await ApiService.getUserFace(pic.path, widget.studentId);
 | |
|         if (res['result'] == 'success') {
 | |
|           _onSuccess();
 | |
|         }
 | |
|       } catch (_) {
 | |
| 
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| /// 手动上传验证
 | |
|   Future<void> _captureAndReload() async {
 | |
|     _showLoading();
 | |
|     try {
 | |
|       final pic = await _cameraController!.takePicture();
 | |
|       final res = await ApiService.reloadMyFace(pic.path, widget.studentId);
 | |
|       _hideLoading();
 | |
|       if (res['result'] == 'success') {
 | |
|         _onSuccess();
 | |
|       } else {
 | |
|         ToastUtil.showError(context, '验证失败,请重试');
 | |
| 
 | |
|       }
 | |
|     } catch (_) {
 | |
|       _hideLoading();
 | |
|       _showToast('发生错误,请重试');
 | |
|       ToastUtil.showError(context, '发生错误,请重试');
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   void _onSuccess() {
 | |
|     _timer?.cancel();
 | |
|     if (widget.mode == FaceMode.manual) {
 | |
|       ToastUtil.showSuccess(context, '已更新人脸信息');
 | |
|       Future.delayed(const Duration(milliseconds: 800), () => Navigator.of(context).pop(true));
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     Future.delayed(const Duration(milliseconds: 800), () => Navigator.of(context).pop(true));
 | |
|   }
 | |
| 
 | |
|   void _onTimeout() {
 | |
|     _timer?.cancel();
 | |
|     ToastUtil.showError(context, '人脸超时,请重新识别!');
 | |
|     Future.delayed(const Duration(seconds: 3), () => Navigator.of(context).pop(false));
 | |
|   }
 | |
| 
 | |
|   void _showToast(String msg) {
 | |
|     ToastUtil.showNormal(context, msg);
 | |
|   }
 | |
| 
 | |
|   @override
 | |
|   Widget build(BuildContext context) {
 | |
|     if (_cameraController == null || !_cameraController!.value.isInitialized) {
 | |
|       return const Scaffold(
 | |
|         backgroundColor: Colors.white,
 | |
|         body: Center(child: CircularProgressIndicator()),
 | |
|       );
 | |
|     }
 | |
|     final previewSize = _cameraController!.value.previewSize!;
 | |
|     final previewAspect = previewSize.height / previewSize.width;
 | |
|     final radius = (screenWidth(context) - 100) / 2;
 | |
| 
 | |
|     return Scaffold(
 | |
|       backgroundColor: Colors.white,
 | |
|       appBar: MyAppbar(title: '人脸识别'),
 | |
|       body: Stack(
 | |
|         children: [
 | |
|           Positioned.fill(child: Container(color: Colors.white)),
 | |
| 
 | |
|           Transform.translate(
 | |
|             offset: const Offset(0, -100),
 | |
|             child: Stack(
 | |
|               children: [
 | |
|                 Center(
 | |
|                   child: ClipOval(
 | |
|                     child: AspectRatio(
 | |
|                       aspectRatio: previewAspect,
 | |
|                       child: CameraPreview(_cameraController!),
 | |
|                     ),
 | |
|                   ),
 | |
|                 ),
 | |
|                 Positioned.fill(
 | |
|                   child: CustomPaint(
 | |
|                     painter: _WhiteMaskPainter(radius: radius),
 | |
|                   ),
 | |
|                 ),
 | |
|               ],
 | |
|             ),
 | |
|           ),
 | |
| 
 | |
|           Align(
 | |
|             alignment: Alignment.center,
 | |
|             child: Padding(
 | |
|               padding: const EdgeInsets.only(top: 250),
 | |
|               child: Column(
 | |
|                 mainAxisSize: MainAxisSize.min,
 | |
|                 children: [
 | |
|                   Text(
 | |
|                     '请将人脸置于圆圈内',
 | |
|                     style: const TextStyle(fontSize: 18, color: Colors.black87),
 | |
|                     textAlign: TextAlign.center,
 | |
|                   ),
 | |
|                   const SizedBox(height: 20),
 | |
|                   if (_isManualMode)
 | |
|                     CustomButton(text: '拍照/上传', backgroundColor: Colors.blue,onPressed: _captureAndReload,)
 | |
|                 ],
 | |
|               ),
 | |
|             ),
 | |
|           ),
 | |
|         ],
 | |
|       ),
 | |
|     );
 | |
|   }
 | |
| }
 | |
| 
 | |
| class _WhiteMaskPainter extends CustomPainter {
 | |
|   final double radius;
 | |
|   _WhiteMaskPainter({required this.radius});
 | |
| 
 | |
|   @override
 | |
|   void paint(Canvas canvas, Size size) {
 | |
|     canvas.saveLayer(Offset.zero & size, Paint());
 | |
|     canvas.drawRect(Offset.zero & size, Paint()..color = Colors.white);
 | |
|     canvas.drawCircle(
 | |
|       size.center(Offset.zero),
 | |
|       radius,
 | |
|       Paint()..blendMode = BlendMode.clear,
 | |
|     );
 | |
|     canvas.restore();
 | |
|   }
 | |
| 
 | |
|   @override
 | |
|   bool shouldRepaint(covariant CustomPainter old) => false;
 | |
| }
 |