196 lines
5.6 KiB
Dart
196 lines
5.6 KiB
Dart
|
import 'dart:async';
|
||
|
import 'package:camera/camera.dart';
|
||
|
import 'package:flutter/material.dart';
|
||
|
import 'package:qhd_prevention/customWidget/custom_button.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,
|
||
|
this.studentId = '',
|
||
|
required this.mode,
|
||
|
}) : super(key: key);
|
||
|
|
||
|
@override
|
||
|
_FaceRecognitionPageState createState() => _FaceRecognitionPageState();
|
||
|
}
|
||
|
|
||
|
class _FaceRecognitionPageState extends State<FaceRecognitionPage> {
|
||
|
CameraController? _cameraController;
|
||
|
Timer? _timer;
|
||
|
int _attempts = 0;
|
||
|
String _message = '';
|
||
|
String _tip = '请将人脸置于圆圈内';
|
||
|
|
||
|
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());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Future<void> _captureAndUpload() async {
|
||
|
if (_isManualMode) {
|
||
|
setState(() => _message = '请将人脸置于圆圈内');
|
||
|
} else {
|
||
|
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();
|
||
|
} else {
|
||
|
setState(() => _message = '识别失败,请重试');
|
||
|
}
|
||
|
} catch (_) {
|
||
|
setState(() => _message = '发生错误,请重试');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Future<void> _captureAndReload() async {
|
||
|
setState(() => _message = '上传中...');
|
||
|
try {
|
||
|
final pic = await _cameraController!.takePicture();
|
||
|
final res = await ApiService.reloadMyFace(pic.path,);
|
||
|
if (res['result'] == 'success') {
|
||
|
_onSuccess();
|
||
|
} else {
|
||
|
setState(() => _message = '验证失败,请重试');
|
||
|
}
|
||
|
} catch (_) {
|
||
|
setState(() => _message = '发生错误,请重试');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void _onSuccess() {
|
||
|
_timer?.cancel();
|
||
|
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('验证成功')));
|
||
|
Future.delayed(const Duration(milliseconds: 800), () => Navigator.of(context).pop(true));
|
||
|
}
|
||
|
|
||
|
void _onTimeout() {
|
||
|
_timer?.cancel();
|
||
|
setState(() => _message = '人脸超时,请重新识别!');
|
||
|
Future.delayed(const Duration(seconds: 3), () => Navigator.of(context).pop(false));
|
||
|
}
|
||
|
|
||
|
@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(
|
||
|
_tip,
|
||
|
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;
|
||
|
}
|