init
No files matched your search
@@ -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,36 @@
|
||||
# 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: "8495dee1fd4aacbe9de707e7581203232f591b2f"
|
||||
channel: "stable"
|
||||
|
||||
project_type: app
|
||||
|
||||
# Tracks metadata for the flutter migrate command
|
||||
migration:
|
||||
platforms:
|
||||
- platform: root
|
||||
create_revision: 8495dee1fd4aacbe9de707e7581203232f591b2f
|
||||
base_revision: 8495dee1fd4aacbe9de707e7581203232f591b2f
|
||||
- platform: android
|
||||
create_revision: 8495dee1fd4aacbe9de707e7581203232f591b2f
|
||||
base_revision: 8495dee1fd4aacbe9de707e7581203232f591b2f
|
||||
- platform: ios
|
||||
create_revision: 8495dee1fd4aacbe9de707e7581203232f591b2f
|
||||
base_revision: 8495dee1fd4aacbe9de707e7581203232f591b2f
|
||||
- platform: macos
|
||||
create_revision: 8495dee1fd4aacbe9de707e7581203232f591b2f
|
||||
base_revision: 8495dee1fd4aacbe9de707e7581203232f591b2f
|
||||
|
||||
# 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 @@
|
||||
# pj_supermarket_cloud
|
||||
|
||||
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,80 @@
|
||||
plugins {
|
||||
id "com.android.application"
|
||||
id "kotlin-android"
|
||||
id "dev.flutter.flutter-gradle-plugin"
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.example.pj_supermarket_cloud"
|
||||
compileSdk 35
|
||||
ndkVersion = flutter.ndkVersion
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main() {
|
||||
jniLibs.srcDirs = ['src/main/jniLibs']
|
||||
}
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId = "com.example.pj_supermarket_cloud"
|
||||
// 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
|
||||
ndk {
|
||||
abiFilters 'arm64-v8a'
|
||||
}
|
||||
}
|
||||
|
||||
dataBinding {
|
||||
enabled = true
|
||||
}
|
||||
|
||||
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
|
||||
shrinkResources true
|
||||
minifyEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
buildToolsVersion '29.0.2'
|
||||
}
|
||||
|
||||
flutter {
|
||||
source = "../.."
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
|
||||
// rxjava
|
||||
implementation 'io.reactivex.rxjava2:rxjava:2.2.9'
|
||||
implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
|
||||
|
||||
implementation 'androidx.preference:preference:1.1.1'
|
||||
|
||||
implementation 'com.google.code.gson:gson:2.10.1'
|
||||
|
||||
implementation 'com.tencent.bugly:crashreport:4.1.9.3'
|
||||
implementation 'com.google.code.gson:gson:2.8.6'
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
implementation 'com.google.android.material:material:1.10.0'
|
||||
implementation 'androidx.activity:activity:1.8.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
|
||||
# -keep class com.cash.cash_register_android.** { *; }
|
||||
# -keep class io.flutter.plugins.** { *; }
|
||||
-keep class com.arcsoft.face.** { *; }
|
||||
-keep class com.iflytek.**{*;}
|
||||
-keepattributes Signature
|
||||
@@ -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,59 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.USB_PERMISSION" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
||||
<uses-permission android:name="android.permission.SYSTEM_OVERLAY_WINDOW" />
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.TTS_SERVICE" />
|
||||
</intent>
|
||||
</queries>
|
||||
<application
|
||||
android:label="pj_supermarket_cloud"
|
||||
android:name="${applicationName}"
|
||||
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">
|
||||
<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"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="io.flutter.plugins.facediscern.ui.FaceDiscern"
|
||||
android:exported="false"
|
||||
android:hardwareAccelerated="true"
|
||||
android:launchMode="singleTop"
|
||||
android:theme="@style/LaunchTheme" />
|
||||
|
||||
<meta-data
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
</application>
|
||||
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
||||
<data android:mimeType="text/plain"/>
|
||||
</intent>
|
||||
</queries>
|
||||
</manifest>
|
||||
@@ -0,0 +1,104 @@
|
||||
package io.flutter.plugins.facediscern.database;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteStatement;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import io.flutter.Log;
|
||||
import io.flutter.plugins.facediscern.tool.face.model.FaceEntity;
|
||||
|
||||
public class DataBaseUtils {
|
||||
|
||||
private static final String TAG = "android数据库";
|
||||
|
||||
static public List<FaceEntity> getUserFace(Context context, String queryUserId) {
|
||||
List<FaceEntity> result = new ArrayList<>();
|
||||
int rowCount = 0;
|
||||
try {
|
||||
DatabaseHelper databaseHelper = new DatabaseHelper(context);
|
||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||
String sql = "SELECT * FROM face";
|
||||
Cursor cursor = database.rawQuery(sql, null);
|
||||
if (Objects.nonNull(queryUserId) && !Objects.equals(queryUserId, "")) {
|
||||
sql = sql.concat(" WHERE userId = ?");
|
||||
cursor = database.rawQuery(sql, new String[]{queryUserId});
|
||||
}
|
||||
Log.i(TAG, "getUserFace,sql:" + sql + ";queryUserId:" + queryUserId + ";");
|
||||
rowCount = cursor.getCount();
|
||||
while (cursor.moveToNext()) {
|
||||
int userIdIndex = cursor.getColumnIndex("userId");
|
||||
int sampleIdIndex = cursor.getColumnIndex("sampleId");
|
||||
int userNameIndex = cursor.getColumnIndex("userName");
|
||||
int pathIndex = cursor.getColumnIndex("path");
|
||||
int faceModelIndex = cursor.getColumnIndex("faceModel");
|
||||
FaceEntity item = new FaceEntity();
|
||||
if (userIdIndex != -1) {
|
||||
String userId = cursor.getString(userIdIndex);
|
||||
item.setUserId(userId);
|
||||
}
|
||||
if (sampleIdIndex != -1) {
|
||||
int sampleId = cursor.getInt(sampleIdIndex);
|
||||
item.setSampleId(sampleId);
|
||||
item.setFaceId(sampleId);
|
||||
}
|
||||
if (userNameIndex != -1) {
|
||||
String userName = cursor.getString(userNameIndex);
|
||||
item.setUserName(userName);
|
||||
}
|
||||
if (pathIndex != -1) {
|
||||
String faceFilePath = cursor.getString(pathIndex);
|
||||
item.setFaceFilePath(faceFilePath);
|
||||
}
|
||||
if (faceModelIndex != -1) {
|
||||
String faceModel = cursor.getString(faceModelIndex);
|
||||
if (faceModel != null && !Objects.equals(faceModel, "")) {
|
||||
item.setFaceModel(toByteArray(faceModel));
|
||||
}
|
||||
}
|
||||
result.add(item);
|
||||
}
|
||||
// 关闭资源
|
||||
cursor.close();
|
||||
databaseHelper.close();
|
||||
Log.i(TAG, "查询人脸数据库成功人脸总数:" + rowCount + "list长度:" + result.size() + ";");
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "查询数据库人脸错误error:" + e.getMessage(), e);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static public Boolean updateFaceModel(Context context, FaceEntity faceEntity) {
|
||||
try {
|
||||
DatabaseHelper databaseHelper = new DatabaseHelper(context);
|
||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||
String sqlUpdate = "UPDATE face SET faceModel = ? WHERE sampleId = ?";
|
||||
SQLiteStatement statement = database.compileStatement(sqlUpdate);
|
||||
statement.bindString(1, fromByteArray(faceEntity.getFaceModel()));
|
||||
statement.bindLong(2, faceEntity.getSampleId());
|
||||
int rowsAffected = statement.executeUpdateDelete();
|
||||
Log.i(TAG, "getUserFaceupdateFaceModel,sql:" + sqlUpdate + ";sampleId:" + faceEntity.getSampleId() + ";faceModel长度:" + faceEntity.getFaceModel().length + ";res:" + rowsAffected + ";");
|
||||
return rowsAffected > 0;
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "更新人脸模型失败username:" + faceEntity.getUserName() + ";id:" + faceEntity.getFaceId() + ";");
|
||||
Log.e(TAG, "更新人脸模型失败error:" + e.getMessage(), e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static byte[] toByteArray(String json) {
|
||||
return new Gson().fromJson(json, byte[].class);
|
||||
}
|
||||
|
||||
public static String fromByteArray(byte[] byteArray) {
|
||||
return new Gson().toJson(byteArray);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package io.flutter.plugins.facediscern.database;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.os.Environment;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
|
||||
public class DatabaseHelper extends SQLiteOpenHelper {
|
||||
private static final String TAG = "DatabaseHelper";
|
||||
private static final String DATABASE_NAME = "cash_register.db";
|
||||
private static final int DATABASE_VERSION = 1;
|
||||
|
||||
public DatabaseHelper(Context context) {
|
||||
super(context, getCustomDatabasePath(context), null, DATABASE_VERSION);
|
||||
}
|
||||
|
||||
private static String getCustomDatabasePath(Context context) {
|
||||
File documentsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS);
|
||||
File dbDir = new File(documentsDir, "pj/database");
|
||||
if (!dbDir.exists()) dbDir.mkdirs();
|
||||
return new File(dbDir, DATABASE_NAME).getAbsolutePath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase db) {
|
||||
Log.i(TAG, "onCreate");
|
||||
Cursor cursor = db.query("face", null, null, null, null, null, null);
|
||||
while (cursor.moveToNext()) {
|
||||
int id = cursor.getColumnIndex("id");
|
||||
Log.i(TAG, "查询到人脸数据库的数据id:" + id);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
onCreate(db);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
package io.flutter.plugins.facediscern.flutter;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import io.flutter.Log;
|
||||
import io.flutter.embedding.engine.plugins.FlutterPlugin;
|
||||
import io.flutter.plugin.common.EventChannel;
|
||||
import io.flutter.plugin.common.MethodCall;
|
||||
import io.flutter.plugin.common.MethodChannel;
|
||||
import io.flutter.plugins.facediscern.tool.face.constants.FaceBrodConstant;
|
||||
import io.flutter.plugins.facediscern.ui.FaceDiscern;
|
||||
|
||||
public class FlutterFaceDiscernPlugin implements FlutterPlugin, MethodChannel.MethodCallHandler, EventChannel.StreamHandler {
|
||||
|
||||
private static final String TAG = "人脸识别插件";
|
||||
|
||||
private static final String CHANNEL_TAG = "local/face_discern";
|
||||
private static final String EVENT_TAG = "local/face_discern_event";
|
||||
|
||||
public static MethodChannel staticChannel;
|
||||
|
||||
private Context context;
|
||||
private MethodChannel channel;
|
||||
|
||||
private EventChannel eventChannel;
|
||||
|
||||
private MethodChannel.Result pendingResult;
|
||||
|
||||
private FlutterFaceReceiver flutterFaceReceiver;
|
||||
|
||||
private BroadcastReceiver actionsReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
List<Integer> actions = intent.getIntegerArrayListExtra("actions");
|
||||
pendingResult.success(actions);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@Override
|
||||
public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
|
||||
context = binding.getApplicationContext();
|
||||
channel = new MethodChannel(binding.getBinaryMessenger(), CHANNEL_TAG);
|
||||
staticChannel = channel;
|
||||
channel.setMethodCallHandler(this);
|
||||
eventChannel = new EventChannel(binding.getBinaryMessenger(), EVENT_TAG);
|
||||
eventChannel.setStreamHandler(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
|
||||
channel.setMethodCallHandler(null);
|
||||
eventChannel.setStreamHandler(null);
|
||||
context.unregisterReceiver(actionsReceiver);
|
||||
}
|
||||
|
||||
// flutter 方法的回调
|
||||
@Override
|
||||
public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
|
||||
String method = call.method;
|
||||
Log.i(TAG, "执行方法:" + method + ";参数:" + new Gson().toJson(call.arguments) + ";");
|
||||
switch (method) {
|
||||
case "jumpFaceDiscern":
|
||||
try {
|
||||
Intent intent = new Intent(context, FaceDiscern.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
|
||||
context.startActivity(intent);
|
||||
result.success(true);
|
||||
} catch (Exception e) {
|
||||
result.success(false);
|
||||
Log.i(TAG, "jumpFaceDiscern error:" + e.getMessage(), e);
|
||||
}
|
||||
break;
|
||||
case "active":
|
||||
String appId = Objects.requireNonNull(call.argument("appId")).toString();
|
||||
String activeKey = Objects.requireNonNull(call.argument("activeKey")).toString();
|
||||
String sdkKey = Objects.requireNonNull(call.argument("sdkKey")).toString();
|
||||
Boolean active = FlutterTool.active(context, appId, activeKey, sdkKey);
|
||||
result.success(active);
|
||||
break;
|
||||
case "getConfig":
|
||||
result.success(FlutterTool.getConfig());
|
||||
break;
|
||||
case "registerAllStorageFace":
|
||||
String filePath = Objects.requireNonNull(call.argument("filePath")).toString();
|
||||
String fileName = Objects.requireNonNull(call.argument("fileName")).toString();
|
||||
Map<String, Object> res = FlutterTool.registerAllStorageFace(context, filePath, fileName);
|
||||
result.success(res);
|
||||
break;
|
||||
case "registerFaceByDatabase":
|
||||
String userId = call.argument("userId");
|
||||
List<Integer> errorSampleIdList = FlutterTool.registerFaceByDatabase(context, userId);
|
||||
result.success(errorSampleIdList);
|
||||
break;
|
||||
case "getFace":
|
||||
String sampleId = Objects.requireNonNull(call.argument("sampleId")).toString();
|
||||
Map<String, Object> face = FlutterTool.getFace(context, Integer.parseInt(sampleId));
|
||||
result.success(face);
|
||||
break;
|
||||
case "getFaceCount":
|
||||
int faceCount = FlutterTool.getFaceCount(context);
|
||||
result.success(faceCount);
|
||||
break;
|
||||
case "removeFace":
|
||||
String removeSampleId = Objects.requireNonNull(call.argument("sampleId")).toString();
|
||||
Boolean isRemove = FlutterTool.removeFace(context, Integer.parseInt(removeSampleId));
|
||||
result.success(isRemove);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// flutter回调监听
|
||||
@Override
|
||||
public void onListen(Object arguments, EventChannel.EventSink events) {
|
||||
Log.e(TAG, "flutter回调监听进入");
|
||||
try {
|
||||
IntentFilter intentFilter = new IntentFilter();
|
||||
intentFilter.addAction(FaceBrodConstant.FACE_GLOBAL_INTENT);
|
||||
flutterFaceReceiver = new FlutterFaceReceiver(events);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
context.registerReceiver(flutterFaceReceiver, intentFilter, Context.RECEIVER_EXPORTED);
|
||||
} else {
|
||||
context.registerReceiver(flutterFaceReceiver, intentFilter);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "flutter回调监听错误error:" + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancel(Object arguments) {
|
||||
context.unregisterReceiver(flutterFaceReceiver);
|
||||
flutterFaceReceiver = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package io.flutter.plugins.facediscern.flutter;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import io.flutter.plugin.common.EventChannel;
|
||||
|
||||
public class FlutterFaceReceiver extends BroadcastReceiver {
|
||||
|
||||
private static final String TAG = "flutter回调广播";
|
||||
|
||||
private EventChannel.EventSink eventSink;
|
||||
|
||||
public FlutterFaceReceiver(EventChannel.EventSink eventSink) {
|
||||
this.eventSink = eventSink;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String json = intent.getStringExtra("json");
|
||||
if (json == null || Objects.equals(json, "")) {
|
||||
return;
|
||||
}
|
||||
eventSink.success(json);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
package io.flutter.plugins.facediscern.flutter;
|
||||
|
||||
import static com.arcsoft.face.FaceEngine.activeOnline;
|
||||
|
||||
import static io.flutter.plugins.facediscern.database.DataBaseUtils.getUserFace;
|
||||
import static io.flutter.plugins.facediscern.database.DataBaseUtils.updateFaceModel;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
|
||||
import io.flutter.Log;
|
||||
|
||||
import com.arcsoft.face.ErrorInfo;
|
||||
import com.arcsoft.face.FaceFeatureInfo;
|
||||
import com.arcsoft.imageutil.ArcSoftImageFormat;
|
||||
import com.arcsoft.imageutil.ArcSoftImageUtil;
|
||||
import com.arcsoft.imageutil.ArcSoftImageUtilError;
|
||||
import com.google.gson.Gson;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import io.flutter.plugins.facediscern.flutter.config.Constants;
|
||||
import io.flutter.plugins.facediscern.tool.face.model.FaceEntity;
|
||||
import io.flutter.plugins.facediscern.tool.face.servcie.FaceServer;
|
||||
import io.flutter.plugins.facediscern.tool.file.FileUtil;
|
||||
|
||||
public class FlutterTool {
|
||||
|
||||
static private final String TAG = "人脸识别工具类";
|
||||
|
||||
static private final String FACE_JSON_FILE_NAME = "face.json";
|
||||
|
||||
// 在线激活
|
||||
static public Boolean active(Context context, String appId, String activeKey, String sdkKey) {
|
||||
if (Objects.isNull(appId)) {
|
||||
Log.e(TAG, "请传入appId");
|
||||
return false;
|
||||
}
|
||||
if (Objects.isNull(activeKey)) {
|
||||
Log.e(TAG, "请传入activeKey");
|
||||
return false;
|
||||
}
|
||||
if (Objects.isNull(sdkKey)) {
|
||||
Log.e(TAG, "请传入sdkKey");
|
||||
return false;
|
||||
}
|
||||
Constants.APP_ID = appId;
|
||||
Constants.ACTIVE_KEY = activeKey;
|
||||
Constants.SDK_KEY = sdkKey;
|
||||
int code = activeOnline(context, Constants.ACTIVE_KEY, Constants.APP_ID, Constants.SDK_KEY);
|
||||
switch (code) {
|
||||
case ErrorInfo.MOK:
|
||||
Log.i(TAG, "激活成功!");
|
||||
break;
|
||||
case ErrorInfo.MERR_ASF_ALREADY_ACTIVATED:
|
||||
Log.i(TAG, "已激活!");
|
||||
break;
|
||||
default:
|
||||
Log.e(TAG, "激活失败!code:" + code);
|
||||
}
|
||||
boolean flag = Objects.equals(code, ErrorInfo.MOK) || Objects.equals(code, ErrorInfo.MERR_ASF_ALREADY_ACTIVATED);
|
||||
Constants.IS_ACTIVE = flag;
|
||||
return flag;
|
||||
}
|
||||
|
||||
// 获取配置
|
||||
static Map<String, Object> getConfig() {
|
||||
HashMap<String, Object> config = new HashMap<>();
|
||||
config.put("appId", Constants.APP_ID);
|
||||
config.put("sdkKey", Constants.SDK_KEY);
|
||||
config.put("activeKey", Constants.ACTIVE_KEY);
|
||||
config.put("isActive", Constants.IS_ACTIVE);
|
||||
return config;
|
||||
}
|
||||
|
||||
|
||||
static Integer parseId(String id) {
|
||||
try {
|
||||
int number = Integer.parseInt(id);
|
||||
return number - 10000000;
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 注册所有的本地人脸
|
||||
static public Map<String, Object> registerAllStorageFace(Context context, String filePath, String fileName) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("successCount", 0);
|
||||
result.put("failedCount", 0);
|
||||
result.put("registerCount", 0);
|
||||
result.put("fileCount", 0);
|
||||
File file = new File(filePath);
|
||||
if (!file.exists()) {
|
||||
return null;
|
||||
}
|
||||
File[] files = file.listFiles((dir1, name) -> {
|
||||
String nameLowerCase = name.toLowerCase();
|
||||
return nameLowerCase.endsWith(".jpg") || nameLowerCase.endsWith(".jpeg") || nameLowerCase.endsWith(".png");
|
||||
});
|
||||
if (Objects.nonNull(fileName) && !Objects.equals(fileName, "")) {
|
||||
files = Arrays.stream(files).filter(v -> Objects.equals(v.getName(), fileName)).toArray(File[]::new);
|
||||
Log.i(TAG, "根据fileName查询本地人脸图片fileName:" + fileName + ";数量:" + files.length + ";");
|
||||
}
|
||||
if (files == null || files.length == 0) {
|
||||
return null;
|
||||
}
|
||||
int total = files.length;
|
||||
int failed = 0;
|
||||
int success = 0;
|
||||
for (File v : files) {
|
||||
byte[] bytes = FileUtil.fileToData(v);
|
||||
if (bytes == null) {
|
||||
continue;
|
||||
}
|
||||
String name = v.getName();
|
||||
List<String> list = Arrays.asList(name.split("\\."));
|
||||
if (list.size() == 2) {
|
||||
name = list.get(0);
|
||||
}
|
||||
list = Arrays.asList(name.split("_"));
|
||||
if (list.size() != 3) {
|
||||
continue;
|
||||
}
|
||||
String _userId = list.get(0);
|
||||
Integer _sampleId = parseId(list.get(1));
|
||||
String _username = list.get(2);
|
||||
Log.i(TAG, "注册所有的本地人脸,本地人脸解析userId:" + _userId + ";sampleId:" + _sampleId + ";username:" + _username + ";");
|
||||
if (_userId == null || _sampleId == null || _username == null || _username.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
FaceEntity entity = new FaceEntity();
|
||||
entity.setFaceId(_sampleId);
|
||||
entity.setUserId(_userId);
|
||||
entity.setUserName(_username);
|
||||
entity.setSampleId(_sampleId);
|
||||
Boolean isRegister = registerFace(context, bytes, entity);
|
||||
if (isRegister) {
|
||||
success++;
|
||||
} else {
|
||||
failed++;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
failed++;
|
||||
Log.e(TAG, "注册所有的本地人脸,把人脸图片注册到内存错误图片名:" + name);
|
||||
}
|
||||
}
|
||||
int registerCount = success + failed;
|
||||
Log.i(TAG, "注册所有的本地人脸,注册成功,成功的数量:" + success + ";失败的数量:" + failed + ";尝试注册数量:" + registerCount + ";文件数量:" + total + ";");
|
||||
result.put("successCount", success);
|
||||
result.put("failedCount", failed);
|
||||
result.put("registerCount", registerCount);
|
||||
result.put("fileCount", total);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
// 注册人脸
|
||||
static public Boolean registerFace(Context context, byte[] bytes, FaceEntity entity) {
|
||||
FaceServer faceServer = FaceServer.getInstance();
|
||||
faceServer.init(context);
|
||||
// 类型转换
|
||||
Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
|
||||
Bitmap alignedBitmap = ArcSoftImageUtil.getAlignedBitmap(bitmap, true);
|
||||
byte[] bgr24Data = ArcSoftImageUtil.createImageData(alignedBitmap.getWidth(), alignedBitmap.getHeight(), ArcSoftImageFormat.BGR24);
|
||||
int transformCode = ArcSoftImageUtil.bitmapToImageData(alignedBitmap, bgr24Data, ArcSoftImageFormat.BGR24);
|
||||
if (transformCode != ArcSoftImageUtilError.CODE_SUCCESS) {
|
||||
Log.e(TAG, "注册人脸,bitmapToImageData错误code:" + transformCode);
|
||||
return false;
|
||||
}
|
||||
byte[] faceBytes = faceServer.registerBgr24(
|
||||
context,
|
||||
bgr24Data,
|
||||
alignedBitmap.getWidth(),
|
||||
alignedBitmap.getHeight(),
|
||||
String.valueOf(System.currentTimeMillis())
|
||||
);
|
||||
if (faceBytes == null) {
|
||||
Log.e(TAG, "注册人脸,registerBgr24错误");
|
||||
return false;
|
||||
}
|
||||
entity.setFaceModel(faceBytes);
|
||||
int code = faceServer.registerFaceFeatureInfoFromDb(entity);
|
||||
return code == ErrorInfo.MOK;
|
||||
}
|
||||
|
||||
|
||||
// 获取人脸
|
||||
static public Map<String, Object> getFace(Context context, int sampleId) {
|
||||
FaceServer faceServer = FaceServer.getInstance();
|
||||
faceServer.init(context);
|
||||
FaceFeatureInfo faceFeature = faceServer.getFaceFeature(sampleId);
|
||||
if (Objects.isNull(faceFeature)) return null;
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("searchId", faceFeature.getSearchId());
|
||||
result.put("faceTag", faceFeature.getFaceTag());
|
||||
result.put("featureData", faceFeature.getFeatureData());
|
||||
return result;
|
||||
}
|
||||
|
||||
// 获取人脸总数量
|
||||
static public int getFaceCount(Context context) {
|
||||
FaceServer faceServer = FaceServer.getInstance();
|
||||
faceServer.init(context);
|
||||
return faceServer.getTotalFaceCount();
|
||||
}
|
||||
|
||||
// 删除人脸 -1 删除全部
|
||||
static public Boolean removeFace(Context context, int sampleId) {
|
||||
FaceServer faceServer = FaceServer.getInstance();
|
||||
faceServer.init(context);
|
||||
int i = faceServer.removeFace(sampleId);
|
||||
return i == 0;
|
||||
}
|
||||
|
||||
|
||||
// 批量注册全部人脸
|
||||
static public List<Integer> registerFaceByDatabase(Context context, String userId) {
|
||||
List<FaceEntity> faceList = getUserFace(context, userId);
|
||||
List<Integer> errorSampleIdList = new ArrayList<>();
|
||||
if (faceList.isEmpty()) return errorSampleIdList;
|
||||
long startTime = System.currentTimeMillis();
|
||||
// 批量查询人脸模型
|
||||
for (FaceEntity v : faceList) {
|
||||
if (v.getFaceModel() != null && v.getFaceModel().length > 0) continue;
|
||||
byte[] bytes = null;
|
||||
try {
|
||||
bytes = FileUtil.fileToData(new File(v.getFaceFilePath()));
|
||||
} catch (Exception e) {
|
||||
errorSampleIdList.add(v.getSampleId());
|
||||
Log.e(TAG, "registerFaceByDatabase,方法错误,file读取错误error:" + e.getMessage() + "json:" + new Gson().toJson(v), e);
|
||||
continue;
|
||||
}
|
||||
if (bytes == null) {
|
||||
errorSampleIdList.add(v.getSampleId());
|
||||
Log.e(TAG, "registerFaceByDatabase,方法错误" + v.getUserName() + "bytes is null," + "json:" + new Gson().toJson(v));
|
||||
continue;
|
||||
}
|
||||
Boolean isRegister = registerFace(context, bytes, v);
|
||||
if (isRegister) {
|
||||
updateFaceModel(context, v);
|
||||
}
|
||||
}
|
||||
FaceServer faceServer = FaceServer.getInstance();
|
||||
faceServer.init(context);
|
||||
if (errorSampleIdList.isEmpty()) {
|
||||
try {
|
||||
faceServer.registerFaceFeatureInfoListFromDb(faceList);
|
||||
Log.d(TAG, "registerFaceByDatabase,注册全部人脸耗时:" + (System.currentTimeMillis() - startTime) + "毫秒");
|
||||
} catch (Exception e) {
|
||||
faceServer.removeFace(-1);
|
||||
Log.e(TAG, "registerFaceByDatabase,人脸引擎批量注册失败使用单个注册");
|
||||
}
|
||||
} else {
|
||||
for (FaceEntity v : faceList) {
|
||||
faceServer.registerFaceFeatureInfoFromDb(v);
|
||||
}
|
||||
Log.d(TAG, "registerFaceByDatabase,注册单个人脸耗时:" + (System.currentTimeMillis() - startTime) + "毫秒");
|
||||
}
|
||||
return errorSampleIdList;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package io.flutter.plugins.facediscern.flutter.config;
|
||||
|
||||
|
||||
public class Constants {
|
||||
public static String APP_ID = "FWKhBhtgnUnVrUJXZD3SWqnGYV99AiXP4SS8aNSR6bk3";
|
||||
public static String SDK_KEY = "5SAziGjCHYPY4GdjGY8dTiBABBmuLbiTp7oWsKTcWzMb";
|
||||
public static String ACTIVE_KEY = "U571-11UF-F13U-F43A";
|
||||
public static boolean IS_ACTIVE = false;
|
||||
}
|
||||
@@ -0,0 +1,583 @@
|
||||
package io.flutter.plugins.facediscern.tool.camera;
|
||||
|
||||
import android.graphics.ImageFormat;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.SurfaceTexture;
|
||||
import android.hardware.Camera;
|
||||
import android.util.Log;
|
||||
import android.view.Surface;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.SurfaceView;
|
||||
import android.view.TextureView;
|
||||
import android.view.View;
|
||||
|
||||
import io.flutter.plugins.facediscern.tool.camera.callback.CameraListener;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 打开两个相机的辅助类,和{@link CameraListener}共同使用,获取nv21数据等操作
|
||||
* <p>
|
||||
* 由于IR摄像头和RGB摄像头的默认分辨率可能不同,为了让两者相同,该类做了以下操作:
|
||||
* 1. 获取两者支持的分辨率列表到到静态变量{@link DualCameraHelper#rgbSupportedPreviewSizes}及{@link DualCameraHelper#irSupportedPreviewSizes}中,
|
||||
* 2. 使用{@link DualCameraHelper#getCommonSupportedPreviewSize()}方法取分辨率的交集,
|
||||
* 3. 使用{@link DualCameraHelper#getBestSupportedSize(List, Point)}取最佳分辨率使两个摄像头分辨率尽可能相同
|
||||
*/
|
||||
public class DualCameraHelper implements Camera.PreviewCallback {
|
||||
private static List<Camera.Size> rgbSupportedPreviewSizes;
|
||||
private static List<Camera.Size> irSupportedPreviewSizes;
|
||||
private static final String TAG = "CameraHelper";
|
||||
private Camera mCamera;
|
||||
private int mCameraId;
|
||||
private Point previewViewSize;
|
||||
private View previewDisplayView;
|
||||
private Camera.Size previewSize;
|
||||
private Point specificPreviewSize;
|
||||
private int displayOrientation = 0;
|
||||
private int rotation;
|
||||
private int additionalRotation;
|
||||
private boolean isMirror = false;
|
||||
|
||||
private Integer specificCameraId = null;
|
||||
private CameraListener cameraListener;
|
||||
private static final int MIN_PREVIEW_WIDTH = 720;
|
||||
private static final int MIN_PREVIEW_HEIGHT = 720;
|
||||
|
||||
private DualCameraHelper(Builder builder) {
|
||||
previewDisplayView = builder.previewDisplayView;
|
||||
specificCameraId = builder.specificCameraId;
|
||||
cameraListener = builder.cameraListener;
|
||||
rotation = builder.rotation;
|
||||
additionalRotation = builder.additionalRotation;
|
||||
previewViewSize = builder.previewViewSize;
|
||||
specificPreviewSize = builder.previewSize;
|
||||
if (builder.previewDisplayView instanceof TextureView) {
|
||||
isMirror = builder.isMirror;
|
||||
} else if (isMirror) {
|
||||
throw new RuntimeException("mirror is effective only when the preview is on a textureView");
|
||||
}
|
||||
}
|
||||
|
||||
public void init() {
|
||||
if (previewDisplayView instanceof TextureView) {
|
||||
((TextureView) this.previewDisplayView).setSurfaceTextureListener(textureListener);
|
||||
} else if (previewDisplayView instanceof SurfaceView) {
|
||||
((SurfaceView) previewDisplayView).getHolder().addCallback(surfaceCallback);
|
||||
}
|
||||
|
||||
if (isMirror) {
|
||||
previewDisplayView.setScaleX(-1);
|
||||
}
|
||||
}
|
||||
|
||||
public List<Camera.Size> getCommonSupportedPreviewSize() {
|
||||
/**
|
||||
* irSupportedPreviewSizes 和 rgbSupportedPreviewSizes 为null才去获取,
|
||||
* 不为null就没必要获取了,而且此时有可能该camera已处于打开状态,无法打开camera
|
||||
*/
|
||||
if (rgbSupportedPreviewSizes == null) {
|
||||
Camera rgbCamera = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK);
|
||||
rgbSupportedPreviewSizes = rgbCamera.getParameters().getSupportedPreviewSizes();
|
||||
rgbCamera.release();
|
||||
}
|
||||
try {
|
||||
if (irSupportedPreviewSizes == null) {
|
||||
Camera irCamera = Camera.open(Camera.CameraInfo.CAMERA_FACING_FRONT);
|
||||
irSupportedPreviewSizes = irCamera.getParameters().getSupportedPreviewSizes();
|
||||
irCamera.release();
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
e.printStackTrace();
|
||||
irSupportedPreviewSizes = rgbSupportedPreviewSizes;
|
||||
}
|
||||
List<Camera.Size> commonPreviewSizes = new ArrayList<>();
|
||||
for (Camera.Size rgbPreviewSize : rgbSupportedPreviewSizes) {
|
||||
if (rgbPreviewSize.width < MIN_PREVIEW_WIDTH || rgbPreviewSize.height < MIN_PREVIEW_HEIGHT) {
|
||||
continue;
|
||||
}
|
||||
for (Camera.Size irPreviewSize : irSupportedPreviewSizes) {
|
||||
if (irPreviewSize.width == rgbPreviewSize.width && irPreviewSize.height == rgbPreviewSize.height) {
|
||||
commonPreviewSizes.add(rgbPreviewSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
return commonPreviewSizes;
|
||||
}
|
||||
|
||||
/**
|
||||
* 回传当前使用的cameraID,若当前没打开相机,回传-1
|
||||
*
|
||||
* @return cameraId,失败回传-1
|
||||
*/
|
||||
public int getCurrentOpenedCameraId() {
|
||||
if (mCamera == null) {
|
||||
return -1;
|
||||
}
|
||||
return mCameraId;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
synchronized (this) {
|
||||
if (mCamera != null) {
|
||||
return;
|
||||
}
|
||||
List<Camera.Size> supportedPreviewSize = getCommonSupportedPreviewSize();
|
||||
//相机数量为2则打开1,1则打开0,相机ID 1为前置,0为后置
|
||||
mCameraId = Camera.getNumberOfCameras() - 1;
|
||||
//若指定了相机ID且该相机存在,则打开指定的相机
|
||||
if (specificCameraId != null && specificCameraId <= mCameraId) {
|
||||
mCameraId = specificCameraId;
|
||||
}
|
||||
//没有相机
|
||||
if (mCameraId == -1) {
|
||||
if (cameraListener != null) {
|
||||
cameraListener.onCameraError(new Exception("camera not found"));
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (mCamera == null) {
|
||||
mCamera = Camera.open(mCameraId);
|
||||
}
|
||||
displayOrientation = getCameraOri(rotation);
|
||||
mCamera.setDisplayOrientation(displayOrientation);
|
||||
try {
|
||||
Camera.Parameters parameters = mCamera.getParameters();
|
||||
parameters.setPreviewFormat(ImageFormat.NV21);
|
||||
//预览大小设置
|
||||
previewSize = parameters.getPreviewSize();
|
||||
if (supportedPreviewSize != null && supportedPreviewSize.size() > 0) {
|
||||
previewSize = getBestSupportedSize(supportedPreviewSize, previewViewSize);
|
||||
}
|
||||
Log.i(TAG, "start: " + previewSize.width + "x" + previewSize.height);
|
||||
parameters.setPreviewSize(previewSize.width, previewSize.height);
|
||||
//对焦模式设置
|
||||
List<String> supportedFocusModes = parameters.getSupportedFocusModes();
|
||||
if (supportedFocusModes != null && supportedFocusModes.size() > 0) {
|
||||
if (supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
|
||||
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
|
||||
} else if (supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) {
|
||||
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
|
||||
} else if (supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
|
||||
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
|
||||
}
|
||||
}
|
||||
mCamera.setParameters(parameters);
|
||||
if (previewDisplayView instanceof TextureView) {
|
||||
mCamera.setPreviewTexture(((TextureView) previewDisplayView).getSurfaceTexture());
|
||||
} else {
|
||||
mCamera.setPreviewDisplay(((SurfaceView) previewDisplayView).getHolder());
|
||||
}
|
||||
mCamera.setPreviewCallback(this);
|
||||
mCamera.startPreview();
|
||||
if (cameraListener != null) {
|
||||
cameraListener.onCameraOpened(mCamera, mCameraId, displayOrientation, isMirror);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (cameraListener != null) {
|
||||
cameraListener.onCameraError(e);
|
||||
}
|
||||
}
|
||||
io.flutter.Log.e(TAG, "相机已打卡!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
|
||||
}
|
||||
}
|
||||
|
||||
public void switchCameraId() {
|
||||
mCameraId = 1 - mCameraId;
|
||||
if (specificCameraId != null) {
|
||||
specificCameraId = 1 - specificCameraId;
|
||||
}
|
||||
}
|
||||
|
||||
private int getCameraOri(int rotation) {
|
||||
int degrees = rotation * 90;
|
||||
switch (rotation) {
|
||||
case Surface.ROTATION_0:
|
||||
degrees = 0;
|
||||
break;
|
||||
case Surface.ROTATION_90:
|
||||
degrees = 90;
|
||||
break;
|
||||
case Surface.ROTATION_180:
|
||||
degrees = 180;
|
||||
break;
|
||||
case Surface.ROTATION_270:
|
||||
degrees = 270;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
additionalRotation /= 90;
|
||||
additionalRotation *= 90;
|
||||
degrees += additionalRotation;
|
||||
int result;
|
||||
Camera.CameraInfo info = new Camera.CameraInfo();
|
||||
Camera.getCameraInfo(mCameraId, info);
|
||||
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
|
||||
result = (info.orientation + degrees) % 360;
|
||||
result = (360 - result) % 360;
|
||||
} else {
|
||||
result = (info.orientation - degrees + 360) % 360;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止预览
|
||||
*/
|
||||
public void stop() {
|
||||
synchronized (this) {
|
||||
if (mCamera == null) return;
|
||||
mCamera.setPreviewCallback(null);
|
||||
mCamera.stopPreview();
|
||||
}
|
||||
}
|
||||
// public void stop() {
|
||||
// synchronized (this) {
|
||||
// if (mCamera == null) return;
|
||||
// mCamera.setPreviewCallback(null);
|
||||
// mCamera.stopPreview();
|
||||
// mCamera.release();
|
||||
// mCamera = null;
|
||||
// if (cameraListener != null) {
|
||||
// cameraListener.onCameraClosed();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// 继续预览
|
||||
public void resumePreview() {
|
||||
synchronized (this) {
|
||||
if (mCamera != null) {
|
||||
try {
|
||||
mCamera.startPreview();
|
||||
if (cameraListener != null) {
|
||||
cameraListener.onCameraOpened(mCamera, mCameraId, displayOrientation, isMirror);
|
||||
mCamera.setPreviewCallback(this);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (cameraListener != null) {
|
||||
cameraListener.onCameraError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 释放操作
|
||||
*/
|
||||
public void release() {
|
||||
synchronized (this) {
|
||||
stop();
|
||||
if (cameraListener != null) {
|
||||
cameraListener.onCameraClosed();
|
||||
}
|
||||
mCamera.release();
|
||||
mCamera = null;
|
||||
previewDisplayView = null;
|
||||
specificCameraId = null;
|
||||
cameraListener = null;
|
||||
previewViewSize = null;
|
||||
specificPreviewSize = null;
|
||||
previewSize = null;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isStopped() {
|
||||
synchronized (this) {
|
||||
return mCamera == null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取候选分辨率列表中最接近预览view大小的分辨率
|
||||
*
|
||||
* @param sizes 支持的分辨率
|
||||
* @param previewViewSize 预览view的大小
|
||||
* @return 最接近预览view大小的分辨率
|
||||
*/
|
||||
private Camera.Size getBestSupportedSize(List<Camera.Size> sizes, Point previewViewSize) {
|
||||
if (sizes == null || sizes.size() == 0) {
|
||||
return mCamera.getParameters().getPreviewSize();
|
||||
}
|
||||
Camera.Size[] tempSizes = sizes.toArray(new Camera.Size[0]);
|
||||
Arrays.sort(tempSizes, new Comparator<Camera.Size>() {
|
||||
@Override
|
||||
public int compare(Camera.Size o1, Camera.Size o2) {
|
||||
if (o1.width > o2.width) {
|
||||
return -1;
|
||||
} else if (o1.width == o2.width) {
|
||||
return o1.height > o2.height ? -1 : 1;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
sizes = Arrays.asList(tempSizes);
|
||||
|
||||
Camera.Size bestSize = sizes.get(0);
|
||||
float previewViewRatio;
|
||||
if (previewViewSize != null) {
|
||||
previewViewRatio = (float) previewViewSize.x / (float) previewViewSize.y;
|
||||
} else {
|
||||
previewViewRatio = (float) bestSize.width / (float) bestSize.height;
|
||||
}
|
||||
|
||||
if (previewViewRatio > 1) {
|
||||
previewViewRatio = 1 / previewViewRatio;
|
||||
}
|
||||
boolean isNormalRotate = (additionalRotation % 180 == 0);
|
||||
|
||||
for (Camera.Size s : sizes) {
|
||||
if (specificPreviewSize != null && specificPreviewSize.x == s.width && specificPreviewSize.y == s.height) {
|
||||
return s;
|
||||
}
|
||||
if (isNormalRotate) {
|
||||
if (Math.abs((s.height / (float) s.width) - previewViewRatio) < Math.abs(bestSize.height / (float) bestSize.width - previewViewRatio)) {
|
||||
bestSize = s;
|
||||
}
|
||||
} else {
|
||||
if (Math.abs((s.width / (float) s.height) - previewViewRatio) < Math.abs(bestSize.width / (float) bestSize.height - previewViewRatio)) {
|
||||
bestSize = s;
|
||||
}
|
||||
}
|
||||
}
|
||||
return bestSize;
|
||||
}
|
||||
|
||||
public List<Camera.Size> getSupportedPreviewSizes() {
|
||||
if (mCamera == null) {
|
||||
return null;
|
||||
}
|
||||
return mCamera.getParameters().getSupportedPreviewSizes();
|
||||
}
|
||||
|
||||
public List<Camera.Size> getSupportedPictureSizes() {
|
||||
if (mCamera == null) {
|
||||
return null;
|
||||
}
|
||||
return mCamera.getParameters().getSupportedPictureSizes();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onPreviewFrame(byte[] nv21, Camera camera) {
|
||||
if (cameraListener != null) {
|
||||
cameraListener.onPreview(nv21, camera);
|
||||
}
|
||||
}
|
||||
|
||||
private TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener() {
|
||||
@Override
|
||||
public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
|
||||
// start();
|
||||
if (mCamera != null) {
|
||||
try {
|
||||
mCamera.setPreviewTexture(surfaceTexture);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width, int height) {
|
||||
Log.i(TAG, "onSurfaceTextureSizeChanged: " + width + " " + height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
|
||||
stop();
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
|
||||
|
||||
}
|
||||
};
|
||||
private SurfaceHolder.Callback surfaceCallback = new SurfaceHolder.Callback() {
|
||||
@Override
|
||||
public void surfaceCreated(SurfaceHolder holder) {
|
||||
// start();
|
||||
if (mCamera != null) {
|
||||
try {
|
||||
mCamera.setPreviewDisplay(holder);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceDestroyed(SurfaceHolder holder) {
|
||||
stop();
|
||||
}
|
||||
};
|
||||
|
||||
public void changeDisplayOrientation(int rotation) {
|
||||
if (mCamera != null) {
|
||||
this.rotation = rotation;
|
||||
displayOrientation = getCameraOri(rotation);
|
||||
mCamera.setDisplayOrientation(displayOrientation);
|
||||
if (cameraListener != null) {
|
||||
cameraListener.onCameraConfigurationChanged(mCameraId, displayOrientation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
|
||||
/**
|
||||
* 预览显示的view,目前仅支持surfaceView和textureView
|
||||
*/
|
||||
private View previewDisplayView;
|
||||
|
||||
/**
|
||||
* 是否镜像显示,只支持textureView
|
||||
*/
|
||||
private boolean isMirror;
|
||||
/**
|
||||
* 指定的相机ID
|
||||
*/
|
||||
private Integer specificCameraId;
|
||||
/**
|
||||
* 事件回调
|
||||
*/
|
||||
private CameraListener cameraListener;
|
||||
/**
|
||||
* 屏幕的长宽,在选择最佳相机比例时用到
|
||||
*/
|
||||
private Point previewViewSize;
|
||||
/**
|
||||
* 传入getWindowManager().getDefaultDisplay().getRotation()的值即可
|
||||
*/
|
||||
private int rotation;
|
||||
/**
|
||||
* 指定的预览宽高,若系统支持则会以这个预览宽高进行预览
|
||||
*/
|
||||
private Point previewSize;
|
||||
|
||||
/**
|
||||
* 额外的旋转角度(用于适配一些定制设备)
|
||||
*/
|
||||
private int additionalRotation;
|
||||
|
||||
public Builder() {
|
||||
}
|
||||
|
||||
|
||||
public Builder previewOn(View val) {
|
||||
if (val instanceof SurfaceView || val instanceof TextureView) {
|
||||
previewDisplayView = val;
|
||||
return this;
|
||||
} else {
|
||||
throw new RuntimeException("you must preview on a textureView or a surfaceView");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public Builder isMirror(boolean val) {
|
||||
isMirror = val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder previewSize(Point val) {
|
||||
previewSize = val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder previewViewSize(Point val) {
|
||||
previewViewSize = val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder rotation(int val) {
|
||||
rotation = val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder additionalRotation(int val) {
|
||||
additionalRotation = val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder specificCameraId(Integer val) {
|
||||
specificCameraId = val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder cameraListener(CameraListener val) {
|
||||
cameraListener = val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public DualCameraHelper build() {
|
||||
if (previewViewSize == null) {
|
||||
Log.e(TAG, "previewViewSize is null, now use default previewSize");
|
||||
}
|
||||
if (cameraListener == null) {
|
||||
Log.e(TAG, "cameraListener is null, callback will not be called");
|
||||
}
|
||||
if (previewDisplayView == null) {
|
||||
throw new RuntimeException("you must preview on a textureView or a surfaceView");
|
||||
}
|
||||
return new DualCameraHelper(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据设置的额外旋转角度旋转
|
||||
*
|
||||
* @param additionalRotation 额外旋转角度
|
||||
* @return 当前显示旋转角度
|
||||
*/
|
||||
public int rotateAdditional(int additionalRotation) {
|
||||
this.additionalRotation = additionalRotation;
|
||||
int cameraOri = getCameraOri(rotation);
|
||||
if (mCamera == null) {
|
||||
start();
|
||||
return cameraOri;
|
||||
}
|
||||
mCamera.setDisplayOrientation(cameraOri);
|
||||
return cameraOri;
|
||||
}
|
||||
|
||||
public void setSpecificPreviewSize(Point specificPreviewSize) {
|
||||
this.specificPreviewSize = specificPreviewSize;
|
||||
}
|
||||
|
||||
public static boolean hasDualCamera() {
|
||||
return Camera.getNumberOfCameras() > 1;
|
||||
}
|
||||
|
||||
public static boolean canOpenDualCamera() {
|
||||
Camera camera0 = null;
|
||||
Camera camera1 = null;
|
||||
boolean can = true;
|
||||
try {
|
||||
camera0 = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK);
|
||||
camera1 = Camera.open(Camera.CameraInfo.CAMERA_FACING_FRONT);
|
||||
} catch (Exception e) {
|
||||
can = false;
|
||||
}
|
||||
if (camera0 != null) {
|
||||
camera0.release();
|
||||
}
|
||||
if (camera1 != null) {
|
||||
camera1.release();
|
||||
}
|
||||
return can;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package io.flutter.plugins.facediscern.tool.camera.callback;
|
||||
|
||||
import android.hardware.Camera;
|
||||
|
||||
|
||||
public interface CameraListener {
|
||||
/**
|
||||
* 当打开时执行
|
||||
* @param camera 相机实例
|
||||
* @param cameraId 相机ID
|
||||
* @param displayOrientation 相机预览旋转角度
|
||||
* @param isMirror 是否镜像显示
|
||||
*/
|
||||
void onCameraOpened(Camera camera, int cameraId, int displayOrientation, boolean isMirror);
|
||||
|
||||
/**
|
||||
* 预览数据回调
|
||||
* @param data 预览数据
|
||||
* @param camera 相机实例
|
||||
*/
|
||||
void onPreview(byte[] data, Camera camera);
|
||||
|
||||
/**
|
||||
* 当相机关闭时执行
|
||||
*/
|
||||
void onCameraClosed();
|
||||
|
||||
/**
|
||||
* 当出现异常时执行
|
||||
* @param e 相机相关异常
|
||||
*/
|
||||
void onCameraError(Exception e);
|
||||
|
||||
/**
|
||||
* 属性变化时调用
|
||||
* @param cameraID 相机ID
|
||||
* @param displayOrientation 相机旋转方向
|
||||
*/
|
||||
void onCameraConfigurationChanged(int cameraID, int displayOrientation);
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package io.flutter.plugins.facediscern.tool.camera.config;
|
||||
|
||||
import android.hardware.Camera;
|
||||
|
||||
public class PreviewConfig {
|
||||
/**
|
||||
* 默认的可见光相机ID
|
||||
*/
|
||||
public static final int DEFAULT_RGB_CAMERA_ID = Camera.CameraInfo.CAMERA_FACING_BACK;
|
||||
/**
|
||||
* 默认的红外相机ID
|
||||
*/
|
||||
public static final int DEFAULT_IR_CAMERA_ID = Camera.CameraInfo.CAMERA_FACING_FRONT;
|
||||
|
||||
private int rgbCameraId;
|
||||
private int irCameraId;
|
||||
private int rgbAdditionalDisplayOrientation;
|
||||
private int irAdditionalDisplayOrientation;
|
||||
|
||||
public PreviewConfig(int rgbCameraId, int irCameraId, int rgbAdditionalDisplayOrientation, int irAdditionalDisplayOrientation) {
|
||||
this.rgbCameraId = rgbCameraId;
|
||||
this.irCameraId = irCameraId;
|
||||
this.rgbAdditionalDisplayOrientation = rgbAdditionalDisplayOrientation;
|
||||
this.irAdditionalDisplayOrientation = irAdditionalDisplayOrientation;
|
||||
}
|
||||
|
||||
public int getRgbCameraId() {
|
||||
return rgbCameraId;
|
||||
}
|
||||
|
||||
public int getIrCameraId() {
|
||||
return irCameraId;
|
||||
}
|
||||
|
||||
public int getRgbAdditionalDisplayOrientation() {
|
||||
return rgbAdditionalDisplayOrientation;
|
||||
}
|
||||
|
||||
public int getIrAdditionalDisplayOrientation() {
|
||||
return irAdditionalDisplayOrientation;
|
||||
}
|
||||
|
||||
public void setRgbCameraId(int rgbCameraId) {
|
||||
this.rgbCameraId = rgbCameraId;
|
||||
}
|
||||
|
||||
public void setIrCameraId(int irCameraId) {
|
||||
this.irCameraId = irCameraId;
|
||||
}
|
||||
|
||||
public void setRgbAdditionalDisplayOrientation(int rgbAdditionalDisplayOrientation) {
|
||||
this.rgbAdditionalDisplayOrientation = rgbAdditionalDisplayOrientation;
|
||||
}
|
||||
|
||||
public void setIrAdditionalDisplayOrientation(int irAdditionalDisplayOrientation) {
|
||||
this.irAdditionalDisplayOrientation = irAdditionalDisplayOrientation;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package io.flutter.plugins.facediscern.tool.face.constants;
|
||||
|
||||
public class FaceBrodConstant {
|
||||
public static String FACE_GLOBAL_INTENT = "accessibility_event";
|
||||
public static final String FACE_GLOBAL_ACTIONS = "face_global_actions";
|
||||
// public static final String FACE_GLOBAL_BROD = "face_global_brod";
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package io.flutter.plugins.facediscern.tool.face.constants;
|
||||
|
||||
/**
|
||||
* 活体检测类型
|
||||
*/
|
||||
public enum LivenessType {
|
||||
/**
|
||||
* RGB活体检测
|
||||
*/
|
||||
RGB,
|
||||
/**
|
||||
* 红外活体检测
|
||||
*/
|
||||
IR
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package io.flutter.plugins.facediscern.tool.face.constants;
|
||||
|
||||
import android.graphics.Color;
|
||||
|
||||
/**
|
||||
* 识别过程中人脸框的颜色
|
||||
*/
|
||||
public class RecognizeColor {
|
||||
/**
|
||||
* 未知情况的颜色
|
||||
*/
|
||||
public static final int COLOR_UNKNOWN = Color.YELLOW;
|
||||
/**
|
||||
* 成功的颜色
|
||||
*/
|
||||
public static final int COLOR_SUCCESS = Color.GREEN;
|
||||
/**
|
||||
* 失败的颜色
|
||||
*/
|
||||
public static final int COLOR_FAILED = Color.YELLOW;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package io.flutter.plugins.facediscern.tool.face.constants;
|
||||
|
||||
/**
|
||||
* 人脸识别中可能出现的状态
|
||||
* @author
|
||||
*/
|
||||
public @interface RequestFeatureStatus {
|
||||
/**
|
||||
* 默认状态
|
||||
*/
|
||||
int DEFAULT = -1;
|
||||
/**
|
||||
* 处理中
|
||||
*/
|
||||
int SEARCHING = 0;
|
||||
/**
|
||||
* 识别成功
|
||||
*/
|
||||
int SUCCEED = 1;
|
||||
/**
|
||||
* 待重试
|
||||
*/
|
||||
int TO_RETRY = 2;
|
||||
/**
|
||||
* 识别失败
|
||||
*/
|
||||
int FAILED = 3;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package io.flutter.plugins.facediscern.tool.face.constants;
|
||||
|
||||
public class RequestLivenessStatus {
|
||||
public static final int ANALYZING = 10;
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package io.flutter.plugins.facediscern.tool.face.filter;
|
||||
|
||||
import android.graphics.Rect;
|
||||
|
||||
|
||||
import io.flutter.plugins.facediscern.tool.face.filter.callback.FaceRecognizeFilter;
|
||||
import io.flutter.plugins.facediscern.tool.face.model.FacePreviewInfo;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.LinkedBlockingDeque;
|
||||
|
||||
/**
|
||||
* 人脸移动过滤器:
|
||||
* 仅保留在{@link FaceMoveFilter#CHECK_QUEUE_SIZE}帧数内,每一帧人脸的移动大小都小于{@link FaceMoveFilter#movePixels}的人脸
|
||||
*/
|
||||
public class FaceMoveFilter implements FaceRecognizeFilter {
|
||||
private static final String TAG = "FaceMoveFilter";
|
||||
private Map<Integer, LinkedBlockingDeque<Rect>> facePositionQueueMap = new ConcurrentHashMap<>();
|
||||
private static final int CHECK_QUEUE_SIZE = 5;
|
||||
private double movePixels;
|
||||
|
||||
public FaceMoveFilter(double movePixels) {
|
||||
this.movePixels = movePixels;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void filter(List<FacePreviewInfo> facePreviewInfoList) {
|
||||
clearFacesNotInPreview(facePreviewInfoList);
|
||||
for (FacePreviewInfo facePreviewInfo : facePreviewInfoList) {
|
||||
LinkedBlockingDeque<Rect> rectDeque = facePositionQueueMap.get(facePreviewInfo.getTrackId());
|
||||
if (rectDeque == null) {
|
||||
rectDeque = new LinkedBlockingDeque<>(CHECK_QUEUE_SIZE);
|
||||
facePositionQueueMap.put(facePreviewInfo.getTrackId(), rectDeque);
|
||||
}
|
||||
if (rectDeque.remainingCapacity() == 0) {
|
||||
rectDeque.removeLast();
|
||||
}
|
||||
rectDeque.push(facePreviewInfo.getFaceInfoRgb().getRect());
|
||||
|
||||
if (!facePreviewInfo.isQualityPass()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
boolean qualityPass = false;
|
||||
if (rectDeque.size() == CHECK_QUEUE_SIZE) {
|
||||
qualityPass = true;
|
||||
Iterator<Rect> iterator = rectDeque.iterator();
|
||||
Rect previous = iterator.next();
|
||||
while (iterator.hasNext()) {
|
||||
Rect current = iterator.next();
|
||||
double distance = getDistance(current, previous);
|
||||
previous = current;
|
||||
if (distance > movePixels) {
|
||||
qualityPass = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
facePreviewInfo.setQualityPass(qualityPass);
|
||||
}
|
||||
}
|
||||
|
||||
private void clearFacesNotInPreview(List<FacePreviewInfo> facePreviewInfo) {
|
||||
Set<Integer> trackIdSet = facePositionQueueMap.keySet();
|
||||
for (Integer trackId : trackIdSet) {
|
||||
boolean contains = false;
|
||||
for (FacePreviewInfo previewInfo : facePreviewInfo) {
|
||||
if (previewInfo.getTrackId() == trackId) {
|
||||
contains = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!contains) {
|
||||
facePositionQueueMap.remove(trackId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static double getDistance(Rect first, Rect second) {
|
||||
int firstX = first.centerX();
|
||||
int firstY = first.centerY();
|
||||
|
||||
int secondX = second.centerX();
|
||||
int secondY = second.centerY();
|
||||
|
||||
int distanceX = secondX - firstX;
|
||||
int distanceY = secondY - firstY;
|
||||
|
||||
return Math.sqrt(distanceX * distanceX + distanceY * distanceY);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package io.flutter.plugins.facediscern.tool.face.filter;
|
||||
|
||||
|
||||
import android.graphics.Rect;
|
||||
|
||||
import io.flutter.plugins.facediscern.tool.face.filter.callback.FaceRecognizeFilter;
|
||||
import io.flutter.plugins.facediscern.tool.face.model.FacePreviewInfo;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 人脸识别区域过滤器:
|
||||
* 仅保留人脸区域在{@link FaceRecognizeAreaFilter#validArea}中的人脸。(基于View位置判断)
|
||||
*/
|
||||
public class FaceRecognizeAreaFilter implements FaceRecognizeFilter {
|
||||
private static final String TAG = "FaceRecognizeAreaFilter";
|
||||
private Rect validArea;
|
||||
|
||||
public FaceRecognizeAreaFilter(Rect validArea) {
|
||||
this.validArea = validArea;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void filter(List<FacePreviewInfo> facePreviewInfoList) {
|
||||
for (FacePreviewInfo facePreviewInfo : facePreviewInfoList) {
|
||||
if (!facePreviewInfo.isQualityPass()) {
|
||||
continue;
|
||||
}
|
||||
facePreviewInfo.setQualityPass(validArea.contains(facePreviewInfo.getRgbTransformedRect()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package io.flutter.plugins.facediscern.tool.face.filter;
|
||||
|
||||
import android.graphics.Rect;
|
||||
|
||||
|
||||
import io.flutter.plugins.facediscern.tool.face.filter.callback.FaceRecognizeFilter;
|
||||
import io.flutter.plugins.facediscern.tool.face.model.FacePreviewInfo;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 人脸尺寸过滤器:
|
||||
* 仅保留人脸宽度大于{@link FaceSizeFilter#horizontalSize},且人脸高度大于{@link FaceSizeFilter#verticalSize}的人脸。
|
||||
*/
|
||||
public class FaceSizeFilter implements FaceRecognizeFilter {
|
||||
private int horizontalSize;
|
||||
private int verticalSize;
|
||||
|
||||
private static final String TAG = "FaceSizeFilter";
|
||||
|
||||
public FaceSizeFilter(int horizontalSize, int verticalSize) {
|
||||
this.horizontalSize = horizontalSize;
|
||||
this.verticalSize = verticalSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void filter(List<FacePreviewInfo> facePreviewInfoList) {
|
||||
for (FacePreviewInfo facePreviewInfo : facePreviewInfoList) {
|
||||
if (!facePreviewInfo.isQualityPass()) {
|
||||
continue;
|
||||
}
|
||||
if (facePreviewInfo.getFaceInfoRgb() != null) {
|
||||
Rect rgbRect = facePreviewInfo.getFaceInfoRgb().getRect();
|
||||
Rect irRect = facePreviewInfo.getFaceInfoIr() == null ? null : facePreviewInfo.getFaceInfoIr().getRect();
|
||||
boolean rgbRectValid = rgbRect == null || (rgbRect.width() > horizontalSize && rgbRect.height() > verticalSize);
|
||||
boolean irRectValid = irRect == null || (irRect.width() > horizontalSize && irRect.height() > verticalSize);
|
||||
facePreviewInfo.setQualityPass(rgbRectValid && irRectValid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package io.flutter.plugins.facediscern.tool.face.filter.callback;
|
||||
|
||||
|
||||
|
||||
import io.flutter.plugins.facediscern.tool.face.model.FacePreviewInfo;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 人脸识别过滤器,仅保留满足条件的人脸,(只有满足条件的人脸才进行后续的活体检测、人脸识别操作)
|
||||
*/
|
||||
public interface FaceRecognizeFilter {
|
||||
void filter(List<FacePreviewInfo> facePreviewInfoList);
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
package io.flutter.plugins.facediscern.tool.face.help;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.hardware.Camera;
|
||||
|
||||
/**
|
||||
* 将检测回传的人脸框(基于NV21数据)转换为View绘制(基于View)所需的人脸框
|
||||
*/
|
||||
public class FaceRectTransformer {
|
||||
private int previewWidth, previewHeight, canvasWidth, canvasHeight, cameraDisplayOrientation, cameraId;
|
||||
private boolean isMirror;
|
||||
private boolean mirrorHorizontal = false, mirrorVertical = false;
|
||||
|
||||
/**
|
||||
* 创建一个绘制辅助类对象,并且设置绘制相关的参数
|
||||
*
|
||||
* @param previewWidth 预览宽度
|
||||
* @param previewHeight 预览高度
|
||||
* @param canvasWidth 绘制控件的宽度
|
||||
* @param canvasHeight 绘制控件的高度
|
||||
* @param cameraDisplayOrientation 旋转角度
|
||||
* @param cameraId 相机ID
|
||||
* @param isMirror 是否水平镜像显示(若相机是镜像显示的,设为true,用于纠正)
|
||||
* @param mirrorHorizontal 为兼容部分设备使用,水平再次镜像
|
||||
* @param mirrorVertical 为兼容部分设备使用,垂直再次镜像
|
||||
*/
|
||||
public FaceRectTransformer(int previewWidth, int previewHeight, int canvasWidth,
|
||||
int canvasHeight, int cameraDisplayOrientation, int cameraId,
|
||||
boolean isMirror, boolean mirrorHorizontal, boolean mirrorVertical) {
|
||||
this.previewWidth = previewWidth;
|
||||
this.previewHeight = previewHeight;
|
||||
this.canvasWidth = canvasWidth;
|
||||
this.canvasHeight = canvasHeight;
|
||||
this.cameraDisplayOrientation = cameraDisplayOrientation;
|
||||
this.cameraId = cameraId;
|
||||
this.isMirror = isMirror;
|
||||
this.mirrorHorizontal = mirrorHorizontal;
|
||||
this.mirrorVertical = mirrorVertical;
|
||||
}
|
||||
|
||||
/**
|
||||
* 调整人脸框用来绘制
|
||||
*
|
||||
* @param ftRect FT人脸框
|
||||
* @return 调整后的需要被绘制到View上的rect
|
||||
*/
|
||||
public Rect adjustRect(Rect ftRect) {
|
||||
int previewWidth = this.previewWidth;
|
||||
int previewHeight = this.previewHeight;
|
||||
int canvasWidth = this.canvasWidth;
|
||||
int canvasHeight = this.canvasHeight;
|
||||
int cameraDisplayOrientation = this.cameraDisplayOrientation;
|
||||
int cameraId = this.cameraId;
|
||||
boolean isMirror = this.isMirror;
|
||||
boolean mirrorHorizontal = this.mirrorHorizontal;
|
||||
boolean mirrorVertical = this.mirrorVertical;
|
||||
|
||||
if (ftRect == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Rect rect = new Rect(ftRect);
|
||||
float horizontalRatio;
|
||||
float verticalRatio;
|
||||
if (cameraDisplayOrientation % 180 == 0) {
|
||||
horizontalRatio = (float) canvasWidth / (float) previewWidth;
|
||||
verticalRatio = (float) canvasHeight / (float) previewHeight;
|
||||
} else {
|
||||
horizontalRatio = (float) canvasHeight / (float) previewWidth;
|
||||
verticalRatio = (float) canvasWidth / (float) previewHeight;
|
||||
}
|
||||
rect.left *= horizontalRatio;
|
||||
rect.right *= horizontalRatio;
|
||||
rect.top *= verticalRatio;
|
||||
rect.bottom *= verticalRatio;
|
||||
|
||||
Rect newRect = new Rect();
|
||||
switch (cameraDisplayOrientation) {
|
||||
case 0:
|
||||
if (cameraId == Camera.CameraInfo.CAMERA_FACING_FRONT) {
|
||||
newRect.left = canvasWidth - rect.right;
|
||||
newRect.right = canvasWidth - rect.left;
|
||||
} else {
|
||||
newRect.left = rect.left;
|
||||
newRect.right = rect.right;
|
||||
}
|
||||
newRect.top = rect.top;
|
||||
newRect.bottom = rect.bottom;
|
||||
break;
|
||||
case 90:
|
||||
newRect.right = canvasWidth - rect.top;
|
||||
newRect.left = canvasWidth - rect.bottom;
|
||||
if (cameraId == Camera.CameraInfo.CAMERA_FACING_FRONT) {
|
||||
newRect.top = canvasHeight - rect.right;
|
||||
newRect.bottom = canvasHeight - rect.left;
|
||||
} else {
|
||||
newRect.top = rect.left;
|
||||
newRect.bottom = rect.right;
|
||||
}
|
||||
break;
|
||||
case 180:
|
||||
newRect.top = canvasHeight - rect.bottom;
|
||||
newRect.bottom = canvasHeight - rect.top;
|
||||
if (cameraId == Camera.CameraInfo.CAMERA_FACING_FRONT) {
|
||||
newRect.left = rect.left;
|
||||
newRect.right = rect.right;
|
||||
} else {
|
||||
newRect.left = canvasWidth - rect.right;
|
||||
newRect.right = canvasWidth - rect.left;
|
||||
}
|
||||
break;
|
||||
case 270:
|
||||
newRect.left = rect.top;
|
||||
newRect.right = rect.bottom;
|
||||
if (cameraId == Camera.CameraInfo.CAMERA_FACING_FRONT) {
|
||||
newRect.top = rect.left;
|
||||
newRect.bottom = rect.right;
|
||||
} else {
|
||||
newRect.top = canvasHeight - rect.right;
|
||||
newRect.bottom = canvasHeight - rect.left;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/**
|
||||
* isMirror mirrorHorizontal finalIsMirrorHorizontal
|
||||
* true true false
|
||||
* false false false
|
||||
* true false true
|
||||
* false true true
|
||||
*
|
||||
* XOR
|
||||
*/
|
||||
if (isMirror ^ mirrorHorizontal) {
|
||||
int left = newRect.left;
|
||||
int right = newRect.right;
|
||||
newRect.left = canvasWidth - right;
|
||||
newRect.right = canvasWidth - left;
|
||||
}
|
||||
if (mirrorVertical) {
|
||||
int top = newRect.top;
|
||||
int bottom = newRect.bottom;
|
||||
newRect.top = canvasHeight - bottom;
|
||||
newRect.bottom = canvasHeight - top;
|
||||
}
|
||||
return newRect;
|
||||
}
|
||||
|
||||
public void setPreviewWidth(int previewWidth) {
|
||||
this.previewWidth = previewWidth;
|
||||
}
|
||||
|
||||
public void setPreviewHeight(int previewHeight) {
|
||||
this.previewHeight = previewHeight;
|
||||
}
|
||||
|
||||
public void setCanvasWidth(int canvasWidth) {
|
||||
this.canvasWidth = canvasWidth;
|
||||
}
|
||||
|
||||
public void setCanvasHeight(int canvasHeight) {
|
||||
this.canvasHeight = canvasHeight;
|
||||
}
|
||||
|
||||
public void setCameraDisplayOrientation(int cameraDisplayOrientation) {
|
||||
this.cameraDisplayOrientation = cameraDisplayOrientation;
|
||||
}
|
||||
|
||||
public void setCameraId(int cameraId) {
|
||||
this.cameraId = cameraId;
|
||||
}
|
||||
|
||||
public void setMirror(boolean mirror) {
|
||||
isMirror = mirror;
|
||||
}
|
||||
|
||||
public int getPreviewWidth() {
|
||||
return previewWidth;
|
||||
}
|
||||
|
||||
public int getPreviewHeight() {
|
||||
return previewHeight;
|
||||
}
|
||||
|
||||
public int getCanvasWidth() {
|
||||
return canvasWidth;
|
||||
}
|
||||
|
||||
public int getCanvasHeight() {
|
||||
return canvasHeight;
|
||||
}
|
||||
|
||||
public int getCameraDisplayOrientation() {
|
||||
return cameraDisplayOrientation;
|
||||
}
|
||||
|
||||
public int getCameraId() {
|
||||
return cameraId;
|
||||
}
|
||||
|
||||
public boolean isMirror() {
|
||||
return isMirror;
|
||||
}
|
||||
|
||||
public boolean isMirrorHorizontal() {
|
||||
return mirrorHorizontal;
|
||||
}
|
||||
|
||||
public void setMirrorHorizontal(boolean mirrorHorizontal) {
|
||||
this.mirrorHorizontal = mirrorHorizontal;
|
||||
}
|
||||
|
||||
public boolean isMirrorVertical() {
|
||||
return mirrorVertical;
|
||||
}
|
||||
|
||||
public void setMirrorVertical(boolean mirrorVertical) {
|
||||
this.mirrorVertical = mirrorVertical;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package io.flutter.plugins.facediscern.tool.face.help.callback;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.arcsoft.face.FaceFeature;
|
||||
import com.arcsoft.face.LivenessInfo;
|
||||
|
||||
/**
|
||||
* 人脸处理回调
|
||||
*/
|
||||
public interface FaceListener {
|
||||
/**
|
||||
* 当出现异常时执行
|
||||
*
|
||||
* @param e 异常信息
|
||||
*/
|
||||
void onFail(Exception e);
|
||||
|
||||
|
||||
/**
|
||||
* 请求人脸特征后的回调
|
||||
*
|
||||
* @param faceFeature 人脸特征数据
|
||||
* @param trackId 人脸Id(相当于请求码)
|
||||
* @param errorCode 错误码
|
||||
*/
|
||||
void onFaceFeatureInfoGet(@Nullable FaceFeature faceFeature, Integer trackId, Integer errorCode);
|
||||
|
||||
/**
|
||||
* 请求活体检测后的回调
|
||||
*
|
||||
* @param livenessInfo 活体检测结果
|
||||
* @param trackId 人脸Id(相当于请求码)
|
||||
* @param errorCode 错误码
|
||||
*/
|
||||
void onFaceLivenessInfoGet(@Nullable LivenessInfo livenessInfo, Integer trackId, Integer errorCode);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package io.flutter.plugins.facediscern.tool.face.help.callback;
|
||||
|
||||
import com.arcsoft.face.FaceInfo;
|
||||
|
||||
/**
|
||||
* 设置双目识别时,将RGB Camera帧数据检测到的人脸信息用于IR Camera帧数据活体检测时的转换方式
|
||||
*/
|
||||
public interface IDualCameraFaceInfoTransformer {
|
||||
/**
|
||||
* 将RGB Camera帧数据检测到的人脸信息用于IR Camera帧数据活体检测时的转换方式
|
||||
*
|
||||
* @param faceInfo RGB Camera帧数据检测到的人脸信息
|
||||
* @return 转换后,用于IR活体检测的FaceInfo
|
||||
*/
|
||||
FaceInfo transformFaceInfo(FaceInfo faceInfo);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package io.flutter.plugins.facediscern.tool.face.help.callback;
|
||||
|
||||
|
||||
import io.flutter.plugins.facediscern.tool.face.model.CompareResult;
|
||||
|
||||
public interface RecognizeCallback {
|
||||
/**
|
||||
* 识别结果回调
|
||||
*
|
||||
* @param compareResult 比对结果
|
||||
* @param liveness 活体值
|
||||
* @param similarPass 是否通过(依据设置的阈值)
|
||||
*/
|
||||
void onRecognized(CompareResult compareResult, Integer liveness, boolean similarPass);
|
||||
|
||||
/**
|
||||
* 提示文字变更的回调
|
||||
*/
|
||||
void onNoticeChanged(String notice);
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
package io.flutter.plugins.facediscern.tool.face.image;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Rect;
|
||||
import android.net.Uri;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public class ImageUtil {
|
||||
public static final int DEFAULT_MAX_WIDTH = 1920;
|
||||
public static final int DEFAULT_MAX_HEIGHT = 1080;
|
||||
|
||||
|
||||
private static final int MASK_A = 0xFF000000;
|
||||
private static final int MASK_R = 0x00FF0000;
|
||||
private static final int MASK_G = 0x0000FF00;
|
||||
private static final int MASK_B = 0x000000FF;
|
||||
|
||||
public static int rgbToY(int r, int g, int b) {
|
||||
return (((66 * r + 129 * g + 25 * b + 128) >> 8) + 16);
|
||||
}
|
||||
|
||||
public static int rgbToU(int r, int g, int b) {
|
||||
return (((-38 * r - 74 * g + 112 * b + 128) >> 8) + 128);
|
||||
}
|
||||
|
||||
public static int rgbToV(int r, int g, int b) {
|
||||
return (((112 * r - 94 * g - 18 * b + 128) >> 8) + 128);
|
||||
}
|
||||
|
||||
public static void drawRectOnNv21(byte[] nv21, int width, int height, int color, int strokeWidth, Rect rect) {
|
||||
if (rect == null || rect.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
drawRectOnNv21(nv21, width, height, color, strokeWidth, rect.left, rect.top, rect.right, rect.bottom);
|
||||
}
|
||||
|
||||
public static void drawRectOnNv21(byte[] nv21, int width, int height, int color, int strokeWidth, int left, int top,
|
||||
int right, int bottom) {
|
||||
if ((strokeWidth & 1) == 1) {
|
||||
strokeWidth += 1;
|
||||
}
|
||||
// 确保边界是4的倍数
|
||||
left &= ~0b11;
|
||||
top &= ~0b11;
|
||||
right &= ~0b11;
|
||||
bottom &= ~0b11;
|
||||
// 对于溢出图像的边,不绘制
|
||||
boolean drawLeft = true, drawTop = true, drawRight = true, drawBottom = true;
|
||||
if (left <= 0) {
|
||||
left = 0;
|
||||
drawLeft = false;
|
||||
}
|
||||
if (top <= 0) {
|
||||
top = 0;
|
||||
drawTop = false;
|
||||
}
|
||||
if (right >= width) {
|
||||
right = width;
|
||||
drawRight = false;
|
||||
}
|
||||
if (bottom >= height) {
|
||||
bottom = height;
|
||||
drawBottom = false;
|
||||
}
|
||||
|
||||
// 取出R G B的值,并转换为Y U V
|
||||
int r = (color & MASK_R) >> 16;
|
||||
int g = (color & MASK_G) >> 8;
|
||||
int b = color & MASK_B;
|
||||
int y = rgbToY(r, g, b);
|
||||
int u = rgbToU(r, g, b);
|
||||
int v = rgbToV(r, g, b);
|
||||
|
||||
// 根据边框的strokeWidth确定内边界
|
||||
int innerTop = top + strokeWidth;
|
||||
int innerBottom = bottom - strokeWidth;
|
||||
int innerRight = right - strokeWidth;
|
||||
|
||||
int horizontalPixels = right - left;
|
||||
int yStartIndex;
|
||||
int uvStartIndex;
|
||||
boolean drawUV;
|
||||
if (drawTop) {
|
||||
yStartIndex = top * width + left;
|
||||
uvStartIndex = width * height + ((top / 2 * width) + left);
|
||||
drawUV = false;
|
||||
for (int i = top; i < innerTop; i++) {
|
||||
for (int j = 0; j < horizontalPixels; j++) {
|
||||
nv21[yStartIndex + j] = (byte) y;
|
||||
}
|
||||
yStartIndex += width;
|
||||
if (drawUV = !drawUV) {
|
||||
for (int j = 0; j < horizontalPixels; j += 2) {
|
||||
nv21[uvStartIndex + j] = (byte) v;
|
||||
nv21[uvStartIndex + j + 1] = (byte) u;
|
||||
}
|
||||
uvStartIndex += width;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (drawLeft) {
|
||||
//左边
|
||||
yStartIndex = innerTop * width + left;
|
||||
uvStartIndex = width * height + (innerTop / 2 * width + left);
|
||||
drawUV = false;
|
||||
for (int i = innerTop; i < innerBottom; i++) {
|
||||
for (int j = 0; j < strokeWidth; j++) {
|
||||
nv21[yStartIndex + j] = (byte) y;
|
||||
}
|
||||
yStartIndex += width;
|
||||
if (drawUV = !drawUV) {
|
||||
for (int j = 0; j < strokeWidth; j += 2) {
|
||||
nv21[uvStartIndex + j] = (byte) v;
|
||||
nv21[uvStartIndex + j + 1] = (byte) u;
|
||||
}
|
||||
uvStartIndex += width;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (drawRight) {
|
||||
//右边
|
||||
yStartIndex = innerTop * width + innerRight;
|
||||
uvStartIndex = width * height + (innerTop / 2 * width + innerRight);
|
||||
drawUV = false;
|
||||
for (int i = innerTop; i < innerBottom; i++) {
|
||||
for (int j = 0; j < strokeWidth; j++) {
|
||||
nv21[yStartIndex + j] = (byte) y;
|
||||
}
|
||||
yStartIndex += width;
|
||||
if (drawUV = !drawUV) {
|
||||
for (int j = 0; j < strokeWidth; j += 2) {
|
||||
nv21[uvStartIndex + j] = (byte) v;
|
||||
nv21[uvStartIndex + j + 1] = (byte) u;
|
||||
}
|
||||
uvStartIndex += width;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (drawBottom) {
|
||||
//下边
|
||||
yStartIndex = innerBottom * width + left;
|
||||
uvStartIndex = width * height + ((innerBottom / 2 * width) + left);
|
||||
drawUV = false;
|
||||
for (int i = innerBottom; i < bottom; i++) {
|
||||
for (int j = 0; j < horizontalPixels; j++) {
|
||||
nv21[yStartIndex + j] = (byte) y;
|
||||
}
|
||||
yStartIndex += width;
|
||||
if (drawUV = !drawUV) {
|
||||
for (int j = 0; j < horizontalPixels; j += 2) {
|
||||
nv21[uvStartIndex + j] = (byte) v;
|
||||
nv21[uvStartIndex + j + 1] = (byte) u;
|
||||
}
|
||||
uvStartIndex += width;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 缩放图像,如果需要缩放,就顺便把宽高对齐给做了
|
||||
*
|
||||
* @param bitmap 原图
|
||||
* @param maxWidth 最大目标宽度
|
||||
* @param maxHeight 最大目标高度
|
||||
* @return 缩放后的图像
|
||||
*/
|
||||
public static Bitmap scaleBitmap(Bitmap bitmap, int maxWidth, int maxHeight) {
|
||||
float horizontalScale = ((float) bitmap.getWidth()) / maxWidth;
|
||||
float verticalScale = ((float) bitmap.getHeight()) / maxHeight;
|
||||
if (horizontalScale < 1 || verticalScale < 1) {
|
||||
return bitmap;
|
||||
}
|
||||
float maxScale = Math.max(horizontalScale, verticalScale);
|
||||
// 确保为4的倍数
|
||||
int newWidth = (int) (bitmap.getWidth() / maxScale) & ~0b11;
|
||||
int newHeight = (int) (bitmap.getHeight() / maxScale) & ~0b11;
|
||||
|
||||
return Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将Uri转换为Bitmap,并限制最大宽高
|
||||
*/
|
||||
public static Bitmap uriToScaledBitmap(Context context, Uri uri, int maxWidth, int maxHeight) {
|
||||
ContentResolver contentResolver = context.getContentResolver();
|
||||
byte[] data;
|
||||
try {
|
||||
InputStream input = null;
|
||||
input = contentResolver.openInputStream(uri);
|
||||
data = new byte[input.available()];
|
||||
input.read(data);
|
||||
input.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
return jpegToScaledBitmap(data, maxWidth, maxHeight);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将jpeg形式的压缩图像转换为Bitmap,并限制最大宽高
|
||||
*
|
||||
* @param jpeg jpeg图像数据
|
||||
* @param maxWidth 限制的最大宽度
|
||||
* @param maxHeight 限制的最大高度
|
||||
* @return 宽高小于限制值的Bitmap对象
|
||||
*/
|
||||
public static Bitmap jpegToScaledBitmap(byte[] jpeg, int maxWidth, int maxHeight) {
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
options.inJustDecodeBounds = true;
|
||||
BitmapFactory.decodeByteArray(jpeg, 0, jpeg.length, options);
|
||||
|
||||
int inSampleSize = 1;
|
||||
while (options.outWidth / inSampleSize > maxWidth || options.outHeight / inSampleSize > maxHeight) {
|
||||
inSampleSize++;
|
||||
}
|
||||
options.inSampleSize = inSampleSize;
|
||||
options.inJustDecodeBounds = false;
|
||||
return BitmapFactory.decodeByteArray(jpeg, 0, jpeg.length, options);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package io.flutter.plugins.facediscern.tool.face.model;
|
||||
|
||||
|
||||
|
||||
public class CompareResult {
|
||||
private FaceEntity faceEntity;
|
||||
private float similar;
|
||||
private int trackId;
|
||||
private int compareCode;
|
||||
private long cost;
|
||||
|
||||
public CompareResult(FaceEntity faceEntity, float similar, int compareCode,long cost) {
|
||||
this.faceEntity = faceEntity;
|
||||
this.similar = similar;
|
||||
this.compareCode = compareCode;
|
||||
this.cost = cost;
|
||||
}
|
||||
|
||||
public FaceEntity getFaceEntity() {
|
||||
return faceEntity;
|
||||
}
|
||||
|
||||
public void setFaceEntity(FaceEntity faceEntity) {
|
||||
this.faceEntity = faceEntity;
|
||||
}
|
||||
|
||||
public float getSimilar() {
|
||||
return similar;
|
||||
}
|
||||
|
||||
public void setSimilar(float similar) {
|
||||
this.similar = similar;
|
||||
}
|
||||
|
||||
public int getTrackId() {
|
||||
return trackId;
|
||||
}
|
||||
|
||||
public void setTrackId(int trackId) {
|
||||
this.trackId = trackId;
|
||||
}
|
||||
|
||||
public int getCompareCode() {
|
||||
return compareCode;
|
||||
}
|
||||
|
||||
public void setCompareCode(int compareCode) {
|
||||
this.compareCode = compareCode;
|
||||
}
|
||||
|
||||
public long getCost() {
|
||||
return cost;
|
||||
}
|
||||
|
||||
public void setCost(long cost) {
|
||||
this.cost = cost;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package io.flutter.plugins.facediscern.tool.face.model;
|
||||
|
||||
|
||||
public class FaceEntity {
|
||||
// 人脸模型id
|
||||
Integer faceId;
|
||||
// 用户id
|
||||
String userId;
|
||||
// 用户人脸样本id
|
||||
Integer sampleId;
|
||||
// 用户名
|
||||
String userName;
|
||||
// 人脸图片文件路径
|
||||
String faceFilePath;
|
||||
// 人脸模型
|
||||
byte[] faceModel;
|
||||
|
||||
public FaceEntity() {
|
||||
|
||||
}
|
||||
|
||||
public FaceEntity(byte[] faceModel) {
|
||||
this.faceModel = faceModel;
|
||||
}
|
||||
|
||||
public FaceEntity(Integer faceId, byte[] faceModel) {
|
||||
this.faceId = faceId;
|
||||
this.faceModel = faceModel;
|
||||
}
|
||||
|
||||
public Integer getFaceId() {
|
||||
return faceId;
|
||||
}
|
||||
|
||||
public void setFaceId(Integer faceId) {
|
||||
this.faceId = faceId;
|
||||
}
|
||||
|
||||
public byte[] getFaceModel() {
|
||||
return faceModel;
|
||||
}
|
||||
|
||||
public void setFaceModel(byte[] faceModel) {
|
||||
this.faceModel = faceModel;
|
||||
}
|
||||
|
||||
public String getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public void setUserId(String userId) {
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
public Integer getSampleId() {
|
||||
return sampleId;
|
||||
}
|
||||
|
||||
public void setSampleId(Integer sampleId) {
|
||||
this.sampleId = sampleId;
|
||||
}
|
||||
|
||||
public String getUserName() {
|
||||
return userName;
|
||||
}
|
||||
|
||||
public void setUserName(String userName) {
|
||||
this.userName = userName;
|
||||
}
|
||||
|
||||
public String getFaceFilePath() {
|
||||
return faceFilePath;
|
||||
}
|
||||
|
||||
public void setFaceFilePath(String faceFilePath) {
|
||||
this.faceFilePath = faceFilePath;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
package io.flutter.plugins.facediscern.tool.face.model;
|
||||
|
||||
import android.graphics.Rect;
|
||||
|
||||
import com.arcsoft.face.FaceInfo;
|
||||
import com.arcsoft.face.LivenessInfo;
|
||||
|
||||
/**
|
||||
* 人脸追踪时的信息
|
||||
*/
|
||||
public class FacePreviewInfo {
|
||||
/**
|
||||
* RGB人脸信息,包括人脸框和人脸角度
|
||||
*/
|
||||
private FaceInfo faceInfoRgb;
|
||||
/**
|
||||
* IR人脸信息,包括人脸框和人脸角度
|
||||
*/
|
||||
private FaceInfo faceInfoIr;
|
||||
/**
|
||||
* 可见光成像对应的用于FaceRectView绘制的Rect
|
||||
*/
|
||||
private Rect rgbTransformedRect;
|
||||
/**
|
||||
* 红外成像对应的用于FaceRectView绘制的Rect
|
||||
*/
|
||||
private Rect irTransformedRect;
|
||||
private int rgbLiveness = LivenessInfo.UNKNOWN;
|
||||
private int irLiveness = LivenessInfo.UNKNOWN;
|
||||
private float imageQuality = 0f;
|
||||
/**
|
||||
* 识别区域是否合法
|
||||
*/
|
||||
private boolean recognizeAreaValid;
|
||||
/**
|
||||
* 基于{@link FaceInfo#getFaceId()}的一个偏移值,可理解为SDK截至目前检测到的人次,唯一性同faceId
|
||||
*/
|
||||
private int trackId;
|
||||
/**
|
||||
* 整体质量是否通过,包括人脸大小、角度、移动速度等
|
||||
*/
|
||||
private boolean qualityPass = true;
|
||||
|
||||
/**
|
||||
* 是否戴口罩
|
||||
*/
|
||||
private int mask;
|
||||
|
||||
private Rect foreRect;
|
||||
|
||||
public Rect getForeRect() {
|
||||
return foreRect;
|
||||
}
|
||||
|
||||
public void setForeRect(Rect foreRect) {
|
||||
this.foreRect = foreRect;
|
||||
}
|
||||
|
||||
|
||||
public FacePreviewInfo(FaceInfo faceInfoRgb) {
|
||||
this.faceInfoRgb = faceInfoRgb;
|
||||
}
|
||||
public FacePreviewInfo(FaceInfo faceInfoRgb, int trackId) {
|
||||
this.faceInfoRgb = faceInfoRgb;
|
||||
this.trackId = trackId;
|
||||
}
|
||||
|
||||
public FaceInfo getFaceInfoRgb() {
|
||||
return faceInfoRgb;
|
||||
}
|
||||
|
||||
public void setFaceInfoRgb(FaceInfo faceInfoRgb) {
|
||||
this.faceInfoRgb = faceInfoRgb;
|
||||
}
|
||||
|
||||
|
||||
public int getTrackId() {
|
||||
return trackId;
|
||||
}
|
||||
|
||||
public void setTrackId(int trackId) {
|
||||
this.trackId = trackId;
|
||||
}
|
||||
|
||||
public void setRgbTransformedRect(Rect rgbTransformedRect) {
|
||||
this.rgbTransformedRect = rgbTransformedRect;
|
||||
}
|
||||
|
||||
public void setIrTransformedRect(Rect irTransformedRect) {
|
||||
this.irTransformedRect = irTransformedRect;
|
||||
}
|
||||
|
||||
public Rect getRgbTransformedRect() {
|
||||
return rgbTransformedRect;
|
||||
}
|
||||
|
||||
public Rect getIrTransformedRect() {
|
||||
return irTransformedRect;
|
||||
}
|
||||
|
||||
public boolean isRecognizeAreaValid() {
|
||||
return recognizeAreaValid;
|
||||
}
|
||||
|
||||
public void setRecognizeAreaValid(boolean recognizeAreaValid) {
|
||||
this.recognizeAreaValid = recognizeAreaValid;
|
||||
}
|
||||
|
||||
public void setFaceInfoIr(FaceInfo faceInfoIr) {
|
||||
this.faceInfoIr = faceInfoIr;
|
||||
}
|
||||
|
||||
public FaceInfo getFaceInfoIr() {
|
||||
return faceInfoIr;
|
||||
}
|
||||
|
||||
public int getRgbLiveness() {
|
||||
return rgbLiveness;
|
||||
}
|
||||
|
||||
public void setRgbLiveness(int rgbLiveness) {
|
||||
this.rgbLiveness = rgbLiveness;
|
||||
}
|
||||
|
||||
public int getIrLiveness() {
|
||||
return irLiveness;
|
||||
}
|
||||
|
||||
public void setIrLiveness(int irLiveness) {
|
||||
this.irLiveness = irLiveness;
|
||||
}
|
||||
|
||||
public void setImageQuality(float imageQuality) {
|
||||
this.imageQuality = imageQuality;
|
||||
}
|
||||
|
||||
public float getImageQuality() {
|
||||
return imageQuality;
|
||||
}
|
||||
|
||||
public boolean isQualityPass() {
|
||||
return qualityPass;
|
||||
}
|
||||
|
||||
public void setQualityPass(boolean qualityPass) {
|
||||
this.qualityPass = qualityPass;
|
||||
}
|
||||
|
||||
public int getMask() {
|
||||
return mask;
|
||||
}
|
||||
|
||||
public void setMask(int mask) {
|
||||
this.mask = mask;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,300 @@
|
||||
package io.flutter.plugins.facediscern.tool.face.model;
|
||||
|
||||
import com.arcsoft.face.LivenessParam;
|
||||
|
||||
/**
|
||||
* 识别相关的配置项
|
||||
*/
|
||||
public class RecognizeConfiguration {
|
||||
/**
|
||||
* 产生特征提取失败示语的特征提取次数(小于该值不提示)
|
||||
*/
|
||||
private int extractRetryCount;
|
||||
/**
|
||||
* 产生活体检测失败示语的活体检测次数(小于该值不提示)
|
||||
*/
|
||||
private int livenessRetryCount;
|
||||
/**
|
||||
* 最大人脸检测数量
|
||||
*/
|
||||
private int maxDetectFaces;
|
||||
/**
|
||||
* 识别阈值
|
||||
*/
|
||||
private float similarThreshold;
|
||||
/**
|
||||
* 图像质量检测阈值:适用于不戴口罩且人脸识别场景
|
||||
*/
|
||||
private float imageQualityNoMaskRecognizeThreshold;
|
||||
/**
|
||||
* 图像质量检测阈值:适用于戴口罩且人脸识别场景
|
||||
*/
|
||||
private float imageQualityMaskRecognizeThreshold;
|
||||
/**
|
||||
* 识别失败重试间隔
|
||||
*/
|
||||
private int recognizeFailedRetryInterval;
|
||||
/**
|
||||
* 活体检测未通过重试间隔
|
||||
*/
|
||||
private int livenessFailedRetryInterval;
|
||||
/**
|
||||
* 启用活体
|
||||
*/
|
||||
private boolean enableLiveness;
|
||||
/**
|
||||
* 启用图像质量检测
|
||||
*/
|
||||
private boolean enableImageQuality;
|
||||
/**
|
||||
* 识别区域限制
|
||||
*/
|
||||
private boolean enableFaceAreaLimit;
|
||||
/**
|
||||
* 仅识别最大人脸
|
||||
*/
|
||||
private boolean keepMaxFace;
|
||||
/**
|
||||
* 活体阈值设置
|
||||
*/
|
||||
private LivenessParam livenessParam;
|
||||
|
||||
|
||||
/**
|
||||
* 启用人脸边长限制
|
||||
*/
|
||||
private boolean enableFaceSizeLimit = false;
|
||||
/**
|
||||
* 启用人脸移动限制
|
||||
*/
|
||||
private boolean enableFaceMoveLimit = false;
|
||||
/**
|
||||
* 人脸边长限制值
|
||||
*/
|
||||
private int faceSizeLimit = 0;
|
||||
/**
|
||||
* 人脸上下针移动限制值
|
||||
*/
|
||||
private int faceMoveLimit = 0;
|
||||
|
||||
|
||||
public RecognizeConfiguration(Builder builder) {
|
||||
this.extractRetryCount = builder.extractRetryCount;
|
||||
this.livenessRetryCount = builder.livenessRetryCount;
|
||||
this.livenessFailedRetryInterval = builder.livenessFailedRetryInterval;
|
||||
this.maxDetectFaces = builder.maxDetectFaces;
|
||||
this.similarThreshold = builder.similarThreshold;
|
||||
this.imageQualityNoMaskRecognizeThreshold = builder.imageQualityNoMaskRecognizeThreshold;
|
||||
this.imageQualityMaskRecognizeThreshold = builder.imageQualityMaskRecognizeThreshold;
|
||||
this.enableLiveness = builder.enableLiveness;
|
||||
this.enableImageQuality = builder.enableImageQuality;
|
||||
this.enableFaceAreaLimit = builder.enableFaceAreaLimit;
|
||||
this.keepMaxFace = builder.keepMaxFace;
|
||||
this.recognizeFailedRetryInterval = builder.recognizeFailedRetryInterval;
|
||||
this.livenessParam = builder.livenessParam;
|
||||
this.enableFaceSizeLimit = builder.enableFaceSizeLimit;
|
||||
this.enableFaceMoveLimit = builder.enableFaceMoveLimit;
|
||||
this.faceSizeLimit = builder.faceSizeLimit;
|
||||
this.faceMoveLimit = builder.faceMoveLimit;
|
||||
}
|
||||
|
||||
//TODO: demo不实现所有配置,若以下项也需要进行自定义配置,可参考其他配置项实现
|
||||
public static class Builder {
|
||||
private int extractRetryCount = 3;
|
||||
private int livenessRetryCount = 3;
|
||||
private int maxDetectFaces = 3;
|
||||
private int recognizeFailedRetryInterval = 0;
|
||||
private int livenessFailedRetryInterval = 0;
|
||||
private float similarThreshold = 0.8f;
|
||||
private float imageQualityNoMaskRecognizeThreshold = 0.49f;
|
||||
private float imageQualityMaskRecognizeThreshold = 0.29f;
|
||||
private boolean enableLiveness = false;
|
||||
private boolean enableFaceAreaLimit = false;
|
||||
private boolean enableImageQuality = false;
|
||||
private boolean enableFaceSizeLimit = false;
|
||||
private boolean enableFaceMoveLimit = false;
|
||||
private int faceSizeLimit = 0;
|
||||
private int faceMoveLimit = 0;
|
||||
private boolean keepMaxFace = false;
|
||||
private LivenessParam livenessParam;
|
||||
|
||||
public Builder recognizeFailedRetryInterval(int val) {
|
||||
this.recognizeFailedRetryInterval = val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder livenessFailedRetryInterval(int val) {
|
||||
this.livenessFailedRetryInterval = val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder extractRetryCount(int val) {
|
||||
this.extractRetryCount = val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder livenessRetryCount(int val) {
|
||||
this.livenessRetryCount = val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder maxDetectFaces(int val) {
|
||||
this.maxDetectFaces = val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder similarThreshold(float val) {
|
||||
this.similarThreshold = val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder imageQualityNoMaskRecognizeThreshold(float val) {
|
||||
this.imageQualityNoMaskRecognizeThreshold = val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder imageQualityMaskRecognizeThreshold(float val) {
|
||||
this.imageQualityMaskRecognizeThreshold = val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder enableLiveness(boolean val) {
|
||||
this.enableLiveness = val;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public Builder enableImageQuality(boolean val) {
|
||||
this.enableImageQuality = val;
|
||||
return this;
|
||||
}
|
||||
public Builder enableFaceAreaLimit(boolean val) {
|
||||
this.enableFaceAreaLimit = val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder enableFaceSizeLimit(boolean val) {
|
||||
this.enableFaceSizeLimit = val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder enableFaceMoveLimit(boolean val) {
|
||||
this.enableFaceMoveLimit = val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder faceSizeLimit(int val) {
|
||||
this.faceSizeLimit = val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder faceMoveLimit(int val) {
|
||||
this.faceMoveLimit = val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder keepMaxFace(boolean val) {
|
||||
this.keepMaxFace = val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder livenessParam(LivenessParam val) {
|
||||
this.livenessParam = val;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public RecognizeConfiguration build() {
|
||||
return new RecognizeConfiguration(this);
|
||||
}
|
||||
}
|
||||
|
||||
public float getImageQualityNoMaskRecognizeThreshold() {
|
||||
return imageQualityNoMaskRecognizeThreshold;
|
||||
}
|
||||
|
||||
public float getImageQualityMaskRecognizeThreshold() {
|
||||
return imageQualityMaskRecognizeThreshold;
|
||||
}
|
||||
|
||||
public boolean isEnableImageQuality() {
|
||||
return enableImageQuality;
|
||||
}
|
||||
|
||||
public boolean isEnableFaceAreaLimit() {
|
||||
return enableFaceAreaLimit;
|
||||
}
|
||||
|
||||
public LivenessParam getLivenessParam() {
|
||||
return livenessParam;
|
||||
}
|
||||
|
||||
public int getExtractRetryCount() {
|
||||
return extractRetryCount;
|
||||
}
|
||||
|
||||
public int getLivenessRetryCount() {
|
||||
return livenessRetryCount;
|
||||
}
|
||||
|
||||
public int getMaxDetectFaces() {
|
||||
return maxDetectFaces;
|
||||
}
|
||||
|
||||
public float getSimilarThreshold() {
|
||||
return similarThreshold;
|
||||
}
|
||||
|
||||
public boolean isEnableLiveness() {
|
||||
return enableLiveness;
|
||||
}
|
||||
|
||||
|
||||
public int getRecognizeFailedRetryInterval() {
|
||||
return recognizeFailedRetryInterval;
|
||||
}
|
||||
|
||||
public int getLivenessFailedRetryInterval() {
|
||||
return livenessFailedRetryInterval;
|
||||
}
|
||||
|
||||
public boolean isKeepMaxFace() {
|
||||
return keepMaxFace;
|
||||
}
|
||||
|
||||
public boolean isEnableFaceSizeLimit() {
|
||||
return enableFaceSizeLimit;
|
||||
}
|
||||
|
||||
public boolean isEnableFaceMoveLimit() {
|
||||
return enableFaceMoveLimit;
|
||||
}
|
||||
|
||||
public int getFaceSizeLimit() {
|
||||
return faceSizeLimit;
|
||||
}
|
||||
|
||||
public int getFaceMoveLimit() {
|
||||
return faceMoveLimit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return
|
||||
"extractRetryCount: " + extractRetryCount + "\r\n" +
|
||||
"similarThreshold: " + similarThreshold + "\r\n" +
|
||||
"recognizeFailedRetryInterval: " + recognizeFailedRetryInterval + "\r\n" +
|
||||
|
||||
"keepMaxFace: " + keepMaxFace + "\r\n" +
|
||||
"maxDetectFaces: " + maxDetectFaces + "\r\n" +
|
||||
|
||||
"enableImageQuality: " + enableImageQuality + "\r\n" +
|
||||
"imageQualityNoMaskRecognizeThreshold: " + imageQualityNoMaskRecognizeThreshold + "\r\n" +
|
||||
"imageQualityMaskRecognizeThreshold: " + imageQualityMaskRecognizeThreshold + "\r\n" +
|
||||
|
||||
"enableLiveness: " + enableLiveness + "\r\n" +
|
||||
"livenessRetryCount: " + livenessRetryCount + "\r\n" +
|
||||
"livenessParams: " + (livenessParam == null ? null : (livenessParam.getRgbThreshold() + "," + livenessParam.getIrThreshold()));
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package io.flutter.plugins.facediscern.tool.face.model;
|
||||
|
||||
import com.arcsoft.face.LivenessInfo;
|
||||
import io.flutter.plugins.facediscern.tool.face.constants.RequestFeatureStatus;
|
||||
|
||||
/**
|
||||
* 单个人脸(faceId)识别过程中的信息
|
||||
*/
|
||||
public class RecognizeInfo {
|
||||
/**
|
||||
* 用于记录人脸识别相关状态
|
||||
*/
|
||||
private int recognizeStatus = RequestFeatureStatus.TO_RETRY;
|
||||
/**
|
||||
* 用于记录人脸特征提取出错重试次数
|
||||
*/
|
||||
private int extractErrorRetryCount;
|
||||
/**
|
||||
* 用于存储活体值
|
||||
*/
|
||||
private int liveness = LivenessInfo.UNKNOWN;
|
||||
/**
|
||||
* 用于存储活体检测出错重试次数
|
||||
*/
|
||||
private int livenessErrorRetryCount;
|
||||
/**
|
||||
* 用户姓名,用于显示
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 特征等活体的lock
|
||||
*/
|
||||
private Object waitLock = new Object();
|
||||
|
||||
public int getRecognizeStatus() {
|
||||
return recognizeStatus;
|
||||
}
|
||||
|
||||
public void setRecognizeStatus(int recognizeStatus) {
|
||||
this.recognizeStatus = recognizeStatus;
|
||||
}
|
||||
|
||||
public void setLiveness(int liveness) {
|
||||
this.liveness = liveness;
|
||||
}
|
||||
|
||||
public int increaseAndGetExtractErrorRetryCount() {
|
||||
return ++extractErrorRetryCount;
|
||||
}
|
||||
|
||||
public int getLiveness() {
|
||||
return liveness;
|
||||
}
|
||||
|
||||
public int increaseAndGetLivenessErrorRetryCount() {
|
||||
return ++livenessErrorRetryCount;
|
||||
}
|
||||
|
||||
public void setExtractErrorRetryCount(int extractErrorRetryCount) {
|
||||
this.extractErrorRetryCount = extractErrorRetryCount;
|
||||
}
|
||||
|
||||
public void setLivenessErrorRetryCount(int livenessErrorRetryCount) {
|
||||
this.livenessErrorRetryCount = livenessErrorRetryCount;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Object getWaitLock() {
|
||||
return waitLock;
|
||||
}
|
||||
|
||||
public int getExtractErrorRetryCount() {
|
||||
return extractErrorRetryCount;
|
||||
}
|
||||
|
||||
public int getLivenessErrorRetryCount() {
|
||||
return livenessErrorRetryCount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,359 @@
|
||||
package io.flutter.plugins.facediscern.tool.face.servcie;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Environment;
|
||||
import android.util.Log;
|
||||
|
||||
import com.arcsoft.face.ErrorInfo;
|
||||
import com.arcsoft.face.FaceEngine;
|
||||
import com.arcsoft.face.FaceFeature;
|
||||
import com.arcsoft.face.FaceFeatureInfo;
|
||||
import com.arcsoft.face.FaceInfo;
|
||||
import com.arcsoft.face.MaskInfo;
|
||||
import com.arcsoft.face.SearchResult;
|
||||
import com.arcsoft.face.enums.DetectFaceOrientPriority;
|
||||
import com.arcsoft.face.enums.DetectMode;
|
||||
import com.arcsoft.face.enums.ExtractType;
|
||||
import com.arcsoft.imageutil.ArcSoftImageFormat;
|
||||
import com.arcsoft.imageutil.ArcSoftImageUtil;
|
||||
import com.arcsoft.imageutil.ArcSoftImageUtilError;
|
||||
import com.arcsoft.imageutil.ArcSoftRotateDegree;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import io.flutter.plugins.facediscern.tool.face.model.CompareResult;
|
||||
import io.flutter.plugins.facediscern.tool.face.model.FaceEntity;
|
||||
|
||||
/**
|
||||
* 人脸库操作类,包含注册和搜索
|
||||
*/
|
||||
public class FaceServer {
|
||||
private static final String TAG = "FaceServer";
|
||||
private static FaceEngine faceEngine = null;
|
||||
private static volatile FaceServer faceServer = null;
|
||||
private List<FaceEntity> faceRegisterInfoList = new ArrayList<>();
|
||||
private String imageRootPath;
|
||||
/**
|
||||
* 最大注册人脸数
|
||||
*/
|
||||
private static final int MAX_REGISTER_FACE_COUNT = 30000;
|
||||
|
||||
public synchronized void init(Context context) {
|
||||
if (faceEngine == null && context != null) {
|
||||
faceEngine = new FaceEngine();
|
||||
int engineCode = faceEngine.init(
|
||||
context,
|
||||
DetectMode.ASF_DETECT_MODE_IMAGE,
|
||||
DetectFaceOrientPriority.ASF_OP_ALL_OUT,
|
||||
1,
|
||||
FaceEngine.ASF_FACE_RECOGNITION | FaceEngine.ASF_FACE_DETECT | FaceEngine.ASF_MASK_DETECT
|
||||
);
|
||||
if (engineCode != ErrorInfo.MOK) {
|
||||
Log.e(TAG, "人脸引擎初始化失败code" + engineCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static FaceServer getInstance() {
|
||||
if (faceServer == null) {
|
||||
synchronized (FaceServer.class) {
|
||||
if (faceServer == null) {
|
||||
faceServer = new FaceServer();
|
||||
}
|
||||
}
|
||||
}
|
||||
return faceServer;
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁
|
||||
*/
|
||||
public synchronized void release() {
|
||||
if (faceRegisterInfoList != null) {
|
||||
faceRegisterInfoList.clear();
|
||||
faceRegisterInfoList = null;
|
||||
}
|
||||
if (faceEngine != null) {
|
||||
synchronized (faceEngine) {
|
||||
faceEngine.unInit();
|
||||
}
|
||||
faceEngine = null;
|
||||
}
|
||||
faceServer = null;
|
||||
}
|
||||
|
||||
public int registerFaceFeatureInfoListFromDb(List<FaceEntity> faceEntityList) {
|
||||
if (faceEngine == null) return -99;
|
||||
List<FaceFeatureInfo> faceFeatureInfoList = new ArrayList<>();
|
||||
for (FaceEntity faceEntity : faceEntityList) {
|
||||
FaceFeatureInfo faceFeatureInfo = new FaceFeatureInfo((int) faceEntity.getFaceId(), faceEntity.getFaceModel());
|
||||
faceFeatureInfoList.add(faceFeatureInfo);
|
||||
}
|
||||
//首先清除FaceEngine中所有人脸数据,再添加新的人脸数据
|
||||
faceEngine.removeFaceFeature(-1);
|
||||
int code = faceEngine.registerFaceFeature(faceFeatureInfoList);
|
||||
if (code == ErrorInfo.MOK) {
|
||||
faceRegisterInfoList.addAll(faceEntityList);
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
// 注册单个人脸数据
|
||||
public int registerFaceFeatureInfoFromDb(FaceEntity faceEntity) {
|
||||
if (faceEntity != null && faceEngine != null) {
|
||||
FaceFeatureInfo faceFeatureInfo = new FaceFeatureInfo(faceEntity.getFaceId(), faceEntity.getFaceModel());
|
||||
int code = faceEngine.registerFaceFeature(faceFeatureInfo);
|
||||
if (code == ErrorInfo.MOK) {
|
||||
faceEntity.setFaceModel(faceFeatureInfo.getFeatureData());
|
||||
// 查询一下确保入库成功
|
||||
FaceFeatureInfo afterDetail = getFaceFeature(faceEntity.getFaceId());
|
||||
if (Objects.nonNull(afterDetail)) {
|
||||
long count = faceRegisterInfoList.stream().filter(v -> Objects.equals(v.getFaceId(), faceEntity.getFaceId())).count();
|
||||
if (count == 0) {
|
||||
faceRegisterInfoList.add(faceEntity);
|
||||
}
|
||||
return code;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -99;
|
||||
}
|
||||
|
||||
// 从人脸数据库中搜索人脸
|
||||
public FaceFeatureInfo getFaceFeature(int searchId) {
|
||||
FaceFeatureInfo faceFeatureInfo = null;
|
||||
try {
|
||||
faceFeatureInfo = faceEngine.getFaceFeature(searchId);
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.e(TAG, "从人脸数据库中搜索人脸error:" + e.getMessage(), e);
|
||||
}
|
||||
return faceFeatureInfo;
|
||||
}
|
||||
|
||||
// 获取人脸数量
|
||||
public int getTotalFaceCount() {
|
||||
if (faceEngine == null) return -99;
|
||||
return faceEngine.getFaceCount();
|
||||
}
|
||||
|
||||
// 删除人脸-1删除全部
|
||||
public int removeFace(int searchId) {
|
||||
if (faceEngine == null) return -99;
|
||||
return faceEngine.removeFaceFeature(searchId);
|
||||
}
|
||||
|
||||
// 在特征库中搜索
|
||||
public CompareResult searchFaceFeature(FaceFeature faceFeature) {
|
||||
if (faceFeature == null) return null;
|
||||
if (faceEngine == null) return null;
|
||||
SearchResult searchResult = null;
|
||||
try {
|
||||
long searchStart = System.currentTimeMillis();
|
||||
searchResult = faceEngine.searchFaceFeature(faceFeature);
|
||||
long searchCost = System.currentTimeMillis() - searchStart;
|
||||
if (searchResult != null) {
|
||||
FaceFeatureInfo faceFeatureInfo = searchResult.getFaceFeatureInfo();
|
||||
FaceEntity entity = faceRegisterInfoList.stream().filter(v -> Objects.equals(v.getFaceId(), faceFeatureInfo.getSearchId())).findFirst().get();
|
||||
if (Objects.isNull(entity)) {
|
||||
entity = new FaceEntity(faceFeatureInfo.getSearchId(), faceFeatureInfo.getFeatureData());
|
||||
}
|
||||
Log.i(TAG, "查询人脸总耗时:" + (System.currentTimeMillis() - searchStart) + "ms;username:" + entity.getUserName() + ";faceId:" + entity.getFaceId() + ";");
|
||||
return new CompareResult(entity, searchResult.getMaxSimilar(), ErrorInfo.MOK, searchCost);
|
||||
}
|
||||
} catch (IllegalArgumentException exception) {
|
||||
Log.i(TAG, "exception:" + exception.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public byte[] registerBgr24(Context context, byte[] bgr24, int width, int height, String name) {
|
||||
if (faceEngine == null || context == null || bgr24 == null || width % 4 != 0 || bgr24.length != width * height * 3) {
|
||||
Log.e(TAG, "registerBgr24: invalid params");
|
||||
return null;
|
||||
}
|
||||
//人脸检测
|
||||
List<FaceInfo> faceInfoList = new ArrayList<>();
|
||||
int code;
|
||||
synchronized (faceEngine) {
|
||||
code = faceEngine.detectFaces(bgr24, width, height, FaceEngine.CP_PAF_BGR24, faceInfoList);
|
||||
}
|
||||
if (code == ErrorInfo.MOK && !faceInfoList.isEmpty()) {
|
||||
code = faceEngine.process(bgr24, width, height, FaceEngine.CP_PAF_BGR24, faceInfoList,
|
||||
FaceEngine.ASF_MASK_DETECT);
|
||||
if (code == ErrorInfo.MOK) {
|
||||
List<MaskInfo> maskInfoList = new ArrayList<>();
|
||||
faceEngine.getMask(maskInfoList);
|
||||
if (!maskInfoList.isEmpty()) {
|
||||
int isMask = maskInfoList.get(0).getMask();
|
||||
if (isMask == MaskInfo.WORN) {
|
||||
/*
|
||||
* 注册照要求不戴口罩
|
||||
*/
|
||||
Log.e(TAG, "registerBgr24: maskInfo is worn");
|
||||
return null;
|
||||
}
|
||||
if (isMask == MaskInfo.UNKNOWN) {
|
||||
Log.e(TAG, "registerBgr24: maskInfo is unknown");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.e(TAG, "registerBgr24 mask process fail res=" + code);
|
||||
return null;
|
||||
}
|
||||
FaceFeature faceFeature = new FaceFeature();
|
||||
/*
|
||||
* 特征提取,注册人脸时参数extractType值为ExtractType.REGISTER,参数mask的值为MaskInfo.NOT_WORN
|
||||
*/
|
||||
synchronized (faceEngine) {
|
||||
code = faceEngine.extractFaceFeature(bgr24, width, height, FaceEngine.CP_PAF_BGR24, faceInfoList.get(0),
|
||||
ExtractType.REGISTER, MaskInfo.NOT_WORN, faceFeature);
|
||||
}
|
||||
String userName = name == null ? String.valueOf(System.currentTimeMillis()) : name;
|
||||
//保存注册结果(注册图、特征数据)
|
||||
if (code == ErrorInfo.MOK) {
|
||||
//为了美观,扩大rect截取注册图
|
||||
Rect cropRect = getBestRect(width, height, faceInfoList.get(0).getRect());
|
||||
if (cropRect == null) {
|
||||
Log.e(TAG, "registerBgr24: cropRect is null");
|
||||
return null;
|
||||
}
|
||||
cropRect.left &= ~3;
|
||||
cropRect.top &= ~3;
|
||||
cropRect.right &= ~3;
|
||||
cropRect.bottom &= ~3;
|
||||
String imgPath = getImagePath(userName, context);
|
||||
// 创建一个头像的Bitmap,存放旋转结果图
|
||||
Bitmap headBmp = getHeadImage(bgr24, width, height, faceInfoList.get(0).getOrient(), cropRect, ArcSoftImageFormat.BGR24);
|
||||
try {
|
||||
FileOutputStream fos = new FileOutputStream(imgPath);
|
||||
headBmp.compress(Bitmap.CompressFormat.JPEG, 100, fos);
|
||||
fos.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
return faceFeature.getFeatureData();
|
||||
} else {
|
||||
Log.e(TAG, "registerBgr24: extract face feature failed, code is " + code);
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
Log.e(TAG, "registerBgr24: no face detected, code is " + code);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private String getImagePath(String name, Context context) {
|
||||
if (imageRootPath == null) {
|
||||
imageRootPath = getImageDir(context);
|
||||
File dir = new File(imageRootPath);
|
||||
if (!dir.exists() && !dir.mkdirs()) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return imageRootPath + File.separator + name + "_" + System.currentTimeMillis() + ".jpg";
|
||||
}
|
||||
|
||||
private String getImageDir(Context context) {
|
||||
String path = context
|
||||
.getApplicationContext()
|
||||
.getExternalFilesDir(Environment.DIRECTORY_PICTURES)
|
||||
+ File.separator + "faceDB" + File.separator + "registerFaces";
|
||||
Log.i(TAG, "存储图片的地址为path:" + path);
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将图像中需要截取的Rect向外扩张一倍,若扩张一倍会溢出,则扩张到边界,若Rect已溢出,则收缩到边界
|
||||
*
|
||||
* @param width 图像宽度
|
||||
* @param height 图像高度
|
||||
* @param srcRect 原Rect
|
||||
* @return 调整后的Rect
|
||||
*/
|
||||
private static Rect getBestRect(int width, int height, Rect srcRect) {
|
||||
if (srcRect == null) {
|
||||
return null;
|
||||
}
|
||||
Rect rect = new Rect(srcRect);
|
||||
|
||||
// 原rect边界已溢出宽高的情况
|
||||
int maxOverFlow = Math.max(-rect.left, Math.max(-rect.top, Math.max(rect.right - width, rect.bottom - height)));
|
||||
if (maxOverFlow >= 0) {
|
||||
rect.inset(maxOverFlow, maxOverFlow);
|
||||
return rect;
|
||||
}
|
||||
|
||||
// 原rect边界未溢出宽高的情况
|
||||
int padding = rect.height() / 2;
|
||||
|
||||
// 若以此padding扩张rect会溢出,取最大padding为四个边距的最小值
|
||||
if (!(rect.left - padding > 0 && rect.right + padding < width && rect.top - padding > 0 && rect.bottom + padding < height)) {
|
||||
padding = Math.min(Math.min(Math.min(rect.left, width - rect.right), height - rect.bottom), rect.top);
|
||||
}
|
||||
rect.inset(-padding, -padding);
|
||||
return rect;
|
||||
}
|
||||
|
||||
private Bitmap getHeadImage(byte[] originImageData, int width, int height, int orient, Rect cropRect, ArcSoftImageFormat imageFormat) {
|
||||
byte[] headImageData = ArcSoftImageUtil.createImageData(cropRect.width(), cropRect.height(), imageFormat);
|
||||
int cropCode = ArcSoftImageUtil.cropImage(originImageData, headImageData, width, height, cropRect, imageFormat);
|
||||
if (cropCode != ArcSoftImageUtilError.CODE_SUCCESS) {
|
||||
throw new RuntimeException("crop image failed, code is " + cropCode);
|
||||
}
|
||||
|
||||
//判断人脸旋转角度,若不为0度则旋转注册图
|
||||
byte[] rotateHeadImageData = null;
|
||||
int cropImageWidth;
|
||||
int cropImageHeight;
|
||||
// 90度或270度的情况,需要宽高互换
|
||||
if (orient == FaceEngine.ASF_OC_90 || orient == FaceEngine.ASF_OC_270) {
|
||||
cropImageWidth = cropRect.height();
|
||||
cropImageHeight = cropRect.width();
|
||||
} else {
|
||||
cropImageWidth = cropRect.width();
|
||||
cropImageHeight = cropRect.height();
|
||||
}
|
||||
ArcSoftRotateDegree rotateDegree = null;
|
||||
switch (orient) {
|
||||
case FaceEngine.ASF_OC_90:
|
||||
rotateDegree = ArcSoftRotateDegree.DEGREE_270;
|
||||
break;
|
||||
case FaceEngine.ASF_OC_180:
|
||||
rotateDegree = ArcSoftRotateDegree.DEGREE_180;
|
||||
break;
|
||||
case FaceEngine.ASF_OC_270:
|
||||
rotateDegree = ArcSoftRotateDegree.DEGREE_90;
|
||||
break;
|
||||
case FaceEngine.ASF_OC_0:
|
||||
default:
|
||||
rotateHeadImageData = headImageData;
|
||||
break;
|
||||
}
|
||||
// 非0度的情况,旋转图像
|
||||
if (rotateDegree != null) {
|
||||
rotateHeadImageData = new byte[headImageData.length];
|
||||
int rotateCode = ArcSoftImageUtil.rotateImage(headImageData, rotateHeadImageData, cropRect.width(), cropRect.height(), rotateDegree, imageFormat);
|
||||
if (rotateCode != ArcSoftImageUtilError.CODE_SUCCESS) {
|
||||
throw new RuntimeException("rotate image failed, code is : " + rotateCode + ", code description is : ");
|
||||
}
|
||||
}
|
||||
// 将创建一个Bitmap,并将图像数据存放到Bitmap中
|
||||
Bitmap headBmp = Bitmap.createBitmap(cropImageWidth, cropImageHeight, Bitmap.Config.RGB_565);
|
||||
int imageDataToBitmapCode = ArcSoftImageUtil.imageDataToBitmap(rotateHeadImageData, headBmp, imageFormat);
|
||||
if (imageDataToBitmapCode != ArcSoftImageUtilError.CODE_SUCCESS) {
|
||||
throw new RuntimeException("failed to transform image data to bitmap, code is : " + imageDataToBitmapCode
|
||||
+ ", code description is : ");
|
||||
}
|
||||
return headBmp;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package io.flutter.plugins.facediscern.tool.face.servcie;
|
||||
|
||||
public class RegisterFailedException extends Exception {
|
||||
public RegisterFailedException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,295 @@
|
||||
package io.flutter.plugins.facediscern.tool.face.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.Rect;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.arcsoft.face.AgeInfo;
|
||||
import com.arcsoft.face.FaceAttributeInfo;
|
||||
import com.arcsoft.face.GenderInfo;
|
||||
import com.arcsoft.face.LivenessInfo;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
/**
|
||||
* 用于显示人脸信息的控件
|
||||
*/
|
||||
public class FaceRectView extends View {
|
||||
private CopyOnWriteArrayList<DrawInfo> drawInfoList = new CopyOnWriteArrayList<>();
|
||||
|
||||
// 画笔,复用
|
||||
private Paint paint;
|
||||
|
||||
// 默认人脸框厚度
|
||||
private static final int DEFAULT_FACE_RECT_THICKNESS = 6;
|
||||
|
||||
public FaceRectView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public FaceRectView(Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
paint = new Paint();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
if (drawInfoList != null && drawInfoList.size() > 0) {
|
||||
for (int i = 0; i < drawInfoList.size(); i++) {
|
||||
drawFaceRect(canvas, drawInfoList.get(i), DEFAULT_FACE_RECT_THICKNESS, paint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void clearFaceInfo() {
|
||||
drawInfoList.clear();
|
||||
postInvalidate();
|
||||
}
|
||||
|
||||
public void addFaceInfo(DrawInfo faceInfo) {
|
||||
drawInfoList.add(faceInfo);
|
||||
postInvalidate();
|
||||
}
|
||||
|
||||
public void addFaceInfo(List<DrawInfo> faceInfoList) {
|
||||
drawInfoList.addAll(faceInfoList);
|
||||
postInvalidate();
|
||||
}
|
||||
|
||||
public void drawRealtimeFaceInfo(List<DrawInfo> drawInfoList) {
|
||||
clearFaceInfo();
|
||||
if (drawInfoList == null || drawInfoList.size() == 0) {
|
||||
return;
|
||||
}
|
||||
addFaceInfo(drawInfoList);
|
||||
}
|
||||
|
||||
public static class DrawInfo {
|
||||
private Rect rect;
|
||||
private int sex;
|
||||
private int age;
|
||||
private int liveness;
|
||||
private int color;
|
||||
private int isWithinBoundary;
|
||||
private String name = null;
|
||||
private boolean drawRectInfo;
|
||||
private Rect foreheadRect;
|
||||
private FaceAttributeInfo faceAttributeInfo;
|
||||
private boolean rgbRect;
|
||||
|
||||
public DrawInfo(Rect rect, int sex, int age, int liveness, int color, String name) {
|
||||
this.rect = rect;
|
||||
this.sex = sex;
|
||||
this.age = age;
|
||||
this.liveness = liveness;
|
||||
this.color = color;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public DrawInfo(Rect rect, int sex, int age, int liveness, int color, String name, int isWithinBoundary, Rect foreheadRect,
|
||||
FaceAttributeInfo faceAttributeInfo, boolean drawRectInfo, boolean rgbRect) {
|
||||
this.rect = rect;
|
||||
this.sex = sex;
|
||||
this.age = age;
|
||||
this.liveness = liveness;
|
||||
this.color = color;
|
||||
this.name = name;
|
||||
this.isWithinBoundary = isWithinBoundary;
|
||||
this.drawRectInfo = drawRectInfo;
|
||||
this.foreheadRect = foreheadRect;
|
||||
this.faceAttributeInfo = faceAttributeInfo;
|
||||
this.rgbRect = rgbRect;
|
||||
}
|
||||
|
||||
public DrawInfo(DrawInfo drawInfo) {
|
||||
if (drawInfo == null) {
|
||||
return;
|
||||
}
|
||||
this.rect = drawInfo.rect;
|
||||
this.sex = drawInfo.sex;
|
||||
this.age = drawInfo.age;
|
||||
this.liveness = drawInfo.liveness;
|
||||
this.color = drawInfo.color;
|
||||
this.name = drawInfo.name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Rect getRect() {
|
||||
return rect;
|
||||
}
|
||||
|
||||
public void setRect(Rect rect) {
|
||||
this.rect = rect;
|
||||
}
|
||||
|
||||
public int getSex() {
|
||||
return sex;
|
||||
}
|
||||
|
||||
public void setSex(int sex) {
|
||||
this.sex = sex;
|
||||
}
|
||||
|
||||
public int getAge() {
|
||||
return age;
|
||||
}
|
||||
|
||||
public void setAge(int age) {
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
public int getLiveness() {
|
||||
return liveness;
|
||||
}
|
||||
|
||||
public void setLiveness(int liveness) {
|
||||
this.liveness = liveness;
|
||||
}
|
||||
|
||||
public int getColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
public void setColor(int color) {
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
public boolean isDrawRectInfo() {
|
||||
return drawRectInfo;
|
||||
}
|
||||
|
||||
public void setDrawRectInfo(boolean drawRectInfo) {
|
||||
this.drawRectInfo = drawRectInfo;
|
||||
}
|
||||
|
||||
public Rect getForeheadRect() {
|
||||
return foreheadRect;
|
||||
}
|
||||
|
||||
public void setForeheadRect(Rect foreheadRect) {
|
||||
this.foreheadRect = foreheadRect;
|
||||
}
|
||||
|
||||
public FaceAttributeInfo getFaceAttributeInfo() {
|
||||
return faceAttributeInfo;
|
||||
}
|
||||
|
||||
public void setFaceAttributeInfo(FaceAttributeInfo faceAttributeInfo) {
|
||||
this.faceAttributeInfo = faceAttributeInfo;
|
||||
}
|
||||
|
||||
public int getIsWithinBoundary() {
|
||||
return isWithinBoundary;
|
||||
}
|
||||
|
||||
public void setIsWithinBoundary(int isWithinBoundary) {
|
||||
this.isWithinBoundary = isWithinBoundary;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 绘制数据信息到view上,若 {@link DrawInfo#getName()} 不为null则绘制 {@link DrawInfo#getName()}
|
||||
*
|
||||
* @param canvas 需要被绘制的view的canvas
|
||||
* @param drawInfo 绘制信息
|
||||
* @param faceRectThickness 人脸框厚度
|
||||
* @param paint 画笔
|
||||
*/
|
||||
private static void drawFaceRect(Canvas canvas, DrawInfo drawInfo, int faceRectThickness, Paint paint) {
|
||||
if (canvas == null || drawInfo == null) {
|
||||
return;
|
||||
}
|
||||
paint.setStyle(Paint.Style.STROKE);
|
||||
paint.setStrokeWidth(faceRectThickness);
|
||||
paint.setColor(drawInfo.getColor());
|
||||
paint.setAntiAlias(true);
|
||||
|
||||
Path mPath = new Path();
|
||||
// 左上
|
||||
Rect rect = drawInfo.getRect();
|
||||
mPath.moveTo(rect.left, rect.top + rect.height() / 4);
|
||||
mPath.lineTo(rect.left, rect.top);
|
||||
mPath.lineTo(rect.left + rect.width() / 4, rect.top);
|
||||
// 右上
|
||||
mPath.moveTo(rect.right - rect.width() / 4, rect.top);
|
||||
mPath.lineTo(rect.right, rect.top);
|
||||
mPath.lineTo(rect.right, rect.top + rect.height() / 4);
|
||||
// 右下
|
||||
mPath.moveTo(rect.right, rect.bottom - rect.height() / 4);
|
||||
mPath.lineTo(rect.right, rect.bottom);
|
||||
mPath.lineTo(rect.right - rect.width() / 4, rect.bottom);
|
||||
// 左下
|
||||
mPath.moveTo(rect.left + rect.width() / 4, rect.bottom);
|
||||
mPath.lineTo(rect.left, rect.bottom);
|
||||
mPath.lineTo(rect.left, rect.bottom - rect.height() / 4);
|
||||
canvas.drawPath(mPath, paint);
|
||||
|
||||
// 绘制文字,用最细的即可,避免在某些低像素设备上文字模糊
|
||||
paint.setStrokeWidth(1);
|
||||
|
||||
if (drawInfo.getName() == null) {
|
||||
paint.setStyle(Paint.Style.FILL_AND_STROKE);
|
||||
paint.setTextSize(rect.width() / 12);
|
||||
String str = (drawInfo.getSex() == GenderInfo.MALE ? "MALE" : (drawInfo.getSex() == GenderInfo.FEMALE ? "FEMALE" : "UNKNOWN"))
|
||||
+ ","
|
||||
+ (drawInfo.getAge() == AgeInfo.UNKNOWN_AGE ? "UNKNOWN" : drawInfo.getAge())
|
||||
+ ","
|
||||
+ (drawInfo.getLiveness() == LivenessInfo.ALIVE ? "ALIVE" : (drawInfo.getLiveness() == LivenessInfo.NOT_ALIVE ? "NOT_ALIVE" : "UNKNOWN"));
|
||||
canvas.drawText(str, rect.left, rect.top - 10, paint);
|
||||
} else {
|
||||
paint.setStyle(Paint.Style.FILL_AND_STROKE);
|
||||
paint.setTextSize(rect.width() / 12);
|
||||
canvas.drawText(drawInfo.getName(), rect.left, rect.top - 10, paint);
|
||||
}
|
||||
|
||||
if (drawInfo.drawRectInfo && drawInfo.rgbRect) {
|
||||
Rect foreRect = drawInfo.foreheadRect;
|
||||
if (foreRect != null) {
|
||||
Path forePath = new Path();
|
||||
forePath.moveTo(foreRect.left, foreRect.top);
|
||||
forePath.lineTo(foreRect.right, foreRect.top);
|
||||
forePath.lineTo(foreRect.right, foreRect.bottom);
|
||||
forePath.lineTo(foreRect.left, foreRect.bottom);
|
||||
forePath.lineTo(foreRect.left, foreRect.top);
|
||||
paint.setStyle(Paint.Style.STROKE);
|
||||
paint.setStrokeWidth(3);
|
||||
canvas.drawPath(forePath, paint);
|
||||
}
|
||||
|
||||
FaceAttributeInfo attributeInfo = drawInfo.getFaceAttributeInfo();
|
||||
if (attributeInfo != null) {
|
||||
paint.setStyle(Paint.Style.FILL_AND_STROKE);
|
||||
int textSize = rect.width() / 8;
|
||||
paint.setStrokeWidth(1);
|
||||
paint.setTextSize(textSize);
|
||||
int defX = rect.left;
|
||||
int defY = rect.bottom + rect.width() / 8;
|
||||
|
||||
String isStrInfo0 = drawInfo.getIsWithinBoundary() == 1 ? "是" : "否";
|
||||
String strInfo0 = "是否在相机范围内: " + isStrInfo0;
|
||||
canvas.drawText(strInfo0, defX, defY, paint);
|
||||
|
||||
String isStrInfo1 = attributeInfo.getWearGlasses() == 1 ? "是" : "否";
|
||||
String strInfo1 = "是否戴眼镜: " + isStrInfo1;
|
||||
canvas.drawText(strInfo1, defX, defY + textSize, paint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package io.flutter.plugins.facediscern.tool.file;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
public class FileUtil {
|
||||
/**
|
||||
* 读取文件中的数据内容
|
||||
*
|
||||
* @param file 文件
|
||||
* @return 二进制数据内容
|
||||
*/
|
||||
public static byte[] fileToData(File file) {
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
fis = new FileInputStream(file);
|
||||
byte[] data = new byte[fis.available()];
|
||||
fis.read(data);
|
||||
fis.close();
|
||||
return data;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean saveDataToFile(byte[] data, File file, boolean append) {
|
||||
if (data == null){
|
||||
return false;
|
||||
}
|
||||
File parentFile = file.getParentFile();
|
||||
if (parentFile == null) {
|
||||
return false;
|
||||
}
|
||||
if (!file.getParentFile().exists() && !file.getParentFile().mkdirs()) {
|
||||
return false;
|
||||
}
|
||||
FileOutputStream fos = null;
|
||||
try {
|
||||
fos = new FileOutputStream(file, append);
|
||||
int bufferSize = 1024;
|
||||
int index = 0;
|
||||
while (index < data.length) {
|
||||
if (data.length - index < bufferSize) {
|
||||
bufferSize = data.length - index;
|
||||
}
|
||||
fos.write(data, index, bufferSize);
|
||||
index += bufferSize;
|
||||
}
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
} finally {
|
||||
try {
|
||||
if (fos != null) {
|
||||
fos.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean saveDataToFile(byte[] data, File file) {
|
||||
return saveDataToFile(data, file, false);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,323 @@
|
||||
package io.flutter.plugins.facediscern.ui;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Point;
|
||||
import android.hardware.Camera;
|
||||
import android.os.Bundle;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewTreeObserver;
|
||||
|
||||
import androidx.activity.EdgeToEdge;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.databinding.DataBindingUtil;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import com.example.pj_supermarket_cloud.R;
|
||||
import com.example.pj_supermarket_cloud.databinding.ActivityFaceDiscernBinding;
|
||||
|
||||
import io.flutter.Log;
|
||||
import io.flutter.plugins.facediscern.flutter.FlutterFaceDiscernPlugin;
|
||||
import io.flutter.plugins.facediscern.tool.camera.DualCameraHelper;
|
||||
import io.flutter.plugins.facediscern.tool.camera.callback.CameraListener;
|
||||
import io.flutter.plugins.facediscern.tool.face.constants.LivenessType;
|
||||
import io.flutter.plugins.facediscern.tool.face.help.FaceRectTransformer;
|
||||
import io.flutter.plugins.facediscern.tool.face.model.FacePreviewInfo;
|
||||
import io.flutter.plugins.facediscern.tool.face.widget.FaceRectView;
|
||||
import io.flutter.plugins.facediscern.ui.base.BaseActivity;
|
||||
import io.flutter.plugins.facediscern.ui.viewmodel.FaceDiscernViewModel;
|
||||
import io.flutter.plugin.common.MethodChannel;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class FaceDiscern extends BaseActivity implements ViewTreeObserver.OnGlobalLayoutListener {
|
||||
|
||||
private static final String TAG = "人脸识别页面";
|
||||
|
||||
private static final String[] NEEDED_PERMISSIONS = new String[]{
|
||||
Manifest.permission.CAMERA,
|
||||
Manifest.permission.READ_PHONE_STATE
|
||||
};
|
||||
|
||||
private FaceRectTransformer rgbFaceRectTransformer;
|
||||
|
||||
// 彩色摄像头
|
||||
private DualCameraHelper rgbCameraHelper;
|
||||
|
||||
private FaceDiscernViewModel viewModel;
|
||||
|
||||
private ActivityFaceDiscernBinding binding;
|
||||
|
||||
private Boolean previewLock = false;
|
||||
|
||||
private static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
|
||||
EdgeToEdge.enable(this);
|
||||
binding = DataBindingUtil.setContentView(this, R.layout.activity_face_discern);
|
||||
runOnUiThread(() -> {
|
||||
this.initViewModel();
|
||||
initView();
|
||||
MethodChannel channel = FlutterFaceDiscernPlugin.staticChannel;
|
||||
if (Objects.nonNull(channel)) channel.invokeMethod("faceDiscernPageInit", null);
|
||||
});
|
||||
Log.e(TAG, "相机触发onCreate");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGlobalLayout() {
|
||||
binding.dualCameraTexturePreviewRgb.getViewTreeObserver().removeOnGlobalLayoutListener(FaceDiscern.this);
|
||||
if (!checkPermissions(NEEDED_PERMISSIONS)) {
|
||||
ActivityCompat.requestPermissions(this, NEEDED_PERMISSIONS, 0x001);
|
||||
return;
|
||||
}
|
||||
initRgbCamera();
|
||||
Log.e(TAG, "相机onGlobalLayout");
|
||||
}
|
||||
|
||||
// 请求权限的回调
|
||||
@Override
|
||||
protected void afterRequestPermission(int requestCode, boolean isAllGranted) {
|
||||
super.afterRequestPermission(requestCode, isAllGranted);
|
||||
if (!checkPermissions(NEEDED_PERMISSIONS)) {
|
||||
return;
|
||||
}
|
||||
initRgbCamera();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
Log.e(TAG, "相机触发onNewIntent");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (rgbCameraHelper != null) {
|
||||
rgbCameraHelper.release();
|
||||
rgbCameraHelper = null;
|
||||
}
|
||||
if (binding != null) binding = null;
|
||||
viewModel.destroy();
|
||||
Log.e(TAG, "相机触发onDestroy");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
if (rgbCameraHelper != null) {
|
||||
rgbCameraHelper.stop();
|
||||
}
|
||||
if (viewModel != null) viewModel.stop();
|
||||
binding.dualCameraFaceRectView.clearFaceInfo();
|
||||
previewLock = true;
|
||||
Log.i(TAG, "相机onPause");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
MethodChannel channel = FlutterFaceDiscernPlugin.staticChannel;
|
||||
if (Objects.nonNull(channel)) {
|
||||
channel.invokeMethod("faceDiscernPageInit", null);
|
||||
}
|
||||
if (rgbCameraHelper != null) {
|
||||
rgbCameraHelper.resumePreview();
|
||||
}
|
||||
scheduler.schedule(() -> {
|
||||
try {
|
||||
if (viewModel != null) {
|
||||
viewModel.stop();
|
||||
previewLock = false;
|
||||
Log.i(TAG, "相机开始识别");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "相机开始识别错误", e);
|
||||
}
|
||||
}, 500, TimeUnit.MILLISECONDS);
|
||||
Log.i(TAG, "相机onResume");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
if (rgbCameraHelper != null && !isFinishing()) {
|
||||
rgbCameraHelper.stop();
|
||||
}
|
||||
}
|
||||
|
||||
// 手机返回按钮
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
Log.e(TAG, "相机onBackPressed");
|
||||
Intent intent = new Intent();
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
|
||||
intent.setClassName(this.getPackageName(), this.getPackageName() + ".MainActivity");
|
||||
this.startActivity(intent);
|
||||
MethodChannel channel = FlutterFaceDiscernPlugin.staticChannel;
|
||||
if (Objects.isNull(channel)) return;
|
||||
channel.invokeMethod("faceDiscernBack", null);
|
||||
}
|
||||
|
||||
|
||||
/* ***************** start 初始化 start ***************** */
|
||||
private void initViewModel() {
|
||||
viewModel = new ViewModelProvider(this).get(FaceDiscernViewModel.class);
|
||||
viewModel.init(FaceDiscern.this);
|
||||
|
||||
}
|
||||
|
||||
private void initView() {
|
||||
binding.dualCameraTexturePreviewRgb.getViewTreeObserver().addOnGlobalLayoutListener(FaceDiscern.this);
|
||||
}
|
||||
|
||||
private void initRgbCamera() {
|
||||
CameraListener cameraListener = new CameraListener() {
|
||||
@Override
|
||||
public void onCameraOpened(Camera camera, int cameraId, int displayOrientation, boolean isMirror) {
|
||||
runOnUiThread(() -> {
|
||||
Camera.Size previewSizeRgb = camera.getParameters().getPreviewSize();
|
||||
ViewGroup.LayoutParams layoutParams = adjustPreviewViewSize(
|
||||
binding.dualCameraTexturePreviewRgb,
|
||||
binding.dualCameraTexturePreviewRgb,
|
||||
binding.dualCameraFaceRectView,
|
||||
previewSizeRgb,
|
||||
displayOrientation,
|
||||
1.0f
|
||||
);
|
||||
rgbFaceRectTransformer = new FaceRectTransformer(
|
||||
previewSizeRgb.width,
|
||||
previewSizeRgb.height,
|
||||
layoutParams.width,
|
||||
layoutParams.height,
|
||||
displayOrientation,
|
||||
cameraId,
|
||||
isMirror,
|
||||
true,
|
||||
true
|
||||
);
|
||||
viewModel.onRgbCameraOpened(camera);
|
||||
viewModel.setRgbFaceRectTransformer(rgbFaceRectTransformer);
|
||||
Log.e(TAG, "相机打开,宽:" + previewSizeRgb.width + ",高:" + previewSizeRgb.height + ";layout宽:" + layoutParams.width + ",layout高:" + layoutParams.height + ";");
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPreview(byte[] data, Camera camera) {
|
||||
if (previewLock) return;
|
||||
binding.dualCameraFaceRectView.clearFaceInfo();
|
||||
List<FacePreviewInfo> facePreviewInfoList = viewModel.onPreviewFrame(data, true);
|
||||
if (facePreviewInfoList != null && rgbFaceRectTransformer != null) {
|
||||
drawPreviewInfo(facePreviewInfoList);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCameraClosed() {
|
||||
Log.e(TAG, "相机关闭");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCameraError(Exception e) {
|
||||
Log.e(TAG, "相机错误error", e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCameraConfigurationChanged(int cameraID, int displayOrientation) {
|
||||
|
||||
}
|
||||
};
|
||||
// 使用前置相机
|
||||
int mCameraId = 0;
|
||||
int numberOfCameras = Camera.getNumberOfCameras();
|
||||
Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
|
||||
for (int i = 0; i < numberOfCameras; i++) {
|
||||
Camera.getCameraInfo(i, cameraInfo);
|
||||
if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
|
||||
mCameraId = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Point previewSize = new Point(binding.dualCameraTexturePreviewRgb.getMeasuredWidth(), binding.dualCameraTexturePreviewRgb.getMeasuredHeight());
|
||||
rgbCameraHelper = new DualCameraHelper.Builder()
|
||||
.previewViewSize(previewSize)
|
||||
.previewSize(previewSize)
|
||||
.rotation(getWindowManager().getDefaultDisplay().getRotation())
|
||||
.additionalRotation(0)
|
||||
.specificCameraId(mCameraId)
|
||||
.isMirror(false)
|
||||
.previewOn(binding.dualCameraTexturePreviewRgb)
|
||||
.cameraListener(cameraListener)
|
||||
.build();
|
||||
rgbCameraHelper.init();
|
||||
rgbCameraHelper.start();
|
||||
viewModel.setRgbCameraHelper(rgbCameraHelper);
|
||||
}
|
||||
/* ***************** end end end ***************** */
|
||||
|
||||
|
||||
private ViewGroup.LayoutParams adjustPreviewViewSize(View rgbPreview, View previewView, FaceRectView faceRectView, Camera.Size previewSize, int displayOrientation, float scale) {
|
||||
ViewGroup.LayoutParams layoutParams = previewView.getLayoutParams();
|
||||
int measuredWidth = previewView.getMeasuredWidth();
|
||||
int measuredHeight = previewView.getMeasuredHeight();
|
||||
float ratio = ((float) previewSize.height) / (float) previewSize.width;
|
||||
if (ratio > 1) {
|
||||
ratio = 1 / ratio;
|
||||
}
|
||||
if (displayOrientation % 180 == 0) {
|
||||
layoutParams.width = measuredWidth;
|
||||
layoutParams.height = (int) (measuredWidth * ratio);
|
||||
} else {
|
||||
layoutParams.height = measuredHeight;
|
||||
layoutParams.width = (int) (measuredHeight * ratio);
|
||||
}
|
||||
if (scale < 1f) {
|
||||
ViewGroup.LayoutParams rgbParam = rgbPreview.getLayoutParams();
|
||||
layoutParams.width = (int) (rgbParam.width * scale);
|
||||
layoutParams.height = (int) (rgbParam.height * scale);
|
||||
} else {
|
||||
layoutParams.width *= scale;
|
||||
layoutParams.height *= scale;
|
||||
}
|
||||
|
||||
DisplayMetrics metrics = new DisplayMetrics();
|
||||
getWindowManager().getDefaultDisplay().getMetrics(metrics);
|
||||
|
||||
if (layoutParams.width >= metrics.widthPixels) {
|
||||
float viewRatio = layoutParams.width / ((float) metrics.widthPixels);
|
||||
layoutParams.width /= viewRatio;
|
||||
layoutParams.height /= viewRatio;
|
||||
}
|
||||
if (layoutParams.height >= metrics.heightPixels) {
|
||||
float viewRatio = layoutParams.height / ((float) metrics.heightPixels);
|
||||
layoutParams.width /= viewRatio;
|
||||
layoutParams.height /= viewRatio;
|
||||
}
|
||||
|
||||
previewView.setLayoutParams(layoutParams);
|
||||
faceRectView.setLayoutParams(layoutParams);
|
||||
return layoutParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制RGB、IR画面的实时人脸信息
|
||||
*
|
||||
* @param facePreviewInfoList RGB画面的实时人脸信息
|
||||
*/
|
||||
private void drawPreviewInfo(List<FacePreviewInfo> facePreviewInfoList) {
|
||||
if (rgbFaceRectTransformer != null) {
|
||||
List<FaceRectView.DrawInfo> rgbDrawInfoList = viewModel.getDrawInfo(facePreviewInfoList, LivenessType.RGB, false);
|
||||
binding.dualCameraFaceRectView.drawRealtimeFaceInfo(rgbDrawInfoList);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package io.flutter.plugins.facediscern.ui.base;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class BaseActivity extends AppCompatActivity {
|
||||
private static ExecutorService executor;
|
||||
private static List<Activity> activityList;
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
if (activityList == null) {
|
||||
activityList = new ArrayList<>();
|
||||
}
|
||||
if (executor == null) {
|
||||
executor = new ThreadPoolExecutor(
|
||||
1,
|
||||
1,
|
||||
0L,
|
||||
TimeUnit.MILLISECONDS,
|
||||
new LinkedBlockingQueue<Runnable>(),
|
||||
r -> {
|
||||
Thread t = new Thread(r);
|
||||
t.setName("activity-sub-thread-" + t.getId());
|
||||
return t;
|
||||
});
|
||||
}
|
||||
activityList.add(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
activityList.remove(this);
|
||||
if (activityList.isEmpty()) {
|
||||
activityList = null;
|
||||
executor.shutdown();
|
||||
executor = null;
|
||||
}
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
boolean isAllGranted = true;
|
||||
for (int grantResult : grantResults) {
|
||||
isAllGranted &= (grantResult == PackageManager.PERMISSION_GRANTED);
|
||||
}
|
||||
afterRequestPermission(requestCode, isAllGranted);
|
||||
}
|
||||
|
||||
// 请求权限的回调
|
||||
protected void afterRequestPermission(int requestCode, boolean isAllGranted) {
|
||||
|
||||
}
|
||||
|
||||
// 检查权限
|
||||
protected boolean checkPermissions(String[] neededPermissions) {
|
||||
if (neededPermissions == null || neededPermissions.length == 0) {
|
||||
return true;
|
||||
}
|
||||
boolean allGranted = true;
|
||||
for (String neededPermission : neededPermissions) {
|
||||
allGranted &= ContextCompat.checkSelfPermission(this, neededPermission) == PackageManager.PERMISSION_GRANTED;
|
||||
}
|
||||
return allGranted;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,309 @@
|
||||
package io.flutter.plugins.facediscern.ui.viewmodel;
|
||||
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.hardware.Camera;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.arcsoft.face.AgeInfo;
|
||||
import com.arcsoft.face.FaceEngine;
|
||||
import com.arcsoft.face.FaceInfo;
|
||||
import com.arcsoft.face.GenderInfo;
|
||||
import com.arcsoft.face.LivenessInfo;
|
||||
import com.arcsoft.face.LivenessParam;
|
||||
import com.arcsoft.face.enums.DetectFaceOrientPriority;
|
||||
import com.arcsoft.face.enums.DetectMode;
|
||||
import com.google.gson.Gson;
|
||||
|
||||
import io.flutter.plugins.facediscern.tool.camera.DualCameraHelper;
|
||||
import io.flutter.plugins.facediscern.tool.camera.config.PreviewConfig;
|
||||
import io.flutter.plugins.facediscern.tool.face.constants.FaceBrodConstant;
|
||||
import io.flutter.plugins.facediscern.tool.face.constants.LivenessType;
|
||||
import io.flutter.plugins.facediscern.tool.face.constants.RecognizeColor;
|
||||
import io.flutter.plugins.facediscern.tool.face.constants.RequestFeatureStatus;
|
||||
import io.flutter.plugins.facediscern.tool.face.help.FaceHelper;
|
||||
import io.flutter.plugins.facediscern.tool.face.help.FaceRectTransformer;
|
||||
import io.flutter.plugins.facediscern.tool.face.help.callback.RecognizeCallback;
|
||||
import io.flutter.plugins.facediscern.tool.face.model.CompareResult;
|
||||
import io.flutter.plugins.facediscern.tool.face.model.FaceEntity;
|
||||
import io.flutter.plugins.facediscern.tool.face.model.FacePreviewInfo;
|
||||
import io.flutter.plugins.facediscern.tool.face.model.RecognizeConfiguration;
|
||||
import io.flutter.plugins.facediscern.tool.face.widget.FaceRectView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public class FaceDiscernViewModel extends ViewModel implements RecognizeCallback {
|
||||
|
||||
private final String TAG = "FaceDiscernViewModel";
|
||||
|
||||
// 相机预览的分辨率
|
||||
private Camera.Size previewSize;
|
||||
|
||||
/// ****************** start 引擎 start ******************
|
||||
|
||||
// 用于特征提取的引擎
|
||||
private FaceEngine frEngine;
|
||||
// VIDEO模式人脸检测引擎,用于预览帧人脸追踪及图像质量检测
|
||||
private FaceEngine ftEngine;
|
||||
// VIDEO模式人脸口罩检测引擎,用于预览帧人脸口罩检测
|
||||
private FaceEngine maskEngine;
|
||||
// IMAGE模式活体检测引擎,用于预览帧人脸活体检测
|
||||
private FaceEngine flEngine;
|
||||
// 各个引擎初始化的错误码
|
||||
private MutableLiveData<Integer> ftInitCode = new MutableLiveData<>();
|
||||
private MutableLiveData<Integer> frInitCode = new MutableLiveData<>();
|
||||
private MutableLiveData<Integer> flInitCode = new MutableLiveData<>();
|
||||
|
||||
/// ****************** end 引擎 end ******************
|
||||
|
||||
private Rect recognizeArea = new Rect(Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE);
|
||||
|
||||
// 当前活体检测的检测类型
|
||||
private LivenessType livenessType;
|
||||
|
||||
private FaceRectTransformer rgbFaceRectTransformer;
|
||||
|
||||
private PreviewConfig previewConfig;
|
||||
|
||||
// 人脸操作辅助类,推帧即可,内部会进行特征提取、识别
|
||||
private FaceHelper faceHelper;
|
||||
|
||||
// 人脸库数据加载完成
|
||||
private boolean loadFaceList = true;
|
||||
|
||||
// IR活体数据
|
||||
private byte[] irNV21 = null;
|
||||
|
||||
public static final int REGISTER_STATUS_READY = 0;
|
||||
// 注册人脸状态码,注册中
|
||||
public static final int REGISTER_STATUS_PROCESSING = 1;
|
||||
// 册人脸状态码,注册结束(无论成功失败)
|
||||
public static final int REGISTER_STATUS_DONE = 2;
|
||||
|
||||
private int registerStatus = REGISTER_STATUS_DONE;
|
||||
|
||||
private Intent flutterFaceCallbackIntent;
|
||||
|
||||
private Context context;
|
||||
|
||||
private DualCameraHelper rgbCameraHelper;
|
||||
|
||||
private HashMap<Object, Object> faceDiscern = null;
|
||||
|
||||
private MutableLiveData<RecognizeConfiguration> recognizeConfiguration = new MutableLiveData<>();
|
||||
|
||||
public void setRgbFaceRectTransformer(FaceRectTransformer rgbFaceRectTransformer) {
|
||||
faceHelper.setRgbFaceRectTransformer(rgbFaceRectTransformer);
|
||||
}
|
||||
|
||||
public void setRgbCameraHelper(DualCameraHelper rgbCameraHelper) {
|
||||
this.rgbCameraHelper = rgbCameraHelper;
|
||||
}
|
||||
|
||||
public void setContext(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
private void updateRegisterStatus(int status) {
|
||||
registerStatus = status;
|
||||
}
|
||||
|
||||
// 初始化引擎
|
||||
public void init(Context context) {
|
||||
setContext(context);
|
||||
boolean switchCamera = false;
|
||||
previewConfig = new PreviewConfig(switchCamera ? Camera.CameraInfo.CAMERA_FACING_FRONT : Camera.CameraInfo.CAMERA_FACING_BACK, switchCamera ? Camera.CameraInfo.CAMERA_FACING_BACK : Camera.CameraInfo.CAMERA_FACING_FRONT, 0, 0);
|
||||
// 填入在设置界面设置好的配置信息
|
||||
boolean enableLive = false;
|
||||
boolean enableFaceQualityDetect = true;
|
||||
boolean enableFaceMoveLimit = false;
|
||||
boolean enableFaceSizeLimit = false;
|
||||
int faceSizeLimit = 160;
|
||||
int faceMoveLimit = 20;
|
||||
int maxDetectFaces = 1;
|
||||
boolean keepMaxFace = true;
|
||||
float similarThreshold = 0.8F;
|
||||
float imageQualityNoMaskRecognizeThreshold = 0.49F;
|
||||
float imageQualityMaskRecognizeThreshold = 0.29F;
|
||||
RecognizeConfiguration configuration = new RecognizeConfiguration.Builder().enableFaceMoveLimit(enableFaceMoveLimit).enableFaceSizeLimit(enableFaceSizeLimit).faceSizeLimit(faceSizeLimit).faceMoveLimit(faceMoveLimit).enableLiveness(enableLive).enableImageQuality(enableFaceQualityDetect).maxDetectFaces(maxDetectFaces).keepMaxFace(keepMaxFace).similarThreshold(similarThreshold).imageQualityNoMaskRecognizeThreshold(imageQualityNoMaskRecognizeThreshold).imageQualityMaskRecognizeThreshold(imageQualityMaskRecognizeThreshold).livenessParam(new LivenessParam(0.5F, 0.5F)).build();
|
||||
|
||||
ftEngine = new FaceEngine();
|
||||
ftInitCode.postValue(ftEngine.init(context, DetectMode.ASF_DETECT_MODE_VIDEO, DetectFaceOrientPriority.ASF_OP_ALL_OUT, 1, FaceEngine.ASF_FACE_DETECT));
|
||||
maskEngine = new FaceEngine();
|
||||
ftInitCode.postValue(maskEngine.init(context, DetectMode.ASF_DETECT_MODE_IMAGE, DetectFaceOrientPriority.ASF_OP_ALL_OUT, 1, FaceEngine.ASF_MASK_DETECT));
|
||||
|
||||
frEngine = new FaceEngine();
|
||||
int frEngineMask = FaceEngine.ASF_FACE_RECOGNITION;
|
||||
if (enableFaceQualityDetect) {
|
||||
frEngineMask |= FaceEngine.ASF_IMAGEQUALITY;
|
||||
}
|
||||
frInitCode.postValue(frEngine.init(context, DetectMode.ASF_DETECT_MODE_IMAGE, DetectFaceOrientPriority.ASF_OP_0_ONLY, 10, frEngineMask));
|
||||
//启用活体检测时,才初始化活体引擎
|
||||
if (enableLive) {
|
||||
flEngine = new FaceEngine();
|
||||
int flEngineMask = (livenessType == LivenessType.RGB ? FaceEngine.ASF_LIVENESS : (FaceEngine.ASF_IR_LIVENESS | FaceEngine.ASF_FACE_DETECT));
|
||||
flInitCode.postValue(flEngine.init(context, DetectMode.ASF_DETECT_MODE_IMAGE, DetectFaceOrientPriority.ASF_OP_ALL_OUT, 10, flEngineMask));
|
||||
LivenessParam livenessParam = new LivenessParam(0.5F, 0.5F);
|
||||
flEngine.setLivenessParam(livenessParam);
|
||||
}
|
||||
recognizeConfiguration.setValue(configuration);
|
||||
flutterFaceCallbackIntent = new Intent(FaceBrodConstant.FACE_GLOBAL_INTENT);
|
||||
}
|
||||
|
||||
private void initFaceHelper(Camera.Size lastPreviewSize) {
|
||||
if (faceHelper == null || lastPreviewSize == null || lastPreviewSize.width != previewSize.width || lastPreviewSize.height != previewSize.height) {
|
||||
Integer trackedFaceCount = null;
|
||||
// 记录切换时的人脸序号
|
||||
if (faceHelper != null) {
|
||||
trackedFaceCount = faceHelper.getTrackedFaceCount();
|
||||
faceHelper.release();
|
||||
}
|
||||
int horizontalOffset = 0;
|
||||
int verticalOffset = 0;
|
||||
int maxDetectFaceNum = 1;
|
||||
faceHelper = new FaceHelper.Builder().ftEngine(ftEngine).maskEngine(maskEngine).frEngine(frEngine).flEngine(flEngine).frQueueSize(maxDetectFaceNum).flQueueSize(maxDetectFaceNum).previewSize(previewSize).recognizeCallback(this).recognizeConfiguration(recognizeConfiguration.getValue()).trackedFaceCount(trackedFaceCount == null ? 0 : trackedFaceCount).dualCameraFaceInfoTransformer(faceInfo -> {
|
||||
FaceInfo irFaceInfo = new FaceInfo(faceInfo);
|
||||
irFaceInfo.getRect().offset(horizontalOffset, verticalOffset);
|
||||
return irFaceInfo;
|
||||
}).build();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 当相机打开时由activity调用,进行一些初始化操作
|
||||
public void onRgbCameraOpened(Camera camera) {
|
||||
Camera.Size lastPreviewSize = previewSize;
|
||||
previewSize = camera.getParameters().getPreviewSize();
|
||||
// 切换相机的时候可能会导致预览尺寸发生变化
|
||||
initFaceHelper(lastPreviewSize);
|
||||
}
|
||||
|
||||
public Point loadPreviewSize() {
|
||||
String[] size = new String[]{"1280", "720"};
|
||||
return new Point(Integer.parseInt(size[0]), Integer.parseInt(size[1]));
|
||||
}
|
||||
|
||||
/**
|
||||
* 传入可见光相机预览数据
|
||||
*
|
||||
* @param nv21 可见光相机预览数据
|
||||
* @param doRecognize 是否进行识别
|
||||
* @return 当前帧的检测结果信息
|
||||
*/
|
||||
public List<FacePreviewInfo> onPreviewFrame(byte[] nv21, boolean doRecognize) {
|
||||
if (faceHelper != null) {
|
||||
if (!loadFaceList) {
|
||||
return null;
|
||||
}
|
||||
if (livenessType == LivenessType.IR && irNV21 == null) {
|
||||
return null;
|
||||
}
|
||||
return faceHelper.onPreviewFrame(nv21, irNV21, doRecognize);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据预览信息生成绘制信息
|
||||
*
|
||||
* @param facePreviewInfoList 预览信息
|
||||
* @return 绘制信息
|
||||
*/
|
||||
public List<FaceRectView.DrawInfo> getDrawInfo(List<FacePreviewInfo> facePreviewInfoList, LivenessType livenessType, boolean drawRectInfo) {
|
||||
List<FaceRectView.DrawInfo> drawInfoList = new ArrayList<>();
|
||||
for (int i = 0; i < facePreviewInfoList.size(); i++) {
|
||||
int trackId = facePreviewInfoList.get(i).getTrackId();
|
||||
String name = faceHelper.getName(trackId);
|
||||
Integer liveness = faceHelper.getLiveness(trackId);
|
||||
Integer recognizeStatus = faceHelper.getRecognizeStatus(trackId);
|
||||
|
||||
// 根据识别结果和活体结果设置颜色
|
||||
int color = RecognizeColor.COLOR_UNKNOWN;
|
||||
if (recognizeStatus != null) {
|
||||
if (recognizeStatus == RequestFeatureStatus.FAILED) {
|
||||
color = RecognizeColor.COLOR_FAILED;
|
||||
}
|
||||
if (recognizeStatus == RequestFeatureStatus.SUCCEED) {
|
||||
color = RecognizeColor.COLOR_SUCCESS;
|
||||
}
|
||||
}
|
||||
if (liveness != null && liveness == LivenessInfo.NOT_ALIVE) {
|
||||
color = RecognizeColor.COLOR_FAILED;
|
||||
}
|
||||
|
||||
drawInfoList.add(new FaceRectView.DrawInfo(livenessType == LivenessType.RGB ? facePreviewInfoList.get(i).getRgbTransformedRect() : facePreviewInfoList.get(i).getIrTransformedRect(), GenderInfo.UNKNOWN, AgeInfo.UNKNOWN_AGE, liveness == null ? LivenessInfo.UNKNOWN : liveness, color, name == null ? "" : name, facePreviewInfoList.get(i).getFaceInfoRgb().getIsWithinBoundary(), facePreviewInfoList.get(i).getForeRect(), facePreviewInfoList.get(i).getFaceInfoRgb().getFaceAttributeInfo(), drawRectInfo, livenessType == LivenessType.RGB));
|
||||
}
|
||||
return drawInfoList;
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
if (faceHelper != null) {
|
||||
faceHelper.release();
|
||||
}
|
||||
// FaceServer.getInstance().release();
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
if (faceHelper != null) {
|
||||
faceHelper.stop();
|
||||
}
|
||||
}
|
||||
|
||||
// 识别结果回调
|
||||
@Override
|
||||
public void onRecognized(CompareResult compareResult, Integer liveness, boolean similarPass) {
|
||||
try {
|
||||
if (!similarPass) return;
|
||||
HashMap<Object, Object> json = new HashMap<>();
|
||||
json.put("similar", compareResult.getSimilar());
|
||||
json.put("cost", compareResult.getCost());
|
||||
if (Objects.nonNull(compareResult.getFaceEntity())) {
|
||||
FaceEntity face = compareResult.getFaceEntity();
|
||||
json.put("faceId", face.getFaceId());
|
||||
json.put("sampleId", face.getSampleId());
|
||||
json.put("userId", face.getUserId());
|
||||
json.put("userName", face.getUserName());
|
||||
}
|
||||
faceDiscern = json;
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "识别结果回调,错误error:" + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
// 提示文字变更的回调
|
||||
@Override
|
||||
public void onNoticeChanged(String notice) {
|
||||
try {
|
||||
if (!Objects.equals(notice, "")) return;
|
||||
if (Objects.isNull(faceDiscern)) return;
|
||||
if (flutterFaceCallbackIntent == null) {
|
||||
flutterFaceCallbackIntent = new Intent(FaceBrodConstant.FACE_GLOBAL_INTENT);
|
||||
}
|
||||
Log.i(TAG, "人脸识别成功json:" + new Gson().toJson(faceDiscern));
|
||||
flutterFaceCallbackIntent.putExtra("json", new Gson().toJson(faceDiscern));
|
||||
context.sendBroadcast(flutterFaceCallbackIntent);
|
||||
faceDiscern = null;
|
||||
if (context instanceof Activity) {
|
||||
Thread.sleep(500);
|
||||
String packageName = context.getPackageName();
|
||||
Intent intent = new Intent();
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
|
||||
intent.setClassName(packageName, packageName + ".MainActivity");
|
||||
context.startActivity(intent);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "提示文字变更的回调,错误error:" + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.example.pj_supermarket_cloud
|
||||
|
||||
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>
|
||||
@@ -0,0 +1,67 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/dual_camera_ll_parent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="start">
|
||||
|
||||
<TextureView
|
||||
android:id="@+id/dual_camera_texture_preview_rgb"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<!-- canvas 人脸 -->
|
||||
<io.flutter.plugins.facediscern.tool.face.widget.FaceRectView
|
||||
android:id="@+id/dual_camera_face_rect_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/fl_recognize_ir"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="bottom">
|
||||
|
||||
<TextureView
|
||||
android:id="@+id/dual_camera_texture_preview_ir"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<io.flutter.plugins.facediscern.tool.face.widget.FaceRectView
|
||||
android:id="@+id/dual_camera_face_rect_view_ir"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
/>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<data>
|
||||
|
||||
<variable
|
||||
name="compareResultList"
|
||||
type="java.util.List<io.flutter.plugins.facediscern.tool.face.model.CompareResult>" />
|
||||
|
||||
<variable
|
||||
name="recognizeNotice"
|
||||
type="String" />
|
||||
|
||||
<variable
|
||||
name="drawRectInfoText"
|
||||
type="String" />
|
||||
|
||||
<import type="android.view.View" />
|
||||
</data>
|
||||
|
||||
|
||||
</layout>
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<io.flutter.embedding.android.FlutterView
|
||||
android:id="@+id/flutter_presentation_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
</LinearLayout>
|
||||
|
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,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,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
#distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip
|
||||
distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.3-all.zip
|
||||
@@ -0,0 +1,28 @@
|
||||
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 {
|
||||
maven { url "https://maven.aliyun.com/repository/google" }
|
||||
maven { url "https://maven.aliyun.com/repository/central" }
|
||||
maven { url "https://maven.aliyun.com/repository/gradle-plugin" }
|
||||
maven { url "https://jitpack.io" }
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||
id "com.android.application" version "8.1.0" apply false
|
||||
id "org.jetbrains.kotlin.android" version "1.8.22" apply false
|
||||
}
|
||||
|
||||
include ":app"
|
||||
@@ -0,0 +1,22 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh
|
||||
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
|
||||
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
|
||||
QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT
|
||||
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
|
||||
b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG
|
||||
9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB
|
||||
CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97
|
||||
nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt
|
||||
43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P
|
||||
T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4
|
||||
gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO
|
||||
BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR
|
||||
TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw
|
||||
DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr
|
||||
hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg
|
||||
06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF
|
||||
PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls
|
||||
YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk
|
||||
CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=
|
||||
-----END CERTIFICATE-----
|
||||
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 7.6 KiB |
@@ -0,0 +1,5 @@
|
||||
icons_launcher:
|
||||
image_path: "assets/images/logo.png"
|
||||
platforms:
|
||||
android:
|
||||
enable: true
|
||||
@@ -0,0 +1,59 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_easyloading/flutter_easyloading.dart';
|
||||
import 'package:forui/forui.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:ducafe_ui_core/ducafe_ui_core.dart';
|
||||
|
||||
import 'package:pj_supermarket_cloud/common/index.dart';
|
||||
|
||||
class MainApp extends StatelessWidget {
|
||||
const MainApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ScreenUtilInit(
|
||||
designSize: const Size(1920, 1080),
|
||||
minTextAdapt: true,
|
||||
splitScreenMode: true,
|
||||
builder: (_, child) {
|
||||
final appTheme = Get.put(AppThemeControl());
|
||||
final fTheme = appTheme.theme.value;
|
||||
final sizeScalar = appTheme.sizeScalar.value;
|
||||
final themeData = FThemeData.inherit(
|
||||
colorScheme: fTheme.colorScheme.copyWith(),
|
||||
typography: fTheme.typography.scale(sizeScalar: sizeScalar),
|
||||
style: fTheme.style.copyWith(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(12.w),
|
||||
),
|
||||
borderWidth: 1,
|
||||
iconStyle: fTheme.style.iconStyle.copyWith(
|
||||
size: fTheme.typography.xl2.fontSize,
|
||||
),
|
||||
),
|
||||
);
|
||||
return GetMaterialApp(
|
||||
title: '收银机',
|
||||
initialRoute: RouteNames.load,
|
||||
getPages: RoutePages.list,
|
||||
enableLog: true,
|
||||
debugShowCheckedModeBanner: false,
|
||||
builder: EasyLoading.init(
|
||||
builder: (context, child) => FTheme(
|
||||
data: themeData,
|
||||
child: child!,
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class SubApp extends StatelessWidget {
|
||||
const SubApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
import 'package:pj_supermarket_cloud/common/index.dart';
|
||||
|
||||
abstract class HardApi {
|
||||
/// 获取硬件配置信息
|
||||
static Future<ServiceHardware?> bind(dynamic pa) async {
|
||||
final res = await HttpManage().dio.post(
|
||||
"/hard/hard-api/ad/bind",
|
||||
data: pa,
|
||||
);
|
||||
if (res.data == null) return null;
|
||||
return ServiceHardware.fromJson(res.data);
|
||||
}
|
||||
|
||||
/// 获取硬件配置信息
|
||||
static Future<ServiceBase<ServiceHardware>> get(dynamic pa) async {
|
||||
final res = await HttpManage().dio.get<ServiceBase<ServiceHardware>>(
|
||||
"/hard/hard-api/ad/get",
|
||||
queryParameters: pa,
|
||||
);
|
||||
if (res.data?.data == null) {
|
||||
return res.data!;
|
||||
}
|
||||
final map = res.data!.data as Map<String, dynamic>;
|
||||
final typeData = ServiceHardware.fromJson(map);
|
||||
res.data?.data = typeData;
|
||||
return res.data!;
|
||||
}
|
||||
|
||||
/// 获取用户信息
|
||||
static Future<ServiceBase<ServiceUser>> getUserInfoById(
|
||||
dynamic formData,
|
||||
) async {
|
||||
final res = await HttpManage().dio.get<ServiceBase<ServiceUser>>(
|
||||
"/hard/hard-api/ad/get-user",
|
||||
queryParameters: formData,
|
||||
);
|
||||
if (res.data?.data == null) {
|
||||
return res.data!;
|
||||
}
|
||||
final map = res.data!.data as Map<String, dynamic>;
|
||||
final typeData = ServiceUser.fromJson(map);
|
||||
res.data?.data = typeData;
|
||||
return res.data!;
|
||||
}
|
||||
|
||||
/// 获取用户信息
|
||||
static Future<ServiceBase<ServiceUser>> getUserByCard(
|
||||
dynamic formData,
|
||||
) async {
|
||||
final res = await HttpManage().dio.get<ServiceBase<ServiceUser>>(
|
||||
"/hard/hard-api/ad/get-user-by-card",
|
||||
queryParameters: formData,
|
||||
);
|
||||
if (res.data?.data == null) {
|
||||
return res.data!;
|
||||
}
|
||||
final map = res.data!.data as Map<String, dynamic>;
|
||||
final typeData = ServiceUser.fromJson(map);
|
||||
res.data?.data = typeData;
|
||||
return res.data!;
|
||||
}
|
||||
|
||||
/// 创建硬件订单
|
||||
static Future<ServiceBase> createHardwareOrder(dynamic formData) async {
|
||||
final res = await HttpManage().dio.post<ServiceBase>(
|
||||
"/hard/hard-api/ad/create-order",
|
||||
data: formData,
|
||||
);
|
||||
return res.data as ServiceBase;
|
||||
}
|
||||
|
||||
/// 获取订单详情
|
||||
static Future<ServiceBase> getOrder(dynamic formData) async {
|
||||
final res = await HttpManage().dio.get<ServiceBase>(
|
||||
"/hard/hard-api/ad/get-order",
|
||||
queryParameters: formData,
|
||||
);
|
||||
return res.data as ServiceBase;
|
||||
}
|
||||
|
||||
/// 获取订单购物车
|
||||
static Future<ServiceListBase<ServiceCar>> getCarByOrderId(
|
||||
dynamic formData,
|
||||
) async {
|
||||
final res = await HttpManage().dio.get(
|
||||
"/hard/hard-api/ad/get-by-order-id",
|
||||
queryParameters: formData,
|
||||
);
|
||||
if (res.data?.data == null) {
|
||||
return res.data!;
|
||||
}
|
||||
final typeData =
|
||||
res.data?.data?.map<ServiceCar>((e) => ServiceCar.fromJson(e)).toList();
|
||||
res.data?.data = typeData;
|
||||
return res.data!;
|
||||
}
|
||||
|
||||
/// 核销订单
|
||||
static Future<ServiceBase> cancelQrCode(dynamic formData) async {
|
||||
final res = await HttpManage().dio.post<ServiceBase>(
|
||||
"/hard/hard-api/ad/cancel-code",
|
||||
data: formData,
|
||||
);
|
||||
return res.data!;
|
||||
}
|
||||
|
||||
/// 生成发票
|
||||
static Future<ServiceBase<ServiceGenerateInvoice>> generateInvoice(
|
||||
dynamic formData,
|
||||
) async {
|
||||
final res = await HttpManage().dio.get<ServiceBase<ServiceGenerateInvoice>>(
|
||||
"/hard/hard-api/ad/generate-invoice",
|
||||
queryParameters: formData,
|
||||
);
|
||||
if (res.data?.data == null) {
|
||||
return res.data!;
|
||||
}
|
||||
final map = res.data!.data as Map<String, dynamic>;
|
||||
final typeData = ServiceGenerateInvoice.fromJson(map);
|
||||
res.data?.data = typeData;
|
||||
return res.data!;
|
||||
}
|
||||
|
||||
/// 打印成功
|
||||
static Future<ServiceBase> printInvoice(dynamic formData) async {
|
||||
final res = await HttpManage().dio.get<ServiceBase>(
|
||||
"/hard/hard-api/ad/print-invoice",
|
||||
queryParameters: formData,
|
||||
);
|
||||
return res.data as ServiceBase;
|
||||
}
|
||||
|
||||
/// 离线同步
|
||||
static Future<ServiceBase> syncOfflineOrder(dynamic formData) async {
|
||||
final res = await HttpManage().dio.post<ServiceBase>(
|
||||
"/hard/hard-api/ad/sync-offline-order",
|
||||
data: formData,
|
||||
);
|
||||
return res.data as ServiceBase;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
library;
|
||||
|
||||
export 'product_api.dart';
|
||||
export './hard_api.dart';
|
||||
export './user_api.dart';
|
||||
export './wallet_api.dart';
|
||||
@@ -0,0 +1,57 @@
|
||||
import 'package:pj_supermarket_cloud/common/index.dart';
|
||||
|
||||
abstract class ProductApi {
|
||||
/// 菜品分类
|
||||
static Future<List<ServiceClassify>?> cookbookClassify(
|
||||
dynamic pa,
|
||||
) async {
|
||||
final res = await HttpManage().dio.get(
|
||||
"/canteen/canteenCategory/getAllSimple",
|
||||
queryParameters: pa,
|
||||
);
|
||||
if (res.data == null) return null;
|
||||
return res.data
|
||||
?.map<ServiceClassify>((e) => ServiceClassify.fromJson(e))
|
||||
.toList();
|
||||
}
|
||||
|
||||
/// 菜品商品
|
||||
static Future<List<ServiceProduct>?> canteenProduct(
|
||||
dynamic pa,
|
||||
) async {
|
||||
final res = await HttpManage().dio.get(
|
||||
"/canteen/canteenDailyMenu/ad/getCanteenList",
|
||||
queryParameters: pa,
|
||||
);
|
||||
if (res.data == null) return null;
|
||||
return res.data
|
||||
?.map<ServiceProduct>((e) => ServiceProduct.fromJson(e))
|
||||
.toList();
|
||||
}
|
||||
|
||||
/// 超市分类
|
||||
static Future<List<ServiceClassify>?> supermarketClassify(
|
||||
dynamic pa,
|
||||
) async {
|
||||
final res = await HttpManage().dio.get(
|
||||
"/supermarket-classify",
|
||||
queryParameters: pa,
|
||||
);
|
||||
if (res.data == null) return null;
|
||||
return res.data
|
||||
?.map<ServiceClassify>((e) => ServiceClassify.fromJson(e))
|
||||
.toList();
|
||||
}
|
||||
|
||||
/// 超市商品
|
||||
static Future<List<ServiceProduct>?> supermarketProduct(dynamic pa) async {
|
||||
final res = await HttpManage().dio.get(
|
||||
"/supermarket-product",
|
||||
queryParameters: pa,
|
||||
);
|
||||
if (res.data == null) return null;
|
||||
return res.data
|
||||
?.map<ServiceProduct>((e) => ServiceProduct.fromJson(e))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import 'package:dio/dio.dart' as dio;
|
||||
|
||||
import 'package:pj_supermarket_cloud/common/index.dart';
|
||||
|
||||
abstract class UserApi {
|
||||
/// 硬件管理登录
|
||||
static Future<dynamic> login(dynamic pa) async {
|
||||
final res = await HttpManage().dio.post(
|
||||
"/auth/oauth2/token",
|
||||
queryParameters: {
|
||||
"username": pa["username"],
|
||||
"randomStr": "",
|
||||
"code": "",
|
||||
"grant_type": pa["grant_type"],
|
||||
"scope": pa["scope"],
|
||||
},
|
||||
data: {
|
||||
"password": pa["password"],
|
||||
},
|
||||
options: dio.Options(
|
||||
headers: {
|
||||
"Authorization": EncryptionTool.getLoginAuthorization(),
|
||||
"skipToken": true,
|
||||
"content-type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
),
|
||||
);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
/// 刷新token
|
||||
static Future<ServiceBase<ServiceAuth>> refreshToken(dynamic pa) async {
|
||||
final res = await HttpManage().dio.post(
|
||||
"/refresh-token",
|
||||
queryParameters: pa,
|
||||
);
|
||||
if (res.data?.data == null) {
|
||||
return res.data;
|
||||
}
|
||||
final map = res.data!.data as Map<String, dynamic>;
|
||||
final typeData = ServiceAuth.fromJson(map);
|
||||
res.data?.data = typeData;
|
||||
return res.data;
|
||||
}
|
||||
|
||||
/// 获取人脸
|
||||
static Future<ServiceListBase<ServiceAsyncFace>> getFaceList(
|
||||
dynamic formData,
|
||||
) async {
|
||||
final res = await HttpManage().dio.get(
|
||||
"/admin/user-face/ad/find-face",
|
||||
queryParameters: formData,
|
||||
);
|
||||
if (res.data?.data == null) {
|
||||
return res.data;
|
||||
}
|
||||
final typeData = res.data?.data
|
||||
?.map<ServiceAsyncFace>((e) => ServiceAsyncFace.fromJson(e))
|
||||
.toList();
|
||||
res.data?.data = typeData;
|
||||
return res.data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import 'package:pj_supermarket_cloud/common/index.dart';
|
||||
|
||||
abstract class WalletApi {
|
||||
/// 支付配置
|
||||
static Future<ServicePaySetting?> getPaySetting() async {
|
||||
final res = await HttpManage().dio.get(
|
||||
"/wallet/wallet-setting/ad/get-setting",
|
||||
);
|
||||
if (res.data == null) return null;
|
||||
return ServicePaySetting.fromJson(res.data);
|
||||
}
|
||||
|
||||
/// 开卡列表
|
||||
static Future<ServiceBase?> openCardList(dynamic pa) async {
|
||||
final res = await HttpManage().dio.get(
|
||||
"/wallet/wallet-active/ad/open-card-list",
|
||||
queryParameters: pa,
|
||||
);
|
||||
if (res.data == null) return null;
|
||||
return res.data as ServiceBase;
|
||||
}
|
||||
|
||||
/// 开卡成功
|
||||
static Future<bool> openCardSuccess(dynamic pa) async {
|
||||
final res = await HttpManage().dio.get<ServiceBase>(
|
||||
"/wallet/wallet-active/ad/open-card-success",
|
||||
queryParameters: pa,
|
||||
);
|
||||
if (res.data == null) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// 重新激活
|
||||
static Future<bool> refreshAvatar(dynamic pa) async {
|
||||
final res = await HttpManage().dio.get<ServiceBase>(
|
||||
"/wallet/wallet-active/ad/refresh-avatar",
|
||||
queryParameters: pa,
|
||||
);
|
||||
if (res.data == null) return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
library;
|
||||
|
||||
// export './xxxx.dart';
|
||||
@@ -0,0 +1,3 @@
|
||||
library;
|
||||
|
||||
// export './xxxx.dart';
|
||||
@@ -0,0 +1,3 @@
|
||||
library;
|
||||
|
||||
// export './xxxx.dart';
|
||||
@@ -0,0 +1,13 @@
|
||||
library;
|
||||
|
||||
export 'api/index.dart';
|
||||
export 'components/index.dart';
|
||||
export 'extension/index.dart';
|
||||
export 'i18n/index.dart';
|
||||
export 'models/index.dart';
|
||||
export 'routers/index.dart';
|
||||
export 'services/index.dart';
|
||||
export 'style/index.dart';
|
||||
export 'utils/index.dart';
|
||||
export 'values/index.dart';
|
||||
export 'widgets/index.dart';
|
||||
@@ -0,0 +1,40 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'base_api_models.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class ServiceBase<T> {
|
||||
String? msg;
|
||||
int? code;
|
||||
dynamic data;
|
||||
|
||||
ServiceBase({
|
||||
this.msg,
|
||||
this.code,
|
||||
this.data,
|
||||
});
|
||||
|
||||
factory ServiceBase.fromJson(Map<String, dynamic> json) =>
|
||||
_$ServiceBaseFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$ServiceBaseToJson(this);
|
||||
}
|
||||
|
||||
@JsonSerializable()
|
||||
class ServiceListBase<T> {
|
||||
String? msg;
|
||||
int? code;
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
List<T>? data;
|
||||
|
||||
ServiceListBase({
|
||||
this.msg,
|
||||
this.code,
|
||||
this.data,
|
||||
});
|
||||
|
||||
factory ServiceListBase.fromJson(Map<String, dynamic> json) =>
|
||||
_$ServiceListBaseFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$ServiceListBaseToJson(this);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'base_api_models.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
ServiceBase<T> _$ServiceBaseFromJson<T>(Map<String, dynamic> json) =>
|
||||
ServiceBase<T>(
|
||||
msg: json['msg'] as String?,
|
||||
code: (json['code'] as num?)?.toInt(),
|
||||
data: json['data'],
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$ServiceBaseToJson<T>(ServiceBase<T> instance) =>
|
||||
<String, dynamic>{
|
||||
'msg': instance.msg,
|
||||
'code': instance.code,
|
||||
'data': instance.data,
|
||||
};
|
||||
|
||||
ServiceListBase<T> _$ServiceListBaseFromJson<T>(Map<String, dynamic> json) =>
|
||||
ServiceListBase<T>(
|
||||
msg: json['msg'] as String?,
|
||||
code: (json['code'] as num?)?.toInt(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$ServiceListBaseToJson<T>(ServiceListBase<T> instance) =>
|
||||
<String, dynamic>{
|
||||
'msg': instance.msg,
|
||||
'code': instance.code,
|
||||
};
|
||||
@@ -0,0 +1,203 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
import 'package:pj_supermarket_cloud/common/index.dart';
|
||||
|
||||
part 'hard_api_models.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class ServiceHardware extends ServiceBase {
|
||||
/// 编号
|
||||
final String? id;
|
||||
|
||||
/// 设备名
|
||||
final String? name;
|
||||
|
||||
/// 设备类型
|
||||
final String? type;
|
||||
|
||||
/// 设备类型enum
|
||||
final String? typeEnum;
|
||||
|
||||
/// 商户id
|
||||
final String? merchantId;
|
||||
|
||||
/// 商户部门id
|
||||
final String? merchantDeptId;
|
||||
|
||||
/// 硬件分组id
|
||||
final String? hardGroupId;
|
||||
|
||||
/// 商户名
|
||||
final String? merchantStr;
|
||||
|
||||
/// mq连接id
|
||||
final String? mqClientId;
|
||||
|
||||
/// android唯一id
|
||||
final String? uniqueCode;
|
||||
|
||||
/// 当前设备的topic
|
||||
final String? mainTopic;
|
||||
|
||||
/// mq地址
|
||||
final String? mqSocketAddress;
|
||||
|
||||
/// mq端口
|
||||
final String? mqSocketPort;
|
||||
|
||||
/// mq用户名
|
||||
final String? mqUsername;
|
||||
|
||||
/// mq密码
|
||||
final String? mqPassword;
|
||||
|
||||
/// 同步次数版本
|
||||
final int? mqSyncCount;
|
||||
|
||||
/// 人脸appId
|
||||
final String? faceAppId;
|
||||
|
||||
/// 人脸sdkKey
|
||||
final String? faceSdkKey;
|
||||
|
||||
/// 人脸activeKey
|
||||
final String? faceActiveKey;
|
||||
|
||||
ServiceHardware({
|
||||
this.id,
|
||||
this.name,
|
||||
this.type,
|
||||
this.typeEnum,
|
||||
this.merchantId,
|
||||
this.merchantDeptId,
|
||||
this.hardGroupId,
|
||||
this.merchantStr,
|
||||
this.mqClientId,
|
||||
this.uniqueCode,
|
||||
this.mainTopic,
|
||||
this.mqSocketAddress,
|
||||
this.mqSocketPort,
|
||||
this.mqUsername,
|
||||
this.mqPassword,
|
||||
this.mqSyncCount,
|
||||
this.faceAppId,
|
||||
this.faceSdkKey,
|
||||
this.faceActiveKey,
|
||||
});
|
||||
|
||||
factory ServiceHardware.fromJson(Map<String, dynamic> json) =>
|
||||
_$ServiceHardwareFromJson(json);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() => _$ServiceHardwareToJson(this);
|
||||
}
|
||||
|
||||
@JsonSerializable()
|
||||
class ServiceCar {
|
||||
/// 编号
|
||||
final String? id;
|
||||
|
||||
/// 父购物车id
|
||||
final String? parentCarId;
|
||||
|
||||
/// 商品名
|
||||
final String? productName;
|
||||
|
||||
/// 商品价格
|
||||
final int? productPrice;
|
||||
|
||||
/// 商品id
|
||||
final String? productId;
|
||||
|
||||
/// 数量
|
||||
final int? productCount;
|
||||
|
||||
/// 总价(分)
|
||||
@JsonKey(fromJson: JsonTool.stringToInt, toJson: JsonTool.intToString)
|
||||
final int? totalPrice;
|
||||
|
||||
/// 商品图片
|
||||
final String? productPic;
|
||||
|
||||
ServiceCar({
|
||||
this.id,
|
||||
this.parentCarId,
|
||||
this.productName,
|
||||
this.productPrice,
|
||||
this.productId,
|
||||
this.productCount,
|
||||
this.totalPrice,
|
||||
this.productPic,
|
||||
});
|
||||
|
||||
factory ServiceCar.fromJson(Map<String, dynamic> json) =>
|
||||
_$ServiceCarFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$ServiceCarToJson(this);
|
||||
}
|
||||
|
||||
@JsonSerializable()
|
||||
class ServiceGenerateInvoice {
|
||||
/// 图片
|
||||
final String? pic;
|
||||
|
||||
/// 记录id
|
||||
final String? historyId;
|
||||
|
||||
ServiceGenerateInvoice({
|
||||
this.pic,
|
||||
this.historyId,
|
||||
});
|
||||
|
||||
factory ServiceGenerateInvoice.fromJson(Map<String, dynamic> json) =>
|
||||
_$ServiceGenerateInvoiceFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$ServiceGenerateInvoiceToJson(this);
|
||||
}
|
||||
|
||||
@JsonSerializable()
|
||||
class ServiceQRCode {
|
||||
/// 总id
|
||||
@JsonKey(fromJson: JsonTool.intToString, toJson: JsonTool.stringToInt)
|
||||
final String? id;
|
||||
|
||||
/// 子id
|
||||
@JsonKey(fromJson: JsonTool.intToString, toJson: JsonTool.stringToInt)
|
||||
final String? itemId;
|
||||
|
||||
/// 类型id 比如钱包类型
|
||||
@JsonKey(fromJson: JsonTool.intToString, toJson: JsonTool.stringToInt)
|
||||
final String? typeId;
|
||||
|
||||
/// 用户id
|
||||
@JsonKey(fromJson: JsonTool.intToString, toJson: JsonTool.stringToInt)
|
||||
final String? userId;
|
||||
|
||||
/// 订单类型
|
||||
final String? orderType;
|
||||
|
||||
/// 支付类型
|
||||
final String? payType;
|
||||
|
||||
/// 过期时间 -1 代表没有限制
|
||||
final int? exceedTime;
|
||||
|
||||
/// 额外的用户信息
|
||||
final String? extra;
|
||||
|
||||
ServiceQRCode({
|
||||
this.id,
|
||||
this.itemId,
|
||||
this.typeId,
|
||||
this.userId,
|
||||
this.orderType,
|
||||
this.payType,
|
||||
this.exceedTime,
|
||||
this.extra,
|
||||
});
|
||||
|
||||
factory ServiceQRCode.fromJson(Map<String, dynamic> json) =>
|
||||
_$ServiceQRCodeFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$ServiceQRCodeToJson(this);
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'hard_api_models.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
ServiceHardware _$ServiceHardwareFromJson(Map<String, dynamic> json) =>
|
||||
ServiceHardware(
|
||||
id: json['id'] as String?,
|
||||
name: json['name'] as String?,
|
||||
type: json['type'] as String?,
|
||||
typeEnum: json['typeEnum'] as String?,
|
||||
merchantId: json['merchantId'] as String?,
|
||||
merchantDeptId: json['merchantDeptId'] as String?,
|
||||
hardGroupId: json['hardGroupId'] as String?,
|
||||
merchantStr: json['merchantStr'] as String?,
|
||||
mqClientId: json['mqClientId'] as String?,
|
||||
uniqueCode: json['uniqueCode'] as String?,
|
||||
mainTopic: json['mainTopic'] as String?,
|
||||
mqSocketAddress: json['mqSocketAddress'] as String?,
|
||||
mqSocketPort: json['mqSocketPort'] as String?,
|
||||
mqUsername: json['mqUsername'] as String?,
|
||||
mqPassword: json['mqPassword'] as String?,
|
||||
mqSyncCount: (json['mqSyncCount'] as num?)?.toInt(),
|
||||
faceAppId: json['faceAppId'] as String?,
|
||||
faceSdkKey: json['faceSdkKey'] as String?,
|
||||
faceActiveKey: json['faceActiveKey'] as String?,
|
||||
)
|
||||
..msg = json['msg'] as String?
|
||||
..code = (json['code'] as num?)?.toInt()
|
||||
..data = json['data'];
|
||||
|
||||
Map<String, dynamic> _$ServiceHardwareToJson(ServiceHardware instance) =>
|
||||
<String, dynamic>{
|
||||
'msg': instance.msg,
|
||||
'code': instance.code,
|
||||
'data': instance.data,
|
||||
'id': instance.id,
|
||||
'name': instance.name,
|
||||
'type': instance.type,
|
||||
'typeEnum': instance.typeEnum,
|
||||
'merchantId': instance.merchantId,
|
||||
'merchantDeptId': instance.merchantDeptId,
|
||||
'hardGroupId': instance.hardGroupId,
|
||||
'merchantStr': instance.merchantStr,
|
||||
'mqClientId': instance.mqClientId,
|
||||
'uniqueCode': instance.uniqueCode,
|
||||
'mainTopic': instance.mainTopic,
|
||||
'mqSocketAddress': instance.mqSocketAddress,
|
||||
'mqSocketPort': instance.mqSocketPort,
|
||||
'mqUsername': instance.mqUsername,
|
||||
'mqPassword': instance.mqPassword,
|
||||
'mqSyncCount': instance.mqSyncCount,
|
||||
'faceAppId': instance.faceAppId,
|
||||
'faceSdkKey': instance.faceSdkKey,
|
||||
'faceActiveKey': instance.faceActiveKey,
|
||||
};
|
||||
|
||||
ServiceCar _$ServiceCarFromJson(Map<String, dynamic> json) => ServiceCar(
|
||||
id: json['id'] as String?,
|
||||
parentCarId: json['parentCarId'] as String?,
|
||||
productName: json['productName'] as String?,
|
||||
productPrice: (json['productPrice'] as num?)?.toInt(),
|
||||
productId: json['productId'] as String?,
|
||||
productCount: (json['productCount'] as num?)?.toInt(),
|
||||
totalPrice: JsonTool.stringToInt(json['totalPrice'] as String?),
|
||||
productPic: json['productPic'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$ServiceCarToJson(ServiceCar instance) =>
|
||||
<String, dynamic>{
|
||||
'id': instance.id,
|
||||
'parentCarId': instance.parentCarId,
|
||||
'productName': instance.productName,
|
||||
'productPrice': instance.productPrice,
|
||||
'productId': instance.productId,
|
||||
'productCount': instance.productCount,
|
||||
'totalPrice': JsonTool.intToString(instance.totalPrice),
|
||||
'productPic': instance.productPic,
|
||||
};
|
||||
|
||||
ServiceGenerateInvoice _$ServiceGenerateInvoiceFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
ServiceGenerateInvoice(
|
||||
pic: json['pic'] as String?,
|
||||
historyId: json['historyId'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$ServiceGenerateInvoiceToJson(
|
||||
ServiceGenerateInvoice instance) =>
|
||||
<String, dynamic>{
|
||||
'pic': instance.pic,
|
||||
'historyId': instance.historyId,
|
||||
};
|
||||
|
||||
ServiceQRCode _$ServiceQRCodeFromJson(Map<String, dynamic> json) =>
|
||||
ServiceQRCode(
|
||||
id: JsonTool.intToString((json['id'] as num?)?.toInt()),
|
||||
itemId: JsonTool.intToString((json['itemId'] as num?)?.toInt()),
|
||||
typeId: JsonTool.intToString((json['typeId'] as num?)?.toInt()),
|
||||
userId: JsonTool.intToString((json['userId'] as num?)?.toInt()),
|
||||
orderType: json['orderType'] as String?,
|
||||
payType: json['payType'] as String?,
|
||||
exceedTime: (json['exceedTime'] as num?)?.toInt(),
|
||||
extra: json['extra'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$ServiceQRCodeToJson(ServiceQRCode instance) =>
|
||||
<String, dynamic>{
|
||||
'id': JsonTool.stringToInt(instance.id),
|
||||
'itemId': JsonTool.stringToInt(instance.itemId),
|
||||
'typeId': JsonTool.stringToInt(instance.typeId),
|
||||
'userId': JsonTool.stringToInt(instance.userId),
|
||||
'orderType': instance.orderType,
|
||||
'payType': instance.payType,
|
||||
'exceedTime': instance.exceedTime,
|
||||
'extra': instance.extra,
|
||||
};
|
||||
@@ -0,0 +1,90 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:pj_supermarket_cloud/common/index.dart';
|
||||
|
||||
part 'product_api_models.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class ServiceClassify {
|
||||
/// 分类编号
|
||||
String? id;
|
||||
|
||||
/// 分类名称
|
||||
String? name;
|
||||
|
||||
/// 移动端分类图
|
||||
String? picUrl;
|
||||
|
||||
/// 分类排序
|
||||
int? sort;
|
||||
|
||||
/// 开启状态
|
||||
int? status;
|
||||
|
||||
/// 系统类型
|
||||
int? systemType;
|
||||
|
||||
/// 用餐类型,早餐、中餐、晚餐
|
||||
String? categoryType;
|
||||
|
||||
ServiceClassify({
|
||||
this.id,
|
||||
this.name,
|
||||
this.picUrl,
|
||||
this.sort,
|
||||
this.status,
|
||||
this.systemType,
|
||||
this.categoryType,
|
||||
});
|
||||
|
||||
factory ServiceClassify.fromJson(Map<String, dynamic> json) =>
|
||||
_$ServiceClassifyFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$ServiceClassifyToJson(this);
|
||||
}
|
||||
|
||||
@JsonSerializable()
|
||||
class ServiceProduct {
|
||||
/// id
|
||||
String? id;
|
||||
|
||||
/// 菜品id
|
||||
String? spuId;
|
||||
|
||||
/// 时间类型 (食堂特有)
|
||||
@JsonKey(fromJson: JsonTool.anyToString)
|
||||
String? hourType;
|
||||
|
||||
/// 菜品分类编号
|
||||
String? categoryId;
|
||||
|
||||
/// 商品名
|
||||
String? name;
|
||||
|
||||
/// 商品图片
|
||||
String? picUrl;
|
||||
|
||||
/// 价格分
|
||||
int? price;
|
||||
|
||||
/// 排序
|
||||
int? sort;
|
||||
|
||||
/// 用餐类型,早餐、中餐、晚餐
|
||||
String? categoryType;
|
||||
|
||||
ServiceProduct({
|
||||
this.id,
|
||||
this.spuId,
|
||||
this.categoryId,
|
||||
this.name,
|
||||
this.picUrl,
|
||||
this.hourType,
|
||||
this.sort,
|
||||
this.categoryType,
|
||||
});
|
||||
|
||||
factory ServiceProduct.fromJson(Map<String, dynamic> json) =>
|
||||
_$ServiceProductFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$ServiceProductToJson(this);
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'product_api_models.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
ServiceClassify _$ServiceClassifyFromJson(Map<String, dynamic> json) =>
|
||||
ServiceClassify(
|
||||
id: json['id'] as String?,
|
||||
name: json['name'] as String?,
|
||||
picUrl: json['picUrl'] as String?,
|
||||
sort: (json['sort'] as num?)?.toInt(),
|
||||
status: (json['status'] as num?)?.toInt(),
|
||||
systemType: (json['systemType'] as num?)?.toInt(),
|
||||
categoryType: json['categoryType'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$ServiceClassifyToJson(ServiceClassify instance) =>
|
||||
<String, dynamic>{
|
||||
'id': instance.id,
|
||||
'name': instance.name,
|
||||
'picUrl': instance.picUrl,
|
||||
'sort': instance.sort,
|
||||
'status': instance.status,
|
||||
'systemType': instance.systemType,
|
||||
'categoryType': instance.categoryType,
|
||||
};
|
||||
|
||||
ServiceProduct _$ServiceProductFromJson(Map<String, dynamic> json) =>
|
||||
ServiceProduct(
|
||||
id: json['id'] as String?,
|
||||
spuId: json['spuId'] as String?,
|
||||
categoryId: json['categoryId'] as String?,
|
||||
name: json['name'] as String?,
|
||||
picUrl: json['picUrl'] as String?,
|
||||
hourType: JsonTool.anyToString(json['hourType']),
|
||||
sort: (json['sort'] as num?)?.toInt(),
|
||||
categoryType: json['categoryType'] as String?,
|
||||
)..price = (json['price'] as num?)?.toInt();
|
||||
|
||||
Map<String, dynamic> _$ServiceProductToJson(ServiceProduct instance) =>
|
||||
<String, dynamic>{
|
||||
'id': instance.id,
|
||||
'spuId': instance.spuId,
|
||||
'hourType': instance.hourType,
|
||||
'categoryId': instance.categoryId,
|
||||
'name': instance.name,
|
||||
'picUrl': instance.picUrl,
|
||||
'price': instance.price,
|
||||
'sort': instance.sort,
|
||||
'categoryType': instance.categoryType,
|
||||
};
|
||||
@@ -0,0 +1,128 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
import 'package:pj_supermarket_cloud/common/index.dart';
|
||||
|
||||
part 'user_api_models.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class ServiceUser {
|
||||
/// 用户ID
|
||||
String? userId;
|
||||
|
||||
/// 用户昵称
|
||||
String? username;
|
||||
|
||||
/// 部门ID
|
||||
String? deptId;
|
||||
|
||||
/// 手机号码
|
||||
String? userMobile;
|
||||
|
||||
/// 用户头像
|
||||
String? userAvatar;
|
||||
|
||||
/// 钱包id
|
||||
String? walletId;
|
||||
|
||||
/// 钱包类型id
|
||||
String? walletTypeId;
|
||||
|
||||
/// 钱包余额
|
||||
int? walletBalance;
|
||||
|
||||
/// 是否冻结饭卡
|
||||
@JsonKey(fromJson: JsonTool.stringToBoolean, toJson: JsonTool.booleanToString)
|
||||
bool? walletIsFreeze;
|
||||
|
||||
/// 卡号
|
||||
String? walletCardNumber;
|
||||
|
||||
/// 构造函数
|
||||
ServiceUser({
|
||||
this.userId,
|
||||
this.username,
|
||||
this.deptId,
|
||||
this.userMobile,
|
||||
this.userAvatar,
|
||||
this.walletId,
|
||||
this.walletTypeId,
|
||||
this.walletBalance,
|
||||
this.walletIsFreeze,
|
||||
this.walletCardNumber,
|
||||
});
|
||||
|
||||
factory ServiceUser.fromJson(Map<String, dynamic> json) =>
|
||||
_$ServiceUserFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$ServiceUserToJson(this);
|
||||
}
|
||||
|
||||
@JsonSerializable()
|
||||
class ServiceAuth {
|
||||
/// 用户编号
|
||||
final int? userId;
|
||||
|
||||
/// 访问令牌
|
||||
final String? accessToken;
|
||||
|
||||
/// 刷新令牌
|
||||
final String? refreshToken;
|
||||
|
||||
/// 过期时间
|
||||
final int? expiresTime;
|
||||
|
||||
ServiceAuth({
|
||||
required this.userId,
|
||||
required this.accessToken,
|
||||
required this.refreshToken,
|
||||
required this.expiresTime,
|
||||
});
|
||||
|
||||
factory ServiceAuth.fromJson(Map<String, dynamic> json) =>
|
||||
_$ServiceAuthFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$ServiceAuthToJson(this);
|
||||
}
|
||||
|
||||
@JsonSerializable()
|
||||
class ServiceAsyncFace {
|
||||
/// 编号
|
||||
final String? id;
|
||||
|
||||
/// 人脸编号
|
||||
final String? autoCode;
|
||||
|
||||
/// 人脸图片
|
||||
final String? faceImg;
|
||||
|
||||
/// 用户id
|
||||
final String? userId;
|
||||
|
||||
/// 用户名
|
||||
final String? username;
|
||||
|
||||
/// 用户头像
|
||||
final String? avatar;
|
||||
|
||||
/// 部门id
|
||||
final String? deptId;
|
||||
|
||||
/// 部门名
|
||||
final String? deptName;
|
||||
|
||||
ServiceAsyncFace({
|
||||
this.id,
|
||||
this.autoCode,
|
||||
this.faceImg,
|
||||
this.userId,
|
||||
this.username,
|
||||
this.avatar,
|
||||
this.deptId,
|
||||
this.deptName,
|
||||
});
|
||||
|
||||
factory ServiceAsyncFace.fromJson(Map<String, dynamic> json) =>
|
||||
_$ServiceAsyncFaceFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$ServiceAsyncFaceToJson(this);
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'user_api_models.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
ServiceUser _$ServiceUserFromJson(Map<String, dynamic> json) => ServiceUser(
|
||||
userId: json['userId'] as String?,
|
||||
username: json['username'] as String?,
|
||||
deptId: json['deptId'] as String?,
|
||||
userMobile: json['userMobile'] as String?,
|
||||
userAvatar: json['userAvatar'] as String?,
|
||||
walletId: json['walletId'] as String?,
|
||||
walletTypeId: json['walletTypeId'] as String?,
|
||||
walletBalance: (json['walletBalance'] as num?)?.toInt(),
|
||||
walletIsFreeze:
|
||||
JsonTool.stringToBoolean(json['walletIsFreeze'] as String),
|
||||
walletCardNumber: json['walletCardNumber'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$ServiceUserToJson(ServiceUser instance) =>
|
||||
<String, dynamic>{
|
||||
'userId': instance.userId,
|
||||
'username': instance.username,
|
||||
'deptId': instance.deptId,
|
||||
'userMobile': instance.userMobile,
|
||||
'userAvatar': instance.userAvatar,
|
||||
'walletId': instance.walletId,
|
||||
'walletTypeId': instance.walletTypeId,
|
||||
'walletBalance': instance.walletBalance,
|
||||
'walletIsFreeze': JsonTool.booleanToString(instance.walletIsFreeze),
|
||||
'walletCardNumber': instance.walletCardNumber,
|
||||
};
|
||||
|
||||
ServiceAuth _$ServiceAuthFromJson(Map<String, dynamic> json) => ServiceAuth(
|
||||
userId: (json['userId'] as num?)?.toInt(),
|
||||
accessToken: json['accessToken'] as String?,
|
||||
refreshToken: json['refreshToken'] as String?,
|
||||
expiresTime: (json['expiresTime'] as num?)?.toInt(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$ServiceAuthToJson(ServiceAuth instance) =>
|
||||
<String, dynamic>{
|
||||
'userId': instance.userId,
|
||||
'accessToken': instance.accessToken,
|
||||
'refreshToken': instance.refreshToken,
|
||||
'expiresTime': instance.expiresTime,
|
||||
};
|
||||
|
||||
ServiceAsyncFace _$ServiceAsyncFaceFromJson(Map<String, dynamic> json) =>
|
||||
ServiceAsyncFace(
|
||||
id: json['id'] as String?,
|
||||
autoCode: json['autoCode'] as String?,
|
||||
faceImg: json['faceImg'] as String?,
|
||||
userId: json['userId'] as String?,
|
||||
username: json['username'] as String?,
|
||||
avatar: json['avatar'] as String?,
|
||||
deptId: json['deptId'] as String?,
|
||||
deptName: json['deptName'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$ServiceAsyncFaceToJson(ServiceAsyncFace instance) =>
|
||||
<String, dynamic>{
|
||||
'id': instance.id,
|
||||
'autoCode': instance.autoCode,
|
||||
'faceImg': instance.faceImg,
|
||||
'userId': instance.userId,
|
||||
'username': instance.username,
|
||||
'avatar': instance.avatar,
|
||||
'deptId': instance.deptId,
|
||||
'deptName': instance.deptName,
|
||||
};
|
||||
@@ -0,0 +1,54 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:pj_supermarket_cloud/common/index.dart';
|
||||
|
||||
part 'wallet_api_models.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class ServicePaySetting {
|
||||
/// 唯一标识符
|
||||
final String? id;
|
||||
|
||||
/// 是否开启每一餐的额外缴费
|
||||
@JsonKey(fromJson: JsonTool.stringToBoolean, toJson: JsonTool.booleanToString)
|
||||
final bool? payAmountEnable;
|
||||
|
||||
/// 每一餐的上限(分)
|
||||
final int? payAmountMealLimit;
|
||||
|
||||
/// 每一餐的倍数
|
||||
final double? payAmountMealTimes;
|
||||
|
||||
/// 售后的退款理由
|
||||
final String? afterSaleRefundReasons;
|
||||
|
||||
/// 售后的退货理由
|
||||
final String? afterSaleReturnReasons;
|
||||
|
||||
/// 是否开启钱包二维码刷新
|
||||
@JsonKey(fromJson: JsonTool.stringToBoolean, toJson: JsonTool.booleanToString)
|
||||
final bool? refreshQrCodeEnable;
|
||||
|
||||
/// 二维码刷新频率
|
||||
final int? refreshQrCodeFre;
|
||||
|
||||
/// 是否开启离线订单审批
|
||||
@JsonKey(fromJson: JsonTool.stringToBoolean, toJson: JsonTool.booleanToString)
|
||||
final bool? offLineOrderApplyEnable;
|
||||
|
||||
ServicePaySetting({
|
||||
this.id,
|
||||
this.payAmountEnable,
|
||||
this.payAmountMealLimit,
|
||||
this.payAmountMealTimes,
|
||||
this.afterSaleRefundReasons,
|
||||
this.afterSaleReturnReasons,
|
||||
this.refreshQrCodeEnable,
|
||||
this.refreshQrCodeFre,
|
||||
this.offLineOrderApplyEnable,
|
||||
});
|
||||
|
||||
factory ServicePaySetting.fromJson(Map<String, dynamic> json) =>
|
||||
_$ServicePaySettingFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$ServicePaySettingToJson(this);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'wallet_api_models.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
ServicePaySetting _$ServicePaySettingFromJson(Map<String, dynamic> json) =>
|
||||
ServicePaySetting(
|
||||
id: json['id'] as String?,
|
||||
payAmountEnable:
|
||||
JsonTool.stringToBoolean(json['payAmountEnable'] as String),
|
||||
payAmountMealLimit: (json['payAmountMealLimit'] as num?)?.toInt(),
|
||||
payAmountMealTimes: (json['payAmountMealTimes'] as num?)?.toDouble(),
|
||||
afterSaleRefundReasons: json['afterSaleRefundReasons'] as String?,
|
||||
afterSaleReturnReasons: json['afterSaleReturnReasons'] as String?,
|
||||
refreshQrCodeEnable:
|
||||
JsonTool.stringToBoolean(json['refreshQrCodeEnable'] as String),
|
||||
refreshQrCodeFre: (json['refreshQrCodeFre'] as num?)?.toInt(),
|
||||
offLineOrderApplyEnable:
|
||||
JsonTool.stringToBoolean(json['offLineOrderApplyEnable'] as String),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$ServicePaySettingToJson(ServicePaySetting instance) =>
|
||||
<String, dynamic>{
|
||||
'id': instance.id,
|
||||
'payAmountEnable': JsonTool.booleanToString(instance.payAmountEnable),
|
||||
'payAmountMealLimit': instance.payAmountMealLimit,
|
||||
'payAmountMealTimes': instance.payAmountMealTimes,
|
||||
'afterSaleRefundReasons': instance.afterSaleRefundReasons,
|
||||
'afterSaleReturnReasons': instance.afterSaleReturnReasons,
|
||||
'refreshQrCodeEnable':
|
||||
JsonTool.booleanToString(instance.refreshQrCodeEnable),
|
||||
'refreshQrCodeFre': instance.refreshQrCodeFre,
|
||||
'offLineOrderApplyEnable':
|
||||
JsonTool.booleanToString(instance.offLineOrderApplyEnable),
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
import 'package:common_utils/common_utils.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
abstract interface class BaseEnum<T> {
|
||||
String get code;
|
||||
String get title;
|
||||
}
|
||||
|
||||
extension BaseEnumExtension<T extends BaseEnum> on List<T> {
|
||||
T? fromCode(String? code) {
|
||||
if (ObjectUtil.isEmpty(code)) return null;
|
||||
return firstWhereOrNull((v) => v.code == code);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
import 'package:pj_supermarket_cloud/common/index.dart';
|
||||
|
||||
/// 设备类型
|
||||
enum SysType implements BaseEnum {
|
||||
supermarket("1", "超市", "智慧超市", "supermarket"),
|
||||
canteen("2", "食堂", "智慧食堂", "canteen"),
|
||||
;
|
||||
|
||||
@override
|
||||
final String code;
|
||||
@override
|
||||
final String title;
|
||||
final String cnStr;
|
||||
final String usStr;
|
||||
|
||||
const SysType(this.code, this.title, this.cnStr, this.usStr);
|
||||
}
|
||||
|
||||
/// 订单类型
|
||||
enum OrderType implements BaseEnum {
|
||||
below("1", "线下购买"),
|
||||
reserve("2", "线上订餐"),
|
||||
foreignTicket("3", "外来人员"),
|
||||
strideDept("4", "跨单位"),
|
||||
;
|
||||
|
||||
@override
|
||||
final String code;
|
||||
@override
|
||||
final String title;
|
||||
|
||||
const OrderType(this.code, this.title);
|
||||
}
|
||||
|
||||
/// 付款方式
|
||||
enum OrderPayType implements BaseEnum {
|
||||
walletDefault("1", "普通钱包支付"),
|
||||
walletQrCode("2", "钱包二维码支付"),
|
||||
walletCard("3", "钱包饭卡支付"),
|
||||
face("4", "人脸识别支付"),
|
||||
strideDeptQrCode("5", "跨单位二维码支付"),
|
||||
foreignTicketQrCode("6", "外来人员二维码支付"),
|
||||
reserveQrCode("7", "线上订餐二维码支付"),
|
||||
;
|
||||
|
||||
@override
|
||||
final String code;
|
||||
@override
|
||||
final String title;
|
||||
|
||||
const OrderPayType(this.code, this.title);
|
||||
}
|
||||
|
||||
/// 订单状态
|
||||
enum OrderStatus implements BaseEnum {
|
||||
// 查询使用
|
||||
all("-1", "未付款"),
|
||||
|
||||
nonePay("1", "未付款"),
|
||||
pay("5", "已付款"),
|
||||
offlinePay("6", "本地支付"),
|
||||
refund("10", "已退订"),
|
||||
;
|
||||
|
||||
@override
|
||||
final String code;
|
||||
@override
|
||||
final String title;
|
||||
|
||||
const OrderStatus(this.code, this.title);
|
||||
}
|
||||
|
||||
// 打印状态
|
||||
enum PrintStatus implements BaseEnum {
|
||||
none("10", "未打印"),
|
||||
success("20", "打印成功"),
|
||||
sync("30", "同步成功"),
|
||||
error("40", "打印失败"),
|
||||
;
|
||||
|
||||
@override
|
||||
final String code;
|
||||
@override
|
||||
final String title;
|
||||
|
||||
const PrintStatus(this.code, this.title);
|
||||
}
|
||||
|
||||
enum ProductHourType implements BaseEnum {
|
||||
all("-1", "查询全部"),
|
||||
morning("1", "早餐"),
|
||||
noon("2", "中餐"),
|
||||
night("3", "晚餐"),
|
||||
;
|
||||
|
||||
@override
|
||||
final String code;
|
||||
@override
|
||||
final String title;
|
||||
|
||||
const ProductHourType(this.code, this.title);
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
import 'package:pj_supermarket_cloud/common/index.dart';
|
||||
|
||||
part 'database_models.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class SysSetting extends Equatable {
|
||||
/// 设备类型
|
||||
SysType sysType;
|
||||
|
||||
/// mac地址
|
||||
String? androidId;
|
||||
|
||||
/// 服务端ID
|
||||
String? serviceId;
|
||||
|
||||
/// 服务单设备名
|
||||
String? serviceName;
|
||||
|
||||
/// 商户id
|
||||
String? serviceMerchantId;
|
||||
|
||||
/// 商户部门id
|
||||
String? merchantDeptId;
|
||||
|
||||
/// 硬件分组id
|
||||
String? hardGroupId;
|
||||
|
||||
/// mq地址
|
||||
String? mqSocketAddress;
|
||||
|
||||
/// mq端口
|
||||
String? mqSocketPort;
|
||||
|
||||
/// mq用户名
|
||||
String? mqUsername;
|
||||
|
||||
/// mq密码
|
||||
String? mqPassword;
|
||||
|
||||
/// 当前设备的topic
|
||||
String? mainTopic;
|
||||
|
||||
/// mq连接id
|
||||
String? mqClientId;
|
||||
|
||||
/// mq状态
|
||||
String? mqStatus;
|
||||
|
||||
/// 外部存储地址
|
||||
String? externalPath;
|
||||
|
||||
/// 数据库地址
|
||||
String? sqlAddress;
|
||||
|
||||
/// 日志地址
|
||||
String? logAddress;
|
||||
|
||||
/// 人脸 appId
|
||||
String? faceAppId;
|
||||
|
||||
/// 人脸 sdkKey
|
||||
String? faceSdkKey;
|
||||
|
||||
/// 人脸 activeKey
|
||||
String? faceActiveKey;
|
||||
|
||||
/// 同步时间
|
||||
DateTime? syncTime;
|
||||
|
||||
/// 菜谱分类同步时间
|
||||
DateTime? canteenClassifySyncTime;
|
||||
|
||||
/// 菜谱商品同步时间
|
||||
DateTime? canteenProductSyncTime;
|
||||
|
||||
/// 超市分类同步时间
|
||||
DateTime? supermarketClassifySyncTime;
|
||||
|
||||
/// 超市商品同步时间
|
||||
DateTime? supermarketProductSyncTime;
|
||||
|
||||
/// 离线订单同步时间
|
||||
DateTime? offlineOrderSyncTime;
|
||||
|
||||
/// 长链接重连
|
||||
DateTime? mqSyncTime;
|
||||
|
||||
SysSetting({
|
||||
required this.sysType,
|
||||
this.androidId,
|
||||
this.serviceId,
|
||||
this.mqSocketAddress,
|
||||
this.mqStatus,
|
||||
this.sqlAddress,
|
||||
this.logAddress,
|
||||
this.hardGroupId,
|
||||
});
|
||||
|
||||
factory SysSetting.fromJson(Map<String, dynamic> json) =>
|
||||
_$SysSettingFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$SysSettingToJson(this);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
sysType,
|
||||
androidId,
|
||||
serviceId,
|
||||
serviceName,
|
||||
serviceMerchantId,
|
||||
merchantDeptId,
|
||||
hardGroupId,
|
||||
mqSocketAddress,
|
||||
mqSocketPort,
|
||||
mqUsername,
|
||||
mqPassword,
|
||||
mainTopic,
|
||||
mqClientId,
|
||||
mqStatus,
|
||||
externalPath,
|
||||
sqlAddress,
|
||||
logAddress,
|
||||
faceAppId,
|
||||
faceSdkKey,
|
||||
faceActiveKey,
|
||||
syncTime?.millisecondsSinceEpoch,
|
||||
canteenClassifySyncTime?.millisecondsSinceEpoch,
|
||||
canteenProductSyncTime?.millisecondsSinceEpoch,
|
||||
supermarketClassifySyncTime?.millisecondsSinceEpoch,
|
||||
supermarketProductSyncTime?.millisecondsSinceEpoch,
|
||||
offlineOrderSyncTime?.millisecondsSinceEpoch,
|
||||
mqSyncTime?.millisecondsSinceEpoch,
|
||||
];
|
||||
}
|
||||
|
||||
/// 用户信息
|
||||
@JsonSerializable()
|
||||
class UserSetting extends Equatable {
|
||||
/// 账号
|
||||
String? username;
|
||||
|
||||
/// 密码
|
||||
String? password;
|
||||
|
||||
/// start 服务端信息 start
|
||||
|
||||
/// 设备类型
|
||||
SysType? sysType;
|
||||
|
||||
/// 用户编号
|
||||
int? userId;
|
||||
|
||||
/// 访问令牌
|
||||
String? accessToken;
|
||||
|
||||
/// 刷新令牌
|
||||
String? refreshToken;
|
||||
|
||||
/// 过期时间
|
||||
int? expiresTime;
|
||||
|
||||
/// end 服务端信息 end
|
||||
|
||||
/// 商品时间段类型
|
||||
ProductHourType? hourType;
|
||||
|
||||
UserSetting({
|
||||
this.username,
|
||||
this.password,
|
||||
});
|
||||
|
||||
factory UserSetting.fromJson(Map<String, dynamic> json) =>
|
||||
_$UserSettingFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$UserSettingToJson(this);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
username,
|
||||
password,
|
||||
sysType,
|
||||
userId,
|
||||
accessToken,
|
||||
refreshToken,
|
||||
expiresTime,
|
||||
hourType,
|
||||
];
|
||||
}
|
||||
|
||||
@JsonSerializable()
|
||||
class CanteenExtraPriceSetting extends Equatable {
|
||||
/// 是否开启额外金额
|
||||
bool payAmountEnable;
|
||||
|
||||
/// 每一次上限
|
||||
int payAmountMealLimit;
|
||||
|
||||
/// 超过的倍数
|
||||
double payAmountMealTimes;
|
||||
|
||||
CanteenExtraPriceSetting({
|
||||
required this.payAmountEnable,
|
||||
required this.payAmountMealLimit,
|
||||
required this.payAmountMealTimes,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object> get props => [
|
||||
payAmountEnable,
|
||||
payAmountMealLimit,
|
||||
payAmountMealTimes,
|
||||
];
|
||||
|
||||
factory CanteenExtraPriceSetting.fromJson(Map<String, dynamic> json) =>
|
||||
_$CanteenExtraPriceSettingFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$CanteenExtraPriceSettingToJson(this);
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'database_models.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
SysSetting _$SysSettingFromJson(Map<String, dynamic> json) => SysSetting(
|
||||
sysType: $enumDecode(_$SysTypeEnumMap, json['sysType']),
|
||||
androidId: json['androidId'] as String?,
|
||||
serviceId: json['serviceId'] as String?,
|
||||
mqSocketAddress: json['mqSocketAddress'] as String?,
|
||||
mqStatus: json['mqStatus'] as String?,
|
||||
sqlAddress: json['sqlAddress'] as String?,
|
||||
logAddress: json['logAddress'] as String?,
|
||||
hardGroupId: json['hardGroupId'] as String?,
|
||||
)
|
||||
..serviceName = json['serviceName'] as String?
|
||||
..serviceMerchantId = json['serviceMerchantId'] as String?
|
||||
..merchantDeptId = json['merchantDeptId'] as String?
|
||||
..mqSocketPort = json['mqSocketPort'] as String?
|
||||
..mqUsername = json['mqUsername'] as String?
|
||||
..mqPassword = json['mqPassword'] as String?
|
||||
..mainTopic = json['mainTopic'] as String?
|
||||
..mqClientId = json['mqClientId'] as String?
|
||||
..externalPath = json['externalPath'] as String?
|
||||
..faceAppId = json['faceAppId'] as String?
|
||||
..faceSdkKey = json['faceSdkKey'] as String?
|
||||
..faceActiveKey = json['faceActiveKey'] as String?
|
||||
..syncTime = json['syncTime'] == null
|
||||
? null
|
||||
: DateTime.parse(json['syncTime'] as String)
|
||||
..canteenClassifySyncTime = json['canteenClassifySyncTime'] == null
|
||||
? null
|
||||
: DateTime.parse(json['canteenClassifySyncTime'] as String)
|
||||
..canteenProductSyncTime = json['canteenProductSyncTime'] == null
|
||||
? null
|
||||
: DateTime.parse(json['canteenProductSyncTime'] as String)
|
||||
..supermarketClassifySyncTime =
|
||||
json['supermarketClassifySyncTime'] == null
|
||||
? null
|
||||
: DateTime.parse(json['supermarketClassifySyncTime'] as String)
|
||||
..supermarketProductSyncTime = json['supermarketProductSyncTime'] == null
|
||||
? null
|
||||
: DateTime.parse(json['supermarketProductSyncTime'] as String)
|
||||
..offlineOrderSyncTime = json['offlineOrderSyncTime'] == null
|
||||
? null
|
||||
: DateTime.parse(json['offlineOrderSyncTime'] as String)
|
||||
..mqSyncTime = json['mqSyncTime'] == null
|
||||
? null
|
||||
: DateTime.parse(json['mqSyncTime'] as String);
|
||||
|
||||
Map<String, dynamic> _$SysSettingToJson(SysSetting instance) =>
|
||||
<String, dynamic>{
|
||||
'sysType': _$SysTypeEnumMap[instance.sysType]!,
|
||||
'androidId': instance.androidId,
|
||||
'serviceId': instance.serviceId,
|
||||
'serviceName': instance.serviceName,
|
||||
'serviceMerchantId': instance.serviceMerchantId,
|
||||
'merchantDeptId': instance.merchantDeptId,
|
||||
'hardGroupId': instance.hardGroupId,
|
||||
'mqSocketAddress': instance.mqSocketAddress,
|
||||
'mqSocketPort': instance.mqSocketPort,
|
||||
'mqUsername': instance.mqUsername,
|
||||
'mqPassword': instance.mqPassword,
|
||||
'mainTopic': instance.mainTopic,
|
||||
'mqClientId': instance.mqClientId,
|
||||
'mqStatus': instance.mqStatus,
|
||||
'externalPath': instance.externalPath,
|
||||
'sqlAddress': instance.sqlAddress,
|
||||
'logAddress': instance.logAddress,
|
||||
'faceAppId': instance.faceAppId,
|
||||
'faceSdkKey': instance.faceSdkKey,
|
||||
'faceActiveKey': instance.faceActiveKey,
|
||||
'syncTime': instance.syncTime?.toIso8601String(),
|
||||
'canteenClassifySyncTime':
|
||||
instance.canteenClassifySyncTime?.toIso8601String(),
|
||||
'canteenProductSyncTime':
|
||||
instance.canteenProductSyncTime?.toIso8601String(),
|
||||
'supermarketClassifySyncTime':
|
||||
instance.supermarketClassifySyncTime?.toIso8601String(),
|
||||
'supermarketProductSyncTime':
|
||||
instance.supermarketProductSyncTime?.toIso8601String(),
|
||||
'offlineOrderSyncTime': instance.offlineOrderSyncTime?.toIso8601String(),
|
||||
'mqSyncTime': instance.mqSyncTime?.toIso8601String(),
|
||||
};
|
||||
|
||||
const _$SysTypeEnumMap = {
|
||||
SysType.supermarket: 'supermarket',
|
||||
SysType.canteen: 'canteen',
|
||||
};
|
||||
|
||||
UserSetting _$UserSettingFromJson(Map<String, dynamic> json) => UserSetting(
|
||||
username: json['username'] as String?,
|
||||
password: json['password'] as String?,
|
||||
)
|
||||
..sysType = $enumDecodeNullable(_$SysTypeEnumMap, json['sysType'])
|
||||
..userId = (json['userId'] as num?)?.toInt()
|
||||
..accessToken = json['accessToken'] as String?
|
||||
..refreshToken = json['refreshToken'] as String?
|
||||
..expiresTime = (json['expiresTime'] as num?)?.toInt()
|
||||
..hourType =
|
||||
$enumDecodeNullable(_$ProductHourTypeEnumMap, json['hourType']);
|
||||
|
||||
Map<String, dynamic> _$UserSettingToJson(UserSetting instance) =>
|
||||
<String, dynamic>{
|
||||
'username': instance.username,
|
||||
'password': instance.password,
|
||||
'sysType': _$SysTypeEnumMap[instance.sysType],
|
||||
'userId': instance.userId,
|
||||
'accessToken': instance.accessToken,
|
||||
'refreshToken': instance.refreshToken,
|
||||
'expiresTime': instance.expiresTime,
|
||||
'hourType': _$ProductHourTypeEnumMap[instance.hourType],
|
||||
};
|
||||
|
||||
const _$ProductHourTypeEnumMap = {
|
||||
ProductHourType.all: 'all',
|
||||
ProductHourType.morning: 'morning',
|
||||
ProductHourType.noon: 'noon',
|
||||
ProductHourType.night: 'night',
|
||||
};
|
||||
|
||||
CanteenExtraPriceSetting _$CanteenExtraPriceSettingFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
CanteenExtraPriceSetting(
|
||||
payAmountEnable: json['payAmountEnable'] as bool,
|
||||
payAmountMealLimit: (json['payAmountMealLimit'] as num).toInt(),
|
||||
payAmountMealTimes: (json['payAmountMealTimes'] as num).toDouble(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$CanteenExtraPriceSettingToJson(
|
||||
CanteenExtraPriceSetting instance) =>
|
||||
<String, dynamic>{
|
||||
'payAmountEnable': instance.payAmountEnable,
|
||||
'payAmountMealLimit': instance.payAmountMealLimit,
|
||||
'payAmountMealTimes': instance.payAmountMealTimes,
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
import 'package:common_utils/common_utils.dart';
|
||||
|
||||
import 'package:pj_supermarket_cloud/common/index.dart';
|
||||
|
||||
class EncryptionTool {
|
||||
// 获取base64加密后的 oauth2 APP模式客户端信息
|
||||
static String getLoginAuthorization() {
|
||||
return "Basic ${EncryptUtil.encodeBase64(Constants.oauth2AppClient)}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
library;
|
||||
|
||||
export './api/base_api_models.dart';
|
||||
export 'api/product_api_models.dart';
|
||||
export './api/hard_api_models.dart';
|
||||
export './api/user_api_models.dart';
|
||||
export './api/wallet_api_models.dart';
|
||||
|
||||
export './base/base_models.dart';
|
||||
|
||||
export './page/order_page_enum_models.dart';
|
||||
export './page/order_page_models.dart';
|
||||
export './page/panel_page_models.dart';
|
||||
export './page/setting_page_models.dart';
|
||||
|
||||
export './mq/mq_enum_models.dart';
|
||||
export './mq/mq_models.dart';
|
||||
|
||||
export './manage/usb_facility_enum_manage.dart';
|
||||
export './manage/usb_facility_manage.dart';
|
||||
|
||||
export './encryption/encryption_tool.dart';
|
||||
|
||||
export './database/database_enum_models.dart';
|
||||
export './database/database_models.dart';
|
||||
|
||||
export './plugs/face_discern_models.dart';
|
||||
export './plugs/serial_card_enum_models.dart';
|
||||
export './plugs/serial_card_models.dart';
|
||||
export './plugs/sub_screen_enum_models.dart';
|
||||
export './plugs/sub_screen_models.dart';
|
||||