├── android ├── src │ ├── main │ │ ├── AndroidManifest.xml │ │ └── kotlin │ │ │ └── com │ │ │ └── regula │ │ │ └── plugin │ │ │ └── facesdk │ │ │ └── FlutterFaceApiPlugin.kt │ └── test │ │ └── kotlin │ │ └── com │ │ └── regula │ │ └── plugin │ │ └── facesdk │ │ ├── Shadows.kt │ │ ├── TestUtils.kt │ │ └── FlutterFaceApiPluginTest.kt ├── settings.gradle ├── .gitignore ├── gradle.properties └── build.gradle ├── example ├── test │ └── widget_test.dart ├── analysis_options.yaml ├── ios │ ├── Runner │ │ ├── Runner-Bridging-Header.h │ │ ├── Assets.xcassets │ │ │ ├── LaunchImage.imageset │ │ │ │ ├── LaunchImage.png │ │ │ │ ├── LaunchImage@2x.png │ │ │ │ ├── LaunchImage@3x.png │ │ │ │ ├── README.md │ │ │ │ └── Contents.json │ │ │ └── AppIcon.appiconset │ │ │ │ ├── Icon-App-20x20@1x.png │ │ │ │ ├── Icon-App-20x20@2x.png │ │ │ │ ├── Icon-App-20x20@3x.png │ │ │ │ ├── Icon-App-29x29@1x.png │ │ │ │ ├── Icon-App-29x29@2x.png │ │ │ │ ├── Icon-App-29x29@3x.png │ │ │ │ ├── Icon-App-40x40@1x.png │ │ │ │ ├── Icon-App-40x40@2x.png │ │ │ │ ├── Icon-App-40x40@3x.png │ │ │ │ ├── Icon-App-60x60@2x.png │ │ │ │ ├── Icon-App-60x60@3x.png │ │ │ │ ├── Icon-App-76x76@1x.png │ │ │ │ ├── Icon-App-76x76@2x.png │ │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ │ ├── Icon-App-83.5x83.5@2x.png │ │ │ │ └── Contents.json │ │ ├── Runner.entitlements │ │ ├── AppDelegate.swift │ │ ├── Info.plist │ │ └── Base.lproj │ │ │ ├── Main.storyboard │ │ │ └── LaunchScreen.storyboard │ ├── Flutter │ │ ├── Debug.xcconfig │ │ ├── Release.xcconfig │ │ └── AppFrameworkInfo.plist │ ├── Runner.xcodeproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ ├── WorkspaceSettings.xcsettings │ │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── WorkspaceSettings.xcsettings │ │ │ └── IDEWorkspaceChecks.plist │ ├── .gitignore │ ├── Podfile │ ├── Podfile.lock │ └── Tests │ │ └── Utils.swift ├── assets │ └── images │ │ └── portrait.png ├── android │ ├── gradle.properties │ ├── app │ │ ├── src │ │ │ ├── main │ │ │ │ ├── res │ │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── drawable │ │ │ │ │ │ └── launch_background.xml │ │ │ │ │ ├── drawable-v21 │ │ │ │ │ │ └── launch_background.xml │ │ │ │ │ ├── values │ │ │ │ │ │ └── styles.xml │ │ │ │ │ └── values-night │ │ │ │ │ │ └── styles.xml │ │ │ │ ├── kotlin │ │ │ │ │ └── com │ │ │ │ │ │ └── regula │ │ │ │ │ │ └── example │ │ │ │ │ │ └── face │ │ │ │ │ │ └── flutter │ │ │ │ │ │ └── MainActivity.kt │ │ │ │ └── AndroidManifest.xml │ │ │ ├── debug │ │ │ │ └── AndroidManifest.xml │ │ │ └── profile │ │ │ │ └── AndroidManifest.xml │ │ └── build.gradle │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ ├── .gitignore │ ├── build.gradle │ └── settings.gradle ├── README.md ├── pubspec.yaml ├── .gitignore └── .metadata ├── .gitignore ├── analysis_options.yaml ├── .github └── ISSUE_TEMPLATE │ └── config.yml ├── AUTHORS ├── lib └── src │ ├── internal │ ├── bridge.dart │ ├── utils.dart │ └── event_channels.dart │ ├── customization │ ├── camera_position.dart │ ├── screen_orientation.dart │ ├── font.dart │ ├── customization.dart │ ├── customization_fonts.dart │ └── customization_images.dart │ ├── image_params │ ├── size.dart │ ├── point.dart │ ├── rect.dart │ ├── output_image_params.dart │ └── output_image_crop.dart │ ├── detect_faces │ ├── detect_faces_scenario.dart │ ├── detect_faces_attribute.dart │ ├── detect_faces_attribute_result.dart │ ├── detect_faces_backend_exception.dart │ ├── detect_faces_response.dart │ ├── detect_faces_exception.dart │ ├── detect_face_result.dart │ ├── detect_faces_config.dart │ └── detect_faces_request.dart │ ├── match_faces │ ├── match_faces_backend_exception.dart │ ├── compared_faces_split.dart │ ├── match_faces_config.dart │ ├── compared_face.dart │ ├── match_faces_response.dart │ ├── match_faces_detection.dart │ ├── match_faces_exception.dart │ ├── match_faces_image.dart │ ├── match_faces_request.dart │ ├── compared_faces_pair.dart │ └── match_faces_detection_face.dart │ ├── face_capture │ ├── face_capture_response.dart │ ├── face_capture_exception.dart │ ├── face_capture_image.dart │ └── face_capture_config.dart │ ├── person_database │ ├── edit_group_persons_request.dart │ ├── pageable_item_list.dart │ ├── image_upload.dart │ ├── person_group.dart │ ├── search_person_detection.dart │ ├── person_image.dart │ ├── person.dart │ ├── search_person_image.dart │ ├── search_person.dart │ ├── search_person_request.dart │ └── person_database.dart │ ├── image_quality │ ├── image_quality_range.dart │ ├── image_quality_characteristic.dart │ ├── image_quality_characteristic_name.dart │ └── image_quality_result.dart │ ├── init │ ├── face_sdk_version.dart │ ├── license_exception.dart │ ├── init_exception.dart │ └── init_config.dart │ └── liveness │ ├── liveness_notification.dart │ ├── liveness_exception.dart │ ├── liveness_backend_exception.dart │ ├── liveness_response.dart │ └── liveness_config.dart ├── ios ├── Classes │ ├── FlutterFaceApiPlugin.h │ ├── RFSWMain.h │ ├── RFSWConfig.h │ └── FlutterFaceApiPlugin.m └── flutter_face_api.podspec ├── README.md ├── pubspec.yaml ├── CHANGELOG.md ├── LICENSE ├── .gitlab-ci.yml ├── test ├── nullable.dart ├── utils.dart └── face_api_test.dart ├── .gitlab └── report.yaml └── pubspec.lock /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | void main() {} 2 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'flutter_face_api' 2 | -------------------------------------------------------------------------------- /example/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:lints/core.yaml 2 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | .DS_Store 5 | /build 6 | /captures 7 | /.idea -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | test/json 4 | 5 | .dart_tool/ 6 | .packages 7 | .pub/ 8 | build/ 9 | 10 | doc -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:lints/core.yaml 2 | analyzer: 3 | errors: 4 | deprecated_member_use_from_same_package: ignore -------------------------------------------------------------------------------- /example/assets/images/portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regulaforensics/flutter_face_api/HEAD/example/assets/images/portrait.png -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableR8=true 3 | android.useAndroidX=true 4 | android.enableJetifier=true 5 | -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regulaforensics/flutter_face_api/HEAD/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regulaforensics/flutter_face_api/HEAD/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regulaforensics/flutter_face_api/HEAD/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regulaforensics/flutter_face_api/HEAD/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regulaforensics/flutter_face_api/HEAD/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regulaforensics/flutter_face_api/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regulaforensics/flutter_face_api/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regulaforensics/flutter_face_api/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regulaforensics/flutter_face_api/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regulaforensics/flutter_face_api/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regulaforensics/flutter_face_api/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regulaforensics/flutter_face_api/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regulaforensics/flutter_face_api/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regulaforensics/flutter_face_api/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regulaforensics/flutter_face_api/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regulaforensics/flutter_face_api/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regulaforensics/flutter_face_api/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regulaforensics/flutter_face_api/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regulaforensics/flutter_face_api/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regulaforensics/flutter_face_api/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regulaforensics/flutter_face_api/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regulaforensics/flutter_face_api/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regulaforensics/flutter_face_api/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/com/regula/example/face/flutter/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.regula.example.face.flutter 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() 6 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Submit a request 4 | url: https://support.regulaforensics.com/hc/requests/new?utm_source=github 5 | about: Submit any requests to Regula Support Team 6 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # Below is a list of people and organizations that have contributed 2 | # to the Flutter project. Names should be added to the list like so: 3 | # 4 | # Name/Organization 5 | 6 | Regula 7 | Pavel Masiuk 8 | 9 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip 6 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/to/reference-keystore 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | allprojects { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | } 6 | } 7 | 8 | rootProject.buildDir = "../build" 9 | subprojects { 10 | project.buildDir = "${rootProject.buildDir}/${project.name}" 11 | } 12 | subprojects { 13 | project.evaluationDependsOn(":app") 14 | } 15 | 16 | tasks.register("clean", Delete) { 17 | delete rootProject.buildDir 18 | } 19 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | 4 | @main 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lib/src/internal/bridge.dart: -------------------------------------------------------------------------------- 1 | part of "../../flutter_face_api.dart"; 2 | 3 | const _methodChannelID = 'flutter_face_api/method'; 4 | const MethodChannel _bridge = const MethodChannel(_methodChannelID); 5 | 6 | String _eventPrefix = 'flutter_face_api/event/'; 7 | List _eventChannels = []; 8 | 9 | void _eventChannel(String id, listen(msg)) { 10 | if (_eventChannels.contains(id)) return; 11 | _eventChannels.add(id); 12 | EventChannel(_eventPrefix + id).receiveBroadcastStream().listen(listen); 13 | } 14 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/src/customization/camera_position.dart: -------------------------------------------------------------------------------- 1 | part of "../../flutter_face_api.dart"; 2 | 3 | /// Physical position of camera's hardware on the system. 4 | enum CameraPosition { 5 | /// The camera position corresponds to the front camera. 6 | FRONT(0), 7 | 8 | /// The camera position corresponds to the back camera. 9 | BACK(1); 10 | 11 | const CameraPosition(this.value); 12 | final int value; 13 | 14 | static CameraPosition? getByValue(int? i) { 15 | if (i == null) return null; 16 | return CameraPosition.values.firstWhere((x) => x.value == i); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /android/src/test/kotlin/com/regula/plugin/facesdk/Shadows.kt: -------------------------------------------------------------------------------- 1 | package com.regula.plugin.facesdk 2 | 3 | import android.graphics.Bitmap 4 | import android.graphics.drawable.BitmapDrawable 5 | import android.graphics.drawable.Drawable 6 | import org.robolectric.annotation.Implements 7 | 8 | @Implements(Bitmap::class) 9 | class MyShadowBitmap { 10 | var data: ByteArray? = null 11 | } 12 | 13 | @Implements(Drawable::class) 14 | open class MyShadowDrawable { 15 | var data: ByteArray? = null 16 | } 17 | 18 | @Implements(BitmapDrawable::class) 19 | class MyShadowBitmapDrawable : MyShadowDrawable() -------------------------------------------------------------------------------- /ios/Classes/FlutterFaceApiPlugin.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "RFSWMain.h" 3 | 4 | @interface FlutterFaceApiPlugin : NSObject 5 | @end 6 | 7 | static NSMutableDictionary* eventSinks; 8 | 9 | @interface RFSWCameraSwitchStreamHandler : NSObject 10 | @end 11 | @interface RFSWLivenessNotificationStreamHandler : NSObject 12 | @end 13 | @interface RFSWVideoEncoderCompletionStreamHandler : NSObject 14 | @end 15 | @interface RFSWOnCustomButtonTappedStreamHandler : NSObject 16 | @end 17 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # How to build the demo application 2 | 3 | 1. Download or the clone current repository using the command `git clone https://github.com/regulaforensics/flutter_face_api.git.git`. 4 | 2. Execute `flutter pub get && (cd ios && pod install || pod update)` within this directory. 5 | 3. Run the app: `flutter run`. 6 | 7 | # How to use offline match 8 | 1. Place a license that supports offline match at `assets/regula.license`. 9 | 2. Change core with the following commands: 10 | ```bash 11 | flutter pub remove flutter_face_core_basic 12 | flutter pub add flutter_face_core_match 13 | ``` 14 | 3. Turn off the internet and run the app. 15 | -------------------------------------------------------------------------------- /lib/src/image_params/size.dart: -------------------------------------------------------------------------------- 1 | part of "../../flutter_face_api.dart"; 2 | 3 | class Size { 4 | int get width => _width; 5 | int _width; 6 | 7 | int get height => _height; 8 | int _height; 9 | 10 | Size(int width, int height) 11 | : _width = width, 12 | _height = height; 13 | 14 | @visibleForTesting 15 | static Size? fromJson(jsonObject) { 16 | if (jsonObject == null) return null; 17 | return new Size(jsonObject["width"], jsonObject["height"]); 18 | } 19 | 20 | @visibleForTesting 21 | Map toJson() => { 22 | "width": width, 23 | "height": height, 24 | }.clearNulls(); 25 | } 26 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_face_api_example 2 | description: Demonstrates how to use the flutter_face_api plugin. 3 | publish_to: none 4 | version: 1.0.0 5 | 6 | environment: 7 | sdk: '>=3.1.5 <4.0.0' 8 | flutter: '>=3.10.0' 9 | 10 | dependencies: 11 | path_provider: ^2.1.5 12 | image_picker: ^1.1.2 13 | flutter: 14 | sdk: flutter 15 | flutter_face_api: 16 | path: ../ 17 | flutter_face_core_basic: 7.2.235 18 | cupertino_icons: ^1.0.8 19 | 20 | dev_dependencies: 21 | flutter_test: 22 | sdk: flutter 23 | lints: ^5.0.0 24 | 25 | flutter: 26 | assets: 27 | - assets/ 28 | - assets/images/ 29 | uses-material-design: true 30 | -------------------------------------------------------------------------------- /lib/src/customization/screen_orientation.dart: -------------------------------------------------------------------------------- 1 | part of "../../flutter_face_api.dart"; 2 | 3 | enum ScreenOrientation { 4 | PORTRAIT(0), 5 | 6 | LANDSCAPE(1); 7 | 8 | const ScreenOrientation(this.value); 9 | final int value; 10 | 11 | static ScreenOrientation? getByValue(int? i) { 12 | if (i == null) return null; 13 | return ScreenOrientation.values.firstWhere((x) => x.value == i); 14 | } 15 | 16 | static List? fromIntList(List? input) { 17 | if (input == null) return null; 18 | List list = []; 19 | for (int item in input) { 20 | list.addSafe(getByValue(item)); 21 | } 22 | return list; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/src/detect_faces/detect_faces_scenario.dart: -------------------------------------------------------------------------------- 1 | part of "../../flutter_face_api.dart"; 2 | 3 | enum DetectFacesScenario { 4 | CROP_CENTRAL_FACE("CropCentralFace"), 5 | CROP_ALL_FACES("CropAllFaces"), 6 | THUMBNAIL("Thumbnail"), 7 | ATTRIBUTES_ALL("AttributesAll"), 8 | QUALITY_FULL("QualityFull"), 9 | QUALITY_ICAO("QualityICAO"), 10 | QUALITY_VISA_SCHENGEN("QualityVisaSchengen"), 11 | QUALITY_VISA_USA("QualityVisaUSA"); 12 | 13 | const DetectFacesScenario(this.value); 14 | final String value; 15 | 16 | static DetectFacesScenario? getByValue(String? i) { 17 | if (i == null) return null; 18 | return DetectFacesScenario.values.firstWhere((x) => x.value == i); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/src/image_params/point.dart: -------------------------------------------------------------------------------- 1 | part of "../../flutter_face_api.dart"; 2 | 3 | /// Point class represents a two number X, Y value. 4 | class Point { 5 | int get x => _x; 6 | late int _x; 7 | 8 | int get y => _y; 9 | late int _y; 10 | 11 | Point._privateConstructor(); 12 | 13 | @visibleForTesting 14 | static Point? fromJson(jsonObject) { 15 | if (jsonObject == null) return null; 16 | var result = Point._privateConstructor(); 17 | 18 | result._x = jsonObject["x"]; 19 | result._y = jsonObject["y"]; 20 | 21 | return result; 22 | } 23 | 24 | @visibleForTesting 25 | Map toJson() => { 26 | "x": x, 27 | "y": y, 28 | }.clearNulls(); 29 | } 30 | -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Regula Face SDK plugin for Flutter 2 | 3 | 4 | [![pub package](https://img.shields.io/pub/v/flutter_face_api.svg)](https://pub.dev/packages/flutter_face_api) 5 | 6 | Face SDK is a framework that is used for face matching, recognition and liveness detection. This plugin makes possible to use it with flutter. 7 | 8 | ## Documentation 9 | * [Documentation](https://docs.regulaforensics.com/develop/face-sdk/mobile/) 10 | * [API Reference](https://pub.dev/documentation/flutter_face_api) 11 | 12 | ## Support 13 | If you have any technical questions, feel free to [contact](mailto:support@regulaforensics.com) us or create issues [here](https://github.com/regulaforensics/flutter_face_api/issues). 14 | -------------------------------------------------------------------------------- /lib/src/detect_faces/detect_faces_attribute.dart: -------------------------------------------------------------------------------- 1 | part of "../../flutter_face_api.dart"; 2 | 3 | enum DetectFacesAttribute { 4 | AGE("Age"), 5 | EYE_RIGHT("EyeRight"), 6 | EYE_LEFT("EyeLeft"), 7 | EMOTION("Emotion"), 8 | SMILE("Smile"), 9 | GLASSES("Glasses"), 10 | HEAD_COVERING("HeadCovering"), 11 | FOREHEAD_COVERING("ForeheadCovering"), 12 | MOUTH("Mouth"), 13 | MEDICAL_MASK("MedicalMask"), 14 | OCCLUSION("Occlusion"), 15 | STRONG_MAKEUP("StrongMakeup"), 16 | HEADPHONES("Headphones"); 17 | 18 | const DetectFacesAttribute(this.value); 19 | final String value; 20 | 21 | static DetectFacesAttribute? getByValue(String? i) { 22 | if (i == null) return null; 23 | return DetectFacesAttribute.values.firstWhere((x) => x.value == i); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /ios/flutter_face_api.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'flutter_face_api' 3 | s.version = '7.2.540' 4 | s.summary = 'A new flutter plugin project.' 5 | s.description = <<-DESC 6 | A new flutter plugin project. 7 | DESC 8 | s.homepage = 'http://example.com' 9 | s.license = { :file => '../LICENSE' } 10 | s.author = { 'Your Company' => 'email@example.com' } 11 | s.source = { :path => '.' } 12 | s.source_files = 'Classes/**/*' 13 | s.public_header_files = 'Classes/**/*.h' 14 | s.dependency 'Flutter' 15 | s.platform = :ios, '13.0' 16 | s.dependency 'FaceSDK', '7.2.3306' 17 | s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS[sdk=iphonesimulator*]' => 'x86_64' } 18 | end 19 | -------------------------------------------------------------------------------- /lib/src/match_faces/match_faces_backend_exception.dart: -------------------------------------------------------------------------------- 1 | part of "../../flutter_face_api.dart"; 2 | 3 | class MatchFacesBackendException { 4 | int get code => _code; 5 | late int _code; 6 | 7 | String get message => _message; 8 | late String _message; 9 | 10 | MatchFacesBackendException._privateConstructor(); 11 | 12 | @visibleForTesting 13 | static MatchFacesBackendException? fromJson(jsonObject) { 14 | if (jsonObject == null) return null; 15 | var result = MatchFacesBackendException._privateConstructor(); 16 | 17 | result._code = jsonObject["code"]; 18 | result._message = jsonObject["message"] ?? ""; 19 | 20 | return result; 21 | } 22 | 23 | @visibleForTesting 24 | Map toJson() => { 25 | "code": code, 26 | "message": message, 27 | }.clearNulls(); 28 | } 29 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_face_api 2 | description: 3 | This is a flutter module for Regula Face SDK. 4 | It allows you to easily compaire faces using your phone's camera. Supports Android and iOS. 5 | repository: https://github.com/regulaforensics/flutter_face_api 6 | issue_tracker: https://github.com/regulaforensics/flutter_face_api/issues 7 | version: 7.2.540 8 | 9 | environment: 10 | sdk: '>=3.1.5 <4.0.0' 11 | flutter: '>=3.10.0' 12 | 13 | dependencies: 14 | flutter: 15 | sdk: flutter 16 | 17 | dev_dependencies: 18 | flutter_test: 19 | sdk: flutter 20 | lints: ^4.0.0 21 | meta: ^1.10.0 22 | 23 | flutter: 24 | plugin: 25 | platforms: 26 | android: 27 | package: com.regula.plugin.facesdk 28 | pluginClass: FlutterFaceApiPlugin 29 | ios: 30 | pluginClass: FlutterFaceApiPlugin 31 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | def flutterSdkPath = { 3 | def properties = new Properties() 4 | file("local.properties").withInputStream { properties.load(it) } 5 | def flutterSdkPath = properties.getProperty("flutter.sdk") 6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 7 | return flutterSdkPath 8 | }() 9 | 10 | includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") 11 | 12 | repositories { 13 | google() 14 | mavenCentral() 15 | gradlePluginPortal() 16 | } 17 | } 18 | 19 | plugins { 20 | id "dev.flutter.flutter-plugin-loader" version "1.0.0" 21 | id "com.android.application" version '8.8.0' apply false 22 | id "org.jetbrains.kotlin.android" version "1.9.25" apply false 23 | } 24 | 25 | include ":app" 26 | -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 13.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /lib/src/face_capture/face_capture_response.dart: -------------------------------------------------------------------------------- 1 | part of "../../flutter_face_api.dart"; 2 | 3 | class FaceCaptureResponse { 4 | FaceCaptureImage? get image => _image; 5 | FaceCaptureImage? _image; 6 | 7 | FaceCaptureException? get error => _error; 8 | FaceCaptureException? _error; 9 | 10 | FaceCaptureResponse._privateConstructor(); 11 | 12 | @visibleForTesting 13 | static FaceCaptureResponse? fromJson(jsonObject) { 14 | if (jsonObject == null) return null; 15 | var result = new FaceCaptureResponse._privateConstructor(); 16 | 17 | result._image = FaceCaptureImage.fromJson(jsonObject["image"]); 18 | result._error = FaceCaptureException.fromJson(jsonObject["error"]); 19 | 20 | return result; 21 | } 22 | 23 | @visibleForTesting 24 | Map toJson() => { 25 | "image": image?.toJson(), 26 | "error": error?.toJson(), 27 | }.clearNulls(); 28 | } 29 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .build/ 9 | .buildlog/ 10 | .history 11 | .svn/ 12 | .swiftpm/ 13 | migrate_working_dir/ 14 | 15 | # IntelliJ related 16 | *.iml 17 | *.ipr 18 | *.iws 19 | .idea/ 20 | 21 | # The .vscode folder contains launch configuration and tasks you configure in 22 | # VS Code which you may wish to be included in version control, so this line 23 | # is commented out by default. 24 | #.vscode/ 25 | 26 | # Flutter/Dart/Pub related 27 | **/doc/api/ 28 | **/ios/Flutter/.last_build_id 29 | .dart_tool/ 30 | .flutter-plugins 31 | .flutter-plugins-dependencies 32 | .pub-cache/ 33 | .pub/ 34 | /build/ 35 | 36 | # Symbolication related 37 | app.*.symbols 38 | 39 | # Obfuscation related 40 | app.*.map.json 41 | 42 | # Android Studio will place build artifacts here 43 | /android/app/debug 44 | /android/app/profile 45 | /android/app/release 46 | -------------------------------------------------------------------------------- /lib/src/person_database/edit_group_persons_request.dart: -------------------------------------------------------------------------------- 1 | part of "../../flutter_face_api.dart"; 2 | 3 | class EditGroupPersonsRequest { 4 | List? _personIdsToAdd; 5 | List? _personIdsToRemove; 6 | 7 | EditGroupPersonsRequest( 8 | List? personIdsToAdd, 9 | List? personIdsToRemove, 10 | ) : _personIdsToAdd = personIdsToAdd, 11 | _personIdsToRemove = personIdsToRemove; 12 | 13 | @visibleForTesting 14 | static EditGroupPersonsRequest? fromJson(jsonObject) { 15 | if (jsonObject == null) return null; 16 | return EditGroupPersonsRequest( 17 | _stringListFrom(jsonObject["personIdsToAdd"]), 18 | _stringListFrom(jsonObject["personIdsToRemove"]), 19 | ); 20 | } 21 | 22 | @visibleForTesting 23 | Map toJson() => { 24 | "personIdsToAdd": _personIdsToAdd, 25 | "personIdsToRemove": _personIdsToRemove, 26 | }.clearNulls(); 27 | } 28 | -------------------------------------------------------------------------------- /lib/src/image_params/rect.dart: -------------------------------------------------------------------------------- 1 | part of "../../flutter_face_api.dart"; 2 | 3 | class Rect { 4 | int get left => _left; 5 | late int _left; 6 | 7 | int get top => _top; 8 | late int _top; 9 | 10 | int get right => _right; 11 | late int _right; 12 | 13 | int get bottom => _bottom; 14 | late int _bottom; 15 | 16 | Rect._privateConstructor(); 17 | 18 | @visibleForTesting 19 | static Rect? fromJson(jsonObject) { 20 | if (jsonObject == null) return null; 21 | var result = Rect._privateConstructor(); 22 | 23 | result._left = jsonObject["left"]; 24 | result._top = jsonObject["top"]; 25 | result._right = jsonObject["right"]; 26 | result._bottom = jsonObject["bottom"]; 27 | 28 | return result; 29 | } 30 | 31 | @visibleForTesting 32 | Map toJson() => { 33 | "left": left, 34 | "top": top, 35 | "right": right, 36 | "bottom": bottom, 37 | }.clearNulls(); 38 | } 39 | -------------------------------------------------------------------------------- /lib/src/image_quality/image_quality_range.dart: -------------------------------------------------------------------------------- 1 | part of "../../flutter_face_api.dart"; 2 | 3 | /// Base range value for Image Quality parameters. 4 | class ImageQualityRange { 5 | /// Minimum range value. 6 | double get min => _min; 7 | double _min; 8 | 9 | /// Maximum range value. 10 | double get max => _max; 11 | double _max; 12 | 13 | ImageQualityRange(double min, double max) 14 | : _min = min, 15 | _max = max; 16 | 17 | ImageQualityRange.withValue(double value) 18 | : _min = value, 19 | _max = value; 20 | 21 | @visibleForTesting 22 | static ImageQualityRange? fromJson(jsonObject) { 23 | if (jsonObject == null) return null; 24 | 25 | return ImageQualityRange( 26 | _toDouble(jsonObject["min"])!, 27 | _toDouble(jsonObject["max"])!, 28 | ); 29 | } 30 | 31 | @visibleForTesting 32 | Map toJson() => { 33 | "min": min, 34 | "max": max, 35 | }.clearNulls(); 36 | } 37 | -------------------------------------------------------------------------------- /lib/src/init/face_sdk_version.dart: -------------------------------------------------------------------------------- 1 | part of "../../flutter_face_api.dart"; 2 | 3 | class FaceSDKVersion { 4 | /// Version of the API module. 5 | String? get api => _api; 6 | String? _api; 7 | 8 | /// Version of the CORE module. 9 | String? get core => _core; 10 | String? _core; 11 | 12 | /// CORE module variant. 13 | String? get coreMode => _coreMode; 14 | String? _coreMode; 15 | 16 | FaceSDKVersion._privateConstructor(); 17 | 18 | @visibleForTesting 19 | static FaceSDKVersion? fromJson(jsonObject) { 20 | if (jsonObject == null) return null; 21 | var result = FaceSDKVersion._privateConstructor(); 22 | 23 | result._api = jsonObject["api"]; 24 | result._core = jsonObject["core"]; 25 | result._coreMode = jsonObject["coreMode"]; 26 | 27 | return result; 28 | } 29 | 30 | @visibleForTesting 31 | Map toJson() => { 32 | "api": api, 33 | "core": core, 34 | "coreMode": coreMode, 35 | }.clearNulls(); 36 | } 37 | -------------------------------------------------------------------------------- /lib/src/person_database/pageable_item_list.dart: -------------------------------------------------------------------------------- 1 | part of "../../flutter_face_api.dart"; 2 | 3 | class PageableItemList { 4 | List? get items => _items; 5 | List? _items; 6 | 7 | int get page => _page; 8 | late int _page; 9 | 10 | int get totalPages => _totalPages; 11 | late int _totalPages; 12 | 13 | PageableItemList._privateConstructor(); 14 | 15 | @visibleForTesting 16 | static PageableItemList? fromJson( 17 | jsonObject, 18 | T? Function(dynamic) fromJSON, 19 | ) { 20 | if (jsonObject == null) return null; 21 | var result = PageableItemList._privateConstructor(); 22 | 23 | if (jsonObject["items"] != null) { 24 | result._items = []; 25 | for (var item in jsonObject["items"]) { 26 | var temp = fromJSON(item); 27 | if (temp != null) result._items!.add(temp); 28 | } 29 | } 30 | result._page = jsonObject["page"]; 31 | result._totalPages = jsonObject["totalPages"]; 32 | 33 | return result; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "com.android.application" 3 | id "kotlin-android" 4 | id "dev.flutter.flutter-gradle-plugin" 5 | } 6 | 7 | android { 8 | namespace = "com.regula.example.face.flutter" 9 | compileSdk = flutter.compileSdkVersion 10 | ndkVersion = "27.0.12077973" 11 | 12 | compileOptions { 13 | sourceCompatibility = JavaVersion.VERSION_21 14 | targetCompatibility = JavaVersion.VERSION_21 15 | } 16 | 17 | defaultConfig { 18 | applicationId = "com.regula.example.face.flutter" 19 | minSdk = flutter.minSdkVersion 20 | targetSdk = flutter.targetSdkVersion 21 | versionCode = flutter.versionCode 22 | versionName = flutter.versionName 23 | } 24 | 25 | buildTypes { 26 | release { 27 | signingConfig = signingConfigs.debug 28 | } 29 | } 30 | aaptOptions { 31 | noCompress "Regula/faceSdkResource.dat" 32 | } 33 | } 34 | 35 | flutter { 36 | source = "../.." 37 | } 38 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 6.2.0 2 | 3 | * BREAKING CHANGE: whole FaceSDK plugin rewritten from scratch with focus on user experience and convenience. Migration instructions can be found [here](https://docs.regulaforensics.com/develop/face-sdk/migration-guides/v6.1-to-v6.2/flutter/). 4 | 5 | * [Face SDK 6.2 Release Notes](https://docs.regulaforensics.com/develop/face-sdk/release-notes/6-2/) 6 | 7 | # 6.1.0 8 | 9 | * [Face SDK 6.1 Release Notes](https://docs.regulaforensics.com/develop/face-sdk/release-notes/6-1/) 10 | 11 | # 5.2.0 12 | 13 | * [Face SDK 5.2 Release Notes](https://docs.regulaforensics.com/develop/face-sdk/release-notes/release-notes-5-2/) 14 | 15 | # 5.1.0 16 | 17 | * [Face SDK 5.1 Release Notes](https://docs.regulaforensics.com/develop/face-sdk/release-notes/release-notes-5-1/) 18 | 19 | # 3.2.0 20 | 21 | * [Face SDK 3.2 Release Notes](https://docs.regulaforensics.com/develop/face-sdk/release-notes/release-notes-3.2/) 22 | 23 | # 3.1.0 24 | 25 | * [Face SDK 3.1 Release Notes](https://docs.regulaforensics.com/develop/face-sdk/release-notes/release-notes-3.1/) 26 | 27 | # 3.0.0 28 | 29 | * Initial Release -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Regula Forensics 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /ios/Classes/RFSWMain.h: -------------------------------------------------------------------------------- 1 | #import "RFSWJSONConstructor.h" 2 | @import FaceSDK; 3 | 4 | typedef void (^RFSWCallback)(id _Nullable response); 5 | typedef void (^RFSWEventSender)(NSString* _Nonnull event, id _Nullable data); 6 | extern UIViewController*_Nonnull(^ _Nonnull RFSWRootViewController)(void); 7 | 8 | @interface RFSWMain: NSObject 13 | 14 | +(void)methodCall:(NSString* _Nonnull)method 15 | :(NSArray* _Nonnull)args 16 | :(RFSWCallback _Nonnull)callback 17 | :(RFSWEventSender _Nonnull)eventSender; 18 | 19 | @end 20 | 21 | static NSString* _Nonnull cameraSwitchEvent = @"cameraSwitchEvent"; 22 | static NSString* _Nonnull livenessNotificationEvent = @"livenessNotificationEvent"; 23 | static NSString* _Nonnull videoEncoderCompletionEvent = @"video_encoder_completion"; 24 | static NSString* _Nonnull onCustomButtonTappedEvent = @"onCustomButtonTappedEvent"; 25 | -------------------------------------------------------------------------------- /lib/src/match_faces/compared_faces_split.dart: -------------------------------------------------------------------------------- 1 | part of "../../flutter_face_api.dart"; 2 | 3 | class ComparedFacesSplit { 4 | List get matchedFaces => _matchedFaces; 5 | List _matchedFaces = []; 6 | 7 | List get unmatchedFaces => _unmatchedFaces; 8 | List _unmatchedFaces = []; 9 | 10 | ComparedFacesSplit._privateConstructor(); 11 | 12 | @visibleForTesting 13 | static ComparedFacesSplit? fromJson(jsonObject) { 14 | if (jsonObject == null) return null; 15 | var result = ComparedFacesSplit._privateConstructor(); 16 | 17 | for (var item in jsonObject["matchedFaces"]) { 18 | result._matchedFaces.add(ComparedFacesPair.fromJson(item)!); 19 | } 20 | for (var item in jsonObject["unmatchedFaces"]) { 21 | result._unmatchedFaces.add(ComparedFacesPair.fromJson(item)!); 22 | } 23 | 24 | return result; 25 | } 26 | 27 | @visibleForTesting 28 | Map toJson() => { 29 | "matchedFaces": matchedFaces.map((e) => e.toJson()).toList(), 30 | "unmatchedFaces": unmatchedFaces.map((e) => e.toJson()).toList(), 31 | }.clearNulls(); 32 | } 33 | -------------------------------------------------------------------------------- /lib/src/person_database/image_upload.dart: -------------------------------------------------------------------------------- 1 | part of "../../flutter_face_api.dart"; 2 | 3 | /// An object that represents uploaded image with its settings. 4 | class ImageUpload { 5 | Uint8List? _imageData; 6 | String? _imageUrl; 7 | 8 | ImageUpload._privateConstructor(); 9 | 10 | /// Creates an object of [ImageUpload] using an image. 11 | /// 12 | /// `imageData` - Image base64. 13 | ImageUpload.withImageData(Uint8List imageData) : _imageData = imageData; 14 | 15 | /// Creates an object of [ImageUpload] using an image url. 16 | /// 17 | /// `imageUrl` - Image url. 18 | ImageUpload.withImageUrl(String imageUrl) : _imageUrl = imageUrl; 19 | 20 | @visibleForTesting 21 | static ImageUpload? fromJson(jsonObject) { 22 | if (jsonObject == null) return null; 23 | var result = ImageUpload._privateConstructor(); 24 | 25 | result._imageData = _bytesFromBase64(jsonObject["imageData"]); 26 | result._imageUrl = jsonObject["imageUrl"]; 27 | 28 | return result; 29 | } 30 | 31 | @visibleForTesting 32 | Map toJson() => { 33 | "imageData": _bytesToBase64(_imageData), 34 | "imageUrl": _imageUrl, 35 | }.clearNulls(); 36 | } 37 | -------------------------------------------------------------------------------- /lib/src/person_database/person_group.dart: -------------------------------------------------------------------------------- 1 | part of "../../flutter_face_api.dart"; 2 | 3 | /// Person Database object that represents Group of [Person]. 4 | class PersonGroup { 5 | /// PersonGroup name. 6 | /// Updatable field. 7 | late String name; 8 | 9 | String get id => _id; 10 | late String _id; 11 | 12 | /// A free-form object containing Group extended attributes. 13 | /// Updatable field. 14 | dynamic metadata; 15 | 16 | DateTime get createdAt => _createdAt; 17 | late DateTime _createdAt; 18 | 19 | PersonGroup._privateConstructor(); 20 | 21 | @visibleForTesting 22 | static PersonGroup? fromJson(jsonObject) { 23 | if (jsonObject == null) return null; 24 | var result = PersonGroup._privateConstructor(); 25 | 26 | result.name = jsonObject["name"]; 27 | result._id = jsonObject["id"]; 28 | result.metadata = jsonObject["metadata"]; 29 | result._createdAt = DateTime.parse(jsonObject["createdAt"]); 30 | 31 | return result; 32 | } 33 | 34 | @visibleForTesting 35 | Map toJson() => { 36 | "name": name, 37 | "id": id, 38 | "metadata": metadata, 39 | "createdAt": createdAt.toString(), 40 | }.clearNulls(); 41 | } 42 | -------------------------------------------------------------------------------- /lib/src/detect_faces/detect_faces_attribute_result.dart: -------------------------------------------------------------------------------- 1 | part of "../../flutter_face_api.dart"; 2 | 3 | class DetectFacesAttributeResult { 4 | DetectFacesAttribute get attribute => _attribute; 5 | late DetectFacesAttribute _attribute; 6 | 7 | double? get confidence => _confidence; 8 | double? _confidence; 9 | 10 | String? get value => _value; 11 | String? _value; 12 | 13 | ImageQualityRange? get range => _range; 14 | ImageQualityRange? _range; 15 | 16 | DetectFacesAttributeResult._privateConstructor(); 17 | 18 | @visibleForTesting 19 | static DetectFacesAttributeResult? fromJson(jsonObject) { 20 | if (jsonObject == null) return null; 21 | var result = DetectFacesAttributeResult._privateConstructor(); 22 | 23 | result._attribute = 24 | DetectFacesAttribute.getByValue(jsonObject["attribute"])!; 25 | result._confidence = _toDouble(jsonObject["confidence"]); 26 | result._value = jsonObject["value"]; 27 | result._range = ImageQualityRange.fromJson(jsonObject["range"]); 28 | 29 | return result; 30 | } 31 | 32 | @visibleForTesting 33 | Map toJson() => { 34 | "attribute": attribute.value, 35 | "confidence": confidence, 36 | "value": value, 37 | "range": range?.toJson(), 38 | }.clearNulls(); 39 | } 40 | -------------------------------------------------------------------------------- /lib/src/person_database/search_person_detection.dart: -------------------------------------------------------------------------------- 1 | part of "../../flutter_face_api.dart"; 2 | 3 | class SearchPersonDetection { 4 | List get landmarks => _landmarks; 5 | List _landmarks = []; 6 | 7 | Rect get rect => _rect; 8 | late Rect _rect; 9 | 10 | Uint8List? get crop => _crop; 11 | Uint8List? _crop; 12 | 13 | double? get rotationAngle => _rotationAngle; 14 | double? _rotationAngle; 15 | 16 | SearchPersonDetection._privateConstructor(); 17 | 18 | @visibleForTesting 19 | static SearchPersonDetection? fromJson(jsonObject) { 20 | if (jsonObject == null) return null; 21 | var result = SearchPersonDetection._privateConstructor(); 22 | 23 | for (var item in jsonObject["landmarks"]) { 24 | result._landmarks.add(Point.fromJson(item)!); 25 | } 26 | result._rect = Rect.fromJson(jsonObject["rect"])!; 27 | result._crop = _bytesFromBase64(jsonObject["crop"]); 28 | result._rotationAngle = _toDouble(jsonObject["rotationAngle"]); 29 | 30 | return result; 31 | } 32 | 33 | @visibleForTesting 34 | Map toJson() => { 35 | "landmarks": landmarks.map((e) => e.toJson()), 36 | "rect": rect.toJson(), 37 | "crop": _bytesToBase64(crop), 38 | "rotationAngle": rotationAngle, 39 | }.clearNulls(); 40 | } 41 | -------------------------------------------------------------------------------- /lib/src/match_faces/match_faces_config.dart: -------------------------------------------------------------------------------- 1 | part of "../../flutter_face_api.dart"; 2 | 3 | class MatchFacesConfig { 4 | ProcessingMode processingMode; 5 | bool locationTrackingEnabled; 6 | 7 | MatchFacesConfig({ 8 | ProcessingMode processingMode = ProcessingMode.ONLINE, 9 | bool locationTrackingEnabled = true, 10 | }) : processingMode = processingMode, 11 | locationTrackingEnabled = locationTrackingEnabled; 12 | 13 | @visibleForTesting 14 | static MatchFacesConfig? fromJson(jsonObject) { 15 | if (jsonObject == null) return null; 16 | var result = MatchFacesConfig(); 17 | 18 | result.processingMode = 19 | ProcessingMode.getByValue(jsonObject["processingMode"])!; 20 | result.locationTrackingEnabled = jsonObject["locationTrackingEnabled"]; 21 | 22 | return result; 23 | } 24 | 25 | @visibleForTesting 26 | Map toJson() => { 27 | "processingMode": processingMode.value, 28 | "locationTrackingEnabled": locationTrackingEnabled, 29 | }.clearNulls(); 30 | } 31 | 32 | enum ProcessingMode { 33 | ONLINE(0), 34 | 35 | OFFLINE(1); 36 | 37 | const ProcessingMode(this.value); 38 | final int value; 39 | 40 | static ProcessingMode? getByValue(int? i) { 41 | if (i == null) return null; 42 | return ProcessingMode.values.firstWhere((x) => x.value == i); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /ios/Classes/RFSWConfig.h: -------------------------------------------------------------------------------- 1 | #ifndef RFSWConfig_h 2 | #define RFSWConfig_h 3 | 4 | #import 5 | #import "RFSWJSONConstructor.h" 6 | 7 | @interface RFSWConfig : NSObject 8 | 9 | +(RFSFaceCaptureConfiguration* _Nonnull)faceCaptureConfigFromJSON:(id _Nonnull)input; 10 | +(NSDictionary* _Nonnull)generateFaceCaptureConfig:(RFSFaceCaptureConfiguration* _Nonnull)input; 11 | 12 | +(RFSLivenessConfiguration* _Nonnull)livenessConfigFromJSON:(id _Nonnull)input; 13 | +(NSDictionary* _Nonnull)generateLivenessConfig:(RFSLivenessConfiguration* _Nonnull)input; 14 | 15 | +(RFSMatchFacesConfiguration* _Nonnull)matchFacesConfigFromJSON:(id _Nonnull)input; 16 | +(NSDictionary* _Nonnull)generateMatchFacesConfig:(RFSMatchFacesConfiguration* _Nonnull)input; 17 | 18 | 19 | +(void)setCustomization:(NSDictionary* _Nonnull)config :(RFSCustomization* _Nonnull)result; 20 | 21 | +(RFSImageQualityCharacteristic* _Nonnull)imageQualityCharacteristicWithName:(NSString* _Nonnull)name 22 | recommendedRange:(NSArray* _Nullable)recommendedRange 23 | customRange:(NSArray* _Nullable)customRange 24 | color:(UIColor* _Nullable)color; 25 | 26 | @end 27 | #endif 28 | -------------------------------------------------------------------------------- /lib/src/person_database/person_image.dart: -------------------------------------------------------------------------------- 1 | part of "../../flutter_face_api.dart"; 2 | 3 | class PersonImage { 4 | String get path => _path; 5 | late String _path; 6 | 7 | String get url => _url; 8 | late String _url; 9 | 10 | String? get contentType => _contentType; 11 | String? _contentType; 12 | 13 | String get id => _id; 14 | late String _id; 15 | 16 | dynamic get metadata => _metadata; 17 | dynamic _metadata; 18 | 19 | DateTime get createdAt => _createdAt; 20 | late DateTime _createdAt; 21 | 22 | PersonImage._privateConstructor(); 23 | 24 | @visibleForTesting 25 | static PersonImage? fromJson(jsonObject) { 26 | if (jsonObject == null) return null; 27 | var result = PersonImage._privateConstructor(); 28 | 29 | result._path = jsonObject["path"]; 30 | result._url = jsonObject["url"]; 31 | result._contentType = jsonObject["contentType"]; 32 | result._id = jsonObject["id"]; 33 | result._metadata = jsonObject["metadata"]; 34 | result._createdAt = DateTime.parse(jsonObject["createdAt"]); 35 | 36 | return result; 37 | } 38 | 39 | @visibleForTesting 40 | Map toJson() => { 41 | "path": path, 42 | "url": url, 43 | "contentType": contentType, 44 | "id": id, 45 | "metadata": metadata, 46 | "createdAt": createdAt.toString(), 47 | }.clearNulls(); 48 | } 49 | -------------------------------------------------------------------------------- /lib/src/internal/utils.dart: -------------------------------------------------------------------------------- 1 | part of "../../flutter_face_api.dart"; 2 | 3 | double? _toDouble(value) => value?.toDouble(); 4 | 5 | dynamic _decode(String? value) => value == null ? null : jsonDecode(value); 6 | 7 | ByteData? _dataFromBase64(String? value) => 8 | value == null ? null : ByteData.view(base64Decode(value).buffer); 9 | 10 | String? _dataToBase64(ByteData? value) => 11 | value == null ? null : base64Encode(value.buffer.asUint8List()); 12 | 13 | Uint8List? _bytesFromBase64(String? value) => 14 | value == null ? null : base64Decode(value); 15 | 16 | String? _bytesToBase64(Uint8List? value) => 17 | value == null ? null : base64Encode(value); 18 | 19 | Color? _intToColor(int? value) => value == null ? null : Color(value); 20 | 21 | // ignore: deprecated_member_use 22 | int? _intFromColor(Color? value) => value?.value; 23 | 24 | List? _stringListFrom(List? value) { 25 | if (value == null) return null; 26 | return List.from(value); 27 | } 28 | 29 | extension _NullSafety on List { 30 | void addSafe(E value) { 31 | if (value != null) add(value); 32 | } 33 | } 34 | 35 | extension _ClearNulls on Map { 36 | Map clearNulls() { 37 | Map result = {}; 38 | forEach((key, value) { 39 | if (value != null) result[key] = value; 40 | }); 41 | return result; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/src/face_capture/face_capture_exception.dart: -------------------------------------------------------------------------------- 1 | part of "../../flutter_face_api.dart"; 2 | 3 | class FaceCaptureException { 4 | FaceCaptureErrorCode get code => _code; 5 | late FaceCaptureErrorCode _code; 6 | 7 | String get message => _message; 8 | late String _message; 9 | 10 | FaceCaptureException._privateConstructor(); 11 | 12 | @visibleForTesting 13 | static FaceCaptureException? fromJson(jsonObject) { 14 | if (jsonObject == null) return null; 15 | var result = FaceCaptureException._privateConstructor(); 16 | 17 | result._code = FaceCaptureErrorCode.getByValue(jsonObject["code"])!; 18 | result._message = jsonObject["message"] ?? ""; 19 | 20 | return result; 21 | } 22 | 23 | @visibleForTesting 24 | Map toJson() => { 25 | "code": code.value, 26 | "message": message, 27 | }.clearNulls(); 28 | } 29 | 30 | enum FaceCaptureErrorCode { 31 | CANCEL(0), 32 | TIMEOUT(1), 33 | NOT_INITIALIZED(2), 34 | SESSION_START_FAILED(3), 35 | CAMERA_NOT_AVAILABLE(4), 36 | CAMERA_NO_PERMISSION(5), 37 | IN_PROGRESS_ALREADY(6), 38 | CONTEXT_IS_NULL(7); 39 | 40 | const FaceCaptureErrorCode(this.value); 41 | final int value; 42 | 43 | static FaceCaptureErrorCode? getByValue(int? i) { 44 | if (i == null) return null; 45 | if (i >= 600) i = i - 600; 46 | return FaceCaptureErrorCode.values.firstWhere((x) => x.value == i); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/src/init/license_exception.dart: -------------------------------------------------------------------------------- 1 | part of "../../flutter_face_api.dart"; 2 | 3 | class LicenseException { 4 | LicensingResultCode get code => _code; 5 | late LicensingResultCode _code; 6 | 7 | String get message => _message; 8 | late String _message; 9 | 10 | LicenseException._privateConstructor(); 11 | 12 | @visibleForTesting 13 | static LicenseException? fromJson(jsonObject) { 14 | if (jsonObject == null) return null; 15 | var result = LicenseException._privateConstructor(); 16 | 17 | result._code = LicensingResultCode.getByValue(jsonObject["code"])!; 18 | result._message = jsonObject["message"] ?? ""; 19 | 20 | return result; 21 | } 22 | 23 | @visibleForTesting 24 | Map toJson() => { 25 | "code": code.value, 26 | "message": message, 27 | }.clearNulls(); 28 | } 29 | 30 | enum LicensingResultCode { 31 | OK(0), 32 | LICENSE_CORRUPTED(1), 33 | INVALID_DATE(2), 34 | INVALID_VERSION(3), 35 | INVALID_DEVICE_ID(4), 36 | INVALID_SYSTEM_OR_APP_ID(5), 37 | NO_CAPABILITIES(6), 38 | NO_AUTHENTICITY(7), 39 | LICENSE_ABSENT(8), 40 | NO_INTERNET(9), 41 | NO_DATABASE(10), 42 | DATABASE_INCORRECT(11); 43 | 44 | const LicensingResultCode(this.value); 45 | final int value; 46 | 47 | static LicensingResultCode? getByValue(int? i) { 48 | if (i == null) return null; 49 | return LicensingResultCode.values.firstWhere((x) => x.value == i); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lib/src/match_faces/compared_face.dart: -------------------------------------------------------------------------------- 1 | part of "../../flutter_face_api.dart"; 2 | 3 | /// Represents a reference information of the compared face. 4 | class ComparedFace { 5 | /// The index to the input image in the input array provided to the request. 6 | int get imageIndex => _imageIndex; 7 | late int _imageIndex; 8 | 9 | /// The input image used for comparison operation. 10 | MatchFacesImage get image => _image; 11 | late MatchFacesImage _image; 12 | 13 | /// The index to the array of `faces` in the `detection` results. 14 | int? get faceIndex => _faceIndex; 15 | int? _faceIndex; 16 | 17 | /// The face detection result. 18 | MatchFacesDetectionFace? get face => _face; 19 | MatchFacesDetectionFace? _face; 20 | 21 | ComparedFace._privateConstructor(); 22 | 23 | @visibleForTesting 24 | static ComparedFace? fromJson(jsonObject) { 25 | if (jsonObject == null) return null; 26 | var result = ComparedFace._privateConstructor(); 27 | 28 | result._imageIndex = jsonObject["imageIndex"]; 29 | result._image = MatchFacesImage.fromJson(jsonObject["image"])!; 30 | result._faceIndex = jsonObject["faceIndex"]; 31 | result._face = MatchFacesDetectionFace.fromJson(jsonObject["face"]); 32 | 33 | return result; 34 | } 35 | 36 | @visibleForTesting 37 | Map toJson() => { 38 | "imageIndex": imageIndex, 39 | "image": image.toJson(), 40 | "faceIndex": faceIndex, 41 | "face": face?.toJson(), 42 | }.clearNulls(); 43 | } 44 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | stages: 2 | - test 3 | - reports 4 | 5 | include: 6 | - template: Jobs/SAST.gitlab-ci.yml 7 | - template: Jobs/Secret-Detection.gitlab-ci.yml 8 | - template: Jobs/Dependency-Scanning.gitlab-ci.yml 9 | - local: .gitlab/report.yaml 10 | rules: 11 | - if: $CI_PIPELINE_SOURCE == "merge_request_event" 12 | inputs: 13 | stage: reports 14 | image: ubuntu:22.04 15 | ignore_vulnerabilities: "false" 16 | sast_report_file: gl-sast-report.json 17 | secret_detection_report_file: gl-secret-detection-report.json 18 | dependency_scanning_report_file: gl-dependency-scanning-report.json 19 | 20 | trivy scan: 21 | stage: test 22 | image: aquasec/trivy 23 | rules: 24 | - if: $CI_PIPELINE_SOURCE == "merge_request_event" 25 | script: 26 | - trivy fs --severity CRITICAL,HIGH,MEDIUM,LOW --ignore-unfixed --db-repository container-registry.regula.local:80/aquasecurity/trivy-db:2 --exit-code 1 . 27 | 28 | semgrep-sast: 29 | rules: 30 | - if: $CI_PIPELINE_SOURCE == "merge_request_event" 31 | artifacts: 32 | paths: 33 | - gl-sast-report.json 34 | 35 | secret_detection: 36 | rules: 37 | - if: $CI_PIPELINE_SOURCE == "merge_request_event" 38 | artifacts: 39 | paths: 40 | - gl-secret-detection-report.json 41 | 42 | gemnasium-dependency_scanning: 43 | variables: 44 | DS_MAX_DEPTH: -1 45 | rules: 46 | - if: $CI_PIPELINE_SOURCE == "merge_request_event" 47 | artifacts: 48 | paths: 49 | - gl-dependency-scanning-report.json 50 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | rootProject.allprojects { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | maven { 6 | url "https://maven.regulaforensics.com/RegulaDocumentReader" 7 | } 8 | } 9 | } 10 | 11 | apply plugin: 'com.android.library' 12 | apply plugin: 'kotlin-android' 13 | 14 | android { 15 | namespace 'com.regula.plugin.facesdk' 16 | compileSdk 36 17 | 18 | defaultConfig { 19 | minSdk 24 20 | } 21 | 22 | compileOptions { 23 | sourceCompatibility = JavaVersion.VERSION_11 24 | targetCompatibility = JavaVersion.VERSION_11 25 | } 26 | kotlinOptions { 27 | jvmTarget = '11' 28 | } 29 | 30 | dependencies { 31 | implementation('com.regula.face:api:7.2.4155') { 32 | transitive = true 33 | } 34 | 35 | testImplementation 'junit:junit:4.13.2' 36 | testImplementation 'androidx.test:core:1.6.1' 37 | testImplementation 'org.robolectric:robolectric:4.11.1' 38 | testImplementation 'org.json:json:20240303' 39 | testImplementation 'org.skyscreamer:jsonassert:1.5.1' 40 | } 41 | 42 | testOptions { 43 | unitTests { 44 | includeAndroidResources = true 45 | } 46 | unitTests.all { 47 | testLogging { 48 | events "passed", "skipped", "failed", "standardOut", "standardError" 49 | outputs.upToDateWhen { false } 50 | showStandardStreams = true 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/src/detect_faces/detect_faces_backend_exception.dart: -------------------------------------------------------------------------------- 1 | part of "../../flutter_face_api.dart"; 2 | 3 | class DetectFacesBackendException { 4 | DetectFacesBackendErrorCode get code => _code; 5 | late DetectFacesBackendErrorCode _code; 6 | 7 | String get message => _message; 8 | late String _message; 9 | 10 | DetectFacesBackendException._privateConstructor(); 11 | 12 | @visibleForTesting 13 | static DetectFacesBackendException? fromJson(jsonObject) { 14 | if (jsonObject == null) return null; 15 | var result = DetectFacesBackendException._privateConstructor(); 16 | 17 | result._code = DetectFacesBackendErrorCode.getByValue(jsonObject["code"])!; 18 | result._message = jsonObject["message"] ?? ""; 19 | 20 | return result; 21 | } 22 | 23 | @visibleForTesting 24 | Map toJson() => { 25 | "code": code.value, 26 | "message": message, 27 | }.clearNulls(); 28 | } 29 | 30 | enum DetectFacesBackendErrorCode { 31 | FR_FACE_NOT_DETECTED(2), 32 | FACER_NO_LICENSE(200), 33 | FACER_IS_NOT_INITIALIZED(201), 34 | FACER_COMMAND_IS_NOT_SUPPORTED(202), 35 | FACER_COMMAND_PARAMS_READ_ERROR(203), 36 | UNDEFINED(-1); 37 | 38 | const DetectFacesBackendErrorCode(this.value); 39 | final int value; 40 | 41 | static DetectFacesBackendErrorCode? getByValue(int? i) { 42 | if (i == null) return null; 43 | try { 44 | return DetectFacesBackendErrorCode.values.firstWhere((x) => x.value == i); 45 | } catch (_) { 46 | return DetectFacesBackendErrorCode.UNDEFINED; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lib/src/internal/event_channels.dart: -------------------------------------------------------------------------------- 1 | part of "../../flutter_face_api.dart"; 2 | 3 | late CustomButtonTappedCompletion _customButtonTappedCompletion; 4 | void _setCustomButtonTappedCompletion(CustomButtonTappedCompletion completion) { 5 | _customButtonTappedCompletion = completion; 6 | _eventChannel( 7 | 'onCustomButtonTappedEvent', (msg) => _customButtonTappedCompletion(msg)); 8 | } 9 | 10 | late VideoEncoderCompletion _videoEncoderCompletion; 11 | void _setVideoEncoderCompletion(VideoEncoderCompletion completion) { 12 | _videoEncoderCompletion = completion; 13 | _eventChannel('video_encoder_completion', (msg) { 14 | var jsonObject = json.decode(msg); 15 | var transactionId = jsonObject["transactionId"]; 16 | var success = jsonObject["success"]; 17 | _videoEncoderCompletion(transactionId, success); 18 | }); 19 | } 20 | 21 | LivenessNotificationCompletion? _livenessNotificationCompletion; 22 | void _setLivenessNotificationCompletion( 23 | LivenessNotificationCompletion? completion) { 24 | _livenessNotificationCompletion = completion; 25 | _eventChannel('livenessNotificationEvent', (msg) { 26 | var livenessNotification = LivenessNotification.fromJson(json.decode(msg))!; 27 | _livenessNotificationCompletion?.call(livenessNotification); 28 | }); 29 | } 30 | 31 | CameraSwitchCallback? _cameraSwitchCallback; 32 | void _setCameraSwitchCallback(CameraSwitchCallback? callback) { 33 | _cameraSwitchCallback = callback; 34 | _eventChannel('cameraSwitchEvent', (cameraId) { 35 | _cameraSwitchCallback?.call(cameraId); 36 | }); 37 | } 38 | -------------------------------------------------------------------------------- /test/nullable.dart: -------------------------------------------------------------------------------- 1 | Map?> nullableMap = { 2 | "customizationColors!": [], 3 | "customizationFonts!": [], 4 | "customizationImages!": [], 5 | "outputImageCrop": ["size", "padColor"], 6 | "outputImageParams!": [], 7 | "imageQualityCharacteristic": ["customRange", "color"], 8 | "detectFacesAttributeResult": ["confidence", "value"], 9 | "detectFaceResult!": ["isQualityCompliant"], 10 | "detectFacesConfig!": ["onlyCentralFace"], 11 | "detectFacesRequest!": ["image"], 12 | "detectFacesException": ["underlyingError"], 13 | "detectFacesResponse!": [], 14 | "faceCaptureConfig": [ 15 | "cameraPositionAndroid", 16 | "timeout", 17 | "holdStillDuration" 18 | ], 19 | "faceCaptureResponse!": [], 20 | "faceSDKVersion!": [], 21 | "initException": ["underlyingError"], 22 | "livenessConfig": ["cameraPositionAndroid", "tag"], 23 | "livenessResponse!": ["liveness"], 24 | "livenessException": ["underlyingError"], 25 | "livenessNotification": ["response"], 26 | "matchFacesRequest!": ["images"], 27 | "matchFacesDetectionFace": ["rotationAngle", "originalRect", "crop"], 28 | "matchFacesDetection": ["error"], 29 | "matchFacesException": ["underlyingError"], 30 | "comparedFace": ["faceIndex", "face"], 31 | "comparedFacesPair": ["error"], 32 | "matchFacesResponse": ["tag", "error"], 33 | "editGroupPersonsRequest!": [], 34 | "person": ["metadata"], 35 | "personGroup": ["metadata"], 36 | "personImage": ["metadata"], 37 | "searchPersonImage": ["metadata"], 38 | "searchPersonRequest!": ["imageUpload", "detectAll"], 39 | }; 40 | -------------------------------------------------------------------------------- /example/ios/Podfile: -------------------------------------------------------------------------------- 1 | source "https://github.com/CocoaPods/Specs.git" 2 | 3 | platform :ios, '13.0' 4 | 5 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 6 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 7 | 8 | project 'Runner', { 9 | 'Debug' => :debug, 10 | 'Profile' => :release, 11 | 'Release' => :release, 12 | } 13 | 14 | def flutter_root 15 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 16 | unless File.exist?(generated_xcode_build_settings_path) 17 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 18 | end 19 | 20 | File.foreach(generated_xcode_build_settings_path) do |line| 21 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 22 | return matches[1].strip if matches 23 | end 24 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 25 | end 26 | 27 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 28 | 29 | flutter_ios_podfile_setup 30 | 31 | target 'Runner' do 32 | use_frameworks! 33 | use_modular_headers! 34 | 35 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 36 | target 'RunnerTests' do 37 | inherit! :search_paths 38 | end 39 | end 40 | 41 | post_install do |installer| 42 | installer.pods_project.targets.each do |target| 43 | flutter_additional_ios_build_settings(target) 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/src/detect_faces/detect_faces_response.dart: -------------------------------------------------------------------------------- 1 | part of "../../flutter_face_api.dart"; 2 | 3 | class DetectFacesResponse { 4 | DetectFaceResult? get detection => _detection; 5 | DetectFaceResult? _detection; 6 | 7 | List? get allDetections => _allDetections; 8 | List? _allDetections; 9 | 10 | DetectFacesScenario? get scenario => _scenario; 11 | DetectFacesScenario? _scenario; 12 | 13 | DetectFacesException? get error => _error; 14 | DetectFacesException? _error; 15 | 16 | DetectFacesResponse._privateConstructor(); 17 | 18 | @visibleForTesting 19 | static DetectFacesResponse? fromJson(jsonObject) { 20 | if (jsonObject == null) return null; 21 | var result = DetectFacesResponse._privateConstructor(); 22 | 23 | result._detection = DetectFaceResult.fromJson(jsonObject["detection"]); 24 | if (jsonObject["allDetections"] != null) { 25 | result._allDetections = []; 26 | for (var item in jsonObject["allDetections"]) { 27 | result._allDetections!.add(DetectFaceResult.fromJson(item)!); 28 | } 29 | } 30 | result._scenario = DetectFacesScenario.getByValue(jsonObject["scenario"]); 31 | result._error = DetectFacesException.fromJson(jsonObject["error"]); 32 | 33 | return result; 34 | } 35 | 36 | @visibleForTesting 37 | Map toJson() => { 38 | "detection": detection?.toJson(), 39 | "allDetections": allDetections?.map((e) => e.toJson()).toList(), 40 | "scenario": scenario?.value, 41 | "error": error?.toJson(), 42 | }.clearNulls(); 43 | } 44 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CADisableMinimumFrameDurationOnPhone 6 | 7 | CFBundleDevelopmentRegion 8 | $(DEVELOPMENT_LANGUAGE) 9 | CFBundleDisplayName 10 | Face 11 | CFBundleExecutable 12 | $(EXECUTABLE_NAME) 13 | CFBundleIdentifier 14 | $(PRODUCT_BUNDLE_IDENTIFIER) 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | flutter_face_api_example 19 | CFBundlePackageType 20 | APPL 21 | CFBundleShortVersionString 22 | $(FLUTTER_BUILD_NAME) 23 | CFBundleSignature 24 | ???? 25 | CFBundleVersion 26 | $(FLUTTER_BUILD_NUMBER) 27 | LSRequiresIPhoneOS 28 | 29 | NSCameraUsageDescription 30 | To use camera 31 | NSPhotoLibraryUsageDescription 32 | To use gallery 33 | UIApplicationSupportsIndirectInputEvents 34 | 35 | UILaunchStoryboardName 36 | LaunchScreen 37 | UIMainStoryboardFile 38 | Main 39 | UISupportedInterfaceOrientations 40 | 41 | UIInterfaceOrientationPortrait 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /lib/src/init/init_exception.dart: -------------------------------------------------------------------------------- 1 | part of "../../flutter_face_api.dart"; 2 | 3 | class InitException { 4 | InitErrorCode get code => _code; 5 | late InitErrorCode _code; 6 | 7 | String get message => _message; 8 | late String _message; 9 | 10 | LicenseException? get underlyingError => _underlyingError; 11 | LicenseException? _underlyingError; 12 | 13 | InitException._privateConstructor(); 14 | 15 | @visibleForTesting 16 | static InitException? fromJson(jsonObject) { 17 | if (jsonObject == null) return null; 18 | var result = InitException._privateConstructor(); 19 | 20 | result._code = InitErrorCode.getByValue(jsonObject["code"])!; 21 | result._message = jsonObject["message"] ?? ""; 22 | result._underlyingError = 23 | LicenseException.fromJson(jsonObject["underlyingError"]); 24 | 25 | return result; 26 | } 27 | 28 | @visibleForTesting 29 | Map toJson() => { 30 | "code": code.value, 31 | "message": message, 32 | "underlyingError": underlyingError?.toJson(), 33 | }.clearNulls(); 34 | } 35 | 36 | enum InitErrorCode { 37 | IN_PROGRESS_ALREADY(0), 38 | 39 | MISSING_CORE(1), 40 | 41 | INTERNAL_CORE_ERROR(2), 42 | 43 | BAD_LICENSE(3), 44 | 45 | UNAVAILABLE(4), 46 | 47 | CONTEXT_IS_NULL(100), 48 | 49 | RESOURCE_DAT_ABSENT(101), 50 | 51 | LICENSE_IS_NULL(102); 52 | 53 | const InitErrorCode(this.value); 54 | final int value; 55 | 56 | static InitErrorCode? getByValue(int? i) { 57 | if (i == null) return null; 58 | return InitErrorCode.values.firstWhere((x) => x.value == i); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /lib/src/person_database/person.dart: -------------------------------------------------------------------------------- 1 | part of "../../flutter_face_api.dart"; 2 | 3 | /// A Person Database object that represents Person. 4 | class Person { 5 | /// Person name. 6 | /// Updatable field. 7 | late String name; 8 | 9 | /// Person update date. 10 | DateTime get updatedAt => _updatedAt; 11 | late DateTime _updatedAt; 12 | 13 | /// Array if Group IDs Person belongs to. 14 | List get groups => _groups; 15 | late List _groups; 16 | 17 | String get id => _id; 18 | late String _id; 19 | 20 | /// A free-form object containing Person extended attributes. 21 | /// Updatable field. 22 | dynamic metadata; 23 | 24 | DateTime get createdAt => _createdAt; 25 | late DateTime _createdAt; 26 | 27 | Person._privateConstructor(); 28 | 29 | @visibleForTesting 30 | static Person? fromJson(jsonObject) { 31 | if (jsonObject == null) return null; 32 | var result = Person._privateConstructor(); 33 | 34 | result.name = jsonObject["name"]; 35 | result._updatedAt = DateTime.parse(jsonObject["updatedAt"]); 36 | result._groups = _stringListFrom((jsonObject["groups"]))!; 37 | result._id = jsonObject["id"]; 38 | result.metadata = jsonObject["metadata"]; 39 | result._createdAt = DateTime.parse(jsonObject["createdAt"]); 40 | 41 | return result; 42 | } 43 | 44 | @visibleForTesting 45 | Map toJson() => { 46 | "name": name, 47 | "updatedAt": updatedAt.toString(), 48 | "groups": groups, 49 | "id": id, 50 | "metadata": metadata, 51 | "createdAt": createdAt.toString(), 52 | }.clearNulls(); 53 | } 54 | -------------------------------------------------------------------------------- /lib/src/match_faces/match_faces_response.dart: -------------------------------------------------------------------------------- 1 | part of "../../flutter_face_api.dart"; 2 | 3 | /// The response from the [MatchFacesRequest]. 4 | class MatchFacesResponse { 5 | /// Face comparison results with score and similarity values. 6 | List get results => _results; 7 | List _results = []; 8 | 9 | /// Face detection results for each given image. 10 | List get detections => _detections; 11 | List _detections = []; 12 | 13 | String? get tag => _tag; 14 | String? _tag; 15 | 16 | /// The error describes a failed match faces request and contains [MatchFacesErrorCode] codes. 17 | MatchFacesException? get error => _error; 18 | MatchFacesException? _error; 19 | 20 | MatchFacesResponse._privateConstructor(); 21 | 22 | @visibleForTesting 23 | static MatchFacesResponse? fromJson(jsonObject) { 24 | var result = MatchFacesResponse._privateConstructor(); 25 | 26 | for (var item in jsonObject["results"]) { 27 | result._results.add(ComparedFacesPair.fromJson(item)!); 28 | } 29 | for (var item in jsonObject["detections"]) { 30 | result._detections.add(MatchFacesDetection.fromJson(item)!); 31 | } 32 | result._tag = jsonObject["tag"]; 33 | result._error = MatchFacesException.fromJson(jsonObject["error"]); 34 | 35 | return result; 36 | } 37 | 38 | @visibleForTesting 39 | Map toJson() => { 40 | "results": results.map((e) => e.toJson()).toList(), 41 | "detections": detections.map((e) => e.toJson()).toList(), 42 | "tag": tag, 43 | "error": error?.toJson(), 44 | }.clearNulls(); 45 | } 46 | -------------------------------------------------------------------------------- /lib/src/person_database/search_person_image.dart: -------------------------------------------------------------------------------- 1 | part of "../../flutter_face_api.dart"; 2 | 3 | /// A Person Database object that represents Image result of Person Search. 4 | class SearchPersonImage extends PersonImage { 5 | /// The similarity score. 6 | /// From 0.0 to 1.0. 7 | double get similarity => _similarity; 8 | late double _similarity; 9 | 10 | /// The similarity distance score. 11 | /// The lower the distance, the higher the face's similarity. 12 | /// From 0.0 to 2.0. 13 | double get distance => _distance; 14 | late double _distance; 15 | 16 | SearchPersonImage._privateConstructor() : super._privateConstructor(); 17 | 18 | @visibleForTesting 19 | static SearchPersonImage? fromJson(jsonObject) { 20 | if (jsonObject == null) return null; 21 | var result = SearchPersonImage._privateConstructor(); 22 | 23 | result._similarity = _toDouble(jsonObject["similarity"])!; 24 | result._distance = _toDouble(jsonObject["distance"])!; 25 | result._path = jsonObject["path"]; 26 | result._url = jsonObject["url"]; 27 | result._contentType = jsonObject["contentType"]; 28 | result._id = jsonObject["id"]; 29 | result._metadata = jsonObject["metadata"]; 30 | result._createdAt = DateTime.parse(jsonObject["createdAt"]); 31 | 32 | return result; 33 | } 34 | 35 | @visibleForTesting 36 | Map toJson() => { 37 | "similarity": similarity, 38 | "distance": distance, 39 | "path": path, 40 | "url": url, 41 | "contentType": contentType, 42 | "id": id, 43 | "metadata": metadata, 44 | "createdAt": createdAt.toString(), 45 | }.clearNulls(); 46 | } 47 | -------------------------------------------------------------------------------- /lib/src/match_faces/match_faces_detection.dart: -------------------------------------------------------------------------------- 1 | part of "../../flutter_face_api.dart"; 2 | 3 | /// Represents detection results on an input image as a part of [MatchFacesResponse]. 4 | class MatchFacesDetection { 5 | /// The index to the input image in the input array provided to the request. 6 | int get imageIndex => _imageIndex; 7 | late int _imageIndex; 8 | 9 | /// The input image used for comparison operation. 10 | MatchFacesImage get image => _image; 11 | late MatchFacesImage _image; 12 | 13 | /// The array of faces detected on the image. 14 | List get faces => _faces; 15 | List _faces = []; 16 | 17 | /// The error describes a failed face detection and contains [MatchFacesErrorCode] codes. 18 | MatchFacesException? get error => _error; 19 | MatchFacesException? _error; 20 | 21 | MatchFacesDetection._privateConstructor(); 22 | 23 | @visibleForTesting 24 | static MatchFacesDetection? fromJson(jsonObject) { 25 | var result = MatchFacesDetection._privateConstructor(); 26 | 27 | result._image = MatchFacesImage.fromJson(jsonObject["image"])!; 28 | result._imageIndex = jsonObject["imageIndex"]; 29 | for (var item in jsonObject["faces"]) { 30 | result.faces.add(MatchFacesDetectionFace.fromJson(item)!); 31 | } 32 | result._error = MatchFacesException.fromJson(jsonObject["error"]); 33 | 34 | return result; 35 | } 36 | 37 | @visibleForTesting 38 | Map toJson() => { 39 | "imageIndex": imageIndex, 40 | "image": image.toJson(), 41 | "faces": faces.map((e) => e.toJson()).toList(), 42 | "error": error?.toJson(), 43 | }.clearNulls(); 44 | } 45 | -------------------------------------------------------------------------------- /lib/src/liveness/liveness_notification.dart: -------------------------------------------------------------------------------- 1 | part of "../../flutter_face_api.dart"; 2 | 3 | class LivenessNotification { 4 | LivenessProcessStatus get status => _status; 5 | late LivenessProcessStatus _status; 6 | 7 | LivenessResponse? get response => _response; 8 | LivenessResponse? _response; 9 | 10 | LivenessNotification._privateConstructor(); 11 | 12 | @visibleForTesting 13 | static LivenessNotification? fromJson(jsonObject) { 14 | if (jsonObject == null) return null; 15 | var result = new LivenessNotification._privateConstructor(); 16 | 17 | result._status = LivenessProcessStatus.getByValue(jsonObject["status"])!; 18 | result._response = LivenessResponse.fromJson(jsonObject["response"]); 19 | 20 | return result; 21 | } 22 | 23 | @visibleForTesting 24 | Map toJson() => { 25 | "status": status.value, 26 | "response": response?.toJson(), 27 | }.clearNulls(); 28 | } 29 | 30 | /// Liveness process statuses. 31 | enum LivenessProcessStatus { 32 | START(0), 33 | PREPARING(1), 34 | NEW_SESSION(2), 35 | NEXT_STAGE(3), 36 | SECTOR_CHANGED(4), 37 | PROGRESS(5), 38 | LOW_BRIGHTNESS(6), 39 | FIT_FACE(7), 40 | MOVE_AWAY(8), 41 | MOVE_CLOSER(9), 42 | TURN_HEAD(10), 43 | PROCESSING(11), 44 | FAILED(12), 45 | RETRY(13), 46 | SUCCESS(14), 47 | REMOVE_OCCLUSION(15); 48 | 49 | const LivenessProcessStatus(this.value); 50 | final int value; 51 | 52 | static LivenessProcessStatus? getByValue(int? i) { 53 | if (i == null) return null; 54 | return LivenessProcessStatus.values.firstWhere((x) => x.value == i); 55 | } 56 | } 57 | 58 | typedef LivenessNotificationCompletion = void Function(LivenessNotification); 59 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 11 | 19 | 22 | 23 | 24 | 25 | 26 | 27 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /lib/src/image_params/output_image_params.dart: -------------------------------------------------------------------------------- 1 | part of "../../flutter_face_api.dart"; 2 | 3 | /// Set of parameter for image processing. 4 | class OutputImageParams { 5 | /// If set, the Base64 of an aligned and cropped portrait is returned in the crop field. 6 | /// Alignment is performed according to type. 7 | /// If a head on the original image is tilted, for the returned portrait it is aligned in a straight vertical line. 8 | /// 9 | /// If there are more than one face in the photo, all the faces will be detected and processed, and separate portraits for each face will be returned. 10 | /// So, if there were five people in the photo, you'll get five processed portraits. 11 | OutputImageCrop? get crop => _crop; 12 | OutputImageCrop? _crop; 13 | 14 | /// If set, the background color is replaced. 15 | /// The silhouette of a person is cut out and the background is filled with this color. 16 | Color? get backgroundColor => _backgroundColor; 17 | Color? _backgroundColor; 18 | 19 | OutputImageParams({ 20 | OutputImageCrop? crop, 21 | Color? backgroundColor, 22 | }) : _crop = crop, 23 | _backgroundColor = backgroundColor; 24 | 25 | @visibleForTesting 26 | static OutputImageParams? fromJson(jsonObject) { 27 | if (jsonObject == null) return null; 28 | var result = OutputImageParams(); 29 | 30 | result._crop = OutputImageCrop.fromJson(jsonObject["crop"]); 31 | result._backgroundColor = _intToColor(jsonObject["backgroundColor"]); 32 | 33 | return result; 34 | } 35 | 36 | @visibleForTesting 37 | Map toJson() => { 38 | "crop": crop?.toJson(), 39 | "backgroundColor": _intFromColor(backgroundColor), 40 | }.clearNulls(); 41 | } 42 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /lib/src/match_faces/match_faces_exception.dart: -------------------------------------------------------------------------------- 1 | part of "../../flutter_face_api.dart"; 2 | 3 | class MatchFacesException { 4 | MatchFacesErrorCode get code => _code; 5 | late MatchFacesErrorCode _code; 6 | 7 | String get message => _message; 8 | late String _message; 9 | 10 | MatchFacesBackendException? get underlyingError => _underlyingError; 11 | MatchFacesBackendException? _underlyingError; 12 | 13 | MatchFacesException._privateConstructor(); 14 | 15 | @visibleForTesting 16 | static MatchFacesException? fromJson(jsonObject) { 17 | if (jsonObject == null) return null; 18 | var result = MatchFacesException._privateConstructor(); 19 | 20 | result._code = MatchFacesErrorCode.getByValue(jsonObject["code"])!; 21 | result._message = jsonObject["message"] ?? ""; 22 | result._underlyingError = 23 | MatchFacesBackendException.fromJson(jsonObject["underlyingError"]); 24 | 25 | return result; 26 | } 27 | 28 | @visibleForTesting 29 | Map toJson() => { 30 | "code": code.value, 31 | "message": message, 32 | "underlyingError": underlyingError?.toJson() 33 | }.clearNulls(); 34 | } 35 | 36 | enum MatchFacesErrorCode { 37 | IMAGE_EMPTY(0), 38 | FACE_NOT_DETECTED(1), 39 | LANDMARKS_NOT_DETECTED(2), 40 | FACE_ALIGNER_FAILED(3), 41 | DESCRIPTOR_EXTRACTOR_ERROR(4), 42 | IMAGES_COUNT_LIMIT_EXCEEDED(5), 43 | API_CALL_FAILED(6), 44 | PROCESSING_FAILED(7), 45 | NO_LICENSE(8); 46 | 47 | const MatchFacesErrorCode(this.value); 48 | final int value; 49 | 50 | static MatchFacesErrorCode? getByValue(int? i) { 51 | if (i == null) return null; 52 | return MatchFacesErrorCode.values.firstWhere((x) => x.value == i); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/src/customization/font.dart: -------------------------------------------------------------------------------- 1 | part of "../../flutter_face_api.dart"; 2 | 3 | class Font { 4 | /// Font family. 5 | /// 6 | /// Beware that Android and iOS have diffrent font names, 7 | /// so you will have to use if condition. 8 | String get name => _name; 9 | String _name; 10 | 11 | /// Font size. 12 | int? get size => _size; 13 | int? _size; 14 | 15 | /// Font style. 16 | /// 17 | /// Android only. 18 | FontStyle? get style => _style; 19 | FontStyle? _style; 20 | 21 | Font( 22 | String name, { 23 | int? size, 24 | FontStyle? style, 25 | }) : _name = name, 26 | _size = size, 27 | _style = style; 28 | 29 | /// Allows you to deserialize object. 30 | static Font? fromJson(jsonObject) { 31 | if (jsonObject == null) return null; 32 | 33 | var result = Font(jsonObject["name"]); 34 | result._size = jsonObject["size"]; 35 | result._style = FontStyle.getByValue(jsonObject["style"]); 36 | 37 | return result; 38 | } 39 | 40 | /// Allows you to serialize object. 41 | Map toJson() => { 42 | "name": name, 43 | "size": size, 44 | "style": style?.value, 45 | }.clearNulls(); 46 | } 47 | 48 | enum FontStyle { 49 | /// Will be returned if [getByValue] if a non-existent was passed. 50 | UNKNOWN(-1), 51 | 52 | NORMAL(0), 53 | 54 | BOLD(1), 55 | 56 | ITALIC(2), 57 | 58 | BOLD_ITALIC(3); 59 | 60 | const FontStyle(this.value); 61 | final int value; 62 | 63 | static FontStyle? getByValue(int? i) { 64 | if (i == null) return null; 65 | try { 66 | return FontStyle.values.firstWhere((x) => x.value == i); 67 | } catch (_) { 68 | return FontStyle.UNKNOWN; 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /lib/src/init/init_config.dart: -------------------------------------------------------------------------------- 1 | part of "../../flutter_face_api.dart"; 2 | 3 | /// A configuration file for FaceSDK initialization. 4 | /// Allows to initialize using license binary. 5 | /// Also controls initialization time properties such as licenseUpdate. 6 | class InitConfig { 7 | /// The license binary file. 8 | ByteData get license => _license; 9 | ByteData _license; 10 | 11 | /// Enables automatic license update check during [FaceSDK] initialization. 12 | bool? get licenseUpdate => _licenseUpdate; 13 | bool? _licenseUpdate; 14 | 15 | bool _useBleDevice = false; 16 | 17 | /// Constructor for initialization using a ble device. 18 | /// Doesn't need a license file, it will be fetched automatically from your ble device. 19 | /// 20 | /// Requires [DocumentReader](https://pub.dev/packages/flutter_document_reader_api) to be initialized with ble device. 21 | InitConfig.withBleDevice() : _license = ByteData(0) { 22 | _useBleDevice = true; 23 | } 24 | 25 | InitConfig( 26 | ByteData license, { 27 | bool? licenseUpdate, 28 | }) : _license = license, 29 | _licenseUpdate = licenseUpdate; 30 | 31 | @visibleForTesting 32 | static InitConfig? fromJson(jsonObject) { 33 | if (jsonObject == null) return null; 34 | 35 | var result = InitConfig( 36 | _dataFromBase64(jsonObject["license"])!, 37 | licenseUpdate: jsonObject["licenseUpdate"], 38 | ); 39 | result._useBleDevice = jsonObject["useBleDevice"]; 40 | 41 | return result; 42 | } 43 | 44 | @visibleForTesting 45 | Map toJson() => { 46 | "license": _dataToBase64(license), 47 | "licenseUpdate": licenseUpdate, 48 | "useBleDevice": _useBleDevice, 49 | }.clearNulls(); 50 | } 51 | -------------------------------------------------------------------------------- /lib/src/detect_faces/detect_faces_exception.dart: -------------------------------------------------------------------------------- 1 | part of "../../flutter_face_api.dart"; 2 | 3 | class DetectFacesException { 4 | DetectFacesErrorCode get code => _code; 5 | late DetectFacesErrorCode _code; 6 | 7 | String get message => _message; 8 | late String _message; 9 | 10 | DetectFacesBackendException? get underlyingError => _underlyingError; 11 | DetectFacesBackendException? _underlyingError; 12 | 13 | DetectFacesException._privateConstructor(); 14 | 15 | @visibleForTesting 16 | static DetectFacesException? fromJson(jsonObject) { 17 | if (jsonObject == null) return null; 18 | var result = DetectFacesException._privateConstructor(); 19 | 20 | result._code = DetectFacesErrorCode.getByValue(jsonObject["code"])!; 21 | result._message = jsonObject["message"] ?? ""; 22 | result._underlyingError = 23 | DetectFacesBackendException.fromJson(jsonObject["underlyingError"]); 24 | 25 | return result; 26 | } 27 | 28 | @visibleForTesting 29 | Map toJson() => { 30 | "code": code.value, 31 | "message": message, 32 | "underlyingError": underlyingError?.toJson(), 33 | }.clearNulls(); 34 | } 35 | 36 | /// Error codes for the [DetectFacesResponse] errors. 37 | enum DetectFacesErrorCode { 38 | IMAGE_EMPTY(0), 39 | FR_FACE_NOT_DETECTED(1), 40 | FACER_NO_LICENSE(2), 41 | FACER_IS_NOT_INITIALIZED(3), 42 | FACER_COMMAND_IS_NOT_SUPPORTED(4), 43 | FACER_COMMAND_PARAMS_READ_ERROR(5), 44 | PROCESSING_FAILED(6), 45 | REQUEST_FAILED(7), 46 | API_CALL_FAILED(8); 47 | 48 | const DetectFacesErrorCode(this.value); 49 | final int value; 50 | 51 | static DetectFacesErrorCode? getByValue(int? i) { 52 | if (i == null) return null; 53 | return DetectFacesErrorCode.values.firstWhere((x) => x.value == i); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lib/src/match_faces/match_faces_image.dart: -------------------------------------------------------------------------------- 1 | part of "../../flutter_face_api.dart"; 2 | 3 | /// This class represents the input image and its attributes for [MatchFacesRequest]. 4 | class MatchFacesImage { 5 | /// The underlying image. 6 | Uint8List get image => _image; 7 | Uint8List _image; 8 | 9 | /// The image type. 10 | /// The imageType influences matching results, therefore this field is required. 11 | ImageType get imageType => _imageType; 12 | ImageType _imageType; 13 | 14 | /// Defines whether the comparison and detection should apply for all faces found on the image. Defaults to `false`. 15 | /// When set to `false`, only the most centered faces are compared and detected. 16 | /// Otherwise, all the faces are compared and detected. 17 | bool get detectAll => _detectAll; 18 | bool _detectAll; 19 | 20 | String get identifier => _identifier; 21 | String _identifier; 22 | 23 | MatchFacesImage( 24 | Uint8List image, 25 | ImageType imageType, { 26 | bool? detectAll, 27 | }) : _image = image, 28 | _imageType = imageType, 29 | _detectAll = detectAll ?? false, 30 | _identifier = ""; 31 | 32 | @visibleForTesting 33 | static MatchFacesImage? fromJson(jsonObject) { 34 | if (jsonObject == null) return null; 35 | 36 | var result = MatchFacesImage( 37 | _bytesFromBase64(jsonObject["image"])!, 38 | ImageType.getByValue(jsonObject["imageType"])!, 39 | detectAll: jsonObject["detectAll"], 40 | ); 41 | result._identifier = jsonObject["identifier"]; 42 | return result; 43 | } 44 | 45 | @visibleForTesting 46 | Map toJson() => { 47 | "image": _bytesToBase64(image), 48 | "imageType": imageType.value, 49 | "detectAll": detectAll, 50 | "identifier": identifier 51 | }.clearNulls(); 52 | } 53 | -------------------------------------------------------------------------------- /example/ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Flutter (1.0.0) 3 | - flutter_face_api (6.5.86): 4 | - Flutter 5 | - flutter_face_core_basic (6.4.242): 6 | - Flutter 7 | - image_picker_ios (0.0.1): 8 | - Flutter 9 | - path_provider_foundation (0.0.1): 10 | - Flutter 11 | - FlutterMacOS 12 | 13 | DEPENDENCIES: 14 | - Flutter (from `Flutter`) 15 | - flutter_face_api (from `.symlinks/plugins/flutter_face_api/ios`) 16 | - flutter_face_core_basic (from `.symlinks/plugins/flutter_face_core_basic/ios`) 17 | - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) 18 | - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) 19 | 20 | SPEC REPOS: 21 | https://github.com/CocoaPods/Specs.git: 22 | - FaceCoreBasic 23 | - FaceSDK 24 | - RegulaCommon 25 | 26 | EXTERNAL SOURCES: 27 | Flutter: 28 | :path: Flutter 29 | flutter_face_api: 30 | :path: ".symlinks/plugins/flutter_face_api/ios" 31 | flutter_face_core_basic: 32 | :path: ".symlinks/plugins/flutter_face_core_basic/ios" 33 | image_picker_ios: 34 | :path: ".symlinks/plugins/image_picker_ios/ios" 35 | path_provider_foundation: 36 | :path: ".symlinks/plugins/path_provider_foundation/darwin" 37 | 38 | SPEC CHECKSUMS: 39 | FaceCoreBasic: a2ff9ed108c4d9d67e892df6be5ccc4729f3cda5 40 | FaceSDK: e96016e36d5507ad9d9ee3b011d735f5fe51829f 41 | Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 42 | flutter_face_api: f9903d8c0668f667fa8dbdd2a135916c3a65d5a9 43 | flutter_face_core_basic: 742c0885871401f4d2b57080c127b073399fc9f0 44 | image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a 45 | path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 46 | RegulaCommon: 2a6406d2d3ebbd720b66a9cfa5b9631701d343eb 47 | 48 | PODFILE CHECKSUM: ecf507c2691ab0886f78ec2798fd8eeb40674c15 49 | 50 | COCOAPODS: 1.16.2 51 | -------------------------------------------------------------------------------- /lib/src/person_database/search_person.dart: -------------------------------------------------------------------------------- 1 | part of "../../flutter_face_api.dart"; 2 | 3 | /// A Person Database object that represents th result of Person search. 4 | class SearchPerson extends Person { 5 | /// Array of images where the Person is found. 6 | List get images => _images; 7 | List _images = []; 8 | 9 | /// Detection data relative to the search input image. 10 | SearchPersonDetection? get detection => _detection; 11 | SearchPersonDetection? _detection; 12 | 13 | SearchPerson._privateConstructor() : super._privateConstructor(); 14 | 15 | @visibleForTesting 16 | static SearchPerson? fromJson(Map? jsonObject) { 17 | if (jsonObject == null) return null; 18 | var result = SearchPerson._privateConstructor(); 19 | 20 | if (jsonObject["images"] != null) { 21 | for (var item in jsonObject["images"]) { 22 | result._images.add(SearchPersonImage.fromJson(item)!); 23 | } 24 | } 25 | result._detection = SearchPersonDetection.fromJson(jsonObject["detection"]); 26 | result.name = result.name = jsonObject["name"]; 27 | result._updatedAt = DateTime.parse(jsonObject["updatedAt"]); 28 | result._groups = (jsonObject["groups"] as List).cast(); 29 | result._id = jsonObject["id"]; 30 | result.metadata = jsonObject["metadata"]; 31 | result._createdAt = DateTime.parse(jsonObject["createdAt"]); 32 | 33 | return result; 34 | } 35 | 36 | @visibleForTesting 37 | Map toJson() => { 38 | "images": images.map((e) => e.toJson()), 39 | "detection": detection?.toJson(), 40 | "name": name, 41 | "updatedAt": updatedAt.toString(), 42 | "groups": groups, 43 | "id": id, 44 | "metadata": metadata, 45 | "createdAt": createdAt.toString(), 46 | }.clearNulls(); 47 | } 48 | -------------------------------------------------------------------------------- /example/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: "68415ad1d920f6fe5ec284f5c2febf7c4dd5b0b3" 8 | channel: "stable" 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: 68415ad1d920f6fe5ec284f5c2febf7c4dd5b0b3 17 | base_revision: 68415ad1d920f6fe5ec284f5c2febf7c4dd5b0b3 18 | - platform: android 19 | create_revision: 68415ad1d920f6fe5ec284f5c2febf7c4dd5b0b3 20 | base_revision: 68415ad1d920f6fe5ec284f5c2febf7c4dd5b0b3 21 | - platform: ios 22 | create_revision: 68415ad1d920f6fe5ec284f5c2febf7c4dd5b0b3 23 | base_revision: 68415ad1d920f6fe5ec284f5c2febf7c4dd5b0b3 24 | - platform: linux 25 | create_revision: 68415ad1d920f6fe5ec284f5c2febf7c4dd5b0b3 26 | base_revision: 68415ad1d920f6fe5ec284f5c2febf7c4dd5b0b3 27 | - platform: macos 28 | create_revision: 68415ad1d920f6fe5ec284f5c2febf7c4dd5b0b3 29 | base_revision: 68415ad1d920f6fe5ec284f5c2febf7c4dd5b0b3 30 | - platform: web 31 | create_revision: 68415ad1d920f6fe5ec284f5c2febf7c4dd5b0b3 32 | base_revision: 68415ad1d920f6fe5ec284f5c2febf7c4dd5b0b3 33 | - platform: windows 34 | create_revision: 68415ad1d920f6fe5ec284f5c2febf7c4dd5b0b3 35 | base_revision: 68415ad1d920f6fe5ec284f5c2febf7c4dd5b0b3 36 | 37 | # User provided section 38 | 39 | # List of Local paths (relative to this file) that should be 40 | # ignored by the migrate tool. 41 | # 42 | # Files that are not part of the templates will be ignored by default. 43 | unmanaged_files: 44 | - 'lib/main.dart' 45 | - 'ios/Runner.xcodeproj/project.pbxproj' 46 | -------------------------------------------------------------------------------- /lib/src/image_quality/image_quality_characteristic.dart: -------------------------------------------------------------------------------- 1 | part of "../../flutter_face_api.dart"; 2 | 3 | /// Image Quality parameter to include in [DetectFacesConfig] as [DetectFacesConfig.customQuality]. 4 | class ImageQualityCharacteristic { 5 | ImageQualityCharacteristicName _characteristicName; 6 | ImageQualityRange? _recommendedRange; 7 | ImageQualityRange? _customRange; 8 | Color? _color; 9 | 10 | ImageQualityCharacteristic._create( 11 | ImageQualityCharacteristicName name, { 12 | ImageQualityRange? recommended, 13 | ImageQualityRange? custom, 14 | Color? color, 15 | }) : _characteristicName = name, 16 | _recommendedRange = recommended, 17 | _customRange = custom, 18 | _color = color; 19 | 20 | ImageQualityCharacteristic withCustomRange(double min, double max) { 21 | _customRange = ImageQualityRange(min, max); 22 | return this; 23 | } 24 | 25 | ImageQualityCharacteristic withCustomValue(double value) { 26 | _customRange = ImageQualityRange.withValue(value); 27 | return this; 28 | } 29 | 30 | @visibleForTesting 31 | static ImageQualityCharacteristic? fromJson(jsonObject) { 32 | if (jsonObject == null) return null; 33 | 34 | return ImageQualityCharacteristic._create( 35 | ImageQualityCharacteristicName.getByValue( 36 | jsonObject["characteristicName"])!, 37 | recommended: ImageQualityRange.fromJson(jsonObject["recommendedRange"]), 38 | custom: ImageQualityRange.fromJson(jsonObject["customRange"]), 39 | color: _intToColor(jsonObject["color"]), 40 | ); 41 | } 42 | 43 | @visibleForTesting 44 | Map toJson() => { 45 | "characteristicName": _characteristicName.value, 46 | "recommendedRange": _recommendedRange?.toJson(), 47 | "customRange": _customRange?.toJson(), 48 | "color": _intFromColor(_color), 49 | }.clearNulls(); 50 | } 51 | -------------------------------------------------------------------------------- /lib/src/liveness/liveness_exception.dart: -------------------------------------------------------------------------------- 1 | part of "../../flutter_face_api.dart"; 2 | 3 | class LivenessException { 4 | LivenessErrorCode get code => _code; 5 | late LivenessErrorCode _code; 6 | 7 | String get message => _message; 8 | late String _message; 9 | 10 | LivenessBackendException? get underlyingError => _underlyingError; 11 | LivenessBackendException? _underlyingError; 12 | 13 | LivenessException._privateConstructor(); 14 | 15 | @visibleForTesting 16 | static LivenessException? fromJson(jsonObject) { 17 | if (jsonObject == null) return null; 18 | var result = LivenessException._privateConstructor(); 19 | 20 | result._code = LivenessErrorCode.getByValue(jsonObject["code"])!; 21 | result._message = jsonObject["message"] ?? ""; 22 | result._underlyingError = 23 | LivenessBackendException.fromJson(jsonObject["underlyingError"]); 24 | 25 | return result; 26 | } 27 | 28 | @visibleForTesting 29 | Map toJson() => { 30 | "code": code.value, 31 | "message": message, 32 | "underlyingError": underlyingError?.toJson(), 33 | }.clearNulls(); 34 | } 35 | 36 | enum LivenessErrorCode { 37 | NOT_INITIALIZED(0), 38 | 39 | NO_LICENSE(1), 40 | 41 | API_CALL_FAILED(2), 42 | 43 | SESSION_START_FAILED(3), 44 | 45 | CANCELLED(4), 46 | 47 | PROCESSING_TIMEOUT(5), 48 | 49 | PROCESSING_FAILED(6), 50 | 51 | PROCESSING_FRAME_FAILED(7), 52 | 53 | APPLICATION_INACTIVE(8), 54 | 55 | CONTEXT_IS_NULL(9), 56 | 57 | IN_PROGRESS_ALREADY(10), 58 | 59 | ZOOM_NOT_SUPPORTED(11), 60 | 61 | CAMERA_NO_PERMISSION(12), 62 | 63 | CAMERA_NOT_AVAILABLE(13); 64 | 65 | const LivenessErrorCode(this.value); 66 | final int value; 67 | 68 | static LivenessErrorCode? getByValue(int? i) { 69 | if (i == null) return null; 70 | return LivenessErrorCode.values.firstWhere((x) => x.value == i); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /lib/src/face_capture/face_capture_image.dart: -------------------------------------------------------------------------------- 1 | part of "../../flutter_face_api.dart"; 2 | 3 | class FaceCaptureImage { 4 | Uint8List get image => _image; 5 | late Uint8List _image; 6 | 7 | ImageType get imageType => _imageType; 8 | late ImageType _imageType; 9 | 10 | String? get tag => _tag; 11 | String? _tag; 12 | 13 | FaceCaptureImage._privateConstructor(); 14 | 15 | @visibleForTesting 16 | static FaceCaptureImage? fromJson(jsonObject) { 17 | if (jsonObject == null) return null; 18 | var result = FaceCaptureImage._privateConstructor(); 19 | 20 | result._image = _bytesFromBase64(jsonObject["image"])!; 21 | result._imageType = ImageType.getByValue(jsonObject["imageType"])!; 22 | result._tag = jsonObject["tag"]; 23 | 24 | return result; 25 | } 26 | 27 | @visibleForTesting 28 | Map toJson() => { 29 | "image": _bytesToBase64(image), 30 | "imageType": imageType.value, 31 | "tag": tag 32 | }.clearNulls(); 33 | } 34 | 35 | /// The image type of [FaceCaptureImage] influences matching results and provides the information about the source of the image. 36 | enum ImageType { 37 | /// The image contains a printed portrait of a person. 38 | PRINTED(1), 39 | 40 | /// The image contains a portrait of a person and is taken from the RFID chip. 41 | RFID(2), 42 | 43 | /// The image is taken from the camera. 44 | LIVE(3), 45 | 46 | /// The image contains a document with a portrait of a person. 47 | DOCUMENT_WITH_LIVE(4), 48 | 49 | /// The image from an unknown source. 50 | EXTERNAL(5), 51 | 52 | /// The image is a ghost portrait. 53 | GHOST_PORTRAIT(6), 54 | 55 | /// The image from a barcode. 56 | BARCODE(7); 57 | 58 | const ImageType(this.value); 59 | final int value; 60 | 61 | static ImageType? getByValue(int? i) { 62 | if (i == null) return null; 63 | return ImageType.values.firstWhere((x) => x.value == i); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /lib/src/match_faces/match_faces_request.dart: -------------------------------------------------------------------------------- 1 | part of "../../flutter_face_api.dart"; 2 | 3 | /// Compares two or more images with faces on them to find out the similarity of pairs. 4 | /// The request is used as a parameter to [FaceSDK.matchFaces]. 5 | class MatchFacesRequest { 6 | /// Images with faces to match. 7 | List get images => _images; 8 | List _images; 9 | 10 | /// If set the uploaded image is processed according to the indicated settings. 11 | OutputImageParams? get outputImageParams => _outputImageParams; 12 | OutputImageParams? _outputImageParams; 13 | 14 | /// Defines tag that can be used in match faces processing. Defaults to `null`. 15 | String? get tag => _tag; 16 | String? _tag; 17 | 18 | Map? get metadata => _metadata; 19 | Map? _metadata; 20 | 21 | MatchFacesRequest( 22 | List images, { 23 | OutputImageParams? outputImageParams, 24 | String? tag, 25 | Map? metadata, 26 | }) : _images = images, 27 | _outputImageParams = outputImageParams, 28 | _tag = tag, 29 | _metadata = metadata; 30 | 31 | @visibleForTesting 32 | static MatchFacesRequest? fromJson(jsonObject) { 33 | if (jsonObject == null) return null; 34 | 35 | List images = []; 36 | for (var item in jsonObject["images"]) { 37 | images.add(MatchFacesImage.fromJson(item)!); 38 | } 39 | 40 | return MatchFacesRequest(images, 41 | outputImageParams: 42 | OutputImageParams.fromJson(jsonObject["outputImageParams"]), 43 | tag: jsonObject["tag"], 44 | metadata: jsonObject["metadata"]); 45 | } 46 | 47 | @visibleForTesting 48 | Map toJson() => { 49 | "images": images.map((e) => e.toJson()).toList(), 50 | "outputImageParams": outputImageParams?.toJson(), 51 | "tag": tag, 52 | "metadata": metadata, 53 | }.clearNulls(); 54 | } 55 | -------------------------------------------------------------------------------- /lib/src/match_faces/compared_faces_pair.dart: -------------------------------------------------------------------------------- 1 | part of "../../flutter_face_api.dart"; 2 | 3 | /// Represents a result of the [ComparedFace] attempt to compare input images. 4 | class ComparedFacesPair { 5 | /// The first face in the comparison pair. 6 | ComparedFace get first => _first; 7 | late ComparedFace _first; 8 | 9 | /// The second face in the comparison pair. 10 | ComparedFace get second => _second; 11 | late ComparedFace _second; 12 | 13 | /// Similarity of the faces pair. Floating point value from 0 to 1. 14 | double get similarity => _similarity; 15 | late double _similarity; 16 | 17 | /// The raw value returned by the service without applying any thresholds or comparison rules. 18 | /// The value shows the degree of similarity of compared faces, the lower - the more similar, and vice versa less similar. 19 | /// The `score` is used in conjunction with the input image `imageType` to evaluate `similarity`. 20 | double? get score => _score; 21 | double? _score; 22 | 23 | /// The error describes a failed pair comparison and contains [MatchFacesErrorCode] codes. 24 | MatchFacesException? get error => _error; 25 | MatchFacesException? _error; 26 | 27 | ComparedFacesPair._privateConstructor(); 28 | 29 | @visibleForTesting 30 | static ComparedFacesPair? fromJson(jsonObject) { 31 | if (jsonObject == null) return null; 32 | var result = ComparedFacesPair._privateConstructor(); 33 | 34 | result._first = ComparedFace.fromJson(jsonObject["first"])!; 35 | result._second = ComparedFace.fromJson(jsonObject["second"])!; 36 | result._similarity = _toDouble(jsonObject["similarity"])!; 37 | result._score = _toDouble(jsonObject["score"]); 38 | result._error = MatchFacesException.fromJson(jsonObject["error"]); 39 | 40 | return result; 41 | } 42 | 43 | @visibleForTesting 44 | Map toJson() => { 45 | "first": first.toJson(), 46 | "second": second.toJson(), 47 | "similarity": similarity, 48 | "score": score, 49 | "error": error?.toJson(), 50 | }.clearNulls(); 51 | } 52 | -------------------------------------------------------------------------------- /lib/src/liveness/liveness_backend_exception.dart: -------------------------------------------------------------------------------- 1 | part of "../../flutter_face_api.dart"; 2 | 3 | class LivenessBackendException { 4 | LivenessBackendErrorCode get code => _code; 5 | late LivenessBackendErrorCode _code; 6 | 7 | String get message => _message; 8 | late String _message; 9 | 10 | LivenessBackendException._privateConstructor(); 11 | 12 | @visibleForTesting 13 | static LivenessBackendException? fromJson(jsonObject) { 14 | if (jsonObject == null) return null; 15 | var result = LivenessBackendException._privateConstructor(); 16 | 17 | result._code = LivenessBackendErrorCode.getByValue(jsonObject["code"])!; 18 | result._message = jsonObject["message"] ?? ""; 19 | 20 | return result; 21 | } 22 | 23 | @visibleForTesting 24 | Map toJson() => { 25 | "code": code.value, 26 | "message": message, 27 | }.clearNulls(); 28 | } 29 | 30 | enum LivenessBackendErrorCode { 31 | UNDEFINED(-1), 32 | NO_LICENSE(200), 33 | NOT_INITIALIZED(201), 34 | COMMAND_IS_NOT_SUPPORTED(202), 35 | PARAMS_READ_ERROR(203), 36 | LOW_QUALITY(231), 37 | TRACK_BREAK(246), 38 | CLOSED_EYES_DETECTED(230), 39 | HIGH_ASYMMETRY(232), 40 | FACE_OVER_EMOTIONAL(233), 41 | SUNGLASSES_DETECTED(234), 42 | SMALL_AGE(235), 43 | HEADDRESS_DETECTED(236), 44 | MEDICINE_MASK_DETECTED(239), 45 | OCCLUSION_DETECTED(240), 46 | FOREHEAD_GLASSES_DETECTED(242), 47 | MOUTH_OPENED(243), 48 | ART_MASK_DETECTED(244), 49 | NOT_MATCHED(237), 50 | IMAGES_COUNT_LIMIT_EXCEEDED(238), 51 | ELECTRONIC_DEVICE_DETECTED(245), 52 | WRONG_GEO(247), 53 | WRONG_OF(248), 54 | WRONG_VIEW(249), 55 | TIMEOUT_LIVENESS_TRANSACTION(250), 56 | FAILED_LIVENESS_TRANSACTION(251), 57 | ABORTED_LIVENESS_TRANSACTION(252), 58 | GENERAL_CHECK_FAIL(253), 59 | PASSIVE_LIVENESS_FAIL(254), 60 | PRINTED_FACE_DETECTED(255), 61 | BLOCKED_REQUEST(256), 62 | CORRUPTED_REQUEST(257); 63 | 64 | const LivenessBackendErrorCode(this.value); 65 | final int value; 66 | 67 | static LivenessBackendErrorCode? getByValue(int? i) { 68 | if (i == null) return null; 69 | return LivenessBackendErrorCode.values.firstWhere((x) => x.value == i); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /test/utils.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:io'; 3 | import 'package:flutter_test/flutter_test.dart'; 4 | import 'package:meta/meta.dart'; 5 | 6 | import 'nullable.dart'; 7 | 8 | Future writeJson(String name, Map contents) async { 9 | var file = File("test/json/" + name + ".json"); 10 | var jsonDir = Directory.fromUri(Uri(path: "test/json")); 11 | if (!await jsonDir.exists()) jsonDir.create(); 12 | await file.writeAsString(jsonEncode(contents)); 13 | } 14 | 15 | @isTest 16 | void compare( 17 | String name, 18 | Map json, 19 | T Function(Map) fromJson, 20 | ) { 21 | test(name, () async { 22 | var toJson = (input) => input.toJson(); 23 | var actual = toJson(fromJson(json)); 24 | expect(json, actual); 25 | await writeJson(name, json); 26 | 27 | var nullable = nullableMap[name]; 28 | bool invert = nullableMap[name + "!"] != null; 29 | if (invert) nullable = nullableMap[name + "!"]; 30 | if (nullable != null) { 31 | Map copy = {}; 32 | if (!invert) { 33 | copy = toJson(fromJson(json)); 34 | for (String item in nullable) { 35 | copy.remove(item); 36 | } 37 | } else { 38 | for (String item in nullable) { 39 | copy[item] = json[item]; 40 | } 41 | } 42 | var actual = toJson(fromJson(copy)); 43 | expect(copy, actual); 44 | writeJson(name + "Nullable", copy); 45 | } 46 | }); 47 | } 48 | 49 | @isTest 50 | void compareParams( 51 | String name, 52 | Map json, 53 | T Function(Map) fromJson, { 54 | List? skip, 55 | }) { 56 | test(name, () { 57 | var toJson = (input) => input.toJson(); 58 | var getTestSetters = (input) => input.testSetters; 59 | var object = fromJson(json); 60 | var actual = toJson(object); 61 | var testSetters = getTestSetters(object); 62 | if (skip != null) { 63 | for (String item in skip) { 64 | (actual as Map).remove(item); 65 | (testSetters as Map).remove(item); 66 | } 67 | } 68 | expect(json, actual); 69 | expect(json, testSetters); 70 | writeJson(name, json); 71 | }); 72 | } 73 | -------------------------------------------------------------------------------- /lib/src/liveness/liveness_response.dart: -------------------------------------------------------------------------------- 1 | part of "../../flutter_face_api.dart"; 2 | 3 | /// The response from the Liveness module. 4 | class LivenessResponse { 5 | /// The input image used to determine the liveness. 6 | Uint8List? get image => _image; 7 | Uint8List? _image; 8 | 9 | /// The status of the Liveness processing. 10 | LivenessStatus get liveness => _liveness; 11 | late LivenessStatus _liveness; 12 | 13 | String? get tag => _tag; 14 | String? _tag; 15 | 16 | String? get transactionId => _transactionId; 17 | String? _transactionId; 18 | 19 | int? get estimatedAge => _estimatedAge; 20 | int? _estimatedAge; 21 | 22 | /// The error describes a failed liveness check and contains [LivenessErrorCode] codes. 23 | LivenessException? get error => _error; 24 | LivenessException? _error; 25 | 26 | LivenessResponse._privateConstructor(); 27 | 28 | @visibleForTesting 29 | static LivenessResponse? fromJson(jsonObject) { 30 | if (jsonObject == null) return null; 31 | var result = new LivenessResponse._privateConstructor(); 32 | 33 | result._image = _bytesFromBase64(jsonObject["image"]); 34 | result._liveness = LivenessStatus.getByValue(jsonObject["liveness"])!; 35 | result._tag = jsonObject["tag"]; 36 | result._transactionId = jsonObject["transactionId"]; 37 | result._estimatedAge = jsonObject["estimatedAge"]; 38 | result._error = LivenessException.fromJson(jsonObject["error"]); 39 | 40 | return result; 41 | } 42 | 43 | @visibleForTesting 44 | Map toJson() => { 45 | "image": _bytesToBase64(image), 46 | "liveness": liveness.value, 47 | "tag": tag, 48 | "transactionId": transactionId, 49 | "estimatedAge": estimatedAge, 50 | "error": error?.toJson(), 51 | }.clearNulls(); 52 | } 53 | 54 | /// The status of the Liveness processing. 55 | enum LivenessStatus { 56 | /// The liveness check is passed successfully. 57 | PASSED(0), 58 | 59 | /// The liveness check result is unknown. 60 | UNKNOWN(1); 61 | 62 | const LivenessStatus(this.value); 63 | 64 | final int value; 65 | 66 | static LivenessStatus? getByValue(int? i) { 67 | if (i == null) return null; 68 | return LivenessStatus.values.firstWhere((x) => x.value == i); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /lib/src/detect_faces/detect_face_result.dart: -------------------------------------------------------------------------------- 1 | part of "../../flutter_face_api.dart"; 2 | 3 | class DetectFaceResult { 4 | List? get quality => _quality; 5 | List? _quality; 6 | 7 | List? get attributes => _attributes; 8 | List? _attributes; 9 | 10 | Uint8List? get crop => _crop; 11 | Uint8List? _crop; 12 | 13 | Rect? get faceRect => _faceRect; 14 | Rect? _faceRect; 15 | 16 | Rect? get originalRect => _originalRect; 17 | Rect? _originalRect; 18 | 19 | List? get landmarks => _landmarks; 20 | List? _landmarks; 21 | 22 | bool get isQualityCompliant => _isQualityCompliant; 23 | late bool _isQualityCompliant; 24 | 25 | DetectFaceResult._privateConstructor(); 26 | 27 | @visibleForTesting 28 | static DetectFaceResult? fromJson(jsonObject) { 29 | if (jsonObject == null) return null; 30 | var result = DetectFaceResult._privateConstructor(); 31 | 32 | if (jsonObject["quality"] != null) { 33 | result._quality = []; 34 | for (var item in jsonObject["quality"]) { 35 | result._quality!.add(ImageQualityResult.fromJson(item)!); 36 | } 37 | } 38 | if (jsonObject["attributes"] != null) { 39 | result._attributes = []; 40 | for (var item in jsonObject["attributes"]) { 41 | result._attributes!.add(DetectFacesAttributeResult.fromJson(item)!); 42 | } 43 | } 44 | result._crop = _bytesFromBase64(jsonObject["crop"]); 45 | if (jsonObject["landmarks"] != null) { 46 | result._landmarks = []; 47 | for (var item in jsonObject["landmarks"]) { 48 | result._landmarks!.add(Point.fromJson(item)!); 49 | } 50 | } 51 | result._faceRect = Rect.fromJson(jsonObject["faceRect"]); 52 | result._originalRect = Rect.fromJson(jsonObject["originalRect"]); 53 | result._isQualityCompliant = jsonObject["isQualityCompliant"]; 54 | 55 | return result; 56 | } 57 | 58 | @visibleForTesting 59 | Map toJson() => { 60 | "quality": quality?.map((e) => e.toJson()).toList(), 61 | "crop": _bytesToBase64(crop), 62 | "attributes": attributes?.map((e) => e.toJson()).toList(), 63 | "landmarks": landmarks?.map((e) => e.toJson()).toList(), 64 | "faceRect": faceRect?.toJson(), 65 | "originalRect": originalRect?.toJson(), 66 | "isQualityCompliant": isQualityCompliant, 67 | }.clearNulls(); 68 | } 69 | -------------------------------------------------------------------------------- /lib/src/detect_faces/detect_faces_config.dart: -------------------------------------------------------------------------------- 1 | part of "../../flutter_face_api.dart"; 2 | 3 | /// Custom configuration for [DetectFacesRequest]. 4 | class DetectFacesConfig { 5 | /// Current array for the face image detection attributes. 6 | List? attributes; 7 | 8 | /// Current array for the face image quality assessment rules. 9 | List? customQuality; 10 | 11 | /// If set the uploaded image is processed according to the indicated settings. 12 | OutputImageParams? outputImageParams; 13 | 14 | /// Whether to process only the central face on the image or all the faces. 15 | /// If set to `true`, the SDK detects and processes only one—the most central face in the image. 16 | /// If set to `false`, the SDK processes all faces in the image. 17 | /// Default is `false`. 18 | bool onlyCentralFace; 19 | 20 | DetectFacesConfig({ 21 | List? attributes, 22 | List? customQuality, 23 | OutputImageParams? outputImageParams, 24 | bool onlyCentralFace = false, 25 | }) : attributes = attributes, 26 | customQuality = customQuality, 27 | outputImageParams = outputImageParams, 28 | onlyCentralFace = onlyCentralFace; 29 | 30 | @visibleForTesting 31 | static DetectFacesConfig? fromJson(jsonObject) { 32 | if (jsonObject == null) return null; 33 | var result = DetectFacesConfig(); 34 | 35 | if (jsonObject["attributes"] != null) { 36 | result.attributes = []; 37 | for (var item in jsonObject["attributes"]) { 38 | result.attributes!.add(DetectFacesAttribute.getByValue(item)!); 39 | } 40 | } 41 | if (jsonObject["customQuality"] != null) { 42 | result.customQuality = []; 43 | for (var item in jsonObject["customQuality"]) { 44 | result.customQuality!.add(ImageQualityCharacteristic.fromJson(item)!); 45 | } 46 | } 47 | result.outputImageParams = 48 | OutputImageParams.fromJson(jsonObject["outputImageParams"]); 49 | result.onlyCentralFace = jsonObject["onlyCentralFace"]; 50 | 51 | return result; 52 | } 53 | 54 | @visibleForTesting 55 | Map toJson() => { 56 | "attributes": attributes?.map((e) => e.value).toList(), 57 | "customQuality": customQuality?.map((e) => e.toJson()).toList(), 58 | "outputImageParams": outputImageParams?.toJson(), 59 | "onlyCentralFace": onlyCentralFace, 60 | }.clearNulls(); 61 | } 62 | -------------------------------------------------------------------------------- /.gitlab/report.yaml: -------------------------------------------------------------------------------- 1 | spec: 2 | inputs: 3 | stage: 4 | default: reports 5 | image: 6 | default: ubuntu:22.04 7 | ignore_vulnerabilities: 8 | default: "false" 9 | sast_report_file: 10 | default: gl-sast-report.json 11 | secret_detection_report_file: 12 | default: gl-secret-detection-report.json 13 | dependency_scanning_report_file: 14 | default: gl-dependency-scanning-report.json 15 | --- 16 | 17 | view reports: 18 | image: $[[ inputs.image ]] 19 | stage: $[[ inputs.stage ]] 20 | variables: 21 | IGNORE_VULNERABILITIES: $[[ inputs.ignore_vulnerabilities ]] 22 | SAST_REPORT_FILE: $[[ inputs.sast_report_file ]] 23 | SECRET_DETECTION_REPORT_FILE: $[[ inputs.secret_detection_report_file ]] 24 | DEPENDENCY_SCANNING_REPORT_FILE: $[[ inputs.dependency_scanning_report_file ]] 25 | RED: \033[0;31m 26 | YELLOW: \033[1;33m 27 | GREEN: \033[0;32m 28 | NC: \033[0m 29 | needs: 30 | - job: semgrep-sast 31 | artifacts: true 32 | - job: secret_detection 33 | artifacts: true 34 | - job: gemnasium-dependency_scanning 35 | artifacts: true 36 | rules: 37 | - if: $CI_PIPELINE_SOURCE == "merge_request_event" 38 | script: 39 | - ls -la 40 | - apt update && apt install -y jq 41 | - | 42 | for f in SAST_REPORT_FILE SECRET_DETECTION_REPORT_FILE DEPENDENCY_SCANNING_REPORT_FILE 43 | do 44 | if [[ -f "${!f}" ]] 45 | then 46 | echo -e "${GREEN}File ${!f} exists, adding report${NC}" 47 | r=${f/%_FILE} 48 | declare $r="$(cat ${!f} | jq 'select(.vulnerabilities != []).vulnerabilities')" 49 | if [[ ! -z "${!r}" ]] 50 | then 51 | VULNERABILITIES_FOUND=true 52 | echo -e "${RED}Found $r vulnerabilities${NC}" 53 | echo "${!r}" 54 | echo 55 | fi 56 | else 57 | echo -e "${YELLOW}File ${!f} doesn't exist, skipping${NC}" 58 | fi 59 | done 60 | 61 | if [[ "$VULNERABILITIES_FOUND" == true ]] 62 | then 63 | if [[ "$IGNORE_VULNERABILITIES" == true ]] 64 | then 65 | echo -e "${GREEN}All found vulnerabilities were ignored due to IGNORE_VULNERABILITIES=true${NC}" 66 | exit 0 67 | else 68 | echo -e "${RED}Vulnerabilities found, please see reports above${NC}" 69 | exit 1 70 | fi 71 | fi 72 | 73 | echo -e "${GREEN}No vulnerabilities found${NC}" 74 | 75 | exit 0 76 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /android/src/main/kotlin/com/regula/plugin/facesdk/FlutterFaceApiPlugin.kt: -------------------------------------------------------------------------------- 1 | package com.regula.plugin.facesdk 2 | 3 | import android.content.Context 4 | import android.os.Handler 5 | import android.os.Looper 6 | import android.util.Log 7 | import io.flutter.embedding.engine.plugins.FlutterPlugin 8 | import io.flutter.embedding.engine.plugins.FlutterPlugin.FlutterPluginBinding 9 | import io.flutter.plugin.common.EventChannel 10 | import io.flutter.plugin.common.EventChannel.EventSink 11 | import io.flutter.plugin.common.EventChannel.StreamHandler 12 | import io.flutter.plugin.common.MethodCall 13 | import io.flutter.plugin.common.MethodChannel 14 | import io.flutter.plugin.common.MethodChannel.MethodCallHandler 15 | 16 | const val channelID = "flutter_face_api" 17 | val eventSinks = mutableMapOf() 18 | 19 | lateinit var args: List 20 | lateinit var binding: FlutterPluginBinding 21 | val context: Context 22 | get() = binding.applicationContext 23 | 24 | fun sendEvent(id: String, data: Any? = "") { 25 | eventSinks[id]?.let { Handler(Looper.getMainLooper()).post { it.success(data.toSendable()) } } 26 | } 27 | 28 | inline fun argsNullable(index: Int) = when (val v = args[index]) { 29 | null -> null 30 | is Map<*, *> -> v.toJson() as T 31 | is List<*> -> v.toJson() as T 32 | else -> v as T 33 | } 34 | 35 | fun setupEventChannel(id: String) = EventChannel(binding.binaryMessenger, "$channelID/event/$id").setStreamHandler(object : StreamHandler { 36 | override fun onListen(arguments: Any?, events: EventSink) = events.let { eventSinks[id] = it } 37 | override fun onCancel(arguments: Any?) = Unit 38 | }) 39 | 40 | class FlutterFaceApiPlugin : FlutterPlugin, MethodCallHandler { 41 | override fun onDetachedFromEngine(binding: FlutterPluginBinding) = Unit 42 | override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { 43 | args = call.arguments as List<*> 44 | try { 45 | methodCall(call.method) { data -> result.success(data.toSendable()) } 46 | } catch (error: Exception) { 47 | Log.e("REGULA", "Caught exception in \"${call.method}\" function:", error) 48 | } 49 | } 50 | 51 | override fun onAttachedToEngine(flutterBinding: FlutterPluginBinding) { 52 | binding = flutterBinding 53 | for (event in arrayOf( 54 | cameraSwitchEvent, 55 | livenessNotificationEvent, 56 | videoEncoderCompletionEvent, 57 | onCustomButtonTappedEvent 58 | )) setupEventChannel(event) 59 | MethodChannel(binding.binaryMessenger, "$channelID/method").setMethodCallHandler(this) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /lib/src/match_faces/match_faces_detection_face.dart: -------------------------------------------------------------------------------- 1 | part of "../../flutter_face_api.dart"; 2 | 3 | /// Represents face detection information as a part of [MatchFacesResponse]. 4 | class MatchFacesDetectionFace { 5 | /// The index of the face detection object in the array of detections. 6 | int get faceIndex => _faceIndex; 7 | late int _faceIndex; 8 | 9 | /// Main coordinates of the detected face (eyes, nose, lips, ears and etc.). 10 | List get landmarks => _landmarks; 11 | late List _landmarks; 12 | 13 | /// Rectangular area of the detected face in the original image. 14 | Rect get faceRect => _faceRect; 15 | late Rect _faceRect; 16 | 17 | /// Rotation is measured counterclockwise in degrees, with zero indicating 18 | /// that a line drawn between the eyes is horizontal relative to the image orientation. 19 | double? get rotationAngle => _rotationAngle; 20 | double? _rotationAngle; 21 | 22 | /// Coordinates of the rectangle with the face on the original image prepared for the face crop. 23 | /// Requires [OutputImageCrop.returnOriginalRect] is set. 24 | /// Returns `null` if [OutputImageCrop.returnOriginalRect] isn't set. 25 | Rect? get originalRect => _originalRect; 26 | Rect? _originalRect; 27 | 28 | /// Base64 image of the aligned and cropped portrait. 29 | /// Returned if [MatchFacesRequest.outputImageParams] is set or predefined scenario is used. 30 | Uint8List? get crop => _crop; 31 | Uint8List? _crop; 32 | 33 | MatchFacesDetectionFace._privateConstructor(); 34 | 35 | @visibleForTesting 36 | static MatchFacesDetectionFace? fromJson(jsonObject) { 37 | if (jsonObject == null) return null; 38 | var result = MatchFacesDetectionFace._privateConstructor(); 39 | 40 | List landmarks = []; 41 | for (var item in jsonObject["landmarks"]) { 42 | landmarks.add(Point.fromJson(item)!); 43 | } 44 | 45 | result._faceIndex = jsonObject["faceIndex"]; 46 | result._landmarks = landmarks; 47 | result._faceRect = Rect.fromJson(jsonObject["faceRect"])!; 48 | result._rotationAngle = _toDouble(jsonObject["rotationAngle"]); 49 | result._originalRect = Rect.fromJson(jsonObject["originalRect"]); 50 | result._crop = _bytesFromBase64(jsonObject["crop"]); 51 | 52 | return result; 53 | } 54 | 55 | @visibleForTesting 56 | Map toJson() => { 57 | "faceIndex": faceIndex, 58 | "landmarks": landmarks.map((e) => e.toJson()).toList(), 59 | "faceRect": faceRect.toJson(), 60 | "rotationAngle": rotationAngle, 61 | "originalRect": originalRect?.toJson(), 62 | "crop": _bytesToBase64(crop), 63 | }.clearNulls(); 64 | } 65 | -------------------------------------------------------------------------------- /lib/src/image_params/output_image_crop.dart: -------------------------------------------------------------------------------- 1 | part of "../../flutter_face_api.dart"; 2 | 3 | /// Crop settings for [OutputImageParams]. 4 | class OutputImageCrop { 5 | /// The aspect ratio according to which alignment is performed. 6 | OutputImageCropAspectRatio get type => _type; 7 | OutputImageCropAspectRatio _type; 8 | 9 | /// The resize value to process. 10 | /// If the value doesn't match AspectRatio `type` proportion or minimum size, an adjustment is applied. 11 | /// Use [OutputImageCropAspectRatio] to check you size matches AspectRatio `type` proportions and minimum size. 12 | Size? get size => _size; 13 | Size? _size; 14 | 15 | /// When an image is aligned by `type`, its original size may be insufficient, and in this case it needs to be supplemented, "padded". 16 | /// padColor sets the value for the color that will be used for such a supplement. 17 | Color? get padColor => _padColor; 18 | Color? _padColor; 19 | 20 | /// If set, the coordinates of the rectangle with the face in the original image prepared for the face crop 21 | /// are returned in the [DetectFaceResult.originalRect] field. 22 | /// Default is `false`. 23 | bool get returnOriginalRect => _returnOriginalRect; 24 | bool _returnOriginalRect; 25 | 26 | OutputImageCrop( 27 | OutputImageCropAspectRatio type, { 28 | Size? size, 29 | Color? padColor, 30 | bool? returnOriginalRect, 31 | }) : _type = type, 32 | _size = size, 33 | _padColor = padColor, 34 | _returnOriginalRect = returnOriginalRect ?? false; 35 | 36 | @visibleForTesting 37 | static OutputImageCrop? fromJson(jsonObject) { 38 | if (jsonObject == null) return null; 39 | return OutputImageCrop( 40 | OutputImageCropAspectRatio.getByValue(jsonObject["type"])!, 41 | size: Size.fromJson(jsonObject["size"]), 42 | padColor: _intToColor(jsonObject["padColor"]), 43 | returnOriginalRect: jsonObject["returnOriginalRect"]); 44 | } 45 | 46 | @visibleForTesting 47 | Map toJson() => { 48 | "type": type.value, 49 | "size": size?.toJson(), 50 | "padColor": _intFromColor(padColor), 51 | "returnOriginalRect": returnOriginalRect, 52 | }.clearNulls(); 53 | } 54 | 55 | /// The AspectRatio according to which alignment is performed 56 | enum OutputImageCropAspectRatio { 57 | RATIO_3X4(0), 58 | RATIO_4X5(1), 59 | RATIO_2X3(2), 60 | RATIO_1X1(3), 61 | RATIO_7X9(4); 62 | 63 | const OutputImageCropAspectRatio(this.value); 64 | final int value; 65 | 66 | static OutputImageCropAspectRatio? getByValue(int? i) { 67 | if (i == null) return null; 68 | return OutputImageCropAspectRatio.values.firstWhere((x) => x.value == i); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /lib/src/person_database/search_person_request.dart: -------------------------------------------------------------------------------- 1 | part of "../../flutter_face_api.dart"; 2 | 3 | /// Request object that configures Search settings. 4 | class SearchPersonRequest { 5 | ImageUpload _imageUpload; 6 | List? _groupIdsForSearch; 7 | double? _threshold; 8 | int? _limit; 9 | String? _tag; 10 | bool _detectAll; 11 | OutputImageParams? _outputImageParams; 12 | 13 | /// Create [SearchPersonRequest] object. 14 | /// 15 | /// `image` - Image Upload object to apply search with. 16 | /// 17 | /// `groupIdsForSearch` - The Group IDs of the groups in which the search is performed. 18 | /// 19 | /// `threshold` - The similarity distance threshold, should be between 0.0 and 2.0, 20 | /// where 0.0 is for returning results for only the most similar persons and 2.0 21 | /// is for all the persons, even the dissimilar ones. 22 | /// Default: 1. 23 | /// 24 | /// `limit` - The number of returned Persons limit. 25 | /// Default: 100. 26 | /// 27 | /// `tag` - Defines tag that can be used in search request. 28 | /// Default: null. 29 | /// 30 | /// `detectAll` - Whether to process only the one face on the image or all the faces. 31 | /// Default: `false`. 32 | /// 33 | /// `outputImageParams` - If set the uploaded image is processed according to the indicated settings. 34 | SearchPersonRequest( 35 | ImageUpload image, { 36 | List? groupIdsForSearch, 37 | double? threshold, 38 | int? limit, 39 | String? tag, 40 | bool detectAll = false, 41 | OutputImageParams? outputImageParams, 42 | }) : _imageUpload = image, 43 | _groupIdsForSearch = groupIdsForSearch, 44 | _threshold = threshold, 45 | _limit = limit, 46 | _tag = tag, 47 | _detectAll = detectAll, 48 | _outputImageParams = outputImageParams; 49 | 50 | @visibleForTesting 51 | static SearchPersonRequest? fromJson(jsonObject) { 52 | if (jsonObject == null) return null; 53 | return SearchPersonRequest( 54 | ImageUpload.fromJson(jsonObject["imageUpload"])!, 55 | groupIdsForSearch: _stringListFrom(jsonObject["groupIdsForSearch"]), 56 | threshold: _toDouble(jsonObject["threshold"]), 57 | limit: jsonObject["limit"], 58 | tag: jsonObject["tag"], 59 | detectAll: jsonObject["detectAll"], 60 | outputImageParams: 61 | OutputImageParams.fromJson(jsonObject["outputImageParams"]), 62 | ); 63 | } 64 | 65 | @visibleForTesting 66 | Map toJson() => { 67 | "imageUpload": _imageUpload.toJson(), 68 | "groupIdsForSearch": _groupIdsForSearch, 69 | "threshold": _threshold, 70 | "limit": _limit, 71 | "tag": _tag, 72 | "detectAll": _detectAll, 73 | "outputImageParams": _outputImageParams?.toJson(), 74 | }.clearNulls(); 75 | } 76 | -------------------------------------------------------------------------------- /lib/src/customization/customization.dart: -------------------------------------------------------------------------------- 1 | part of "../../flutter_face_api.dart"; 2 | 3 | /// Params that relate to the camera view controller customization and etc. 4 | class Customization { 5 | CustomizationColors get colors => _colors; 6 | CustomizationColors _colors = CustomizationColors(); 7 | set colors(CustomizationColors val) { 8 | (_colors = val)._apply(this); 9 | } 10 | 11 | CustomizationFonts get fonts => _fonts; 12 | CustomizationFonts _fonts = CustomizationFonts(); 13 | set fonts(CustomizationFonts val) { 14 | (_fonts = val)._apply(this); 15 | } 16 | 17 | CustomizationImages get images => _images; 18 | CustomizationImages _images = CustomizationImages(); 19 | set images(CustomizationImages val) { 20 | (_images = val)._apply(this); 21 | } 22 | 23 | /// Unmodifiable property. Use setter instead of `.remove()`, `.addAll()`, etc. 24 | Map? get uiCustomizationLayer => _uiCustomizationLayer; 25 | Map? _uiCustomizationLayer; 26 | set uiCustomizationLayer(Map? val) { 27 | if (val != null) val = Map.unmodifiable(val); 28 | _uiCustomizationLayer = val; 29 | _set({"uiCustomizationLayer": val}); 30 | } 31 | 32 | /// Set onClick listener for buttons from [uiCustomizationLayer]. 33 | set onCustomButtonTapped(CustomButtonTappedCompletion completion) => 34 | _setCustomButtonTappedCompletion(completion); 35 | 36 | /// Allows you to deserialize object. 37 | static Customization fromJson(jsonObject) { 38 | var result = Customization(); 39 | result.testSetters = {}; 40 | 41 | result.colors = CustomizationColors.fromJson(jsonObject["colors"]); 42 | result.fonts = CustomizationFonts.fromJson(jsonObject["fonts"]); 43 | result.images = CustomizationImages.fromJson(jsonObject["images"]); 44 | result._uiCustomizationLayer = jsonObject["uiCustomizationLayer"]; 45 | 46 | return result; 47 | } 48 | 49 | /// Allows you to serialize object. 50 | Map toJson() => { 51 | "colors": colors.toJson(), 52 | "fonts": fonts.toJson(), 53 | "images": images.toJson(), 54 | "uiCustomizationLayer": uiCustomizationLayer 55 | }.clearNulls(); 56 | 57 | void _set(Map json) { 58 | if (identical(this, FaceSDK.instance.customization)) { 59 | _bridge.invokeMethod("setCustomization", [json]); 60 | } 61 | testSetters.addAll(json); 62 | } 63 | 64 | void _apply() => _set(toJson()); 65 | 66 | @visibleForTesting 67 | Map testSetters = {}; 68 | } 69 | 70 | /// Callback for receiving signal, when a custom button, 71 | /// configured in [Customization.uiCustomizationLayer], is pressed. 72 | /// 73 | /// [tag] button id, indication which button was pressed. 74 | typedef CustomButtonTappedCompletion = void Function(int tag); 75 | -------------------------------------------------------------------------------- /lib/src/image_quality/image_quality_characteristic_name.dart: -------------------------------------------------------------------------------- 1 | part of "../../flutter_face_api.dart"; 2 | 3 | /// Image Quality Characteristic Name. 4 | enum ImageQualityCharacteristicName { 5 | IMAGE_WIDTH("ImageWidth"), 6 | IMAGE_HEIGHT("ImageHeight"), 7 | IMAGE_WIDTH_TO_HEIGHT("ImageWidthToHeight"), 8 | IMAGE_CHANNELS_NUMBER("ImageChannelsNumber"), 9 | ART_FACE("ArtFace"), 10 | PADDING_RATIO("PaddingRatio"), 11 | FACE_MID_POINT_HORIZONTAL_POSITION("FaceMidPointHorizontalPosition"), 12 | FACE_MID_POINT_VERTICAL_POSITION("FaceMidPointVerticalPosition"), 13 | HEAD_WIDTH_RATIO("HeadWidthRatio"), 14 | HEAD_HEIGHT_RATIO("HeadHeightRatio"), 15 | EYES_DISTANCE("EyesDistance"), 16 | YAW("Yaw"), 17 | PITCH("Pitch"), 18 | ROLL("Roll"), 19 | BLUR_LEVEL("BlurLevel"), 20 | NOISE_LEVEL("NoiseLevel"), 21 | UNNATURAL_SKIN_TONE("UnnaturalSkinTone"), 22 | FACE_DYNAMIC_RANGE("FaceDynamicRange"), 23 | EYE_RIGHT_CLOSED("EyeRightClosed"), 24 | EYE_LEFT_CLOSED("EyeLeftClosed"), 25 | EYE_RIGHT_OCCLUDED("EyeRightOccluded"), 26 | EYE_LEFT_OCCLUDED("EyeLeftOccluded"), 27 | EYES_RED("EyesRed"), 28 | EYE_RIGHT_COVERED_WITH_HAIR("EyeRightCoveredWithHair"), 29 | EYE_LEFT_COVERED_WITH_HAIR("EyeLeftCoveredWithHair"), 30 | OFF_GAZE("OffGaze"), 31 | TOO_DARK("TooDark"), 32 | TOO_LIGHT("TooLight"), 33 | FACE_GLARE("FaceGlare"), 34 | SHADOWS_ON_FACE("ShadowsOnFace"), 35 | SHOULDERS_POSE("ShouldersPose"), 36 | EXPRESSION_LEVEL("ExpressionLevel"), 37 | MOUTH_OPEN("MouthOpen"), 38 | SMILE("Smile"), 39 | DARK_GLASSES("DarkGlasses"), 40 | REFLECTION_ON_GLASSES("ReflectionOnGlasses"), 41 | FRAMES_TOO_HEAVY("FramesTooHeavy"), 42 | FACE_OCCLUDED("FaceOccluded"), 43 | HEAD_COVERING("HeadCovering"), 44 | FOREHEAD_COVERING("ForeheadCovering"), 45 | STRONG_MAKEUP("StrongMakeup"), 46 | HEAD_PHONES("Headphones"), 47 | MEDICAL_MASK("MedicalMask"), 48 | BACKGROUND_UNIFORMITY("BackgroundUniformity"), 49 | SHADOWS_ON_BACKGROUND("ShadowsOnBackground"), 50 | OTHER_FACES("OtherFaces"), 51 | BACKGROUND_COLOR_MATCH("BackgroundColorMatch"), 52 | UNKNOWN("Unknown"), 53 | IMAGE_CHARACTERISTIC_ALL_RECOMMENDED("ImageCharacteristic"), 54 | HEAD_SIZE_AND_POSITION_ALL_RECOMMENDED("HeadSizeAndPosition"), 55 | FACE_IMAGE_QUALITY_ALL_RECOMMENDED("FaceImageQuality"), 56 | EYES_CHARACTERISTICS_ALL_RECOMMENDED("EyesCharacteristics"), 57 | SHADOW_AND_LIGHTING_ALL_RECOMMENDED("ShadowsAndLightning"), 58 | POSE_AND_EXPRESSION_ALL_RECOMMENDED("PoseAndExpression"), 59 | HEAD_OCCLUSION_ALL_RECOMMENDED("HeadOcclusion"), 60 | QUALITY_BACKGROUND_ALL_RECOMMENDED("QualityBackground"); 61 | 62 | const ImageQualityCharacteristicName(this.value); 63 | final String value; 64 | 65 | static ImageQualityCharacteristicName? getByValue(String? i) { 66 | if (i == null) return null; 67 | return ImageQualityCharacteristicName.values 68 | .firstWhere((x) => x.value == i); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /lib/src/image_quality/image_quality_result.dart: -------------------------------------------------------------------------------- 1 | part of "../../flutter_face_api.dart"; 2 | 3 | /// Quality assessment result. 4 | class ImageQualityResult { 5 | /// Image quality characteristic group. 6 | ImageQualityGroupName get group => _group; 7 | late ImageQualityGroupName _group; 8 | 9 | /// The name of the characteristic. 10 | ImageQualityCharacteristicName get name => _name; 11 | late ImageQualityCharacteristicName _name; 12 | 13 | /// The assessment status of the characteristic. 14 | ImageQualityResultStatus get status => _status; 15 | late ImageQualityResultStatus _status; 16 | 17 | /// The assessed value for the characteristic. 18 | double get value => _value; 19 | late double _value; 20 | 21 | /// The range of set values for this characteristic. 22 | ImageQualityRange get range => _range; 23 | late ImageQualityRange _range; 24 | 25 | ImageQualityResult._privateConstructor(); 26 | 27 | @visibleForTesting 28 | static ImageQualityResult? fromJson(jsonObject) { 29 | if (jsonObject == null) return null; 30 | var result = ImageQualityResult._privateConstructor(); 31 | 32 | result._group = ImageQualityGroupName.getByValue(jsonObject["group"])!; 33 | result._name = 34 | ImageQualityCharacteristicName.getByValue(jsonObject["name"])!; 35 | result._status = ImageQualityResultStatus.getByValue(jsonObject["status"])!; 36 | result._value = _toDouble(jsonObject["value"])!; 37 | result._range = ImageQualityRange.fromJson(jsonObject["range"])!; 38 | 39 | return result; 40 | } 41 | 42 | @visibleForTesting 43 | Map toJson() => { 44 | "group": group.value, 45 | "name": name.value, 46 | "status": status.value, 47 | "value": value, 48 | "range": range.toJson(), 49 | }.clearNulls(); 50 | } 51 | 52 | enum ImageQualityGroupName { 53 | IMAGE_CHARACTERISTICS(1), 54 | HEAD_SIZE_AND_POSITION(2), 55 | FACE_QUALITY(3), 56 | EYES_CHARACTERISTICS(4), 57 | SHADOWS_AND_LIGHTNING(5), 58 | POSE_AND_EXPRESSION(6), 59 | HEAD_OCCLUSION(7), 60 | BACKGROUND(8), 61 | UNKNOWN(9); 62 | 63 | const ImageQualityGroupName(this.value); 64 | final int value; 65 | 66 | static ImageQualityGroupName? getByValue(int? i) { 67 | if (i == null) return null; 68 | return ImageQualityGroupName.values.firstWhere((x) => x.value == i); 69 | } 70 | } 71 | 72 | /// The assessment status of Image Quality Characteristic 73 | enum ImageQualityResultStatus { 74 | /// The characteristic is defined but is out of the range of set values. 75 | FALSE(0), 76 | 77 | /// The characteristic is defined and fits the range of set values. 78 | TRUE(1), 79 | 80 | /// The characteristic is not defined. 81 | UNDETERMINED(2); 82 | 83 | const ImageQualityResultStatus(this.value); 84 | final int value; 85 | 86 | static ImageQualityResultStatus? getByValue(int? i) { 87 | if (i == null) return null; 88 | return ImageQualityResultStatus.values.firstWhere((x) => x.value == i); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /example/ios/Tests/Utils.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | func compare(name: String, 4 | fromJson: ([String: Any]) -> T, 5 | generate: (T) -> [String: Any], 6 | omit: [String] = [] 7 | ) { 8 | compareSingle(name: name, fromJson: fromJson, generate: generate, omit: omit) 9 | compareSingle(name: name + "Nullable", fromJson: fromJson, generate: generate, omit: omit) 10 | } 11 | 12 | func compareSingle(name: String, 13 | fromJson: ([String: Any]) -> T, 14 | generate: (T) -> [String: Any], 15 | omit: [String] = [] 16 | ) { 17 | do { 18 | var expected = try readJSONFile(forName: name) 19 | for s in omit { 20 | expected = omitDeep(dict: expected, path: s.components(separatedBy: "."), index: 0) 21 | } 22 | var actual = generate(fromJson(expected)) 23 | for s in omit { 24 | actual = omitDeep(dict: actual, path: s.components(separatedBy: "."), index: 0) 25 | } 26 | actual = removeNulls(input: actual) 27 | XCTAssertEqual(expected as NSDictionary, actual as NSDictionary) 28 | } catch { } 29 | } 30 | 31 | func omitDeep(dict: [String: Any], path: [String], index: Int) -> [String: Any] { 32 | var mutableDict = dict 33 | if (index < path.count - 1) { 34 | if(dict[path[index]] == nil) { 35 | return dict // not found 36 | } 37 | let node = dict[path[index]] 38 | if (node is [String : Any]) { 39 | mutableDict[path[index]] = omitDeep(dict: node as! [String : Any], path: path, index: index + 1) 40 | } else if (node is Array) { 41 | mutableDict[path[index]] = omitDeep(arr: node as! Array, path: path, index: index + 1) 42 | } 43 | } else { 44 | mutableDict.removeValue(forKey: path[index]) 45 | } 46 | return mutableDict 47 | } 48 | 49 | func omitDeep(arr: Array, path: [String], index: Int) -> Array { 50 | var mutableArr = arr 51 | for (i, item) in arr.enumerated() { 52 | mutableArr[i] = omitDeep(dict: item as! [String : Any], path: path, index: index) 53 | } 54 | return mutableArr 55 | } 56 | 57 | func removeNulls(input: [String: Any]) -> [String: Any] { 58 | var result = input 59 | for (key, value) in input { 60 | if (value as! NSObject == NSNull()) { 61 | result.removeValue(forKey: key) 62 | } else if (value is [String: Any]) { 63 | result[key] = removeNulls(input: value as! [String: Any]) 64 | } 65 | } 66 | return result 67 | } 68 | 69 | func readJSONFile(forName name: String) throws -> [String: Any] { 70 | do { 71 | let path = Bundle(for: Tests.self).path(forResource: "json/" + name, ofType: "json") 72 | if(path == nil){ 73 | throw "file not found" 74 | } 75 | let data = try String(contentsOfFile: path!).data(using: .utf8)! 76 | return try JSONSerialization.jsonObject(with: data, options: .mutableLeaves) as! [String: Any] 77 | } catch { 78 | throw(error) 79 | } 80 | } 81 | 82 | extension String: Error {} 83 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /ios/Classes/FlutterFaceApiPlugin.m: -------------------------------------------------------------------------------- 1 | #import "FlutterFaceApiPlugin.h" 2 | 3 | @implementation FlutterFaceApiPlugin 4 | 5 | UIViewController*(^RFSWRootViewController)(void) = ^UIViewController*(){ 6 | for (UIWindow *window in UIApplication.sharedApplication.windows) 7 | if (window.isKeyWindow) 8 | return window.rootViewController; 9 | return nil; 10 | }; 11 | 12 | + (void)registerWithRegistrar:(NSObject*)registrar { 13 | eventSinks = [NSMutableDictionary new]; 14 | void(^setupEventChannel)(NSObject* registrar, NSString*eventId, NSObject*handler) = ^(NSObject* registrar, NSString*eventId, NSObject*handler) { 15 | [[FlutterEventChannel eventChannelWithName:[NSString stringWithFormat:@"%@%@", @"flutter_face_api/event/", eventId] binaryMessenger:[registrar messenger]] setStreamHandler:handler]; 16 | }; 17 | setupEventChannel(registrar, cameraSwitchEvent, [RFSWCameraSwitchStreamHandler new]); 18 | setupEventChannel(registrar, livenessNotificationEvent, [RFSWLivenessNotificationStreamHandler new]); 19 | setupEventChannel(registrar, videoEncoderCompletionEvent, [RFSWVideoEncoderCompletionStreamHandler new]); 20 | setupEventChannel(registrar, onCustomButtonTappedEvent, [RFSWOnCustomButtonTappedStreamHandler new]); 21 | 22 | FlutterMethodChannel* channel = [FlutterMethodChannel methodChannelWithName:@"flutter_face_api/method" binaryMessenger:[registrar messenger]]; 23 | [registrar addMethodCallDelegate:[FlutterFaceApiPlugin new] channel:channel]; 24 | } 25 | 26 | - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { 27 | RFSWEventSender sendEvent = ^(NSString* event, id data) { 28 | dispatch_async(dispatch_get_main_queue(), ^{ 29 | if (eventSinks[event] != nil) eventSinks[event]([RFSWJSONConstructor toSendable:data]); 30 | }); 31 | }; 32 | 33 | [RFSWMain methodCall:call.method 34 | :call.arguments 35 | :^(id data) { result([RFSWJSONConstructor toSendable:data]); } 36 | :sendEvent]; 37 | } 38 | 39 | @end 40 | 41 | 42 | @implementation RFSWCameraSwitchStreamHandler 43 | - (FlutterError*)onListenWithArguments:(id)arguments eventSink:(FlutterEventSink)eventSink { 44 | eventSinks[cameraSwitchEvent] = eventSink; 45 | return nil; 46 | } 47 | 48 | - (FlutterError*)onCancelWithArguments:(id)arguments { 49 | eventSinks[cameraSwitchEvent] = nil; 50 | return nil; 51 | } 52 | @end 53 | 54 | @implementation RFSWLivenessNotificationStreamHandler 55 | - (FlutterError*)onListenWithArguments:(id)arguments eventSink:(FlutterEventSink)eventSink { 56 | eventSinks[livenessNotificationEvent] = eventSink; 57 | return nil; 58 | } 59 | 60 | - (FlutterError*)onCancelWithArguments:(id)arguments { 61 | eventSinks[livenessNotificationEvent] = nil; 62 | return nil; 63 | } 64 | @end 65 | 66 | @implementation RFSWVideoEncoderCompletionStreamHandler 67 | - (FlutterError*)onListenWithArguments:(id)arguments eventSink:(FlutterEventSink)eventSink { 68 | eventSinks[videoEncoderCompletionEvent] = eventSink; 69 | return nil; 70 | } 71 | 72 | - (FlutterError*)onCancelWithArguments:(id)arguments { 73 | eventSinks[videoEncoderCompletionEvent] = nil; 74 | return nil; 75 | } 76 | @end 77 | 78 | @implementation RFSWOnCustomButtonTappedStreamHandler 79 | - (FlutterError*)onListenWithArguments:(id)arguments eventSink:(FlutterEventSink)eventSink { 80 | eventSinks[onCustomButtonTappedEvent] = eventSink; 81 | return nil; 82 | } 83 | 84 | - (FlutterError*)onCancelWithArguments:(id)arguments { 85 | eventSinks[onCustomButtonTappedEvent] = nil; 86 | return nil; 87 | } 88 | @end 89 | -------------------------------------------------------------------------------- /lib/src/face_capture/face_capture_config.dart: -------------------------------------------------------------------------------- 1 | part of "../../flutter_face_api.dart"; 2 | 3 | class FaceCaptureConfig { 4 | bool copyright; 5 | 6 | bool cameraSwitchEnabled; 7 | 8 | bool closeButtonEnabled; 9 | 10 | bool torchButtonEnabled; 11 | 12 | bool vibrateOnSteps; 13 | 14 | /// Enables 'remove occlusion' animation & hint. 15 | bool detectOcclusion; 16 | 17 | /// Enables global face hint animation. 18 | bool showFaceAnimation; 19 | 20 | /// Android only. 21 | int? cameraPositionAndroid; 22 | 23 | /// IOS only. 24 | CameraPosition cameraPositionIOS; 25 | 26 | /// Allows you to specify an orientation of the camera view controller. 27 | List screenOrientation; 28 | 29 | double? timeout; 30 | 31 | double? holdStillDuration; 32 | 33 | FaceCaptureConfig({ 34 | bool copyright = true, 35 | bool cameraSwitchEnabled = false, 36 | bool closeButtonEnabled = true, 37 | bool torchButtonEnabled = true, 38 | bool vibrateOnSteps = true, 39 | bool detectOcclusion = true, 40 | bool showFaceAnimation = true, 41 | int? cameraPositionAndroid, 42 | CameraPosition cameraPositionIOS = CameraPosition.FRONT, 43 | List screenOrientation = const [ 44 | ScreenOrientation.PORTRAIT 45 | ], 46 | double? timeout, 47 | double? holdStillDuration, 48 | }) : copyright = copyright, 49 | cameraSwitchEnabled = cameraSwitchEnabled, 50 | closeButtonEnabled = closeButtonEnabled, 51 | torchButtonEnabled = torchButtonEnabled, 52 | vibrateOnSteps = vibrateOnSteps, 53 | detectOcclusion = detectOcclusion, 54 | showFaceAnimation = showFaceAnimation, 55 | cameraPositionAndroid = cameraPositionAndroid, 56 | cameraPositionIOS = cameraPositionIOS, 57 | screenOrientation = screenOrientation, 58 | timeout = timeout, 59 | holdStillDuration = holdStillDuration; 60 | 61 | @visibleForTesting 62 | static FaceCaptureConfig? fromJson(jsonObject) { 63 | if (jsonObject == null) return null; 64 | var result = FaceCaptureConfig(); 65 | 66 | result.copyright = jsonObject["copyright"]; 67 | result.cameraSwitchEnabled = jsonObject["cameraSwitchEnabled"]; 68 | result.closeButtonEnabled = jsonObject["closeButtonEnabled"]; 69 | result.torchButtonEnabled = jsonObject["torchButtonEnabled"]; 70 | result.vibrateOnSteps = jsonObject["vibrateOnSteps"]; 71 | result.detectOcclusion = jsonObject["detectOcclusion"]; 72 | result.showFaceAnimation = jsonObject["showFaceAnimation"]; 73 | result.cameraPositionAndroid = jsonObject["cameraPositionAndroid"]; 74 | result.cameraPositionIOS = 75 | CameraPosition.getByValue(jsonObject["cameraPositionIOS"])!; 76 | result.screenOrientation = 77 | ScreenOrientation.fromIntList(jsonObject["screenOrientation"])!; 78 | result.timeout = _toDouble(jsonObject["timeout"]); 79 | result.holdStillDuration = _toDouble(jsonObject["holdStillDuration"]); 80 | 81 | return result; 82 | } 83 | 84 | @visibleForTesting 85 | Map toJson() => { 86 | "copyright": copyright, 87 | "cameraSwitchEnabled": cameraSwitchEnabled, 88 | "closeButtonEnabled": closeButtonEnabled, 89 | "torchButtonEnabled": torchButtonEnabled, 90 | "vibrateOnSteps": vibrateOnSteps, 91 | "detectOcclusion": detectOcclusion, 92 | "showFaceAnimation": showFaceAnimation, 93 | "cameraPositionAndroid": cameraPositionAndroid, 94 | "cameraPositionIOS": cameraPositionIOS.value, 95 | "screenOrientation": screenOrientation.map((e) => e.value).toList(), 96 | "timeout": timeout, 97 | "holdStillDuration": holdStillDuration, 98 | }.clearNulls(); 99 | } 100 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 43 | 49 | 50 | 51 | 52 | 53 | 63 | 65 | 71 | 72 | 73 | 74 | 80 | 82 | 88 | 89 | 90 | 91 | 93 | 94 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /lib/src/customization/customization_fonts.dart: -------------------------------------------------------------------------------- 1 | part of "../../flutter_face_api.dart"; 2 | 3 | class CustomizationFonts { 4 | Font? _onboardingScreenStartButton; 5 | set onboardingScreenStartButton(Font? val) { 6 | _onboardingScreenStartButton = val; 7 | _set({"100": val?.toJson()}); 8 | } 9 | 10 | Font? _onboardingScreenTitleLabel; 11 | set onboardingScreenTitleLabel(Font? val) { 12 | _onboardingScreenTitleLabel = val; 13 | _set({"101": val?.toJson()}); 14 | } 15 | 16 | Font? _onboardingScreenSubtitleLabel; 17 | set onboardingScreenSubtitleLabel(Font? val) { 18 | _onboardingScreenSubtitleLabel = val; 19 | _set({"102": val?.toJson()}); 20 | } 21 | 22 | Font? _onboardingScreenMessageLabels; 23 | set onboardingScreenMessageLabels(Font? val) { 24 | _onboardingScreenMessageLabels = val; 25 | _set({"103": val?.toJson()}); 26 | } 27 | 28 | Font? _cameraScreenHintLabel; 29 | set cameraScreenHintLabel(Font? val) { 30 | _cameraScreenHintLabel = val; 31 | _set({"200": val?.toJson()}); 32 | } 33 | 34 | Font? _retryScreenRetryButton; 35 | set retryScreenRetryButton(Font? val) { 36 | _retryScreenRetryButton = val; 37 | _set({"300": val?.toJson()}); 38 | } 39 | 40 | Font? _retryScreenTitleLabel; 41 | set retryScreenTitleLabel(Font? val) { 42 | _retryScreenTitleLabel = val; 43 | _set({"301": val?.toJson()}); 44 | } 45 | 46 | Font? _retryScreenSubtitleLabel; 47 | set retryScreenSubtitleLabel(Font? val) { 48 | _retryScreenSubtitleLabel = val; 49 | _set({"302": val?.toJson()}); 50 | } 51 | 52 | Font? _retryScreenHintLabels; 53 | set retryScreenHintLabels(Font? val) { 54 | _retryScreenHintLabels = val; 55 | _set({"303": val?.toJson()}); 56 | } 57 | 58 | Font? _processingScreenLabel; 59 | set processingScreenLabel(Font? val) { 60 | _processingScreenLabel = val; 61 | _set({"400": val?.toJson()}); 62 | } 63 | 64 | /// Allows you to deserialize object. 65 | static CustomizationFonts fromJson(jsonObject) { 66 | var result = CustomizationFonts(); 67 | result.testSetters = {}; 68 | 69 | result._onboardingScreenStartButton = Font.fromJson(jsonObject["100"]); 70 | result._onboardingScreenTitleLabel = Font.fromJson(jsonObject["101"]); 71 | result._onboardingScreenSubtitleLabel = Font.fromJson(jsonObject["102"]); 72 | result._onboardingScreenMessageLabels = Font.fromJson(jsonObject["103"]); 73 | result._cameraScreenHintLabel = Font.fromJson(jsonObject["200"]); 74 | result._retryScreenRetryButton = Font.fromJson(jsonObject["300"]); 75 | result._retryScreenTitleLabel = Font.fromJson(jsonObject["301"]); 76 | result._retryScreenSubtitleLabel = Font.fromJson(jsonObject["302"]); 77 | result._retryScreenHintLabels = Font.fromJson(jsonObject["303"]); 78 | result._processingScreenLabel = Font.fromJson(jsonObject["400"]); 79 | 80 | return result; 81 | } 82 | 83 | /// Allows you to serialize object. 84 | Map toJson() => { 85 | "100": _onboardingScreenStartButton?.toJson(), 86 | "101": _onboardingScreenTitleLabel?.toJson(), 87 | "102": _onboardingScreenSubtitleLabel?.toJson(), 88 | "103": _onboardingScreenMessageLabels?.toJson(), 89 | "200": _cameraScreenHintLabel?.toJson(), 90 | "300": _retryScreenRetryButton?.toJson(), 91 | "301": _retryScreenTitleLabel?.toJson(), 92 | "302": _retryScreenSubtitleLabel?.toJson(), 93 | "303": _retryScreenHintLabels?.toJson(), 94 | "400": _processingScreenLabel?.toJson(), 95 | }.clearNulls(); 96 | 97 | void _set(Map json, {Customization? directParent}) { 98 | var parentJson = {"fonts": json}; 99 | var parent = FaceSDK.instance.customization; 100 | if (identical(this, parent.fonts)) parent._set(parentJson); 101 | directParent?.testSetters.addAll(parentJson); 102 | testSetters.addAll(json); 103 | } 104 | 105 | void _apply(Customization parent) => _set(toJson(), directParent: parent); 106 | 107 | @visibleForTesting 108 | Map testSetters = {}; 109 | } 110 | -------------------------------------------------------------------------------- /lib/src/detect_faces/detect_faces_request.dart: -------------------------------------------------------------------------------- 1 | part of "../../flutter_face_api.dart"; 2 | 3 | /// Detect Faces Request. 4 | /// Could be created by predefined scenarios (e.g: [DetectFacesRequest.qualityICAO], [DetectFacesRequest.cropAllFaces] etc. ) 5 | /// or by using custom [DetectFacesConfig]. 6 | class DetectFacesRequest { 7 | String? _tag; 8 | DetectFacesScenario? _scenario; 9 | late Uint8List _image; 10 | DetectFacesConfig? _configuration; 11 | 12 | DetectFacesRequest._privateConstructor(); 13 | 14 | /// Create [DetectFacesRequest] object. 15 | /// 16 | /// `image` - Image base64. 17 | /// 18 | /// `config` - Custom Request configuration to specify image, quality, attributes parameters. 19 | /// 20 | /// `tag` - Defines tag that can be used in detect faces processing. Defaults to `null`. 21 | DetectFacesRequest( 22 | Uint8List image, 23 | DetectFacesConfig config, { 24 | String? tag, 25 | }) : _image = image, 26 | _configuration = config, 27 | _tag = tag; 28 | 29 | /// Creates a request to check all the available quality characteristics. 30 | /// 31 | /// [image] - Image base64. 32 | DetectFacesRequest.qualityFull(Uint8List image) 33 | : _image = image, 34 | _scenario = DetectFacesScenario.QUALITY_FULL; 35 | 36 | /// Creates a request to check the quality characteristics based on the ICAO standard. 37 | /// 38 | /// [image] - Image base64. 39 | DetectFacesRequest.qualityICAO(Uint8List image) 40 | : _image = image, 41 | _scenario = DetectFacesScenario.QUALITY_ICAO; 42 | 43 | /// Creates a request to check the quality characteristics based on the Schengen visa standard. 44 | /// 45 | /// [image] - Image base64. 46 | DetectFacesRequest.qualityVisaSchengen(Uint8List image) 47 | : _image = image, 48 | _scenario = DetectFacesScenario.QUALITY_VISA_SCHENGEN; 49 | 50 | /// Creates a request to check the quality characteristics based on the USA visa standard. 51 | /// 52 | /// [image] - Image base64. 53 | DetectFacesRequest.qualityVisaUSA(Uint8List image) 54 | : _image = image, 55 | _scenario = DetectFacesScenario.QUALITY_VISA_USA; 56 | 57 | /// Creates a request for a cropped portrait of the person whose face is the most central. 58 | /// 59 | /// [image] - Image base64. 60 | DetectFacesRequest.cropCentralFace(Uint8List image) 61 | : _image = image, 62 | _scenario = DetectFacesScenario.CROP_CENTRAL_FACE; 63 | 64 | /// Creates a request for cropped portraits of all the people in the image. 65 | /// 66 | /// [image] - Image base64. 67 | DetectFacesRequest.cropAllFaces(Uint8List image) 68 | : _image = image, 69 | _scenario = DetectFacesScenario.CROP_ALL_FACES; 70 | 71 | /// Creates a request for a cropped portrait of the person whose face is the most central in the image in the original size. 72 | /// 73 | /// [image] - Image base64. 74 | DetectFacesRequest.thumbnail(Uint8List image) 75 | : _image = image, 76 | _scenario = DetectFacesScenario.THUMBNAIL; 77 | 78 | /// Creates a request for all available attribute results. 79 | /// 80 | /// [image] - Image base64. 81 | DetectFacesRequest.allAttributes(Uint8List image) 82 | : _image = image, 83 | _scenario = DetectFacesScenario.ATTRIBUTES_ALL; 84 | 85 | @visibleForTesting 86 | static DetectFacesRequest? fromJson(jsonObject) { 87 | if (jsonObject == null) return null; 88 | var result = DetectFacesRequest._privateConstructor(); 89 | 90 | result._tag = jsonObject["tag"]; 91 | result._scenario = DetectFacesScenario.getByValue(jsonObject["scenario"]); 92 | result._image = _bytesFromBase64(jsonObject["image"])!; 93 | result._configuration = 94 | DetectFacesConfig.fromJson(jsonObject["configuration"]); 95 | 96 | return result; 97 | } 98 | 99 | @visibleForTesting 100 | Map toJson() => { 101 | "tag": _tag, 102 | "scenario": _scenario?.value, 103 | "image": _bytesToBase64(_image), 104 | "configuration": _configuration?.toJson(), 105 | }.clearNulls(); 106 | } 107 | -------------------------------------------------------------------------------- /test/face_api_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_face_api/flutter_face_api.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | 4 | import 'json.dart'; 5 | import 'utils.dart'; 6 | 7 | void main() { 8 | group("FaceSDK", () { 9 | compare('customization', customization, Customization.fromJson); 10 | 11 | compare('point', point, Point.fromJson); 12 | compare('rect', rect, Rect.fromJson); 13 | compare('size', size, Size.fromJson); 14 | compare('outputImageCrop', outputImageCrop, OutputImageCrop.fromJson); 15 | compare('outputImageParams', outputImageParams, OutputImageParams.fromJson); 16 | 17 | compare('imageQualityRange', imageQualityRange, ImageQualityRange.fromJson); 18 | compare( 19 | 'imageQualityResult', imageQualityResult, ImageQualityResult.fromJson); 20 | compare('imageQualityCharacteristic', imageQualityCharacteristic, 21 | ImageQualityCharacteristic.fromJson); 22 | 23 | compare('faceSDKVersion', faceSDKVersion, FaceSDKVersion.fromJson); 24 | compare('initConfig', initConfig, InitConfig.fromJson); 25 | compare('licenseException', licenseException, LicenseException.fromJson); 26 | compare('initException', initException, InitException.fromJson); 27 | 28 | compare('detectFacesAttributeResult', detectFacesAttributeResult, 29 | DetectFacesAttributeResult.fromJson); 30 | compare('detectFaceResult', detectFaceResult, DetectFaceResult.fromJson); 31 | compare('detectFacesConfig', detectFacesConfig, DetectFacesConfig.fromJson); 32 | compare( 33 | 'detectFacesRequest', detectFacesRequest, DetectFacesRequest.fromJson); 34 | compare('detectFacesBackendException', detectFacesBackendException, 35 | DetectFacesBackendException.fromJson); 36 | compare('detectFacesException', detectFacesException, 37 | DetectFacesException.fromJson); 38 | compare('detectFacesResponse', detectFacesResponse, 39 | DetectFacesResponse.fromJson); 40 | 41 | compare('faceCaptureConfig', faceCaptureConfig, FaceCaptureConfig.fromJson); 42 | compare('faceCaptureImage', faceCaptureImage, FaceCaptureImage.fromJson); 43 | compare('faceCaptureException', faceCaptureException, 44 | FaceCaptureException.fromJson); 45 | compare('faceCaptureResponse', faceCaptureResponse, 46 | FaceCaptureResponse.fromJson); 47 | 48 | compare('livenessConfig', livenessConfig, LivenessConfig.fromJson); 49 | compare('livenessBackendException', livenessBackendException, 50 | LivenessBackendException.fromJson); 51 | compare('livenessException', livenessException, LivenessException.fromJson); 52 | compare('livenessResponse', livenessResponse, LivenessResponse.fromJson); 53 | compare('livenessNotification', livenessNotification, 54 | LivenessNotification.fromJson); 55 | 56 | compare('matchFacesConfig', matchFacesConfig, MatchFacesConfig.fromJson); 57 | compare('matchFacesImage', matchFacesImage, MatchFacesImage.fromJson); 58 | compare('matchFacesRequest', matchFacesRequest, MatchFacesRequest.fromJson); 59 | compare('matchFacesDetectionFace', matchFacesDetectionFace, 60 | MatchFacesDetectionFace.fromJson); 61 | compare('matchFacesBackendException', matchFacesBackendException, 62 | MatchFacesBackendException.fromJson); 63 | compare('matchFacesException', matchFacesException, 64 | MatchFacesException.fromJson); 65 | compare('matchFacesDetection', matchFacesDetection, 66 | MatchFacesDetection.fromJson); 67 | compare('comparedFace', comparedFace, ComparedFace.fromJson); 68 | compare('comparedFacesPair', comparedFacesPair, ComparedFacesPair.fromJson); 69 | compare( 70 | 'matchFacesResponse', matchFacesResponse, MatchFacesResponse.fromJson); 71 | compare( 72 | 'comparedFacesSplit', comparedFacesSplit, ComparedFacesSplit.fromJson); 73 | 74 | compare('editGroupPersonsRequest', editGroupPersonsRequest, 75 | EditGroupPersonsRequest.fromJson); 76 | compare('imageUpload', imageUpload, ImageUpload.fromJson); 77 | compare('person', person, Person.fromJson); 78 | compare('personGroup', personGroup, PersonGroup.fromJson); 79 | compare('personImage', personImage, PersonImage.fromJson); 80 | compare('searchPersonDetection', searchPersonDetection, 81 | SearchPersonDetection.fromJson); 82 | compare('searchPersonImage', searchPersonImage, SearchPersonImage.fromJson); 83 | compare('searchPerson', searchPerson, SearchPerson.fromJson); 84 | compare('searchPersonRequest', searchPersonRequest, 85 | SearchPersonRequest.fromJson); 86 | }); 87 | } 88 | -------------------------------------------------------------------------------- /android/src/test/kotlin/com/regula/plugin/facesdk/TestUtils.kt: -------------------------------------------------------------------------------- 1 | package com.regula.plugin.facesdk 2 | 3 | import android.content.Context 4 | import android.graphics.Bitmap 5 | import android.graphics.drawable.BitmapDrawable 6 | import android.graphics.drawable.Drawable 7 | import org.json.JSONArray 8 | import org.json.JSONObject 9 | import org.robolectric.shadow.api.Shadow 10 | import org.skyscreamer.jsonassert.JSONAssert 11 | import java.io.IOException 12 | import java.nio.file.Files 13 | import java.nio.file.Paths 14 | import java.util.Base64 15 | 16 | fun readFile(name: String): JSONObject { 17 | val bytes = Files.readAllBytes(Paths.get("../test/json/$name.json")) 18 | return JSONObject(String(bytes)) 19 | } 20 | 21 | fun compareJSONs(name: String, expected: JSONObject, actual: JSONObject) = 22 | try { 23 | JSONAssert.assertEquals(expected, actual, false) 24 | } catch (e: Throwable) { 25 | println("\nAndroid test failed: $name") 26 | println(" Expected JSON:\n$expected") 27 | println(" Actual JSON:\n$actual") 28 | throw e 29 | } 30 | 31 | fun compareSingle( 32 | name: String, 33 | fromJson: (JSONObject) -> T, 34 | toJson: (T) -> JSONObject?, 35 | vararg omit: String 36 | ) { 37 | try { 38 | var expected = readFile(name) 39 | for (key in omit) expected = omitDeep(expected, key.split("."), 0) 40 | val actual = toJson(fromJson(expected))!! 41 | compareJSONs(name, expected, actual) 42 | } catch (_: IOException) { 43 | } 44 | } 45 | 46 | fun compare( 47 | name: String, 48 | fromJson: (JSONObject) -> T, 49 | toJson: (T) -> JSONObject?, 50 | vararg omit: String 51 | ) { 52 | compareSingle(name, fromJson, toJson, *omit) 53 | compareSingle(name + "Nullable", fromJson, toJson, *omit) 54 | } 55 | 56 | fun omitDeep(dict: JSONObject, path: List, index: Int): JSONObject { 57 | if (index < path.size - 1) { 58 | if(!dict.has(path[index])) 59 | return dict // in this case its probably trying to omit in nullability test 60 | val node = dict.get(path[index]) 61 | if (node is JSONObject) 62 | dict.put(path[index], omitDeep(node, path, index + 1)) 63 | else if (node is JSONArray) 64 | dict.put(path[index], omitDeep(node, path, index + 1)) 65 | } else 66 | dict.remove(path[index]) 67 | return dict 68 | } 69 | 70 | fun omitDeep(dict: JSONArray, path: List, index: Int): JSONArray { 71 | for (i in 0 until dict.length()) 72 | dict.put(i, omitDeep(dict.getJSONObject(i), path, index)) 73 | return dict 74 | } 75 | 76 | fun floatToDouble(input: JSONObject): JSONObject { 77 | for (key in input.keys()) { 78 | val value = input.get(key) 79 | if (value is JSONObject) input.put(key, floatToDouble(value)) 80 | if (value is JSONArray) input.put(key, floatToDouble(value)) 81 | if (value is Float) input.put(key, java.lang.Double.parseDouble(value.toString())) 82 | } 83 | return input 84 | } 85 | 86 | fun floatToDouble(input: JSONArray): JSONArray { 87 | for (i in 0 until input.length()) { 88 | val value = input.get(i) 89 | if (value is JSONObject) input.put(i, floatToDouble(value)) 90 | if (value is JSONArray) input.put(i, floatToDouble(value)) 91 | if (value is Float) input.put(i, java.lang.Double.parseDouble(value.toString())) 92 | } 93 | return input 94 | } 95 | 96 | @Suppress("unused", "MemberVisibilityCanBePrivate", "UNUSED_PARAMETER") 97 | internal object Convert { 98 | fun String?.toByteArray() = this?.let { Base64.getDecoder().decode(it) } 99 | fun ByteArray?.toBase64() = this?.let { Base64.getEncoder().encodeToString(it) } 100 | 101 | fun String?.toBitmap() = this?.let { 102 | val bitmap = Shadow.newInstanceOf(Bitmap::class.java) 103 | val shadow = Shadow.extract(bitmap) 104 | shadow.data = toByteArray() 105 | bitmap 106 | } 107 | 108 | fun Bitmap?.toBase64() = this?.let { 109 | val shadow = Shadow.extract(it) 110 | shadow.data.toBase64() 111 | } 112 | 113 | fun String?.toDrawable(context: Context) = this?.let { 114 | val bitmap = Shadow.newInstanceOf(BitmapDrawable::class.java) 115 | val shadow = Shadow.extract(bitmap) 116 | shadow.data = it.toByteArray() 117 | bitmap 118 | } 119 | 120 | fun Drawable?.toString() = this?.let { 121 | val shadow = Shadow.extract(this) 122 | shadow.data.toBase64() 123 | } 124 | } -------------------------------------------------------------------------------- /lib/src/customization/customization_images.dart: -------------------------------------------------------------------------------- 1 | part of "../../flutter_face_api.dart"; 2 | 3 | class CustomizationImages { 4 | ByteData? _onboardingScreenCloseButton; 5 | set onboardingScreenCloseButton(ByteData val) { 6 | _onboardingScreenCloseButton = val; 7 | _set({"100": _dataToBase64(val)}); 8 | } 9 | 10 | ByteData? _onboardingScreenIllumination; 11 | set onboardingScreenIllumination(ByteData val) { 12 | _onboardingScreenIllumination = val; 13 | _set({"101": _dataToBase64(val)}); 14 | } 15 | 16 | ByteData? _onboardingScreenAccessories; 17 | set onboardingScreenAccessories(ByteData val) { 18 | _onboardingScreenAccessories = val; 19 | _set({"102": _dataToBase64(val)}); 20 | } 21 | 22 | ByteData? _onboardingScreenCameraLevel; 23 | set onboardingScreenCameraLevel(ByteData val) { 24 | _onboardingScreenCameraLevel = val; 25 | _set({"103": _dataToBase64(val)}); 26 | } 27 | 28 | ByteData? _cameraScreenCloseButton; 29 | set cameraScreenCloseButton(ByteData val) { 30 | _cameraScreenCloseButton = val; 31 | _set({"200": _dataToBase64(val)}); 32 | } 33 | 34 | ByteData? _cameraScreenLightOnButton; 35 | set cameraScreenLightOnButton(ByteData val) { 36 | _cameraScreenLightOnButton = val; 37 | _set({"201": _dataToBase64(val)}); 38 | } 39 | 40 | ByteData? _cameraScreenLightOffButton; 41 | set cameraScreenLightOffButton(ByteData val) { 42 | _cameraScreenLightOffButton = val; 43 | _set({"202": _dataToBase64(val)}); 44 | } 45 | 46 | ByteData? _cameraScreenSwitchButton; 47 | set cameraScreenSwitchButton(ByteData val) { 48 | _cameraScreenSwitchButton = val; 49 | _set({"203": _dataToBase64(val)}); 50 | } 51 | 52 | ByteData? _retryScreenCloseButton; 53 | set retryScreenCloseButton(ByteData val) { 54 | _retryScreenCloseButton = val; 55 | _set({"300": _dataToBase64(val)}); 56 | } 57 | 58 | ByteData? _retryScreenHintEnvironment; 59 | set retryScreenHintEnvironment(ByteData val) { 60 | _retryScreenHintEnvironment = val; 61 | _set({"301": _dataToBase64(val)}); 62 | } 63 | 64 | ByteData? _retryScreenHintSubject; 65 | set retryScreenHintSubject(ByteData val) { 66 | _retryScreenHintSubject = val; 67 | _set({"302": _dataToBase64(val)}); 68 | } 69 | 70 | ByteData? _processingScreenCloseButton; 71 | set processingScreenCloseButton(ByteData val) { 72 | _processingScreenCloseButton = val; 73 | _set({"400": _dataToBase64(val)}); 74 | } 75 | 76 | ByteData? _successScreenImage; 77 | set successScreenImage(ByteData val) { 78 | _successScreenImage = val; 79 | _set({"500": _dataToBase64(val)}); 80 | } 81 | 82 | /// Allows you to deserialize object. 83 | static CustomizationImages fromJson(jsonObject) { 84 | var result = CustomizationImages(); 85 | result.testSetters = {}; 86 | 87 | result._onboardingScreenCloseButton = _dataFromBase64(jsonObject["100"])!; 88 | result._onboardingScreenIllumination = _dataFromBase64(jsonObject["101"])!; 89 | result._onboardingScreenAccessories = _dataFromBase64(jsonObject["102"])!; 90 | result._onboardingScreenCameraLevel = _dataFromBase64(jsonObject["103"])!; 91 | result._cameraScreenCloseButton = _dataFromBase64(jsonObject["200"])!; 92 | result._cameraScreenLightOnButton = _dataFromBase64(jsonObject["201"])!; 93 | result._cameraScreenLightOffButton = _dataFromBase64(jsonObject["202"])!; 94 | result._cameraScreenSwitchButton = _dataFromBase64(jsonObject["203"])!; 95 | result._retryScreenCloseButton = _dataFromBase64(jsonObject["300"])!; 96 | result._retryScreenHintEnvironment = _dataFromBase64(jsonObject["301"])!; 97 | result._retryScreenHintSubject = _dataFromBase64(jsonObject["302"])!; 98 | result._processingScreenCloseButton = _dataFromBase64(jsonObject["400"])!; 99 | result._successScreenImage = _dataFromBase64(jsonObject["500"])!; 100 | 101 | return result; 102 | } 103 | 104 | /// Allows you to serialize object. 105 | Map toJson() => { 106 | "100": _dataToBase64(_onboardingScreenCloseButton), 107 | "101": _dataToBase64(_onboardingScreenIllumination), 108 | "102": _dataToBase64(_onboardingScreenAccessories), 109 | "103": _dataToBase64(_onboardingScreenCameraLevel), 110 | "200": _dataToBase64(_cameraScreenCloseButton), 111 | "201": _dataToBase64(_cameraScreenLightOnButton), 112 | "202": _dataToBase64(_cameraScreenLightOffButton), 113 | "203": _dataToBase64(_cameraScreenSwitchButton), 114 | "300": _dataToBase64(_retryScreenCloseButton), 115 | "301": _dataToBase64(_retryScreenHintEnvironment), 116 | "302": _dataToBase64(_retryScreenHintSubject), 117 | "400": _dataToBase64(_processingScreenCloseButton), 118 | "500": _dataToBase64(_successScreenImage), 119 | }.clearNulls(); 120 | 121 | void _set(Map json, {Customization? directParent}) { 122 | var parentJson = {"images": json}; 123 | var parent = FaceSDK.instance.customization; 124 | if (identical(this, parent.images)) parent._set(parentJson); 125 | directParent?.testSetters.addAll(parentJson); 126 | testSetters.addAll(json); 127 | } 128 | 129 | void _apply(Customization parent) => _set(toJson(), directParent: parent); 130 | 131 | @visibleForTesting 132 | Map testSetters = {}; 133 | } 134 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" 9 | url: "https://pub.dev" 10 | source: hosted 11 | version: "2.13.0" 12 | boolean_selector: 13 | dependency: transitive 14 | description: 15 | name: boolean_selector 16 | sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" 17 | url: "https://pub.dev" 18 | source: hosted 19 | version: "2.1.2" 20 | characters: 21 | dependency: transitive 22 | description: 23 | name: characters 24 | sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 25 | url: "https://pub.dev" 26 | source: hosted 27 | version: "1.4.0" 28 | clock: 29 | dependency: transitive 30 | description: 31 | name: clock 32 | sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b 33 | url: "https://pub.dev" 34 | source: hosted 35 | version: "1.1.2" 36 | collection: 37 | dependency: transitive 38 | description: 39 | name: collection 40 | sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" 41 | url: "https://pub.dev" 42 | source: hosted 43 | version: "1.19.1" 44 | fake_async: 45 | dependency: transitive 46 | description: 47 | name: fake_async 48 | sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" 49 | url: "https://pub.dev" 50 | source: hosted 51 | version: "1.3.3" 52 | flutter: 53 | dependency: "direct main" 54 | description: flutter 55 | source: sdk 56 | version: "0.0.0" 57 | flutter_test: 58 | dependency: "direct dev" 59 | description: flutter 60 | source: sdk 61 | version: "0.0.0" 62 | leak_tracker: 63 | dependency: transitive 64 | description: 65 | name: leak_tracker 66 | sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" 67 | url: "https://pub.dev" 68 | source: hosted 69 | version: "11.0.2" 70 | leak_tracker_flutter_testing: 71 | dependency: transitive 72 | description: 73 | name: leak_tracker_flutter_testing 74 | sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" 75 | url: "https://pub.dev" 76 | source: hosted 77 | version: "3.0.10" 78 | leak_tracker_testing: 79 | dependency: transitive 80 | description: 81 | name: leak_tracker_testing 82 | sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" 83 | url: "https://pub.dev" 84 | source: hosted 85 | version: "3.0.2" 86 | lints: 87 | dependency: "direct dev" 88 | description: 89 | name: lints 90 | sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235" 91 | url: "https://pub.dev" 92 | source: hosted 93 | version: "4.0.0" 94 | matcher: 95 | dependency: transitive 96 | description: 97 | name: matcher 98 | sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 99 | url: "https://pub.dev" 100 | source: hosted 101 | version: "0.12.17" 102 | material_color_utilities: 103 | dependency: transitive 104 | description: 105 | name: material_color_utilities 106 | sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec 107 | url: "https://pub.dev" 108 | source: hosted 109 | version: "0.11.1" 110 | meta: 111 | dependency: "direct dev" 112 | description: 113 | name: meta 114 | sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" 115 | url: "https://pub.dev" 116 | source: hosted 117 | version: "1.17.0" 118 | path: 119 | dependency: transitive 120 | description: 121 | name: path 122 | sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" 123 | url: "https://pub.dev" 124 | source: hosted 125 | version: "1.9.1" 126 | sky_engine: 127 | dependency: transitive 128 | description: flutter 129 | source: sdk 130 | version: "0.0.0" 131 | source_span: 132 | dependency: transitive 133 | description: 134 | name: source_span 135 | sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" 136 | url: "https://pub.dev" 137 | source: hosted 138 | version: "1.10.1" 139 | stack_trace: 140 | dependency: transitive 141 | description: 142 | name: stack_trace 143 | sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" 144 | url: "https://pub.dev" 145 | source: hosted 146 | version: "1.12.1" 147 | stream_channel: 148 | dependency: transitive 149 | description: 150 | name: stream_channel 151 | sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" 152 | url: "https://pub.dev" 153 | source: hosted 154 | version: "2.1.4" 155 | string_scanner: 156 | dependency: transitive 157 | description: 158 | name: string_scanner 159 | sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" 160 | url: "https://pub.dev" 161 | source: hosted 162 | version: "1.4.1" 163 | term_glyph: 164 | dependency: transitive 165 | description: 166 | name: term_glyph 167 | sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" 168 | url: "https://pub.dev" 169 | source: hosted 170 | version: "1.2.2" 171 | test_api: 172 | dependency: transitive 173 | description: 174 | name: test_api 175 | sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 176 | url: "https://pub.dev" 177 | source: hosted 178 | version: "0.7.7" 179 | vector_math: 180 | dependency: transitive 181 | description: 182 | name: vector_math 183 | sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b 184 | url: "https://pub.dev" 185 | source: hosted 186 | version: "2.2.0" 187 | vm_service: 188 | dependency: transitive 189 | description: 190 | name: vm_service 191 | sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60" 192 | url: "https://pub.dev" 193 | source: hosted 194 | version: "15.0.2" 195 | sdks: 196 | dart: ">=3.8.0-0 <4.0.0" 197 | flutter: ">=3.18.0-18.0.pre.54" 198 | -------------------------------------------------------------------------------- /android/src/test/kotlin/com/regula/plugin/facesdk/FlutterFaceApiPluginTest.kt: -------------------------------------------------------------------------------- 1 | package com.regula.plugin.facesdk 2 | 3 | import org.junit.Test 4 | import org.junit.runner.RunWith 5 | import org.robolectric.RobolectricTestRunner 6 | import org.robolectric.annotation.Config 7 | 8 | @RunWith(RobolectricTestRunner::class) 9 | @Config(shadows = [MyShadowBitmap::class, MyShadowDrawable::class, MyShadowBitmapDrawable::class]) 10 | class FlutterFaceApiPluginTest { 11 | // image_params 12 | 13 | @Test 14 | fun point() = compare("point", ::pointFromJSON, ::generatePoint) 15 | 16 | @Test 17 | fun rect() = compare("rect", ::rectFromJSON, ::generateRect) 18 | 19 | @Test 20 | fun size() = compare("size", ::sizeFromJSON, ::generateSize) 21 | 22 | @Test 23 | fun outputImageCrop() = compare("outputImageCrop", ::outputImageCropFromJSON, ::generateOutputImageCrop) 24 | 25 | @Test 26 | fun outputImageParams() = compare("outputImageParams", ::outputImageParamsFromJSON, ::generateOutputImageParams) 27 | 28 | // image_quality 29 | 30 | @Test 31 | fun imageQualityRange() = compare("imageQualityRange", ::imageQualityRangeFromJSON, ::generateImageQualityRange) 32 | 33 | @Test 34 | fun imageQualityResult() = compare("imageQualityResult", ::imageQualityResultFromJSON, ::generateImageQualityResult) 35 | 36 | @Test 37 | fun imageQualityCharacteristic() = compare("imageQualityCharacteristic", ::imageQualityCharacteristicFromJSON, ::generateImageQualityCharacteristic) 38 | 39 | // init 40 | 41 | @Test 42 | fun faceSDKVersion() = compare("faceSDKVersion", ::faceSDKVersionFromJSON, ::generateFaceSDKVersion) 43 | 44 | @Test 45 | fun initConfig() = compare("initConfig", ::initConfigFromJSON, ::generateInitConfig, "useBleDevice") 46 | 47 | @Test 48 | fun licenseException() = compare("licenseException", ::licenseExceptionFromJSON, ::generateLicenseException) 49 | 50 | @Test 51 | fun initException() = compare("initException", ::initExceptionFromJSON, ::generateInitException) 52 | 53 | // detect_faces 54 | 55 | @Test 56 | fun detectFacesAttributeResult() = compare("detectFacesAttributeResult", ::detectFacesAttributeResultFromJSON, ::generateDetectFacesAttributeResult) 57 | 58 | @Test 59 | fun detectFaceResult() = compare("detectFaceResult", ::detectFaceResultFromJSON, ::generateDetectFaceResult) 60 | 61 | @Test 62 | fun detectFacesConfig() = compare("detectFacesConfig", ::detectFacesConfigFromJSON, ::generateDetectFacesConfig) 63 | 64 | @Test 65 | fun detectFacesRequest() = compare("detectFacesRequest", ::detectFacesRequestFromJSON, ::generateDetectFacesRequest) 66 | 67 | @Test 68 | fun detectFacesBackendException() = compare("detectFacesBackendException", ::detectFacesBackendExceptionFromJSON, ::generateDetectFacesBackendException) 69 | 70 | @Test 71 | fun detectFacesException() = compare("detectFacesException", ::detectFacesExceptionFromJSON, ::generateDetectFacesException) 72 | 73 | @Test 74 | fun detectFacesResponse() = compare("detectFacesResponse", ::detectFacesResponseFromJSON, ::generateDetectFacesResponse) 75 | 76 | // face_capture 77 | 78 | @Test 79 | fun faceCaptureConfig() = compare("faceCaptureConfig", ::faceCaptureConfigFromJSON, ::generateFaceCaptureConfig, "cameraPositionIOS") 80 | 81 | @Test 82 | fun faceCaptureImage() = compare("faceCaptureImage", ::faceCaptureImageFromJSON, ::generateFaceCaptureImage) 83 | 84 | @Test 85 | fun faceCaptureException() = compare("faceCaptureException", ::faceCaptureExceptionFromJSON, ::generateFaceCaptureException) 86 | 87 | @Test 88 | fun faceCaptureResponse() = compare("faceCaptureResponse", ::faceCaptureResponseFromJSON, ::generateFaceCaptureResponse) 89 | 90 | // liveness 91 | 92 | @Test 93 | fun livenessConfig() = compare("livenessConfig", ::livenessConfigFromJSON, ::generateLivenessConfig, "cameraPositionIOS") 94 | 95 | @Test 96 | fun livenessBackendException() = compare("livenessBackendException", ::livenessBackendExceptionFromJSON, ::generateLivenessBackendException) 97 | 98 | @Test 99 | fun livenessException() = compare("livenessException", ::livenessExceptionFromJSON, ::generateLivenessException) 100 | 101 | @Test 102 | fun livenessResponse() = compare("livenessResponse", ::livenessResponseFromJSON, ::generateLivenessResponse) 103 | 104 | @Test 105 | fun livenessNotification() = compare("livenessNotification", ::livenessNotificationFromJSON, ::generateLivenessNotification) 106 | 107 | // match_faces 108 | 109 | @Test 110 | fun matchFacesConfig() = compare("matchFacesConfig", ::matchFacesConfigFromJSON, ::generateMatchFacesConfig) 111 | 112 | @Test 113 | fun matchFacesImage() = compare("matchFacesImage", ::matchFacesImageFromJSON, ::generateMatchFacesImage) 114 | 115 | @Test 116 | fun matchFacesRequest() = compare("matchFacesRequest", ::matchFacesRequestFromJSON, ::generateMatchFacesRequest) 117 | 118 | @Test 119 | fun matchFacesDetectionFace() = compare("matchFacesDetectionFace", ::matchFacesDetectionFaceFromJSON, ::generateMatchFacesDetectionFace) 120 | 121 | @Test 122 | fun matchFacesException() = compare("matchFacesException", ::matchFacesExceptionFromJSON, ::generateMatchFacesException) 123 | 124 | @Test 125 | fun matchFacesDetection() = compare("matchFacesDetection", ::matchFacesDetectionFromJSON, ::generateMatchFacesDetection, "error") 126 | 127 | @Test 128 | fun comparedFace() = compare("comparedFace", ::comparedFaceFromJSON, ::generateComparedFace) 129 | 130 | @Test 131 | fun comparedFacesPair() = compare("comparedFacesPair", ::comparedFacesPairFromJSON, ::generateComparedFacesPair, "error") 132 | 133 | @Test 134 | fun matchFacesResponse() = compare("matchFacesResponse", ::matchFacesResponseFromJSON, ::generateMatchFacesResponse) 135 | 136 | // person_database 137 | 138 | @Test 139 | fun editGroupPersonsRequest() = compare("editGroupPersonsRequest", ::editGroupPersonsRequestFromJSON, ::generateEditGroupPersonsRequest) 140 | 141 | @Test 142 | fun imageUpload() = compare("imageUpload", ::imageUploadFromJSON, ::generateImageUpload) 143 | 144 | @Test 145 | fun person() = compare("person", ::personFromJSON, ::generatePerson) 146 | 147 | @Test 148 | fun personGroup() = compare("personGroup", ::personGroupFromJSON, ::generatePersonGroup) 149 | 150 | @Test 151 | fun personImage() = compare("personImage", ::personImageFromJSON, ::generatePersonImage) 152 | 153 | @Test 154 | fun searchPersonDetection() = compare("searchPersonDetection", ::searchPersonDetectionFromJSON, ::generateSearchPersonDetection) 155 | 156 | @Test 157 | fun searchPersonImage() = compare("searchPersonImage", ::searchPersonImageFromJSON, ::generateSearchPersonImage) 158 | 159 | @Test 160 | fun searchPerson() = compare("searchPerson", ::searchPersonFromJSON, ::generateSearchPerson) 161 | 162 | @Test 163 | fun searchPersonRequest() = compare("searchPersonRequest", ::searchPersonRequestFromJSON, ::generateSearchPersonRequest) 164 | } -------------------------------------------------------------------------------- /lib/src/liveness/liveness_config.dart: -------------------------------------------------------------------------------- 1 | part of "../../flutter_face_api.dart"; 2 | 3 | /// Configuration for the Liveness processing. 4 | /// The configuration provides convenient properties to change the behavior and the appearance of the Liveness UI module. 5 | class LivenessConfig { 6 | /// Defines, whether the logo is visible on the bottom of Liveness UI screens. Defaults to `true`. 7 | bool copyright; 8 | 9 | /// Defines, whether the camera's toolbar switch camera button is available on the Liveness UI. Defaults to `false`. 10 | /// When set to `true` the CameraToolbarView will contain a button to change current `cameraPosition`. 11 | /// Only for livenessType = [LivenessType.PASSIVE]. 12 | bool cameraSwitchEnabled; 13 | 14 | bool closeButtonEnabled; 15 | 16 | /// Defines, whether the camera's toolbar torch button is available on the Liveness UI. Defaults to `true`. 17 | /// When set to `false` the CameraToolbarView won't contain a button to toggle camera's flashlight. 18 | /// Only for livenessType = [LivenessType.PASSIVE]. 19 | bool torchButtonEnabled; 20 | 21 | /// Enables vibration during Liveness processing. Defaults to `true`. 22 | bool vibrateOnSteps; 23 | 24 | /// Android only. 25 | int? cameraPositionAndroid; 26 | 27 | /// IOS only. 28 | CameraPosition cameraPositionIOS; 29 | 30 | /// Allows you to specify an orientation of the camera view controller. 31 | List screenOrientation; 32 | 33 | /// Defines whether the liveness request sends a location of a device. Defaults to `true`. 34 | /// When set to `true` the liveness request to web service will contain the `location` 35 | /// object within the json `metadata` object. 36 | /// The location is used only when permissions are granted and the location is available. 37 | bool locationTrackingEnabled; 38 | 39 | /// The number of attempts to pass the Liveness before completing with error. Defaults to `0`. 40 | /// When set to `0` the Liveness will always ask to retry on error. 41 | int attemptsCount; 42 | 43 | /// Defines whether the liveness recording video of processing. 44 | /// Defaults to [RecordingProcess.ASYNCHRONOUS_UPLOAD]. 45 | RecordingProcess recordingProcess; 46 | 47 | /// Defines whether the liveness processing type. Defaults to [LivenessType.ACTIVE]. 48 | LivenessType livenessType; 49 | 50 | /// Defines tag that can be used in Liveness processing. Defaults to `null`. 51 | String? tag; 52 | 53 | /// Defines which steps of the user interface can be omitted. See [LivenessSkipStep] enum for details. 54 | List skipStep; 55 | 56 | dynamic metadata; 57 | 58 | LivenessConfig({ 59 | bool copyright = true, 60 | bool cameraSwitchEnabled = false, 61 | bool closeButtonEnabled = true, 62 | bool torchButtonEnabled = true, 63 | bool vibrateOnSteps = true, 64 | int? cameraPositionAndroid, 65 | CameraPosition cameraPositionIOS = CameraPosition.FRONT, 66 | List screenOrientation = const [ 67 | ScreenOrientation.PORTRAIT 68 | ], 69 | bool locationTrackingEnabled = true, 70 | int attemptsCount = 0, 71 | RecordingProcess recordingProcess = RecordingProcess.ASYNCHRONOUS_UPLOAD, 72 | LivenessType livenessType = LivenessType.ACTIVE, 73 | String? tag, 74 | List skipStep = const [], 75 | dynamic metadata, 76 | }) : copyright = copyright, 77 | cameraSwitchEnabled = cameraSwitchEnabled, 78 | closeButtonEnabled = closeButtonEnabled, 79 | torchButtonEnabled = torchButtonEnabled, 80 | vibrateOnSteps = vibrateOnSteps, 81 | cameraPositionAndroid = cameraPositionAndroid, 82 | cameraPositionIOS = cameraPositionIOS, 83 | screenOrientation = screenOrientation, 84 | locationTrackingEnabled = locationTrackingEnabled, 85 | attemptsCount = attemptsCount, 86 | recordingProcess = recordingProcess, 87 | livenessType = livenessType, 88 | tag = tag, 89 | skipStep = skipStep, 90 | metadata = metadata; 91 | 92 | @visibleForTesting 93 | static LivenessConfig? fromJson(jsonObject) { 94 | if (jsonObject == null) return null; 95 | var result = LivenessConfig(); 96 | 97 | result.copyright = jsonObject["copyright"]; 98 | result.cameraSwitchEnabled = jsonObject["cameraSwitchEnabled"]; 99 | result.closeButtonEnabled = jsonObject["closeButtonEnabled"]; 100 | result.torchButtonEnabled = jsonObject["torchButtonEnabled"]; 101 | result.vibrateOnSteps = jsonObject["vibrateOnSteps"]; 102 | result.cameraPositionAndroid = jsonObject["cameraPositionAndroid"]; 103 | result.cameraPositionIOS = 104 | CameraPosition.getByValue(jsonObject["cameraPositionIOS"])!; 105 | result.screenOrientation = 106 | ScreenOrientation.fromIntList(jsonObject["screenOrientation"])!; 107 | result.locationTrackingEnabled = jsonObject["locationTrackingEnabled"]; 108 | result.attemptsCount = jsonObject["attemptsCount"]; 109 | result.recordingProcess = 110 | RecordingProcess.getByValue(jsonObject["recordingProcess"])!; 111 | result.livenessType = LivenessType.getByValue(jsonObject["livenessType"])!; 112 | result.tag = jsonObject["tag"]; 113 | result.skipStep = 114 | LivenessSkipStep.fromIntList(jsonObject["screenOrientation"])!; 115 | result.metadata = jsonObject["metadata"]; 116 | 117 | return result; 118 | } 119 | 120 | @visibleForTesting 121 | Map toJson() => { 122 | "copyright": copyright, 123 | "cameraSwitchEnabled": cameraSwitchEnabled, 124 | "closeButtonEnabled": closeButtonEnabled, 125 | "torchButtonEnabled": torchButtonEnabled, 126 | "vibrateOnSteps": vibrateOnSteps, 127 | "cameraPositionAndroid": cameraPositionAndroid, 128 | "cameraPositionIOS": cameraPositionIOS.value, 129 | "screenOrientation": screenOrientation.map((e) => e.value).toList(), 130 | "locationTrackingEnabled": locationTrackingEnabled, 131 | "attemptsCount": attemptsCount, 132 | "recordingProcess": recordingProcess.value, 133 | "livenessType": livenessType.value, 134 | "tag": tag, 135 | "skipStep": skipStep.map((e) => e.value).toList(), 136 | "metadata": metadata, 137 | }.clearNulls(); 138 | } 139 | 140 | enum RecordingProcess { 141 | ASYNCHRONOUS_UPLOAD(0), 142 | 143 | SYNCHRONOUS_UPLOAD(1), 144 | 145 | NOT_UPLOAD(2); 146 | 147 | const RecordingProcess(this.value); 148 | final int value; 149 | 150 | static RecordingProcess? getByValue(int? i) { 151 | if (i == null) return null; 152 | return RecordingProcess.values.firstWhere((x) => x.value == i); 153 | } 154 | } 155 | 156 | enum LivenessType { 157 | ACTIVE(0), 158 | 159 | PASSIVE(1); 160 | 161 | const LivenessType(this.value); 162 | final int value; 163 | 164 | static LivenessType? getByValue(int? i) { 165 | if (i == null) return null; 166 | return LivenessType.values.firstWhere((x) => x.value == i); 167 | } 168 | } 169 | 170 | enum LivenessSkipStep { 171 | ONBOARDING_STEP(0), 172 | 173 | SUCCESS_STEP(1); 174 | 175 | const LivenessSkipStep(this.value); 176 | final int value; 177 | 178 | static LivenessSkipStep? getByValue(int? i) { 179 | if (i == null) return null; 180 | return LivenessSkipStep.values.firstWhere((x) => x.value == i); 181 | } 182 | 183 | static List? fromIntList(List? input) { 184 | if (input == null) return null; 185 | List list = []; 186 | for (int item in input) { 187 | list.addSafe(getByValue(item)); 188 | } 189 | return list; 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /lib/src/person_database/person_database.dart: -------------------------------------------------------------------------------- 1 | part of "../../flutter_face_api.dart"; 2 | 3 | /// Represents Regula Database layer and is the entry point for Person Database operations. 4 | class PersonDatabase { 5 | PersonDatabase._privateConstructor(); 6 | 7 | Future<(Person?, String?)> createPerson( 8 | String name, { 9 | List groupIds = const [], 10 | dynamic metadata, 11 | }) async { 12 | var response = await _bridge.invokeMethod("createPerson", [ 13 | name, 14 | groupIds, 15 | metadata, 16 | ]); 17 | return _itemResponseFromJson(response, Person.fromJson); 18 | } 19 | 20 | Future<(bool, String?)> updatePerson(Person person) async { 21 | var response = await _bridge.invokeMethod( 22 | "updatePerson", 23 | [person.toJson()], 24 | ); 25 | return _successResponseFromJson(response); 26 | } 27 | 28 | Future<(bool, String?)> deletePerson(String personId) async { 29 | var response = await _bridge.invokeMethod("deletePerson", [personId]); 30 | return _successResponseFromJson(response); 31 | } 32 | 33 | Future<(Person?, String?)> getPerson(String personId) async { 34 | var response = await _bridge.invokeMethod("getPerson", [personId]); 35 | return _itemResponseFromJson(response, Person.fromJson); 36 | } 37 | 38 | Future<(PersonImage?, String?)> addPersonImage( 39 | String personId, 40 | ImageUpload image, 41 | ) async { 42 | var response = await _bridge.invokeMethod("addPersonImage", [ 43 | personId, 44 | image.toJson(), 45 | ]); 46 | return _itemResponseFromJson(response, PersonImage.fromJson); 47 | } 48 | 49 | Future<(bool, String?)> deletePersonImage( 50 | String personId, 51 | String imageId, 52 | ) async { 53 | var response = await _bridge.invokeMethod("deletePersonImage", [ 54 | personId, 55 | imageId, 56 | ]); 57 | return _successResponseFromJson(response); 58 | } 59 | 60 | Future<(Uint8List?, String?)> getPersonImage( 61 | String personId, 62 | String imageId, 63 | ) async { 64 | var response = await _bridge.invokeMethod("getPersonImage", [ 65 | personId, 66 | imageId, 67 | ]); 68 | return _itemResponseFromJson( 69 | response, 70 | (data) => _bytesFromBase64(data as String?), 71 | ); 72 | } 73 | 74 | Future<(PageableItemList, String?)> getPersonImages( 75 | String personId, 76 | ) async { 77 | var response = await _bridge.invokeMethod("getPersonImages", [personId]); 78 | return _listResponseFromJson(response, PersonImage.fromJson); 79 | } 80 | 81 | Future<(PageableItemList, String?)> getPersonImagesForPage( 82 | String personId, 83 | int page, 84 | int size, 85 | ) async { 86 | var response = await _bridge.invokeMethod("getPersonImagesForPage", [ 87 | personId, 88 | page, 89 | size, 90 | ]); 91 | return _listResponseFromJson(response, PersonImage.fromJson); 92 | } 93 | 94 | Future<(PersonGroup?, String?)> createGroup( 95 | String name, { 96 | dynamic metadata, 97 | }) async { 98 | var response = await _bridge.invokeMethod("createGroup", [ 99 | name, 100 | metadata, 101 | ]); 102 | return _itemResponseFromJson(response, PersonGroup.fromJson); 103 | } 104 | 105 | Future<(bool, String?)> updateGroup(PersonGroup group) async { 106 | var response = await _bridge.invokeMethod( 107 | "updateGroup", 108 | [group.toJson()], 109 | ); 110 | return _successResponseFromJson(response); 111 | } 112 | 113 | Future<(bool, String?)> editPersonsInGroup( 114 | String groupId, 115 | EditGroupPersonsRequest request, 116 | ) async { 117 | var response = await _bridge.invokeMethod("editPersonsInGroup", [ 118 | groupId, 119 | request.toJson(), 120 | ]); 121 | return _successResponseFromJson(response); 122 | } 123 | 124 | Future<(bool, String?)> deleteGroup(String groupId) async { 125 | var response = await _bridge.invokeMethod("deleteGroup", [groupId]); 126 | return _successResponseFromJson(response); 127 | } 128 | 129 | Future<(PersonGroup?, String?)> getGroup(String groupId) async { 130 | var response = await _bridge.invokeMethod("getGroup", [groupId]); 131 | return _itemResponseFromJson(response, PersonGroup.fromJson); 132 | } 133 | 134 | Future<(PageableItemList, String?)> getGroups() async { 135 | var response = await _bridge.invokeMethod("getGroups", []); 136 | return _listResponseFromJson(response, PersonGroup.fromJson); 137 | } 138 | 139 | Future<(PageableItemList, String?)> getGroupsForPage( 140 | int page, 141 | int size, 142 | ) async { 143 | var response = await _bridge.invokeMethod("getGroupsForPage", [ 144 | page, 145 | size, 146 | ]); 147 | return _listResponseFromJson(response, PersonGroup.fromJson); 148 | } 149 | 150 | Future<(PageableItemList, String?)> getPersonGroups( 151 | String personId, 152 | ) async { 153 | var response = await _bridge.invokeMethod("getPersonGroups", [personId]); 154 | return _listResponseFromJson(response, PersonGroup.fromJson); 155 | } 156 | 157 | Future<(PageableItemList, String?)> getPersonGroupsForPage( 158 | String personId, 159 | int page, 160 | int size, 161 | ) async { 162 | var response = await _bridge.invokeMethod("getPersonGroupsForPage", [ 163 | personId, 164 | page, 165 | size, 166 | ]); 167 | return _listResponseFromJson(response, PersonGroup.fromJson); 168 | } 169 | 170 | Future<(PageableItemList, String?)> getPersonsInGroup( 171 | String groupId, 172 | ) async { 173 | var response = await _bridge.invokeMethod("getPersonsInGroup", [groupId]); 174 | return _listResponseFromJson(response, Person.fromJson); 175 | } 176 | 177 | Future<(PageableItemList, String?)> getPersonsInGroupForPage( 178 | String groupId, 179 | int page, 180 | int size, 181 | ) async { 182 | var response = await _bridge.invokeMethod("getPersonsInGroupForPage", [ 183 | groupId, 184 | page, 185 | size, 186 | ]); 187 | return _listResponseFromJson(response, Person.fromJson); 188 | } 189 | 190 | Future<(List?, String?)> searchPerson( 191 | SearchPersonRequest request, 192 | ) async { 193 | var response = await _bridge.invokeMethod( 194 | "searchPerson", 195 | [request.toJson()], 196 | ); 197 | 198 | var jsonObject = _decode(response); 199 | List? data = null; 200 | if (jsonObject["data"] != null) { 201 | data = []; 202 | for (var item in jsonObject["data"]) { 203 | data.add(SearchPerson.fromJson(item)!); 204 | } 205 | } 206 | String? error = jsonObject["error"]; 207 | return (data, error); 208 | } 209 | 210 | (bool, String?) _successResponseFromJson(String jsonString) { 211 | var jsonObject = _decode(jsonString); 212 | var data = jsonObject["data"]; 213 | var error = jsonObject["error"]; 214 | bool success = data ?? false; 215 | return (success, error); 216 | } 217 | 218 | (T?, String?) _itemResponseFromJson( 219 | String jsonString, 220 | T? Function(dynamic) fromJSON, 221 | ) { 222 | var jsonObject = _decode(jsonString); 223 | var data = fromJSON(jsonObject["data"]); 224 | var error = jsonObject["error"]; 225 | return (data, error); 226 | } 227 | 228 | (PageableItemList, String?) _listResponseFromJson( 229 | String jsonString, 230 | T? Function(dynamic) fromJSON, 231 | ) { 232 | var jsonObject = _decode(jsonString); 233 | var data = PageableItemList.fromJson(jsonObject["data"], fromJSON)!; 234 | var error = jsonObject["error"]; 235 | return (data, error); 236 | } 237 | } 238 | --------------------------------------------------------------------------------