769 lines
25 KiB
Dart
769 lines
25 KiB
Dart
import 'package:json_annotation/json_annotation.dart';
|
|
|
|
part 'face_discern_models.g.dart';
|
|
|
|
@JsonSerializable()
|
|
class FaceStream {
|
|
/// 本次比对相似度,范围为 0 到 1。
|
|
double similar;
|
|
|
|
/// 虹软完成本次比对耗时,单位为毫秒。
|
|
int cost;
|
|
|
|
/// 本地虹软人脸库记录编号,旧协议可能为空。
|
|
int? faceId;
|
|
|
|
/// 服务端同步到终端的人脸样本编号。
|
|
int? sampleId;
|
|
|
|
/// 识别成功人员的服务端用户编号。
|
|
String? userId;
|
|
|
|
/// 识别成功人员的展示姓名。
|
|
String? userName;
|
|
|
|
/// 原生会话编号,用于把一次成功识别绑定到唯一订单;旧原生插件为空。
|
|
String? sessionId;
|
|
|
|
/// 实际命中比对的 NV21 帧采集时间,单位为 Unix 毫秒。
|
|
int? capturedAt;
|
|
|
|
/// 原生裁剪后的现场 JPEG 私有路径;该路径只在终端本地队列使用。
|
|
String? evidenceFilePath;
|
|
|
|
/// 现场 JPEG 的 SHA-256 摘要,文件缺失或裁剪失败时为空。
|
|
String? evidenceHash;
|
|
|
|
/// 现场 JPEG 大小,单位为字节。
|
|
int? evidenceSize;
|
|
|
|
/// 现场 JPEG 宽度,单位为像素。
|
|
int? evidenceWidth;
|
|
|
|
/// 现场 JPEG 高度,单位为像素。
|
|
int? evidenceHeight;
|
|
|
|
/// 本次人脸比对采用的阈值快照,范围为 0 到 1。
|
|
double? similarityThreshold;
|
|
|
|
/// 现场证据生成失败的安全摘要,不得包含完整路径、原始帧或图片内容。
|
|
String? evidenceError;
|
|
|
|
FaceStream({
|
|
required this.similar,
|
|
required this.cost,
|
|
this.faceId,
|
|
this.sampleId,
|
|
this.userId,
|
|
this.userName,
|
|
this.sessionId,
|
|
this.capturedAt,
|
|
this.evidenceFilePath,
|
|
this.evidenceHash,
|
|
this.evidenceSize,
|
|
this.evidenceWidth,
|
|
this.evidenceHeight,
|
|
this.similarityThreshold,
|
|
this.evidenceError,
|
|
});
|
|
|
|
factory FaceStream.fromJson(Map<String, dynamic> json) =>
|
|
_$FaceStreamFromJson(json);
|
|
|
|
Map<String, dynamic> toJson() => _$FaceStreamToJson(this);
|
|
}
|
|
|
|
@JsonSerializable()
|
|
class FaceConfig {
|
|
String? appId;
|
|
|
|
String? sdkKey;
|
|
|
|
String? activeKey;
|
|
|
|
// 是否激活
|
|
bool isActive = false;
|
|
|
|
FaceConfig({
|
|
this.appId,
|
|
this.sdkKey,
|
|
this.activeKey,
|
|
this.isActive = false,
|
|
});
|
|
|
|
factory FaceConfig.fromJson(Map<String, dynamic> json) =>
|
|
_$FaceConfigFromJson(json);
|
|
|
|
Map<String, dynamic> toJson() => _$FaceConfigToJson(this);
|
|
}
|
|
|
|
/// 终端本机的人脸识别策略,对应 Android `RecognizeConfiguration` 的可调白名单。
|
|
class FaceRecognitionConfiguration {
|
|
const FaceRecognitionConfiguration({
|
|
required this.enableLiveness,
|
|
required this.similarThreshold,
|
|
required this.rgbLivenessThreshold,
|
|
required this.enableImageQuality,
|
|
required this.imageQualityNoMaskRecognizeThreshold,
|
|
required this.imageQualityMaskRecognizeThreshold,
|
|
required this.libraryFaceQualityThreshold,
|
|
required this.keepMaxFace,
|
|
required this.maxDetectFaces,
|
|
required this.extractRetryCount,
|
|
required this.livenessRetryCount,
|
|
required this.recognizeFailedRetryInterval,
|
|
required this.livenessFailedRetryInterval,
|
|
required this.livenessType,
|
|
});
|
|
|
|
const FaceRecognitionConfiguration.defaults()
|
|
: enableLiveness = false,
|
|
similarThreshold = 0.8,
|
|
rgbLivenessThreshold = 0.5,
|
|
enableImageQuality = true,
|
|
imageQualityNoMaskRecognizeThreshold = 0.49,
|
|
imageQualityMaskRecognizeThreshold = 0.29,
|
|
libraryFaceQualityThreshold = 0.60,
|
|
keepMaxFace = true,
|
|
maxDetectFaces = 1,
|
|
extractRetryCount = 3,
|
|
livenessRetryCount = 3,
|
|
recognizeFailedRetryInterval = 0,
|
|
livenessFailedRetryInterval = 0,
|
|
livenessType = 'RGB';
|
|
|
|
final bool enableLiveness;
|
|
final double similarThreshold;
|
|
final double rgbLivenessThreshold;
|
|
final bool enableImageQuality;
|
|
final double imageQualityNoMaskRecognizeThreshold;
|
|
final double imageQualityMaskRecognizeThreshold;
|
|
|
|
/// 在库照片诊断阈值;只决定低质量列表分类,不阻止人脸同步或参与识别。
|
|
final double libraryFaceQualityThreshold;
|
|
final bool keepMaxFace;
|
|
final int maxDetectFaces;
|
|
final int extractRetryCount;
|
|
final int livenessRetryCount;
|
|
final int recognizeFailedRetryInterval;
|
|
final int livenessFailedRetryInterval;
|
|
final String livenessType;
|
|
|
|
factory FaceRecognitionConfiguration.fromMap(Map<dynamic, dynamic> map) {
|
|
const defaults = FaceRecognitionConfiguration.defaults();
|
|
return FaceRecognitionConfiguration(
|
|
enableLiveness: _asBoolOr(map['enableLiveness'], defaults.enableLiveness),
|
|
similarThreshold:
|
|
_asDouble(map['similarThreshold'], defaults.similarThreshold),
|
|
rgbLivenessThreshold: _asDouble(
|
|
map['rgbLivenessThreshold'],
|
|
defaults.rgbLivenessThreshold,
|
|
),
|
|
enableImageQuality:
|
|
_asBoolOr(map['enableImageQuality'], defaults.enableImageQuality),
|
|
imageQualityNoMaskRecognizeThreshold: _asDouble(
|
|
map['imageQualityNoMaskRecognizeThreshold'],
|
|
defaults.imageQualityNoMaskRecognizeThreshold,
|
|
),
|
|
imageQualityMaskRecognizeThreshold: _asDouble(
|
|
map['imageQualityMaskRecognizeThreshold'],
|
|
defaults.imageQualityMaskRecognizeThreshold,
|
|
),
|
|
libraryFaceQualityThreshold: _asDouble(
|
|
map['libraryFaceQualityThreshold'],
|
|
defaults.libraryFaceQualityThreshold,
|
|
).clamp(0.0, 1.0).toDouble(),
|
|
keepMaxFace: _asBoolOr(map['keepMaxFace'], defaults.keepMaxFace),
|
|
maxDetectFaces: _asIntOr(map['maxDetectFaces'], defaults.maxDetectFaces),
|
|
extractRetryCount:
|
|
_asIntOr(map['extractRetryCount'], defaults.extractRetryCount),
|
|
livenessRetryCount:
|
|
_asIntOr(map['livenessRetryCount'], defaults.livenessRetryCount),
|
|
recognizeFailedRetryInterval: _asIntOr(
|
|
map['recognizeFailedRetryInterval'],
|
|
defaults.recognizeFailedRetryInterval,
|
|
),
|
|
livenessFailedRetryInterval: _asIntOr(
|
|
map['livenessFailedRetryInterval'],
|
|
defaults.livenessFailedRetryInterval,
|
|
),
|
|
livenessType: map['livenessType']?.toString() ?? defaults.livenessType,
|
|
);
|
|
}
|
|
|
|
Map<String, dynamic> toMap() => {
|
|
'enableLiveness': enableLiveness,
|
|
'similarThreshold': similarThreshold,
|
|
'rgbLivenessThreshold': rgbLivenessThreshold,
|
|
'enableImageQuality': enableImageQuality,
|
|
'imageQualityNoMaskRecognizeThreshold':
|
|
imageQualityNoMaskRecognizeThreshold,
|
|
'imageQualityMaskRecognizeThreshold':
|
|
imageQualityMaskRecognizeThreshold,
|
|
'libraryFaceQualityThreshold':
|
|
libraryFaceQualityThreshold.clamp(0.0, 1.0).toDouble(),
|
|
'keepMaxFace': keepMaxFace,
|
|
'maxDetectFaces': maxDetectFaces,
|
|
'extractRetryCount': extractRetryCount,
|
|
'livenessRetryCount': livenessRetryCount,
|
|
'recognizeFailedRetryInterval': recognizeFailedRetryInterval,
|
|
'livenessFailedRetryInterval': livenessFailedRetryInterval,
|
|
'livenessType': livenessType,
|
|
};
|
|
|
|
bool get usesDefaults =>
|
|
this == const FaceRecognitionConfiguration.defaults();
|
|
|
|
FaceRecognitionConfiguration copyWith({
|
|
bool? enableLiveness,
|
|
double? similarThreshold,
|
|
double? rgbLivenessThreshold,
|
|
bool? enableImageQuality,
|
|
double? imageQualityNoMaskRecognizeThreshold,
|
|
double? imageQualityMaskRecognizeThreshold,
|
|
double? libraryFaceQualityThreshold,
|
|
bool? keepMaxFace,
|
|
int? maxDetectFaces,
|
|
int? extractRetryCount,
|
|
int? livenessRetryCount,
|
|
int? recognizeFailedRetryInterval,
|
|
int? livenessFailedRetryInterval,
|
|
}) {
|
|
return FaceRecognitionConfiguration(
|
|
enableLiveness: enableLiveness ?? this.enableLiveness,
|
|
similarThreshold: similarThreshold ?? this.similarThreshold,
|
|
rgbLivenessThreshold: rgbLivenessThreshold ?? this.rgbLivenessThreshold,
|
|
enableImageQuality: enableImageQuality ?? this.enableImageQuality,
|
|
imageQualityNoMaskRecognizeThreshold:
|
|
imageQualityNoMaskRecognizeThreshold ??
|
|
this.imageQualityNoMaskRecognizeThreshold,
|
|
imageQualityMaskRecognizeThreshold: imageQualityMaskRecognizeThreshold ??
|
|
this.imageQualityMaskRecognizeThreshold,
|
|
libraryFaceQualityThreshold:
|
|
(libraryFaceQualityThreshold ?? this.libraryFaceQualityThreshold)
|
|
.clamp(0.0, 1.0)
|
|
.toDouble(),
|
|
keepMaxFace: keepMaxFace ?? this.keepMaxFace,
|
|
maxDetectFaces: maxDetectFaces ?? this.maxDetectFaces,
|
|
extractRetryCount: extractRetryCount ?? this.extractRetryCount,
|
|
livenessRetryCount: livenessRetryCount ?? this.livenessRetryCount,
|
|
recognizeFailedRetryInterval:
|
|
recognizeFailedRetryInterval ?? this.recognizeFailedRetryInterval,
|
|
livenessFailedRetryInterval:
|
|
livenessFailedRetryInterval ?? this.livenessFailedRetryInterval,
|
|
livenessType: livenessType,
|
|
);
|
|
}
|
|
|
|
@override
|
|
bool operator ==(Object other) =>
|
|
identical(this, other) ||
|
|
other is FaceRecognitionConfiguration &&
|
|
enableLiveness == other.enableLiveness &&
|
|
similarThreshold == other.similarThreshold &&
|
|
rgbLivenessThreshold == other.rgbLivenessThreshold &&
|
|
enableImageQuality == other.enableImageQuality &&
|
|
imageQualityNoMaskRecognizeThreshold ==
|
|
other.imageQualityNoMaskRecognizeThreshold &&
|
|
imageQualityMaskRecognizeThreshold ==
|
|
other.imageQualityMaskRecognizeThreshold &&
|
|
libraryFaceQualityThreshold == other.libraryFaceQualityThreshold &&
|
|
keepMaxFace == other.keepMaxFace &&
|
|
maxDetectFaces == other.maxDetectFaces &&
|
|
extractRetryCount == other.extractRetryCount &&
|
|
livenessRetryCount == other.livenessRetryCount &&
|
|
recognizeFailedRetryInterval == other.recognizeFailedRetryInterval &&
|
|
livenessFailedRetryInterval == other.livenessFailedRetryInterval &&
|
|
livenessType == other.livenessType;
|
|
|
|
@override
|
|
int get hashCode => Object.hash(
|
|
enableLiveness,
|
|
similarThreshold,
|
|
rgbLivenessThreshold,
|
|
enableImageQuality,
|
|
imageQualityNoMaskRecognizeThreshold,
|
|
imageQualityMaskRecognizeThreshold,
|
|
libraryFaceQualityThreshold,
|
|
keepMaxFace,
|
|
maxDetectFaces,
|
|
extractRetryCount,
|
|
livenessRetryCount,
|
|
recognizeFailedRetryInterval,
|
|
livenessFailedRetryInterval,
|
|
livenessType,
|
|
);
|
|
}
|
|
|
|
@JsonSerializable()
|
|
class RegisterAllStorageFaceRes {
|
|
// 注册成功数量
|
|
int successCount;
|
|
|
|
// 注册失败数量
|
|
int failedCount;
|
|
|
|
// 注册数量
|
|
int registerCount;
|
|
|
|
// 本地文件数量
|
|
int fileCount;
|
|
|
|
RegisterAllStorageFaceRes({
|
|
required this.successCount,
|
|
required this.failedCount,
|
|
required this.registerCount,
|
|
required this.fileCount,
|
|
});
|
|
|
|
factory RegisterAllStorageFaceRes.fromJson(Map<String, dynamic> json) =>
|
|
_$RegisterAllStorageFaceResFromJson(json);
|
|
|
|
Map<String, dynamic> toJson() => _$RegisterAllStorageFaceResToJson(this);
|
|
}
|
|
|
|
@JsonSerializable()
|
|
class FaceRes {
|
|
int searchId;
|
|
|
|
List<int> featureData;
|
|
|
|
String faceTag;
|
|
|
|
FaceRes({
|
|
required this.searchId,
|
|
required this.featureData,
|
|
required this.faceTag,
|
|
});
|
|
|
|
factory FaceRes.fromJson(Map<String, dynamic> json) =>
|
|
_$FaceResFromJson(json);
|
|
|
|
Map<String, dynamic> toJson() => _$FaceResToJson(this);
|
|
}
|
|
|
|
/// 原生图片特征提取结果;提取阶段不修改 SQLite 或虹软内存库。
|
|
class FaceFeatureExtractionResult {
|
|
/// 创建一次原生特征提取结果。
|
|
const FaceFeatureExtractionResult({
|
|
required this.success,
|
|
required this.message,
|
|
required this.featureData,
|
|
this.qualityStatus = FaceQualityStatus.pending,
|
|
this.qualityScore,
|
|
this.qualityErrorCode,
|
|
});
|
|
|
|
/// 从 MethodChannel 返回映射创建特征提取结果。
|
|
factory FaceFeatureExtractionResult.fromMap(Map<dynamic, dynamic> map) {
|
|
final rawFeature = map['featureData'];
|
|
return FaceFeatureExtractionResult(
|
|
success: map['success'] == true,
|
|
message: map['message']?.toString() ?? '人脸特征提取失败',
|
|
featureData: rawFeature is List
|
|
? rawFeature.map((item) => (item as num).toInt()).toList()
|
|
: const [],
|
|
qualityStatus: FaceQualityStatus.fromCode(
|
|
map['qualityStatus']?.toString(),
|
|
),
|
|
qualityScore: (map['qualityScore'] as num?)?.toDouble(),
|
|
qualityErrorCode: (map['qualityErrorCode'] as num?)?.toInt(),
|
|
);
|
|
}
|
|
|
|
/// 是否成功提取且返回非空特征。
|
|
final bool success;
|
|
|
|
/// 不包含图片路径和特征内容的安全结果摘要。
|
|
final String message;
|
|
|
|
/// 虹软注册特征字节;仅在进程内传递给增量应用通道。
|
|
final List<int> featureData;
|
|
|
|
/// 静态图片质量检测状态;失败不改变 [success] 的特征提取语义。
|
|
final FaceQualityStatus qualityStatus;
|
|
|
|
/// 虹软图片质量分,范围为 0 到 1;未检测或检测失败时为空。
|
|
final double? qualityScore;
|
|
|
|
/// 虹软质量检测错误码;插件内部错误使用负值,成功时为空。
|
|
final int? qualityErrorCode;
|
|
}
|
|
|
|
/// 在库人脸质量状态的跨端持久化协议。
|
|
enum FaceQualityStatus {
|
|
/// 历史数据尚未执行质量检测。
|
|
pending('pending'),
|
|
|
|
/// 虹软已返回有效的 0 到 1 质量分。
|
|
detected('detected'),
|
|
|
|
/// 图片、授权或 SDK 异常导致本次未取得有效质量分。
|
|
failed('failed');
|
|
|
|
/// 创建一个数据库和 MethodChannel 共用的稳定状态值。
|
|
const FaceQualityStatus(this.code);
|
|
|
|
/// 写入 SQLite 和原生通道的稳定编码,不依赖 Dart 枚举名称。
|
|
final String code;
|
|
|
|
/// 将未知或空状态兼容为待检测,避免旧版本数据被误判为低质量。
|
|
static FaceQualityStatus fromCode(String? code) {
|
|
return FaceQualityStatus.values.firstWhere(
|
|
(value) => value.code == code,
|
|
orElse: () => FaceQualityStatus.pending,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// 提交给 Android 原生批量检测的一条本地图片引用。
|
|
class FaceQualityAuditItem {
|
|
/// 创建一条仅在进程内传递的样本与本地文件映射。
|
|
const FaceQualityAuditItem({
|
|
required this.sampleId,
|
|
required this.filePath,
|
|
});
|
|
|
|
/// 服务端同步到终端的虹软样本编号。
|
|
final String sampleId;
|
|
|
|
/// 终端私有存储中的图片绝对路径;只传给本机原生层,不写日志或上报。
|
|
final String filePath;
|
|
|
|
/// 转换为 StandardMessageCodec 支持的原生参数。
|
|
Map<String, dynamic> toMap() => {
|
|
'sampleId': sampleId,
|
|
'filePath': filePath,
|
|
};
|
|
}
|
|
|
|
/// 原生批量质量检测中的单条结果。
|
|
class FaceImageQualityResult {
|
|
/// 创建一条只包含样本编号和诊断信息的质量结果。
|
|
const FaceImageQualityResult({
|
|
required this.sampleId,
|
|
required this.status,
|
|
required this.message,
|
|
this.score,
|
|
this.errorCode,
|
|
});
|
|
|
|
/// 从 Android StandardMessageCodec 映射解析单条结果。
|
|
factory FaceImageQualityResult.fromMap(Map<dynamic, dynamic> map) =>
|
|
FaceImageQualityResult(
|
|
sampleId: map['sampleId']?.toString() ?? '',
|
|
status: FaceQualityStatus.fromCode(map['status']?.toString()),
|
|
message: map['message']?.toString() ?? '图片质量检测失败',
|
|
score: (map['score'] as num?)?.toDouble(),
|
|
errorCode: (map['errorCode'] as num?)?.toInt(),
|
|
);
|
|
|
|
/// 服务端同步到终端的虹软样本编号。
|
|
final String sampleId;
|
|
|
|
/// 本次图片质量检测状态。
|
|
final FaceQualityStatus status;
|
|
|
|
/// 不包含文件路径和人员信息的安全结果摘要。
|
|
final String message;
|
|
|
|
/// 虹软质量分,检测成功时范围为 0 到 1。
|
|
final double? score;
|
|
|
|
/// 虹软或插件内部错误码,检测成功时为空。
|
|
final int? errorCode;
|
|
}
|
|
|
|
/// 原生批量质量检测的实时进度。
|
|
class FaceQualityAuditProgress {
|
|
/// 创建一份可供设置页展示的批量检测进度快照。
|
|
const FaceQualityAuditProgress({
|
|
required this.processed,
|
|
required this.total,
|
|
required this.successCount,
|
|
required this.failedCount,
|
|
required this.inProgress,
|
|
required this.message,
|
|
this.currentSampleId,
|
|
});
|
|
|
|
/// 从质量检测事件通道解析进度快照。
|
|
factory FaceQualityAuditProgress.fromMap(Map<dynamic, dynamic> map) =>
|
|
FaceQualityAuditProgress(
|
|
processed: (map['processed'] as num?)?.toInt() ?? 0,
|
|
total: (map['total'] as num?)?.toInt() ?? 0,
|
|
successCount: (map['successCount'] as num?)?.toInt() ?? 0,
|
|
failedCount: (map['failedCount'] as num?)?.toInt() ?? 0,
|
|
inProgress: map['inProgress'] == true,
|
|
message: map['message']?.toString() ?? '等待检测',
|
|
currentSampleId: map['currentSampleId']?.toString(),
|
|
);
|
|
|
|
/// 已完成检测的样本数量。
|
|
final int processed;
|
|
|
|
/// 本轮提交给原生层的样本总数。
|
|
final int total;
|
|
|
|
/// 已取得有效质量分的样本数量。
|
|
final int successCount;
|
|
|
|
/// 未取得有效质量分的样本数量。
|
|
final int failedCount;
|
|
|
|
/// 原生批量任务是否仍持有人脸操作互斥锁。
|
|
final bool inProgress;
|
|
|
|
/// 当前阶段的安全展示文案。
|
|
final String message;
|
|
|
|
/// 当前处理样本编号;空值表示尚未开始或已经结束。
|
|
final String? currentSampleId;
|
|
|
|
/// 供进度条使用的 0 到 1 比例,空任务视为 0。
|
|
double get fraction =>
|
|
total <= 0 ? 0.0 : (processed / total).clamp(0.0, 1.0).toDouble();
|
|
}
|
|
|
|
/// 原生批量质量检测完成结果。
|
|
class FaceQualityAuditResult {
|
|
/// 创建一轮不可变的质量检测结果集合。
|
|
const FaceQualityAuditResult({
|
|
required this.results,
|
|
required this.message,
|
|
});
|
|
|
|
/// 从 MethodChannel 返回映射解析全部单条结果。
|
|
factory FaceQualityAuditResult.fromMap(Map<dynamic, dynamic> map) {
|
|
final rawResults = map['results'];
|
|
return FaceQualityAuditResult(
|
|
results: rawResults is List
|
|
? rawResults
|
|
.whereType<Map>()
|
|
.map(FaceImageQualityResult.fromMap)
|
|
.toList(growable: false)
|
|
: const [],
|
|
message: map['message']?.toString() ?? '在库人脸质量检测完成',
|
|
);
|
|
}
|
|
|
|
/// 与请求样本一一对应的检测结果。
|
|
final List<FaceImageQualityResult> results;
|
|
|
|
/// 本轮检测完成后的安全摘要。
|
|
final String message;
|
|
}
|
|
|
|
/// 原生单样本人脸增删结果。
|
|
class FaceDeltaApplyResult {
|
|
/// 创建一次原生增量应用结果。
|
|
const FaceDeltaApplyResult({
|
|
required this.success,
|
|
required this.message,
|
|
required this.code,
|
|
});
|
|
|
|
/// 从 MethodChannel 返回映射创建增量结果。
|
|
factory FaceDeltaApplyResult.fromMap(Map<dynamic, dynamic> map) =>
|
|
FaceDeltaApplyResult(
|
|
success: map['success'] == true,
|
|
message: map['message']?.toString() ?? '人脸库增量更新失败',
|
|
code: (map['code'] as num?)?.toInt() ?? -99,
|
|
);
|
|
|
|
/// 虹软内存库是否已完成目标状态。
|
|
final bool success;
|
|
|
|
/// 不包含用户姓名、图片地址或特征的安全结果摘要。
|
|
final String message;
|
|
|
|
/// 虹软 SDK 返回码;插件异常使用内部负值。
|
|
final int code;
|
|
}
|
|
|
|
/// 原生人脸插件、授权文件和内存人脸库的实时状态。
|
|
class FacePluginStatus {
|
|
const FacePluginStatus({
|
|
required this.activated,
|
|
required this.activationCode,
|
|
required this.parametersComplete,
|
|
required this.engineReady,
|
|
required this.engineCode,
|
|
required this.databaseCount,
|
|
required this.memoryCount,
|
|
required this.registrationInProgress,
|
|
required this.libraryConsistent,
|
|
this.activationStartTime,
|
|
this.activationEndTime,
|
|
this.registrationProgress,
|
|
this.lastRegistrationResult,
|
|
});
|
|
|
|
final bool activated;
|
|
final int activationCode;
|
|
final bool parametersComplete;
|
|
final bool engineReady;
|
|
final int engineCode;
|
|
final int databaseCount;
|
|
final int memoryCount;
|
|
final bool registrationInProgress;
|
|
final bool libraryConsistent;
|
|
final String? activationStartTime;
|
|
final String? activationEndTime;
|
|
final FaceRegistrationProgress? registrationProgress;
|
|
final FaceRegistrationResult? lastRegistrationResult;
|
|
|
|
bool get ready =>
|
|
activated && engineReady && !registrationInProgress && libraryConsistent;
|
|
|
|
factory FacePluginStatus.fromMap(Map<dynamic, dynamic> map) {
|
|
return FacePluginStatus(
|
|
activated: _asBool(map['activated']),
|
|
activationCode: _asInt(map['activationCode']),
|
|
parametersComplete: _asBool(map['parametersComplete']),
|
|
engineReady: _asBool(map['engineReady']),
|
|
engineCode: _asInt(map['engineCode']),
|
|
databaseCount: _asInt(map['databaseCount']),
|
|
memoryCount: _asInt(map['memoryCount']),
|
|
registrationInProgress: _asBool(map['registrationInProgress']),
|
|
libraryConsistent: _asBool(map['libraryConsistent']),
|
|
activationStartTime: _asNullableString(map['activationStartTime']),
|
|
activationEndTime: _asNullableString(map['activationEndTime']),
|
|
registrationProgress: map['registrationProgress'] is Map
|
|
? FaceRegistrationProgress.fromMap(
|
|
map['registrationProgress'] as Map,
|
|
)
|
|
: null,
|
|
lastRegistrationResult: map['lastRegistrationResult'] is Map
|
|
? FaceRegistrationResult.fromMap(
|
|
map['lastRegistrationResult'] as Map,
|
|
)
|
|
: null,
|
|
);
|
|
}
|
|
}
|
|
|
|
class FaceActivationResult {
|
|
const FaceActivationResult({
|
|
required this.success,
|
|
required this.code,
|
|
required this.message,
|
|
required this.requiresPermission,
|
|
required this.canOpenSettings,
|
|
});
|
|
|
|
final bool success;
|
|
final int code;
|
|
final String message;
|
|
final bool requiresPermission;
|
|
final bool canOpenSettings;
|
|
|
|
factory FaceActivationResult.fromMap(Map<dynamic, dynamic> map) {
|
|
return FaceActivationResult(
|
|
success: _asBool(map['success']),
|
|
code: _asInt(map['code']),
|
|
message: _asString(map['message'], '人脸插件激活失败'),
|
|
requiresPermission: _asBool(map['requiresPermission']),
|
|
canOpenSettings: _asBool(map['canOpenSettings']),
|
|
);
|
|
}
|
|
}
|
|
|
|
class FaceRegistrationProgress {
|
|
const FaceRegistrationProgress({
|
|
required this.phase,
|
|
required this.message,
|
|
required this.processed,
|
|
required this.total,
|
|
required this.successCount,
|
|
required this.failedCount,
|
|
required this.inProgress,
|
|
this.currentSampleId,
|
|
});
|
|
|
|
final String phase;
|
|
final String message;
|
|
final int processed;
|
|
final int total;
|
|
final int successCount;
|
|
final int failedCount;
|
|
final bool inProgress;
|
|
final int? currentSampleId;
|
|
|
|
double get fraction =>
|
|
total <= 0 ? 0.0 : (processed / total).clamp(0.0, 1.0).toDouble();
|
|
|
|
factory FaceRegistrationProgress.fromMap(Map<dynamic, dynamic> map) {
|
|
return FaceRegistrationProgress(
|
|
phase: _asString(map['phase'], 'idle'),
|
|
message: _asString(map['message'], '尚未执行人脸库注册'),
|
|
processed: _asInt(map['processed']),
|
|
total: _asInt(map['total']),
|
|
successCount: _asInt(map['successCount']),
|
|
failedCount: _asInt(map['failedCount']),
|
|
inProgress: _asBool(map['inProgress']),
|
|
currentSampleId: map['currentSampleId'] == null
|
|
? null
|
|
: _asInt(map['currentSampleId']),
|
|
);
|
|
}
|
|
}
|
|
|
|
class FaceRegistrationResult {
|
|
const FaceRegistrationResult({
|
|
required this.success,
|
|
required this.message,
|
|
required this.total,
|
|
required this.successCount,
|
|
required this.failedCount,
|
|
required this.failedSampleIds,
|
|
required this.durationMs,
|
|
});
|
|
|
|
final bool success;
|
|
final String message;
|
|
final int total;
|
|
final int successCount;
|
|
final int failedCount;
|
|
final List<int> failedSampleIds;
|
|
final int durationMs;
|
|
|
|
factory FaceRegistrationResult.fromMap(Map<dynamic, dynamic> map) {
|
|
final failedSampleIds = map['failedSampleIds'];
|
|
return FaceRegistrationResult(
|
|
success: _asBool(map['success']),
|
|
message: _asString(map['message'], '人脸库注册失败'),
|
|
total: _asInt(map['total']),
|
|
successCount: _asInt(map['successCount']),
|
|
failedCount: _asInt(map['failedCount']),
|
|
failedSampleIds: failedSampleIds is List
|
|
? failedSampleIds.map(_asInt).toList(growable: false)
|
|
: const [],
|
|
durationMs: _asInt(map['durationMs']),
|
|
);
|
|
}
|
|
}
|
|
|
|
bool _asBool(dynamic value) => value == true;
|
|
|
|
int _asInt(dynamic value) => value is num ? value.toInt() : 0;
|
|
|
|
int _asIntOr(dynamic value, int fallback) =>
|
|
value is num ? value.toInt() : fallback;
|
|
|
|
double _asDouble(dynamic value, double fallback) =>
|
|
value is num ? value.toDouble() : fallback;
|
|
|
|
bool _asBoolOr(dynamic value, bool fallback) =>
|
|
value is bool ? value : fallback;
|
|
|
|
String _asString(dynamic value, String fallback) {
|
|
final result = value?.toString();
|
|
return result == null || result.isEmpty ? fallback : result;
|
|
}
|
|
|
|
String? _asNullableString(dynamic value) {
|
|
final result = value?.toString();
|
|
return result == null || result.isEmpty ? null : result;
|
|
}
|