From b2eb3d3630685c59ed5ca97513a21063aca66ee7 Mon Sep 17 00:00:00 2001 From: Conezi Date: Sun, 26 May 2024 20:41:27 +0100 Subject: [PATCH 1/9] Updated dependencies --- example/pubspec.lock | 96 +++++++++++++++++++++++++++----------------- pubspec.yaml | 4 +- 2 files changed, 62 insertions(+), 38 deletions(-) diff --git a/example/pubspec.lock b/example/pubspec.lock index f3e9365..f96b804 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -21,42 +21,42 @@ packages: dependency: transitive description: name: camera - sha256: "9499cbc2e51d8eb0beadc158b288380037618ce4e30c9acbc4fae1ac3ecb5797" + sha256: cf8ed1789aa244392cfc49d13e97879700476ba641c92500e01f630e109c0f6c url: "https://pub.dev" source: hosted - version: "0.10.5+9" - camera_android: + version: "0.11.0" + camera_android_camerax: dependency: transitive description: - name: camera_android - sha256: f83e406d34f5faa80bf0f5c3beee4b4c11da94a94e9621c1bb8e312988621b4b + name: camera_android_camerax + sha256: "59967e6d80df9d682a33b86f228cc524e6b52d6184b84f6ac62151dd98bd1ea0" url: "https://pub.dev" source: hosted - version: "0.10.8+2" + version: "0.6.5+2" camera_avfoundation: dependency: transitive description: name: camera_avfoundation - sha256: "1a416e452b30955b392f4efbf23291d3f2ba3660a85e1628859eb62d2a2bab26" + sha256: "7d021e8cd30d9b71b8b92b4ad669e80af432d722d18d6aac338572754a786c15" url: "https://pub.dev" source: hosted - version: "0.9.13+2" + version: "0.9.16" camera_platform_interface: dependency: transitive description: name: camera_platform_interface - sha256: "60fa0bb62a4f3bf3a7c413e31e4cd01b69c779ccc8e4668904a24581b86c316b" + sha256: a250314a48ea337b35909a4c9d5416a208d736dcb01d0b02c6af122be66660b0 url: "https://pub.dev" source: hosted - version: "2.5.1" + version: "2.7.4" camera_web: dependency: transitive description: name: camera_web - sha256: "496de93c5d462738ce991dbfe91fb19026f115ed36406700a20a380fb0018299" + sha256: "9e9aba2fbab77ce2472924196ff8ac4dd8f9126c4f9a3096171cd1d870d6b26c" url: "https://pub.dev" source: hosted - version: "0.3.1+1" + version: "0.3.3" characters: dependency: transitive description: @@ -147,18 +147,18 @@ packages: dependency: transitive description: name: google_mlkit_commons - sha256: "046586b381cdd139f7f6a05ad6998f7e339d061bd70158249907358394b5f496" + sha256: "27d626c66a181351a953eba5b6ff1ff123aadb891b4dab085b292118f039d6ac" url: "https://pub.dev" source: hosted - version: "0.6.1" + version: "0.7.1" google_mlkit_face_detection: dependency: transitive description: name: google_mlkit_face_detection - sha256: a6ccff7d6ca8dfc2ab845ea7481645342e82aed31a81edd075169a3abbb25ed3 + sha256: "5b597061cafe4dfa70f66adddadd19381eb88bd3312b074528c62b246392304b" url: "https://pub.dev" source: hosted - version: "0.9.0" + version: "0.11.0" js: dependency: transitive description: @@ -167,6 +167,30 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.7" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + url: "https://pub.dev" + source: hosted + version: "10.0.4" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + url: "https://pub.dev" + source: hosted + version: "3.0.3" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" lints: dependency: transitive description: @@ -179,42 +203,42 @@ packages: dependency: transitive description: name: matcher - sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb url: "https://pub.dev" source: hosted - version: "0.12.16" + version: "0.12.16+1" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" url: "https://pub.dev" source: hosted - version: "0.5.0" + version: "0.8.0" meta: dependency: transitive description: name: meta - sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e + sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.12.0" path: dependency: transitive description: name: path - sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" url: "https://pub.dev" source: hosted - version: "1.8.3" + version: "1.9.0" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface - sha256: "075f927ebbab4262ace8d0b283929ac5410c0ac4e7fc123c76429564facfb757" + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.8" sky_engine: dependency: transitive description: flutter @@ -248,10 +272,10 @@ packages: dependency: transitive description: name: stream_transform - sha256: ed464977cb26a1f41537e177e190c67223dbd9f4f683489b6ab2e5d211ec564e + sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "2.1.0" string_scanner: dependency: transitive description: @@ -272,10 +296,10 @@ packages: dependency: transitive description: name: test_api - sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" url: "https://pub.dev" source: hosted - version: "0.6.1" + version: "0.7.0" vector_math: dependency: transitive description: @@ -284,14 +308,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" - web: + vm_service: dependency: transitive description: - name: web - sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 + name: vm_service + sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" url: "https://pub.dev" source: hosted - version: "0.3.0" + version: "14.2.1" sdks: - dart: ">=3.2.0-194.0.dev <4.0.0" - flutter: ">=3.10.0" + dart: ">=3.3.0 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/pubspec.yaml b/pubspec.yaml index bb6694a..143082c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -10,8 +10,8 @@ environment: dependencies: flutter: sdk: flutter - camera: ^0.10.5+9 - google_mlkit_face_detection: ^0.9.0 + camera: ^0.11.0 + google_mlkit_face_detection: ^0.11.0 dev_dependencies: flutter_test: From a75fbf3698168ecd2013a5f7c82eaeb37d252eca Mon Sep 17 00:00:00 2001 From: Conezi Date: Mon, 27 May 2024 01:15:32 +0100 Subject: [PATCH 2/9] Complete implementation of FaceCameraController --- example/lib/main.dart | 26 +- lib/face_camera.dart | 2 +- .../controllers/face_camera_controller.dart | 272 +++++++++ lib/src/controllers/face_camera_state.dart | 90 +++ lib/src/smart_face_camera.dart | 520 +++++------------- 5 files changed, 517 insertions(+), 393 deletions(-) create mode 100644 lib/src/controllers/face_camera_controller.dart create mode 100644 lib/src/controllers/face_camera_state.dart diff --git a/example/lib/main.dart b/example/lib/main.dart index e82bef7..97629db 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -22,6 +22,23 @@ class MyApp extends StatefulWidget { class _MyAppState extends State { File? _capturedImage; + late FaceCameraController controller; + + @override + void initState() { + controller = FaceCameraController( + autoCapture: true, + defaultCameraLens: CameraLens.front, + onCapture: (File? image) { + setState(() => _capturedImage = image); + }, + onFaceDetected: (Face? face) { + //Do something + }, + ); + super.initState(); + } + @override Widget build(BuildContext context) { return MaterialApp( @@ -53,14 +70,7 @@ class _MyAppState extends State { ); } return SmartFaceCamera( - autoCapture: true, - defaultCameraLens: CameraLens.front, - onCapture: (File? image) { - setState(() => _capturedImage = image); - }, - onFaceDetected: (Face? face) { - //Do something - }, + controller: controller, messageBuilder: (context, face) { if (face == null) { return _message('Place your face in the camera'); diff --git a/lib/face_camera.dart b/lib/face_camera.dart index 4c2ac88..ef186a0 100644 --- a/lib/face_camera.dart +++ b/lib/face_camera.dart @@ -9,9 +9,9 @@ export 'package:google_mlkit_face_detection/google_mlkit_face_detection.dart'; export 'package:face_camera/src/smart_face_camera.dart'; export 'package:face_camera/src/res/enums.dart'; export 'package:face_camera/src/models/detected_image.dart'; +export 'package:face_camera/src/controllers/face_camera_controller.dart'; class FaceCamera { - //static const MethodChannel _channel = MethodChannel('face_camera'); static List _cameras = []; /// Initialize device cameras diff --git a/lib/src/controllers/face_camera_controller.dart b/lib/src/controllers/face_camera_controller.dart new file mode 100644 index 0000000..dc9418f --- /dev/null +++ b/lib/src/controllers/face_camera_controller.dart @@ -0,0 +1,272 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:camera/camera.dart'; +import 'package:flutter/widgets.dart'; + +import '../../face_camera.dart'; +import '../handlers/enum_handler.dart'; +import '../handlers/face_identifier.dart'; +import '../utils/logger.dart'; +import 'face_camera_state.dart'; + +/// The controller for the [SmartFaceCamera] widget. +class FaceCameraController extends ValueNotifier { + /// Construct a new [FaceCameraController] instance. + FaceCameraController({ + this.imageResolution = ImageResolution.medium, + this.defaultCameraLens, + this.defaultFlashMode = CameraFlashMode.auto, + this.enableAudio = true, + this.autoCapture = false, + this.orientation = CameraOrientation.portraitUp, + this.performanceMode = FaceDetectorMode.fast, + required this.onCapture, + this.onFaceDetected, + }) : super(FaceCameraState.uninitialized()); + + /// The desired resolution for the camera. + final ImageResolution imageResolution; + + /// Use this to set initial camera lens direction. + final CameraLens? defaultCameraLens; + + /// Use this to set initial flash mode. + final CameraFlashMode defaultFlashMode; + + /// Set false to disable capture sound. + final bool enableAudio; + + /// Set true to capture image on face detected. + final bool autoCapture; + + /// Use this to lock camera orientation. + final CameraOrientation? orientation; + + /// Use this to set your preferred performance mode. + final FaceDetectorMode performanceMode; + + /// Callback invoked when camera captures image. + final void Function(File? image) onCapture; + + /// Callback invoked when camera detects face. + final void Function(Face? face)? onFaceDetected; + + /// Gets all available camera lens and set current len + void _getAllAvailableCameraLens() { + int currentCameraLens = 0; + final List availableCameraLens = []; + for (CameraDescription d in FaceCamera.cameras) { + final lens = EnumHandler.cameraLensDirectionToCameraLens(d.lensDirection); + if (lens != null && !availableCameraLens.contains(lens)) { + availableCameraLens.add(lens); + } + } + + if (defaultCameraLens != null) { + try { + currentCameraLens = availableCameraLens.indexOf(defaultCameraLens!); + } catch (e) { + logError(e.toString()); + } + } + + value = value.copyWith( + availableCameraLens: availableCameraLens, + currentCameraLens: currentCameraLens); + } + + Future _initCamera() async { + final cameras = FaceCamera.cameras + .where((c) => + c.lensDirection == + EnumHandler.cameraLensToCameraLensDirection( + value.availableCameraLens[value.currentCameraLens])) + .toList(); + + if (cameras.isNotEmpty) { + final cameraController = CameraController(cameras.first, + EnumHandler.imageResolutionToResolutionPreset(imageResolution), + enableAudio: enableAudio, + imageFormatGroup: Platform.isAndroid + ? ImageFormatGroup.nv21 + : ImageFormatGroup.bgra8888) + ..initialize(); + + await changeFlashMode(value.availableFlashMode.indexOf(defaultFlashMode)); + + await cameraController + .lockCaptureOrientation( + EnumHandler.cameraOrientationToDeviceOrientation(orientation)) + .then((_) { + value = value.copyWith( + isInitialized: true, cameraController: cameraController); + }); + } + + startImageStream(); + } + + Future changeFlashMode([int? index]) async { + final newIndex = + index ?? (value.currentFlashMode + 1) % value.availableFlashMode.length; + await value.cameraController! + .setFlashMode(EnumHandler.cameraFlashModeToFlashMode( + value.availableFlashMode[newIndex])) + .then((_) { + value = value.copyWith(currentCameraLens: newIndex); + }); + } + + Future changeCameraLens() async { + value = value.copyWith( + currentCameraLens: + (value.currentCameraLens + 1) % value.availableCameraLens.length); + _initCamera(); + } + + Future takePicture() async { + final CameraController? cameraController = value.cameraController; + if (cameraController == null || !cameraController.value.isInitialized) { + logError('Error: select a camera first.'); + return null; + } + + if (cameraController.value.isTakingPicture) { + logError('A capture is already pending'); + return null; + } + + try { + XFile file = await cameraController.takePicture(); + return file; + } on CameraException catch (e) { + _showCameraException(e); + return null; + } + } + + void _showCameraException(CameraException e) { + logError(e.code, e.description); + } + + void startImageStream() { + final CameraController? cameraController = value.cameraController; + if (cameraController == null || !cameraController.value.isInitialized) { + return; + } + if (!cameraController.value.isStreamingImages) { + cameraController.startImageStream(_processImage); + } + } + + void stopImageStream() { + final CameraController? cameraController = value.cameraController; + if (cameraController == null || !cameraController.value.isInitialized) { + return; + } + if (cameraController.value.isStreamingImages) { + cameraController.stopImageStream(); + } + } + + void _processImage(CameraImage cameraImage) async { + final CameraController? cameraController = value.cameraController; + if (!value.alreadyCheckingImage) { + value = value.copyWith(alreadyCheckingImage: true); + try { + await FaceIdentifier.scanImage( + cameraImage: cameraImage, + controller: cameraController, + performanceMode: performanceMode) + .then((result) async { + value = value.copyWith(detectedFace: result); + + if (result != null) { + try { + if (result.wellPositioned) { + onFaceDetected?.call(result.face); + if (autoCapture) { + onTakePictureButtonPressed(); + } + } + } catch (e) { + logError(e.toString()); + } + } + }); + value = value.copyWith(alreadyCheckingImage: false); + } catch (ex, stack) { + value = value.copyWith(alreadyCheckingImage: false); + logError('$ex, $stack'); + } + } + } + + void onTakePictureButtonPressed() async { + final CameraController? cameraController = value.cameraController; + try { + cameraController!.stopImageStream().whenComplete(() async { + await Future.delayed(const Duration(milliseconds: 500)); + takePicture().then((XFile? file) { + /// Return image callback + if (file != null) { + onCapture.call(File(file.path)); + } + + /// Resume image stream after 2 seconds of capture + Future.delayed(const Duration(seconds: 2)).whenComplete(() { + if (cameraController.value.isInitialized) { + try { + startImageStream(); + } catch (e) { + logError(e.toString()); + } + } + }); + }); + }); + } catch (e) { + logError(e.toString()); + } + } + +/* void onViewFinderTap(TapDownDetails details, BoxConstraints constraints) { + if (value.cameraController == null) { + return; + } + + final CameraController cameraController = value.cameraController!; + + final offset = Offset( + details.localPosition.dx / constraints.maxWidth, + details.localPosition.dy / constraints.maxHeight, + ); + cameraController.setExposurePoint(offset); + cameraController.setFocusPoint(offset); + }*/ + + Future initialize() async { + _getAllAvailableCameraLens(); + _initCamera(); + } + + /// Enables controls only when camera is initialized. + bool get enableControls { + final CameraController? cameraController = value.cameraController; + return cameraController != null && cameraController.value.isInitialized; + } + + /// Dispose the controller. + /// + /// Once the controller is disposed, it cannot be used anymore. + @override + Future dispose() async { + final CameraController? cameraController = value.cameraController; + + if (cameraController != null && cameraController.value.isInitialized) { + cameraController.dispose(); + } + super.dispose(); + } +} diff --git a/lib/src/controllers/face_camera_state.dart b/lib/src/controllers/face_camera_state.dart new file mode 100644 index 0000000..cdb4a11 --- /dev/null +++ b/lib/src/controllers/face_camera_state.dart @@ -0,0 +1,90 @@ +import 'package:camera/camera.dart'; + +import '../../face_camera.dart'; + +/// This class represents the current state of a [FaceCameraController]. +class FaceCameraState { + /// Create a new [FaceCameraState] instance. + const FaceCameraState({ + required this.currentCameraLens, + required this.currentFlashMode, + required this.isInitialized, + required this.availableCameraLens, + required this.availableFlashMode, + required this.alreadyCheckingImage, + this.cameraController, + this.detectedFace, + }); + + /// Create a new [FaceCameraState] instance that is uninitialized. + FaceCameraState.uninitialized() + : this( + availableCameraLens: [], + currentCameraLens: 0, + currentFlashMode: 0, + isInitialized: false, + alreadyCheckingImage: false, + cameraController: null, + detectedFace: null, + availableFlashMode: [ + CameraFlashMode.off, + CameraFlashMode.auto, + CameraFlashMode.always + ], + ); + + /// Camera dependency controller + final CameraController? cameraController; + + /// The available cameras. + /// + /// This is null if no camera is found. + final List availableCameraLens; + + /// The current camera lens in use. + /// + /// Default value is 0. + final int currentCameraLens; + + /// The current flash mode in use. + /// + /// Default value is 0. + final int currentFlashMode; + + /// Whether the face camera has initialized successfully. + /// + /// This is `true` if the camera is ready to be used. + final bool isInitialized; + + final List availableFlashMode; + + final bool alreadyCheckingImage; + + final DetectedFace? detectedFace; + + /// Create a copy of this state with the given parameters. + FaceCameraState copyWith({ + List? availableCameraLens, + int? currentCameraLens, + int? currentFlashMode, + CameraFlashMode? flashMode, + bool? isInitialized, + bool? isRunning, + bool? alreadyCheckingImage, + double? zoomScale, + CameraController? cameraController, + List? availableFlashMode, + DetectedFace? detectedFace, + }) { + return FaceCameraState( + availableCameraLens: availableCameraLens ?? this.availableCameraLens, + currentCameraLens: currentCameraLens ?? this.currentCameraLens, + currentFlashMode: currentFlashMode ?? this.currentFlashMode, + isInitialized: isInitialized ?? this.isInitialized, + alreadyCheckingImage: alreadyCheckingImage ?? this.alreadyCheckingImage, + cameraController: cameraController ?? this.cameraController, + availableFlashMode: availableFlashMode ?? this.availableFlashMode, + detectedFace: detectedFace ?? this.detectedFace, + ); + } +} diff --git a/lib/src/smart_face_camera.dart b/lib/src/smart_face_camera.dart index 59fc115..2070f19 100644 --- a/lib/src/smart_face_camera.dart +++ b/lib/src/smart_face_camera.dart @@ -1,33 +1,13 @@ -import 'dart:async'; -import 'dart:io'; - import 'package:camera/camera.dart'; import 'package:flutter/material.dart'; import '../face_camera.dart'; -import 'handlers/enum_handler.dart'; -import 'handlers/face_identifier.dart'; +import 'controllers/face_camera_state.dart'; import 'paints/face_painter.dart'; import 'paints/hole_painter.dart'; import 'res/builders.dart'; -import 'utils/logger.dart'; class SmartFaceCamera extends StatefulWidget { - /// Use this to set image resolution. - final ImageResolution imageResolution; - - /// Use this to set initial camera lens direction. - final CameraLens? defaultCameraLens; - - /// Use this to set initial flash mode. - final CameraFlashMode defaultFlashMode; - - /// Set false to disable capture sound. - final bool enableAudio; - - /// Set true to capture image on face detected. - final bool autoCapture; - /// Set false to hide all controls. final bool showControls; @@ -46,18 +26,6 @@ class SmartFaceCamera extends StatefulWidget { /// Style applied to the message widget. final TextStyle messageStyle; - /// Use this to lock camera orientation. - final CameraOrientation? orientation; - - /// Callback invoked when camera captures image. - final void Function(File? image) onCapture; - - /// Callback invoked when camera detects face. - final void Function(Face? face)? onFaceDetected; - - /// Use this to render a custom widget for capture control. - final Widget? captureControlIcon; - /// Use this to build custom widgets for capture control. final CaptureControlBuilder? captureControlBuilder; @@ -82,26 +50,18 @@ class SmartFaceCamera extends StatefulWidget { /// Set true to automatically disable capture control widget when no face is detected. final bool autoDisableCaptureControl; - /// Use this to set your preferred performance mode. - final FaceDetectorMode performanceMode; + /// The controller for the [SmartFaceCamera] widget. + final FaceCameraController controller; const SmartFaceCamera( - {this.imageResolution = ImageResolution.medium, - this.defaultCameraLens, - this.enableAudio = true, - this.autoCapture = false, + {required this.controller, this.showControls = true, this.showCaptureControl = true, this.showFlashControl = true, this.showCameraLensControl = true, this.message, - this.defaultFlashMode = CameraFlashMode.auto, - this.orientation = CameraOrientation.portraitUp, this.messageStyle = const TextStyle( fontSize: 14, height: 1.5, fontWeight: FontWeight.w400), - required this.onCapture, - this.onFaceDetected, - @Deprecated('Use [captureControlBuilder]') this.captureControlIcon, this.captureControlBuilder, this.lensControlIcon, this.flashControlBuilder, @@ -110,7 +70,6 @@ class SmartFaceCamera extends StatefulWidget { this.indicatorAssetImage, this.indicatorBuilder, this.autoDisableCaptureControl = false, - this.performanceMode = FaceDetectorMode.fast, Key? key}) : assert( indicatorShape != IndicatorShape.image || @@ -123,227 +82,146 @@ class SmartFaceCamera extends StatefulWidget { } class _SmartFaceCameraState extends State - with WidgetsBindingObserver, TickerProviderStateMixin { - CameraController? _controller; - - bool _alreadyCheckingImage = false; - - DetectedFace? _detectedFace; - - int _currentFlashMode = 0; - final List _availableFlashMode = [ - CameraFlashMode.off, - CameraFlashMode.auto, - CameraFlashMode.always - ]; - - int _currentCameraLens = 0; - final List _availableCameraLens = []; - - void _getAllAvailableCameraLens() { - for (CameraDescription d in FaceCamera.cameras) { - final lens = EnumHandler.cameraLensDirectionToCameraLens(d.lensDirection); - if (lens != null && !_availableCameraLens.contains(lens)) { - _availableCameraLens.add(lens); - } - } - - if (widget.defaultCameraLens != null) { - try { - _currentCameraLens = - _availableCameraLens.indexOf(widget.defaultCameraLens!); - } catch (e) { - logError(e.toString()); - } - } - } - - Future _initCamera() async { - final cameras = FaceCamera.cameras - .where((c) => - c.lensDirection == - EnumHandler.cameraLensToCameraLensDirection( - _availableCameraLens[_currentCameraLens])) - .toList(); - - if (cameras.isNotEmpty) { - _controller = CameraController(cameras.first, - EnumHandler.imageResolutionToResolutionPreset(widget.imageResolution), - enableAudio: widget.enableAudio, - imageFormatGroup: Platform.isAndroid - ? ImageFormatGroup.nv21 - : ImageFormatGroup.bgra8888); - - await _controller!.initialize().then((_) { - if (!mounted) { - return; - } - setState(() {}); - }); - - await _changeFlashMode( - _availableFlashMode.indexOf(widget.defaultFlashMode)); - - await _controller! - .lockCaptureOrientation( - EnumHandler.cameraOrientationToDeviceOrientation( - widget.orientation)) - .then((_) { - if (mounted) setState(() {}); - }); - } - - _startImageStream(); - } - - Future _changeFlashMode(int index) async { - await _controller! - .setFlashMode( - EnumHandler.cameraFlashModeToFlashMode(_availableFlashMode[index])) - .then((_) { - if (mounted) setState(() => _currentFlashMode = index); - }); - } - + with WidgetsBindingObserver { @override void initState() { WidgetsBinding.instance.addObserver(this); - _getAllAvailableCameraLens(); - _initCamera(); + widget.controller.initialize(); super.initState(); } @override void dispose() { WidgetsBinding.instance.removeObserver(this); - final CameraController? cameraController = _controller; - - if (cameraController != null && cameraController.value.isInitialized) { - cameraController.dispose(); - } - + widget.controller.dispose(); super.dispose(); } @override void didChangeAppLifecycleState(AppLifecycleState state) { - final CameraController? cameraController = _controller; - - // App state changed before we got the chance to initialize. - if (cameraController == null || !cameraController.value.isInitialized) { - return; - } - if (state == AppLifecycleState.inactive) { - if (cameraController.value.isStreamingImages) { - cameraController.stopImageStream(); - } + widget.controller.stopImageStream(); + } else if (state == AppLifecycleState.paused) { + widget.controller.stopImageStream(); } else if (state == AppLifecycleState.resumed) { - if (!cameraController.value.isStreamingImages) { - _startImageStream(); - } + widget.controller.startImageStream(); } } @override Widget build(BuildContext context) { final size = MediaQuery.of(context).size; - final CameraController? cameraController = _controller; - return Stack( - alignment: Alignment.center, - children: [ - if (cameraController != null && - cameraController.value.isInitialized) ...[ - Transform.scale( - scale: 1.0, - child: AspectRatio( - aspectRatio: size.aspectRatio, - child: OverflowBox( - alignment: Alignment.center, - child: FittedBox( - fit: BoxFit.fitHeight, - child: SizedBox( - width: size.width, - height: size.width * cameraController.value.aspectRatio, - child: Stack( - fit: StackFit.expand, - children: [ - _cameraDisplayWidget(), - if (_detectedFace != null && - widget.indicatorShape != IndicatorShape.none) ...[ - SizedBox( - width: cameraController.value.previewSize!.width, - height: - cameraController.value.previewSize!.height, - child: widget.indicatorBuilder?.call( - context, - _detectedFace, - Size( - _controller!.value.previewSize!.height, - _controller!.value.previewSize!.width, - )) ?? - CustomPaint( - painter: FacePainter( - face: _detectedFace!.face, - indicatorShape: widget.indicatorShape, - indicatorAssetImage: - widget.indicatorAssetImage, - imageSize: Size( - _controller! - .value.previewSize!.height, - _controller!.value.previewSize!.width, - )), - )) - ] - ], + return ValueListenableBuilder( + valueListenable: widget.controller, + builder: (BuildContext context, FaceCameraState value, Widget? child) { + final CameraController? cameraController = value.cameraController; + return Stack( + alignment: Alignment.center, + children: [ + if (cameraController != null && + cameraController.value.isInitialized) ...[ + Transform.scale( + scale: 1.0, + child: AspectRatio( + aspectRatio: size.aspectRatio, + child: OverflowBox( + alignment: Alignment.center, + child: FittedBox( + fit: BoxFit.fitHeight, + child: SizedBox( + width: size.width, + height: size.width * cameraController.value.aspectRatio, + child: Stack( + fit: StackFit.expand, + children: [ + _cameraDisplayWidget(value), + if (value.detectedFace != null && + widget.indicatorShape != + IndicatorShape.none) ...[ + SizedBox( + width: + cameraController.value.previewSize!.width, + height: cameraController + .value.previewSize!.height, + child: widget.indicatorBuilder?.call( + context, + value.detectedFace, + Size( + cameraController + .value.previewSize!.height, + cameraController + .value.previewSize!.width, + )) ?? + CustomPaint( + painter: FacePainter( + face: value.detectedFace!.face, + indicatorShape: + widget.indicatorShape, + indicatorAssetImage: + widget.indicatorAssetImage, + imageSize: Size( + cameraController + .value.previewSize!.height, + cameraController + .value.previewSize!.width, + )), + )) + ] + ], + ), + ), ), ), ), - ), - ), - ) - ] else ...[ - const Text('No Camera Detected', - style: TextStyle( - fontSize: 18.0, - fontWeight: FontWeight.w500, - )), - CustomPaint( - size: size, - painter: HolePainter(), - ) - ], - if (widget.showControls) ...[ - Align( - alignment: Alignment.bottomCenter, - child: Padding( - padding: const EdgeInsets.all(15.0), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - if (widget.showFlashControl) ...[_flashControlWidget()], - if (widget.showCaptureControl) ...[ - const SizedBox(width: 15), - _captureControlWidget(), - const SizedBox(width: 15) - ], - if (widget.showCameraLensControl) ...[_lensControlWidget()], - ], - ), - ), - ) - ] - ], + ) + ] else ...[ + const Text('No Camera Detected', + style: TextStyle( + fontSize: 18.0, + fontWeight: FontWeight.w500, + )), + CustomPaint( + size: size, + painter: HolePainter(), + ) + ], + if (widget.showControls) ...[ + Align( + alignment: Alignment.bottomCenter, + child: Padding( + padding: const EdgeInsets.all(15.0), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (widget.showFlashControl) ...[ + _flashControlWidget(value) + ], + if (widget.showCaptureControl) ...[ + const SizedBox(width: 15), + _captureControlWidget(value), + const SizedBox(width: 15) + ], + if (widget.showCameraLensControl) ...[ + _lensControlWidget() + ], + ], + ), + ), + ) + ] + ], + ); + }, ); } /// Render camera. - Widget _cameraDisplayWidget() { - final CameraController? cameraController = _controller; + Widget _cameraDisplayWidget(FaceCameraState value) { + final CameraController? cameraController = value.cameraController; if (cameraController != null && cameraController.value.isInitialized) { return CameraPreview(cameraController, child: Builder(builder: (context) { if (widget.messageBuilder != null) { - return widget.messageBuilder!.call(context, _detectedFace); + return widget.messageBuilder!.call(context, value.detectedFace); } if (widget.message != null) { return Padding( @@ -358,52 +236,48 @@ class _SmartFaceCameraState extends State return const SizedBox.shrink(); } - /// Enables controls only when camera is initialized. - bool get _enableControls { - final CameraController? cameraController = _controller; - return cameraController != null && cameraController.value.isInitialized; - } - /// Determines when to disable the capture control button. bool get _disableCapture => - widget.autoDisableCaptureControl && _detectedFace?.face == null; + widget.autoDisableCaptureControl && + widget.controller.value.detectedFace?.face == null; /// Determines the camera controls color. Color? get iconColor => - _enableControls ? null : Theme.of(context).disabledColor; + widget.controller.enableControls ? null : Theme.of(context).disabledColor; /// Display the control buttons to take pictures. - Widget _captureControlWidget() { + Widget _captureControlWidget(FaceCameraState value) { return IconButton( - icon: widget.captureControlBuilder?.call(context, _detectedFace) ?? - widget.captureControlIcon ?? + icon: widget.captureControlBuilder?.call(context, value.detectedFace) ?? CircleAvatar( radius: 35, - foregroundColor: _enableControls && !_disableCapture - ? null - : Theme.of(context).disabledColor, + foregroundColor: + widget.controller.enableControls && !_disableCapture + ? null + : Theme.of(context).disabledColor, child: const Padding( padding: EdgeInsets.all(8.0), child: Icon(Icons.camera_alt, size: 35), )), - onPressed: _enableControls && !_disableCapture - ? _onTakePictureButtonPressed + onPressed: widget.controller.enableControls && !_disableCapture + ? widget.controller.onTakePictureButtonPressed : null, ); } /// Display the control buttons to switch between flash modes. - Widget _flashControlWidget() { - final icon = - _availableFlashMode[_currentFlashMode] == CameraFlashMode.always - ? Icons.flash_on - : _availableFlashMode[_currentFlashMode] == CameraFlashMode.off - ? Icons.flash_off - : Icons.flash_auto; + Widget _flashControlWidget(FaceCameraState value) { + final availableFlashMode = value.availableFlashMode; + final currentFlashMode = value.currentFlashMode; + final icon = availableFlashMode[currentFlashMode] == CameraFlashMode.always + ? Icons.flash_on + : availableFlashMode[currentFlashMode] == CameraFlashMode.off + ? Icons.flash_off + : Icons.flash_auto; return IconButton( icon: widget.flashControlBuilder - ?.call(context, _availableFlashMode[_currentFlashMode]) ?? + ?.call(context, availableFlashMode[currentFlashMode]) ?? CircleAvatar( radius: 25, foregroundColor: iconColor, @@ -411,9 +285,8 @@ class _SmartFaceCameraState extends State padding: const EdgeInsets.all(2.0), child: Icon(icon, size: 25), )), - onPressed: _enableControls - ? () => _changeFlashMode( - (_currentFlashMode + 1) % _availableFlashMode.length) + onPressed: widget.controller.enableControls + ? widget.controller.changeFlashMode : null, ); } @@ -429,129 +302,8 @@ class _SmartFaceCameraState extends State padding: EdgeInsets.all(2.0), child: Icon(Icons.switch_camera_sharp, size: 25), )), - onPressed: _enableControls - ? () { - _currentCameraLens = - (_currentCameraLens + 1) % _availableCameraLens.length; - _initCamera(); - } + onPressed: widget.controller.enableControls + ? widget.controller.changeCameraLens : null); } - - String timestamp() => DateTime.now().millisecondsSinceEpoch.toString(); - - void showInSnackBar(String message) { - ScaffoldMessenger.of(context) - .showSnackBar(SnackBar(content: Text(message))); - } - - void onViewFinderTap(TapDownDetails details, BoxConstraints constraints) { - if (_controller == null) { - return; - } - - final CameraController cameraController = _controller!; - - final offset = Offset( - details.localPosition.dx / constraints.maxWidth, - details.localPosition.dy / constraints.maxHeight, - ); - cameraController.setExposurePoint(offset); - cameraController.setFocusPoint(offset); - } - - void _onTakePictureButtonPressed() async { - final CameraController? cameraController = _controller; - try { - cameraController!.stopImageStream().whenComplete(() async { - await Future.delayed(const Duration(milliseconds: 500)); - takePicture().then((XFile? file) { - /// Return image callback - if (file != null) { - widget.onCapture(File(file.path)); - } - - /// Resume image stream after 2 seconds of capture - Future.delayed(const Duration(seconds: 2)).whenComplete(() { - if (mounted && cameraController.value.isInitialized) { - try { - _startImageStream(); - } catch (e) { - logError(e.toString()); - } - } - }); - }); - }); - } catch (e) { - logError(e.toString()); - } - } - - Future takePicture() async { - final CameraController? cameraController = _controller; - if (cameraController == null || !cameraController.value.isInitialized) { - showInSnackBar('Error: select a camera first.'); - return null; - } - - if (cameraController.value.isTakingPicture) { - // A capture is already pending, do nothing. - return null; - } - - try { - XFile file = await cameraController.takePicture(); - return file; - } on CameraException catch (e) { - _showCameraException(e); - return null; - } - } - - void _showCameraException(CameraException e) { - logError(e.code, e.description); - showInSnackBar('Error: ${e.code}\n${e.description}'); - } - - void _startImageStream() { - final CameraController? cameraController = _controller; - if (cameraController != null) { - cameraController.startImageStream(_processImage); - } - } - - void _processImage(CameraImage cameraImage) async { - final CameraController? cameraController = _controller; - if (!_alreadyCheckingImage && mounted) { - _alreadyCheckingImage = true; - try { - await FaceIdentifier.scanImage( - cameraImage: cameraImage, - controller: cameraController, - performanceMode: widget.performanceMode) - .then((result) async { - setState(() => _detectedFace = result); - - if (result != null) { - try { - if (result.wellPositioned) { - if (widget.onFaceDetected != null) { - widget.onFaceDetected!.call(result.face); - } - if (widget.autoCapture) { - _onTakePictureButtonPressed(); - } - } - } catch (e) { - logError(e.toString()); - } - } - }); - _alreadyCheckingImage = false; - } catch (ex, stack) { - logError('$ex, $stack'); - } - } - } } From 10025a87428c36cbf837573eb4d37c82c8e7fc06 Mon Sep 17 00:00:00 2001 From: Conezi Date: Mon, 27 May 2024 12:50:02 +0100 Subject: [PATCH 3/9] Updated controller, working well on IOS --- example/ios/Podfile.lock | 122 +++++++++--------- example/ios/Runner.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/xcschemes/Runner.xcscheme | 2 +- example/lib/main.dart | 12 +- example/pubspec.lock | 28 ++-- .../controllers/face_camera_controller.dart | 39 +++--- lib/src/smart_face_camera.dart | 2 +- 7 files changed, 103 insertions(+), 104 deletions(-) diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index f58b6ee..b2053fd 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -4,67 +4,63 @@ PODS: - face_camera (0.0.1): - Flutter - Flutter (1.0.0) - - google_mlkit_commons (0.6.1): + - google_mlkit_commons (0.7.1): - Flutter - MLKitVision - - google_mlkit_face_detection (0.9.0): + - google_mlkit_face_detection (0.11.0): - Flutter - google_mlkit_commons - - GoogleMLKit/FaceDetection (~> 4.0.0) - - GoogleDataTransport (9.2.3): + - GoogleMLKit/FaceDetection (~> 6.0.0) + - GoogleDataTransport (9.4.1): - GoogleUtilities/Environment (~> 7.7) - - nanopb (< 2.30910.0, >= 2.30908.0) + - nanopb (< 2.30911.0, >= 2.30908.0) - PromisesObjC (< 3.0, >= 1.2) - - GoogleMLKit/FaceDetection (4.0.0): + - GoogleMLKit/FaceDetection (6.0.0): - GoogleMLKit/MLKitCore - - MLKitFaceDetection (~> 3.0.0) - - GoogleMLKit/MLKitCore (4.0.0): - - MLKitCommon (~> 9.0.0) - - GoogleToolboxForMac/DebugUtils (2.3.2): - - GoogleToolboxForMac/Defines (= 2.3.2) - - GoogleToolboxForMac/Defines (2.3.2) - - GoogleToolboxForMac/Logger (2.3.2): - - GoogleToolboxForMac/Defines (= 2.3.2) - - "GoogleToolboxForMac/NSData+zlib (2.3.2)": - - GoogleToolboxForMac/Defines (= 2.3.2) - - "GoogleToolboxForMac/NSDictionary+URLArguments (2.3.2)": - - GoogleToolboxForMac/DebugUtils (= 2.3.2) - - GoogleToolboxForMac/Defines (= 2.3.2) - - "GoogleToolboxForMac/NSString+URLArguments (= 2.3.2)" - - "GoogleToolboxForMac/NSString+URLArguments (2.3.2)" - - GoogleUtilities/Environment (7.11.1): + - MLKitFaceDetection (~> 5.0.0) + - GoogleMLKit/MLKitCore (6.0.0): + - MLKitCommon (~> 11.0.0) + - GoogleToolboxForMac/Defines (4.2.1) + - GoogleToolboxForMac/Logger (4.2.1): + - GoogleToolboxForMac/Defines (= 4.2.1) + - "GoogleToolboxForMac/NSData+zlib (4.2.1)": + - GoogleToolboxForMac/Defines (= 4.2.1) + - GoogleUtilities/Environment (7.13.3): + - GoogleUtilities/Privacy - PromisesObjC (< 3.0, >= 1.2) - - GoogleUtilities/Logger (7.11.1): + - GoogleUtilities/Logger (7.13.3): - GoogleUtilities/Environment - - GoogleUtilities/UserDefaults (7.11.1): + - GoogleUtilities/Privacy + - GoogleUtilities/Privacy (7.13.3) + - GoogleUtilities/UserDefaults (7.13.3): - GoogleUtilities/Logger + - GoogleUtilities/Privacy - GoogleUtilitiesComponents (1.1.0): - GoogleUtilities/Logger - - GTMSessionFetcher/Core (2.3.0) - - MLImage (1.0.0-beta4) - - MLKitCommon (9.0.0): - - GoogleDataTransport (~> 9.0) - - GoogleToolboxForMac/Logger (~> 2.1) - - "GoogleToolboxForMac/NSData+zlib (~> 2.1)" - - "GoogleToolboxForMac/NSDictionary+URLArguments (~> 2.1)" - - GoogleUtilities/UserDefaults (~> 7.0) + - GTMSessionFetcher/Core (3.4.1) + - MLImage (1.0.0-beta5) + - MLKitCommon (11.0.0): + - GoogleDataTransport (< 10.0, >= 9.4.1) + - GoogleToolboxForMac/Logger (< 5.0, >= 4.2.1) + - "GoogleToolboxForMac/NSData+zlib (< 5.0, >= 4.2.1)" + - GoogleUtilities/UserDefaults (< 8.0, >= 7.13.0) - GoogleUtilitiesComponents (~> 1.0) - - GTMSessionFetcher/Core (< 3.0, >= 1.1) - - MLKitFaceDetection (3.0.0): - - MLKitCommon (~> 9.0) - - MLKitVision (~> 5.0) - - MLKitVision (5.0.0): - - GoogleToolboxForMac/Logger (~> 2.1) - - "GoogleToolboxForMac/NSData+zlib (~> 2.1)" - - GTMSessionFetcher/Core (< 3.0, >= 1.1) - - MLImage (= 1.0.0-beta4) - - MLKitCommon (~> 9.0) - - nanopb (2.30909.0): - - nanopb/decode (= 2.30909.0) - - nanopb/encode (= 2.30909.0) - - nanopb/decode (2.30909.0) - - nanopb/encode (2.30909.0) - - PromisesObjC (2.2.0) + - GTMSessionFetcher/Core (< 4.0, >= 3.3.2) + - MLKitFaceDetection (5.0.0): + - MLKitCommon (~> 11.0) + - MLKitVision (~> 7.0) + - MLKitVision (7.0.0): + - GoogleToolboxForMac/Logger (< 5.0, >= 4.2.1) + - "GoogleToolboxForMac/NSData+zlib (< 5.0, >= 4.2.1)" + - GTMSessionFetcher/Core (< 4.0, >= 3.3.2) + - MLImage (= 1.0.0-beta5) + - MLKitCommon (~> 11.0) + - nanopb (2.30910.0): + - nanopb/decode (= 2.30910.0) + - nanopb/encode (= 2.30910.0) + - nanopb/decode (2.30910.0) + - nanopb/encode (2.30910.0) + - PromisesObjC (2.4.0) DEPENDENCIES: - camera_avfoundation (from `.symlinks/plugins/camera_avfoundation/ios`) @@ -101,24 +97,24 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/google_mlkit_face_detection/ios" SPEC CHECKSUMS: - camera_avfoundation: 3125e8cd1a4387f6f31c6c63abb8a55892a9eeeb + camera_avfoundation: 759172d1a77ae7be0de08fc104cfb79738b8a59e face_camera: 9473f8c80e20d67bd2d946ed573ccee168d31186 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - google_mlkit_commons: 3857c1e9f23ca02073f8dd34a7a3580feb814cef - google_mlkit_face_detection: 52f7e4c15a7c49b98940fa32af4c2b10fd8608fc - GoogleDataTransport: f0308f5905a745f94fb91fea9c6cbaf3831cb1bd - GoogleMLKit: 2bd0dc6253c4d4f227aad460f69215a504b2980e - GoogleToolboxForMac: 8bef7c7c5cf7291c687cf5354f39f9db6399ad34 - GoogleUtilities: 9aa0ad5a7bc171f8bae016300bfcfa3fb8425749 + google_mlkit_commons: 96aaca445520311b84a2da013dedf3427fe4cc69 + google_mlkit_face_detection: b760d6035222630f347352b3b13f4a23ea9fb994 + GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a + GoogleMLKit: 97ac7af399057e99182ee8edfa8249e3226a4065 + GoogleToolboxForMac: d1a2cbf009c453f4d6ded37c105e2f67a32206d8 + GoogleUtilities: ea963c370a38a8069cc5f7ba4ca849a60b6d7d15 GoogleUtilitiesComponents: 679b2c881db3b615a2777504623df6122dd20afe - GTMSessionFetcher: 3a63d75eecd6aa32c2fc79f578064e1214dfdec2 - MLImage: 7bb7c4264164ade9bf64f679b40fb29c8f33ee9b - MLKitCommon: c1b791c3e667091918d91bda4bba69a91011e390 - MLKitFaceDetection: 4981488f71ea8ed718cda692a29ab562637d9ae5 - MLKitVision: 8baa5f46ee3352614169b85250574fde38c36f49 - nanopb: b552cce312b6c8484180ef47159bc0f65a1f0431 - PromisesObjC: 09985d6d70fbe7878040aa746d78236e6946d2ef + GTMSessionFetcher: 8000756fc1c19d2e5697b90311f7832d2e33f6cd + MLImage: 1824212150da33ef225fbd3dc49f184cf611046c + MLKitCommon: afec63980417d29ffbb4790529a1b0a2291699e1 + MLKitFaceDetection: 7c0e8bf09ddd27105da32d088fca978a99fc30cc + MLKitVision: e858c5f125ecc288e4a31127928301eaba9ae0c1 + nanopb: 438bc412db1928dac798aa6fd75726007be04262 + PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 PODFILE CHECKSUM: 0d895a65d153cd6b7f421b4d23b29c977b9f03bc -COCOAPODS: 1.14.3 +COCOAPODS: 1.15.2 diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index be7536e..02a2dd2 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -156,7 +156,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1430; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index a6b826d..5e31d3d 100644 --- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ { fit: BoxFit.fitWidth, ), ElevatedButton( - onPressed: () => setState(() => _capturedImage = null), + onPressed: () async { + await controller.startImageStream(); + setState(() => _capturedImage = null); + }, child: const Text( 'Capture Again', textAlign: TextAlign.center, @@ -91,4 +94,11 @@ class _MyAppState extends State { style: const TextStyle( fontSize: 14, height: 1.5, fontWeight: FontWeight.w400)), ); + + @override + void dispose() { + controller.dispose(); + super.dispose(); + } + } diff --git a/example/pubspec.lock b/example/pubspec.lock index f96b804..85384b0 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -171,26 +171,26 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" url: "https://pub.dev" source: hosted - version: "10.0.4" + version: "10.0.0" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "2.0.1" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "2.0.1" lints: dependency: transitive description: @@ -219,10 +219,10 @@ packages: dependency: transitive description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.11.0" path: dependency: transitive description: @@ -296,10 +296,10 @@ packages: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.6.1" vector_math: dependency: transitive description: @@ -312,10 +312,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 url: "https://pub.dev" source: hosted - version: "14.2.1" + version: "13.0.0" sdks: - dart: ">=3.3.0 <4.0.0" - flutter: ">=3.18.0-18.0.pre.54" + dart: ">=3.2.3 <4.0.0" + flutter: ">=3.16.6" diff --git a/lib/src/controllers/face_camera_controller.dart b/lib/src/controllers/face_camera_controller.dart index dc9418f..f00b11b 100644 --- a/lib/src/controllers/face_camera_controller.dart +++ b/lib/src/controllers/face_camera_controller.dart @@ -90,18 +90,18 @@ class FaceCameraController extends ValueNotifier { enableAudio: enableAudio, imageFormatGroup: Platform.isAndroid ? ImageFormatGroup.nv21 - : ImageFormatGroup.bgra8888) - ..initialize(); + : ImageFormatGroup.bgra8888); + + await cameraController.initialize().whenComplete((){ + value = value.copyWith( + isInitialized: true, cameraController: cameraController); + }); await changeFlashMode(value.availableFlashMode.indexOf(defaultFlashMode)); await cameraController .lockCaptureOrientation( - EnumHandler.cameraOrientationToDeviceOrientation(orientation)) - .then((_) { - value = value.copyWith( - isInitialized: true, cameraController: cameraController); - }); + EnumHandler.cameraOrientationToDeviceOrientation(orientation)); } startImageStream(); @@ -114,7 +114,7 @@ class FaceCameraController extends ValueNotifier { .setFlashMode(EnumHandler.cameraFlashModeToFlashMode( value.availableFlashMode[newIndex])) .then((_) { - value = value.copyWith(currentCameraLens: newIndex); + value = value.copyWith(currentFlashMode: newIndex); }); } @@ -150,23 +150,23 @@ class FaceCameraController extends ValueNotifier { logError(e.code, e.description); } - void startImageStream() { + Future startImageStream() async { final CameraController? cameraController = value.cameraController; if (cameraController == null || !cameraController.value.isInitialized) { return; } if (!cameraController.value.isStreamingImages) { - cameraController.startImageStream(_processImage); + await cameraController.startImageStream(_processImage); } } - void stopImageStream() { + Future stopImageStream() async { final CameraController? cameraController = value.cameraController; if (cameraController == null || !cameraController.value.isInitialized) { return; } if (cameraController.value.isStreamingImages) { - cameraController.stopImageStream(); + await cameraController.stopImageStream(); } } @@ -184,9 +184,13 @@ class FaceCameraController extends ValueNotifier { if (result != null) { try { + print('object 1'); if (result.wellPositioned) { + print('object 2'); onFaceDetected?.call(result.face); + print('object 3'); if (autoCapture) { + print('object 4'); onTakePictureButtonPressed(); } } @@ -213,17 +217,6 @@ class FaceCameraController extends ValueNotifier { if (file != null) { onCapture.call(File(file.path)); } - - /// Resume image stream after 2 seconds of capture - Future.delayed(const Duration(seconds: 2)).whenComplete(() { - if (cameraController.value.isInitialized) { - try { - startImageStream(); - } catch (e) { - logError(e.toString()); - } - } - }); }); }); } catch (e) { diff --git a/lib/src/smart_face_camera.dart b/lib/src/smart_face_camera.dart index 2070f19..73170dd 100644 --- a/lib/src/smart_face_camera.dart +++ b/lib/src/smart_face_camera.dart @@ -93,7 +93,7 @@ class _SmartFaceCameraState extends State @override void dispose() { WidgetsBinding.instance.removeObserver(this); - widget.controller.dispose(); + widget.controller.stopImageStream(); super.dispose(); } From 148e9ae4c717d106f97cae28363b8d1d00c492d7 Mon Sep 17 00:00:00 2001 From: Conezi Date: Mon, 27 May 2024 15:44:45 +0100 Subject: [PATCH 4/9] Updated README.md --- README.md | 63 ++++++++++++------- .../controllers/face_camera_controller.dart | 4 -- 2 files changed, 42 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 4ccfa16..ee692b8 100644 --- a/README.md +++ b/README.md @@ -60,18 +60,32 @@ void main() async{ runApp(const MyApp()); } ``` -* Then render the component in your application setting the onCapture callback. + +* Create a new `FaceCameraController` controller, setting the onCapture callback. +```dart + late FaceCameraController controller; + + @override + void initState() { + controller = FaceCameraController( + autoCapture: true, + defaultCameraLens: CameraLens.front, + onCapture: (File? image) { + + }, + ); + super.initState(); +} +``` + +* Then render the component in your application using the required options. ```dart @override Widget build(BuildContext context) { return Scaffold( body: SmartFaceCamera( - autoCapture: true, - defaultCameraLens: CameraLens.front, + controller: controller, message: 'Center your face in the square', - onCapture: (File? image){ - - }, ) ); } @@ -82,6 +96,27 @@ void main() async{ Here is a list of properties available to customize your widget: +| Name | Type | Description | +|---------------------------|-----------------------|-------------------------------------------------------------------------------| +| controller | FaceCameraController | The controller for the [SmartFaceCamera] widget | +| showControls | bool | set false to hide all controls | +| showCaptureControl | bool | set false to hide capture control icon | +| showFlashControl | bool | set false to hide flash control control icon | +| showCameraLensControl | bool | set false to hide camera lens control icon | +| message | String | use this pass a message above the camera | +| messageStyle | TextStyle | style applied to the message widget | +| lensControlIcon | Widget | use this to render a custom widget for camera lens control | +| flashControlBuilder | FlashControlBuilder | use this to build custom widgets for flash control based on camera flash mode | +| messageBuilder | MessageBuilder | use this to build custom messages based on face position | +| indicatorShape | IndicatorShape | use this to change the shape of the face indicator | +| indicatorAssetImage | String | use this to pass an asset image when IndicatorShape is set to image | +| indicatorBuilder | IndicatorBuilder | use this to build custom widgets for the face indicator | +| captureControlBuilder | CaptureControlBuilder | use this to build custom widgets for capture control | +| autoDisableCaptureControl | bool | set true to disable capture control widget when no face is detected | + + +Here is a list of properties available to customize your controller: + | Name | Type | Description | |---------------------------|-------------------------|-------------------------------------------------------------------------------| | onCapture | Function(File?) | callback invoked when camera captures image | @@ -91,24 +126,10 @@ Here is a list of properties available to customize your widget: | defaultFlashMode | CameraFlashMode | use this to set initial flash mode | | enableAudio | bool | set false to disable capture sound | | autoCapture | bool | set true to capture image on face detected | -| showControls | bool | set false to hide all controls | -| showCaptureControl | bool | set false to hide capture control icon | -| showFlashControl | bool | set false to hide flash control control icon | -| showCameraLensControl | bool | set false to hide camera lens control icon | -| message | String | use this pass a message above the camera | -| messageStyle | TextStyle | style applied to the message widget | | orientation | CameraOrientation | use this to lock camera orientation | -| captureControlIcon | Widget | use this to render a custom widget for capture control | -| lensControlIcon | Widget | use this to render a custom widget for camera lens control | -| flashControlBuilder | FlashControlBuilder | use this to build custom widgets for flash control based on camera flash mode | -| messageBuilder | MessageBuilder | use this to build custom messages based on face position | -| indicatorShape | IndicatorShape | use this to change the shape of the face indicator | -| indicatorAssetImage | String | use this to pass an asset image when IndicatorShape is set to image | -| indicatorBuilder | IndicatorBuilder | use this to build custom widgets for the face indicator | -| captureControlBuilder | CaptureControlBuilder | use this to build custom widgets for capture control | -| autoDisableCaptureControl | bool | set true to disable capture control widget when no face is detected | | performanceMode | FaceDetectorMode | Use this to set your preferred performance mode | + ### Contributions --- diff --git a/lib/src/controllers/face_camera_controller.dart b/lib/src/controllers/face_camera_controller.dart index f00b11b..2a04cbd 100644 --- a/lib/src/controllers/face_camera_controller.dart +++ b/lib/src/controllers/face_camera_controller.dart @@ -184,13 +184,9 @@ class FaceCameraController extends ValueNotifier { if (result != null) { try { - print('object 1'); if (result.wellPositioned) { - print('object 2'); onFaceDetected?.call(result.face); - print('object 3'); if (autoCapture) { - print('object 4'); onTakePictureButtonPressed(); } } From dc5a80af5f38e69e66e59fcefba5c8753aaa22a1 Mon Sep 17 00:00:00 2001 From: Conezi Date: Mon, 27 May 2024 15:58:41 +0100 Subject: [PATCH 5/9] Updated CHANGELOG.md --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9fed612..a1369a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +## 0.1.1 + +- Added `FaceCameraController` allowing developers to control the `SmartFaceCamera` widget. +- Updated dependencies. +- Modified `README.md`. + +**BREAKING CHANGES:** +- `captureControlIcon` has been replaced with `captureControlBuilder`. +- `imageResolution, defaultCameraLens, defaultFlashMode, enableAudio, autoCapture, orientation, onCapture, onFaceDetected, performanceMode` has been moved to `FaceCameraController`. + ## 0.1.0 - Improved codebase documentations. From a385f7338f7d89dd89a61a3fcd62c5943d896f56 Mon Sep 17 00:00:00 2001 From: Conezi Date: Tue, 28 May 2024 14:09:50 +0100 Subject: [PATCH 6/9] Fix for Android devices --- example/pubspec.lock | 28 +++++++++++++-------------- lib/src/handlers/face_identifier.dart | 18 ++++++++--------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/example/pubspec.lock b/example/pubspec.lock index 85384b0..f96b804 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -171,26 +171,26 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" + sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" url: "https://pub.dev" source: hosted - version: "10.0.0" + version: "10.0.4" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 + sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.3" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.1" lints: dependency: transitive description: @@ -219,10 +219,10 @@ packages: dependency: transitive description: name: meta - sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 + sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.12.0" path: dependency: transitive description: @@ -296,10 +296,10 @@ packages: dependency: transitive description: name: test_api - sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" url: "https://pub.dev" source: hosted - version: "0.6.1" + version: "0.7.0" vector_math: dependency: transitive description: @@ -312,10 +312,10 @@ packages: dependency: transitive description: name: vm_service - sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 + sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" url: "https://pub.dev" source: hosted - version: "13.0.0" + version: "14.2.1" sdks: - dart: ">=3.2.3 <4.0.0" - flutter: ">=3.16.6" + dart: ">=3.3.0 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/lib/src/handlers/face_identifier.dart b/lib/src/handlers/face_identifier.dart index cee139d..0ffb478 100644 --- a/lib/src/handlers/face_identifier.dart +++ b/lib/src/handlers/face_identifier.dart @@ -61,24 +61,24 @@ class FaceIdentifier { final format = InputImageFormatValue.fromRawValue(image.format.raw); // validate format depending on platform // only supported formats: - // * nv21 for Android // * bgra8888 for iOS - if (format == null || - (Platform.isAndroid && format != InputImageFormat.nv21) || - (Platform.isIOS && format != InputImageFormat.bgra8888)) return null; + if (format == null || (Platform.isIOS && format != InputImageFormat.bgra8888)) return null; - // since format is constraint to nv21 or bgra8888, both only have one plane - if (image.planes.length != 1) return null; - final plane = image.planes.first; + if (image.planes.isEmpty) return null; // compose InputImage using bytes return InputImage.fromBytes( - bytes: plane.bytes, + bytes: Uint8List.fromList( + image.planes.fold( + [], + (List previousValue, element) => + previousValue..addAll(element.bytes)), + ), metadata: InputImageMetadata( size: Size(image.width.toDouble(), image.height.toDouble()), rotation: rotation, // used only in Android format: format, // used only in iOS - bytesPerRow: plane.bytesPerRow, // used only in iOS + bytesPerRow: image.planes.first.bytesPerRow, // used only in iOS ), ); } From 79dd55b8e9c930d5d96c4cb7b25bcaf9ec5359ec Mon Sep 17 00:00:00 2001 From: Conezi Date: Tue, 28 May 2024 15:38:14 +0100 Subject: [PATCH 7/9] Formatted codebase --- example/lib/main.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index 4ee4e3a..4cf5eb3 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -100,5 +100,4 @@ class _MyAppState extends State { controller.dispose(); super.dispose(); } - } From abfb77b4f8d06e51a5d1c90972e9bdb5fd735693 Mon Sep 17 00:00:00 2001 From: Conezi Date: Tue, 28 May 2024 15:47:33 +0100 Subject: [PATCH 8/9] Updated github workflow --- .github/workflows/Dev_ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/Dev_ci.yml b/.github/workflows/Dev_ci.yml index cfcb46a..25d2d19 100644 --- a/.github/workflows/Dev_ci.yml +++ b/.github/workflows/Dev_ci.yml @@ -21,7 +21,7 @@ jobs: - uses: subosito/flutter-action@v2 with: channel: 'stable' # 'dev', 'alpha', default to: 'stable' - flutter-version: '3.16.5' # you can also specify exact version of flutter + flutter-version: '3.22.1' # you can also specify exact version of flutter - uses: dart-lang/setup-dart@9a04e6d73cca37bd455e0608d7e5092f881fd603 From d14ffe562750a961c5cc5592d015c35522b898b6 Mon Sep 17 00:00:00 2001 From: Conezi Date: Tue, 28 May 2024 15:51:03 +0100 Subject: [PATCH 9/9] Formatted codebase --- lib/src/controllers/face_camera_controller.dart | 7 +++---- lib/src/handlers/face_identifier.dart | 7 ++++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/src/controllers/face_camera_controller.dart b/lib/src/controllers/face_camera_controller.dart index 2a04cbd..6c2d538 100644 --- a/lib/src/controllers/face_camera_controller.dart +++ b/lib/src/controllers/face_camera_controller.dart @@ -92,16 +92,15 @@ class FaceCameraController extends ValueNotifier { ? ImageFormatGroup.nv21 : ImageFormatGroup.bgra8888); - await cameraController.initialize().whenComplete((){ + await cameraController.initialize().whenComplete(() { value = value.copyWith( isInitialized: true, cameraController: cameraController); }); await changeFlashMode(value.availableFlashMode.indexOf(defaultFlashMode)); - await cameraController - .lockCaptureOrientation( - EnumHandler.cameraOrientationToDeviceOrientation(orientation)); + await cameraController.lockCaptureOrientation( + EnumHandler.cameraOrientationToDeviceOrientation(orientation)); } startImageStream(); diff --git a/lib/src/handlers/face_identifier.dart b/lib/src/handlers/face_identifier.dart index 0ffb478..f56a7bd 100644 --- a/lib/src/handlers/face_identifier.dart +++ b/lib/src/handlers/face_identifier.dart @@ -62,7 +62,8 @@ class FaceIdentifier { // validate format depending on platform // only supported formats: // * bgra8888 for iOS - if (format == null || (Platform.isIOS && format != InputImageFormat.bgra8888)) return null; + if (format == null || + (Platform.isIOS && format != InputImageFormat.bgra8888)) return null; if (image.planes.isEmpty) return null; @@ -71,8 +72,8 @@ class FaceIdentifier { bytes: Uint8List.fromList( image.planes.fold( [], - (List previousValue, element) => - previousValue..addAll(element.bytes)), + (List previousValue, element) => + previousValue..addAll(element.bytes)), ), metadata: InputImageMetadata( size: Size(image.width.toDouble(), image.height.toDouble()),