├── 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 | [](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 |
--------------------------------------------------------------------------------