initial commit
@ -0,0 +1,45 @@
|
||||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.build/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
.swiftpm/
|
||||
migrate_working_dir/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
#.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
**/doc/api/
|
||||
**/ios/Flutter/.last_build_id
|
||||
.dart_tool/
|
||||
.flutter-plugins
|
||||
.flutter-plugins-dependencies
|
||||
.pub-cache/
|
||||
.pub/
|
||||
/build/
|
||||
|
||||
# Symbolication related
|
||||
app.*.symbols
|
||||
|
||||
# Obfuscation related
|
||||
app.*.map.json
|
||||
|
||||
# Android Studio will place build artifacts here
|
||||
/android/app/debug
|
||||
/android/app/profile
|
||||
/android/app/release
|
||||
@ -0,0 +1,42 @@
|
||||
# This file tracks properties of this Flutter project.
|
||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||
#
|
||||
# This file should be version controlled and should not be manually edited.
|
||||
|
||||
version:
|
||||
revision: "c519ee916eaeb88923e67befb89c0f1dabfa83e6"
|
||||
channel: "stable"
|
||||
|
||||
project_type: app
|
||||
|
||||
# Tracks metadata for the flutter migrate command
|
||||
migration:
|
||||
platforms:
|
||||
- platform: root
|
||||
create_revision: c519ee916eaeb88923e67befb89c0f1dabfa83e6
|
||||
base_revision: c519ee916eaeb88923e67befb89c0f1dabfa83e6
|
||||
- platform: android
|
||||
create_revision: c519ee916eaeb88923e67befb89c0f1dabfa83e6
|
||||
base_revision: c519ee916eaeb88923e67befb89c0f1dabfa83e6
|
||||
- platform: ios
|
||||
create_revision: c519ee916eaeb88923e67befb89c0f1dabfa83e6
|
||||
base_revision: c519ee916eaeb88923e67befb89c0f1dabfa83e6
|
||||
- platform: linux
|
||||
create_revision: c519ee916eaeb88923e67befb89c0f1dabfa83e6
|
||||
base_revision: c519ee916eaeb88923e67befb89c0f1dabfa83e6
|
||||
- platform: macos
|
||||
create_revision: c519ee916eaeb88923e67befb89c0f1dabfa83e6
|
||||
base_revision: c519ee916eaeb88923e67befb89c0f1dabfa83e6
|
||||
- platform: web
|
||||
create_revision: c519ee916eaeb88923e67befb89c0f1dabfa83e6
|
||||
base_revision: c519ee916eaeb88923e67befb89c0f1dabfa83e6
|
||||
|
||||
# User provided section
|
||||
|
||||
# List of Local paths (relative to this file) that should be
|
||||
# ignored by the migrate tool.
|
||||
#
|
||||
# Files that are not part of the templates will be ignored by default.
|
||||
unmanaged_files:
|
||||
- 'lib/main.dart'
|
||||
- 'ios/Runner.xcodeproj/project.pbxproj'
|
||||
@ -0,0 +1,16 @@
|
||||
# finger_print_verification
|
||||
|
||||
A new Flutter project.
|
||||
|
||||
## Getting Started
|
||||
|
||||
This project is a starting point for a Flutter application.
|
||||
|
||||
A few resources to get you started if this is your first Flutter project:
|
||||
|
||||
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
|
||||
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
|
||||
|
||||
For help getting started with Flutter development, view the
|
||||
[online documentation](https://docs.flutter.dev/), which offers tutorials,
|
||||
samples, guidance on mobile development, and a full API reference.
|
||||
@ -0,0 +1,28 @@
|
||||
# This file configures the analyzer, which statically analyzes Dart code to
|
||||
# check for errors, warnings, and lints.
|
||||
#
|
||||
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
|
||||
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
|
||||
# invoked from the command line by running `flutter analyze`.
|
||||
|
||||
# The following line activates a set of recommended lints for Flutter apps,
|
||||
# packages, and plugins designed to encourage good coding practices.
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
linter:
|
||||
# The lint rules applied to this project can be customized in the
|
||||
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
||||
# included above or to enable additional rules. A list of all available lints
|
||||
# and their documentation is published at https://dart.dev/lints.
|
||||
#
|
||||
# Instead of disabling a lint rule for the entire project in the
|
||||
# section below, it can also be suppressed for a single line of code
|
||||
# or a specific dart file by using the `// ignore: name_of_lint` and
|
||||
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
||||
# producing the lint.
|
||||
rules:
|
||||
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
||||
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
@ -0,0 +1,13 @@
|
||||
gradle-wrapper.jar
|
||||
/.gradle
|
||||
/captures/
|
||||
/gradlew
|
||||
/gradlew.bat
|
||||
/local.properties
|
||||
GeneratedPluginRegistrant.java
|
||||
|
||||
# Remember to never publicly share your keystore.
|
||||
# See https://flutter.dev/to/reference-keystore
|
||||
key.properties
|
||||
**/*.keystore
|
||||
**/*.jks
|
||||
@ -0,0 +1,44 @@
|
||||
plugins {
|
||||
id "com.android.application"
|
||||
id "kotlin-android"
|
||||
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
|
||||
id "dev.flutter.flutter-gradle-plugin"
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.cloudsolutions.finger_print_verification"
|
||||
compileSdk = flutter.compileSdkVersion
|
||||
ndkVersion = flutter.ndkVersion
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId = "com.cloudsolutions.finger_print_verification"
|
||||
// You can update the following values to match your application needs.
|
||||
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
||||
minSdk = flutter.minSdkVersion
|
||||
targetSdk = flutter.targetSdkVersion
|
||||
versionCode = flutter.versionCode
|
||||
versionName = flutter.versionName
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
// TODO: Add your own signing config for the release build.
|
||||
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||
signingConfig = signingConfigs.debug
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flutter {
|
||||
source = "../.."
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- The INTERNET permission is required for development. Specifically,
|
||||
the Flutter tool needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
</manifest>
|
||||
@ -0,0 +1,63 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-permission
|
||||
android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="32" />
|
||||
<uses-permission
|
||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="32"
|
||||
tools:ignore="ScopedStorage" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.hardware.usb.host" />
|
||||
<application
|
||||
android:label="finger_print_verification"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTop"
|
||||
android:taskAffinity=""
|
||||
android:theme="@style/LaunchTheme"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
android:hardwareAccelerated="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||
the Android process has started. This theme is visible to the user
|
||||
while the Flutter UI initializes. After that, this theme continues
|
||||
to determine the Window background behind the Flutter UI. -->
|
||||
<meta-data
|
||||
android:name="io.flutter.embedding.android.NormalTheme"
|
||||
android:resource="@style/NormalTheme"
|
||||
/>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
|
||||
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
|
||||
android:resource="@xml/device_filter" />
|
||||
</activity>
|
||||
<!-- Don't delete the meta-data below.
|
||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||
<meta-data
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
</application>
|
||||
<!-- Required to query activities that can process text, see:
|
||||
https://developer.android.com/training/package-visibility and
|
||||
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
|
||||
|
||||
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
||||
<data android:mimeType="text/plain"/>
|
||||
</intent>
|
||||
</queries>
|
||||
</manifest>
|
||||
@ -0,0 +1,5 @@
|
||||
package com.cloudsolutions.finger_print_verification
|
||||
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
|
||||
class MainActivity: FlutterActivity()
|
||||
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="?android:colorBackground" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
</layer-list>
|
||||
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@android:color/white" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
</layer-list>
|
||||
|
After Width: | Height: | Size: 544 B |
|
After Width: | Height: | Size: 442 B |
|
After Width: | Height: | Size: 721 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
the Flutter engine draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
||||
@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
the Flutter engine draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
||||
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<usb-device vendor-id="6997" product-id="289" />
|
||||
</resources>
|
||||
@ -0,0 +1,7 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- The INTERNET permission is required for development. Specifically,
|
||||
the Flutter tool needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
</manifest>
|
||||
@ -0,0 +1,18 @@
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.buildDir = "../build"
|
||||
subprojects {
|
||||
project.buildDir = "${rootProject.buildDir}/${project.name}"
|
||||
}
|
||||
subprojects {
|
||||
project.evaluationDependsOn(":app")
|
||||
}
|
||||
|
||||
tasks.register("clean", Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
@ -0,0 +1,7 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
@ -0,0 +1,25 @@
|
||||
pluginManagement {
|
||||
def flutterSdkPath = {
|
||||
def properties = new Properties()
|
||||
file("local.properties").withInputStream { properties.load(it) }
|
||||
def flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
||||
return flutterSdkPath
|
||||
}()
|
||||
|
||||
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
|
||||
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||
id "com.android.application" version "8.2.1" apply false
|
||||
id "org.jetbrains.kotlin.android" version "1.8.22" apply false
|
||||
}
|
||||
|
||||
include ":app"
|
||||
@ -0,0 +1,34 @@
|
||||
**/dgph
|
||||
*.mode1v3
|
||||
*.mode2v3
|
||||
*.moved-aside
|
||||
*.pbxuser
|
||||
*.perspectivev3
|
||||
**/*sync/
|
||||
.sconsign.dblite
|
||||
.tags*
|
||||
**/.vagrant/
|
||||
**/DerivedData/
|
||||
Icon?
|
||||
**/Pods/
|
||||
**/.symlinks/
|
||||
profile
|
||||
xcuserdata
|
||||
**/.generated/
|
||||
Flutter/App.framework
|
||||
Flutter/Flutter.framework
|
||||
Flutter/Flutter.podspec
|
||||
Flutter/Generated.xcconfig
|
||||
Flutter/ephemeral/
|
||||
Flutter/app.flx
|
||||
Flutter/app.zip
|
||||
Flutter/flutter_assets/
|
||||
Flutter/flutter_export_environment.sh
|
||||
ServiceDefinitions.json
|
||||
Runner/GeneratedPluginRegistrant.*
|
||||
|
||||
# Exceptions to above rules.
|
||||
!default.mode1v3
|
||||
!default.mode2v3
|
||||
!default.pbxuser
|
||||
!default.perspectivev3
|
||||
@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>App</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>io.flutter.flutter.app</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>App</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>MinimumOSVersion</key>
|
||||
<string>12.0</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@ -0,0 +1,2 @@
|
||||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
|
||||
#include "Generated.xcconfig"
|
||||
@ -0,0 +1 @@
|
||||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
|
||||
@ -0,0 +1,44 @@
|
||||
# Uncomment this line to define a global platform for your project
|
||||
# platform :ios, '12.0'
|
||||
|
||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||
|
||||
project 'Runner', {
|
||||
'Debug' => :debug,
|
||||
'Profile' => :release,
|
||||
'Release' => :release,
|
||||
}
|
||||
|
||||
def flutter_root
|
||||
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
|
||||
unless File.exist?(generated_xcode_build_settings_path)
|
||||
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
|
||||
end
|
||||
|
||||
File.foreach(generated_xcode_build_settings_path) do |line|
|
||||
matches = line.match(/FLUTTER_ROOT\=(.*)/)
|
||||
return matches[1].strip if matches
|
||||
end
|
||||
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
|
||||
end
|
||||
|
||||
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
|
||||
|
||||
flutter_ios_podfile_setup
|
||||
|
||||
target 'Runner' do
|
||||
use_frameworks!
|
||||
use_modular_headers!
|
||||
|
||||
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
|
||||
target 'RunnerTests' do
|
||||
inherit! :search_paths
|
||||
end
|
||||
end
|
||||
|
||||
post_install do |installer|
|
||||
installer.pods_project.targets.each do |target|
|
||||
flutter_additional_ios_build_settings(target)
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,619 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 54;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
||||
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
|
||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
|
||||
remoteInfo = Runner;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
||||
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
||||
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
|
||||
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
|
||||
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
97C146EB1CF9000F007C117D /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
331C8082294A63A400263BE5 /* RunnerTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
331C807B294A618700263BE5 /* RunnerTests.swift */,
|
||||
);
|
||||
path = RunnerTests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9740EEB11CF90186004384FC /* Flutter */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
|
||||
9740EEB21CF90195004384FC /* Debug.xcconfig */,
|
||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
|
||||
9740EEB31CF90195004384FC /* Generated.xcconfig */,
|
||||
);
|
||||
name = Flutter;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
97C146E51CF9000F007C117D = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9740EEB11CF90186004384FC /* Flutter */,
|
||||
97C146F01CF9000F007C117D /* Runner */,
|
||||
97C146EF1CF9000F007C117D /* Products */,
|
||||
331C8082294A63A400263BE5 /* RunnerTests */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
97C146EF1CF9000F007C117D /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
97C146EE1CF9000F007C117D /* Runner.app */,
|
||||
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
97C146F01CF9000F007C117D /* Runner */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
97C146FA1CF9000F007C117D /* Main.storyboard */,
|
||||
97C146FD1CF9000F007C117D /* Assets.xcassets */,
|
||||
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
|
||||
97C147021CF9000F007C117D /* Info.plist */,
|
||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
|
||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
|
||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
|
||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
|
||||
);
|
||||
path = Runner;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
331C8080294A63A400263BE5 /* RunnerTests */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
|
||||
buildPhases = (
|
||||
331C807D294A63A400263BE5 /* Sources */,
|
||||
331C807F294A63A400263BE5 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
331C8086294A63A400263BE5 /* PBXTargetDependency */,
|
||||
);
|
||||
name = RunnerTests;
|
||||
productName = RunnerTests;
|
||||
productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.unit-test";
|
||||
};
|
||||
97C146ED1CF9000F007C117D /* Runner */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||
buildPhases = (
|
||||
9740EEB61CF901F6004384FC /* Run Script */,
|
||||
97C146EA1CF9000F007C117D /* Sources */,
|
||||
97C146EB1CF9000F007C117D /* Frameworks */,
|
||||
97C146EC1CF9000F007C117D /* Resources */,
|
||||
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = Runner;
|
||||
productName = Runner;
|
||||
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
97C146E61CF9000F007C117D /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
BuildIndependentTargetsInParallel = YES;
|
||||
LastUpgradeCheck = 1510;
|
||||
ORGANIZATIONNAME = "";
|
||||
TargetAttributes = {
|
||||
331C8080294A63A400263BE5 = {
|
||||
CreatedOnToolsVersion = 14.0;
|
||||
TestTargetID = 97C146ED1CF9000F007C117D;
|
||||
};
|
||||
97C146ED1CF9000F007C117D = {
|
||||
CreatedOnToolsVersion = 7.3.1;
|
||||
LastSwiftMigration = 1100;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
|
||||
compatibilityVersion = "Xcode 9.3";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = 97C146E51CF9000F007C117D;
|
||||
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
97C146ED1CF9000F007C117D /* Runner */,
|
||||
331C8080294A63A400263BE5 /* RunnerTests */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
331C807F294A63A400263BE5 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
97C146EC1CF9000F007C117D /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
|
||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
|
||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
|
||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
|
||||
);
|
||||
name = "Thin Binary";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
|
||||
};
|
||||
9740EEB61CF901F6004384FC /* Run Script */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Run Script";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
331C807D294A63A400263BE5 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
97C146EA1CF9000F007C117D /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
|
||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 97C146ED1CF9000F007C117D /* Runner */;
|
||||
targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
97C146FB1CF9000F007C117D /* Base */,
|
||||
);
|
||||
name = Main.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
97C147001CF9000F007C117D /* Base */,
|
||||
);
|
||||
name = LaunchScreen.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
249021D3217E4FDB00AE95B9 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = iphoneos;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
249021D4217E4FDB00AE95B9 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = 3A359E86ZF;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.cloudsolutions.fingerPrintVerification;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
331C8088294A63A400263BE5 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.cloudsolutions.fingerPrintVerification.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
331C8089294A63A400263BE5 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.cloudsolutions.fingerPrintVerification.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
331C808A294A63A400263BE5 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.cloudsolutions.fingerPrintVerification.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
97C147031CF9000F007C117D /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
97C147041CF9000F007C117D /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = iphoneos;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
97C147061CF9000F007C117D /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = 3A359E86ZF;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.cloudsolutions.fingerPrintVerification;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
97C147071CF9000F007C117D /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = 3A359E86ZF;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.cloudsolutions.fingerPrintVerification;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
331C8088294A63A400263BE5 /* Debug */,
|
||||
331C8089294A63A400263BE5 /* Release */,
|
||||
331C808A294A63A400263BE5 /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
97C147031CF9000F007C117D /* Debug */,
|
||||
97C147041CF9000F007C117D /* Release */,
|
||||
249021D3217E4FDB00AE95B9 /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
97C147061CF9000F007C117D /* Debug */,
|
||||
97C147071CF9000F007C117D /* Release */,
|
||||
249021D4217E4FDB00AE95B9 /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 97C146E61CF9000F007C117D /* Project object */;
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>PreviewsEnabled</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
@ -0,0 +1,98 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1510"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO"
|
||||
parallelizable = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "331C8080294A63A400263BE5"
|
||||
BuildableName = "RunnerTests.xctest"
|
||||
BlueprintName = "RunnerTests"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Profile"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:Runner.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>PreviewsEnabled</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
@ -0,0 +1,13 @@
|
||||
import Flutter
|
||||
import UIKit
|
||||
|
||||
@main
|
||||
@objc class AppDelegate: FlutterAppDelegate {
|
||||
override func application(
|
||||
_ application: UIApplication,
|
||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||
) -> Bool {
|
||||
GeneratedPluginRegistrant.register(with: self)
|
||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,122 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-20x20@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-20x20@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-29x29@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-29x29@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-29x29@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-40x40@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-40x40@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-60x60@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-60x60@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-20x20@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-20x20@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-29x29@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-29x29@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-40x40@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-40x40@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-76x76@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-76x76@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "83.5x83.5",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-83.5x83.5@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "1024x1024",
|
||||
"idiom" : "ios-marketing",
|
||||
"filename" : "Icon-App-1024x1024@1x.png",
|
||||
"scale" : "1x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 295 B |
|
After Width: | Height: | Size: 406 B |
|
After Width: | Height: | Size: 450 B |
|
After Width: | Height: | Size: 282 B |
|
After Width: | Height: | Size: 462 B |
|
After Width: | Height: | Size: 704 B |
|
After Width: | Height: | Size: 406 B |
|
After Width: | Height: | Size: 586 B |
|
After Width: | Height: | Size: 862 B |
|
After Width: | Height: | Size: 862 B |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 762 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
@ -0,0 +1,23 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "LaunchImage.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "LaunchImage@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "LaunchImage@3x.png",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 68 B |
|
After Width: | Height: | Size: 68 B |
|
After Width: | Height: | Size: 68 B |
@ -0,0 +1,5 @@
|
||||
# Launch Screen Assets
|
||||
|
||||
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
|
||||
|
||||
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.
|
||||
@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="EHf-IW-A2E">
|
||||
<objects>
|
||||
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
|
||||
</imageView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
|
||||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="53" y="375"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="LaunchImage" width="168" height="185"/>
|
||||
</resources>
|
||||
</document>
|
||||
@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Flutter View Controller-->
|
||||
<scene sceneID="tne-QT-ifu">
|
||||
<objects>
|
||||
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Finger Print Verification</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>finger_print_verification</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@ -0,0 +1 @@
|
||||
#import "GeneratedPluginRegistrant.h"
|
||||
@ -0,0 +1,12 @@
|
||||
import Flutter
|
||||
import UIKit
|
||||
import XCTest
|
||||
|
||||
class RunnerTests: XCTestCase {
|
||||
|
||||
func testExample() {
|
||||
// If you add code to the Runner application, consider adding tests here.
|
||||
// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,51 @@
|
||||
/// Sealed response type returned by every [ApiClient] call.
|
||||
///
|
||||
/// Use Dart 3 exhaustive pattern-matching so both outcomes are always handled:
|
||||
/// ```dart
|
||||
/// final result = await ApiClient.get<User>(
|
||||
/// url: '/users/me',
|
||||
/// fromJson: (j) => User.fromJson(j as Map<String, dynamic>),
|
||||
/// );
|
||||
///
|
||||
/// switch (result) {
|
||||
/// case ApiSuccess(:final data):
|
||||
/// print('Got user: ${data.name}');
|
||||
/// case ApiFailure(:final code, :final message):
|
||||
/// print('Error $code: $message');
|
||||
/// }
|
||||
/// ```
|
||||
sealed class ApiResponse<T> {}
|
||||
|
||||
/// Represents a successful API call whose response body was parsed into [data]
|
||||
/// via the caller-supplied `fromJson` function.
|
||||
final class ApiSuccess<T> extends ApiResponse<T> {
|
||||
/// The parsed response payload.
|
||||
final T data;
|
||||
|
||||
ApiSuccess(this.data);
|
||||
}
|
||||
|
||||
/// Represents a failed API call, carrying an optional HTTP status [code] and a
|
||||
/// human-readable [message].
|
||||
///
|
||||
/// Failure sources include:
|
||||
/// * No internet connectivity → `code: null`
|
||||
/// * Connection / receive timeout → `code: null` or server code
|
||||
/// * HTTP error (non-2xx or `isSuccess` predicate returned `false`) → HTTP code
|
||||
/// * Response JSON parsing error → HTTP code
|
||||
/// * Request cancellation via `ApiClient.cancel()` → `code: -1`
|
||||
/// * Client not initialized (`ApiClientNotInitializedException`) → `code: null`
|
||||
/// * Unknown / unexpected error → `code: null`
|
||||
final class ApiFailure<T> extends ApiResponse<T> {
|
||||
/// HTTP status code when available, or `null` when the failure happened
|
||||
/// before / after the network layer (e.g. no connectivity, parsing error).
|
||||
///
|
||||
/// A synthetic value of `-1` is used for cancellations so callers can
|
||||
/// distinguish them from real HTTP errors without string-matching.
|
||||
final int? code;
|
||||
|
||||
/// Human-readable description of the failure suitable for display.
|
||||
final String message;
|
||||
|
||||
ApiFailure({this.code, required this.message});
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
/// HTTP verb enum used internally by [ApiClient._call] to dispatch to the
|
||||
/// correct [Dio] method.
|
||||
///
|
||||
/// The public [ApiClient] API exposes **named methods** (`get`, `post`, …)
|
||||
/// rather than accepting an [HttpMethod] directly. The enum is kept as a
|
||||
/// clearly-typed pivot inside [ApiClient._call] so all request logic lives in
|
||||
/// one place with zero duplication.
|
||||
enum HttpMethod {
|
||||
get,
|
||||
post,
|
||||
put,
|
||||
patch,
|
||||
delete,
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
/// Core networking layer.
|
||||
///
|
||||
/// Import this single file to access [ApiClient], [ApiResponse],
|
||||
/// [ApiSuccess], [ApiFailure], [TokenManager], and
|
||||
/// [ApiClientNotInitializedException].
|
||||
///
|
||||
/// ```dart
|
||||
/// import 'package:finger_print_verification/core/network/network.dart';
|
||||
/// ```
|
||||
library;
|
||||
export 'api_client.dart';
|
||||
export 'api_response.dart';
|
||||
export 'http_method.dart';
|
||||
export 'token_manager.dart';
|
||||
@ -0,0 +1,41 @@
|
||||
/// Lightweight **in-memory** authentication token holder.
|
||||
///
|
||||
/// The token is **never persisted to disk** — it lives only for the duration
|
||||
/// of the current process. Use [setToken] after a successful login response
|
||||
/// and [clearToken] on log-out.
|
||||
///
|
||||
/// [ApiClient] reads from here automatically whenever `shouldKeepToken: true`
|
||||
/// is passed to any call method, and updates the stored token when the server
|
||||
/// returns a new `Authorization` response header.
|
||||
///
|
||||
/// Example:
|
||||
/// ```dart
|
||||
/// // After a login call:
|
||||
/// TokenManager.setToken(loginResponse.accessToken);
|
||||
///
|
||||
/// // Any subsequent call with shouldKeepToken: true will attach:
|
||||
/// // Authorization: Bearer <token>
|
||||
///
|
||||
/// // On log-out:
|
||||
/// TokenManager.clearToken();
|
||||
/// ```
|
||||
final class TokenManager {
|
||||
// Pure-static class — disallow instantiation.
|
||||
TokenManager._();
|
||||
|
||||
static String? _token;
|
||||
|
||||
/// Store an authentication token in memory.
|
||||
///
|
||||
/// Pass the **raw token value** (without a `Bearer ` prefix).
|
||||
static void setToken(String token) => _token = token;
|
||||
|
||||
/// Retrieve the currently stored token, or `null` if none has been set yet.
|
||||
static String? getToken() => _token;
|
||||
|
||||
/// Wipe the stored token (e.g. on log-out or session expiry).
|
||||
static void clearToken() => _token = null;
|
||||
|
||||
/// `true` when a non-empty token is currently held in memory.
|
||||
static bool get hasToken => _token != null && _token!.isNotEmpty;
|
||||
}
|
||||
@ -0,0 +1,82 @@
|
||||
part of 'fingerprint_bloc.dart';
|
||||
|
||||
abstract class FingerprintEvent extends Equatable {
|
||||
const FingerprintEvent();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
/// Dispatched on app startup to trigger the background login / token-fetch.
|
||||
class AppStarted extends FingerprintEvent {
|
||||
const AppStarted();
|
||||
}
|
||||
|
||||
/// Dropdown: document type changed
|
||||
class DocumentTypeChanged extends FingerprintEvent {
|
||||
final DocumentType documentType;
|
||||
const DocumentTypeChanged(this.documentType);
|
||||
@override
|
||||
List<Object?> get props => [documentType];
|
||||
}
|
||||
|
||||
/// Dropdown: finger selection changed.
|
||||
/// [fingerIndex] is the numeric key from [fingerIndexMap].
|
||||
class FingerTypeChanged extends FingerprintEvent {
|
||||
final int fingerIndex;
|
||||
const FingerTypeChanged(this.fingerIndex);
|
||||
@override
|
||||
List<Object?> get props => [fingerIndex];
|
||||
}
|
||||
|
||||
/// Text field: national ID changed
|
||||
class NationalIdChanged extends FingerprintEvent {
|
||||
final String value;
|
||||
const NationalIdChanged(this.value);
|
||||
@override
|
||||
List<Object?> get props => [value];
|
||||
}
|
||||
|
||||
/// Text field: nationality changed (passport mode)
|
||||
class NationalityChanged extends FingerprintEvent {
|
||||
final String value;
|
||||
const NationalityChanged(this.value);
|
||||
@override
|
||||
List<Object?> get props => [value];
|
||||
}
|
||||
|
||||
/// Text field: passport number changed
|
||||
class PassportNumberChanged extends FingerprintEvent {
|
||||
final String value;
|
||||
const PassportNumberChanged(this.value);
|
||||
@override
|
||||
List<Object?> get props => [value];
|
||||
}
|
||||
|
||||
/// Connect to the device
|
||||
class ConnectDevice extends FingerprintEvent {
|
||||
const ConnectDevice();
|
||||
}
|
||||
|
||||
/// Trigger a fingerprint scan
|
||||
class ScanFingerprintEvent extends FingerprintEvent {
|
||||
const ScanFingerprintEvent();
|
||||
}
|
||||
|
||||
/// Reset form to initial state
|
||||
class ResetBlocEvent extends FingerprintEvent {
|
||||
const ResetBlocEvent();
|
||||
}
|
||||
|
||||
/// Send captured fingerprint data
|
||||
class SendFingerprintEvent extends FingerprintEvent {
|
||||
const SendFingerprintEvent();
|
||||
}
|
||||
|
||||
/// Verify the captured fingerprint against stored records
|
||||
class VerifyFingerprintEvent extends FingerprintEvent {
|
||||
const VerifyFingerprintEvent();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -0,0 +1,133 @@
|
||||
part of 'fingerprint_bloc.dart';
|
||||
|
||||
enum DocumentType { nationalId, passport }
|
||||
|
||||
enum FormStatus {
|
||||
idle,
|
||||
loggingIn,
|
||||
connecting,
|
||||
scanning,
|
||||
scanned,
|
||||
verifying,
|
||||
verified,
|
||||
error,
|
||||
}
|
||||
|
||||
class FingerprintFormState extends Equatable {
|
||||
final DocumentType documentType;
|
||||
|
||||
/// The selected finger's numeric key from [fingerIndexMap].
|
||||
/// Defaults to 1 (Right Thumb).
|
||||
final int selectedFingerIndex;
|
||||
|
||||
final String nationalIdInput;
|
||||
final String nationality;
|
||||
final String passportNumber;
|
||||
final FormStatus status;
|
||||
final String? base64Data;
|
||||
final String? errorMessage;
|
||||
|
||||
/// Live device status text shown during the connect retry loop.
|
||||
final String? deviceStatusMessage;
|
||||
|
||||
/// True once the scanner has successfully connected.
|
||||
final bool isDeviceConnected;
|
||||
|
||||
/// True when the Scan button triggered the connection phase
|
||||
/// (so the scan button shows "Connecting…" instead of the connect button).
|
||||
final bool isScanConnecting;
|
||||
final String? nationalIdError;
|
||||
final String? nationalityError;
|
||||
final String? passportNumberError;
|
||||
|
||||
/// Populated after a successful verification API call.
|
||||
final AuthResultUiModel? authResult;
|
||||
|
||||
const FingerprintFormState({
|
||||
this.documentType = DocumentType.nationalId,
|
||||
this.selectedFingerIndex = 1,
|
||||
this.nationalIdInput = '',
|
||||
this.nationality = '',
|
||||
this.passportNumber = '',
|
||||
this.status = FormStatus.idle,
|
||||
this.base64Data,
|
||||
this.errorMessage,
|
||||
this.deviceStatusMessage,
|
||||
this.isDeviceConnected = false,
|
||||
this.isScanConnecting = false,
|
||||
this.nationalIdError,
|
||||
this.nationalityError,
|
||||
this.passportNumberError,
|
||||
this.authResult,
|
||||
});
|
||||
|
||||
FingerprintFormState copyWith({
|
||||
DocumentType? documentType,
|
||||
int? selectedFingerIndex,
|
||||
String? nationalIdInput,
|
||||
String? nationality,
|
||||
String? passportNumber,
|
||||
FormStatus? status,
|
||||
String? base64Data,
|
||||
String? errorMessage,
|
||||
String? deviceStatusMessage,
|
||||
bool? isDeviceConnected,
|
||||
bool? isScanConnecting,
|
||||
String? nationalIdError,
|
||||
String? nationalityError,
|
||||
String? passportNumberError,
|
||||
AuthResultUiModel? authResult,
|
||||
bool clearBase64 = false,
|
||||
bool clearError = false,
|
||||
bool clearDeviceStatus = false,
|
||||
bool clearNationalIdError = false,
|
||||
bool clearNationalityError = false,
|
||||
bool clearPassportNumberError = false,
|
||||
bool clearAuthResult = false,
|
||||
}) {
|
||||
return FingerprintFormState(
|
||||
documentType: documentType ?? this.documentType,
|
||||
selectedFingerIndex: selectedFingerIndex ?? this.selectedFingerIndex,
|
||||
nationalIdInput: nationalIdInput ?? this.nationalIdInput,
|
||||
nationality: nationality ?? this.nationality,
|
||||
passportNumber: passportNumber ?? this.passportNumber,
|
||||
status: status ?? this.status,
|
||||
base64Data: clearBase64 ? null : (base64Data ?? this.base64Data),
|
||||
errorMessage: clearError ? null : (errorMessage ?? this.errorMessage),
|
||||
deviceStatusMessage: clearDeviceStatus
|
||||
? null
|
||||
: (deviceStatusMessage ?? this.deviceStatusMessage),
|
||||
isDeviceConnected: isDeviceConnected ?? this.isDeviceConnected,
|
||||
isScanConnecting: isScanConnecting ?? this.isScanConnecting,
|
||||
nationalIdError:
|
||||
clearNationalIdError ? null : (nationalIdError ?? this.nationalIdError),
|
||||
nationalityError:
|
||||
clearNationalityError ? null : (nationalityError ?? this.nationalityError),
|
||||
passportNumberError: clearPassportNumberError
|
||||
? null
|
||||
: (passportNumberError ?? this.passportNumberError),
|
||||
authResult: clearAuthResult ? null : (authResult ?? this.authResult),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
documentType,
|
||||
selectedFingerIndex,
|
||||
nationalIdInput,
|
||||
nationality,
|
||||
passportNumber,
|
||||
status,
|
||||
base64Data,
|
||||
errorMessage,
|
||||
deviceStatusMessage,
|
||||
isDeviceConnected,
|
||||
isScanConnecting,
|
||||
nationalIdError,
|
||||
nationalityError,
|
||||
passportNumberError,
|
||||
authResult,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,139 @@
|
||||
/// Raw API response model — mirrors the JSON returned by the verification endpoint.
|
||||
///
|
||||
/// Example JSON:
|
||||
/// ```json
|
||||
/// {
|
||||
/// "success": true,
|
||||
/// "statusMessage": "Authentication successful!",
|
||||
/// "personInfo": {
|
||||
/// "firstName": "ASIF",
|
||||
/// "lastName": "EHTESHAM UL HAQ",
|
||||
/// "fatherName": "RAZA EHTESHAM",
|
||||
/// "grandfatherName": "MUHAMMAD",
|
||||
/// "occupation": "Computer Science Specialist",
|
||||
/// "gender": "1",
|
||||
/// "sponsor": "",
|
||||
/// "idNumber": "2257773628",
|
||||
/// "birthDate": "1985-01-04",
|
||||
/// "nationality": "304"
|
||||
/// },
|
||||
/// "error": {
|
||||
/// "type": "",
|
||||
/// "message": ""
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
class AuthResponseModel {
|
||||
final bool success;
|
||||
final String statusMessage;
|
||||
final PersonInfoResponse? personInfo;
|
||||
final ErrorResponse? error;
|
||||
|
||||
const AuthResponseModel({
|
||||
required this.success,
|
||||
required this.statusMessage,
|
||||
this.personInfo,
|
||||
this.error,
|
||||
});
|
||||
|
||||
factory AuthResponseModel.fromJson(Map<String, dynamic> json) {
|
||||
return AuthResponseModel(
|
||||
success: json['success'] as bool? ?? false,
|
||||
statusMessage: json['statusMessage'] as String? ?? '',
|
||||
personInfo: json['personInfo'] != null
|
||||
? PersonInfoResponse.fromJson(
|
||||
json['personInfo'] as Map<String, dynamic>)
|
||||
: null,
|
||||
error: json['error'] != null
|
||||
? ErrorResponse.fromJson(json['error'] as Map<String, dynamic>)
|
||||
: null,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'success': success,
|
||||
'statusMessage': statusMessage,
|
||||
'personInfo': personInfo?.toJson(),
|
||||
'error': error?.toJson(),
|
||||
};
|
||||
}
|
||||
|
||||
/// Sub-model for the person information section of the response.
|
||||
class PersonInfoResponse {
|
||||
final String firstName;
|
||||
final String lastName;
|
||||
final String fatherName;
|
||||
final String grandfatherName;
|
||||
final String occupation;
|
||||
final String gender;
|
||||
final String sponsor;
|
||||
final String idNumber;
|
||||
final String birthDate;
|
||||
final String nationality;
|
||||
|
||||
const PersonInfoResponse({
|
||||
required this.firstName,
|
||||
required this.lastName,
|
||||
required this.fatherName,
|
||||
required this.grandfatherName,
|
||||
required this.occupation,
|
||||
required this.gender,
|
||||
required this.sponsor,
|
||||
required this.idNumber,
|
||||
required this.birthDate,
|
||||
required this.nationality,
|
||||
});
|
||||
|
||||
factory PersonInfoResponse.fromJson(Map<String, dynamic> json) {
|
||||
return PersonInfoResponse(
|
||||
firstName: json['firstName'] as String? ?? '',
|
||||
lastName: json['lastName'] as String? ?? '',
|
||||
fatherName: json['fatherName'] as String? ?? '',
|
||||
grandfatherName: json['grandfatherName'] as String? ?? '',
|
||||
occupation: json['occupation'] as String? ?? '',
|
||||
gender: json['gender'] as String? ?? '',
|
||||
sponsor: json['sponsor'] as String? ?? '',
|
||||
idNumber: json['idNumber'] as String? ?? '',
|
||||
birthDate: json['birthDate'] as String? ?? '',
|
||||
nationality: json['nationality'] as String? ?? '',
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'firstName': firstName,
|
||||
'lastName': lastName,
|
||||
'fatherName': fatherName,
|
||||
'grandfatherName': grandfatherName,
|
||||
'occupation': occupation,
|
||||
'gender': gender,
|
||||
'sponsor': sponsor,
|
||||
'idNumber': idNumber,
|
||||
'birthDate': birthDate,
|
||||
'nationality': nationality,
|
||||
};
|
||||
}
|
||||
|
||||
/// Sub-model for the error section of the response.
|
||||
class ErrorResponse {
|
||||
final String type;
|
||||
final String message;
|
||||
|
||||
const ErrorResponse({
|
||||
required this.type,
|
||||
required this.message,
|
||||
});
|
||||
|
||||
factory ErrorResponse.fromJson(Map<String, dynamic> json) {
|
||||
return ErrorResponse(
|
||||
type: json['type'] as String? ?? '',
|
||||
message: json['message'] as String? ?? '',
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'type': type,
|
||||
'message': message,
|
||||
};
|
||||
|
||||
bool get hasError => type.isNotEmpty || message.isNotEmpty;
|
||||
}
|
||||
@ -0,0 +1,147 @@
|
||||
import 'auth_response_model.dart';
|
||||
import 'fingerprint_verify_response_model.dart';
|
||||
|
||||
/// Presentation-layer model derived from [AuthResponseModel].
|
||||
///
|
||||
/// All raw API fields are mapped and sanitised here so the UI layer
|
||||
/// never has to inspect raw JSON.
|
||||
class AuthResultUiModel {
|
||||
final bool isSuccess;
|
||||
final String statusMessage;
|
||||
|
||||
// ── Person Information ─────────────────────────────────────────────────────
|
||||
final String firstName;
|
||||
final String lastName;
|
||||
final String fatherName;
|
||||
final String grandfatherName;
|
||||
final String occupation;
|
||||
final String gender;
|
||||
final String sponsor;
|
||||
final String idNumber;
|
||||
final String birthDate;
|
||||
final String nationality;
|
||||
|
||||
// ── Error ──────────────────────────────────────────────────────────────────
|
||||
/// null when there is no error.
|
||||
final String? errorType;
|
||||
|
||||
/// null when there is no error.
|
||||
final String? errorMessage;
|
||||
|
||||
const AuthResultUiModel({
|
||||
required this.isSuccess,
|
||||
required this.statusMessage,
|
||||
required this.firstName,
|
||||
required this.lastName,
|
||||
required this.fatherName,
|
||||
required this.grandfatherName,
|
||||
required this.occupation,
|
||||
required this.gender,
|
||||
required this.sponsor,
|
||||
required this.idNumber,
|
||||
required this.birthDate,
|
||||
required this.nationality,
|
||||
this.errorType,
|
||||
this.errorMessage,
|
||||
});
|
||||
|
||||
/// Convert a raw [AuthResponseModel] (legacy/mock API response) into a UI model.
|
||||
factory AuthResultUiModel.fromResponse(AuthResponseModel response) {
|
||||
final info = response.personInfo;
|
||||
final err = response.error;
|
||||
|
||||
return AuthResultUiModel(
|
||||
isSuccess: response.success,
|
||||
statusMessage: response.statusMessage,
|
||||
firstName: info?.firstName ?? '',
|
||||
lastName: info?.lastName ?? '',
|
||||
fatherName: info?.fatherName ?? '',
|
||||
grandfatherName: info?.grandfatherName ?? '',
|
||||
occupation: info?.occupation ?? '',
|
||||
gender: info?.gender ?? '',
|
||||
sponsor: info?.sponsor ?? '',
|
||||
idNumber: info?.idNumber ?? '',
|
||||
birthDate: info?.birthDate ?? '',
|
||||
nationality: info?.nationality ?? '',
|
||||
errorType:
|
||||
(err?.type.isNotEmpty ?? false) ? err!.type : null,
|
||||
errorMessage:
|
||||
(err?.message.isNotEmpty ?? false) ? err!.message : null,
|
||||
);
|
||||
}
|
||||
|
||||
/// Convert a [FingerprintVerifyResponseModel] (real API response for both
|
||||
/// the National-ID and Passport authentication endpoints) into a UI model.
|
||||
factory AuthResultUiModel.fromFingerprintVerifyResponse(
|
||||
FingerprintVerifyResponseModel response) {
|
||||
final data = response.data;
|
||||
final info = data?.personInfo;
|
||||
|
||||
// Derive gender label from sex code
|
||||
String genderLabel = '';
|
||||
if (info?.sexCode == '1' || info?.sexCode.toLowerCase() == 'm') {
|
||||
genderLabel = 'Male';
|
||||
} else if (info?.sexCode == '2' || info?.sexCode.toLowerCase() == 'f') {
|
||||
genderLabel = 'Female';
|
||||
} else if (info?.sexCode.isNotEmpty ?? false) {
|
||||
genderLabel = info!.sexCode;
|
||||
}
|
||||
|
||||
// Use English occupation if available, otherwise Arabic
|
||||
final occupationLabel = (info?.occupationDescEn.isNotEmpty ?? false)
|
||||
? info!.occupationDescEn
|
||||
: (info?.occupationDescAr ?? '');
|
||||
|
||||
// Combine first name + family name for "last name" field in the UI
|
||||
final lastName = (info?.familyName.isNotEmpty ?? false)
|
||||
? info!.familyName
|
||||
: '';
|
||||
|
||||
// Build error message from data fields when isError is true
|
||||
String? errorType;
|
||||
String? errorMessage;
|
||||
if (data?.isError == true) {
|
||||
errorType = data?.errorType.isNotEmpty == true ? data!.errorType : null;
|
||||
errorMessage =
|
||||
data?.errorMessage.isNotEmpty == true ? data!.errorMessage : null;
|
||||
}
|
||||
|
||||
return AuthResultUiModel(
|
||||
isSuccess: response.success && (data?.matchFlag ?? false),
|
||||
statusMessage: response.message.isNotEmpty
|
||||
? response.message
|
||||
: (data?.matchFlag == true
|
||||
? 'Authentication successful!'
|
||||
: 'Authentication failed'),
|
||||
firstName: info?.firstName ?? '',
|
||||
lastName: lastName,
|
||||
fatherName: info?.fatherName ?? '',
|
||||
grandfatherName: info?.grandFatherName ?? '',
|
||||
occupation: occupationLabel,
|
||||
gender: genderLabel,
|
||||
sponsor: info?.sponsorName ?? '',
|
||||
idNumber: info?.id ?? '',
|
||||
birthDate: info?.birthDateG ?? '',
|
||||
nationality: info?.nationalityCode ?? '',
|
||||
errorType: errorType,
|
||||
errorMessage: errorMessage,
|
||||
);
|
||||
}
|
||||
|
||||
/// Convenience getter — first and last name joined on two visual lines.
|
||||
String get fullNameDisplay =>
|
||||
[firstName, lastName].where((s) => s.isNotEmpty).join('\n');
|
||||
|
||||
bool get hasPersonInfo =>
|
||||
firstName.isNotEmpty ||
|
||||
lastName.isNotEmpty ||
|
||||
fatherName.isNotEmpty ||
|
||||
grandfatherName.isNotEmpty ||
|
||||
occupation.isNotEmpty ||
|
||||
gender.isNotEmpty ||
|
||||
idNumber.isNotEmpty ||
|
||||
birthDate.isNotEmpty ||
|
||||
nationality.isNotEmpty;
|
||||
|
||||
bool get hasError => errorType != null || errorMessage != null;
|
||||
}
|
||||
@ -0,0 +1,143 @@
|
||||
/// Shared response model for both fingerprint authentication endpoints:
|
||||
/// - [POST api/Fingerprint/authenticate] (National ID flow)
|
||||
/// - [POST api/Fingerprint/authenticate-passport] (Passport flow)
|
||||
///
|
||||
/// Both APIs return the same JSON shape.
|
||||
class FingerprintVerifyResponseModel {
|
||||
final bool success;
|
||||
final String message;
|
||||
final FingerprintVerifyData? data;
|
||||
final List<String> errors;
|
||||
|
||||
const FingerprintVerifyResponseModel({
|
||||
required this.success,
|
||||
required this.message,
|
||||
this.data,
|
||||
this.errors = const [],
|
||||
});
|
||||
|
||||
factory FingerprintVerifyResponseModel.fromJson(Map<String, dynamic> json) {
|
||||
return FingerprintVerifyResponseModel(
|
||||
success: json['success'] as bool? ?? false,
|
||||
message: json['message'] as String? ?? '',
|
||||
data: json['data'] != null
|
||||
? FingerprintVerifyData.fromJson(
|
||||
json['data'] as Map<String, dynamic>)
|
||||
: null,
|
||||
errors: (json['errors'] as List<dynamic>?)
|
||||
?.map((e) => e.toString())
|
||||
.toList() ??
|
||||
const [],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// The [data] payload inside [FingerprintVerifyResponseModel].
|
||||
class FingerprintVerifyData {
|
||||
final bool matchFlag;
|
||||
final FingerprintPersonInfo? personInfo;
|
||||
|
||||
// ── Error / status fields returned directly in data ─────────────────────
|
||||
final int? errorCode;
|
||||
final String errorType;
|
||||
final String errorDetail;
|
||||
final String errorMessage;
|
||||
final int? statusCode;
|
||||
final bool isError;
|
||||
final String? rawResponseBody;
|
||||
final String? responseReceivedAt;
|
||||
|
||||
const FingerprintVerifyData({
|
||||
required this.matchFlag,
|
||||
this.personInfo,
|
||||
this.errorCode,
|
||||
this.errorType = '',
|
||||
this.errorDetail = '',
|
||||
this.errorMessage = '',
|
||||
this.statusCode,
|
||||
this.isError = false,
|
||||
this.rawResponseBody,
|
||||
this.responseReceivedAt,
|
||||
});
|
||||
|
||||
factory FingerprintVerifyData.fromJson(Map<String, dynamic> json) {
|
||||
return FingerprintVerifyData(
|
||||
matchFlag: json['matchFlag'] as bool? ?? false,
|
||||
personInfo: json['personInfo'] != null
|
||||
? FingerprintPersonInfo.fromJson(
|
||||
json['personInfo'] as Map<String, dynamic>)
|
||||
: null,
|
||||
errorCode: json['errorCode'] as int?,
|
||||
errorType: json['errorType'] as String? ?? '',
|
||||
errorDetail: json['errorDetail'] as String? ?? '',
|
||||
errorMessage: json['errorMessage'] as String? ?? '',
|
||||
statusCode: json['statusCode'] as int?,
|
||||
isError: json['isError'] as bool? ?? false,
|
||||
rawResponseBody: json['rawResponseBody'] as String?,
|
||||
responseReceivedAt: json['responseReceivedAt'] as String?,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Person information returned inside [FingerprintVerifyData].
|
||||
class FingerprintPersonInfo {
|
||||
final String id;
|
||||
final String firstNameT;
|
||||
final String firstName;
|
||||
final String fatherName;
|
||||
final String fatherNameT;
|
||||
final String grandFatherName;
|
||||
final String grandFatherNameT;
|
||||
final String familyName;
|
||||
final String familyNameT;
|
||||
final String? birthDateG;
|
||||
final String sexCode;
|
||||
final String occupationDescAr;
|
||||
final String occupationDescEn;
|
||||
final String occupationCode;
|
||||
final String nationalityCode;
|
||||
final String sponsorName;
|
||||
final String sponsorNameT;
|
||||
|
||||
const FingerprintPersonInfo({
|
||||
this.id = '',
|
||||
this.firstNameT = '',
|
||||
this.firstName = '',
|
||||
this.fatherName = '',
|
||||
this.fatherNameT = '',
|
||||
this.grandFatherName = '',
|
||||
this.grandFatherNameT = '',
|
||||
this.familyName = '',
|
||||
this.familyNameT = '',
|
||||
this.birthDateG,
|
||||
this.sexCode = '',
|
||||
this.occupationDescAr = '',
|
||||
this.occupationDescEn = '',
|
||||
this.occupationCode = '',
|
||||
this.nationalityCode = '',
|
||||
this.sponsorName = '',
|
||||
this.sponsorNameT = '',
|
||||
});
|
||||
|
||||
factory FingerprintPersonInfo.fromJson(Map<String, dynamic> json) {
|
||||
return FingerprintPersonInfo(
|
||||
id: json['id'] as String? ?? '',
|
||||
firstNameT: json['firstNameT'] as String? ?? '',
|
||||
firstName: json['firstName'] as String? ?? '',
|
||||
fatherName: json['fatherName'] as String? ?? '',
|
||||
fatherNameT: json['fatherNameT'] as String? ?? '',
|
||||
grandFatherName: json['grandFatherName'] as String? ?? '',
|
||||
grandFatherNameT: json['grandFatherNameT'] as String? ?? '',
|
||||
familyName: json['familyName'] as String? ?? '',
|
||||
familyNameT: json['familyNameT'] as String? ?? '',
|
||||
birthDateG: json['birthDateG'] as String?,
|
||||
sexCode: json['sexCode'] as String? ?? '',
|
||||
occupationDescAr: json['occupationDescAr'] as String? ?? '',
|
||||
occupationDescEn: json['occupationDescEn'] as String? ?? '',
|
||||
occupationCode: json['occupationCode'] as String? ?? '',
|
||||
nationalityCode: json['nationalityCode'] as String? ?? '',
|
||||
sponsorName: json['sponsorName'] as String? ?? '',
|
||||
sponsorNameT: json['sponsorNameT'] as String? ?? '',
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,59 @@
|
||||
/// Response model for the [POST api/Auth/login] endpoint.
|
||||
///
|
||||
/// Example JSON:
|
||||
/// ```json
|
||||
/// {
|
||||
/// "success": true,
|
||||
/// "message": "Login successful",
|
||||
/// "data": {
|
||||
/// "token": "eyJhbGci…",
|
||||
/// "expiresAt": "2026-06-22T11:43:12.071Z"
|
||||
/// },
|
||||
/// "errors": []
|
||||
/// }
|
||||
/// ```
|
||||
class LoginResponseModel {
|
||||
final bool success;
|
||||
final String message;
|
||||
final LoginDataModel? data;
|
||||
final List<String> errors;
|
||||
|
||||
const LoginResponseModel({
|
||||
required this.success,
|
||||
required this.message,
|
||||
this.data,
|
||||
this.errors = const [],
|
||||
});
|
||||
|
||||
factory LoginResponseModel.fromJson(Map<String, dynamic> json) {
|
||||
return LoginResponseModel(
|
||||
success: json['success'] as bool? ?? false,
|
||||
message: json['message'] as String? ?? '',
|
||||
data: json['data'] != null
|
||||
? LoginDataModel.fromJson(json['data'] as Map<String, dynamic>)
|
||||
: null,
|
||||
errors: (json['errors'] as List<dynamic>?)
|
||||
?.map((e) => e.toString())
|
||||
.toList() ??
|
||||
const [],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Token payload returned inside [LoginResponseModel.data].
|
||||
class LoginDataModel {
|
||||
final String token;
|
||||
final String expiresAt;
|
||||
|
||||
const LoginDataModel({
|
||||
required this.token,
|
||||
required this.expiresAt,
|
||||
});
|
||||
|
||||
factory LoginDataModel.fromJson(Map<String, dynamic> json) {
|
||||
return LoginDataModel(
|
||||
token: json['token'] as String? ?? '',
|
||||
expiresAt: json['expiresAt'] as String? ?? '',
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,214 @@
|
||||
|
||||
|
||||
/// Maps finger position index (sent to the API as [fingerType]) to its
|
||||
/// human-readable display label shown in the finger-selection dropdown.
|
||||
const Map<int, String> fingerIndexMap = {
|
||||
1: 'Right Thumb',
|
||||
2: 'Right Index Finger',
|
||||
3: 'Right Middle Finger',
|
||||
4: 'Right Ring Finger',
|
||||
5: 'Right Baby Finger (or Pinky)',
|
||||
6: 'Left Thumb',
|
||||
7: 'Left Index Finger',
|
||||
8: 'Left Middle Finger',
|
||||
9: 'Left Ring Finger',
|
||||
10: 'Left Baby Finger (or Pinky)',
|
||||
};
|
||||
|
||||
const List<Map<String, String>> countryAlpha2List = [
|
||||
{'name': 'Afghanistan', 'code': 'AF'},
|
||||
{'name': 'Albania', 'code': 'AL'},
|
||||
{'name': 'Algeria', 'code': 'DZ'},
|
||||
{'name': 'Andorra', 'code': 'AD'},
|
||||
{'name': 'Angola', 'code': 'AO'},
|
||||
{'name': 'Antigua and Barbuda', 'code': 'AG'},
|
||||
{'name': 'Argentina', 'code': 'AR'},
|
||||
{'name': 'Armenia', 'code': 'AM'},
|
||||
{'name': 'Australia', 'code': 'AU'},
|
||||
{'name': 'Austria', 'code': 'AT'},
|
||||
{'name': 'Azerbaijan', 'code': 'AZ'},
|
||||
{'name': 'Bahamas', 'code': 'BS'},
|
||||
{'name': 'Bahrain', 'code': 'BH'},
|
||||
{'name': 'Bangladesh', 'code': 'BD'},
|
||||
{'name': 'Barbados', 'code': 'BB'},
|
||||
{'name': 'Belarus', 'code': 'BY'},
|
||||
{'name': 'Belgium', 'code': 'BE'},
|
||||
{'name': 'Belize', 'code': 'BZ'},
|
||||
{'name': 'Benin', 'code': 'BJ'},
|
||||
{'name': 'Bhutan', 'code': 'BT'},
|
||||
{'name': 'Bolivia', 'code': 'BO'},
|
||||
{'name': 'Bosnia and Herzegovina', 'code': 'BA'},
|
||||
{'name': 'Botswana', 'code': 'BW'},
|
||||
{'name': 'Brazil', 'code': 'BR'},
|
||||
{'name': 'Brunei', 'code': 'BN'},
|
||||
{'name': 'Bulgaria', 'code': 'BG'},
|
||||
{'name': 'Burkina Faso', 'code': 'BF'},
|
||||
{'name': 'Burundi', 'code': 'BI'},
|
||||
{'name': 'Cabo Verde', 'code': 'CV'},
|
||||
{'name': 'Cambodia', 'code': 'KH'},
|
||||
{'name': 'Cameroon', 'code': 'CM'},
|
||||
{'name': 'Canada', 'code': 'CA'},
|
||||
{'name': 'Central African Republic', 'code': 'CF'},
|
||||
{'name': 'Chad', 'code': 'TD'},
|
||||
{'name': 'Chile', 'code': 'CL'},
|
||||
{'name': 'China', 'code': 'CN'},
|
||||
{'name': 'Colombia', 'code': 'CO'},
|
||||
{'name': 'Comoros', 'code': 'KM'},
|
||||
{'name': 'Congo', 'code': 'CG'},
|
||||
{'name': 'Costa Rica', 'code': 'CR'},
|
||||
{'name': 'Croatia', 'code': 'HR'},
|
||||
{'name': 'Cuba', 'code': 'CU'},
|
||||
{'name': 'Cyprus', 'code': 'CY'},
|
||||
{'name': 'Czechia', 'code': 'CZ'},
|
||||
{'name': 'Democratic Republic of the Congo', 'code': 'CD'},
|
||||
{'name': 'Denmark', 'code': 'DK'},
|
||||
{'name': 'Djibouti', 'code': 'DJ'},
|
||||
{'name': 'Dominica', 'code': 'DM'},
|
||||
{'name': 'Dominican Republic', 'code': 'DO'},
|
||||
{'name': 'Ecuador', 'code': 'EC'},
|
||||
{'name': 'Egypt', 'code': 'EG'},
|
||||
{'name': 'El Salvador', 'code': 'SV'},
|
||||
{'name': 'Equatorial Guinea', 'code': 'GQ'},
|
||||
{'name': 'Eritrea', 'code': 'ER'},
|
||||
{'name': 'Estonia', 'code': 'EE'},
|
||||
{'name': 'Eswatini', 'code': 'SZ'},
|
||||
{'name': 'Ethiopia', 'code': 'ET'},
|
||||
{'name': 'Fiji', 'code': 'FJ'},
|
||||
{'name': 'Finland', 'code': 'FI'},
|
||||
{'name': 'France', 'code': 'FR'},
|
||||
{'name': 'Gabon', 'code': 'GA'},
|
||||
{'name': 'Gambia', 'code': 'GM'},
|
||||
{'name': 'Georgia', 'code': 'GE'},
|
||||
{'name': 'Germany', 'code': 'DE'},
|
||||
{'name': 'Ghana', 'code': 'GH'},
|
||||
{'name': 'Greece', 'code': 'GR'},
|
||||
{'name': 'Grenada', 'code': 'GD'},
|
||||
{'name': 'Guatemala', 'code': 'GT'},
|
||||
{'name': 'Guinea', 'code': 'GN'},
|
||||
{'name': 'Guinea-Bissau', 'code': 'GW'},
|
||||
{'name': 'Guyana', 'code': 'GY'},
|
||||
{'name': 'Haiti', 'code': 'HT'},
|
||||
{'name': 'Honduras', 'code': 'HN'},
|
||||
{'name': 'Hungary', 'code': 'HU'},
|
||||
{'name': 'Iceland', 'code': 'IS'},
|
||||
{'name': 'India', 'code': 'IN'},
|
||||
{'name': 'Indonesia', 'code': 'ID'},
|
||||
{'name': 'Iran', 'code': 'IR'},
|
||||
{'name': 'Iraq', 'code': 'IQ'},
|
||||
{'name': 'Ireland', 'code': 'IE'},
|
||||
{'name': 'Israel', 'code': 'IL'},
|
||||
{'name': 'Italy', 'code': 'IT'},
|
||||
{'name': 'Jamaica', 'code': 'JM'},
|
||||
{'name': 'Japan', 'code': 'JP'},
|
||||
{'name': 'Jordan', 'code': 'JO'},
|
||||
{'name': 'Kazakhstan', 'code': 'KZ'},
|
||||
{'name': 'Kenya', 'code': 'KE'},
|
||||
{'name': 'Kiribati', 'code': 'KI'},
|
||||
{'name': 'Kuwait', 'code': 'KW'},
|
||||
{'name': 'Kyrgyzstan', 'code': 'KG'},
|
||||
{'name': 'Laos', 'code': 'LA'},
|
||||
{'name': 'Latvia', 'code': 'LV'},
|
||||
{'name': 'Lebanon', 'code': 'LB'},
|
||||
{'name': 'Lesotho', 'code': 'LS'},
|
||||
{'name': 'Liberia', 'code': 'LR'},
|
||||
{'name': 'Libya', 'code': 'LY'},
|
||||
{'name': 'Liechtenstein', 'code': 'LI'},
|
||||
{'name': 'Lithuania', 'code': 'LT'},
|
||||
{'name': 'Luxembourg', 'code': 'LU'},
|
||||
{'name': 'Madagascar', 'code': 'MG'},
|
||||
{'name': 'Malawi', 'code': 'MW'},
|
||||
{'name': 'Malaysia', 'code': 'MY'},
|
||||
{'name': 'Maldives', 'code': 'MV'},
|
||||
{'name': 'Mali', 'code': 'ML'},
|
||||
{'name': 'Malta', 'code': 'MT'},
|
||||
{'name': 'Marshall Islands', 'code': 'MH'},
|
||||
{'name': 'Mauritania', 'code': 'MR'},
|
||||
{'name': 'Mauritius', 'code': 'MU'},
|
||||
{'name': 'Mexico', 'code': 'MX'},
|
||||
{'name': 'Micronesia', 'code': 'FM'},
|
||||
{'name': 'Moldova', 'code': 'MD'},
|
||||
{'name': 'Monaco', 'code': 'MC'},
|
||||
{'name': 'Mongolia', 'code': 'MN'},
|
||||
{'name': 'Montenegro', 'code': 'ME'},
|
||||
{'name': 'Morocco', 'code': 'MA'},
|
||||
{'name': 'Mozambique', 'code': 'MZ'},
|
||||
{'name': 'Myanmar', 'code': 'MM'},
|
||||
{'name': 'Namibia', 'code': 'NA'},
|
||||
{'name': 'Nauru', 'code': 'NR'},
|
||||
{'name': 'Nepal', 'code': 'NP'},
|
||||
{'name': 'Netherlands', 'code': 'NL'},
|
||||
{'name': 'New Zealand', 'code': 'NZ'},
|
||||
{'name': 'Nicaragua', 'code': 'NI'},
|
||||
{'name': 'Niger', 'code': 'NE'},
|
||||
{'name': 'Nigeria', 'code': 'NG'},
|
||||
{'name': 'North Korea', 'code': 'KP'},
|
||||
{'name': 'North Macedonia', 'code': 'MK'},
|
||||
{'name': 'Norway', 'code': 'NO'},
|
||||
{'name': 'Oman', 'code': 'OM'},
|
||||
{'name': 'Pakistan', 'code': 'PK'},
|
||||
{'name': 'Palau', 'code': 'PW'},
|
||||
{'name': 'Palestine', 'code': 'PS'},
|
||||
{'name': 'Panama', 'code': 'PA'},
|
||||
{'name': 'Papua New Guinea', 'code': 'PG'},
|
||||
{'name': 'Paraguay', 'code': 'PY'},
|
||||
{'name': 'Peru', 'code': 'PE'},
|
||||
{'name': 'Philippines', 'code': 'PH'},
|
||||
{'name': 'Poland', 'code': 'PL'},
|
||||
{'name': 'Portugal', 'code': 'PT'},
|
||||
{'name': 'Qatar', 'code': 'QA'},
|
||||
{'name': 'Romania', 'code': 'RO'},
|
||||
{'name': 'Russia', 'code': 'RU'},
|
||||
{'name': 'Rwanda', 'code': 'RW'},
|
||||
{'name': 'Saint Kitts and Nevis', 'code': 'KN'},
|
||||
{'name': 'Saint Lucia', 'code': 'LC'},
|
||||
{'name': 'Saint Vincent and the Grenadines', 'code': 'VC'},
|
||||
{'name': 'Samoa', 'code': 'WS'},
|
||||
{'name': 'San Marino', 'code': 'SM'},
|
||||
{'name': 'Sao Tome and Principe', 'code': 'ST'},
|
||||
{'name': 'Saudi Arabia', 'code': 'SA'},
|
||||
{'name': 'Senegal', 'code': 'SN'},
|
||||
{'name': 'Serbia', 'code': 'RS'},
|
||||
{'name': 'Seychelles', 'code': 'SC'},
|
||||
{'name': 'Sierra Leone', 'code': 'SL'},
|
||||
{'name': 'Singapore', 'code': 'SG'},
|
||||
{'name': 'Slovakia', 'code': 'SK'},
|
||||
{'name': 'Slovenia', 'code': 'SI'},
|
||||
{'name': 'Solomon Islands', 'code': 'SB'},
|
||||
{'name': 'Somalia', 'code': 'SO'},
|
||||
{'name': 'South Africa', 'code': 'ZA'},
|
||||
{'name': 'South Korea', 'code': 'KR'},
|
||||
{'name': 'South Sudan', 'code': 'SS'},
|
||||
{'name': 'Spain', 'code': 'ES'},
|
||||
{'name': 'Sri Lanka', 'code': 'LK'},
|
||||
{'name': 'Sudan', 'code': 'SD'},
|
||||
{'name': 'Suriname', 'code': 'SR'},
|
||||
{'name': 'Sweden', 'code': 'SE'},
|
||||
{'name': 'Switzerland', 'code': 'CH'},
|
||||
{'name': 'Syria', 'code': 'SY'},
|
||||
{'name': 'Taiwan', 'code': 'TW'},
|
||||
{'name': 'Tajikistan', 'code': 'TJ'},
|
||||
{'name': 'Tanzania', 'code': 'TZ'},
|
||||
{'name': 'Thailand', 'code': 'TH'},
|
||||
{'name': 'Timor-Leste', 'code': 'TL'},
|
||||
{'name': 'Togo', 'code': 'TG'},
|
||||
{'name': 'Tonga', 'code': 'TO'},
|
||||
{'name': 'Trinidad and Tobago', 'code': 'TT'},
|
||||
{'name': 'Tunisia', 'code': 'TN'},
|
||||
{'name': 'Turkey', 'code': 'TR'},
|
||||
{'name': 'Turkmenistan', 'code': 'TM'},
|
||||
{'name': 'Tuvalu', 'code': 'TV'},
|
||||
{'name': 'Uganda', 'code': 'UG'},
|
||||
{'name': 'Ukraine', 'code': 'UA'},
|
||||
{'name': 'United Arab Emirates', 'code': 'AE'},
|
||||
{'name': 'United Kingdom', 'code': 'GB'},
|
||||
{'name': 'United States', 'code': 'US'},
|
||||
{'name': 'Uruguay', 'code': 'UY'},
|
||||
{'name': 'Uzbekistan', 'code': 'UZ'},
|
||||
{'name': 'Vanuatu', 'code': 'VU'},
|
||||
{'name': 'Vatican City', 'code': 'VA'},
|
||||
{'name': 'Venezuela', 'code': 'VE'},
|
||||
{'name': 'Vietnam', 'code': 'VN'},
|
||||
{'name': 'Yemen', 'code': 'YE'},
|
||||
{'name': 'Zambia', 'code': 'ZM'},
|
||||
{'name': 'Zimbabwe', 'code': 'ZW'},
|
||||
];
|
||||
@ -0,0 +1,10 @@
|
||||
abstract class FingerprintManager {
|
||||
/// Connect to the fingerprint sensor
|
||||
Future<void> connect();
|
||||
|
||||
/// Disconnect from the fingerprint sensor
|
||||
Future<void> disconnect();
|
||||
|
||||
/// Scan the fingerprint and return the base64 encoded result
|
||||
Future<String?> scan();
|
||||
}
|
||||
@ -0,0 +1,108 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
import 'package:image/image.dart' as img;
|
||||
|
||||
class FingerprintValidation {
|
||||
final bool isComplete;
|
||||
final double coverageRatio; // % of "ridge" pixels
|
||||
final double contrast; // std dev of pixel intensity
|
||||
final String reason;
|
||||
|
||||
FingerprintValidation({
|
||||
required this.isComplete,
|
||||
required this.coverageRatio,
|
||||
required this.contrast,
|
||||
required this.reason,
|
||||
});
|
||||
}
|
||||
|
||||
FingerprintValidation validateFingerprintBase64(
|
||||
String base64Image, {
|
||||
double minCoverage = 0.20, // tune per scanner DPI/size
|
||||
double maxCoverage = 0.65,
|
||||
double minContrast = 25.0,
|
||||
double edgeCutoffThreshold = 0.45, // % of border pixels that are "ridge"
|
||||
}) {
|
||||
final bytes = base64Decode(base64Image);
|
||||
final image = img.decodeImage(bytes);
|
||||
|
||||
if (image == null) {
|
||||
return FingerprintValidation(
|
||||
isComplete: false,
|
||||
coverageRatio: 0,
|
||||
contrast: 0,
|
||||
reason: 'decode_failed',
|
||||
);
|
||||
}
|
||||
|
||||
final gray = img.grayscale(image);
|
||||
final w = gray.width, h = gray.height;
|
||||
|
||||
int ridgePixels = 0;
|
||||
int total = 0;
|
||||
double sum = 0, sumSq = 0;
|
||||
|
||||
// simple Otsu-ish threshold: use mean as baseline, refine below
|
||||
final List<int> intensities = [];
|
||||
for (final px in gray) {
|
||||
final v = img.getLuminance(px).toDouble();
|
||||
intensities.add(v.toInt());
|
||||
sum += v;
|
||||
total++;
|
||||
}
|
||||
final mean = sum / total;
|
||||
for (final v in intensities) {
|
||||
sumSq += (v - mean) * (v - mean);
|
||||
}
|
||||
final contrast = sqrt(sumSq / total);
|
||||
|
||||
// threshold: pixels darker than mean - 0.5*std are "ridges"
|
||||
final threshold = mean - (contrast * 0.5);
|
||||
for (final v in intensities) {
|
||||
if (v < threshold) ridgePixels++;
|
||||
}
|
||||
final coverage = ridgePixels / total;
|
||||
|
||||
// edge/border check — if ridge pattern is heavily cut off at edges,
|
||||
// finger was likely partial or misplaced
|
||||
int borderRidge = 0, borderTotal = 0;
|
||||
for (int x = 0; x < w; x++) {
|
||||
for (final y in [0, h - 1]) {
|
||||
final v = img.getLuminance(gray.getPixel(x, y)).toDouble();
|
||||
borderTotal++;
|
||||
if (v < threshold) borderRidge++;
|
||||
}
|
||||
}
|
||||
for (int y = 0; y < h; y++) {
|
||||
for (final x in [0, w - 1]) {
|
||||
final v = img.getLuminance(gray.getPixel(x, y)).toDouble();
|
||||
borderTotal++;
|
||||
if (v < threshold) borderRidge++;
|
||||
}
|
||||
}
|
||||
final borderRatio = borderTotal == 0 ? 0 : borderRidge / borderTotal;
|
||||
|
||||
String reason = 'ok';
|
||||
bool isComplete = true;
|
||||
|
||||
if (contrast < minContrast) {
|
||||
isComplete = false;
|
||||
reason = 'Press finger on the scanner with more pressure';
|
||||
} else if (coverage < minCoverage) {
|
||||
isComplete = false;
|
||||
reason = 'Place finger fully on the scanner';
|
||||
} else if (coverage > maxCoverage) {
|
||||
isComplete = false;
|
||||
reason = 'Place finger lightly on the scanner';
|
||||
} else if (borderRatio > edgeCutoffThreshold) {
|
||||
isComplete = false;
|
||||
reason = 'Finger is not centered on the scanner';
|
||||
}
|
||||
|
||||
return FingerprintValidation(
|
||||
isComplete: isComplete,
|
||||
coverageRatio: coverage,
|
||||
contrast: contrast,
|
||||
reason: reason,
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,283 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:zkfinger10/finger_status.dart';
|
||||
import 'package:zkfinger10/finger_status_type.dart';
|
||||
import 'package:zkfinger10/zk_finger.dart';
|
||||
|
||||
import 'finger_print_manager.dart';
|
||||
|
||||
class ZktFingerPrintScanner implements FingerprintManager {
|
||||
bool _isConnected = false;
|
||||
|
||||
// ── Scan state (fresh per scan call) ────────────────────────────────────
|
||||
Completer<String>? _scanCompleter;
|
||||
StreamController<String>? _base64ImageController;
|
||||
StreamSubscription<dynamic>? _imageSubscription;
|
||||
|
||||
// ── Status stream state ──────────────────────────────────────────────────
|
||||
StreamSubscription<dynamic>? _statusSubscription;
|
||||
FingerStatus? lastStatus;
|
||||
FingerStatusType? _tempStatusType;
|
||||
|
||||
/// Set during connect(); completed by _updateStatus when a success status
|
||||
/// arrives. Reset at the start of every retry attempt.
|
||||
Completer<void>? _deviceReadyCompleter;
|
||||
|
||||
static const int _maxConnectAttempts = 5;
|
||||
static const Duration _attemptTimeout = Duration(seconds: 4);
|
||||
static const Duration _retryDelay = Duration(seconds: 4);
|
||||
|
||||
static const List<FingerStatusType> _successStatuses = [
|
||||
FingerStatusType.STARTED_ALREADY,
|
||||
FingerStatusType.STARTED_SUCCESS,
|
||||
];
|
||||
|
||||
static const List<FingerStatusType> _usbConnectionFailureStatuses = [
|
||||
FingerStatusType.FINGER_USB_PERMISSION_DENIED,
|
||||
FingerStatusType.FINGER_USB_PERMISSION_ERROR,
|
||||
];
|
||||
|
||||
// ── Stream accessor ──────────────────────────────────────────────────────
|
||||
Stream<String> get base64ImageStream =>
|
||||
_base64ImageController?.stream ?? const Stream.empty();
|
||||
|
||||
ZktFingerPrintScanner();
|
||||
|
||||
// ── Status callback ──────────────────────────────────────────────────────
|
||||
|
||||
void _updateStatus(dynamic value) {
|
||||
try {
|
||||
final statusMap = value as Map<dynamic, dynamic>;
|
||||
final statusType =
|
||||
FingerStatusType.values[statusMap['fingerStatus'] as int];
|
||||
lastStatus = FingerStatus(
|
||||
statusMap['message'],
|
||||
statusType,
|
||||
statusMap['id'],
|
||||
statusMap['data'],
|
||||
);
|
||||
|
||||
|
||||
if (statusType == _tempStatusType &&
|
||||
_tempStatusType == FingerStatusType.CAPTURE_ERROR) {
|
||||
// Ignore repeated CAPTURE_ERROR — device is stuck, do nothing
|
||||
} else {
|
||||
_tempStatusType = statusType;
|
||||
if (_deviceReadyCompleter != null &&
|
||||
!_deviceReadyCompleter!.isCompleted &&
|
||||
_usbConnectionFailureStatuses.contains(statusType) ){
|
||||
_deviceReadyCompleter!.completeError(Exception(
|
||||
'Device not connected'
|
||||
));
|
||||
}
|
||||
|
||||
// If we are in the connect-retry loop and got a success status, resolve
|
||||
if (_deviceReadyCompleter != null &&
|
||||
!_deviceReadyCompleter!.isCompleted &&
|
||||
lastStatus != null &&
|
||||
_successStatuses.contains(statusType) && _successStatuses.contains(lastStatus!.statusType)) {
|
||||
_deviceReadyCompleter!.complete();
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
if (_deviceReadyCompleter != null &&
|
||||
!_deviceReadyCompleter!.isCompleted) {
|
||||
_deviceReadyCompleter!.completeError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Image callback ───────────────────────────────────────────────────────
|
||||
|
||||
void _onNativeImage(dynamic imageBytes) {
|
||||
try {
|
||||
final base64Image = uint8ListTob64(imageBytes as Uint8List);
|
||||
|
||||
if (_base64ImageController != null &&
|
||||
!_base64ImageController!.isClosed) {
|
||||
_base64ImageController!.add(base64Image);
|
||||
}
|
||||
|
||||
if (_scanCompleter != null && !_scanCompleter!.isCompleted) {
|
||||
_scanCompleter!.complete(base64Image);
|
||||
}
|
||||
} catch (e) {
|
||||
if (_base64ImageController != null &&
|
||||
!_base64ImageController!.isClosed) {
|
||||
_base64ImageController!.addError(e);
|
||||
}
|
||||
if (_scanCompleter != null && !_scanCompleter!.isCompleted) {
|
||||
_scanCompleter!.completeError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Public API ───────────────────────────────────────────────────────────
|
||||
|
||||
@override
|
||||
Future<void> connect() async {
|
||||
try {
|
||||
if (_isConnected) await disconnect();
|
||||
|
||||
final result = await ZkFinger.openConnection();
|
||||
if (result != true) {
|
||||
throw Exception('Failed to initialize fingerprint device');
|
||||
}
|
||||
|
||||
// ── Start status stream ──────────────────────────────────────────────
|
||||
await _statusSubscription?.cancel();
|
||||
_statusSubscription =
|
||||
ZkFinger.statusChangeStream.receiveBroadcastStream().listen(
|
||||
_updateStatus,
|
||||
onError: (e) {
|
||||
if (_deviceReadyCompleter != null &&
|
||||
!_deviceReadyCompleter!.isCompleted) {
|
||||
_deviceReadyCompleter!.completeError(e);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// ── Retry loop: up to 5 attempts, 5 s window each ───────────────────
|
||||
bool deviceReady = false;
|
||||
|
||||
for (int attempt = 1; attempt <= _maxConnectAttempts; attempt++) {
|
||||
|
||||
_deviceReadyCompleter = Completer<void>();
|
||||
final result = await ZkFinger.openConnection();
|
||||
if (result != true) {
|
||||
throw Exception('Failed to initialize fingerprint device');
|
||||
}
|
||||
|
||||
try {
|
||||
await _deviceReadyCompleter!.future.timeout(_attemptTimeout);
|
||||
// Success status received within the window
|
||||
deviceReady = true;
|
||||
break;
|
||||
} on TimeoutException {
|
||||
if (attempt < _maxConnectAttempts) {
|
||||
await Future.delayed(_retryDelay);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!deviceReady) {
|
||||
// Clean up status subscription and close hardware
|
||||
await _statusSubscription?.cancel();
|
||||
_statusSubscription = null;
|
||||
try { await ZkFinger.closeConnection(); } catch (_) {}
|
||||
throw Exception(
|
||||
'Device not ready after $_maxConnectAttempts attempts. '
|
||||
'Last status: ${lastStatus?.message ?? "no status received"}',
|
||||
);
|
||||
}
|
||||
|
||||
_isConnected = true;
|
||||
|
||||
// ── Subscribe to image stream now that device is confirmed ready ─────
|
||||
_imageSubscription =
|
||||
ZkFinger.imageStream.receiveBroadcastStream().listen(
|
||||
_onNativeImage,
|
||||
onError: (e) {
|
||||
if (_scanCompleter != null && !_scanCompleter!.isCompleted) {
|
||||
_scanCompleter!.completeError(e);
|
||||
}
|
||||
if (_base64ImageController != null &&
|
||||
!_base64ImageController!.isClosed) {
|
||||
_base64ImageController!.addError(e);
|
||||
}
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
_isConnected = false;
|
||||
rethrow; // preserve original message
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> disconnect() async {
|
||||
try { await ZkFinger.closeConnection(); } catch (_) {}
|
||||
|
||||
try { await _imageSubscription?.cancel(); } catch (_) {}
|
||||
_imageSubscription = null;
|
||||
|
||||
try { await _statusSubscription?.cancel(); } catch (_) {}
|
||||
_statusSubscription = null;
|
||||
|
||||
if (_deviceReadyCompleter != null &&
|
||||
!_deviceReadyCompleter!.isCompleted) {
|
||||
_deviceReadyCompleter!.completeError(StateError('Disconnected'));
|
||||
}
|
||||
_deviceReadyCompleter = null;
|
||||
|
||||
if (_scanCompleter != null && !_scanCompleter!.isCompleted) {
|
||||
_scanCompleter!.completeError(StateError('Disconnected'));
|
||||
}
|
||||
_scanCompleter = null;
|
||||
|
||||
try {
|
||||
if (_base64ImageController != null &&
|
||||
!_base64ImageController!.isClosed) {
|
||||
await _base64ImageController!.close();
|
||||
}
|
||||
} catch (_) {}
|
||||
_base64ImageController = null;
|
||||
|
||||
_isConnected = false;
|
||||
_tempStatusType = null;
|
||||
}
|
||||
|
||||
/// Returns a base64-encoded fingerprint for a single scan.
|
||||
/// Creates a fresh [Completer] + [StreamController] each call so
|
||||
/// consecutive scans always work.
|
||||
@override
|
||||
Future<String?> scan(
|
||||
{Duration timeout = const Duration(seconds: 20)}) async {
|
||||
try {
|
||||
if (!_isConnected) await connect();
|
||||
|
||||
try {
|
||||
if (_base64ImageController != null &&
|
||||
!_base64ImageController!.isClosed) {
|
||||
await _base64ImageController!.close();
|
||||
}
|
||||
} catch (_) {}
|
||||
|
||||
_base64ImageController = StreamController<String>.broadcast();
|
||||
_scanCompleter = Completer<String>();
|
||||
|
||||
final timer = Timer(timeout, () {
|
||||
if (_scanCompleter != null && !_scanCompleter!.isCompleted) {
|
||||
_scanCompleter!
|
||||
.completeError(TimeoutException('Scan timed out', timeout));
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
return await _scanCompleter!.future;
|
||||
} finally {
|
||||
timer.cancel();
|
||||
}
|
||||
} catch (e) {
|
||||
throw Exception('Fingerprint scan failed: ${e.toString()}');
|
||||
}
|
||||
}
|
||||
|
||||
// ── Helpers ──────────────────────────────────────────────────────────────
|
||||
|
||||
String uint8ListTob64(Uint8List uint8list) {
|
||||
return 'data:image/png;base64,${base64Encode(uint8list)}';
|
||||
}
|
||||
|
||||
bool get isConnected => _isConnected;
|
||||
|
||||
void dispose() {
|
||||
_imageSubscription?.cancel();
|
||||
_statusSubscription?.cancel();
|
||||
_scanCompleter = null;
|
||||
_deviceReadyCompleter = null;
|
||||
_base64ImageController?.close();
|
||||
_base64ImageController = null;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,647 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../theme/app_theme.dart';
|
||||
import '../models/auth_result_ui_model.dart';
|
||||
|
||||
/// Full-screen view that displays the authentication result returned by the
|
||||
/// verification API. Redesigned with a "futuristic hospital, KSA" theme:
|
||||
/// dark glassy panels, glowing cyan/green accents, Material 3 components,
|
||||
/// and a responsive two-column layout on tablets.
|
||||
class AuthResultScreen extends StatelessWidget {
|
||||
final AuthResultUiModel result;
|
||||
|
||||
const AuthResultScreen({super.key, required this.result});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final accent = result.isSuccess ? AppTheme.saudiGreen : AppTheme.redAlert;
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: AppTheme.ink,
|
||||
appBar: AppBar(
|
||||
leading: const BackButton(),
|
||||
title: const Text('Authentication Result'),
|
||||
actions: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 16),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
|
||||
decoration: BoxDecoration(
|
||||
color: accent.withValues(alpha: 0.12),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(color: accent.withValues(alpha: 0.4)),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
result.isSuccess ? Icons.verified_outlined : Icons.error_outline,
|
||||
size: 14,
|
||||
color: accent,
|
||||
),
|
||||
const SizedBox(width: 5),
|
||||
Text(
|
||||
result.isSuccess ? 'VERIFIED' : 'FAILED',
|
||||
style: TextStyle(fontSize: 11, color: accent, fontWeight: FontWeight.w700),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: TechGridBackground(
|
||||
child: SafeArea(
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final tablet = constraints.maxWidth >= 760;
|
||||
return Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.fromLTRB(16, 16, 16, 8),
|
||||
child: Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(maxWidth: tablet ? 1000 : 720),
|
||||
child: tablet ? _TabletBody(result: result) : _PhoneBody(result: result),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 4, 16, 16),
|
||||
child: Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(maxWidth: tablet ? 1000 : 720),
|
||||
child: FilledButton.icon(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
icon: const Icon(Icons.fingerprint),
|
||||
label: const Text('Return to Fingerprint'),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Phone body: stacked sections ────────────────────────────────────────────
|
||||
|
||||
class _PhoneBody extends StatelessWidget {
|
||||
final AuthResultUiModel result;
|
||||
const _PhoneBody({required this.result});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
_StatusBanner(result: result),
|
||||
const SizedBox(height: 16),
|
||||
if (result.isSuccess) _PersonInfoPanel(result: result),
|
||||
if (!result.isSuccess) _ErrorPanel(result: result),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Tablet body: status banner full width, info/error two-column grid ─────
|
||||
|
||||
class _TabletBody extends StatelessWidget {
|
||||
final AuthResultUiModel result;
|
||||
const _TabletBody({required this.result});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
_StatusBanner(result: result),
|
||||
const SizedBox(height: 20),
|
||||
if (result.isSuccess) _PersonInfoPanel(result: result, twoColumn: true),
|
||||
if (!result.isSuccess) _ErrorPanel(result: result),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Status banner ───────────────────────────────────────────────────────────
|
||||
|
||||
class _StatusBanner extends StatelessWidget {
|
||||
final AuthResultUiModel result;
|
||||
const _StatusBanner({required this.result});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final accent = result.isSuccess ? AppTheme.saudiGreen : AppTheme.redAlert;
|
||||
final message = result.statusMessage.isEmpty
|
||||
? (result.isSuccess ? 'Authentication successful!' : 'Authentication failed')
|
||||
: result.statusMessage;
|
||||
|
||||
return GlowPanel(
|
||||
glowColor: accent,
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 52,
|
||||
height: 52,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: accent.withValues(alpha: 0.12),
|
||||
border: Border.all(color: accent.withValues(alpha: 0.4)),
|
||||
),
|
||||
child: Icon(
|
||||
result.isSuccess ? Icons.check_circle_outline : Icons.cancel_outlined,
|
||||
color: accent,
|
||||
size: 28,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'STATUS',
|
||||
style: TextStyle(
|
||||
color: AppTheme.textMuted,
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w700,
|
||||
letterSpacing: 1.1,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
message,
|
||||
style: TextStyle(color: accent, fontWeight: FontWeight.w700, fontSize: 16),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Person information panel ────────────────────────────────────────────────
|
||||
|
||||
class _PersonInfoPanel extends StatelessWidget {
|
||||
final AuthResultUiModel result;
|
||||
final bool twoColumn;
|
||||
const _PersonInfoPanel({required this.result, this.twoColumn = false});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final fullName = [result.firstName, result.lastName].where((s) => s.isNotEmpty).join(' ');
|
||||
|
||||
final rows = <_DetailRow>[
|
||||
_DetailRow(label: 'Father', value: result.fatherName, icon: Icons.man_outlined),
|
||||
_DetailRow(label: 'Grandfather', value: result.grandfatherName, icon: Icons.elderly_outlined),
|
||||
_DetailRow(label: 'Occupation', value: result.occupation, icon: Icons.work_outline),
|
||||
_DetailRow(label: 'Gender', value: result.gender, icon: Icons.wc_outlined),
|
||||
_DetailRow(label: 'Sponsor', value: result.sponsor, icon: Icons.shield_outlined),
|
||||
_DetailRow(label: 'ID Number', value: result.idNumber, icon: Icons.badge_outlined),
|
||||
_DetailRow(label: 'Birth Date', value: result.birthDate, icon: Icons.cake_outlined),
|
||||
_DetailRow(label: 'Nationality', value: result.nationality, icon: Icons.flag_outlined),
|
||||
];
|
||||
|
||||
return GlowPanel(
|
||||
glowColor: AppTheme.saudiGreen,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 44,
|
||||
height: 44,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: AppTheme.saudiGreen.withValues(alpha: 0.12),
|
||||
border: Border.all(color: AppTheme.saudiGreen.withValues(alpha: 0.4)),
|
||||
),
|
||||
child: const Icon(Icons.person_outline, color: AppTheme.saudiGreen, size: 22),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'PERSON INFORMATION',
|
||||
style: TextStyle(
|
||||
color: AppTheme.saudiGreen,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w700,
|
||||
letterSpacing: 1.0,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
fullName.isEmpty ? '—' : fullName,
|
||||
style: const TextStyle(
|
||||
color: AppTheme.textPrimary,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const Divider(height: 1),
|
||||
const SizedBox(height: 8),
|
||||
if (twoColumn)
|
||||
GridView.count(
|
||||
crossAxisCount: 2,
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
mainAxisSpacing: 4,
|
||||
crossAxisSpacing: 16,
|
||||
childAspectRatio: 5.4,
|
||||
children: rows,
|
||||
)
|
||||
else
|
||||
Column(children: rows),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Error panel ──────────────────────────────────────────────────────────────
|
||||
|
||||
class _ErrorPanel extends StatelessWidget {
|
||||
final AuthResultUiModel result;
|
||||
const _ErrorPanel({required this.result});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GlowPanel(
|
||||
glowColor: AppTheme.redAlert,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 44,
|
||||
height: 44,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: AppTheme.redAlert.withValues(alpha: 0.12),
|
||||
border: Border.all(color: AppTheme.redAlert.withValues(alpha: 0.4)),
|
||||
),
|
||||
child: const Icon(Icons.error_outline, color: AppTheme.redAlert, size: 22),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
const Text(
|
||||
'ERROR DETAILS',
|
||||
style: TextStyle(
|
||||
color: AppTheme.redAlert,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w700,
|
||||
letterSpacing: 1.0,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const Divider(height: 1),
|
||||
const SizedBox(height: 8),
|
||||
_DetailRow(label: 'Type', value: result.errorType ?? '', icon: Icons.category_outlined),
|
||||
_DetailRow(label: 'Message', value: result.errorMessage ?? '', icon: Icons.notes_outlined),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Reusable detail row (icon + label + value) ──────────────────────────────
|
||||
|
||||
class _DetailRow extends StatelessWidget {
|
||||
final String label;
|
||||
final String value;
|
||||
final IconData icon;
|
||||
|
||||
const _DetailRow({required this.label, required this.value, required this.icon});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Icon(icon, size: 16, color: AppTheme.textMuted),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppTheme.textMuted,
|
||||
letterSpacing: 0.4,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
value.isEmpty ? '—' : value,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: AppTheme.textPrimary,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
// import 'package:flutter/material.dart';
|
||||
// import '../models/auth_result_ui_model.dart';
|
||||
//
|
||||
// /// Full-screen view that displays the authentication result returned by the
|
||||
// /// verification API. Designed with the Material 3 design system.
|
||||
// class AuthResultScreen extends StatelessWidget {
|
||||
// final AuthResultUiModel result;
|
||||
//
|
||||
// const AuthResultScreen({super.key, required this.result});
|
||||
//
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// final cs = Theme.of(context).colorScheme;
|
||||
//
|
||||
// return Scaffold(
|
||||
// backgroundColor: cs.surfaceContainerLowest,
|
||||
// appBar: AppBar(
|
||||
// backgroundColor: cs.surface,
|
||||
// surfaceTintColor: cs.surfaceTint,
|
||||
// leading: const BackButton(),
|
||||
// title: const Text('Authentication Result'),
|
||||
// titleTextStyle: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||
// fontWeight: FontWeight.bold,
|
||||
// ),
|
||||
// centerTitle: false,
|
||||
// elevation: 0,
|
||||
// scrolledUnderElevation: 1,
|
||||
// ),
|
||||
// body: SafeArea(
|
||||
// child: Column(
|
||||
// children: [
|
||||
// // ── Scrollable content ───────────────────────────────────────────
|
||||
// Expanded(
|
||||
// child: SingleChildScrollView(
|
||||
// padding: const EdgeInsets.fromLTRB(16, 16, 16, 8),
|
||||
// child: Card(
|
||||
// elevation: 0,
|
||||
// shape: RoundedRectangleBorder(
|
||||
// borderRadius: BorderRadius.circular(16),
|
||||
// side: BorderSide(color: cs.outlineVariant),
|
||||
// ),
|
||||
// child: Padding(
|
||||
// padding: const EdgeInsets.symmetric(
|
||||
// vertical: 20, horizontal: 16),
|
||||
// child: Column(
|
||||
// crossAxisAlignment: CrossAxisAlignment.start,
|
||||
// children: [
|
||||
// // ── Status ────────────────────────────────────────
|
||||
// _StatusSection(result: result),
|
||||
//
|
||||
// _divider(),
|
||||
//
|
||||
// // ── Person information ───────────────────────────
|
||||
// _PersonInfoSection(result: result),
|
||||
//
|
||||
// _divider(),
|
||||
//
|
||||
// // ── Error ─────────────────────────────────────────
|
||||
// _ErrorSection(result: result),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
//
|
||||
// // ── Return button ────────────────────────────────────────────────
|
||||
// Padding(
|
||||
// padding: const EdgeInsets.fromLTRB(16, 4, 16, 16),
|
||||
// child: FilledButton.icon(
|
||||
// onPressed: () => Navigator.of(context).pop(),
|
||||
// icon: const Icon(Icons.fingerprint),
|
||||
// label: const Text('Return to Fingerprint'),
|
||||
// style: FilledButton.styleFrom(
|
||||
// minimumSize: const Size.fromHeight(52),
|
||||
// shape: RoundedRectangleBorder(
|
||||
// borderRadius: BorderRadius.circular(12),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// Widget _divider() => const Padding(
|
||||
// padding: EdgeInsets.symmetric(vertical: 12),
|
||||
// child: Divider(height: 1),
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// // ── Status section ─────────────────────────────────────────────────────────────
|
||||
//
|
||||
// class _StatusSection extends StatelessWidget {
|
||||
// final AuthResultUiModel result;
|
||||
//
|
||||
// const _StatusSection({required this.result});
|
||||
//
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// final tt = Theme.of(context).textTheme;
|
||||
// final successColor = const Color(0xFF2E7D32); // green-800
|
||||
// final errorColor = Theme.of(context).colorScheme.error;
|
||||
//
|
||||
// return Column(
|
||||
// crossAxisAlignment: CrossAxisAlignment.start,
|
||||
// children: [
|
||||
// Text('Status:', style: tt.titleSmall?.copyWith(fontWeight: FontWeight.bold)),
|
||||
// const SizedBox(height: 4),
|
||||
// Text(
|
||||
// result.statusMessage.isEmpty
|
||||
// ? (result.isSuccess ? 'Authentication successful!' : 'Authentication failed')
|
||||
// : result.statusMessage,
|
||||
// style: tt.bodyMedium?.copyWith(
|
||||
// color: result.isSuccess ? successColor : errorColor,
|
||||
// fontWeight: FontWeight.w600,
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // ── Person information section ─────────────────────────────────────────────────
|
||||
//
|
||||
// class _PersonInfoSection extends StatelessWidget {
|
||||
// final AuthResultUiModel result;
|
||||
//
|
||||
// const _PersonInfoSection({required this.result});
|
||||
//
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// final tt = Theme.of(context).textTheme;
|
||||
// const successGreen = Color(0xFF2E7D32);
|
||||
//
|
||||
// return Column(
|
||||
// crossAxisAlignment: CrossAxisAlignment.start,
|
||||
// children: [
|
||||
// // Section header
|
||||
// Row(
|
||||
// children: [
|
||||
// const Icon(Icons.check, color: successGreen, size: 18),
|
||||
// const SizedBox(width: 6),
|
||||
// Text(
|
||||
// 'Person Information:',
|
||||
// style: tt.titleSmall?.copyWith(
|
||||
// fontWeight: FontWeight.bold,
|
||||
// color: successGreen,
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// const SizedBox(height: 12),
|
||||
//
|
||||
// // ── Full Name (two visual lines: first name + last name) ──────────
|
||||
// _DetailRow(
|
||||
// label: 'Full Name:',
|
||||
// value: result.firstName,
|
||||
// valueBold: true,
|
||||
// ),
|
||||
// if (result.lastName.isNotEmpty) ...[
|
||||
// const SizedBox(height: 2),
|
||||
// Padding(
|
||||
// padding: const EdgeInsets.only(bottom: 8),
|
||||
// child: Text(
|
||||
// result.lastName,
|
||||
// style: tt.bodyMedium?.copyWith(fontWeight: FontWeight.bold),
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
//
|
||||
// _DetailRow(label: 'Father:', value: result.fatherName),
|
||||
// _DetailRow(label: 'Grandfather:', value: result.grandfatherName),
|
||||
// _DetailRow(label: 'Occupation:', value: result.occupation),
|
||||
// _DetailRow(label: 'Gender:', value: result.gender),
|
||||
// _DetailRow(label: 'Sponsor:', value: result.sponsor),
|
||||
//
|
||||
// const SizedBox(height: 4),
|
||||
//
|
||||
// _DetailRow(label: 'ID Number:', value: result.idNumber),
|
||||
// _DetailRow(label: 'Birth Date:', value: result.birthDate),
|
||||
// _DetailRow(label: 'Nationality:', value: result.nationality),
|
||||
// ],
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // ── Error section ──────────────────────────────────────────────────────────────
|
||||
//
|
||||
// class _ErrorSection extends StatelessWidget {
|
||||
// final AuthResultUiModel result;
|
||||
//
|
||||
// const _ErrorSection({required this.result});
|
||||
//
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// final tt = Theme.of(context).textTheme;
|
||||
// final errorColor = Theme.of(context).colorScheme.error;
|
||||
//
|
||||
// return Column(
|
||||
// crossAxisAlignment: CrossAxisAlignment.start,
|
||||
// children: [
|
||||
// // Section header
|
||||
// Row(
|
||||
// children: [
|
||||
// Icon(Icons.close, color: errorColor, size: 18),
|
||||
// const SizedBox(width: 6),
|
||||
// Text(
|
||||
// 'Error:',
|
||||
// style: tt.titleSmall?.copyWith(
|
||||
// fontWeight: FontWeight.bold,
|
||||
// color: errorColor,
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// const SizedBox(height: 12),
|
||||
// _DetailRow(label: 'Type:', value: result.errorType ?? ''),
|
||||
// _DetailRow(label: 'Message:', value: result.errorMessage ?? ''),
|
||||
// ],
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // ── Reusable detail row (label + value) ────────────────────────────────────────
|
||||
//
|
||||
// class _DetailRow extends StatelessWidget {
|
||||
// final String label;
|
||||
// final String value;
|
||||
// final bool valueBold;
|
||||
//
|
||||
// const _DetailRow({
|
||||
// required this.label,
|
||||
// required this.value,
|
||||
// this.valueBold = false,
|
||||
// });
|
||||
//
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// final tt = Theme.of(context).textTheme;
|
||||
//
|
||||
// return Padding(
|
||||
// padding: const EdgeInsets.only(bottom: 8),
|
||||
// child: Column(
|
||||
// crossAxisAlignment: CrossAxisAlignment.start,
|
||||
// children: [
|
||||
// Text(
|
||||
// label,
|
||||
// style: tt.bodySmall?.copyWith(
|
||||
// fontWeight: FontWeight.bold,
|
||||
// color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
// ),
|
||||
// ),
|
||||
// const SizedBox(height: 2),
|
||||
// Text(
|
||||
// value.isEmpty ? '—' : value,
|
||||
// style: tt.bodyMedium?.copyWith(
|
||||
// fontWeight: valueBold ? FontWeight.bold : FontWeight.normal,
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
@ -0,0 +1,276 @@
|
||||
import 'package:finger_print_verification/theme/app_theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import '../models/auth_result_ui_model.dart';
|
||||
|
||||
/// ─────────────────────────────────────────────────────────────────────────
|
||||
/// Shared, public building blocks for rendering an [AuthResultUiModel].
|
||||
///
|
||||
/// These used to live as private widgets inside `auth_result_screen.dart`.
|
||||
/// They're now public and standalone so the SAME widgets can be reused:
|
||||
/// • full-screen, on phones, via [AuthResultScreen] (pushed route)
|
||||
/// • inline, on tablets, embedded directly in [FingerprintView]'s result
|
||||
/// pane — no navigation needed, the result just appears next to the form.
|
||||
/// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
class ResultStatusBanner extends StatelessWidget {
|
||||
final AuthResultUiModel result;
|
||||
const ResultStatusBanner({super.key, required this.result});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final accent = result.isSuccess ? AppTheme.saudiGreen : AppTheme.redAlert;
|
||||
final message = result.statusMessage.isEmpty
|
||||
? (result.isSuccess ? 'Authentication successful!' : 'Authentication failed')
|
||||
: result.statusMessage;
|
||||
|
||||
return GlowPanel(
|
||||
glowColor: accent,
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 52,
|
||||
height: 52,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: accent.withValues(alpha: 0.12),
|
||||
border: Border.all(color: accent.withValues(alpha: 0.4)),
|
||||
),
|
||||
child: Icon(
|
||||
result.isSuccess ? Icons.check_circle_outline : Icons.cancel_outlined,
|
||||
color: accent,
|
||||
size: 28,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'STATUS',
|
||||
style: TextStyle(
|
||||
color: AppTheme.textMuted,
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w700,
|
||||
letterSpacing: 1.1,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
message,
|
||||
style: TextStyle(color: accent, fontWeight: FontWeight.w700, fontSize: 16),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ResultPersonInfoPanel extends StatelessWidget {
|
||||
final AuthResultUiModel result;
|
||||
final bool twoColumn;
|
||||
const ResultPersonInfoPanel({super.key, required this.result, this.twoColumn = false});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final fullName = [result.firstName, result.lastName].where((s) => s.isNotEmpty).join(' ');
|
||||
|
||||
final rows = <ResultDetailRow>[
|
||||
ResultDetailRow(label: 'Father', value: result.fatherName, icon: Icons.man_outlined),
|
||||
ResultDetailRow(label: 'Grandfather', value: result.grandfatherName, icon: Icons.elderly_outlined),
|
||||
ResultDetailRow(label: 'Occupation', value: result.occupation, icon: Icons.work_outline),
|
||||
ResultDetailRow(label: 'Gender', value: result.gender, icon: Icons.wc_outlined),
|
||||
ResultDetailRow(label: 'Sponsor', value: result.sponsor, icon: Icons.shield_outlined),
|
||||
ResultDetailRow(label: 'ID Number', value: result.idNumber, icon: Icons.badge_outlined),
|
||||
ResultDetailRow(label: 'Birth Date', value: result.birthDate, icon: Icons.cake_outlined),
|
||||
ResultDetailRow(label: 'Nationality', value: result.nationality, icon: Icons.flag_outlined),
|
||||
];
|
||||
|
||||
return GlowPanel(
|
||||
glowColor: AppTheme.saudiGreen,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 44,
|
||||
height: 44,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: AppTheme.saudiGreen.withValues(alpha: 0.12),
|
||||
border: Border.all(color: AppTheme.saudiGreen.withValues(alpha: 0.4)),
|
||||
),
|
||||
child: const Icon(Icons.person_outline, color: AppTheme.saudiGreen, size: 22),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'PERSON INFORMATION',
|
||||
style: TextStyle(
|
||||
color: AppTheme.saudiGreen,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w700,
|
||||
letterSpacing: 1.0,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
fullName.isEmpty ? '—' : fullName,
|
||||
style: const TextStyle(
|
||||
color: AppTheme.textPrimary,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const Divider(height: 1),
|
||||
const SizedBox(height: 8),
|
||||
if (twoColumn)
|
||||
GridView.count(
|
||||
crossAxisCount: 2,
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
mainAxisSpacing: 4,
|
||||
crossAxisSpacing: 16,
|
||||
childAspectRatio: 5.4,
|
||||
children: rows,
|
||||
)
|
||||
else
|
||||
Column(children: rows),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ResultErrorPanel extends StatelessWidget {
|
||||
final AuthResultUiModel result;
|
||||
const ResultErrorPanel({super.key, required this.result});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GlowPanel(
|
||||
glowColor: AppTheme.redAlert,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 44,
|
||||
height: 44,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: AppTheme.redAlert.withValues(alpha: 0.12),
|
||||
border: Border.all(color: AppTheme.redAlert.withValues(alpha: 0.4)),
|
||||
),
|
||||
child: const Icon(Icons.error_outline, color: AppTheme.redAlert, size: 22),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
const Text(
|
||||
'ERROR DETAILS',
|
||||
style: TextStyle(
|
||||
color: AppTheme.redAlert,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w700,
|
||||
letterSpacing: 1.0,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const Divider(height: 1),
|
||||
const SizedBox(height: 8),
|
||||
ResultDetailRow(label: 'Type', value: result.errorType ?? '', icon: Icons.category_outlined),
|
||||
ResultDetailRow(label: 'Message', value: result.errorMessage ?? '', icon: Icons.notes_outlined),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Reusable detail row (icon + label + value).
|
||||
class ResultDetailRow extends StatelessWidget {
|
||||
final String label;
|
||||
final String value;
|
||||
final IconData icon;
|
||||
|
||||
const ResultDetailRow({super.key, required this.label, required this.value, required this.icon});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Icon(icon, size: 16, color: AppTheme.textMuted),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppTheme.textMuted,
|
||||
letterSpacing: 0.4,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
value.isEmpty ? '—' : value,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: AppTheme.textPrimary,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Full result body (banner + person-info-or-error), used by both the
|
||||
/// pushed phone route and the inline tablet pane.
|
||||
class ResultBody extends StatelessWidget {
|
||||
final AuthResultUiModel result;
|
||||
final bool twoColumn;
|
||||
const ResultBody({super.key, required this.result, this.twoColumn = false});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
ResultStatusBanner(result: result),
|
||||
const SizedBox(height: 16),
|
||||
if (result.isSuccess) ResultPersonInfoPanel(result: result, twoColumn: twoColumn),
|
||||
if (!result.isSuccess) ResultErrorPanel(result: result),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
import 'package:finger_print_verification/core/network/network.dart';
|
||||
import 'package:finger_print_verification/fingerprint/view/fingerprint.dart';
|
||||
import 'package:finger_print_verification/theme/app_theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
void main() {
|
||||
// ── Configure the global API client BEFORE runApp() ───────────────────────
|
||||
// All relative URLs (e.g. '/verify') will be resolved against this base URL.
|
||||
// Replace the placeholder with your real endpoint before going to production.
|
||||
ApiClient.initialize(
|
||||
baseUrl: 'https://hmgpharmacyapi.hmg.com/fingerprint/',
|
||||
connectTimeout: const Duration(seconds: 60),
|
||||
receiveTimeout: const Duration(seconds: 30),
|
||||
interceptors: [
|
||||
LogInterceptor(
|
||||
requestBody: true,
|
||||
responseBody: true,
|
||||
logPrint: (object) => debugPrint(object.toString()), // Recommended for Flutter
|
||||
),
|
||||
]
|
||||
);
|
||||
|
||||
runApp(const MyApp());
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
const MyApp({super.key});
|
||||
|
||||
// This widget is the root of your application.
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'Fingerprint Verification',
|
||||
theme:AppTheme.light(),
|
||||
home: const FingerprintView(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,256 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// ─────────────────────────────────────────────────────────────────────────
|
||||
/// "Futuristic Hospital — Dr. Sulaiman Al Habib (HMG)" design tokens.
|
||||
///
|
||||
/// LIGHT MODE variant: clean white clinical surfaces with HMG red (#D02127)
|
||||
/// as the single brand accent — used for primary actions, focus states,
|
||||
/// active scanner glow, and status highlights. Panels are white cards with
|
||||
/// soft red-tinted shadows/borders instead of the previous dark glass look,
|
||||
/// keeping a crisp, futuristic-kiosk feel while staying bright and readable.
|
||||
///
|
||||
/// Drop this file in lib/theme/app_theme.dart and wrap MaterialApp with
|
||||
/// `theme: AppTheme.light()`.
|
||||
/// ─────────────────────────────────────────────────────────────────────────
|
||||
class AppTheme {
|
||||
AppTheme._();
|
||||
|
||||
// Core brand colors — HMG red & white
|
||||
static const Color red = Color(0xFFD02127); // HMG primary brand red
|
||||
static const Color redDark = Color(0xFFA8191F); // pressed / deep variant
|
||||
static const Color redTint = Color(0xFFFCE9EA); // faint red surface tint
|
||||
|
||||
static const Color background = Color(0xFFFFFFFF); // page background
|
||||
static const Color surface = Color(0xFFFFFFFF); // card surfaces
|
||||
static const Color surfaceMuted = Color(0xFFF7F4F4); // sunken fields, chips
|
||||
|
||||
static const Color saudiGreen = Color(0xFF1E9E6B); // success / verified (semantic only)
|
||||
static const Color amberAlert = Color(0xFFC8821A); // pending / connecting
|
||||
static const Color redAlert = Color(0xFFD02127); // error reuses brand red
|
||||
|
||||
static const Color textPrimary = Color(0xFF1A1213);
|
||||
static const Color textMuted = Color(0xFF7A6C6D);
|
||||
static const Color hairline = Color(0xFFE7DCDD);
|
||||
|
||||
// Backward-compatible aliases — anywhere older screen code references
|
||||
// AppTheme.cyan / AppTheme.gold / AppTheme.ink*, it now resolves to the
|
||||
// light-mode equivalents so existing screens compile unchanged.
|
||||
static const Color cyan = red;
|
||||
static const Color gold = red;
|
||||
static const Color ink = background;
|
||||
static const Color inkRaised = surface;
|
||||
static const Color inkRaised2 = surfaceMuted;
|
||||
|
||||
static ThemeData light() {
|
||||
final scheme = ColorScheme.fromSeed(
|
||||
seedColor: red,
|
||||
brightness: Brightness.light,
|
||||
primary: red,
|
||||
secondary: redDark,
|
||||
error: redAlert,
|
||||
surface: surface,
|
||||
).copyWith(
|
||||
surfaceContainerLowest: background,
|
||||
surfaceContainerLow: surface,
|
||||
surfaceContainer: surfaceMuted,
|
||||
surfaceContainerHigh: surfaceMuted,
|
||||
outline: hairline,
|
||||
outlineVariant: hairline,
|
||||
onSurface: textPrimary,
|
||||
onSurfaceVariant: textMuted,
|
||||
onPrimary: Colors.white,
|
||||
);
|
||||
|
||||
return ThemeData(
|
||||
useMaterial3: true,
|
||||
brightness: Brightness.light,
|
||||
colorScheme: scheme,
|
||||
scaffoldBackgroundColor: background,
|
||||
fontFamily: 'Roboto',
|
||||
textTheme: const TextTheme().apply(
|
||||
bodyColor: textPrimary,
|
||||
displayColor: textPrimary,
|
||||
),
|
||||
appBarTheme: const AppBarTheme(
|
||||
backgroundColor: background,
|
||||
surfaceTintColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
foregroundColor: textPrimary,
|
||||
titleTextStyle: TextStyle(
|
||||
color: textPrimary,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w700,
|
||||
letterSpacing: 0.2,
|
||||
),
|
||||
),
|
||||
cardTheme: CardThemeData(
|
||||
color: surface,
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
side: const BorderSide(color: hairline),
|
||||
),
|
||||
),
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
filled: true,
|
||||
fillColor: surfaceMuted,
|
||||
labelStyle: const TextStyle(color: textMuted),
|
||||
hintStyle: const TextStyle(color: textMuted),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
borderSide: const BorderSide(color: hairline),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
borderSide: const BorderSide(color: hairline),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
borderSide: const BorderSide(color: red, width: 1.4),
|
||||
),
|
||||
errorBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
borderSide: const BorderSide(color: redAlert),
|
||||
),
|
||||
),
|
||||
filledButtonTheme: FilledButtonThemeData(
|
||||
style: FilledButton.styleFrom(
|
||||
backgroundColor: red,
|
||||
foregroundColor: Colors.white,
|
||||
minimumSize: const Size.fromHeight(54),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||
textStyle: const TextStyle(fontWeight: FontWeight.w700, fontSize: 15),
|
||||
),
|
||||
),
|
||||
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: surfaceMuted,
|
||||
foregroundColor: textPrimary,
|
||||
elevation: 0,
|
||||
minimumSize: const Size.fromHeight(54),
|
||||
side: const BorderSide(color: hairline),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||
textStyle: const TextStyle(fontWeight: FontWeight.w700, fontSize: 15),
|
||||
),
|
||||
),
|
||||
dividerTheme: const DividerThemeData(color: hairline, thickness: 1),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// A reusable card-style panel with a soft red-tinted shadow/border — the
|
||||
/// signature surface treatment of this light theme. Replaces the old dark
|
||||
/// "glow panel"; same API so existing screens compile unchanged.
|
||||
class GlowPanel extends StatelessWidget {
|
||||
final Widget child;
|
||||
final Color glowColor;
|
||||
final EdgeInsetsGeometry padding;
|
||||
final double borderRadius;
|
||||
|
||||
const GlowPanel({
|
||||
super.key,
|
||||
required this.child,
|
||||
this.glowColor = AppTheme.red,
|
||||
this.padding = const EdgeInsets.all(20),
|
||||
this.borderRadius = 20,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: padding,
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.surface,
|
||||
borderRadius: BorderRadius.circular(borderRadius),
|
||||
border: Border.all(color: glowColor.withValues(alpha: 0.22)),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: glowColor.withValues(alpha: 0.10),
|
||||
blurRadius: 20,
|
||||
offset: const Offset(0, 6),
|
||||
),
|
||||
const BoxShadow(
|
||||
color: Color(0x0A1A1213),
|
||||
blurRadius: 12,
|
||||
offset: Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Subtle hex/grid backdrop to reinforce the "med-tech kiosk" feel without
|
||||
/// hurting readability. Cheap to render — pure CustomPaint, no images.
|
||||
/// In light mode this is a very faint red grid on white.
|
||||
class TechGridBackground extends StatelessWidget {
|
||||
final Widget child;
|
||||
const TechGridBackground({super.key, required this.child});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
gradient: RadialGradient(
|
||||
center: const Alignment(0.9, -0.9),
|
||||
radius: 1.4,
|
||||
colors: [
|
||||
AppTheme.red.withValues(alpha: 0.05),
|
||||
AppTheme.background,
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned.fill(child: CustomPaint(painter: _GridPainter())),
|
||||
child,
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _GridPainter extends CustomPainter {
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final paint = Paint()
|
||||
..color = AppTheme.red.withValues(alpha: 0.025)
|
||||
..strokeWidth = 1;
|
||||
const step = 36.0;
|
||||
for (double x = 0; x < size.width; x += step) {
|
||||
canvas.drawLine(Offset(x, 0), Offset(x, size.height), paint);
|
||||
}
|
||||
for (double y = 0; y < size.height; y += step) {
|
||||
canvas.drawLine(Offset(0, y), Offset(size.width, y), paint);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
|
||||
}
|
||||
|
||||
/// Centers content and clamps width on tablets/desktop so forms don't
|
||||
/// stretch edge-to-edge on large screens.
|
||||
class ResponsiveCenter extends StatelessWidget {
|
||||
final Widget child;
|
||||
final double maxWidth;
|
||||
const ResponsiveCenter({super.key, required this.child, this.maxWidth = 760});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(maxWidth: maxWidth),
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Breakpoint helper.
|
||||
bool isTablet(BuildContext context) =>
|
||||
MediaQuery.sizeOf(context).shortestSide >= 600;
|
||||
@ -0,0 +1 @@
|
||||
flutter/ephemeral
|
||||
@ -0,0 +1,128 @@
|
||||
# Project-level configuration.
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
project(runner LANGUAGES CXX)
|
||||
|
||||
# The name of the executable created for the application. Change this to change
|
||||
# the on-disk name of your application.
|
||||
set(BINARY_NAME "finger_print_verification")
|
||||
# The unique GTK application identifier for this application. See:
|
||||
# https://wiki.gnome.org/HowDoI/ChooseApplicationID
|
||||
set(APPLICATION_ID "com.cloudsolutions.finger_print_verification")
|
||||
|
||||
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
|
||||
# versions of CMake.
|
||||
cmake_policy(SET CMP0063 NEW)
|
||||
|
||||
# Load bundled libraries from the lib/ directory relative to the binary.
|
||||
set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
|
||||
|
||||
# Root filesystem for cross-building.
|
||||
if(FLUTTER_TARGET_PLATFORM_SYSROOT)
|
||||
set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT})
|
||||
set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||
endif()
|
||||
|
||||
# Define build configuration options.
|
||||
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
||||
set(CMAKE_BUILD_TYPE "Debug" CACHE
|
||||
STRING "Flutter build mode" FORCE)
|
||||
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
|
||||
"Debug" "Profile" "Release")
|
||||
endif()
|
||||
|
||||
# Compilation settings that should be applied to most targets.
|
||||
#
|
||||
# Be cautious about adding new options here, as plugins use this function by
|
||||
# default. In most cases, you should add new options to specific targets instead
|
||||
# of modifying this function.
|
||||
function(APPLY_STANDARD_SETTINGS TARGET)
|
||||
target_compile_features(${TARGET} PUBLIC cxx_std_14)
|
||||
target_compile_options(${TARGET} PRIVATE -Wall -Werror)
|
||||
target_compile_options(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:-O3>")
|
||||
target_compile_definitions(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:NDEBUG>")
|
||||
endfunction()
|
||||
|
||||
# Flutter library and tool build rules.
|
||||
set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
|
||||
add_subdirectory(${FLUTTER_MANAGED_DIR})
|
||||
|
||||
# System-level dependencies.
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
|
||||
|
||||
# Application build; see runner/CMakeLists.txt.
|
||||
add_subdirectory("runner")
|
||||
|
||||
# Run the Flutter tool portions of the build. This must not be removed.
|
||||
add_dependencies(${BINARY_NAME} flutter_assemble)
|
||||
|
||||
# Only the install-generated bundle's copy of the executable will launch
|
||||
# correctly, since the resources must in the right relative locations. To avoid
|
||||
# people trying to run the unbundled copy, put it in a subdirectory instead of
|
||||
# the default top-level location.
|
||||
set_target_properties(${BINARY_NAME}
|
||||
PROPERTIES
|
||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run"
|
||||
)
|
||||
|
||||
|
||||
# Generated plugin build rules, which manage building the plugins and adding
|
||||
# them to the application.
|
||||
include(flutter/generated_plugins.cmake)
|
||||
|
||||
|
||||
# === Installation ===
|
||||
# By default, "installing" just makes a relocatable bundle in the build
|
||||
# directory.
|
||||
set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle")
|
||||
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
|
||||
set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
|
||||
endif()
|
||||
|
||||
# Start with a clean build bundle directory every time.
|
||||
install(CODE "
|
||||
file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\")
|
||||
" COMPONENT Runtime)
|
||||
|
||||
set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
|
||||
set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib")
|
||||
|
||||
install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
|
||||
COMPONENT Runtime)
|
||||
|
||||
install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
|
||||
COMPONENT Runtime)
|
||||
|
||||
install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||
COMPONENT Runtime)
|
||||
|
||||
foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES})
|
||||
install(FILES "${bundled_library}"
|
||||
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||
COMPONENT Runtime)
|
||||
endforeach(bundled_library)
|
||||
|
||||
# Copy the native assets provided by the build.dart from all packages.
|
||||
set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/")
|
||||
install(DIRECTORY "${NATIVE_ASSETS_DIR}"
|
||||
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||
COMPONENT Runtime)
|
||||
|
||||
# Fully re-copy the assets directory on each build to avoid having stale files
|
||||
# from a previous install.
|
||||
set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
|
||||
install(CODE "
|
||||
file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
|
||||
" COMPONENT Runtime)
|
||||
install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
|
||||
DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
|
||||
|
||||
# Install the AOT library on non-Debug builds only.
|
||||
if(NOT CMAKE_BUILD_TYPE MATCHES "Debug")
|
||||
install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||
COMPONENT Runtime)
|
||||
endif()
|
||||
@ -0,0 +1,88 @@
|
||||
# This file controls Flutter-level build steps. It should not be edited.
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
|
||||
set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
|
||||
|
||||
# Configuration provided via flutter tool.
|
||||
include(${EPHEMERAL_DIR}/generated_config.cmake)
|
||||
|
||||
# TODO: Move the rest of this into files in ephemeral. See
|
||||
# https://github.com/flutter/flutter/issues/57146.
|
||||
|
||||
# Serves the same purpose as list(TRANSFORM ... PREPEND ...),
|
||||
# which isn't available in 3.10.
|
||||
function(list_prepend LIST_NAME PREFIX)
|
||||
set(NEW_LIST "")
|
||||
foreach(element ${${LIST_NAME}})
|
||||
list(APPEND NEW_LIST "${PREFIX}${element}")
|
||||
endforeach(element)
|
||||
set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# === Flutter Library ===
|
||||
# System-level dependencies.
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
|
||||
pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0)
|
||||
pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0)
|
||||
|
||||
set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so")
|
||||
|
||||
# Published to parent scope for install step.
|
||||
set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
|
||||
set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
|
||||
set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
|
||||
set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE)
|
||||
|
||||
list(APPEND FLUTTER_LIBRARY_HEADERS
|
||||
"fl_basic_message_channel.h"
|
||||
"fl_binary_codec.h"
|
||||
"fl_binary_messenger.h"
|
||||
"fl_dart_project.h"
|
||||
"fl_engine.h"
|
||||
"fl_json_message_codec.h"
|
||||
"fl_json_method_codec.h"
|
||||
"fl_message_codec.h"
|
||||
"fl_method_call.h"
|
||||
"fl_method_channel.h"
|
||||
"fl_method_codec.h"
|
||||
"fl_method_response.h"
|
||||
"fl_plugin_registrar.h"
|
||||
"fl_plugin_registry.h"
|
||||
"fl_standard_message_codec.h"
|
||||
"fl_standard_method_codec.h"
|
||||
"fl_string_codec.h"
|
||||
"fl_value.h"
|
||||
"fl_view.h"
|
||||
"flutter_linux.h"
|
||||
)
|
||||
list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/")
|
||||
add_library(flutter INTERFACE)
|
||||
target_include_directories(flutter INTERFACE
|
||||
"${EPHEMERAL_DIR}"
|
||||
)
|
||||
target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}")
|
||||
target_link_libraries(flutter INTERFACE
|
||||
PkgConfig::GTK
|
||||
PkgConfig::GLIB
|
||||
PkgConfig::GIO
|
||||
)
|
||||
add_dependencies(flutter flutter_assemble)
|
||||
|
||||
# === Flutter tool backend ===
|
||||
# _phony_ is a non-existent file to force this command to run every time,
|
||||
# since currently there's no way to get a full input/output list from the
|
||||
# flutter tool.
|
||||
add_custom_command(
|
||||
OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
|
||||
${CMAKE_CURRENT_BINARY_DIR}/_phony_
|
||||
COMMAND ${CMAKE_COMMAND} -E env
|
||||
${FLUTTER_TOOL_ENVIRONMENT}
|
||||
"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh"
|
||||
${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE}
|
||||
VERBATIM
|
||||
)
|
||||
add_custom_target(flutter_assemble DEPENDS
|
||||
"${FLUTTER_LIBRARY}"
|
||||
${FLUTTER_LIBRARY_HEADERS}
|
||||
)
|
||||
@ -0,0 +1,11 @@
|
||||
//
|
||||
// Generated file. Do not edit.
|
||||
//
|
||||
|
||||
// clang-format off
|
||||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
|
||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
//
|
||||
// Generated file. Do not edit.
|
||||
//
|
||||
|
||||
// clang-format off
|
||||
|
||||
#ifndef GENERATED_PLUGIN_REGISTRANT_
|
||||
#define GENERATED_PLUGIN_REGISTRANT_
|
||||
|
||||
#include <flutter_linux/flutter_linux.h>
|
||||
|
||||
// Registers Flutter plugins.
|
||||
void fl_register_plugins(FlPluginRegistry* registry);
|
||||
|
||||
#endif // GENERATED_PLUGIN_REGISTRANT_
|
||||
@ -0,0 +1,23 @@
|
||||
#
|
||||
# Generated file, do not edit.
|
||||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
)
|
||||
|
||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||
|
||||
foreach(plugin ${FLUTTER_PLUGIN_LIST})
|
||||
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin})
|
||||
target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
|
||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
|
||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
|
||||
endforeach(plugin)
|
||||
|
||||
foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
|
||||
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin})
|
||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
|
||||
endforeach(ffi_plugin)
|
||||
@ -0,0 +1,26 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
project(runner LANGUAGES CXX)
|
||||
|
||||
# Define the application target. To change its name, change BINARY_NAME in the
|
||||
# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer
|
||||
# work.
|
||||
#
|
||||
# Any new source files that you add to the application should be added here.
|
||||
add_executable(${BINARY_NAME}
|
||||
"main.cc"
|
||||
"my_application.cc"
|
||||
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
|
||||
)
|
||||
|
||||
# Apply the standard set of build settings. This can be removed for applications
|
||||
# that need different build settings.
|
||||
apply_standard_settings(${BINARY_NAME})
|
||||
|
||||
# Add preprocessor definitions for the application ID.
|
||||
add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}")
|
||||
|
||||
# Add dependency libraries. Add any application-specific dependencies here.
|
||||
target_link_libraries(${BINARY_NAME} PRIVATE flutter)
|
||||
target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK)
|
||||
|
||||
target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
|
||||
@ -0,0 +1,6 @@
|
||||
#include "my_application.h"
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
g_autoptr(MyApplication) app = my_application_new();
|
||||
return g_application_run(G_APPLICATION(app), argc, argv);
|
||||
}
|
||||
@ -0,0 +1,130 @@
|
||||
#include "my_application.h"
|
||||
|
||||
#include <flutter_linux/flutter_linux.h>
|
||||
#ifdef GDK_WINDOWING_X11
|
||||
#include <gdk/gdkx.h>
|
||||
#endif
|
||||
|
||||
#include "flutter/generated_plugin_registrant.h"
|
||||
|
||||
struct _MyApplication {
|
||||
GtkApplication parent_instance;
|
||||
char** dart_entrypoint_arguments;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
|
||||
|
||||
// Implements GApplication::activate.
|
||||
static void my_application_activate(GApplication* application) {
|
||||
MyApplication* self = MY_APPLICATION(application);
|
||||
GtkWindow* window =
|
||||
GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
|
||||
|
||||
// Use a header bar when running in GNOME as this is the common style used
|
||||
// by applications and is the setup most users will be using (e.g. Ubuntu
|
||||
// desktop).
|
||||
// If running on X and not using GNOME then just use a traditional title bar
|
||||
// in case the window manager does more exotic layout, e.g. tiling.
|
||||
// If running on Wayland assume the header bar will work (may need changing
|
||||
// if future cases occur).
|
||||
gboolean use_header_bar = TRUE;
|
||||
#ifdef GDK_WINDOWING_X11
|
||||
GdkScreen* screen = gtk_window_get_screen(window);
|
||||
if (GDK_IS_X11_SCREEN(screen)) {
|
||||
const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen);
|
||||
if (g_strcmp0(wm_name, "GNOME Shell") != 0) {
|
||||
use_header_bar = FALSE;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (use_header_bar) {
|
||||
GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
|
||||
gtk_widget_show(GTK_WIDGET(header_bar));
|
||||
gtk_header_bar_set_title(header_bar, "finger_print_verification");
|
||||
gtk_header_bar_set_show_close_button(header_bar, TRUE);
|
||||
gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
|
||||
} else {
|
||||
gtk_window_set_title(window, "finger_print_verification");
|
||||
}
|
||||
|
||||
gtk_window_set_default_size(window, 1280, 720);
|
||||
gtk_widget_show(GTK_WIDGET(window));
|
||||
|
||||
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
||||
fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments);
|
||||
|
||||
FlView* view = fl_view_new(project);
|
||||
gtk_widget_show(GTK_WIDGET(view));
|
||||
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
|
||||
|
||||
fl_register_plugins(FL_PLUGIN_REGISTRY(view));
|
||||
|
||||
gtk_widget_grab_focus(GTK_WIDGET(view));
|
||||
}
|
||||
|
||||
// Implements GApplication::local_command_line.
|
||||
static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) {
|
||||
MyApplication* self = MY_APPLICATION(application);
|
||||
// Strip out the first argument as it is the binary name.
|
||||
self->dart_entrypoint_arguments = g_strdupv(*arguments + 1);
|
||||
|
||||
g_autoptr(GError) error = nullptr;
|
||||
if (!g_application_register(application, nullptr, &error)) {
|
||||
g_warning("Failed to register: %s", error->message);
|
||||
*exit_status = 1;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
g_application_activate(application);
|
||||
*exit_status = 0;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// Implements GApplication::startup.
|
||||
static void my_application_startup(GApplication* application) {
|
||||
//MyApplication* self = MY_APPLICATION(object);
|
||||
|
||||
// Perform any actions required at application startup.
|
||||
|
||||
G_APPLICATION_CLASS(my_application_parent_class)->startup(application);
|
||||
}
|
||||
|
||||
// Implements GApplication::shutdown.
|
||||
static void my_application_shutdown(GApplication* application) {
|
||||
//MyApplication* self = MY_APPLICATION(object);
|
||||
|
||||
// Perform any actions required at application shutdown.
|
||||
|
||||
G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application);
|
||||
}
|
||||
|
||||
// Implements GObject::dispose.
|
||||
static void my_application_dispose(GObject* object) {
|
||||
MyApplication* self = MY_APPLICATION(object);
|
||||
g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev);
|
||||
G_OBJECT_CLASS(my_application_parent_class)->dispose(object);
|
||||
}
|
||||
|
||||
static void my_application_class_init(MyApplicationClass* klass) {
|
||||
G_APPLICATION_CLASS(klass)->activate = my_application_activate;
|
||||
G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line;
|
||||
G_APPLICATION_CLASS(klass)->startup = my_application_startup;
|
||||
G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown;
|
||||
G_OBJECT_CLASS(klass)->dispose = my_application_dispose;
|
||||
}
|
||||
|
||||
static void my_application_init(MyApplication* self) {}
|
||||
|
||||
MyApplication* my_application_new() {
|
||||
// Set the program name to the application ID, which helps various systems
|
||||
// like GTK and desktop environments map this running application to its
|
||||
// corresponding .desktop file. This ensures better integration by allowing
|
||||
// the application to be recognized beyond its binary name.
|
||||
g_set_prgname(APPLICATION_ID);
|
||||
|
||||
return MY_APPLICATION(g_object_new(my_application_get_type(),
|
||||
"application-id", APPLICATION_ID,
|
||||
"flags", G_APPLICATION_NON_UNIQUE,
|
||||
nullptr));
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
#ifndef FLUTTER_MY_APPLICATION_H_
|
||||
#define FLUTTER_MY_APPLICATION_H_
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION,
|
||||
GtkApplication)
|
||||
|
||||
/**
|
||||
* my_application_new:
|
||||
*
|
||||
* Creates a new Flutter-based application.
|
||||
*
|
||||
* Returns: a new #MyApplication.
|
||||
*/
|
||||
MyApplication* my_application_new();
|
||||
|
||||
#endif // FLUTTER_MY_APPLICATION_H_
|
||||
@ -0,0 +1,7 @@
|
||||
# Flutter-related
|
||||
**/Flutter/ephemeral/
|
||||
**/Pods/
|
||||
|
||||
# Xcode-related
|
||||
**/dgph
|
||||
**/xcuserdata/
|
||||
@ -0,0 +1,2 @@
|
||||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
|
||||
#include "ephemeral/Flutter-Generated.xcconfig"
|
||||
@ -0,0 +1,2 @@
|
||||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
|
||||
#include "ephemeral/Flutter-Generated.xcconfig"
|
||||
@ -0,0 +1,12 @@
|
||||
//
|
||||
// Generated file. Do not edit.
|
||||
//
|
||||
|
||||
import FlutterMacOS
|
||||
import Foundation
|
||||
|
||||
import connectivity_plus
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin"))
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
platform :osx, '10.15'
|
||||
|
||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||
|
||||
project 'Runner', {
|
||||
'Debug' => :debug,
|
||||
'Profile' => :release,
|
||||
'Release' => :release,
|
||||
}
|
||||
|
||||
def flutter_root
|
||||
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__)
|
||||
unless File.exist?(generated_xcode_build_settings_path)
|
||||
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first"
|
||||
end
|
||||
|
||||
File.foreach(generated_xcode_build_settings_path) do |line|
|
||||
matches = line.match(/FLUTTER_ROOT\=(.*)/)
|
||||
return matches[1].strip if matches
|
||||
end
|
||||
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\""
|
||||
end
|
||||
|
||||
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
|
||||
|
||||
flutter_macos_podfile_setup
|
||||
|
||||
target 'Runner' do
|
||||
use_frameworks!
|
||||
use_modular_headers!
|
||||
|
||||
flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__))
|
||||
target 'RunnerTests' do
|
||||
inherit! :search_paths
|
||||
end
|
||||
end
|
||||
|
||||
post_install do |installer|
|
||||
installer.pods_project.targets.each do |target|
|
||||
flutter_additional_macos_build_settings(target)
|
||||
end
|
||||
end
|
||||