├── .gitignore ├── .metadata ├── CHANGELOG.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── android ├── .gitignore ├── build.gradle ├── settings.gradle └── 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 ├── example ├── .gitignore ├── README.md ├── analysis_options.yaml ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── bagussubagja │ │ │ │ │ └── flutter_liveness_detection_randomized_plugin │ │ │ │ │ └── flutter_liveness_detection_randomized_plugin_example │ │ │ │ │ └── MainActivity.kt │ │ │ └── res │ │ │ │ ├── drawable-v21 │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable │ │ │ │ └── launch_background.xml │ │ │ │ ├── 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 │ │ │ │ ├── values-night │ │ │ │ └── styles.xml │ │ │ │ └── values │ │ │ │ └── styles.xml │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle ├── integration_test │ └── plugin_integration_test.dart ├── ios │ ├── .gitignore │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Podfile │ ├── Podfile.lock │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ └── WorkspaceSettings.xcsettings │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ ├── Runner │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ │ ├── AppIcon.appiconset │ │ │ │ ├── Contents.json │ │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ │ ├── 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-83.5x83.5@2x.png │ │ │ └── LaunchImage.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── LaunchImage.png │ │ │ │ ├── LaunchImage@2x.png │ │ │ │ ├── LaunchImage@3x.png │ │ │ │ └── README.md │ │ ├── Base.lproj │ │ │ ├── LaunchScreen.storyboard │ │ │ └── Main.storyboard │ │ ├── Info.plist │ │ └── Runner-Bridging-Header.h │ └── RunnerTests │ │ └── RunnerTests.swift ├── lib │ └── main.dart ├── pubspec.lock ├── pubspec.yaml └── test │ └── widget_test.dart ├── ios ├── .gitignore ├── Assets │ └── .gitkeep ├── Classes │ └── FlutterLivenessDetectionRandomizedPlugin.swift ├── Resources │ └── PrivacyInfo.xcprivacy └── flutter_liveness_detection_randomized_plugin.podspec ├── lib ├── flutter_liveness_detection_randomized_plugin.dart ├── flutter_liveness_detection_randomized_plugin_method_channel.dart ├── flutter_liveness_detection_randomized_plugin_platform_interface.dart ├── index.dart └── src │ ├── core │ ├── assets │ │ ├── face-detected.json │ │ └── face-id-anim.json │ ├── constants │ │ ├── index.dart │ │ └── liveness_detection_step_constant.dart │ ├── enums │ │ ├── index.dart │ │ └── liveness_detection_step.dart │ ├── index.dart │ └── utils │ │ ├── index.dart │ │ └── machine_learning_kit_helper.dart │ ├── models │ ├── index.dart │ ├── liveness_detection_config.dart │ ├── liveness_detection_label_model.dart │ ├── liveness_detection_step_item.dart │ └── liveness_detection_threshold.dart │ └── presentation │ ├── views │ ├── index.dart │ └── liveness_detection_view.dart │ └── widgets │ ├── circular_progress_widget │ ├── circular_progress_painter.dart │ └── circular_progress_widget.dart │ ├── index.dart │ ├── liveness_detection_step_overlay_widget.dart │ └── liveness_detection_tutorial_widget.dart ├── pubspec.yaml └── test ├── flutter_liveness_detection_randomized_plugin_method_channel_test.dart └── flutter_liveness_detection_randomized_plugin_test.dart /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.0.5 🚀 2 | 3 | - 🛠️ Improve security liveness challenge 4 | - 🎨 Add set to max brightness option 5 | - 🛠️ Update readme.md 6 | 7 | ## 1.0.4 🚀 8 | 9 | - ⚡ Improved performance during liveness challenge verification 10 | - 🎭 Customizable liveness challenge labels 11 | - ⏳ Flexible security verification duration 12 | - 🎲 Adjustable number of liveness challenges 13 | 14 | ## 1.0.3 🚀 15 | 16 | - 🛠️ Adjust to compatible camera dependency to prevent face not found 17 | - 🔐 Ajdust threshold for smile and look down challenge 18 | - 🎨 Add showCurrentStep parameter (default : false) 19 | - 🎨 Add Light and Dark mode 20 | 21 | ## 1.0.2 🚀 22 | 23 | ### Update README.md 24 | 25 | - 🛠️ Update readme.md file 26 | 27 | ## 1.0.1 🚀 28 | 29 | ### Update dependencies 🛠️ 30 | 31 | - 🛠️ Update camera dependencies and also add camera_android_camerax for better experience while using liveness detection 32 | 33 | ## 1.0.0 🚀 34 | 35 | ### Introducing Flutter Liveness Detection Randomized Plugin! 36 | 37 | ✨ First Major Release Highlights: 38 | - 🎯 Smart Liveness Detection System 39 | - 🎲 Dynamic Random Challenge Generator 40 | - 🔐 Enhanced Security Protocols 41 | - 📱 Cross-Platform Support (iOS & Android) 42 | - ⚡ Real-time Processing 43 | - 🎨 Sleek & Modern UI 44 | - 🛠️ Developer-Friendly Integration 45 | 46 | Ready to revolutionize your biometric authentication? Let's make your app more secure! 💪 -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flutter Liveness Detection Randomized Plugin 2 | 3 | A Flutter plugin for liveness detection with randomized challenge response method with an interaction mechanism between the user and the system in the form of a movement challenge that indicates life is detected on the face. This plugin helps implement secure biometric authentication by detecting real human presence through dynamic facial verification challenges. 4 | 5 | [![pub package](https://img.shields.io/pub/v/flutter_liveness_detection_randomized_plugin.svg)](https://pub.dev/packages/flutter_liveness_detection_randomized_plugin) 6 | 7 | [![Codacy Badge](https://app.codacy.com/project/badge/Grade/50b64954ad654b65b0424d266399b026)](https://app.codacy.com/gh/bagussubagja/flutter-liveness-detection-randomized-plugin/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade) 8 | 9 | ## Preview 🪟 10 | ![Slide 16_9 - 1](https://github.com/user-attachments/assets/55e59d51-e0da-4562-879e-ae50adaced33) 11 | 12 | https://github.com/user-attachments/assets/f7266dc9-c4a2-4fba-8684-0ead2f678180 13 | 14 | ## Features ✨ 15 | 16 | - 📱 Real-time face detection 17 | - 🎲 Randomized challenge sequence generation 18 | - 💫 Cross-platform support (iOS & Android) 19 | - 🎨 Light and dark mode support 20 | - ✅ High accuracy liveness verification 21 | - 🚀 Simple integration API 22 | - 🎭 Customizable liveness challenge labels 23 | - ⏳ Flexible security verification duration 24 | - 🎲 Adjustable number of liveness challenges 25 | 26 | ## Getting Started 🌟 27 | 28 | Add this to your package's `pubspec.yaml` file: 29 | 30 | ```yaml 31 | dependencies: 32 | flutter_liveness_detection_randomized_plugin: ^1.0.5 33 | ``` 34 | 35 | ## Customized Steps Label 36 | You can customized steps label or use certain step only of liveness challenge with this example : 37 | ``` 38 | config: LivenessDetectionConfig( 39 | customizedLabel: LivenessDetectionLabelModel( 40 | blink: '', // add empty string to skip/pass this liveness challenge 41 | lookDown: '', 42 | lookLeft: '', 43 | lookRight: '', 44 | lookUp: 'Tengok Atas', // example of customize label name for liveness challenge. it will replace default 'look up' 45 | smile: null, // null value to use default label name 46 | ), 47 | ), 48 | ``` 49 | 50 | ## Platform Setup 51 | 52 | ### Android 53 | Add camera permission to your AndroidManifest.xml: 54 | ``` 55 | 56 | ``` 57 | Minimum SDK version: 23 58 | 59 | ### iOS 60 | Add camera usage description to Info.plist: 61 | ``` 62 | NSCameraUsageDescription 63 | Camera access is required for liveness detection 64 | ``` 65 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | kotlinOptions { 40 | jvmTarget = JavaVersion.VERSION_1_8 41 | } 42 | 43 | sourceSets { 44 | main.java.srcDirs += "src/main/kotlin" 45 | test.java.srcDirs += "src/test/kotlin" 46 | } 47 | 48 | defaultConfig { 49 | minSdk = 21 50 | } 51 | 52 | dependencies { 53 | testImplementation("org.jetbrains.kotlin:kotlin-test") 54 | testImplementation("org.mockito:mockito-core:5.0.0") 55 | } 56 | 57 | testOptions { 58 | unitTests.all { 59 | useJUnitPlatform() 60 | 61 | testLogging { 62 | events "passed", "skipped", "failed", "standardOut", "standardError" 63 | outputs.upToDateWhen {false} 64 | showStandardStreams = true 65 | } 66 | } 67 | } 68 | } 69 | 70 | // Add this block for subprojects 71 | subprojects { 72 | afterEvaluate { project -> 73 | if (project.hasProperty('android')) { 74 | project.android { 75 | if (namespace == null) { 76 | namespace project.group 77 | } 78 | } 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | package android 2 | 3 | rootProject.name = 'flutter_liveness_detection_randomized_plugin' -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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/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 = 35 11 | ndkVersion = flutter.ndkVersion 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 | buildTypes { 34 | release { 35 | // TODO: Add your own signing config for the release build. 36 | // Signing with the debug keys for now, so `flutter run --release` works. 37 | signingConfig = signingConfigs.debug 38 | } 39 | } 40 | } 41 | 42 | flutter { 43 | source = "../.." 44 | } 45 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /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/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bagussubagja/flutter-liveness-detection-randomized-plugin/adddb10f2143a7eb1e12a31db15d8848b29ba39a/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/adddb10f2143a7eb1e12a31db15d8848b29ba39a/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/adddb10f2143a7eb1e12a31db15d8848b29ba39a/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/adddb10f2143a7eb1e12a31db15d8848b29ba39a/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/adddb10f2143a7eb1e12a31db15d8848b29ba39a/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /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/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /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.3-all.zip 6 | -------------------------------------------------------------------------------- /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.1.0" apply false 22 | id "org.jetbrains.kotlin.android" version "1.8.22" apply false 23 | } 24 | 25 | include ":app" 26 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 77; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 11 | 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 12 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 13 | 4991D568284B0C117102DD24 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B69F25746A7A91F7864D378D /* Pods_Runner.framework */; }; 14 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 15 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 16 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 17 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 18 | D26FFD1BC3C877A608D51594 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 26C942E844CFBFD8E4CEA993 /* Pods_RunnerTests.framework */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXContainerItemProxy section */ 22 | 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { 23 | isa = PBXContainerItemProxy; 24 | containerPortal = 97C146E61CF9000F007C117D /* Project object */; 25 | proxyType = 1; 26 | remoteGlobalIDString = 97C146ED1CF9000F007C117D; 27 | remoteInfo = Runner; 28 | }; 29 | /* End PBXContainerItemProxy section */ 30 | 31 | /* Begin PBXCopyFilesBuildPhase section */ 32 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 33 | isa = PBXCopyFilesBuildPhase; 34 | buildActionMask = 2147483647; 35 | dstPath = ""; 36 | dstSubfolderSpec = 10; 37 | files = ( 38 | ); 39 | name = "Embed Frameworks"; 40 | runOnlyForDeploymentPostprocessing = 0; 41 | }; 42 | /* End PBXCopyFilesBuildPhase section */ 43 | 44 | /* Begin PBXFileReference section */ 45 | 003BA38B6755473DEB6CABA8 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; 46 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 47 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 48 | 26C942E844CFBFD8E4CEA993 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 49 | 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 50 | 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 51 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 52 | 506D8CDC24298CFD235697DF /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; 53 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 54 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 55 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 56 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 57 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 58 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 59 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 60 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 61 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 62 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 63 | B69F25746A7A91F7864D378D /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 64 | C48AB0E70E841C9E5A1FF8B2 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 65 | CDD5AC6AB2E0D54DB7B4A169 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 66 | D3B913BD2DA586CA991FC3E3 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 67 | EBF102D6C8CA46E223A517A4 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; 68 | /* End PBXFileReference section */ 69 | 70 | /* Begin PBXFrameworksBuildPhase section */ 71 | 8C184B0435D47700AF90D783 /* Frameworks */ = { 72 | isa = PBXFrameworksBuildPhase; 73 | buildActionMask = 2147483647; 74 | files = ( 75 | D26FFD1BC3C877A608D51594 /* Pods_RunnerTests.framework in Frameworks */, 76 | ); 77 | runOnlyForDeploymentPostprocessing = 0; 78 | }; 79 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 80 | isa = PBXFrameworksBuildPhase; 81 | buildActionMask = 2147483647; 82 | files = ( 83 | 4991D568284B0C117102DD24 /* Pods_Runner.framework in Frameworks */, 84 | ); 85 | runOnlyForDeploymentPostprocessing = 0; 86 | }; 87 | /* End PBXFrameworksBuildPhase section */ 88 | 89 | /* Begin PBXGroup section */ 90 | 088181CC13A452CBEF41DB29 /* Frameworks */ = { 91 | isa = PBXGroup; 92 | children = ( 93 | B69F25746A7A91F7864D378D /* Pods_Runner.framework */, 94 | 26C942E844CFBFD8E4CEA993 /* Pods_RunnerTests.framework */, 95 | ); 96 | name = Frameworks; 97 | sourceTree = ""; 98 | }; 99 | 331C8082294A63A400263BE5 /* RunnerTests */ = { 100 | isa = PBXGroup; 101 | children = ( 102 | 331C807B294A618700263BE5 /* RunnerTests.swift */, 103 | ); 104 | path = RunnerTests; 105 | sourceTree = ""; 106 | }; 107 | 884A061E157D3DACE1EE6C72 /* Pods */ = { 108 | isa = PBXGroup; 109 | children = ( 110 | D3B913BD2DA586CA991FC3E3 /* Pods-Runner.debug.xcconfig */, 111 | C48AB0E70E841C9E5A1FF8B2 /* Pods-Runner.release.xcconfig */, 112 | CDD5AC6AB2E0D54DB7B4A169 /* Pods-Runner.profile.xcconfig */, 113 | 506D8CDC24298CFD235697DF /* Pods-RunnerTests.debug.xcconfig */, 114 | 003BA38B6755473DEB6CABA8 /* Pods-RunnerTests.release.xcconfig */, 115 | EBF102D6C8CA46E223A517A4 /* Pods-RunnerTests.profile.xcconfig */, 116 | ); 117 | path = Pods; 118 | sourceTree = ""; 119 | }; 120 | 9740EEB11CF90186004384FC /* Flutter */ = { 121 | isa = PBXGroup; 122 | children = ( 123 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 124 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 125 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 126 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 127 | ); 128 | name = Flutter; 129 | sourceTree = ""; 130 | }; 131 | 97C146E51CF9000F007C117D = { 132 | isa = PBXGroup; 133 | children = ( 134 | 9740EEB11CF90186004384FC /* Flutter */, 135 | 97C146F01CF9000F007C117D /* Runner */, 136 | 97C146EF1CF9000F007C117D /* Products */, 137 | 331C8082294A63A400263BE5 /* RunnerTests */, 138 | 884A061E157D3DACE1EE6C72 /* Pods */, 139 | 088181CC13A452CBEF41DB29 /* Frameworks */, 140 | ); 141 | sourceTree = ""; 142 | }; 143 | 97C146EF1CF9000F007C117D /* Products */ = { 144 | isa = PBXGroup; 145 | children = ( 146 | 97C146EE1CF9000F007C117D /* Runner.app */, 147 | 331C8081294A63A400263BE5 /* RunnerTests.xctest */, 148 | ); 149 | name = Products; 150 | sourceTree = ""; 151 | }; 152 | 97C146F01CF9000F007C117D /* Runner */ = { 153 | isa = PBXGroup; 154 | children = ( 155 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 156 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 157 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 158 | 97C147021CF9000F007C117D /* Info.plist */, 159 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 160 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 161 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 162 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 163 | ); 164 | path = Runner; 165 | sourceTree = ""; 166 | }; 167 | /* End PBXGroup section */ 168 | 169 | /* Begin PBXNativeTarget section */ 170 | 331C8080294A63A400263BE5 /* RunnerTests */ = { 171 | isa = PBXNativeTarget; 172 | buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; 173 | buildPhases = ( 174 | 1A21B3ADE244CD2627474ACD /* [CP] Check Pods Manifest.lock */, 175 | 331C807D294A63A400263BE5 /* Sources */, 176 | 331C807F294A63A400263BE5 /* Resources */, 177 | 8C184B0435D47700AF90D783 /* Frameworks */, 178 | ); 179 | buildRules = ( 180 | ); 181 | dependencies = ( 182 | 331C8086294A63A400263BE5 /* PBXTargetDependency */, 183 | ); 184 | name = RunnerTests; 185 | productName = RunnerTests; 186 | productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; 187 | productType = "com.apple.product-type.bundle.unit-test"; 188 | }; 189 | 97C146ED1CF9000F007C117D /* Runner */ = { 190 | isa = PBXNativeTarget; 191 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 192 | buildPhases = ( 193 | 812C25770147C0FA879D65CF /* [CP] Check Pods Manifest.lock */, 194 | 9740EEB61CF901F6004384FC /* Run Script */, 195 | 97C146EA1CF9000F007C117D /* Sources */, 196 | 97C146EB1CF9000F007C117D /* Frameworks */, 197 | 97C146EC1CF9000F007C117D /* Resources */, 198 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 199 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 200 | 92ADCB8954218860049186D9 /* [CP] Embed Pods Frameworks */, 201 | 4BC0B567789EB7E1B91C2D68 /* [CP] Copy Pods Resources */, 202 | ); 203 | buildRules = ( 204 | ); 205 | dependencies = ( 206 | ); 207 | name = Runner; 208 | productName = Runner; 209 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 210 | productType = "com.apple.product-type.application"; 211 | }; 212 | /* End PBXNativeTarget section */ 213 | 214 | /* Begin PBXProject section */ 215 | 97C146E61CF9000F007C117D /* Project object */ = { 216 | isa = PBXProject; 217 | attributes = { 218 | BuildIndependentTargetsInParallel = YES; 219 | LastUpgradeCheck = 1510; 220 | ORGANIZATIONNAME = ""; 221 | TargetAttributes = { 222 | 331C8080294A63A400263BE5 = { 223 | CreatedOnToolsVersion = 14.0; 224 | TestTargetID = 97C146ED1CF9000F007C117D; 225 | }; 226 | 97C146ED1CF9000F007C117D = { 227 | CreatedOnToolsVersion = 7.3.1; 228 | LastSwiftMigration = 1100; 229 | }; 230 | }; 231 | }; 232 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 233 | developmentRegion = en; 234 | hasScannedForEncodings = 0; 235 | knownRegions = ( 236 | en, 237 | Base, 238 | ); 239 | mainGroup = 97C146E51CF9000F007C117D; 240 | preferredProjectObjectVersion = 77; 241 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 242 | projectDirPath = ""; 243 | projectRoot = ""; 244 | targets = ( 245 | 97C146ED1CF9000F007C117D /* Runner */, 246 | 331C8080294A63A400263BE5 /* RunnerTests */, 247 | ); 248 | }; 249 | /* End PBXProject section */ 250 | 251 | /* Begin PBXResourcesBuildPhase section */ 252 | 331C807F294A63A400263BE5 /* Resources */ = { 253 | isa = PBXResourcesBuildPhase; 254 | buildActionMask = 2147483647; 255 | files = ( 256 | ); 257 | runOnlyForDeploymentPostprocessing = 0; 258 | }; 259 | 97C146EC1CF9000F007C117D /* Resources */ = { 260 | isa = PBXResourcesBuildPhase; 261 | buildActionMask = 2147483647; 262 | files = ( 263 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 264 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 265 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 266 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 267 | ); 268 | runOnlyForDeploymentPostprocessing = 0; 269 | }; 270 | /* End PBXResourcesBuildPhase section */ 271 | 272 | /* Begin PBXShellScriptBuildPhase section */ 273 | 1A21B3ADE244CD2627474ACD /* [CP] Check Pods Manifest.lock */ = { 274 | isa = PBXShellScriptBuildPhase; 275 | buildActionMask = 2147483647; 276 | files = ( 277 | ); 278 | inputFileListPaths = ( 279 | ); 280 | inputPaths = ( 281 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 282 | "${PODS_ROOT}/Manifest.lock", 283 | ); 284 | name = "[CP] Check Pods Manifest.lock"; 285 | outputFileListPaths = ( 286 | ); 287 | outputPaths = ( 288 | "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", 289 | ); 290 | runOnlyForDeploymentPostprocessing = 0; 291 | shellPath = /bin/sh; 292 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 293 | showEnvVarsInLog = 0; 294 | }; 295 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 296 | isa = PBXShellScriptBuildPhase; 297 | alwaysOutOfDate = 1; 298 | buildActionMask = 2147483647; 299 | files = ( 300 | ); 301 | inputPaths = ( 302 | "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", 303 | ); 304 | name = "Thin Binary"; 305 | outputPaths = ( 306 | ); 307 | runOnlyForDeploymentPostprocessing = 0; 308 | shellPath = /bin/sh; 309 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; 310 | }; 311 | 4BC0B567789EB7E1B91C2D68 /* [CP] Copy Pods Resources */ = { 312 | isa = PBXShellScriptBuildPhase; 313 | buildActionMask = 2147483647; 314 | files = ( 315 | ); 316 | inputFileListPaths = ( 317 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", 318 | ); 319 | name = "[CP] Copy Pods Resources"; 320 | outputFileListPaths = ( 321 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", 322 | ); 323 | runOnlyForDeploymentPostprocessing = 0; 324 | shellPath = /bin/sh; 325 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; 326 | showEnvVarsInLog = 0; 327 | }; 328 | 812C25770147C0FA879D65CF /* [CP] Check Pods Manifest.lock */ = { 329 | isa = PBXShellScriptBuildPhase; 330 | buildActionMask = 2147483647; 331 | files = ( 332 | ); 333 | inputFileListPaths = ( 334 | ); 335 | inputPaths = ( 336 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 337 | "${PODS_ROOT}/Manifest.lock", 338 | ); 339 | name = "[CP] Check Pods Manifest.lock"; 340 | outputFileListPaths = ( 341 | ); 342 | outputPaths = ( 343 | "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", 344 | ); 345 | runOnlyForDeploymentPostprocessing = 0; 346 | shellPath = /bin/sh; 347 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 348 | showEnvVarsInLog = 0; 349 | }; 350 | 92ADCB8954218860049186D9 /* [CP] Embed Pods Frameworks */ = { 351 | isa = PBXShellScriptBuildPhase; 352 | buildActionMask = 2147483647; 353 | files = ( 354 | ); 355 | inputFileListPaths = ( 356 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", 357 | ); 358 | name = "[CP] Embed Pods Frameworks"; 359 | outputFileListPaths = ( 360 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", 361 | ); 362 | runOnlyForDeploymentPostprocessing = 0; 363 | shellPath = /bin/sh; 364 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; 365 | showEnvVarsInLog = 0; 366 | }; 367 | 9740EEB61CF901F6004384FC /* Run Script */ = { 368 | isa = PBXShellScriptBuildPhase; 369 | alwaysOutOfDate = 1; 370 | buildActionMask = 2147483647; 371 | files = ( 372 | ); 373 | inputPaths = ( 374 | ); 375 | name = "Run Script"; 376 | outputPaths = ( 377 | ); 378 | runOnlyForDeploymentPostprocessing = 0; 379 | shellPath = /bin/sh; 380 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 381 | }; 382 | /* End PBXShellScriptBuildPhase section */ 383 | 384 | /* Begin PBXSourcesBuildPhase section */ 385 | 331C807D294A63A400263BE5 /* Sources */ = { 386 | isa = PBXSourcesBuildPhase; 387 | buildActionMask = 2147483647; 388 | files = ( 389 | 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, 390 | ); 391 | runOnlyForDeploymentPostprocessing = 0; 392 | }; 393 | 97C146EA1CF9000F007C117D /* Sources */ = { 394 | isa = PBXSourcesBuildPhase; 395 | buildActionMask = 2147483647; 396 | files = ( 397 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 398 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 399 | ); 400 | runOnlyForDeploymentPostprocessing = 0; 401 | }; 402 | /* End PBXSourcesBuildPhase section */ 403 | 404 | /* Begin PBXTargetDependency section */ 405 | 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { 406 | isa = PBXTargetDependency; 407 | target = 97C146ED1CF9000F007C117D /* Runner */; 408 | targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; 409 | }; 410 | /* End PBXTargetDependency section */ 411 | 412 | /* Begin PBXVariantGroup section */ 413 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 414 | isa = PBXVariantGroup; 415 | children = ( 416 | 97C146FB1CF9000F007C117D /* Base */, 417 | ); 418 | name = Main.storyboard; 419 | sourceTree = ""; 420 | }; 421 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 422 | isa = PBXVariantGroup; 423 | children = ( 424 | 97C147001CF9000F007C117D /* Base */, 425 | ); 426 | name = LaunchScreen.storyboard; 427 | sourceTree = ""; 428 | }; 429 | /* End PBXVariantGroup section */ 430 | 431 | /* Begin XCBuildConfiguration section */ 432 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 433 | isa = XCBuildConfiguration; 434 | buildSettings = { 435 | ALWAYS_SEARCH_USER_PATHS = NO; 436 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 437 | CLANG_ANALYZER_NONNULL = YES; 438 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 439 | CLANG_CXX_LIBRARY = "libc++"; 440 | CLANG_ENABLE_MODULES = YES; 441 | CLANG_ENABLE_OBJC_ARC = YES; 442 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 443 | CLANG_WARN_BOOL_CONVERSION = YES; 444 | CLANG_WARN_COMMA = YES; 445 | CLANG_WARN_CONSTANT_CONVERSION = YES; 446 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 447 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 448 | CLANG_WARN_EMPTY_BODY = YES; 449 | CLANG_WARN_ENUM_CONVERSION = YES; 450 | CLANG_WARN_INFINITE_RECURSION = YES; 451 | CLANG_WARN_INT_CONVERSION = YES; 452 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 453 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 454 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 455 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 456 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 457 | CLANG_WARN_STRICT_PROTOTYPES = YES; 458 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 459 | CLANG_WARN_UNREACHABLE_CODE = YES; 460 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 461 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 462 | COPY_PHASE_STRIP = NO; 463 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 464 | ENABLE_NS_ASSERTIONS = NO; 465 | ENABLE_STRICT_OBJC_MSGSEND = YES; 466 | ENABLE_USER_SCRIPT_SANDBOXING = NO; 467 | GCC_C_LANGUAGE_STANDARD = gnu99; 468 | GCC_NO_COMMON_BLOCKS = YES; 469 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 470 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 471 | GCC_WARN_UNDECLARED_SELECTOR = YES; 472 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 473 | GCC_WARN_UNUSED_FUNCTION = YES; 474 | GCC_WARN_UNUSED_VARIABLE = YES; 475 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 476 | MTL_ENABLE_DEBUG_INFO = NO; 477 | SDKROOT = iphoneos; 478 | SUPPORTED_PLATFORMS = iphoneos; 479 | TARGETED_DEVICE_FAMILY = "1,2"; 480 | VALIDATE_PRODUCT = YES; 481 | }; 482 | name = Profile; 483 | }; 484 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 485 | isa = XCBuildConfiguration; 486 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 487 | buildSettings = { 488 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 489 | CLANG_ENABLE_MODULES = YES; 490 | CODE_SIGN_IDENTITY = "Apple Development"; 491 | CODE_SIGN_STYLE = Automatic; 492 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 493 | DEVELOPMENT_TEAM = Q6ZHR7WWAY; 494 | ENABLE_BITCODE = NO; 495 | INFOPLIST_FILE = Runner/Info.plist; 496 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools"; 497 | LD_RUNPATH_SEARCH_PATHS = ( 498 | "$(inherited)", 499 | "@executable_path/Frameworks", 500 | ); 501 | MARKETING_VERSION = 1.0.5; 502 | PRODUCT_BUNDLE_IDENTIFIER = com.bagussubagja.flutterlivenessdetection; 503 | PRODUCT_NAME = "$(TARGET_NAME)"; 504 | PROVISIONING_PROFILE_SPECIFIER = ""; 505 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 506 | SWIFT_VERSION = 5.0; 507 | VERSIONING_SYSTEM = "apple-generic"; 508 | }; 509 | name = Profile; 510 | }; 511 | 331C8088294A63A400263BE5 /* Debug */ = { 512 | isa = XCBuildConfiguration; 513 | baseConfigurationReference = 506D8CDC24298CFD235697DF /* Pods-RunnerTests.debug.xcconfig */; 514 | buildSettings = { 515 | BUNDLE_LOADER = "$(TEST_HOST)"; 516 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; 517 | CODE_SIGN_STYLE = Automatic; 518 | CURRENT_PROJECT_VERSION = 1; 519 | DEVELOPMENT_TEAM = Q6ZHR7WWAY; 520 | GENERATE_INFOPLIST_FILE = YES; 521 | MARKETING_VERSION = 1.0; 522 | PRODUCT_BUNDLE_IDENTIFIER = com.bagussubagja.flutterlivenessdetection; 523 | PRODUCT_NAME = "$(TARGET_NAME)"; 524 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 525 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 526 | SWIFT_VERSION = 5.0; 527 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; 528 | }; 529 | name = Debug; 530 | }; 531 | 331C8089294A63A400263BE5 /* Release */ = { 532 | isa = XCBuildConfiguration; 533 | baseConfigurationReference = 003BA38B6755473DEB6CABA8 /* Pods-RunnerTests.release.xcconfig */; 534 | buildSettings = { 535 | BUNDLE_LOADER = "$(TEST_HOST)"; 536 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; 537 | CODE_SIGN_STYLE = Automatic; 538 | CURRENT_PROJECT_VERSION = 1; 539 | DEVELOPMENT_TEAM = Q6ZHR7WWAY; 540 | GENERATE_INFOPLIST_FILE = YES; 541 | MARKETING_VERSION = 1.0; 542 | PRODUCT_BUNDLE_IDENTIFIER = com.bagussubagja.flutterlivenessdetection; 543 | PRODUCT_NAME = "$(TARGET_NAME)"; 544 | SWIFT_VERSION = 5.0; 545 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; 546 | }; 547 | name = Release; 548 | }; 549 | 331C808A294A63A400263BE5 /* Profile */ = { 550 | isa = XCBuildConfiguration; 551 | baseConfigurationReference = EBF102D6C8CA46E223A517A4 /* Pods-RunnerTests.profile.xcconfig */; 552 | buildSettings = { 553 | BUNDLE_LOADER = "$(TEST_HOST)"; 554 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; 555 | CODE_SIGN_STYLE = Automatic; 556 | CURRENT_PROJECT_VERSION = 1; 557 | DEVELOPMENT_TEAM = Q6ZHR7WWAY; 558 | GENERATE_INFOPLIST_FILE = YES; 559 | MARKETING_VERSION = 1.0; 560 | PRODUCT_BUNDLE_IDENTIFIER = com.bagussubagja.flutterlivenessdetection; 561 | PRODUCT_NAME = "$(TARGET_NAME)"; 562 | SWIFT_VERSION = 5.0; 563 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; 564 | }; 565 | name = Profile; 566 | }; 567 | 97C147031CF9000F007C117D /* Debug */ = { 568 | isa = XCBuildConfiguration; 569 | buildSettings = { 570 | ALWAYS_SEARCH_USER_PATHS = NO; 571 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 572 | CLANG_ANALYZER_NONNULL = YES; 573 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 574 | CLANG_CXX_LIBRARY = "libc++"; 575 | CLANG_ENABLE_MODULES = YES; 576 | CLANG_ENABLE_OBJC_ARC = YES; 577 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 578 | CLANG_WARN_BOOL_CONVERSION = YES; 579 | CLANG_WARN_COMMA = YES; 580 | CLANG_WARN_CONSTANT_CONVERSION = YES; 581 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 582 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 583 | CLANG_WARN_EMPTY_BODY = YES; 584 | CLANG_WARN_ENUM_CONVERSION = YES; 585 | CLANG_WARN_INFINITE_RECURSION = YES; 586 | CLANG_WARN_INT_CONVERSION = YES; 587 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 588 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 589 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 590 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 591 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 592 | CLANG_WARN_STRICT_PROTOTYPES = YES; 593 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 594 | CLANG_WARN_UNREACHABLE_CODE = YES; 595 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 596 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 597 | COPY_PHASE_STRIP = NO; 598 | DEBUG_INFORMATION_FORMAT = dwarf; 599 | ENABLE_STRICT_OBJC_MSGSEND = YES; 600 | ENABLE_TESTABILITY = YES; 601 | ENABLE_USER_SCRIPT_SANDBOXING = NO; 602 | GCC_C_LANGUAGE_STANDARD = gnu99; 603 | GCC_DYNAMIC_NO_PIC = NO; 604 | GCC_NO_COMMON_BLOCKS = YES; 605 | GCC_OPTIMIZATION_LEVEL = 0; 606 | GCC_PREPROCESSOR_DEFINITIONS = ( 607 | "DEBUG=1", 608 | "$(inherited)", 609 | ); 610 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 611 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 612 | GCC_WARN_UNDECLARED_SELECTOR = YES; 613 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 614 | GCC_WARN_UNUSED_FUNCTION = YES; 615 | GCC_WARN_UNUSED_VARIABLE = YES; 616 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 617 | MTL_ENABLE_DEBUG_INFO = YES; 618 | ONLY_ACTIVE_ARCH = YES; 619 | SDKROOT = iphoneos; 620 | TARGETED_DEVICE_FAMILY = "1,2"; 621 | }; 622 | name = Debug; 623 | }; 624 | 97C147041CF9000F007C117D /* Release */ = { 625 | isa = XCBuildConfiguration; 626 | buildSettings = { 627 | ALWAYS_SEARCH_USER_PATHS = NO; 628 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 629 | CLANG_ANALYZER_NONNULL = YES; 630 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 631 | CLANG_CXX_LIBRARY = "libc++"; 632 | CLANG_ENABLE_MODULES = YES; 633 | CLANG_ENABLE_OBJC_ARC = YES; 634 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 635 | CLANG_WARN_BOOL_CONVERSION = YES; 636 | CLANG_WARN_COMMA = YES; 637 | CLANG_WARN_CONSTANT_CONVERSION = YES; 638 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 639 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 640 | CLANG_WARN_EMPTY_BODY = YES; 641 | CLANG_WARN_ENUM_CONVERSION = YES; 642 | CLANG_WARN_INFINITE_RECURSION = YES; 643 | CLANG_WARN_INT_CONVERSION = YES; 644 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 645 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 646 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 647 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 648 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 649 | CLANG_WARN_STRICT_PROTOTYPES = YES; 650 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 651 | CLANG_WARN_UNREACHABLE_CODE = YES; 652 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 653 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 654 | COPY_PHASE_STRIP = NO; 655 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 656 | ENABLE_NS_ASSERTIONS = NO; 657 | ENABLE_STRICT_OBJC_MSGSEND = YES; 658 | ENABLE_USER_SCRIPT_SANDBOXING = NO; 659 | GCC_C_LANGUAGE_STANDARD = gnu99; 660 | GCC_NO_COMMON_BLOCKS = YES; 661 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 662 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 663 | GCC_WARN_UNDECLARED_SELECTOR = YES; 664 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 665 | GCC_WARN_UNUSED_FUNCTION = YES; 666 | GCC_WARN_UNUSED_VARIABLE = YES; 667 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 668 | MTL_ENABLE_DEBUG_INFO = NO; 669 | SDKROOT = iphoneos; 670 | SUPPORTED_PLATFORMS = iphoneos; 671 | SWIFT_COMPILATION_MODE = wholemodule; 672 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 673 | TARGETED_DEVICE_FAMILY = "1,2"; 674 | VALIDATE_PRODUCT = YES; 675 | }; 676 | name = Release; 677 | }; 678 | 97C147061CF9000F007C117D /* Debug */ = { 679 | isa = XCBuildConfiguration; 680 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 681 | buildSettings = { 682 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 683 | CLANG_ENABLE_MODULES = YES; 684 | CODE_SIGN_IDENTITY = "Apple Development"; 685 | CODE_SIGN_STYLE = Automatic; 686 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 687 | DEVELOPMENT_TEAM = Q6ZHR7WWAY; 688 | ENABLE_BITCODE = NO; 689 | INFOPLIST_FILE = Runner/Info.plist; 690 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools"; 691 | LD_RUNPATH_SEARCH_PATHS = ( 692 | "$(inherited)", 693 | "@executable_path/Frameworks", 694 | ); 695 | MARKETING_VERSION = 1.0.5; 696 | PRODUCT_BUNDLE_IDENTIFIER = com.bagussubagja.flutterlivenessdetection; 697 | PRODUCT_NAME = "$(TARGET_NAME)"; 698 | PROVISIONING_PROFILE_SPECIFIER = ""; 699 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 700 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 701 | SWIFT_VERSION = 5.0; 702 | VERSIONING_SYSTEM = "apple-generic"; 703 | }; 704 | name = Debug; 705 | }; 706 | 97C147071CF9000F007C117D /* Release */ = { 707 | isa = XCBuildConfiguration; 708 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 709 | buildSettings = { 710 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 711 | CLANG_ENABLE_MODULES = YES; 712 | CODE_SIGN_IDENTITY = "Apple Development"; 713 | CODE_SIGN_STYLE = Automatic; 714 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 715 | DEVELOPMENT_TEAM = Q6ZHR7WWAY; 716 | ENABLE_BITCODE = NO; 717 | INFOPLIST_FILE = Runner/Info.plist; 718 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools"; 719 | LD_RUNPATH_SEARCH_PATHS = ( 720 | "$(inherited)", 721 | "@executable_path/Frameworks", 722 | ); 723 | MARKETING_VERSION = 1.0.5; 724 | PRODUCT_BUNDLE_IDENTIFIER = com.bagussubagja.flutterlivenessdetection; 725 | PRODUCT_NAME = "$(TARGET_NAME)"; 726 | PROVISIONING_PROFILE_SPECIFIER = ""; 727 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 728 | SWIFT_VERSION = 5.0; 729 | VERSIONING_SYSTEM = "apple-generic"; 730 | }; 731 | name = Release; 732 | }; 733 | /* End XCBuildConfiguration section */ 734 | 735 | /* Begin XCConfigurationList section */ 736 | 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { 737 | isa = XCConfigurationList; 738 | buildConfigurations = ( 739 | 331C8088294A63A400263BE5 /* Debug */, 740 | 331C8089294A63A400263BE5 /* Release */, 741 | 331C808A294A63A400263BE5 /* Profile */, 742 | ); 743 | defaultConfigurationIsVisible = 0; 744 | defaultConfigurationName = Release; 745 | }; 746 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 747 | isa = XCConfigurationList; 748 | buildConfigurations = ( 749 | 97C147031CF9000F007C117D /* Debug */, 750 | 97C147041CF9000F007C117D /* Release */, 751 | 249021D3217E4FDB00AE95B9 /* Profile */, 752 | ); 753 | defaultConfigurationIsVisible = 0; 754 | defaultConfigurationName = Release; 755 | }; 756 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 757 | isa = XCConfigurationList; 758 | buildConfigurations = ( 759 | 97C147061CF9000F007C117D /* Debug */, 760 | 97C147071CF9000F007C117D /* Release */, 761 | 249021D4217E4FDB00AE95B9 /* Profile */, 762 | ); 763 | defaultConfigurationIsVisible = 0; 764 | defaultConfigurationName = Release; 765 | }; 766 | /* End XCConfigurationList section */ 767 | }; 768 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 769 | } 770 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /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/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/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bagussubagja/flutter-liveness-detection-randomized-plugin/adddb10f2143a7eb1e12a31db15d8848b29ba39a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bagussubagja/flutter-liveness-detection-randomized-plugin/adddb10f2143a7eb1e12a31db15d8848b29ba39a/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/adddb10f2143a7eb1e12a31db15d8848b29ba39a/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/adddb10f2143a7eb1e12a31db15d8848b29ba39a/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/adddb10f2143a7eb1e12a31db15d8848b29ba39a/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/adddb10f2143a7eb1e12a31db15d8848b29ba39a/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/adddb10f2143a7eb1e12a31db15d8848b29ba39a/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/adddb10f2143a7eb1e12a31db15d8848b29ba39a/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/adddb10f2143a7eb1e12a31db15d8848b29ba39a/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/adddb10f2143a7eb1e12a31db15d8848b29ba39a/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/adddb10f2143a7eb1e12a31db15d8848b29ba39a/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/adddb10f2143a7eb1e12a31db15d8848b29ba39a/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/adddb10f2143a7eb1e12a31db15d8848b29ba39a/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/adddb10f2143a7eb1e12a31db15d8848b29ba39a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.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/adddb10f2143a7eb1e12a31db15d8848b29ba39a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bagussubagja/flutter-liveness-detection-randomized-plugin/adddb10f2143a7eb1e12a31db15d8848b29ba39a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bagussubagja/flutter-liveness-detection-randomized-plugin/adddb10f2143a7eb1e12a31db15d8848b29ba39a/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/adddb10f2143a7eb1e12a31db15d8848b29ba39a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /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/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_liveness_detection_randomized_plugin/index.dart'; 2 | 3 | void main() { 4 | runApp(const MaterialApp( 5 | debugShowCheckedModeBanner: false, 6 | home: HomeView(), 7 | )); 8 | } 9 | 10 | class HomeView extends StatefulWidget { 11 | const HomeView({super.key}); 12 | 13 | @override 14 | State createState() => _HomeViewState(); 15 | } 16 | 17 | class _HomeViewState extends State { 18 | List capturedImages = []; 19 | String? imgPath; 20 | 21 | @override 22 | void initState() { 23 | super.initState(); 24 | } 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | return Scaffold( 29 | body: Center( 30 | child: ListView( 31 | shrinkWrap: true, 32 | padding: const EdgeInsets.all(12), 33 | children: [ 34 | if (imgPath != null) ...[ 35 | const Text( 36 | 'Result Liveness Detection', 37 | textAlign: TextAlign.center, 38 | ), 39 | const SizedBox( 40 | height: 12, 41 | ), 42 | Align( 43 | child: SizedBox( 44 | height: 100, 45 | width: 100, 46 | child: ClipRRect( 47 | borderRadius: BorderRadius.circular(20), 48 | child: Image.file( 49 | File(imgPath!), 50 | fit: BoxFit.cover, 51 | ), 52 | ), 53 | ), 54 | ), 55 | const SizedBox( 56 | height: 12, 57 | ), 58 | ], 59 | ElevatedButton.icon( 60 | icon: const Icon(Icons.camera_alt_rounded), 61 | onPressed: () async { 62 | final String? response = 63 | await FlutterLivenessDetectionRandomizedPlugin.instance 64 | .livenessDetection( 65 | context: context, 66 | config: LivenessDetectionConfig( 67 | isEnableMaxBrightness: true, // enable disable max brightness when taking face photo 68 | durationLivenessVerify: 60, // default duration value is 45 second 69 | showDurationUiText: false, // show or hide duration remaining when perfoming liveness detection 70 | startWithInfoScreen: true, // show or hide tutorial screen 71 | useCustomizedLabel: false, // set to true value for enable 'customizedLabel', set to false to use default label 72 | // provide an empty string if you want to pass the liveness challenge 73 | customizedLabel: LivenessDetectionLabelModel( 74 | blink: '', // add empty string to skip/pass this liveness challenge 75 | lookDown: '', 76 | lookLeft: '', 77 | lookRight: '', 78 | lookUp: 'Tengok Atas', // example of customize label name for liveness challenge. it will replace default 'look up' 79 | smile: null, // null value to use default label name 80 | ), 81 | ), 82 | isEnableSnackBar: true, // snackbar to notify either liveness is success or failed 83 | shuffleListWithSmileLast: true, // put 'smile' challenge always at the end of liveness challenge, if `useCustomizedLabel` is true, this automatically set to false 84 | isDarkMode: false, // enable dark/light mode 85 | showCurrentStep: true, // show number current step of liveness 86 | ); 87 | if (mounted) { 88 | setState(() { 89 | imgPath = response; // result liveness 90 | }); 91 | } 92 | }, 93 | label: const Text('Liveness Detection System')), 94 | ], 95 | )), 96 | ); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /example/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | archive: 5 | dependency: transitive 6 | description: 7 | name: archive 8 | sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d 9 | url: "https://pub.dev" 10 | source: hosted 11 | version: "3.6.1" 12 | async: 13 | dependency: transitive 14 | description: 15 | name: async 16 | sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" 17 | url: "https://pub.dev" 18 | source: hosted 19 | version: "2.11.0" 20 | boolean_selector: 21 | dependency: transitive 22 | description: 23 | name: boolean_selector 24 | sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" 25 | url: "https://pub.dev" 26 | source: hosted 27 | version: "2.1.1" 28 | camera: 29 | dependency: transitive 30 | description: 31 | name: camera 32 | sha256: dfa8fc5a1adaeb95e7a54d86a5bd56f4bb0e035515354c8ac6d262e35cec2ec8 33 | url: "https://pub.dev" 34 | source: hosted 35 | version: "0.10.6" 36 | camera_android: 37 | dependency: transitive 38 | description: 39 | name: camera_android 40 | sha256: "007c57cdcace4751014071e3d42f2eb8a64a519254abed35b714223d81d66234" 41 | url: "https://pub.dev" 42 | source: hosted 43 | version: "0.10.10" 44 | camera_avfoundation: 45 | dependency: transitive 46 | description: 47 | name: camera_avfoundation 48 | sha256: "1eeb9ce7c9a397e312343fd7db337d95f35c3e65ad5a62ff637c8abce5102b98" 49 | url: "https://pub.dev" 50 | source: hosted 51 | version: "0.9.18+8" 52 | camera_platform_interface: 53 | dependency: transitive 54 | description: 55 | name: camera_platform_interface 56 | sha256: "953e7baed3a7c8fae92f7200afeb2be503ff1a17c3b4e4ed7b76f008c2810a31" 57 | url: "https://pub.dev" 58 | source: hosted 59 | version: "2.9.0" 60 | camera_web: 61 | dependency: transitive 62 | description: 63 | name: camera_web 64 | sha256: "595f28c89d1fb62d77c73c633193755b781c6d2e0ebcd8dc25b763b514e6ba8f" 65 | url: "https://pub.dev" 66 | source: hosted 67 | version: "0.3.5" 68 | characters: 69 | dependency: transitive 70 | description: 71 | name: characters 72 | sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" 73 | url: "https://pub.dev" 74 | source: hosted 75 | version: "1.3.0" 76 | clock: 77 | dependency: transitive 78 | description: 79 | name: clock 80 | sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf 81 | url: "https://pub.dev" 82 | source: hosted 83 | version: "1.1.1" 84 | collection: 85 | dependency: transitive 86 | description: 87 | name: collection 88 | sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a 89 | url: "https://pub.dev" 90 | source: hosted 91 | version: "1.18.0" 92 | cross_file: 93 | dependency: transitive 94 | description: 95 | name: cross_file 96 | sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" 97 | url: "https://pub.dev" 98 | source: hosted 99 | version: "0.3.4+2" 100 | crypto: 101 | dependency: transitive 102 | description: 103 | name: crypto 104 | sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" 105 | url: "https://pub.dev" 106 | source: hosted 107 | version: "3.0.6" 108 | cupertino_icons: 109 | dependency: "direct main" 110 | description: 111 | name: cupertino_icons 112 | sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 113 | url: "https://pub.dev" 114 | source: hosted 115 | version: "1.0.8" 116 | equatable: 117 | dependency: transitive 118 | description: 119 | name: equatable 120 | sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7" 121 | url: "https://pub.dev" 122 | source: hosted 123 | version: "2.0.7" 124 | fake_async: 125 | dependency: transitive 126 | description: 127 | name: fake_async 128 | sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" 129 | url: "https://pub.dev" 130 | source: hosted 131 | version: "1.3.1" 132 | file: 133 | dependency: transitive 134 | description: 135 | name: file 136 | sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" 137 | url: "https://pub.dev" 138 | source: hosted 139 | version: "7.0.0" 140 | flutter: 141 | dependency: "direct main" 142 | description: flutter 143 | source: sdk 144 | version: "0.0.0" 145 | flutter_driver: 146 | dependency: transitive 147 | description: flutter 148 | source: sdk 149 | version: "0.0.0" 150 | flutter_lints: 151 | dependency: "direct dev" 152 | description: 153 | name: flutter_lints 154 | sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c" 155 | url: "https://pub.dev" 156 | source: hosted 157 | version: "4.0.0" 158 | flutter_liveness_detection_randomized_plugin: 159 | dependency: "direct main" 160 | description: 161 | path: ".." 162 | relative: true 163 | source: path 164 | version: "1.0.5" 165 | flutter_plugin_android_lifecycle: 166 | dependency: transitive 167 | description: 168 | name: flutter_plugin_android_lifecycle 169 | sha256: "1c2b787f99bdca1f3718543f81d38aa1b124817dfeb9fb196201bea85b6134bf" 170 | url: "https://pub.dev" 171 | source: hosted 172 | version: "2.0.26" 173 | flutter_test: 174 | dependency: "direct dev" 175 | description: flutter 176 | source: sdk 177 | version: "0.0.0" 178 | flutter_web_plugins: 179 | dependency: transitive 180 | description: flutter 181 | source: sdk 182 | version: "0.0.0" 183 | fuchsia_remote_debug_protocol: 184 | dependency: transitive 185 | description: flutter 186 | source: sdk 187 | version: "0.0.0" 188 | google_mlkit_commons: 189 | dependency: transitive 190 | description: 191 | name: google_mlkit_commons 192 | sha256: "27d626c66a181351a953eba5b6ff1ff123aadb891b4dab085b292118f039d6ac" 193 | url: "https://pub.dev" 194 | source: hosted 195 | version: "0.7.1" 196 | google_mlkit_face_detection: 197 | dependency: transitive 198 | description: 199 | name: google_mlkit_face_detection 200 | sha256: "5b597061cafe4dfa70f66adddadd19381eb88bd3312b074528c62b246392304b" 201 | url: "https://pub.dev" 202 | source: hosted 203 | version: "0.11.0" 204 | image: 205 | dependency: transitive 206 | description: 207 | name: image 208 | sha256: f31d52537dc417fdcde36088fdf11d191026fd5e4fae742491ebd40e5a8bea7d 209 | url: "https://pub.dev" 210 | source: hosted 211 | version: "4.3.0" 212 | integration_test: 213 | dependency: "direct dev" 214 | description: flutter 215 | source: sdk 216 | version: "0.0.0" 217 | leak_tracker: 218 | dependency: transitive 219 | description: 220 | name: leak_tracker 221 | sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" 222 | url: "https://pub.dev" 223 | source: hosted 224 | version: "10.0.5" 225 | leak_tracker_flutter_testing: 226 | dependency: transitive 227 | description: 228 | name: leak_tracker_flutter_testing 229 | sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" 230 | url: "https://pub.dev" 231 | source: hosted 232 | version: "3.0.5" 233 | leak_tracker_testing: 234 | dependency: transitive 235 | description: 236 | name: leak_tracker_testing 237 | sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" 238 | url: "https://pub.dev" 239 | source: hosted 240 | version: "3.0.1" 241 | lints: 242 | dependency: transitive 243 | description: 244 | name: lints 245 | sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235" 246 | url: "https://pub.dev" 247 | source: hosted 248 | version: "4.0.0" 249 | lottie: 250 | dependency: transitive 251 | description: 252 | name: lottie 253 | sha256: a93542cc2d60a7057255405f62252533f8e8956e7e06754955669fd32fb4b216 254 | url: "https://pub.dev" 255 | source: hosted 256 | version: "2.7.0" 257 | matcher: 258 | dependency: transitive 259 | description: 260 | name: matcher 261 | sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb 262 | url: "https://pub.dev" 263 | source: hosted 264 | version: "0.12.16+1" 265 | material_color_utilities: 266 | dependency: transitive 267 | description: 268 | name: material_color_utilities 269 | sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec 270 | url: "https://pub.dev" 271 | source: hosted 272 | version: "0.11.1" 273 | meta: 274 | dependency: transitive 275 | description: 276 | name: meta 277 | sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 278 | url: "https://pub.dev" 279 | source: hosted 280 | version: "1.15.0" 281 | path: 282 | dependency: transitive 283 | description: 284 | name: path 285 | sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" 286 | url: "https://pub.dev" 287 | source: hosted 288 | version: "1.9.0" 289 | petitparser: 290 | dependency: transitive 291 | description: 292 | name: petitparser 293 | sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 294 | url: "https://pub.dev" 295 | source: hosted 296 | version: "6.0.2" 297 | platform: 298 | dependency: transitive 299 | description: 300 | name: platform 301 | sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" 302 | url: "https://pub.dev" 303 | source: hosted 304 | version: "3.1.5" 305 | plugin_platform_interface: 306 | dependency: transitive 307 | description: 308 | name: plugin_platform_interface 309 | sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" 310 | url: "https://pub.dev" 311 | source: hosted 312 | version: "2.1.8" 313 | process: 314 | dependency: transitive 315 | description: 316 | name: process 317 | sha256: "21e54fd2faf1b5bdd5102afd25012184a6793927648ea81eea80552ac9405b32" 318 | url: "https://pub.dev" 319 | source: hosted 320 | version: "5.0.2" 321 | screen_brightness: 322 | dependency: transitive 323 | description: 324 | name: screen_brightness 325 | sha256: eca7bd9d2c3c688bcad14855361cab7097839400b6b4a56f62b7ae511c709958 326 | url: "https://pub.dev" 327 | source: hosted 328 | version: "2.1.2" 329 | screen_brightness_android: 330 | dependency: transitive 331 | description: 332 | name: screen_brightness_android 333 | sha256: "6ba1b5812f66c64e9e4892be2d36ecd34210f4e0da8bdec6a2ea34f1aa42683e" 334 | url: "https://pub.dev" 335 | source: hosted 336 | version: "2.1.1" 337 | screen_brightness_ios: 338 | dependency: transitive 339 | description: 340 | name: screen_brightness_ios 341 | sha256: bfd9bfd0ac852e7aa170e7e356cc27195b2a75037b72c8c6336cf6fb2115cffb 342 | url: "https://pub.dev" 343 | source: hosted 344 | version: "2.1.1" 345 | screen_brightness_macos: 346 | dependency: transitive 347 | description: 348 | name: screen_brightness_macos 349 | sha256: "4edf330ad21078686d8bfaf89413325fbaf571dcebe1e89254d675a3f288b5b9" 350 | url: "https://pub.dev" 351 | source: hosted 352 | version: "2.1.1" 353 | screen_brightness_platform_interface: 354 | dependency: transitive 355 | description: 356 | name: screen_brightness_platform_interface 357 | sha256: "737bd47b57746bc4291cab1b8a5843ee881af499514881b0247ec77447ee769c" 358 | url: "https://pub.dev" 359 | source: hosted 360 | version: "2.1.0" 361 | screen_brightness_windows: 362 | dependency: transitive 363 | description: 364 | name: screen_brightness_windows 365 | sha256: d3518bf0f5d7a884cee2c14449ae0b36803802866de09f7ef74077874b6b2448 366 | url: "https://pub.dev" 367 | source: hosted 368 | version: "2.1.0" 369 | sky_engine: 370 | dependency: transitive 371 | description: flutter 372 | source: sdk 373 | version: "0.0.99" 374 | source_span: 375 | dependency: transitive 376 | description: 377 | name: source_span 378 | sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" 379 | url: "https://pub.dev" 380 | source: hosted 381 | version: "1.10.0" 382 | stack_trace: 383 | dependency: transitive 384 | description: 385 | name: stack_trace 386 | sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" 387 | url: "https://pub.dev" 388 | source: hosted 389 | version: "1.11.1" 390 | stream_channel: 391 | dependency: transitive 392 | description: 393 | name: stream_channel 394 | sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 395 | url: "https://pub.dev" 396 | source: hosted 397 | version: "2.1.2" 398 | stream_transform: 399 | dependency: transitive 400 | description: 401 | name: stream_transform 402 | sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 403 | url: "https://pub.dev" 404 | source: hosted 405 | version: "2.1.1" 406 | string_scanner: 407 | dependency: transitive 408 | description: 409 | name: string_scanner 410 | sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" 411 | url: "https://pub.dev" 412 | source: hosted 413 | version: "1.2.0" 414 | sync_http: 415 | dependency: transitive 416 | description: 417 | name: sync_http 418 | sha256: "7f0cd72eca000d2e026bcd6f990b81d0ca06022ef4e32fb257b30d3d1014a961" 419 | url: "https://pub.dev" 420 | source: hosted 421 | version: "0.3.1" 422 | term_glyph: 423 | dependency: transitive 424 | description: 425 | name: term_glyph 426 | sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 427 | url: "https://pub.dev" 428 | source: hosted 429 | version: "1.2.1" 430 | test_api: 431 | dependency: transitive 432 | description: 433 | name: test_api 434 | sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" 435 | url: "https://pub.dev" 436 | source: hosted 437 | version: "0.7.2" 438 | typed_data: 439 | dependency: transitive 440 | description: 441 | name: typed_data 442 | sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 443 | url: "https://pub.dev" 444 | source: hosted 445 | version: "1.4.0" 446 | vector_math: 447 | dependency: transitive 448 | description: 449 | name: vector_math 450 | sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" 451 | url: "https://pub.dev" 452 | source: hosted 453 | version: "2.1.4" 454 | vm_service: 455 | dependency: transitive 456 | description: 457 | name: vm_service 458 | sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" 459 | url: "https://pub.dev" 460 | source: hosted 461 | version: "14.2.5" 462 | web: 463 | dependency: transitive 464 | description: 465 | name: web 466 | sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" 467 | url: "https://pub.dev" 468 | source: hosted 469 | version: "1.1.1" 470 | webdriver: 471 | dependency: transitive 472 | description: 473 | name: webdriver 474 | sha256: "003d7da9519e1e5f329422b36c4dcdf18d7d2978d1ba099ea4e45ba490ed845e" 475 | url: "https://pub.dev" 476 | source: hosted 477 | version: "3.0.3" 478 | xml: 479 | dependency: transitive 480 | description: 481 | name: xml 482 | sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 483 | url: "https://pub.dev" 484 | source: hosted 485 | version: "6.5.0" 486 | sdks: 487 | dart: ">=3.5.3 <4.0.0" 488 | flutter: ">=3.24.0" 489 | -------------------------------------------------------------------------------- /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.5.3 9 | 10 | # Dependencies specify other packages that your package needs in order to work. 11 | # To automatically upgrade your package dependencies to the latest versions 12 | # consider running `flutter pub upgrade --major-versions`. Alternatively, 13 | # dependencies can be manually updated by changing the version numbers below to 14 | # the latest version available on pub.dev. To see which dependencies have newer 15 | # versions available, run `flutter pub outdated`. 16 | dependencies: 17 | flutter: 18 | sdk: flutter 19 | 20 | flutter_liveness_detection_randomized_plugin: 21 | # When depending on this package from a real application you should use: 22 | # flutter_liveness_detection_randomized_plugin: ^x.y.z 23 | # See https://dart.dev/tools/pub/dependencies#version-constraints 24 | # The example app is bundled with the plugin so we use a path dependency on 25 | # the parent directory to use the current plugin's version. 26 | path: ../ 27 | 28 | # The following adds the Cupertino Icons font to your application. 29 | # Use with the CupertinoIcons class for iOS style icons. 30 | cupertino_icons: ^1.0.8 31 | 32 | dev_dependencies: 33 | integration_test: 34 | sdk: flutter 35 | flutter_test: 36 | sdk: flutter 37 | 38 | # The "flutter_lints" package below contains a set of recommended lints to 39 | # encourage good coding practices. The lint set provided by the package is 40 | # activated in the `analysis_options.yaml` file located at the root of your 41 | # package. See that file for information about deactivating specific lint 42 | # rules and activating additional ones. 43 | flutter_lints: ^4.0.0 44 | 45 | # For information on the generic Dart part of this file, see the 46 | # following page: https://dart.dev/tools/pub/pubspec 47 | 48 | # The following section is specific to Flutter packages. 49 | flutter: 50 | 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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /ios/Assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bagussubagja/flutter-liveness-detection-randomized-plugin/adddb10f2143a7eb1e12a31db15d8848b29ba39a/ios/Assets/.gitkeep -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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 | required bool isEnableSnackBar, 17 | required bool shuffleListWithSmileLast, 18 | required bool showCurrentStep, 19 | required bool isDarkMode, 20 | }) async { 21 | final String? capturedFacePath = await Navigator.of(context).push( 22 | MaterialPageRoute( 23 | builder: (context) => LivenessDetectionView( 24 | config: config, 25 | isEnableSnackBar: isEnableSnackBar, 26 | shuffleListWithSmileLast: shuffleListWithSmileLast, 27 | showCurrentStep: showCurrentStep, 28 | isDarkMode: isDarkMode, 29 | ), 30 | ), 31 | ); 32 | return capturedFacePath; 33 | } 34 | 35 | Future getPlatformVersion() { 36 | return FlutterLivenessDetectionRandomizedPluginPlatform.instance 37 | .getPlatformVersion(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | 13 | export 'src/core/index.dart'; 14 | export './flutter_liveness_detection_randomized_plugin.dart'; 15 | export './flutter_liveness_detection_randomized_plugin_method_channel.dart'; 16 | export './flutter_liveness_detection_randomized_plugin_platform_interface.dart'; -------------------------------------------------------------------------------- /lib/src/core/constants/index.dart: -------------------------------------------------------------------------------- 1 | export 'liveness_detection_step_constant.dart'; -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lib/src/core/enums/index.dart: -------------------------------------------------------------------------------- 1 | export 'liveness_detection_step.dart'; -------------------------------------------------------------------------------- /lib/src/core/enums/liveness_detection_step.dart: -------------------------------------------------------------------------------- 1 | enum LivenessDetectionStep { 2 | blink, 3 | lookRight, 4 | lookLeft, 5 | lookUp, 6 | lookDown, 7 | smile 8 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lib/src/core/utils/index.dart: -------------------------------------------------------------------------------- 1 | export 'machine_learning_kit_helper.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 | final List faces = await faceDetector.processImage(imgFile); 23 | if (faces.isNotEmpty) return faces; 24 | } 25 | 26 | return []; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/src/models/index.dart: -------------------------------------------------------------------------------- 1 | export 'liveness_detection_label_model.dart'; 2 | export 'liveness_detection_step_item.dart'; -------------------------------------------------------------------------------- /lib/src/models/liveness_detection_config.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_liveness_detection_randomized_plugin/src/models/liveness_detection_label_model.dart'; 2 | 3 | class LivenessDetectionConfig { 4 | final bool startWithInfoScreen; 5 | final int? durationLivenessVerify; 6 | final bool showDurationUiText; 7 | final bool useCustomizedLabel; 8 | final LivenessDetectionLabelModel? customizedLabel; 9 | final bool isEnableMaxBrightness; 10 | 11 | LivenessDetectionConfig({ 12 | this.startWithInfoScreen = false, 13 | this.durationLivenessVerify = 45, 14 | this.showDurationUiText = false, 15 | this.useCustomizedLabel = false, 16 | this.customizedLabel, 17 | this.isEnableMaxBrightness = true 18 | }); 19 | } 20 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 get props => []; 9 | } 10 | 11 | class LivenessThresholdSmile extends LivenessDetectionThreshold { 12 | final double probability; 13 | 14 | LivenessThresholdSmile({ 15 | this.probability = 0.75, 16 | }) : super() { 17 | assert( 18 | probability < 1.0 || probability > 0.0, 19 | "Smile Probability has to be between 1.0 and 0.0", 20 | ); 21 | } 22 | 23 | LivenessThresholdSmile copyWith({ 24 | double? probability, 25 | }) { 26 | return LivenessThresholdSmile( 27 | probability: probability ?? this.probability, 28 | ); 29 | } 30 | 31 | @override 32 | Map toMap() { 33 | final result = {}; 34 | 35 | result.addAll({'probability': probability}); 36 | 37 | return result; 38 | } 39 | 40 | @override 41 | factory LivenessThresholdSmile.fromMap(Map map) { 42 | return LivenessThresholdSmile( 43 | probability: map['probability']?.toDouble() ?? 0.0, 44 | ); 45 | } 46 | 47 | String toJson() => json.encode(toMap()); 48 | 49 | factory LivenessThresholdSmile.fromJson(String source) => 50 | LivenessThresholdSmile.fromMap(json.decode(source)); 51 | 52 | @override 53 | String toString() => 'LivenessThresholdSmile(probability: $probability)'; 54 | 55 | @override 56 | bool operator ==(Object other) { 57 | if (identical(this, other)) return true; 58 | 59 | return other is LivenessThresholdSmile && other.probability == probability; 60 | } 61 | 62 | @override 63 | int get hashCode => probability.hashCode; 64 | 65 | @override 66 | LivenessThresholdSmile fromDict(Map map) { 67 | return LivenessThresholdSmile( 68 | probability: map['probability']?.toDouble() ?? 0.0, 69 | ); 70 | } 71 | } 72 | 73 | class LivenessThresholdBlink extends LivenessDetectionThreshold { 74 | 75 | final double leftEyeProbability; 76 | final double rightEyeProbability; 77 | 78 | LivenessThresholdBlink({ 79 | this.leftEyeProbability = 0.25, 80 | this.rightEyeProbability = 0.25, 81 | }) : super() { 82 | assert( 83 | leftEyeProbability < 1.0 || leftEyeProbability > 0.0, 84 | "Left Probability has to be between 1.0 and 0.0", 85 | ); 86 | assert( 87 | rightEyeProbability < 1.0 || rightEyeProbability > 0.0, 88 | "Right Probability has to be between 1.0 and 0.0", 89 | ); 90 | } 91 | 92 | LivenessThresholdBlink copyWith({ 93 | double? leftEyeProbability, 94 | double? rightEyeProbability, 95 | }) { 96 | return LivenessThresholdBlink( 97 | leftEyeProbability: leftEyeProbability ?? this.leftEyeProbability, 98 | rightEyeProbability: rightEyeProbability ?? this.rightEyeProbability, 99 | ); 100 | } 101 | 102 | @override 103 | Map toMap() { 104 | final result = {}; 105 | 106 | result.addAll({'leftEyeProbability': leftEyeProbability}); 107 | result.addAll({'rightEyeProbability': rightEyeProbability}); 108 | 109 | return result; 110 | } 111 | 112 | factory LivenessThresholdBlink.fromMap(Map map) { 113 | return LivenessThresholdBlink( 114 | leftEyeProbability: map['leftEyeProbability']?.toDouble() ?? 0.0, 115 | rightEyeProbability: map['rightEyeProbability']?.toDouble() ?? 0.0, 116 | ); 117 | } 118 | 119 | String toJson() => json.encode(toMap()); 120 | 121 | factory LivenessThresholdBlink.fromJson(String source) => 122 | LivenessThresholdBlink.fromMap(json.decode(source)); 123 | 124 | @override 125 | String toString() => 126 | 'LivenessThresholdBlink(leftEyeProbability: $leftEyeProbability, rightEyeProbability: $rightEyeProbability)'; 127 | 128 | @override 129 | bool operator ==(Object other) { 130 | if (identical(this, other)) return true; 131 | 132 | return other is LivenessThresholdBlink && 133 | other.leftEyeProbability == leftEyeProbability && 134 | other.rightEyeProbability == rightEyeProbability; 135 | } 136 | 137 | @override 138 | int get hashCode => 139 | leftEyeProbability.hashCode ^ rightEyeProbability.hashCode; 140 | 141 | @override 142 | LivenessDetectionThreshold fromDict(Map map) { 143 | return LivenessThresholdBlink( 144 | leftEyeProbability: map['leftEyeProbability']?.toDouble() ?? 0.0, 145 | rightEyeProbability: map['rightEyeProbability']?.toDouble() ?? 0.0, 146 | ); 147 | } 148 | } 149 | 150 | class LivenessThresholdHead extends LivenessDetectionThreshold{ 151 | final double rotationAngle; 152 | 153 | LivenessThresholdHead({ 154 | this.rotationAngle = 45.0, 155 | }) : super() { 156 | assert( 157 | rotationAngle > 180.0 || rotationAngle > 0.0, 158 | "To detect the livelyness of the face, it has to be properly visible in the camera. The threshold angle should be more than 0.0 degrees and less than 180 degrees.", 159 | ); 160 | } 161 | 162 | LivenessThresholdHead copyWith({ 163 | double? rotationAngle, 164 | }) { 165 | return LivenessThresholdHead( 166 | rotationAngle: rotationAngle ?? this.rotationAngle, 167 | ); 168 | } 169 | 170 | @override 171 | Map toMap() { 172 | final result = {}; 173 | 174 | result.addAll({'rotationAngle': rotationAngle}); 175 | 176 | return result; 177 | } 178 | 179 | factory LivenessThresholdHead.fromMap(Map map) { 180 | return LivenessThresholdHead( 181 | rotationAngle: map['rotationAngle']?.toDouble() ?? 0.0, 182 | ); 183 | } 184 | 185 | String toJson() => json.encode(toMap()); 186 | 187 | factory LivenessThresholdHead.fromJson(String source) => 188 | LivenessThresholdHead.fromMap(json.decode(source)); 189 | 190 | @override 191 | String toString() => 192 | 'LivenessThresholdHead(rotationAngle: $rotationAngle)'; 193 | 194 | @override 195 | bool operator ==(Object other) { 196 | if (identical(this, other)) return true; 197 | 198 | return other is LivenessThresholdHead && 199 | other.rotationAngle == rotationAngle; 200 | } 201 | 202 | @override 203 | int get hashCode => rotationAngle.hashCode; 204 | 205 | @override 206 | LivenessDetectionThreshold fromDict(Map map) { 207 | return LivenessThresholdHead( 208 | rotationAngle: map['rotationAngle']?.toDouble() ?? 0.0, 209 | ); 210 | } 211 | } -------------------------------------------------------------------------------- /lib/src/presentation/views/index.dart: -------------------------------------------------------------------------------- 1 | export '../widgets/index.dart'; 2 | export 'liveness_detection_view.dart'; 3 | -------------------------------------------------------------------------------- /lib/src/presentation/views/liveness_detection_view.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: depend_on_referenced_packages 2 | import 'package:flutter/foundation.dart'; 3 | import 'package:flutter_liveness_detection_randomized_plugin/index.dart'; 4 | import 'package:flutter_liveness_detection_randomized_plugin/src/core/constants/liveness_detection_step_constant.dart'; 5 | import 'package:collection/collection.dart'; 6 | import 'package:screen_brightness/screen_brightness.dart'; 7 | 8 | List availableCams = []; 9 | 10 | class LivenessDetectionView extends StatefulWidget { 11 | final LivenessDetectionConfig config; 12 | final bool isEnableSnackBar; 13 | final bool shuffleListWithSmileLast; 14 | final bool showCurrentStep; 15 | final bool isDarkMode; 16 | 17 | const LivenessDetectionView({ 18 | super.key, 19 | required this.config, 20 | required this.isEnableSnackBar, 21 | this.isDarkMode = true, 22 | this.showCurrentStep = false, 23 | this.shuffleListWithSmileLast = true, 24 | }); 25 | 26 | @override 27 | State createState() => _LivenessDetectionScreenState(); 28 | } 29 | 30 | class _LivenessDetectionScreenState extends State { 31 | // Camera related variables 32 | CameraController? _cameraController; 33 | int _cameraIndex = 0; 34 | bool _isBusy = false; 35 | bool _isTakingPicture = false; 36 | Timer? _timerToDetectFace; 37 | 38 | // Detection state variables 39 | late bool _isInfoStepCompleted; 40 | bool _isProcessingStep = false; 41 | bool _faceDetectedState = false; 42 | static late List _cachedShuffledSteps; 43 | static bool _isShuffled = false; 44 | 45 | // Brightness Screen 46 | Future setApplicationBrightness(double brightness) async { 47 | try { 48 | await ScreenBrightness.instance 49 | .setApplicationScreenBrightness(brightness); 50 | } catch (e) { 51 | throw 'Failed to set application brightness'; 52 | } 53 | } 54 | 55 | Future resetApplicationBrightness() async { 56 | try { 57 | await ScreenBrightness.instance.resetApplicationScreenBrightness(); 58 | } catch (e) { 59 | throw 'Failed to reset application brightness'; 60 | } 61 | } 62 | 63 | // Steps related variables 64 | late final List steps; 65 | final GlobalKey _stepsKey = 66 | GlobalKey(); 67 | 68 | static void shuffleListLivenessChallenge({ 69 | required List list, 70 | required bool isSmileLast, 71 | }) { 72 | if (isSmileLast) { 73 | int? blinkIndex = 74 | list.indexWhere((item) => item.step == LivenessDetectionStep.blink); 75 | int? smileIndex = 76 | list.indexWhere((item) => item.step == LivenessDetectionStep.smile); 77 | 78 | if (blinkIndex != -1 && smileIndex != -1) { 79 | LivenessDetectionStepItem blinkItem = list.removeAt(blinkIndex); 80 | LivenessDetectionStepItem smileItem = list 81 | .removeAt(smileIndex > blinkIndex ? smileIndex - 1 : smileIndex); 82 | list.shuffle(Random()); 83 | list.insert(list.length - 1, blinkItem); 84 | list.add(smileItem); 85 | } else { 86 | list.shuffle(Random()); 87 | } 88 | } else { 89 | list.shuffle(Random()); 90 | } 91 | } 92 | 93 | List manualRandomItemLiveness(List list) { 94 | final random = Random(); 95 | List shuffledList = List.from(list); 96 | for (int i = shuffledList.length - 1; i > 0; i--) { 97 | int j = random.nextInt(i + 1); 98 | 99 | T temp = shuffledList[i]; 100 | shuffledList[i] = shuffledList[j]; 101 | shuffledList[j] = temp; 102 | } 103 | return shuffledList; 104 | } 105 | 106 | List customizedLivenessLabel( 107 | LivenessDetectionLabelModel label) { 108 | if (!_isShuffled) { 109 | List customizedSteps = []; 110 | 111 | if (label.blink != "" && widget.config.useCustomizedLabel) { 112 | customizedSteps.add(LivenessDetectionStepItem( 113 | step: LivenessDetectionStep.blink, 114 | title: label.blink ?? "Blink 2-3 Times", 115 | )); 116 | } 117 | 118 | if (label.lookRight != "" && widget.config.useCustomizedLabel) { 119 | customizedSteps.add(LivenessDetectionStepItem( 120 | step: LivenessDetectionStep.lookRight, 121 | title: label.lookRight ?? "Look Right", 122 | )); 123 | } 124 | 125 | if (label.lookLeft != "" && widget.config.useCustomizedLabel) { 126 | customizedSteps.add(LivenessDetectionStepItem( 127 | step: LivenessDetectionStep.lookLeft, 128 | title: label.lookLeft ?? "Look Left", 129 | )); 130 | } 131 | 132 | if (label.lookUp != "" && widget.config.useCustomizedLabel) { 133 | customizedSteps.add(LivenessDetectionStepItem( 134 | step: LivenessDetectionStep.lookUp, 135 | title: label.lookUp ?? "Look Up", 136 | )); 137 | } 138 | 139 | if (label.lookDown != "" && widget.config.useCustomizedLabel) { 140 | customizedSteps.add(LivenessDetectionStepItem( 141 | step: LivenessDetectionStep.lookDown, 142 | title: label.lookDown ?? "Look Down", 143 | )); 144 | } 145 | 146 | if (label.smile != "" && widget.config.useCustomizedLabel) { 147 | customizedSteps.add(LivenessDetectionStepItem( 148 | step: LivenessDetectionStep.smile, 149 | title: label.smile ?? "Smile", 150 | )); 151 | } 152 | _cachedShuffledSteps = manualRandomItemLiveness(customizedSteps); 153 | _isShuffled = true; 154 | } 155 | 156 | return _cachedShuffledSteps; 157 | } 158 | 159 | @override 160 | void initState() { 161 | _preInitCallBack(); 162 | super.initState(); 163 | WidgetsBinding.instance.addPostFrameCallback((_) => _postFrameCallBack()); 164 | } 165 | 166 | @override 167 | void dispose() { 168 | _timerToDetectFace?.cancel(); 169 | _timerToDetectFace = null; 170 | _cameraController?.dispose(); 171 | shuffleListLivenessChallenge( 172 | list: widget.config.useCustomizedLabel && 173 | widget.config.customizedLabel != null 174 | ? customizedLivenessLabel(widget.config.customizedLabel!) 175 | : stepLiveness, 176 | isSmileLast: widget.config.useCustomizedLabel 177 | ? false 178 | : widget.shuffleListWithSmileLast); 179 | if (widget.config.isEnableMaxBrightness) { 180 | resetApplicationBrightness(); 181 | } 182 | super.dispose(); 183 | } 184 | 185 | void _preInitCallBack() { 186 | _isInfoStepCompleted = !widget.config.startWithInfoScreen; 187 | shuffleListLivenessChallenge( 188 | list: widget.config.useCustomizedLabel && 189 | widget.config.customizedLabel != null 190 | ? customizedLivenessLabel(widget.config.customizedLabel!) 191 | : stepLiveness, 192 | isSmileLast: widget.config.useCustomizedLabel 193 | ? false 194 | : widget.shuffleListWithSmileLast); 195 | if (widget.config.isEnableMaxBrightness) { 196 | setApplicationBrightness(1.0); 197 | } 198 | } 199 | 200 | void _postFrameCallBack() async { 201 | availableCams = await availableCameras(); 202 | if (availableCams.any((element) => 203 | element.lensDirection == CameraLensDirection.front && 204 | element.sensorOrientation == 90)) { 205 | _cameraIndex = availableCams.indexOf( 206 | availableCams.firstWhere((element) => 207 | element.lensDirection == CameraLensDirection.front && 208 | element.sensorOrientation == 90), 209 | ); 210 | } else { 211 | _cameraIndex = availableCams.indexOf( 212 | availableCams.firstWhere( 213 | (element) => element.lensDirection == CameraLensDirection.front), 214 | ); 215 | } 216 | if (!widget.config.startWithInfoScreen) { 217 | _startLiveFeed(); 218 | } 219 | 220 | shuffleListLivenessChallenge( 221 | list: widget.config.useCustomizedLabel && 222 | widget.config.customizedLabel != null 223 | ? customizedLivenessLabel(widget.config.customizedLabel!) 224 | : stepLiveness, 225 | isSmileLast: widget.shuffleListWithSmileLast); 226 | } 227 | 228 | void _startLiveFeed() async { 229 | final camera = availableCams[_cameraIndex]; 230 | _cameraController = 231 | CameraController(camera, ResolutionPreset.high, enableAudio: false); 232 | 233 | _cameraController?.initialize().then((_) { 234 | if (!mounted) return; 235 | _cameraController?.startImageStream(_processCameraImage); 236 | setState(() {}); 237 | }); 238 | _startFaceDetectionTimer(); 239 | } 240 | 241 | void _startFaceDetectionTimer() { 242 | _timerToDetectFace = Timer( 243 | Duration(seconds: widget.config.durationLivenessVerify ?? 45), 244 | () => _onDetectionCompleted(imgToReturn: null)); 245 | } 246 | 247 | Future _processCameraImage(CameraImage cameraImage) async { 248 | final WriteBuffer allBytes = WriteBuffer(); 249 | for (final Plane plane in cameraImage.planes) { 250 | allBytes.putUint8List(plane.bytes); 251 | } 252 | final bytes = allBytes.done().buffer.asUint8List(); 253 | 254 | final Size imageSize = Size( 255 | cameraImage.width.toDouble(), 256 | cameraImage.height.toDouble(), 257 | ); 258 | 259 | final camera = availableCams[_cameraIndex]; 260 | final imageRotation = 261 | InputImageRotationValue.fromRawValue(camera.sensorOrientation); 262 | if (imageRotation == null) return; 263 | 264 | final inputImageFormat = 265 | InputImageFormatValue.fromRawValue(cameraImage.format.raw); 266 | if (inputImageFormat == null) return; 267 | 268 | final inputImageData = InputImageMetadata( 269 | size: imageSize, 270 | rotation: imageRotation, 271 | format: inputImageFormat, 272 | bytesPerRow: cameraImage.planes[0].bytesPerRow, 273 | ); 274 | 275 | final inputImage = InputImage.fromBytes( 276 | metadata: inputImageData, 277 | bytes: bytes, 278 | ); 279 | 280 | _processImage(inputImage); 281 | } 282 | 283 | Future _processImage(InputImage inputImage) async { 284 | if (_isBusy) return; 285 | _isBusy = true; 286 | 287 | final faces = 288 | await MachineLearningKitHelper.instance.processInputImage(inputImage); 289 | 290 | if (inputImage.metadata?.size != null && 291 | inputImage.metadata?.rotation != null) { 292 | if (faces.isEmpty) { 293 | _resetSteps(); 294 | if (mounted) setState(() => _faceDetectedState = false); 295 | } else { 296 | if (mounted) setState(() => _faceDetectedState = true); 297 | final currentIndex = _stepsKey.currentState?.currentIndex ?? 0; 298 | if (widget.config.useCustomizedLabel) { 299 | if (currentIndex < 300 | customizedLivenessLabel(widget.config.customizedLabel!).length) { 301 | _detectFace( 302 | face: faces.first, 303 | step: customizedLivenessLabel( 304 | widget.config.customizedLabel!)[currentIndex] 305 | .step, 306 | ); 307 | } 308 | } else { 309 | if (currentIndex < stepLiveness.length) { 310 | _detectFace( 311 | face: faces.first, 312 | step: stepLiveness[currentIndex].step, 313 | ); 314 | } 315 | } 316 | } 317 | } else { 318 | _resetSteps(); 319 | } 320 | 321 | _isBusy = false; 322 | if (mounted) setState(() {}); 323 | } 324 | 325 | void _detectFace({ 326 | required Face face, 327 | required LivenessDetectionStep step, 328 | }) async { 329 | if (_isProcessingStep) return; 330 | 331 | debugPrint('Current Step: $step'); 332 | 333 | switch (step) { 334 | case LivenessDetectionStep.blink: 335 | await _handlingBlinkStep(face: face, step: step); 336 | break; 337 | 338 | case LivenessDetectionStep.lookRight: 339 | await _handlingTurnRight(face: face, step: step); 340 | break; 341 | 342 | case LivenessDetectionStep.lookLeft: 343 | await _handlingTurnLeft(face: face, step: step); 344 | break; 345 | 346 | case LivenessDetectionStep.lookUp: 347 | await _handlingLookUp(face: face, step: step); 348 | break; 349 | 350 | case LivenessDetectionStep.lookDown: 351 | await _handlingLookDown(face: face, step: step); 352 | break; 353 | 354 | case LivenessDetectionStep.smile: 355 | await _handlingSmile(face: face, step: step); 356 | break; 357 | } 358 | } 359 | 360 | Future _completeStep({required LivenessDetectionStep step}) async { 361 | if (mounted) setState(() {}); 362 | await _stepsKey.currentState?.nextPage(); 363 | _stopProcessing(); 364 | } 365 | 366 | void _takePicture() async { 367 | try { 368 | if (_cameraController == null || _isTakingPicture) return; 369 | 370 | if (mounted) setState(() => _isTakingPicture = true); 371 | await _cameraController?.stopImageStream(); 372 | 373 | final XFile? clickedImage = await _cameraController?.takePicture(); 374 | if (clickedImage == null) { 375 | _startLiveFeed(); 376 | return; 377 | } 378 | debugPrint('Image path: ${clickedImage.path}'); 379 | _onDetectionCompleted(imgToReturn: clickedImage); 380 | } catch (e) { 381 | _startLiveFeed(); 382 | } 383 | } 384 | 385 | void _onDetectionCompleted({XFile? imgToReturn}) { 386 | final String? imgPath = imgToReturn?.path; 387 | if (widget.isEnableSnackBar) { 388 | final snackBar = SnackBar( 389 | content: Text(imgToReturn == null 390 | ? 'Verification of liveness detection failed, please try again. (Exceeds time limit ${widget.config.durationLivenessVerify ?? 45} second.)' 391 | : 'Verification of liveness detection success!'), 392 | ); 393 | ScaffoldMessenger.of(context).showSnackBar(snackBar); 394 | } 395 | Navigator.of(context).pop(imgPath); 396 | } 397 | 398 | void _resetSteps() { 399 | if (widget.config.useCustomizedLabel) { 400 | for (var step 401 | in customizedLivenessLabel(widget.config.customizedLabel!)) { 402 | final index = customizedLivenessLabel(widget.config.customizedLabel!) 403 | .indexWhere((p1) => p1.step == step.step); 404 | customizedLivenessLabel(widget.config.customizedLabel!)[index] = 405 | customizedLivenessLabel(widget.config.customizedLabel!)[index] 406 | .copyWith(); 407 | } 408 | if (_stepsKey.currentState?.currentIndex != 0) { 409 | _stepsKey.currentState?.reset(); 410 | } 411 | if (mounted) setState(() {}); 412 | } else { 413 | for (var step in stepLiveness) { 414 | final index = stepLiveness.indexWhere((p1) => p1.step == step.step); 415 | stepLiveness[index] = stepLiveness[index].copyWith(); 416 | } 417 | if (_stepsKey.currentState?.currentIndex != 0) { 418 | _stepsKey.currentState?.reset(); 419 | } 420 | if (mounted) setState(() {}); 421 | } 422 | } 423 | 424 | void _startProcessing() { 425 | if (!mounted) return; 426 | if (mounted) setState(() => _isProcessingStep = true); 427 | } 428 | 429 | void _stopProcessing() { 430 | if (!mounted) return; 431 | if (mounted) setState(() => _isProcessingStep = false); 432 | } 433 | 434 | @override 435 | Widget build(BuildContext context) { 436 | return Scaffold( 437 | backgroundColor: widget.isDarkMode ? Colors.black : Colors.white, 438 | body: _buildBody(), 439 | ); 440 | } 441 | 442 | Widget _buildBody() { 443 | return Stack( 444 | children: [ 445 | _isInfoStepCompleted 446 | ? _buildDetectionBody() 447 | : LivenessDetectionTutorialScreen( 448 | duration: widget.config.durationLivenessVerify ?? 45, 449 | isDarkMode: widget.isDarkMode, 450 | onStartTap: () { 451 | if (mounted) setState(() => _isInfoStepCompleted = true); 452 | _startLiveFeed(); 453 | }, 454 | ), 455 | ], 456 | ); 457 | } 458 | 459 | Widget _buildDetectionBody() { 460 | if (_cameraController == null || 461 | _cameraController?.value.isInitialized == false) { 462 | return const Center(child: CircularProgressIndicator.adaptive()); 463 | } 464 | 465 | final size = MediaQuery.of(context).size; 466 | var scale = size.aspectRatio * _cameraController!.value.aspectRatio; 467 | if (scale < 1) scale = 1 / scale; 468 | 469 | return Stack( 470 | children: [ 471 | Container( 472 | height: MediaQuery.of(context).size.height, 473 | width: MediaQuery.of(context).size.width, 474 | color: widget.isDarkMode ? Colors.black : Colors.white, 475 | ), 476 | LivenessDetectionStepOverlayWidget( 477 | duration: widget.config.durationLivenessVerify, 478 | showDurationUiText: widget.config.showDurationUiText, 479 | isDarkMode: widget.isDarkMode, 480 | isFaceDetected: _faceDetectedState, 481 | camera: CameraPreview(_cameraController!), 482 | key: _stepsKey, 483 | steps: widget.config.useCustomizedLabel 484 | ? customizedLivenessLabel(widget.config.customizedLabel!) 485 | : stepLiveness, 486 | showCurrentStep: widget.showCurrentStep, 487 | onCompleted: () => Future.delayed( 488 | const Duration(milliseconds: 500), 489 | () => _takePicture(), 490 | ), 491 | ), 492 | ], 493 | ); 494 | } 495 | 496 | Future _handlingBlinkStep({ 497 | required Face face, 498 | required LivenessDetectionStep step, 499 | }) async { 500 | final blinkThreshold = FlutterLivenessDetectionRandomizedPlugin 501 | .instance.thresholdConfig 502 | .firstWhereOrNull((p0) => p0 is LivenessThresholdBlink) 503 | as LivenessThresholdBlink?; 504 | 505 | if ((face.leftEyeOpenProbability ?? 1.0) < 506 | (blinkThreshold?.leftEyeProbability ?? 0.25) && 507 | (face.rightEyeOpenProbability ?? 1.0) < 508 | (blinkThreshold?.rightEyeProbability ?? 0.25)) { 509 | _startProcessing(); 510 | await _completeStep(step: step); 511 | } 512 | } 513 | 514 | Future _handlingTurnRight({ 515 | required Face face, 516 | required LivenessDetectionStep step, 517 | }) async { 518 | if (Platform.isAndroid) { 519 | final headTurnThreshold = FlutterLivenessDetectionRandomizedPlugin 520 | .instance.thresholdConfig 521 | .firstWhereOrNull((p0) => p0 is LivenessThresholdHead) 522 | as LivenessThresholdHead?; 523 | if ((face.headEulerAngleY ?? 0) < 524 | (headTurnThreshold?.rotationAngle ?? -30)) { 525 | _startProcessing(); 526 | await _completeStep(step: step); 527 | } 528 | } else if (Platform.isIOS) { 529 | final headTurnThreshold = FlutterLivenessDetectionRandomizedPlugin 530 | .instance.thresholdConfig 531 | .firstWhereOrNull((p0) => p0 is LivenessThresholdHead) 532 | as LivenessThresholdHead?; 533 | if ((face.headEulerAngleY ?? 0) > 534 | (headTurnThreshold?.rotationAngle ?? 30)) { 535 | _startProcessing(); 536 | await _completeStep(step: step); 537 | } 538 | } 539 | } 540 | 541 | Future _handlingTurnLeft({ 542 | required Face face, 543 | required LivenessDetectionStep step, 544 | }) async { 545 | if (Platform.isAndroid) { 546 | final headTurnThreshold = FlutterLivenessDetectionRandomizedPlugin 547 | .instance.thresholdConfig 548 | .firstWhereOrNull((p0) => p0 is LivenessThresholdHead) 549 | as LivenessThresholdHead?; 550 | if ((face.headEulerAngleY ?? 0) > 551 | (headTurnThreshold?.rotationAngle ?? 30)) { 552 | _startProcessing(); 553 | await _completeStep(step: step); 554 | } 555 | } else if (Platform.isIOS) { 556 | final headTurnThreshold = FlutterLivenessDetectionRandomizedPlugin 557 | .instance.thresholdConfig 558 | .firstWhereOrNull((p0) => p0 is LivenessThresholdHead) 559 | as LivenessThresholdHead?; 560 | if ((face.headEulerAngleY ?? 0) < 561 | (headTurnThreshold?.rotationAngle ?? -30)) { 562 | _startProcessing(); 563 | await _completeStep(step: step); 564 | } 565 | } 566 | } 567 | 568 | Future _handlingLookUp({ 569 | required Face face, 570 | required LivenessDetectionStep step, 571 | }) async { 572 | final headTurnThreshold = FlutterLivenessDetectionRandomizedPlugin 573 | .instance.thresholdConfig 574 | .firstWhereOrNull((p0) => p0 is LivenessThresholdHead) 575 | as LivenessThresholdHead?; 576 | if ((face.headEulerAngleX ?? 0) > 577 | (headTurnThreshold?.rotationAngle ?? 20)) { 578 | _startProcessing(); 579 | await _completeStep(step: step); 580 | } 581 | } 582 | 583 | Future _handlingLookDown({ 584 | required Face face, 585 | required LivenessDetectionStep step, 586 | }) async { 587 | final headTurnThreshold = FlutterLivenessDetectionRandomizedPlugin 588 | .instance.thresholdConfig 589 | .firstWhereOrNull((p0) => p0 is LivenessThresholdHead) 590 | as LivenessThresholdHead?; 591 | if ((face.headEulerAngleX ?? 0) < 592 | (headTurnThreshold?.rotationAngle ?? -15)) { 593 | _startProcessing(); 594 | await _completeStep(step: step); 595 | } 596 | } 597 | 598 | Future _handlingSmile({ 599 | required Face face, 600 | required LivenessDetectionStep step, 601 | }) async { 602 | final smileThreshold = FlutterLivenessDetectionRandomizedPlugin 603 | .instance.thresholdConfig 604 | .firstWhereOrNull((p0) => p0 is LivenessThresholdSmile) 605 | as LivenessThresholdSmile?; 606 | 607 | if ((face.smilingProbability ?? 0) > 608 | (smileThreshold?.probability ?? 0.65)) { 609 | _startProcessing(); 610 | await _completeStep(step: step); 611 | } 612 | } 613 | } 614 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lib/src/presentation/widgets/index.dart: -------------------------------------------------------------------------------- 1 | export 'liveness_detection_tutorial_widget.dart'; 2 | export 'liveness_detection_step_overlay_widget.dart'; -------------------------------------------------------------------------------- /lib/src/presentation/widgets/liveness_detection_step_overlay_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter_liveness_detection_randomized_plugin/index.dart'; 3 | import 'package:flutter_liveness_detection_randomized_plugin/src/presentation/widgets/circular_progress_widget/circular_progress_widget.dart'; 4 | import 'package:lottie/lottie.dart'; 5 | 6 | class LivenessDetectionStepOverlayWidget extends StatefulWidget { 7 | final List steps; 8 | final VoidCallback onCompleted; 9 | final Widget camera; 10 | final bool isFaceDetected; 11 | final bool showCurrentStep; 12 | final bool isDarkMode; 13 | final bool showDurationUiText; 14 | final int? duration; 15 | 16 | const LivenessDetectionStepOverlayWidget( 17 | {super.key, 18 | required this.steps, 19 | required this.onCompleted, 20 | required this.camera, 21 | required this.isFaceDetected, 22 | this.showCurrentStep = false, 23 | this.isDarkMode = true, 24 | this.showDurationUiText = false, 25 | this.duration}); 26 | 27 | @override 28 | State createState() => 29 | LivenessDetectionStepOverlayWidgetState(); 30 | } 31 | 32 | class LivenessDetectionStepOverlayWidgetState 33 | extends State { 34 | int get currentIndex => _currentIndex; 35 | 36 | bool _isLoading = false; 37 | int _currentIndex = 0; 38 | double _currentStepIndicator = 0; 39 | late final PageController _pageController; 40 | late CircularProgressWidget _circularProgressWidget; 41 | 42 | // Add timer and remaining duration variables 43 | Timer? _countdownTimer; 44 | int _remainingDuration = 0; 45 | 46 | static const double _indicatorMaxStep = 100; 47 | static const double _heightLine = 25; 48 | 49 | double _getStepIncrement(int stepLength) { 50 | return 100 / stepLength; 51 | } 52 | 53 | String get stepCounter => "$_currentIndex/${widget.steps.length}"; 54 | 55 | @override 56 | void initState() { 57 | super.initState(); 58 | _initializeControllers(); 59 | _initializeTimer(); 60 | debugPrint('showCurrentStep ${widget.showCurrentStep}'); 61 | } 62 | 63 | void _initializeControllers() { 64 | _pageController = PageController(initialPage: 0); 65 | _circularProgressWidget = _buildCircularIndicator(); 66 | } 67 | 68 | void _initializeTimer() { 69 | if (widget.duration != null && widget.showDurationUiText) { 70 | _remainingDuration = widget.duration!; 71 | _startCountdownTimer(); 72 | } 73 | } 74 | 75 | void _startCountdownTimer() { 76 | _countdownTimer = Timer.periodic(const Duration(seconds: 1), (timer) { 77 | if (_remainingDuration > 0) { 78 | setState(() { 79 | _remainingDuration--; 80 | }); 81 | } else { 82 | _countdownTimer?.cancel(); 83 | } 84 | }); 85 | } 86 | 87 | CircularProgressWidget _buildCircularIndicator() { 88 | return CircularProgressWidget( 89 | unselectedColor: Colors.grey, 90 | selectedColor: Colors.green, 91 | heightLine: _heightLine, 92 | current: _currentStepIndicator, 93 | maxStep: _indicatorMaxStep, 94 | child: widget.camera, 95 | ); 96 | } 97 | 98 | @override 99 | void dispose() { 100 | _pageController.dispose(); 101 | _countdownTimer?.cancel(); 102 | super.dispose(); 103 | } 104 | 105 | Future nextPage() async { 106 | if (_isLoading) return; 107 | 108 | if (_currentIndex + 1 <= widget.steps.length - 1) { 109 | await _handleNextStep(); 110 | } else { 111 | await _handleCompletion(); 112 | } 113 | } 114 | 115 | Future _handleNextStep() async { 116 | _showLoader(); 117 | await Future.delayed(const Duration(milliseconds: 100)); 118 | await _pageController.nextPage( 119 | duration: const Duration(milliseconds: 1), 120 | curve: Curves.easeIn, 121 | ); 122 | await Future.delayed(const Duration(seconds: 1)); 123 | _hideLoader(); 124 | _updateState(); 125 | } 126 | 127 | Future _handleCompletion() async { 128 | _updateState(); 129 | await Future.delayed(const Duration(milliseconds: 500)); 130 | widget.onCompleted(); 131 | } 132 | 133 | void _updateState() { 134 | if (mounted) { 135 | setState(() { 136 | _currentIndex++; 137 | _currentStepIndicator += _getStepIncrement(widget.steps.length); 138 | _circularProgressWidget = _buildCircularIndicator(); 139 | }); 140 | } 141 | } 142 | 143 | void reset() { 144 | _pageController.jumpToPage(0); 145 | if (mounted) { 146 | setState(() { 147 | _currentIndex = 0; 148 | _currentStepIndicator = 0; 149 | _circularProgressWidget = _buildCircularIndicator(); 150 | }); 151 | } 152 | } 153 | 154 | void _showLoader() { 155 | if (mounted) setState(() => _isLoading = true); 156 | } 157 | 158 | void _hideLoader() { 159 | if (mounted) setState(() => _isLoading = false); 160 | } 161 | 162 | @override 163 | Widget build(BuildContext context) { 164 | return SafeArea( 165 | minimum: const EdgeInsets.all(16), 166 | child: Container( 167 | margin: const EdgeInsets.all(12), 168 | height: double.infinity, 169 | width: double.infinity, 170 | color: Colors.transparent, 171 | child: Stack( 172 | children: [ 173 | GestureDetector( 174 | onTap: () => Navigator.pop(context), 175 | child: widget.showCurrentStep 176 | ? Row( 177 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 178 | children: [ 179 | Text( 180 | 'Back', 181 | style: TextStyle( 182 | color: widget.isDarkMode 183 | ? Colors.white 184 | : Colors.black), 185 | ), 186 | Visibility( 187 | replacement: const SizedBox.shrink(), 188 | visible: widget.showDurationUiText, 189 | child: Text( 190 | _getRemainingTimeText(_remainingDuration), 191 | style: TextStyle( 192 | color: widget.isDarkMode 193 | ? Colors.white 194 | : Colors.black, 195 | fontWeight: FontWeight.bold, 196 | ), 197 | ), 198 | ), 199 | Text( 200 | stepCounter, 201 | style: TextStyle( 202 | color: widget.isDarkMode 203 | ? Colors.white 204 | : Colors.black), 205 | ) 206 | ], 207 | ) 208 | : Text('Back', 209 | style: TextStyle( 210 | color: 211 | widget.isDarkMode ? Colors.white : Colors.black)), 212 | ), 213 | _buildBody(), 214 | ], 215 | ), 216 | ), 217 | ); 218 | } 219 | 220 | Widget _buildBody() { 221 | return Column( 222 | mainAxisAlignment: MainAxisAlignment.center, 223 | crossAxisAlignment: CrossAxisAlignment.center, 224 | mainAxisSize: MainAxisSize.max, 225 | children: [ 226 | _buildCircularCamera(), 227 | const SizedBox(height: 16), 228 | _buildFaceDetectionStatus(), 229 | const SizedBox(height: 16), 230 | _buildStepPageView(), 231 | const SizedBox(height: 16), 232 | widget.isDarkMode ? _buildLoaderDarkMode() : _buildLoaderLightMode(), 233 | ], 234 | ); 235 | } 236 | 237 | Widget _buildCircularCamera() { 238 | return SizedBox( 239 | height: 300, 240 | width: 300, 241 | child: ClipRRect( 242 | borderRadius: BorderRadius.circular(1000), 243 | child: _circularProgressWidget, 244 | ), 245 | ); 246 | } 247 | 248 | String _getRemainingTimeText(int duration) { 249 | int minutes = duration ~/ 60; 250 | int seconds = duration % 60; 251 | return "${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}"; 252 | } 253 | 254 | Widget _buildFaceDetectionStatus() { 255 | return Row( 256 | mainAxisAlignment: MainAxisAlignment.center, 257 | children: [ 258 | SizedBox( 259 | child: widget.isDarkMode 260 | ? LottieBuilder.asset( 261 | widget.isFaceDetected 262 | ? 'packages/flutter_liveness_detection_randomized_plugin/src/core/assets/face-detected.json' 263 | : 'packages/flutter_liveness_detection_randomized_plugin/src/core/assets/face-id-anim.json', 264 | height: widget.isFaceDetected ? 32 : 22, 265 | width: widget.isFaceDetected ? 32 : 22, 266 | ) 267 | : ColorFiltered( 268 | colorFilter: ColorFilter.mode( 269 | widget.isFaceDetected ? Colors.green : Colors.black, 270 | BlendMode.modulate), 271 | child: LottieBuilder.asset( 272 | widget.isFaceDetected 273 | ? 'packages/flutter_liveness_detection_randomized_plugin/src/core/assets/face-detected.json' 274 | : 'packages/flutter_liveness_detection_randomized_plugin/src/core/assets/face-id-anim.json', 275 | height: widget.isFaceDetected ? 32 : 22, 276 | width: widget.isFaceDetected ? 32 : 22, 277 | )), 278 | ), 279 | const SizedBox(width: 16), 280 | Text( 281 | widget.isFaceDetected ? 'User Face Found' : 'User Face Not Found...', 282 | style: 283 | TextStyle(color: widget.isDarkMode ? Colors.white : Colors.black), 284 | ), 285 | ], 286 | ); 287 | } 288 | 289 | Widget _buildStepPageView() { 290 | return SizedBox( 291 | height: MediaQuery.of(context).size.height / 10, 292 | width: MediaQuery.of(context).size.width, 293 | child: AbsorbPointer( 294 | absorbing: true, 295 | child: PageView.builder( 296 | controller: _pageController, 297 | itemCount: widget.steps.length, 298 | itemBuilder: _buildStepItem, 299 | ), 300 | ), 301 | ); 302 | } 303 | 304 | Widget _buildStepItem(BuildContext context, int index) { 305 | return Padding( 306 | padding: const EdgeInsets.all(10), 307 | child: Container( 308 | decoration: BoxDecoration( 309 | color: widget.isDarkMode ? Colors.black : Colors.white, 310 | borderRadius: BorderRadius.circular(20), 311 | ), 312 | alignment: Alignment.center, 313 | margin: const EdgeInsets.symmetric(horizontal: 30), 314 | padding: const EdgeInsets.all(10), 315 | child: Text( 316 | widget.steps[index].title, 317 | textAlign: TextAlign.center, 318 | style: TextStyle( 319 | color: widget.isDarkMode ? Colors.white : Colors.black, 320 | fontSize: 24, 321 | fontWeight: FontWeight.w500, 322 | ), 323 | ), 324 | ), 325 | ); 326 | } 327 | 328 | Widget _buildLoaderDarkMode() { 329 | return Center( 330 | child: CupertinoActivityIndicator( 331 | color: !_isLoading ? Colors.transparent : Colors.white, 332 | ), 333 | ); 334 | } 335 | 336 | Widget _buildLoaderLightMode() { 337 | return Center( 338 | child: CupertinoActivityIndicator( 339 | color: _isLoading ? Colors.transparent : Colors.white, 340 | ), 341 | ); 342 | } 343 | } 344 | -------------------------------------------------------------------------------- /lib/src/presentation/widgets/liveness_detection_tutorial_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_liveness_detection_randomized_plugin/index.dart'; 2 | 3 | class LivenessDetectionTutorialScreen extends StatefulWidget { 4 | final VoidCallback onStartTap; 5 | final bool isDarkMode; 6 | final int? duration; 7 | const LivenessDetectionTutorialScreen( 8 | {super.key, 9 | required this.onStartTap, 10 | this.isDarkMode = false, 11 | required this.duration}); 12 | 13 | @override 14 | State createState() => 15 | _LivenessDetectionTutorialScreenState(); 16 | } 17 | 18 | class _LivenessDetectionTutorialScreenState 19 | extends State { 20 | @override 21 | void initState() { 22 | super.initState(); 23 | } 24 | 25 | @override 26 | Widget build(BuildContext context) { 27 | return Scaffold( 28 | backgroundColor: widget.isDarkMode ? Colors.black : Colors.white, 29 | body: SafeArea( 30 | minimum: const EdgeInsets.all(12), 31 | child: Column( 32 | mainAxisAlignment: MainAxisAlignment.center, 33 | crossAxisAlignment: CrossAxisAlignment.center, 34 | children: [ 35 | const Spacer(), 36 | const SizedBox( 37 | height: 16, 38 | ), 39 | Text( 40 | 'Liveness Detection - Tutorial', 41 | style: TextStyle( 42 | fontWeight: FontWeight.bold, 43 | fontSize: 24, 44 | color: widget.isDarkMode ? Colors.white : Colors.black, 45 | ), 46 | ), 47 | const SizedBox( 48 | height: 32, 49 | ), 50 | Container( 51 | width: MediaQuery.of(context).size.width, 52 | padding: const EdgeInsets.symmetric(vertical: 16), 53 | decoration: BoxDecoration( 54 | borderRadius: BorderRadius.circular(8), 55 | color: widget.isDarkMode ? Colors.black87 : Colors.white, 56 | boxShadow: !widget.isDarkMode 57 | ? [ 58 | BoxShadow( 59 | color: Colors.grey.withOpacity(0.2), 60 | spreadRadius: 5, 61 | blurRadius: 7, 62 | offset: const Offset(0, 3), 63 | ), 64 | ] 65 | : null, 66 | ), 67 | child: Column( 68 | children: [ 69 | ListTile( 70 | leading: Text( 71 | '1', 72 | style: TextStyle( 73 | fontSize: 20, 74 | fontWeight: FontWeight.bold, 75 | color: 76 | widget.isDarkMode ? Colors.white : Colors.black), 77 | ), 78 | subtitle: Text( 79 | "Make sure you are in an area that has sufficient lighting and that your ears are not covered by anything", 80 | style: TextStyle( 81 | color: 82 | widget.isDarkMode ? Colors.white : Colors.black), 83 | ), 84 | title: Text( 85 | "Sufficient Lighting", 86 | style: TextStyle( 87 | fontSize: 20, 88 | fontWeight: FontWeight.bold, 89 | color: 90 | widget.isDarkMode ? Colors.white : Colors.black), 91 | ), 92 | ), 93 | ListTile( 94 | leading: Text( 95 | '2', 96 | style: TextStyle( 97 | fontSize: 20, 98 | fontWeight: FontWeight.bold, 99 | color: 100 | widget.isDarkMode ? Colors.white : Colors.black), 101 | ), 102 | subtitle: Text( 103 | "Hold the phone at eye level and look straight at the camera", 104 | style: TextStyle( 105 | color: 106 | widget.isDarkMode ? Colors.white : Colors.black), 107 | ), 108 | title: Text( 109 | "Straight Ahead View", 110 | style: TextStyle( 111 | fontSize: 20, 112 | fontWeight: FontWeight.bold, 113 | color: 114 | widget.isDarkMode ? Colors.white : Colors.black), 115 | ), 116 | ), 117 | ListTile( 118 | leading: Text( 119 | '3', 120 | style: TextStyle( 121 | fontSize: 20, 122 | fontWeight: FontWeight.bold, 123 | color: 124 | widget.isDarkMode ? Colors.white : Colors.black), 125 | ), 126 | subtitle: Text( 127 | "The time limit given for the liveness detection system verification process is ${widget.duration ?? 45} seconds", 128 | style: TextStyle( 129 | color: 130 | widget.isDarkMode ? Colors.white : Colors.black), 131 | ), 132 | title: Text( 133 | "Time Limit Verification", 134 | style: TextStyle( 135 | fontSize: 20, 136 | fontWeight: FontWeight.bold, 137 | color: 138 | widget.isDarkMode ? Colors.white : Colors.black), 139 | ), 140 | ) 141 | ], 142 | ), 143 | ), 144 | const SizedBox( 145 | height: 24, 146 | ), 147 | ElevatedButton.icon( 148 | style: ElevatedButton.styleFrom( 149 | backgroundColor: 150 | widget.isDarkMode ? Colors.black87 : Colors.white, 151 | foregroundColor: 152 | widget.isDarkMode ? Colors.white : Colors.black, 153 | shape: RoundedRectangleBorder( 154 | borderRadius: BorderRadius.circular(8), 155 | ), 156 | ), 157 | icon: const Icon(Icons.camera_alt_outlined), 158 | onPressed: () => widget.onStartTap(), 159 | label: const Text( 160 | "Start the Liveness Detection System", 161 | ), 162 | ), 163 | const SizedBox( 164 | height: 10, 165 | ), 166 | const Spacer(), 167 | const Row( 168 | mainAxisAlignment: MainAxisAlignment.center, 169 | children: [ 170 | Icon( 171 | Icons.info_outline_rounded, 172 | color: Colors.grey, 173 | size: 15, 174 | ), 175 | SizedBox( 176 | width: 10, 177 | ), 178 | Text( 179 | 'Package Version: 1.0.5', 180 | style: TextStyle(color: Colors.grey), 181 | ) 182 | ], 183 | ) 184 | ], 185 | ), 186 | ), 187 | ); 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /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.0.5 4 | homepage: "https://bagussubagja.vercel.app" 5 | repository: "https://github.com/bagussubagja/flutter-liveness-detection-randomized-plugin" 6 | 7 | environment: 8 | sdk: ^3.5.3 9 | flutter: '>=3.3.0' 10 | 11 | dependencies: 12 | flutter: 13 | sdk: flutter 14 | plugin_platform_interface: ^2.0.2 15 | google_mlkit_face_detection: 0.11.0 16 | camera: 0.10.6 17 | equatable: ^2.0.5 18 | image: ^4.0.15 19 | lottie: ^2.4.0 20 | collection: ^1.18.0 21 | screen_brightness: ^2.1.2 22 | 23 | dependency_overrides: 24 | camera_android_camerax: 0.6.10+1 25 | 26 | dev_dependencies: 27 | flutter_test: 28 | sdk: flutter 29 | flutter_lints: ^4.0.0 30 | 31 | flutter: 32 | plugin: 33 | platforms: 34 | android: 35 | package: com.bagussubagja.flutter_liveness_detection_randomized_plugin.flutter_liveness_detection_randomized_plugin 36 | pluginClass: FlutterLivenessDetectionRandomizedPlugin 37 | ios: 38 | pluginClass: FlutterLivenessDetectionRandomizedPlugin 39 | 40 | assets: 41 | - packages/flutter_liveness_detection_randomized_plugin/src/core/assets/face-id-anim.json 42 | - packages/flutter_liveness_detection_randomized_plugin/src/core/assets/face-detected.json -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------