123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440 |
- import 'package:camera/camera.dart';
- import 'package:flutter/foundation.dart';
- import 'package:flutter/material.dart';
- import 'package:flutter/services.dart';
- import 'package:get/get.dart';
- import 'package:image_gallery_saver/image_gallery_saver.dart';
- import 'package:vitalapp/architecture/utils/prompt_box.dart';
- import 'package:vitalapp/managers/interfaces/cache.dart';
- import 'index.dart';
- class FacialRecognitionController extends GetxController
- with WidgetsBindingObserver {
- FacialRecognitionController();
- final state = FacialRecognitionState();
- List<CameraDescription> _cameras = <CameraDescription>[];
- List<CameraDescription> get cameras => _cameras;
- CameraController? kCameraController;
- double _minAvailableExposureOffset = 0.0;
- double _maxAvailableExposureOffset = 0.0;
- double _minAvailableZoom = 1.0;
- double _maxAvailableZoom = 1.0;
- // CameraController? controller;
- XFile? imageFile;
- XFile? videoFile;
- // VideoPlayerController? videoController;
- VoidCallback? videoPlayerListener;
- bool enableAudio = true;
- double _currentScale = 1.0;
- double _baseScale = 1.0;
- // 屏幕上手指数量
- int pointers = 0;
- // 当前身份证信息
- IdCardInfoModel idCardInfo = IdCardInfoModel();
- /// 开始缩放
- void handleScaleStart(ScaleStartDetails details) {
- _baseScale = _currentScale;
- }
- /// 缩放更新
- Future<void> handleScaleUpdate(ScaleUpdateDetails details) async {
- // When there are not exactly two fingers on screen don't scale
- if (kCameraController == null || pointers != 2) {
- return;
- }
- _currentScale = (_baseScale * details.scale)
- .clamp(_minAvailableZoom, _maxAvailableZoom);
- await kCameraController!.setZoomLevel(_currentScale);
- }
- /// 修改对焦点 TODO
- void onViewFinderTap(TapDownDetails details, BoxConstraints constraints) {
- if (kCameraController == null) {
- return;
- }
- final CameraController cameraController = kCameraController!;
- final Offset offset = Offset(
- details.localPosition.dx / constraints.maxWidth,
- details.localPosition.dy / constraints.maxHeight,
- );
- cameraController.setExposurePoint(offset);
- cameraController.setFocusPoint(offset);
- }
- /// 初始化相机
- Future<void> initAvailableCameras() async {
- try {
- _cameras = await availableCameras();
- if (_cameras.isNotEmpty) {
- // state.isCameraReady = true;
- }
- print("cameras: ${_cameras.length}");
- } on CameraException catch (e) {
- print("cameras: ${e.code} ${e.description}");
- }
- }
- /// 启动指定相机
- Future<void> onNewCameraSelected(CameraDescription cameraDescription) async {
- final CameraController? oldController = kCameraController;
- if (oldController != null) {
- // `kCameraController` needs to be set to null before getting disposed,
- // to avoid a race condition when we use the kCameraController that is being
- // disposed. This happens when camera permission dialog shows up,
- // which triggers `didChangeAppLifecycleState`, which disposes and
- // re-creates the kCameraController.
- kCameraController = null;
- await oldController.dispose();
- }
- final CameraController cameraController = CameraController(
- cameraDescription,
- ResolutionPreset.max,
- enableAudio: false,
- imageFormatGroup: ImageFormatGroup.jpeg,
- );
- kCameraController = cameraController;
- // If the kCameraController is updated then update the UI.
- cameraController.addListener(() {
- if (cameraController.value.hasError) {
- PromptBox.toast(
- "Camera error ${cameraController.value.errorDescription}");
- }
- });
- try {
- await cameraController.initialize();
- await Future.wait(<Future<Object?>>[
- // The exposure mode is currently not supported on the web.
- ...!kIsWeb
- ? <Future<Object?>>[
- cameraController.getMinExposureOffset().then(
- (double value) => _minAvailableExposureOffset = value),
- cameraController
- .getMaxExposureOffset()
- .then((double value) => _maxAvailableExposureOffset = value)
- ]
- : <Future<Object?>>[],
- cameraController
- .getMaxZoomLevel()
- .then((double value) => _maxAvailableZoom = value),
- cameraController
- .getMinZoomLevel()
- .then((double value) => _minAvailableZoom = value),
- ]);
- } on CameraException catch (e) {
- switch (e.code) {
- case 'CameraAccessDenied':
- PromptBox.toast('You have denied camera access.');
- break;
- case 'CameraAccessDeniedWithoutPrompt':
- // iOS only
- PromptBox.toast('Please go to Settings app to enable camera access.');
- break;
- case 'CameraAccessRestricted':
- // iOS only
- PromptBox.toast('Camera access is restricted.');
- break;
- case 'AudioAccessDenied':
- PromptBox.toast('You have denied audio access.');
- break;
- case 'AudioAccessDeniedWithoutPrompt':
- // iOS only
- PromptBox.toast('Please go to Settings app to enable audio access.');
- break;
- case 'AudioAccessRestricted':
- // iOS only
- PromptBox.toast('Audio access is restricted.');
- break;
- default:
- PromptBox.toast('Error: ${e.code}\n${e.description}');
- break;
- }
- }
- }
- /// 遍历当前相机列表并启动后置相机
- void openBackCamera() async {
- if (_cameras.isEmpty) {
- PromptBox.toast('Error: No cameras found.');
- } else {
- for (CameraDescription cameraDescription in _cameras) {
- if (cameraDescription.lensDirection == CameraLensDirection.back) {
- await onNewCameraSelected(cameraDescription);
- lockCaptureOrientation();
- update();
- state.isCameraReady = true;
- break;
- }
- }
- }
- }
- /// 相机锁定旋转
- Future<void> lockCaptureOrientation() async {
- final CameraController? cameraController = kCameraController;
- if (cameraController == null || !cameraController.value.isInitialized) {
- PromptBox.toast('Error: select a camera first.');
- return;
- }
- if (!cameraController.value.isCaptureOrientationLocked) {
- try {
- await cameraController
- .lockCaptureOrientation(DeviceOrientation.landscapeLeft);
- } on CameraException catch (e) {
- PromptBox.toast('Error: ${e.code}\n${e.description}');
- }
- } else {
- PromptBox.toast('Rotation lock is already enabled.');
- }
- }
- /// 执行一次拍摄
- Future<XFile?> takePicture() async {
- final CameraController? cameraController = kCameraController;
- if (cameraController == null || !cameraController.value.isInitialized) {
- PromptBox.toast('Error: select a camera first.');
- return null;
- }
- if (cameraController.value.isTakingPicture) {
- // A capture is already pending, do nothing.
- return null;
- }
- try {
- final XFile file = await cameraController.takePicture();
- saveImageToGallery(file);
- return file;
- } on CameraException catch (e) {
- PromptBox.toast('Error: ${e.code}\n${e.description}');
- return null;
- }
- }
- /// 测试图像文件缓存,print 遍历输出
- void debugShowCache() async {
- final cacheManager = Get.find<ICacheManager>();
- double cacheSize = await cacheManager.getCacheSize();
- print('cacheSize = $cacheSize : ${formatSize(cacheSize)}');
- double imageCacheSize = await cacheManager.getImageCacheSize();
- print('imageCacheSize = $imageCacheSize : ${formatSize(imageCacheSize)}');
- }
- static String formatSize(double value) {
- List<String> unitArr = ['B', 'K', 'M', 'G'];
- int index = 0;
- while (value > 1024) {
- index++;
- value = value / 1024;
- }
- String size = value.toStringAsFixed(2);
- return size + unitArr[index];
- }
- void saveImageToGallery(XFile image) async {
- // 获取图像的字节数据
- Uint8List bytes = await image.readAsBytes();
- // 将图像保存到相册
- await ImageGallerySaver.saveImage(bytes);
- print('图像已保存到相册!');
- }
- /// 发生拍摄身份证事件
- void onCaptureIdCardButtonPressed() {
- takePicture().then((XFile? file) {
- // imageFile = file;
- if (file != null) {
- print('Picture saved to ${file.path}');
- /// TODO 上传给server,获取返回值信息
- if (true) {
- PromptBox.toast('身份证识别成功');
- idCardInfo.localCardImagePath = file.path;
- idCardInfo.idCardName = '金阳';
- idCardInfo.idCardGender = '女';
- idCardInfo.idCardNation = '汉';
- idCardInfo.idCardBirthDate = '1980年10月27日';
- idCardInfo.idCardAddress = '北京市西城区复兴门外大街999号院11号楼3单元502室';
- idCardInfo.idCardNumber = '110101198010270000';
- state.isShowIdCardInfoSwitch = true;
- state.isIdCardInfoShow = true;
- state.isInFaceRecognition = true;
- }
- debugShowCache();
- }
- });
- }
- /// 发生结束录制视频事件
- void onStopButtonPressed() {
- stopVideoRecording().then((XFile? file) {
- if (file != null) {
- PromptBox.toast('Video recorded to ${file.path}');
- // videoFile = file;
- // _startVideoPlayer();
- }
- update();
- });
- }
- /// 发生开始录制视频事件
- void onVideoRecordButtonPressed() {
- startVideoRecording().then((_) {
- update();
- });
- }
- /// 暂停录制视频
- void onPauseButtonPressed() {
- pauseVideoRecording().then((_) {
- update();
- });
- }
- /// 恢复视频录制
- void onResumeButtonPressed() {
- resumeVideoRecording().then((_) {
- update();
- });
- }
- /// 开始录制视频
- Future<void> startVideoRecording() async {
- final CameraController? cameraController = kCameraController;
- if (cameraController == null || !cameraController.value.isInitialized) {
- PromptBox.toast('Error: select a camera first.');
- return;
- }
- if (cameraController.value.isRecordingVideo) {
- // A recording is already started, do nothing.
- return;
- }
- try {
- await cameraController.startVideoRecording();
- } on CameraException catch (e) {
- PromptBox.toast('Error: ${e.code}\n${e.description}');
- return;
- }
- }
- /// 停止录制视频
- Future<XFile?> stopVideoRecording() async {
- final CameraController? cameraController = kCameraController;
- if (cameraController == null || !cameraController.value.isRecordingVideo) {
- return null;
- }
- try {
- return cameraController.stopVideoRecording();
- } on CameraException catch (e) {
- PromptBox.toast('Error: ${e.code}\n${e.description}');
- return null;
- }
- }
- /// 暂停录制视频
- Future<void> pauseVideoRecording() async {
- final CameraController? cameraController = kCameraController;
- if (cameraController == null || !cameraController.value.isRecordingVideo) {
- return;
- }
- try {
- await cameraController.pauseVideoRecording();
- } on CameraException catch (e) {
- PromptBox.toast('Error: ${e.code}\n${e.description}');
- rethrow;
- }
- }
- /// 恢复视频录制
- Future<void> resumeVideoRecording() async {
- final CameraController? cameraController = kCameraController;
- if (cameraController == null || !cameraController.value.isRecordingVideo) {
- return;
- }
- try {
- await cameraController.resumeVideoRecording();
- } on CameraException catch (e) {
- PromptBox.toast('Error: ${e.code}\n${e.description}');
- rethrow;
- }
- }
- /// 在 widget 内存中分配后立即调用。
- @override
- void onInit() async {
- // await initCamera();
- super.onInit();
- }
- /// 在 onInit() 之后调用 1 帧。这是进入的理想场所
- @override
- void onReady() async {
- super.onReady();
- await initAvailableCameras();
- openBackCamera();
- }
- /// 在 [onDelete] 方法之前调用。
- @override
- void onClose() {
- super.onClose();
- final cacheManager = Get.find<ICacheManager>();
- cacheManager.clearApplicationImageCache();
- }
- /// dispose 释放内存
- @override
- void dispose() {
- super.dispose();
- }
- @override
- void didChangeAppLifecycleState(AppLifecycleState state) {
- // FIXME 未执行
- super.didChangeAppLifecycleState(state);
- print('state = $state');
- final CameraController? cameraController = kCameraController;
- // App state changed before we got the chance to initialize.
- if (cameraController == null || !cameraController.value.isInitialized) {
- return;
- }
- if (state == AppLifecycleState.inactive) {
- cameraController.dispose();
- } else if (state == AppLifecycleState.resumed) {
- onNewCameraSelected(cameraController.description);
- }
- }
- }
|