├── .fvm
├── release
├── version
├── flutter_sdk
└── fvm_config.json
├── ios
├── Assets
│ └── .gitkeep
├── Resources
│ └── PrivacyInfo.xcprivacy
├── .gitignore
├── Classes
│ └── FlutterLivenessDetectionRandomizedPlugin.swift
└── flutter_liveness_detection_randomized_plugin.podspec
├── .fvmrc
├── lib
├── src
│ ├── core
│ │ ├── enums
│ │ │ ├── index.dart
│ │ │ └── liveness_detection_step.dart
│ │ ├── services
│ │ │ ├── index.dart
│ │ │ └── liveness_cooldown_service.dart
│ │ ├── utils
│ │ │ ├── index.dart
│ │ │ └── machine_learning_kit_helper.dart
│ │ ├── constants
│ │ │ ├── index.dart
│ │ │ └── liveness_detection_step_constant.dart
│ │ └── index.dart
│ ├── presentation
│ │ ├── views
│ │ │ ├── index.dart
│ │ │ └── liveness_detection_view.dart
│ │ └── widgets
│ │ │ ├── index.dart
│ │ │ ├── circular_progress_widget
│ │ │ ├── circular_progress_painter.dart
│ │ │ └── circular_progress_widget.dart
│ │ │ ├── liveness_cooldown_widget.dart
│ │ │ ├── liveness_detection_tutorial_widget.dart
│ │ │ └── liveness_detection_step_overlay_widget.dart
│ └── models
│ │ ├── index.dart
│ │ ├── liveness_detection_label_model.dart
│ │ ├── liveness_detection_config.dart
│ │ ├── liveness_detection_cooldown.dart
│ │ ├── liveness_detection_step_item.dart
│ │ └── liveness_detection_threshold.dart
├── flutter_liveness_detection_randomized_plugin_method_channel.dart
├── index.dart
├── flutter_liveness_detection_randomized_plugin_platform_interface.dart
└── flutter_liveness_detection_randomized_plugin.dart
├── .vscode
├── settings.json
└── launch.json
├── example
├── 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
│ │ ├── AppDelegate.swift
│ │ ├── Base.lproj
│ │ │ ├── Main.storyboard
│ │ │ └── LaunchScreen.storyboard
│ │ └── Info.plist
│ ├── 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
│ ├── RunnerTests
│ │ └── RunnerTests.swift
│ ├── Podfile
│ └── Podfile.lock
├── 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
│ │ │ │ │ │ └── bagussubagja
│ │ │ │ │ │ └── flutter_liveness_detection_randomized_plugin
│ │ │ │ │ │ └── flutter_liveness_detection_randomized_plugin_example
│ │ │ │ │ │ └── MainActivity.kt
│ │ │ │ └── AndroidManifest.xml
│ │ │ ├── debug
│ │ │ │ └── AndroidManifest.xml
│ │ │ └── profile
│ │ │ │ └── AndroidManifest.xml
│ │ └── build.gradle
│ ├── gradle
│ │ └── wrapper
│ │ │ └── gradle-wrapper.properties
│ ├── .gitignore
│ ├── build.gradle
│ └── settings.gradle
├── README.md
├── .gitignore
├── test
│ └── widget_test.dart
├── integration_test
│ └── plugin_integration_test.dart
├── analysis_options.yaml
├── pubspec.yaml
├── lib
│ └── main.dart
└── pubspec.lock
├── android
├── settings.gradle
├── .gitignore
├── src
│ ├── main
│ │ ├── AndroidManifest.xml
│ │ └── kotlin
│ │ │ └── com
│ │ │ └── bagussubagja
│ │ │ └── flutter_liveness_detection_randomized_plugin
│ │ │ └── flutter_liveness_detection_randomized_plugin
│ │ │ └── FlutterLivenessDetectionRandomizedPlugin.kt
│ └── test
│ │ └── kotlin
│ │ └── com
│ │ └── bagussubagja
│ │ └── flutter_liveness_detection_randomized_plugin
│ │ └── flutter_liveness_detection_randomized_plugin
│ │ └── FlutterLivenessDetectionRandomizedPluginTest.kt
└── build.gradle
├── analysis_options.yaml
├── .gitignore
├── test
├── flutter_liveness_detection_randomized_plugin_method_channel_test.dart
└── flutter_liveness_detection_randomized_plugin_test.dart
├── LICENSE
├── .metadata
├── pubspec.yaml
├── example_customized_label_usage.dart
├── .github
└── workflows
│ └── ci-cd.yml
├── CHANGELOG.md
└── README.md
/.fvm/release:
--------------------------------------------------------------------------------
1 | 3.38.1
--------------------------------------------------------------------------------
/.fvm/version:
--------------------------------------------------------------------------------
1 | 3.38.1
--------------------------------------------------------------------------------
/ios/Assets/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.fvmrc:
--------------------------------------------------------------------------------
1 | {
2 | "flutter": "3.38.1"
3 | }
--------------------------------------------------------------------------------
/.fvm/flutter_sdk:
--------------------------------------------------------------------------------
1 | /Users/bagussubagja/fvm/versions/3.38.1
--------------------------------------------------------------------------------
/.fvm/fvm_config.json:
--------------------------------------------------------------------------------
1 | {
2 | "flutterSdkVersion": "3.38.1"
3 | }
--------------------------------------------------------------------------------
/lib/src/core/enums/index.dart:
--------------------------------------------------------------------------------
1 | export 'liveness_detection_step.dart';
--------------------------------------------------------------------------------
/lib/src/core/services/index.dart:
--------------------------------------------------------------------------------
1 | export 'liveness_cooldown_service.dart';
--------------------------------------------------------------------------------
/lib/src/core/utils/index.dart:
--------------------------------------------------------------------------------
1 | export 'machine_learning_kit_helper.dart';
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "dart.flutterSdkPath": ".fvm/versions/3.38.1"
3 | }
--------------------------------------------------------------------------------
/lib/src/core/constants/index.dart:
--------------------------------------------------------------------------------
1 | export 'liveness_detection_step_constant.dart';
--------------------------------------------------------------------------------
/example/ios/Runner/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "GeneratedPluginRegistrant.h"
2 |
--------------------------------------------------------------------------------
/android/settings.gradle:
--------------------------------------------------------------------------------
1 | package android
2 |
3 | rootProject.name = 'flutter_liveness_detection_randomized_plugin'
--------------------------------------------------------------------------------
/lib/src/presentation/views/index.dart:
--------------------------------------------------------------------------------
1 | export '../widgets/index.dart';
2 | export 'liveness_detection_view.dart';
3 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/android/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 | .cxx
10 |
--------------------------------------------------------------------------------
/lib/src/core/enums/liveness_detection_step.dart:
--------------------------------------------------------------------------------
1 | enum LivenessDetectionStep {
2 | blink,
3 | lookRight,
4 | lookLeft,
5 | lookUp,
6 | lookDown,
7 | smile
8 | }
--------------------------------------------------------------------------------
/example/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError
2 | android.useAndroidX=true
3 | android.enableJetifier=true
4 |
--------------------------------------------------------------------------------
/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | include: package:flutter_lints/flutter.yaml
2 |
3 | # Additional information about this file can be found at
4 | # https://dart.dev/guides/language/analysis-options
5 |
--------------------------------------------------------------------------------
/lib/src/presentation/widgets/index.dart:
--------------------------------------------------------------------------------
1 | export 'liveness_detection_tutorial_widget.dart';
2 | export 'liveness_detection_step_overlay_widget.dart';
3 | export 'liveness_cooldown_widget.dart';
--------------------------------------------------------------------------------
/lib/src/core/index.dart:
--------------------------------------------------------------------------------
1 | export '../presentation/views/index.dart';
2 | export '../models/index.dart';
3 | export 'enums/index.dart';
4 | export 'utils/index.dart';
5 | export 'services/index.dart';
6 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bagussubagja/flutter-liveness-detection-randomized-plugin/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/bagussubagja/flutter-liveness-detection-randomized-plugin/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/bagussubagja/flutter-liveness-detection-randomized-plugin/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/bagussubagja/flutter-liveness-detection-randomized-plugin/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/bagussubagja/flutter-liveness-detection-randomized-plugin/HEAD/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bagussubagja/flutter-liveness-detection-randomized-plugin/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/bagussubagja/flutter-liveness-detection-randomized-plugin/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/bagussubagja/flutter-liveness-detection-randomized-plugin/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/bagussubagja/flutter-liveness-detection-randomized-plugin/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/bagussubagja/flutter-liveness-detection-randomized-plugin/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/bagussubagja/flutter-liveness-detection-randomized-plugin/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/bagussubagja/flutter-liveness-detection-randomized-plugin/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/bagussubagja/flutter-liveness-detection-randomized-plugin/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/bagussubagja/flutter-liveness-detection-randomized-plugin/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/bagussubagja/flutter-liveness-detection-randomized-plugin/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/bagussubagja/flutter-liveness-detection-randomized-plugin/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/bagussubagja/flutter-liveness-detection-randomized-plugin/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/bagussubagja/flutter-liveness-detection-randomized-plugin/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/bagussubagja/flutter-liveness-detection-randomized-plugin/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/bagussubagja/flutter-liveness-detection-randomized-plugin/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/bagussubagja/flutter-liveness-detection-randomized-plugin/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/android/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bagussubagja/flutter-liveness-detection-randomized-plugin/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/bagussubagja/flutter-liveness-detection-randomized-plugin/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/lib/src/models/index.dart:
--------------------------------------------------------------------------------
1 | export 'liveness_detection_label_model.dart';
2 | export 'liveness_detection_step_item.dart';
3 | export 'liveness_detection_config.dart';
4 | export 'liveness_detection_threshold.dart';
5 | export 'liveness_detection_cooldown.dart';
--------------------------------------------------------------------------------
/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.7-all.zip
6 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/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/android/app/src/main/kotlin/com/bagussubagja/flutter_liveness_detection_randomized_plugin/flutter_liveness_detection_randomized_plugin_example/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.bagussubagja.flutter_liveness_detection_randomized_plugin.flutter_liveness_detection_randomized_plugin_example
2 |
3 | import io.flutter.embedding.android.FlutterActivity
4 |
5 | class MainActivity: FlutterActivity()
6 |
--------------------------------------------------------------------------------
/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/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/example/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/ios/Resources/PrivacyInfo.xcprivacy:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | NSPrivacyTrackingDomains
6 |
7 | NSPrivacyAccessedAPITypes
8 |
9 | NSPrivacyCollectedDataTypes
10 |
11 | NSPrivacyTracking
12 |
13 | NSCameraUsageDescription
14 | Camera access is required for liveness detection
15 |
16 |
17 |
--------------------------------------------------------------------------------
/ios/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | .vagrant/
3 | .sconsign.dblite
4 | .svn/
5 |
6 | .DS_Store
7 | *.swp
8 | profile
9 |
10 | DerivedData/
11 | build/
12 | GeneratedPluginRegistrant.h
13 | GeneratedPluginRegistrant.m
14 |
15 | .generated/
16 |
17 | *.pbxuser
18 | *.mode1v3
19 | *.mode2v3
20 | *.perspectivev3
21 |
22 | !default.pbxuser
23 | !default.mode1v3
24 | !default.mode2v3
25 | !default.perspectivev3
26 |
27 | xcuserdata
28 |
29 | *.moved-aside
30 |
31 | *.pyc
32 | *sync/
33 | Icon?
34 | .tags*
35 |
36 | /Flutter/Generated.xcconfig
37 | /Flutter/ephemeral/
38 | /Flutter/flutter_export_environment.sh
39 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 | migrate_working_dir/
12 |
13 | # IntelliJ related
14 | *.iml
15 | *.ipr
16 | *.iws
17 | .idea/
18 |
19 | # The .vscode folder contains launch configuration and tasks you configure in
20 | # VS Code which you may wish to be included in version control, so this line
21 | # is commented out by default.
22 | #.vscode/
23 |
24 | # Flutter/Dart/Pub related
25 | # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
26 | /pubspec.lock
27 | **/doc/api/
28 | .dart_tool/
29 | build/
30 |
31 | # FVM Version Cache
32 | .fvm/
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | # flutter_liveness_detection_randomized_plugin_example
2 |
3 | Demonstrates how to use the flutter_liveness_detection_randomized_plugin plugin.
4 |
5 | ## Getting Started
6 |
7 | This project is a starting point for a Flutter application.
8 |
9 | A few resources to get you started if this is your first Flutter project:
10 |
11 | - [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
12 | - [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
13 |
14 | For help getting started with Flutter development, view the
15 | [online documentation](https://docs.flutter.dev/), which offers tutorials,
16 | samples, guidance on mobile development, and a full API reference.
17 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/ios/Classes/FlutterLivenessDetectionRandomizedPlugin.swift:
--------------------------------------------------------------------------------
1 | import Flutter
2 | import UIKit
3 |
4 | public class FlutterLivenessDetectionRandomizedPlugin: NSObject, FlutterPlugin {
5 | public static func register(with registrar: FlutterPluginRegistrar) {
6 | let channel = FlutterMethodChannel(name: "flutter_liveness_detection_randomized_plugin", binaryMessenger: registrar.messenger())
7 | let instance = FlutterLivenessDetectionRandomizedPlugin()
8 | registrar.addMethodCallDelegate(instance, channel: channel)
9 | }
10 |
11 | public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
12 | switch call.method {
13 | case "getPlatformVersion":
14 | result("iOS " + UIDevice.current.systemVersion)
15 | default:
16 | result(FlutterMethodNotImplemented)
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/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.6.0" apply false
22 | id "org.jetbrains.kotlin.android" version "2.1.0" apply false
23 | }
24 |
25 | include ":app"
26 |
--------------------------------------------------------------------------------
/lib/flutter_liveness_detection_randomized_plugin_method_channel.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/foundation.dart';
2 | import 'package:flutter/services.dart';
3 |
4 | import 'flutter_liveness_detection_randomized_plugin_platform_interface.dart';
5 |
6 | /// An implementation of [FlutterLivenessDetectionRandomizedPluginPlatform] that uses method channels.
7 | class MethodChannelFlutterLivenessDetectionRandomizedPlugin extends FlutterLivenessDetectionRandomizedPluginPlatform {
8 | /// The method channel used to interact with the native platform.
9 | @visibleForTesting
10 | final methodChannel = const MethodChannel('flutter_liveness_detection_randomized_plugin');
11 |
12 | @override
13 | Future getPlatformVersion() async {
14 | final version = await methodChannel.invokeMethod('getPlatformVersion');
15 | return version;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/lib/src/core/constants/liveness_detection_step_constant.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_liveness_detection_randomized_plugin/index.dart';
2 |
3 | List stepLiveness = [
4 | LivenessDetectionStepItem(
5 | step: LivenessDetectionStep.blink,
6 | title: "Blink 2-3 Times",
7 | ),
8 | LivenessDetectionStepItem(
9 | step: LivenessDetectionStep.lookUp,
10 | title: "Look UP",
11 | ),
12 | LivenessDetectionStepItem(
13 | step: LivenessDetectionStep.lookDown,
14 | title: "Look DOWN",
15 | ),
16 | LivenessDetectionStepItem(
17 | step: LivenessDetectionStep.lookRight,
18 | title: "Look RIGHT",
19 | ),
20 | LivenessDetectionStepItem(
21 | step: LivenessDetectionStep.lookLeft,
22 | title: "Look LEFT",
23 | ),
24 | LivenessDetectionStepItem(
25 | step: LivenessDetectionStep.smile,
26 | title: "Smile",
27 | ),
28 | ];
29 |
--------------------------------------------------------------------------------
/example/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 | migrate_working_dir/
12 |
13 | # IntelliJ related
14 | *.iml
15 | *.ipr
16 | *.iws
17 | .idea/
18 |
19 | # The .vscode folder contains launch configuration and tasks you configure in
20 | # VS Code which you may wish to be included in version control, so this line
21 | # is commented out by default.
22 | #.vscode/
23 |
24 | # Flutter/Dart/Pub related
25 | **/doc/api/
26 | **/ios/Flutter/.last_build_id
27 | .dart_tool/
28 | .flutter-plugins
29 | .flutter-plugins-dependencies
30 | .pub-cache/
31 | .pub/
32 | /build/
33 |
34 | # Symbolication related
35 | app.*.symbols
36 |
37 | # Obfuscation related
38 | app.*.map.json
39 |
40 | # Android Studio will place build artifacts here
41 | /android/app/debug
42 | /android/app/profile
43 | /android/app/release
44 |
--------------------------------------------------------------------------------
/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 | 12.0
25 |
26 |
27 |
--------------------------------------------------------------------------------
/example/ios/RunnerTests/RunnerTests.swift:
--------------------------------------------------------------------------------
1 | import Flutter
2 | import UIKit
3 | import XCTest
4 |
5 |
6 | @testable import flutter_liveness_detection_randomized_plugin
7 |
8 | // This demonstrates a simple unit test of the Swift portion of this plugin's implementation.
9 | //
10 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest.
11 |
12 | class RunnerTests: XCTestCase {
13 |
14 | func testGetPlatformVersion() {
15 | let plugin = FlutterLivenessDetectionRandomizedPlugin()
16 |
17 | let call = FlutterMethodCall(methodName: "getPlatformVersion", arguments: [])
18 |
19 | let resultExpectation = expectation(description: "result block must be called.")
20 | plugin.handle(call) { result in
21 | XCTAssertEqual(result as! String, "iOS " + UIDevice.current.systemVersion)
22 | resultExpectation.fulfill()
23 | }
24 | waitForExpectations(timeout: 1)
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/example/test/widget_test.dart:
--------------------------------------------------------------------------------
1 | // This is a basic Flutter widget test.
2 | //
3 | // To perform an interaction with a widget in your test, use the WidgetTester
4 | // utility in the flutter_test package. For example, you can send tap and scroll
5 | // gestures. You can also use WidgetTester to find child widgets in the widget
6 | // tree, read text, and verify that the values of widget properties are correct.
7 |
8 | import 'package:flutter/material.dart';
9 | import 'package:flutter_test/flutter_test.dart';
10 |
11 | import 'package:flutter_liveness_detection_randomized_plugin_example/main.dart';
12 |
13 | void main() {
14 | testWidgets('Verify Platform version', (WidgetTester tester) async {
15 | // Build our app and trigger a frame.
16 | await tester.pumpWidget(const HomeView());
17 |
18 | // Verify that platform version is retrieved.
19 | expect(
20 | find.byWidgetPredicate(
21 | (Widget widget) => widget is Text &&
22 | widget.data!.startsWith('Running on:'),
23 | ),
24 | findsOneWidget,
25 | );
26 | });
27 | }
28 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/test/flutter_liveness_detection_randomized_plugin_method_channel_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/services.dart';
2 | import 'package:flutter_test/flutter_test.dart';
3 | import 'package:flutter_liveness_detection_randomized_plugin/flutter_liveness_detection_randomized_plugin_method_channel.dart';
4 |
5 | void main() {
6 | TestWidgetsFlutterBinding.ensureInitialized();
7 |
8 | MethodChannelFlutterLivenessDetectionRandomizedPlugin platform = MethodChannelFlutterLivenessDetectionRandomizedPlugin();
9 | const MethodChannel channel = MethodChannel('flutter_liveness_detection_randomized_plugin');
10 |
11 | setUp(() {
12 | TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
13 | channel,
14 | (MethodCall methodCall) async {
15 | return '42';
16 | },
17 | );
18 | });
19 |
20 | tearDown(() {
21 | TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(channel, null);
22 | });
23 |
24 | test('getPlatformVersion', () async {
25 | expect(await platform.getPlatformVersion(), '42');
26 | });
27 | }
28 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Bagus Subagja
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 |
--------------------------------------------------------------------------------
/example/integration_test/plugin_integration_test.dart:
--------------------------------------------------------------------------------
1 | // This is a basic Flutter integration test.
2 | //
3 | // Since integration tests run in a full Flutter application, they can interact
4 | // with the host side of a plugin implementation, unlike Dart unit tests.
5 | //
6 | // For more information about Flutter integration tests, please see
7 | // https://flutter.dev/to/integration-testing
8 |
9 |
10 | import 'package:flutter_test/flutter_test.dart';
11 | import 'package:integration_test/integration_test.dart';
12 |
13 | import 'package:flutter_liveness_detection_randomized_plugin/flutter_liveness_detection_randomized_plugin.dart';
14 |
15 | void main() {
16 | IntegrationTestWidgetsFlutterBinding.ensureInitialized();
17 |
18 | testWidgets('getPlatformVersion test', (WidgetTester tester) async {
19 | final FlutterLivenessDetectionRandomizedPlugin plugin = FlutterLivenessDetectionRandomizedPlugin.instance;
20 | final String? version = await plugin.getPlatformVersion();
21 | // The version string depends on the host platform running the test, so
22 | // just assert that some non-empty string is returned.
23 | expect(version?.isNotEmpty, true);
24 | });
25 | }
26 |
--------------------------------------------------------------------------------
/.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: "2663184aa79047d0a33a14a3b607954f8fdd8730"
8 | channel: "stable"
9 |
10 | project_type: plugin
11 |
12 | # Tracks metadata for the flutter migrate command
13 | migration:
14 | platforms:
15 | - platform: root
16 | create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
17 | base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
18 | - platform: android
19 | create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
20 | base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
21 | - platform: ios
22 | create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
23 | base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
24 |
25 | # User provided section
26 |
27 | # List of Local paths (relative to this file) that should be
28 | # ignored by the migrate tool.
29 | #
30 | # Files that are not part of the templates will be ignored by default.
31 | unmanaged_files:
32 | - 'lib/main.dart'
33 | - 'ios/Runner.xcodeproj/project.pbxproj'
34 |
--------------------------------------------------------------------------------
/lib/index.dart:
--------------------------------------------------------------------------------
1 | export 'dart:convert';
2 | export 'dart:io';
3 | export 'dart:async';
4 | export 'dart:math';
5 | export 'package:camera/camera.dart';
6 | export 'package:equatable/equatable.dart';
7 | export 'package:flutter/material.dart';
8 | export 'package:google_mlkit_face_detection/google_mlkit_face_detection.dart';
9 | export 'package:flutter_liveness_detection_randomized_plugin/src/models/liveness_detection_step_item.dart';
10 | export 'package:flutter_liveness_detection_randomized_plugin/src/models/liveness_detection_config.dart';
11 | export 'package:flutter_liveness_detection_randomized_plugin/src/models/liveness_detection_threshold.dart';
12 | export 'package:flutter_liveness_detection_randomized_plugin/src/models/liveness_detection_cooldown.dart';
13 | export 'package:flutter_liveness_detection_randomized_plugin/src/core/services/liveness_cooldown_service.dart';
14 | export 'package:flutter_liveness_detection_randomized_plugin/src/presentation/widgets/liveness_cooldown_widget.dart';
15 |
16 | export 'src/core/index.dart';
17 | export './flutter_liveness_detection_randomized_plugin.dart';
18 | export './flutter_liveness_detection_randomized_plugin_method_channel.dart';
19 | export './flutter_liveness_detection_randomized_plugin_platform_interface.dart';
--------------------------------------------------------------------------------
/lib/src/core/utils/machine_learning_kit_helper.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_liveness_detection_randomized_plugin/index.dart';
2 |
3 | class MachineLearningKitHelper {
4 | MachineLearningKitHelper._privateConstructor();
5 | static final MachineLearningKitHelper instance =
6 | MachineLearningKitHelper._privateConstructor();
7 |
8 | final FaceDetector faceDetector = FaceDetector(
9 | options: FaceDetectorOptions(
10 | enableContours: true,
11 | enableClassification: true,
12 | enableLandmarks: true,
13 | enableTracking: true,
14 | performanceMode: FaceDetectorMode.accurate,
15 | ),
16 | );
17 |
18 | Future> processInputImage(InputImage imgFile) async {
19 | const maxAttempts = 3;
20 |
21 | for (var attempt = 0; attempt < maxAttempts; attempt++) {
22 | try {
23 | final List faces = await faceDetector.processImage(imgFile);
24 | if (faces.isNotEmpty) return faces;
25 | } catch (e) {
26 | debugPrint('Face detection error (attempt ${attempt + 1}): $e');
27 | if (e.toString().contains('InputImageConverterError') ||
28 | e.toString().contains('ImageFormat is not supported')) {
29 | return [];
30 | }
31 | }
32 | }
33 |
34 | return [];
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/lib/src/models/liveness_detection_label_model.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 |
3 | LivenessDetectionLabelModel livenessDetectionLabelModelFromJson(String str) => LivenessDetectionLabelModel.fromJson(json.decode(str));
4 |
5 | String livenessDetectionLabelModelToJson(LivenessDetectionLabelModel data) => json.encode(data.toJson());
6 |
7 | class LivenessDetectionLabelModel {
8 | String? smile;
9 | String? lookUp;
10 | String? lookDown;
11 | String? lookLeft;
12 | String? lookRight;
13 | String? blink;
14 |
15 | LivenessDetectionLabelModel({
16 | this.smile,
17 | this.lookUp,
18 | this.lookDown,
19 | this.lookLeft,
20 | this.lookRight,
21 | this.blink,
22 | });
23 |
24 | factory LivenessDetectionLabelModel.fromJson(Map json) => LivenessDetectionLabelModel(
25 | smile: json["smile"],
26 | lookUp: json["lookUp"],
27 | lookDown: json["lookDown"],
28 | lookLeft: json["lookLeft"],
29 | lookRight: json["lookRight"],
30 | blink: json["blink"],
31 | );
32 |
33 | Map toJson() => {
34 | "smile": smile,
35 | "lookUp": lookUp,
36 | "lookDown": lookDown,
37 | "lookLeft": lookLeft,
38 | "lookRight": lookRight,
39 | "blink": blink,
40 | };
41 | }
42 |
--------------------------------------------------------------------------------
/android/src/test/kotlin/com/bagussubagja/flutter_liveness_detection_randomized_plugin/flutter_liveness_detection_randomized_plugin/FlutterLivenessDetectionRandomizedPluginTest.kt:
--------------------------------------------------------------------------------
1 | package com.bagussubagja.flutter_liveness_detection_randomized_plugin.flutter_liveness_detection_randomized_plugin
2 |
3 | import io.flutter.plugin.common.MethodCall
4 | import io.flutter.plugin.common.MethodChannel
5 | import kotlin.test.Test
6 | import org.mockito.Mockito
7 |
8 | /*
9 | * This demonstrates a simple unit test of the Kotlin portion of this plugin's implementation.
10 | *
11 | * Once you have built the plugin's example app, you can run these tests from the command
12 | * line by running `./gradlew testDebugUnitTest` in the `example/android/` directory, or
13 | * you can run them directly from IDEs that support JUnit such as Android Studio.
14 | */
15 |
16 | internal class FlutterLivenessDetectionRandomizedPluginTest {
17 | @Test
18 | fun onMethodCall_getPlatformVersion_returnsExpectedValue() {
19 | val plugin = FlutterLivenessDetectionRandomizedPlugin()
20 |
21 | val call = MethodCall("getPlatformVersion", null)
22 | val mockResult: MethodChannel.Result = Mockito.mock(MethodChannel.Result::class.java)
23 | plugin.onMethodCall(call, mockResult)
24 |
25 | Mockito.verify(mockResult).success("Android " + android.os.Build.VERSION.RELEASE)
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: flutter_liveness_detection_randomized_plugin
2 | description: "A Flutter plugin for liveness detection with randomized challenge response method"
3 | version: 1.1.0
4 | homepage: "https://bagussubagja.vercel.app"
5 | repository: "https://github.com/bagussubagja/flutter-liveness-detection-randomized-plugin"
6 |
7 | environment:
8 | sdk: '>=3.10.0 <4.0.0'
9 | flutter: '>=3.38.1'
10 |
11 | dependencies:
12 | flutter:
13 | sdk: flutter
14 | plugin_platform_interface: ^2.0.2
15 | google_mlkit_face_detection: ^0.13.1
16 | camera: 0.10.6
17 | equatable: ^2.0.5
18 | image: ^4.0.15
19 | path_provider: ^2.1.3
20 | lottie: ^2.4.0
21 | collection: ^1.18.0
22 | screen_brightness: ^2.1.2
23 | shared_preferences: ^2.2.2
24 |
25 | dependency_overrides:
26 | camera_android_camerax: 0.6.10+1
27 |
28 | dev_dependencies:
29 | flutter_test:
30 | sdk: flutter
31 | flutter_lints: ^4.0.0
32 |
33 | flutter:
34 | plugin:
35 | platforms:
36 | android:
37 | package: com.bagussubagja.flutter_liveness_detection_randomized_plugin.flutter_liveness_detection_randomized_plugin
38 | pluginClass: FlutterLivenessDetectionRandomizedPlugin
39 | ios:
40 | pluginClass: FlutterLivenessDetectionRandomizedPlugin
41 |
42 | assets:
43 | - packages/flutter_liveness_detection_randomized_plugin/src/core/assets/face-id-anim.json
44 | - packages/flutter_liveness_detection_randomized_plugin/src/core/assets/face-detected.json
--------------------------------------------------------------------------------
/lib/flutter_liveness_detection_randomized_plugin_platform_interface.dart:
--------------------------------------------------------------------------------
1 | import 'package:plugin_platform_interface/plugin_platform_interface.dart';
2 |
3 | import 'flutter_liveness_detection_randomized_plugin_method_channel.dart';
4 |
5 | abstract class FlutterLivenessDetectionRandomizedPluginPlatform extends PlatformInterface {
6 | /// Constructs a FlutterLivenessDetectionRandomizedPluginPlatform.
7 | FlutterLivenessDetectionRandomizedPluginPlatform() : super(token: _token);
8 |
9 | static final Object _token = Object();
10 |
11 | static FlutterLivenessDetectionRandomizedPluginPlatform _instance = MethodChannelFlutterLivenessDetectionRandomizedPlugin();
12 |
13 | /// The default instance of [FlutterLivenessDetectionRandomizedPluginPlatform] to use.
14 | ///
15 | /// Defaults to [MethodChannelFlutterLivenessDetectionRandomizedPlugin].
16 | static FlutterLivenessDetectionRandomizedPluginPlatform get instance => _instance;
17 |
18 | /// Platform-specific implementations should set this with their own
19 | /// platform-specific class that extends [FlutterLivenessDetectionRandomizedPluginPlatform] when
20 | /// they register themselves.
21 | static set instance(FlutterLivenessDetectionRandomizedPluginPlatform instance) {
22 | PlatformInterface.verifyToken(instance, _token);
23 | _instance = instance;
24 | }
25 |
26 | Future getPlatformVersion() {
27 | throw UnimplementedError('platformVersion() has not been implemented.');
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": "flutter-liveness-detection-randomized-plugin",
9 | "request": "launch",
10 | "type": "dart"
11 | },
12 | {
13 | "name": "flutter-liveness-detection-randomized-plugin (profile mode)",
14 | "request": "launch",
15 | "type": "dart",
16 | "flutterMode": "profile"
17 | },
18 | {
19 | "name": "flutter-liveness-detection-randomized-plugin (release mode)",
20 | "request": "launch",
21 | "type": "dart",
22 | "flutterMode": "release"
23 | },
24 | {
25 | "name": "example",
26 | "cwd": "example",
27 | "request": "launch",
28 | "type": "dart"
29 | },
30 | {
31 | "name": "example (profile mode)",
32 | "cwd": "example",
33 | "request": "launch",
34 | "type": "dart",
35 | "flutterMode": "profile"
36 | },
37 | {
38 | "name": "example (release mode)",
39 | "cwd": "example",
40 | "request": "launch",
41 | "type": "dart",
42 | "flutterMode": "release"
43 | }
44 | ]
45 | }
--------------------------------------------------------------------------------
/example/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | # This file configures the analyzer, which statically analyzes Dart code to
2 | # check for errors, warnings, and lints.
3 | #
4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled
5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
6 | # invoked from the command line by running `flutter analyze`.
7 |
8 | # The following line activates a set of recommended lints for Flutter apps,
9 | # packages, and plugins designed to encourage good coding practices.
10 | include: package:flutter_lints/flutter.yaml
11 |
12 | linter:
13 | # The lint rules applied to this project can be customized in the
14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml`
15 | # included above or to enable additional rules. A list of all available lints
16 | # and their documentation is published at https://dart.dev/lints.
17 | #
18 | # Instead of disabling a lint rule for the entire project in the
19 | # section below, it can also be suppressed for a single line of code
20 | # or a specific dart file by using the `// ignore: name_of_lint` and
21 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file
22 | # producing the lint.
23 | rules:
24 | # avoid_print: false # Uncomment to disable the `avoid_print` rule
25 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
26 |
27 | # Additional information about this file can be found at
28 | # https://dart.dev/guides/language/analysis-options
29 |
--------------------------------------------------------------------------------
/example/ios/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment this line to define a global platform for your project
2 | # platform :ios, '12.0'
3 |
4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true'
6 |
7 | project 'Runner', {
8 | 'Debug' => :debug,
9 | 'Profile' => :release,
10 | 'Release' => :release,
11 | }
12 |
13 | def flutter_root
14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
15 | unless File.exist?(generated_xcode_build_settings_path)
16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
17 | end
18 |
19 | File.foreach(generated_xcode_build_settings_path) do |line|
20 | matches = line.match(/FLUTTER_ROOT\=(.*)/)
21 | return matches[1].strip if matches
22 | end
23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
24 | end
25 |
26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
27 |
28 | flutter_ios_podfile_setup
29 |
30 | target 'Runner' do
31 | use_frameworks!
32 | use_modular_headers!
33 |
34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
35 | target 'RunnerTests' do
36 | inherit! :search_paths
37 | end
38 | end
39 |
40 | post_install do |installer|
41 | installer.pods_project.targets.each do |target|
42 | flutter_additional_ios_build_settings(target)
43 | end
44 | end
45 |
--------------------------------------------------------------------------------
/lib/src/models/liveness_detection_config.dart:
--------------------------------------------------------------------------------
1 | import 'package:camera/camera.dart';
2 | import 'package:flutter_liveness_detection_randomized_plugin/src/models/liveness_detection_label_model.dart';
3 |
4 | class LivenessDetectionConfig {
5 | final bool startWithInfoScreen;
6 | final int? durationLivenessVerify;
7 | final bool showDurationUiText;
8 | final bool useCustomizedLabel;
9 | final LivenessDetectionLabelModel? customizedLabel;
10 | final bool isEnableMaxBrightness;
11 | final int imageQuality;
12 | final ResolutionPreset cameraResolution;
13 | final bool enableCooldownOnFailure;
14 | final int maxFailedAttempts;
15 | final int cooldownMinutes;
16 | final bool isEnableSnackBar;
17 | final bool shuffleListWithSmileLast;
18 | final bool showCurrentStep;
19 | final bool isDarkMode;
20 |
21 | LivenessDetectionConfig({
22 | this.startWithInfoScreen = false,
23 | this.durationLivenessVerify = 45,
24 | this.showDurationUiText = false,
25 | this.useCustomizedLabel = false,
26 | this.customizedLabel,
27 | this.isEnableMaxBrightness = true,
28 | this.imageQuality = 100,
29 | this.cameraResolution = ResolutionPreset.high,
30 | this.enableCooldownOnFailure = true,
31 | this.maxFailedAttempts = 3,
32 | this.cooldownMinutes = 10,
33 | this.isEnableSnackBar = true,
34 | this.shuffleListWithSmileLast = true,
35 | this.showCurrentStep = false,
36 | this.isDarkMode = true,
37 | }) : assert(
38 | !useCustomizedLabel || customizedLabel != null,
39 | 'customizedLabel must not be null when useCustomizedLabel is true',
40 | );
41 | }
42 |
--------------------------------------------------------------------------------
/ios/flutter_liveness_detection_randomized_plugin.podspec:
--------------------------------------------------------------------------------
1 | #
2 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
3 | # Run `pod lib lint flutter_liveness_detection_randomized_plugin.podspec` to validate before publishing.
4 | #
5 | Pod::Spec.new do |s|
6 | s.name = 'flutter_liveness_detection_randomized_plugin'
7 | s.version = '0.0.1'
8 | s.summary = 'A Flutter plugin for liveness detection with randomized challenge response method'
9 | s.description = <<-DESC
10 | A Flutter plugin for liveness detection with randomized challenge response method
11 | DESC
12 | s.homepage = 'http://example.com'
13 | s.license = { :file => '../LICENSE' }
14 | s.author = { 'Your Company' => 'email@example.com' }
15 | s.source = { :path => '.' }
16 | s.source_files = 'Classes/**/*'
17 | s.dependency 'Flutter'
18 | s.platform = :ios, '12.0'
19 |
20 | # Flutter.framework does not contain a i386 slice.
21 | s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' }
22 | s.swift_version = '5.0'
23 |
24 | # If your plugin requires a privacy manifest, for example if it uses any
25 | # required reason APIs, update the PrivacyInfo.xcprivacy file to describe your
26 | # plugin's privacy impact, and then uncomment this line. For more information,
27 | # see https://developer.apple.com/documentation/bundleresources/privacy_manifest_files
28 | # s.resource_bundles = {'flutter_liveness_detection_randomized_plugin_privacy' => ['Resources/PrivacyInfo.xcprivacy']}
29 | end
30 |
--------------------------------------------------------------------------------
/lib/src/models/liveness_detection_cooldown.dart:
--------------------------------------------------------------------------------
1 | class LivenessDetectionCooldown {
2 | final int failedAttempts;
3 | final DateTime? cooldownEndTime;
4 | final bool isInCooldown;
5 |
6 | const LivenessDetectionCooldown({
7 | this.failedAttempts = 0,
8 | this.cooldownEndTime,
9 | this.isInCooldown = false,
10 | });
11 |
12 | LivenessDetectionCooldown copyWith({
13 | int? failedAttempts,
14 | DateTime? cooldownEndTime,
15 | bool? isInCooldown,
16 | }) {
17 | return LivenessDetectionCooldown(
18 | failedAttempts: failedAttempts ?? this.failedAttempts,
19 | cooldownEndTime: cooldownEndTime ?? this.cooldownEndTime,
20 | isInCooldown: isInCooldown ?? this.isInCooldown,
21 | );
22 | }
23 |
24 | Duration get remainingCooldownTime {
25 | if (cooldownEndTime == null || !isInCooldown) {
26 | return Duration.zero;
27 | }
28 | final remaining = cooldownEndTime!.difference(DateTime.now());
29 | return remaining.isNegative ? Duration.zero : remaining;
30 | }
31 |
32 | Map toJson() {
33 | return {
34 | 'failedAttempts': failedAttempts,
35 | 'cooldownEndTime': cooldownEndTime?.millisecondsSinceEpoch,
36 | 'isInCooldown': isInCooldown,
37 | };
38 | }
39 |
40 | factory LivenessDetectionCooldown.fromJson(Map json) {
41 | return LivenessDetectionCooldown(
42 | failedAttempts: json['failedAttempts'] ?? 0,
43 | cooldownEndTime: json['cooldownEndTime'] != null
44 | ? DateTime.fromMillisecondsSinceEpoch(json['cooldownEndTime'])
45 | : null,
46 | isInCooldown: json['isInCooldown'] ?? false,
47 | );
48 | }
49 | }
--------------------------------------------------------------------------------
/test/flutter_liveness_detection_randomized_plugin_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_test/flutter_test.dart';
2 | import 'package:flutter_liveness_detection_randomized_plugin/flutter_liveness_detection_randomized_plugin.dart';
3 | import 'package:flutter_liveness_detection_randomized_plugin/flutter_liveness_detection_randomized_plugin_platform_interface.dart';
4 | import 'package:flutter_liveness_detection_randomized_plugin/flutter_liveness_detection_randomized_plugin_method_channel.dart';
5 | import 'package:plugin_platform_interface/plugin_platform_interface.dart';
6 |
7 | class MockFlutterLivenessDetectionRandomizedPluginPlatform
8 | with MockPlatformInterfaceMixin
9 | implements FlutterLivenessDetectionRandomizedPluginPlatform {
10 |
11 | @override
12 | Future getPlatformVersion() => Future.value('42');
13 | }
14 |
15 | void main() {
16 | final FlutterLivenessDetectionRandomizedPluginPlatform initialPlatform = FlutterLivenessDetectionRandomizedPluginPlatform.instance;
17 |
18 | test('$MethodChannelFlutterLivenessDetectionRandomizedPlugin is the default instance', () {
19 | expect(initialPlatform, isInstanceOf());
20 | });
21 |
22 | test('getPlatformVersion', () async {
23 | FlutterLivenessDetectionRandomizedPlugin flutterLivenessDetectionRandomizedPlugin = FlutterLivenessDetectionRandomizedPlugin.instance;
24 | MockFlutterLivenessDetectionRandomizedPluginPlatform fakePlatform = MockFlutterLivenessDetectionRandomizedPluginPlatform();
25 | FlutterLivenessDetectionRandomizedPluginPlatform.instance = fakePlatform;
26 |
27 | expect(await flutterLivenessDetectionRandomizedPlugin.getPlatformVersion(), '42');
28 | });
29 | }
30 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/com/bagussubagja/flutter_liveness_detection_randomized_plugin/flutter_liveness_detection_randomized_plugin/FlutterLivenessDetectionRandomizedPlugin.kt:
--------------------------------------------------------------------------------
1 | package com.bagussubagja.flutter_liveness_detection_randomized_plugin.flutter_liveness_detection_randomized_plugin
2 |
3 | import androidx.annotation.NonNull
4 |
5 | import io.flutter.embedding.engine.plugins.FlutterPlugin
6 | import io.flutter.plugin.common.MethodCall
7 | import io.flutter.plugin.common.MethodChannel
8 | import io.flutter.plugin.common.MethodChannel.MethodCallHandler
9 | import io.flutter.plugin.common.MethodChannel.Result
10 |
11 | /** FlutterLivenessDetectionRandomizedPlugin */
12 | class FlutterLivenessDetectionRandomizedPlugin: FlutterPlugin, MethodCallHandler {
13 | /// The MethodChannel that will the communication between Flutter and native Android
14 | ///
15 | /// This local reference serves to register the plugin with the Flutter Engine and unregister it
16 | /// when the Flutter Engine is detached from the Activity
17 | private lateinit var channel : MethodChannel
18 |
19 | override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
20 | channel = MethodChannel(flutterPluginBinding.binaryMessenger, "flutter_liveness_detection_randomized_plugin")
21 | channel.setMethodCallHandler(this)
22 | }
23 |
24 | override fun onMethodCall(call: MethodCall, result: Result) {
25 | if (call.method == "getPlatformVersion") {
26 | result.success("Android ${android.os.Build.VERSION.RELEASE}")
27 | } else {
28 | result.notImplemented()
29 | }
30 | }
31 |
32 | override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
33 | channel.setMethodCallHandler(null)
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/example/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id "com.android.application"
3 | id "kotlin-android"
4 | // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
5 | id "dev.flutter.flutter-gradle-plugin"
6 | }
7 |
8 | android {
9 | namespace = "com.bagussubagja.flutter_liveness_detection_randomized_plugin.flutter_liveness_detection_randomized_plugin_example"
10 | compileSdk = 36
11 | ndkVersion = "28.2.13676358"
12 |
13 | compileOptions {
14 | sourceCompatibility = JavaVersion.VERSION_1_8
15 | targetCompatibility = JavaVersion.VERSION_1_8
16 | }
17 |
18 | kotlinOptions {
19 | jvmTarget = JavaVersion.VERSION_1_8
20 | }
21 |
22 | defaultConfig {
23 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
24 | applicationId = "com.bagussubagja.flutter_liveness_detection_randomized_plugin.flutter_liveness_detection_randomized_plugin_example"
25 | // You can update the following values to match your application needs.
26 | // For more information, see: https://flutter.dev/to/review-gradle-config.
27 | minSdk = 24
28 | targetSdk = flutter.targetSdkVersion
29 | versionCode = flutter.versionCode
30 | versionName = flutter.versionName
31 | }
32 |
33 | packagingOptions {
34 | jniLibs {
35 | useLegacyPackaging true
36 | }
37 | }
38 |
39 | buildTypes {
40 | release {
41 | // TODO: Add your own signing config for the release build.
42 | // Signing with the debug keys for now, so `flutter run --release` works.
43 | signingConfig = signingConfigs.debug
44 | }
45 | }
46 | }
47 |
48 | flutter {
49 | source = "../.."
50 | }
51 |
--------------------------------------------------------------------------------
/example/ios/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CADisableMinimumFrameDurationOnPhone
6 |
7 | CFBundleDevelopmentRegion
8 | $(DEVELOPMENT_LANGUAGE)
9 | CFBundleDisplayName
10 | Flutter Liveness Detection Randomized Plugin
11 | CFBundleExecutable
12 | $(EXECUTABLE_NAME)
13 | CFBundleIdentifier
14 | $(PRODUCT_BUNDLE_IDENTIFIER)
15 | CFBundleInfoDictionaryVersion
16 | 6.0
17 | CFBundleName
18 | flutter_liveness_detection_randomized_plugin_example
19 | CFBundlePackageType
20 | APPL
21 | CFBundleShortVersionString
22 | $(FLUTTER_BUILD_NAME)
23 | CFBundleSignature
24 | ????
25 | CFBundleVersion
26 | $(FLUTTER_BUILD_NUMBER)
27 | LSRequiresIPhoneOS
28 |
29 | UIApplicationSupportsIndirectInputEvents
30 |
31 | UILaunchStoryboardName
32 | LaunchScreen
33 | UIMainStoryboardFile
34 | Main
35 | UISupportedInterfaceOrientations
36 |
37 | UIInterfaceOrientationPortrait
38 | UIInterfaceOrientationLandscapeLeft
39 | UIInterfaceOrientationLandscapeRight
40 |
41 | UISupportedInterfaceOrientations~ipad
42 |
43 | UIInterfaceOrientationPortrait
44 | UIInterfaceOrientationPortraitUpsideDown
45 | UIInterfaceOrientationLandscapeLeft
46 | UIInterfaceOrientationLandscapeRight
47 |
48 | NSCameraUsageDescription
49 | Camera access is required for liveness detection
50 |
51 |
52 |
--------------------------------------------------------------------------------
/lib/src/models/liveness_detection_step_item.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_liveness_detection_randomized_plugin/index.dart';
2 |
3 | class LivenessDetectionStepItem {
4 | final LivenessDetectionStep step;
5 | final String title;
6 | final double? thresholdToCheck;
7 |
8 | LivenessDetectionStepItem({
9 | required this.step,
10 | required this.title,
11 | this.thresholdToCheck,
12 | });
13 |
14 | LivenessDetectionStepItem copyWith({
15 | LivenessDetectionStep? step,
16 | String? title,
17 | double? thresholdToCheck,
18 | }) {
19 | return LivenessDetectionStepItem(
20 | step: step ?? this.step,
21 | title: title ?? this.title,
22 | thresholdToCheck: thresholdToCheck ?? this.thresholdToCheck,
23 | );
24 | }
25 | Map toMap() {
26 | final result = {};
27 |
28 | result.addAll({'step': step.index});
29 | result.addAll({'title': title});
30 | if (thresholdToCheck != null) {
31 | result.addAll({'thresholdToCheck': thresholdToCheck});
32 | }
33 |
34 | return result;
35 | }
36 |
37 | factory LivenessDetectionStepItem.fromMap(Map map) {
38 | return LivenessDetectionStepItem(
39 | step: LivenessDetectionStep.values[map['step'] ?? 0],
40 | title: map['title'] ?? '',
41 | thresholdToCheck: map['thresholdToCheck']?.toDouble(),
42 | );
43 | }
44 |
45 | String toJson() => json.encode(toMap());
46 |
47 | factory LivenessDetectionStepItem.fromJson(String source) =>
48 | LivenessDetectionStepItem.fromMap(json.decode(source));
49 |
50 | @override
51 | String toString() {
52 | return 'Liveness Detection (step: $step, title: $title, thresholdToCheck: $thresholdToCheck)';
53 | }
54 |
55 | @override
56 | bool operator ==(Object other) {
57 | if (identical(this, other)) return true;
58 |
59 | return other is LivenessDetectionStepItem &&
60 | other.step == step &&
61 | other.title == title &&
62 | other.thresholdToCheck == thresholdToCheck;
63 | }
64 |
65 | @override
66 | int get hashCode {
67 | return step.hashCode ^
68 | title.hashCode ^
69 | thresholdToCheck.hashCode;
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/lib/flutter_liveness_detection_randomized_plugin.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_liveness_detection_randomized_plugin/index.dart';
2 |
3 | class FlutterLivenessDetectionRandomizedPlugin {
4 | FlutterLivenessDetectionRandomizedPlugin._privateConstructor();
5 | static final FlutterLivenessDetectionRandomizedPlugin instance =
6 | FlutterLivenessDetectionRandomizedPlugin._privateConstructor();
7 | final List _thresholds = [];
8 |
9 | List get thresholdConfig {
10 | return _thresholds;
11 | }
12 |
13 | Future livenessDetection({
14 | required BuildContext context,
15 | required LivenessDetectionConfig config,
16 | }) async {
17 | if (config.enableCooldownOnFailure) {
18 | LivenessCooldownService.instance.configure(
19 | maxFailedAttempts: config.maxFailedAttempts,
20 | cooldownMinutes: config.cooldownMinutes,
21 | );
22 | final cooldownState = await LivenessCooldownService.instance.getCooldownState();
23 | if (cooldownState.isInCooldown && context.mounted) {
24 | await Navigator.of(context).push(
25 | MaterialPageRoute(
26 | builder: (context) => LivenessCooldownWidget(
27 | cooldownState: cooldownState,
28 | isDarkMode: config.isDarkMode,
29 | maxFailedAttempts: config.maxFailedAttempts,
30 | ),
31 | ),
32 | );
33 | return null;
34 | }
35 | }
36 |
37 | if (!context.mounted) return null;
38 |
39 | final String? capturedFacePath = await Navigator.of(context).push(
40 | MaterialPageRoute(
41 | builder: (context) => LivenessDetectionView(
42 | config: config,
43 | ),
44 | ),
45 | );
46 |
47 | if (config.enableCooldownOnFailure) {
48 | if (capturedFacePath != null) {
49 | await LivenessCooldownService.instance.recordSuccessfulAttempt();
50 | } else {
51 | await LivenessCooldownService.instance.recordFailedAttempt();
52 | }
53 | }
54 |
55 | return capturedFacePath;
56 | }
57 |
58 | Future getPlatformVersion() {
59 | return FlutterLivenessDetectionRandomizedPluginPlatform.instance
60 | .getPlatformVersion();
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | group = "com.bagussubagja.flutter_liveness_detection_randomized_plugin.flutter_liveness_detection_randomized_plugin"
2 | version = "1.0-SNAPSHOT"
3 |
4 | buildscript {
5 | ext.kotlin_version = "1.8.22"
6 | repositories {
7 | google()
8 | mavenCentral()
9 | }
10 |
11 | dependencies {
12 | classpath("com.android.tools.build:gradle:8.1.0")
13 | classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version")
14 | }
15 | }
16 |
17 | allprojects {
18 | repositories {
19 | google()
20 | mavenCentral()
21 | }
22 | }
23 |
24 | apply plugin: "com.android.library"
25 | apply plugin: "kotlin-android"
26 |
27 | android {
28 | if (project.android.hasProperty("namespace")) {
29 | namespace = "com.bagussubagja.flutter_liveness_detection_randomized_plugin.flutter_liveness_detection_randomized_plugin"
30 | }
31 |
32 | compileSdk = 34
33 |
34 | compileOptions {
35 | sourceCompatibility = JavaVersion.VERSION_1_8
36 | targetCompatibility = JavaVersion.VERSION_1_8
37 | }
38 |
39 | packagingOptions {
40 | jniLibs {
41 | useLegacyPackaging true
42 | }
43 | }
44 |
45 | kotlinOptions {
46 | jvmTarget = JavaVersion.VERSION_1_8
47 | }
48 |
49 | sourceSets {
50 | main.java.srcDirs += "src/main/kotlin"
51 | test.java.srcDirs += "src/test/kotlin"
52 | }
53 |
54 | defaultConfig {
55 | minSdk = 21
56 | }
57 |
58 | dependencies {
59 | testImplementation("org.jetbrains.kotlin:kotlin-test")
60 | testImplementation("org.mockito:mockito-core:5.0.0")
61 | }
62 |
63 | testOptions {
64 | unitTests.all {
65 | useJUnitPlatform()
66 |
67 | testLogging {
68 | events "passed", "skipped", "failed", "standardOut", "standardError"
69 | outputs.upToDateWhen {false}
70 | showStandardStreams = true
71 | }
72 | }
73 | }
74 | }
75 |
76 | // Add this block for subprojects
77 | subprojects {
78 | afterEvaluate { project ->
79 | if (project.hasProperty('android')) {
80 | project.android {
81 | if (namespace == null) {
82 | namespace project.group
83 | }
84 | }
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/example_customized_label_usage.dart:
--------------------------------------------------------------------------------
1 | // Example of correct customizedLabel usage
2 |
3 | // ignore_for_file: unused_local_variable
4 |
5 | import 'package:flutter_liveness_detection_randomized_plugin/index.dart';
6 |
7 | void exampleUsage() {
8 | // ✅ CORRECT: Skip blink and lookDown, use custom labels for others
9 | final config1 = LivenessDetectionConfig(
10 | useCustomizedLabel: true,
11 | customizedLabel: LivenessDetectionLabelModel(
12 | blink: '', // Empty string = skip this challenge
13 | lookUp: 'Tengok Atas', // Custom label
14 | lookDown: '', // Empty string = skip this challenge
15 | lookLeft: null, // null = use default label "Look LEFT"
16 | lookRight: null, // null = use default label "Look RIGHT"
17 | smile: 'Senyum Dong!', // Custom label
18 | ),
19 | );
20 | // Result: Only lookUp, lookLeft, lookRight, smile challenges will appear
21 |
22 | // ✅ CORRECT: Use all challenges with custom labels
23 | final config2 = LivenessDetectionConfig(
24 | useCustomizedLabel: true,
25 | customizedLabel: LivenessDetectionLabelModel(
26 | blink: 'Kedipkan Mata',
27 | lookUp: 'Lihat Atas',
28 | lookDown: 'Lihat Bawah',
29 | lookLeft: 'Lihat Kiri',
30 | lookRight: 'Lihat Kanan',
31 | smile: 'Tersenyum',
32 | ),
33 | );
34 | // Result: All 6 challenges with Indonesian labels
35 |
36 | // ✅ CORRECT: Mix of custom, default, and skipped
37 | final config3 = LivenessDetectionConfig(
38 | useCustomizedLabel: true,
39 | customizedLabel: LivenessDetectionLabelModel(
40 | blink: null, // Use default "Blink 2-3 Times"
41 | lookUp: '', // Skip
42 | lookDown: '', // Skip
43 | lookLeft: 'Turn Left Please',
44 | lookRight: 'Turn Right Please',
45 | smile: null, // Use default "Smile"
46 | ),
47 | );
48 | // Result: Only blink, lookLeft, lookRight, smile challenges
49 |
50 | // ❌ WRONG: This will throw assertion error
51 | // final configWrong = LivenessDetectionConfig(
52 | // useCustomizedLabel: true,
53 | // customizedLabel: null, // ERROR: Cannot be null when useCustomizedLabel is true
54 | // );
55 |
56 | // ✅ CORRECT: Use default behavior
57 | final config4 = LivenessDetectionConfig(
58 | useCustomizedLabel: false, // customizedLabel will be ignored
59 | shuffleListWithSmileLast: true, // This works with default steps
60 | );
61 | }
62 |
--------------------------------------------------------------------------------
/example/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
15 |
19 |
23 |
24 |
25 |
26 |
27 |
28 |
30 |
33 |
34 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/.github/workflows/ci-cd.yml:
--------------------------------------------------------------------------------
1 | name: CI/CD Pipeline
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 | pull_request:
7 | branches: [ master ]
8 |
9 | jobs:
10 | analyze:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v4
14 |
15 | - name: Setup Flutter
16 | uses: subosito/flutter-action@v2
17 | with:
18 | flutter-version: '3.24.5'
19 | channel: 'stable'
20 |
21 | - name: Get dependencies
22 | run: flutter pub get
23 |
24 | - name: Analyze code
25 | run: flutter analyze --fatal-infos
26 |
27 | - name: Run tests
28 | run: flutter test
29 |
30 | build-and-release:
31 | needs: analyze
32 | runs-on: ubuntu-latest
33 | if: github.ref == 'refs/heads/master' && github.event_name == 'push'
34 |
35 | steps:
36 | - uses: actions/checkout@v4
37 |
38 | - name: Setup Flutter
39 | uses: subosito/flutter-action@v2
40 | with:
41 | flutter-version: '3.24.5'
42 | channel: 'stable'
43 |
44 | - name: Setup Java
45 | uses: actions/setup-java@v4
46 | with:
47 | distribution: 'zulu'
48 | java-version: '17'
49 |
50 | - name: Get plugin dependencies
51 | run: flutter pub get
52 |
53 | - name: Get example dependencies
54 | working-directory: example
55 | run: flutter pub get
56 |
57 | - name: Build Android APK
58 | working-directory: example
59 | run: flutter build apk --release
60 |
61 | - name: Extract version from pubspec.yaml
62 | id: version
63 | run: |
64 | VERSION=$(grep '^version:' pubspec.yaml | sed 's/version: //')
65 | echo "version=$VERSION" >> $GITHUB_OUTPUT
66 | echo "Version: $VERSION"
67 |
68 | - name: Create Release
69 | uses: softprops/action-gh-release@v1
70 | with:
71 | tag_name: v${{ steps.version.outputs.version }}
72 | name: Release v${{ steps.version.outputs.version }}
73 | body: |
74 | ## Flutter Liveness Detection Plugin v${{ steps.version.outputs.version }}
75 |
76 | ### Demo App
77 | Download the demo app to try the liveness detection features:
78 | - Android APK: See assets below
79 |
80 | ### Changes
81 | See [CHANGELOG.md](CHANGELOG.md) for detailed changes.
82 | files: |
83 | example/build/app/outputs/flutter-apk/app-release.apk
84 | draft: false
85 | prerelease: false
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/lib/src/presentation/widgets/circular_progress_widget/circular_progress_painter.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math' as math;
2 |
3 | import 'package:flutter/material.dart';
4 |
5 | class CircularProgressPainter extends CustomPainter {
6 | final double currentStep;
7 | final double maxStep;
8 | final double widthLine;
9 | final double heightLine;
10 | final Color? selectedColor;
11 | final Color? unselectedColor;
12 | final Gradient? gradientColor;
13 |
14 | CircularProgressPainter({
15 | required this.maxStep,
16 | required this.widthLine,
17 | required this.heightLine,
18 | required this.currentStep,
19 | required this.selectedColor,
20 | required this.unselectedColor,
21 | required this.gradientColor,
22 | });
23 | double get maxDefinedSize {
24 | return math.max(1, math.max(0, 0));
25 | }
26 |
27 | @override
28 | void paint(Canvas canvas, Size size) {
29 | final w = size.width;
30 | final h = size.height;
31 | Paint paint = Paint()..style = PaintingStyle.stroke;
32 |
33 | final rect = Rect.fromCenter(
34 | center: Offset(w / 2, h / 2),
35 | height: h - maxDefinedSize,
36 | width: w - maxDefinedSize,
37 | );
38 |
39 | if (gradientColor != null) {
40 | paint.shader = gradientColor!.createShader(rect);
41 | }
42 | _drawStepArc(canvas, paint, rect, size);
43 | }
44 |
45 | /// Draw a series of arcs, each composing the full steps of the indicator
46 | void _drawStepArc(
47 | Canvas canvas,
48 | Paint paint,
49 | Rect rect,
50 | Size size,
51 | ) {
52 | var centerX = rect.center.dx;
53 | var centerY = rect.center.dy;
54 | var radius = math.min(centerX, centerY);
55 |
56 | var draw = (360 * currentStep) / maxStep;
57 | var stepLine = 360 / maxStep;
58 |
59 | for (double i = 0; i < 360; i += stepLine) {
60 | var outerCircleRadius = (radius - (i < draw ? 0 : heightLine / 2));
61 | var innerCircleRadius = (radius - heightLine);
62 |
63 | var x1 = centerX + outerCircleRadius * math.cos(i * math.pi / 180);
64 | var y1 = centerX + outerCircleRadius * math.sin(i * math.pi / 180);
65 | //
66 | var x2 = centerX + innerCircleRadius * math.cos(i * math.pi / 180);
67 | var y2 = centerX + innerCircleRadius * math.sin(i * math.pi / 180);
68 | var dashBrush = paint
69 | ..color = i < draw
70 | ? selectedColor ?? Colors.red
71 | : unselectedColor ?? Colors.yellow
72 | ..style = PaintingStyle.stroke
73 | ..strokeCap = StrokeCap.round
74 | ..strokeWidth = widthLine;
75 | canvas.drawLine(Offset(x1, y1), Offset(x2, y2), dashBrush);
76 | }
77 | }
78 |
79 | @override
80 | bool shouldRepaint(CustomPainter oldDelegate) => true;
81 | }
82 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/example/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: flutter_liveness_detection_randomized_plugin_example
2 | description: "Demonstrates how to use the flutter_liveness_detection_randomized_plugin plugin."
3 | # The following line prevents the package from being accidentally published to
4 | # pub.dev using `flutter pub publish`. This is preferred for private packages.
5 | publish_to: "none" # Remove this line if you wish to publish to pub.dev
6 |
7 | environment:
8 | sdk: '>=3.10.0 <4.0.0'
9 | flutter: '>=3.38.1'
10 |
11 | # Dependencies specify other packages that your package needs in order to work.
12 | # To automatically upgrade your package dependencies to the latest versions
13 | # consider running `flutter pub upgrade --major-versions`. Alternatively,
14 | # dependencies can be manually updated by changing the version numbers below to
15 | # the latest version available on pub.dev. To see which dependencies have newer
16 | # versions available, run `flutter pub outdated`.
17 | dependencies:
18 | flutter:
19 | sdk: flutter
20 |
21 | flutter_liveness_detection_randomized_plugin:
22 | # When depending on this package from a real application you should use:
23 | # flutter_liveness_detection_randomized_plugin: ^x.y.z
24 | # See https://dart.dev/tools/pub/dependencies#version-constraints
25 | # The example app is bundled with the plugin so we use a path dependency on
26 | # the parent directory to use the current plugin's version.
27 | path: ../
28 |
29 | # The following adds the Cupertino Icons font to your application.
30 | # Use with the CupertinoIcons class for iOS style icons.
31 | cupertino_icons: ^1.0.8
32 |
33 | dev_dependencies:
34 | integration_test:
35 | sdk: flutter
36 | flutter_test:
37 | sdk: flutter
38 |
39 | # The "flutter_lints" package below contains a set of recommended lints to
40 | # encourage good coding practices. The lint set provided by the package is
41 | # activated in the `analysis_options.yaml` file located at the root of your
42 | # package. See that file for information about deactivating specific lint
43 | # rules and activating additional ones.
44 | flutter_lints: ^4.0.0
45 |
46 | # For information on the generic Dart part of this file, see the
47 | # following page: https://dart.dev/tools/pub/pubspec
48 |
49 | # The following section is specific to Flutter packages.
50 | flutter:
51 | # The following line ensures that the Material Icons font is
52 | # included with your application, so that you can use the icons in
53 | # the material Icons class.
54 | uses-material-design: true
55 |
56 | # To add assets to your application, add an assets section, like this:
57 | # assets:
58 | # - images/a_dot_burr.jpeg
59 | # - images/a_dot_ham.jpeg
60 |
61 | # An image asset can refer to one or more resolution-specific "variants", see
62 | # https://flutter.dev/to/resolution-aware-images
63 |
64 | # For details regarding adding assets from package dependencies, see
65 | # https://flutter.dev/to/asset-from-package
66 |
67 | # To add custom fonts to your application, add a fonts section here,
68 | # in this "flutter" section. Each entry in this list should have a
69 | # "family" key with the font family name, and a "fonts" key with a
70 | # list giving the asset and other descriptors for the font. For
71 | # example:
72 | # fonts:
73 | # - family: Schyler
74 | # fonts:
75 | # - asset: fonts/Schyler-Regular.ttf
76 | # - asset: fonts/Schyler-Italic.ttf
77 | # style: italic
78 | # - family: Trajan Pro
79 | # fonts:
80 | # - asset: fonts/TrajanPro.ttf
81 | # - asset: fonts/TrajanPro_Bold.ttf
82 | # weight: 700
83 | #
84 | # For details regarding fonts from package dependencies,
85 | # see https://flutter.dev/to/font-from-package
86 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # 1.1.0 🚀
2 |
3 | ## BREAKING CHANGES
4 | - 🔄 **API Refactor**: All parameters now consolidated into `LivenessDetectionConfig`
5 | - 📦 **Simplified API**: `livenessDetection()` method now only requires `context` and `config`
6 | - 🛠️ **Migration Required**: Update your implementation to use the new unified config approach
7 |
8 | ## New Features
9 | - ⏱️ **NEW**: Automatic cooldown feature after 3 failed verification attempts. 10-minute waiting period with persistent countdown (survives app restarts). `enableCooldownOnFailure` parameter to control cooldown feature
10 |
11 | ## Bug Fixes
12 | - 🛠️ **Fixed customizedLabel logic**: Corrected skip challenge behavior (empty string now properly skips)
13 | - ✅ **Added validation**: `customizedLabel` must not be null when `useCustomizedLabel` is true
14 | - 🔄 **Improved consistency**: Unified steps handling logic across the codebase
15 |
16 | ## Other Changes
17 | - ✅ Moved `isEnableSnackBar` to config
18 | - ✅ Moved `shuffleListWithSmileLast` to config
19 | - ✅ Moved `showCurrentStep` to config
20 | - ✅ Moved `isDarkMode` to config
21 | - Update compile sdk and Gradle version for example & change deprecated .withOpacity(0.2) to .withAlpha(51) (Thanks to https://github.com/erikwibowo)
22 |
23 | ### Migration Guide:
24 | **Before (v1.0.x):**
25 | ```dart
26 | await plugin.livenessDetection(
27 | context: context,
28 | config: LivenessDetectionConfig(...),
29 | isEnableSnackBar: true,
30 | shuffleListWithSmileLast: true,
31 | showCurrentStep: true,
32 | isDarkMode: false,
33 | );
34 | ```
35 |
36 | **After (v1.1.0+):**
37 | ```dart
38 | await plugin.livenessDetection(
39 | context: context,
40 | config: LivenessDetectionConfig(
41 | isEnableSnackBar: true,
42 | shuffleListWithSmileLast: true,
43 | showCurrentStep: true,
44 | isDarkMode: false,
45 | // ... other parameters
46 | ),
47 | );
48 | ```
49 |
50 |
51 | ## 1.0.8 🚀
52 |
53 | - 📦 Add packagingOptions with useLegacyPackaging for Android compatibility
54 | - 🛠️ Fix InputImageConverterError for unsupported image formats
55 | - 📷 Add configurable camera resolution preset (cameraResolution parameter)
56 | - ⚡ Improved error handling for ML Kit face detection
57 | - 🔧 Platform-specific image format optimization (NV21 for Android, BGRA8888 for iOS)
58 |
59 | ## 1.0.7 🚀
60 |
61 | - ⚡ Update google_mlkit_face_detection for better compability to newest flutter version
62 |
63 | ## 1.0.6 🚀
64 | - 🛠️ Fix issue camera preview freeze while start liveness detection
65 | - 🎨 Face preview now looks better, no longer stretching
66 | - 🎨 Add parameter to adjust image quality liveness result
67 |
68 | ## 1.0.5 🚀
69 |
70 | - 🛠️ Improve security liveness challenge
71 | - 🎨 Add set to max brightness option
72 | - 🛠️ Update readme.md
73 |
74 | ## 1.0.4 🚀
75 |
76 | - ⚡ Improved performance during liveness challenge verification
77 | - 🎭 Customizable liveness challenge labels
78 | - ⏳ Flexible security verification duration
79 | - 🎲 Adjustable number of liveness challenges
80 |
81 | ## 1.0.3 🚀
82 |
83 | - 🛠️ Adjust to compatible camera dependency to prevent face not found
84 | - 🔐 Ajdust threshold for smile and look down challenge
85 | - 🎨 Add showCurrentStep parameter (default : false)
86 | - 🎨 Add Light and Dark mode
87 |
88 | ## 1.0.2 🚀
89 |
90 | ### Update README.md
91 |
92 | - 🛠️ Update readme.md file
93 |
94 | ## 1.0.1 🚀
95 |
96 | ### Update dependencies 🛠️
97 |
98 | - 🛠️ Update camera dependencies and also add camera_android_camerax for better experience while using liveness detection
99 |
100 | ## 1.0.0 🚀
101 |
102 | ### Introducing Flutter Liveness Detection Randomized Plugin!
103 |
104 | ✨ First Major Release Highlights:
105 | - 🎯 Smart Liveness Detection System
106 | - 🎲 Dynamic Random Challenge Generator
107 | - 🔐 Enhanced Security Protocols
108 | - 📱 Cross-Platform Support (iOS & Android)
109 | - ⚡ Real-time Processing
110 | - 🎨 Sleek & Modern UI
111 | - 🛠️ Developer-Friendly Integration
112 |
113 | Ready to revolutionize your biometric authentication? Let's make your app more secure! 💪
--------------------------------------------------------------------------------
/lib/src/core/services/liveness_cooldown_service.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 | import 'dart:convert';
3 | import 'package:shared_preferences/shared_preferences.dart';
4 | import 'package:flutter_liveness_detection_randomized_plugin/src/models/liveness_detection_cooldown.dart';
5 |
6 | class LivenessCooldownService {
7 | static const String _cooldownKey = 'liveness_detection_cooldown';
8 | int _maxFailedAttempts = 3;
9 | int _cooldownMinutes = 10;
10 |
11 | static LivenessCooldownService? _instance;
12 | static LivenessCooldownService get instance {
13 | _instance ??= LivenessCooldownService._();
14 | return _instance!;
15 | }
16 |
17 | LivenessCooldownService._();
18 |
19 | void configure({required int maxFailedAttempts, required int cooldownMinutes}) {
20 | _maxFailedAttempts = maxFailedAttempts;
21 | _cooldownMinutes = cooldownMinutes;
22 | }
23 |
24 | Timer? _cooldownTimer;
25 | final StreamController _cooldownController =
26 | StreamController.broadcast();
27 |
28 | Stream get cooldownStream =>
29 | _cooldownController.stream;
30 |
31 | Future getCooldownState() async {
32 | final prefs = await SharedPreferences.getInstance();
33 | final cooldownJson = prefs.getString(_cooldownKey);
34 |
35 | if (cooldownJson == null) {
36 | return const LivenessDetectionCooldown();
37 | }
38 |
39 | final cooldown = LivenessDetectionCooldown.fromJson(
40 | jsonDecode(cooldownJson),
41 | );
42 |
43 | // Check if cooldown has expired
44 | if (cooldown.isInCooldown &&
45 | cooldown.remainingCooldownTime.inSeconds <= 0) {
46 | return await _resetCooldown();
47 | }
48 |
49 | return cooldown;
50 | }
51 |
52 | Future recordFailedAttempt() async {
53 | final currentState = await getCooldownState();
54 |
55 | if (currentState.isInCooldown) {
56 | return currentState;
57 | }
58 |
59 | final newFailedAttempts = currentState.failedAttempts + 1;
60 |
61 | LivenessDetectionCooldown newState;
62 |
63 | if (newFailedAttempts >= _maxFailedAttempts) {
64 | // Start cooldown
65 | final cooldownEndTime = DateTime.now().add(
66 | Duration(minutes: _cooldownMinutes),
67 | );
68 |
69 | newState = LivenessDetectionCooldown(
70 | failedAttempts: newFailedAttempts,
71 | cooldownEndTime: cooldownEndTime,
72 | isInCooldown: true,
73 | );
74 |
75 | _startCooldownTimer(newState);
76 | } else {
77 | newState = LivenessDetectionCooldown(
78 | failedAttempts: newFailedAttempts,
79 | cooldownEndTime: null,
80 | isInCooldown: false,
81 | );
82 | }
83 |
84 | await _saveCooldownState(newState);
85 | _cooldownController.add(newState);
86 | return newState;
87 | }
88 |
89 | Future recordSuccessfulAttempt() async {
90 | return await _resetCooldown();
91 | }
92 |
93 | Future _resetCooldown() async {
94 | const newState = LivenessDetectionCooldown();
95 | await _saveCooldownState(newState);
96 | _cooldownController.add(newState);
97 | _cooldownTimer?.cancel();
98 | return newState;
99 | }
100 |
101 | Future _saveCooldownState(LivenessDetectionCooldown state) async {
102 | final prefs = await SharedPreferences.getInstance();
103 | await prefs.setString(_cooldownKey, jsonEncode(state.toJson()));
104 | }
105 |
106 | void _startCooldownTimer(LivenessDetectionCooldown state) {
107 | _cooldownTimer?.cancel();
108 |
109 | final remaining = state.remainingCooldownTime;
110 | if (remaining.inSeconds <= 0) return;
111 |
112 | _cooldownTimer = Timer(remaining, () async {
113 | await _resetCooldown();
114 | });
115 | }
116 |
117 | Future initializeCooldownTimer() async {
118 | final state = await getCooldownState();
119 | if (state.isInCooldown && state.remainingCooldownTime.inSeconds > 0) {
120 | _startCooldownTimer(state);
121 | }
122 | _cooldownController.add(state);
123 | }
124 |
125 | void dispose() {
126 | _cooldownTimer?.cancel();
127 | _cooldownController.close();
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/lib/src/presentation/widgets/circular_progress_widget/circular_progress_widget.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_liveness_detection_randomized_plugin/src/presentation/widgets/circular_progress_widget/circular_progress_painter.dart';
3 |
4 | class CircularProgressWidget extends StatefulWidget {
5 | const CircularProgressWidget({
6 | super.key,
7 | required this.current,
8 | this.height,
9 | this.width,
10 | this.selectedColor,
11 | this.unselectedColor,
12 | this.child,
13 | this.gradientColor,
14 | this.maxStep = 100,
15 | this.widthLine = 3,
16 | this.heightLine = 20,
17 | this.curve = Curves.easeInOutQuint,
18 | this.duration = const Duration(seconds: 2),
19 | });
20 |
21 | final double current;
22 | final double maxStep;
23 | final double widthLine;
24 | final double heightLine;
25 | final double? height;
26 | final double? width;
27 | final Color? selectedColor;
28 | final Color? unselectedColor;
29 | final Widget? child;
30 | final Gradient? gradientColor;
31 | final Curve curve;
32 | final Duration duration;
33 |
34 | @override
35 | // ignore: library_private_types_in_public_api
36 | _CircularProgressWidgetState createState() => _CircularProgressWidgetState();
37 | }
38 |
39 | class _CircularProgressWidgetState extends State
40 | with SingleTickerProviderStateMixin, AutomaticKeepAliveClientMixin {
41 | AnimationController? _animationController;
42 | Animation? _animation;
43 | double _current = 0.0;
44 |
45 | @override
46 | void initState() {
47 | super.initState();
48 | _animationController = AnimationController(
49 | vsync: this,
50 | duration: widget.duration,
51 | );
52 | _animation = Tween(begin: 0.0, end: widget.current).animate(
53 | CurvedAnimation(
54 | parent: _animationController!,
55 | curve: widget.curve,
56 | ),
57 | )..addListener(() {
58 | if (mounted) {
59 | setState(() {
60 | _current = _animation!.value;
61 | });
62 | }
63 | });
64 |
65 | _animationController!.forward();
66 | }
67 |
68 | @override
69 | void dispose() {
70 | _animationController?.dispose();
71 | super.dispose();
72 | }
73 |
74 | @override
75 | void didUpdateWidget(CircularProgressWidget oldWidget) {
76 | super.didUpdateWidget(oldWidget);
77 | if (oldWidget.current != widget.current) {
78 | if (_animationController != null) {
79 | _animation = Tween(
80 | begin: oldWidget.current,
81 | end: widget.current,
82 | ).animate(
83 | CurvedAnimation(
84 | parent: _animationController!,
85 | curve: widget.curve,
86 | ),
87 | );
88 | _animationController?.forward(from: 0.0);
89 | } else {
90 | _updateProgress();
91 | }
92 | }
93 | }
94 |
95 | _updateProgress() {
96 | if (mounted) {
97 | setState(() => _current = widget.current);
98 | }
99 | }
100 |
101 | @override
102 | Widget build(BuildContext context) {
103 | super.build(context);
104 | return LayoutBuilder(
105 | builder: (BuildContext context, BoxConstraints constraints) {
106 | return SizedBox(
107 | height: widget.height ?? constraints.maxHeight,
108 | width: widget.width ?? constraints.maxWidth,
109 | child: RotationTransition(
110 | turns: const AlwaysStoppedAnimation(-90 / 360),
111 | child: CustomPaint(
112 | painter: CircularProgressPainter(
113 | currentStep: _current,
114 | selectedColor: widget.selectedColor,
115 | unselectedColor: widget.unselectedColor,
116 | gradientColor: widget.gradientColor,
117 | maxStep: widget.maxStep,
118 | widthLine: widget.widthLine,
119 | heightLine: widget.heightLine,
120 | ),
121 | child: RotationTransition(
122 | turns: const AlwaysStoppedAnimation(90 / 360),
123 | child: Padding(
124 | padding: EdgeInsets.all(widget.heightLine),
125 | child: Container(
126 | decoration: const BoxDecoration(
127 | shape: BoxShape.circle,
128 | ),
129 | clipBehavior: Clip.antiAlias,
130 | child: widget.child,
131 | ),
132 | ),
133 | ),
134 | ),
135 | ),
136 | );
137 | },
138 | );
139 | }
140 |
141 | @override
142 | bool get wantKeepAlive => true;
143 | }
144 |
--------------------------------------------------------------------------------
/example/ios/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - camera_avfoundation (0.0.1):
3 | - Flutter
4 | - Flutter (1.0.0)
5 | - flutter_liveness_detection_randomized_plugin (0.0.1):
6 | - Flutter
7 | - google_mlkit_commons (0.7.1):
8 | - Flutter
9 | - MLKitVision
10 | - google_mlkit_face_detection (0.11.0):
11 | - Flutter
12 | - google_mlkit_commons
13 | - GoogleMLKit/FaceDetection (~> 6.0.0)
14 | - GoogleDataTransport (9.4.1):
15 | - GoogleUtilities/Environment (~> 7.7)
16 | - nanopb (< 2.30911.0, >= 2.30908.0)
17 | - PromisesObjC (< 3.0, >= 1.2)
18 | - GoogleMLKit/FaceDetection (6.0.0):
19 | - GoogleMLKit/MLKitCore
20 | - MLKitFaceDetection (~> 5.0.0)
21 | - GoogleMLKit/MLKitCore (6.0.0):
22 | - MLKitCommon (~> 11.0.0)
23 | - GoogleToolboxForMac/Defines (4.2.1)
24 | - GoogleToolboxForMac/Logger (4.2.1):
25 | - GoogleToolboxForMac/Defines (= 4.2.1)
26 | - "GoogleToolboxForMac/NSData+zlib (4.2.1)":
27 | - GoogleToolboxForMac/Defines (= 4.2.1)
28 | - GoogleUtilities/Environment (7.13.3):
29 | - GoogleUtilities/Privacy
30 | - PromisesObjC (< 3.0, >= 1.2)
31 | - GoogleUtilities/Logger (7.13.3):
32 | - GoogleUtilities/Environment
33 | - GoogleUtilities/Privacy
34 | - GoogleUtilities/Privacy (7.13.3)
35 | - GoogleUtilities/UserDefaults (7.13.3):
36 | - GoogleUtilities/Logger
37 | - GoogleUtilities/Privacy
38 | - GoogleUtilitiesComponents (1.1.0):
39 | - GoogleUtilities/Logger
40 | - GTMSessionFetcher/Core (3.5.0)
41 | - integration_test (0.0.1):
42 | - Flutter
43 | - MLImage (1.0.0-beta5)
44 | - MLKitCommon (11.0.0):
45 | - GoogleDataTransport (< 10.0, >= 9.4.1)
46 | - GoogleToolboxForMac/Logger (< 5.0, >= 4.2.1)
47 | - "GoogleToolboxForMac/NSData+zlib (< 5.0, >= 4.2.1)"
48 | - GoogleUtilities/UserDefaults (< 8.0, >= 7.13.0)
49 | - GoogleUtilitiesComponents (~> 1.0)
50 | - GTMSessionFetcher/Core (< 4.0, >= 3.3.2)
51 | - MLKitFaceDetection (5.0.0):
52 | - MLKitCommon (~> 11.0)
53 | - MLKitVision (~> 7.0)
54 | - MLKitVision (7.0.0):
55 | - GoogleToolboxForMac/Logger (< 5.0, >= 4.2.1)
56 | - "GoogleToolboxForMac/NSData+zlib (< 5.0, >= 4.2.1)"
57 | - GTMSessionFetcher/Core (< 4.0, >= 3.3.2)
58 | - MLImage (= 1.0.0-beta5)
59 | - MLKitCommon (~> 11.0)
60 | - nanopb (2.30910.0):
61 | - nanopb/decode (= 2.30910.0)
62 | - nanopb/encode (= 2.30910.0)
63 | - nanopb/decode (2.30910.0)
64 | - nanopb/encode (2.30910.0)
65 | - PromisesObjC (2.4.0)
66 |
67 | DEPENDENCIES:
68 | - camera_avfoundation (from `.symlinks/plugins/camera_avfoundation/ios`)
69 | - Flutter (from `Flutter`)
70 | - flutter_liveness_detection_randomized_plugin (from `.symlinks/plugins/flutter_liveness_detection_randomized_plugin/ios`)
71 | - google_mlkit_commons (from `.symlinks/plugins/google_mlkit_commons/ios`)
72 | - google_mlkit_face_detection (from `.symlinks/plugins/google_mlkit_face_detection/ios`)
73 | - integration_test (from `.symlinks/plugins/integration_test/ios`)
74 |
75 | SPEC REPOS:
76 | trunk:
77 | - GoogleDataTransport
78 | - GoogleMLKit
79 | - GoogleToolboxForMac
80 | - GoogleUtilities
81 | - GoogleUtilitiesComponents
82 | - GTMSessionFetcher
83 | - MLImage
84 | - MLKitCommon
85 | - MLKitFaceDetection
86 | - MLKitVision
87 | - nanopb
88 | - PromisesObjC
89 |
90 | EXTERNAL SOURCES:
91 | camera_avfoundation:
92 | :path: ".symlinks/plugins/camera_avfoundation/ios"
93 | Flutter:
94 | :path: Flutter
95 | flutter_liveness_detection_randomized_plugin:
96 | :path: ".symlinks/plugins/flutter_liveness_detection_randomized_plugin/ios"
97 | google_mlkit_commons:
98 | :path: ".symlinks/plugins/google_mlkit_commons/ios"
99 | google_mlkit_face_detection:
100 | :path: ".symlinks/plugins/google_mlkit_face_detection/ios"
101 | integration_test:
102 | :path: ".symlinks/plugins/integration_test/ios"
103 |
104 | SPEC CHECKSUMS:
105 | camera_avfoundation: 04b44aeb14070126c6529e5ab82cc7c9fca107cf
106 | Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
107 | flutter_liveness_detection_randomized_plugin: d8961eea48ebce31e93c24ca6f7f87a14b66f732
108 | google_mlkit_commons: 9f155ff61a70e0fad8692a7edb5aa2dc468536d3
109 | google_mlkit_face_detection: 35c4b9a56db1acee146600be8d75c45b7a2fe6fc
110 | GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a
111 | GoogleMLKit: 97ac7af399057e99182ee8edfa8249e3226a4065
112 | GoogleToolboxForMac: d1a2cbf009c453f4d6ded37c105e2f67a32206d8
113 | GoogleUtilities: ea963c370a38a8069cc5f7ba4ca849a60b6d7d15
114 | GoogleUtilitiesComponents: 679b2c881db3b615a2777504623df6122dd20afe
115 | GTMSessionFetcher: 5aea5ba6bd522a239e236100971f10cb71b96ab6
116 | integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e
117 | MLImage: 1824212150da33ef225fbd3dc49f184cf611046c
118 | MLKitCommon: afec63980417d29ffbb4790529a1b0a2291699e1
119 | MLKitFaceDetection: 7c0e8bf09ddd27105da32d088fca978a99fc30cc
120 | MLKitVision: e858c5f125ecc288e4a31127928301eaba9ae0c1
121 | nanopb: 438bc412db1928dac798aa6fd75726007be04262
122 | PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
123 |
124 | PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796
125 |
126 | COCOAPODS: 1.16.2
127 |
--------------------------------------------------------------------------------
/lib/src/models/liveness_detection_threshold.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_liveness_detection_randomized_plugin/index.dart';
2 |
3 | abstract class LivenessDetectionThreshold extends Equatable {
4 | const LivenessDetectionThreshold();
5 | LivenessDetectionThreshold fromDict(Map map);
6 | Map toMap();
7 | @override
8 | List