This commit is contained in:
dzc committed 2025-03-09 18:53:36 +08:00
commit 295b4058be
243 files changed
+19832

No files matched your search

+45
View File
@@ -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
+36
View File
@@ -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'
+16
View File
@@ -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.
+28
View File
@@ -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
+13
View File
@@ -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
+80
View File
@@ -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'
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
+27
View File
@@ -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>
+59
View File
@@ -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);
}
}
}
Binary file not shown.
Binary file not shown.
@@ -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&lt;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>
Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

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>
+18
View File
@@ -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
}
+3
View File
@@ -0,0 +1,3 @@
org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
android.enableJetifier=true
+6
View File
@@ -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
+28
View File
@@ -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"
+22
View File
@@ -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-----
Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

+5
View File
@@ -0,0 +1,5 @@
icons_launcher:
image_path: "assets/images/logo.png"
platforms:
android:
enable: true
+59
View File
@@ -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();
}
}
+141
View File
@@ -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;
}
}
+6
View File
@@ -0,0 +1,6 @@
library;
export 'product_api.dart';
export './hard_api.dart';
export './user_api.dart';
export './wallet_api.dart';
+57
View File
@@ -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();
}
}
+63
View File
@@ -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;
}
}
+42
View File
@@ -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;
}
}
+3
View File
@@ -0,0 +1,3 @@
library;
// export './xxxx.dart';
+3
View File
@@ -0,0 +1,3 @@
library;
// export './xxxx.dart';
+3
View File
@@ -0,0 +1,3 @@
library;
// export './xxxx.dart';
+13
View File
@@ -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,
};
+203
View File
@@ -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,
};
+128
View File
@@ -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),
};
+14
View File
@@ -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)}";
}
}
+31
View File
@@ -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';
Loaded 100 of 243 files, more files were not shown because too many files have changed in this diff. Show more