211 lines
6.6 KiB
Dart
211 lines
6.6 KiB
Dart
import 'package:ducafe_ui_core/ducafe_ui_core.dart';
|
|
import 'package:flutter/material.dart';
|
|
|
|
import 'package:pj_supermarket_cloud/common/models/plugs/face_recognition_session_models.dart';
|
|
import 'package:pj_supermarket_cloud/common/style/xfj_design_tokens.dart';
|
|
import 'package:pj_supermarket_cloud/common/widgets/face/face_recognition_preview_layer.dart';
|
|
|
|
/// 人脸摄像头画面组件,只负责 Texture、人脸框、摄像头状态和空画面提示。
|
|
class FaceCameraPreview extends StatelessWidget {
|
|
/// 创建一个不持有识别会话和插件资源的摄像头展示区域。
|
|
const FaceCameraPreview({
|
|
super.key,
|
|
required this.texture,
|
|
required this.event,
|
|
required this.status,
|
|
this.statusText,
|
|
this.statusColor,
|
|
this.scale = 1,
|
|
});
|
|
|
|
/// 原生创建的预览纹理;为空时展示摄像头准备状态。
|
|
final FaceRecognitionPreviewTexture? texture;
|
|
|
|
/// 最近一次原生识别事件,用于绘制人脸框和方向信息。
|
|
final FaceRecognitionSessionEvent? event;
|
|
|
|
/// 当前识别状态,用于展示文字和非颜色状态标识。
|
|
final FaceRecognitionSessionStatus status;
|
|
|
|
/// 业务页面覆盖的摄像头状态文案;为空时沿用原生会话状态文案。
|
|
final String? statusText;
|
|
|
|
/// 业务页面覆盖的摄像头状态颜色;为空时沿用原生会话状态颜色。
|
|
final Color? statusColor;
|
|
|
|
/// 宿主 Engine 提供的尺寸缩放系数,副屏不得依赖主屏 ScreenUtil 全局状态。
|
|
final double scale;
|
|
|
|
/// 构建固定比例的摄像头画面和状态浮层。
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return ColoredBox(
|
|
color: const Color(0xFF102635),
|
|
child: <Widget>[
|
|
if (texture != null)
|
|
FaceRecognitionPreviewLayer(
|
|
texture: texture!,
|
|
event: event,
|
|
labelScale: scale,
|
|
)
|
|
else
|
|
_FaceCameraEmptyState(status: status, scale: scale),
|
|
_FaceCameraStatusPill(
|
|
status: status,
|
|
text: statusText,
|
|
color: statusColor,
|
|
scale: scale,
|
|
).positioned(
|
|
left: 18 * scale,
|
|
top: 18 * scale,
|
|
),
|
|
_FaceCameraResolutionLabel(
|
|
texture: texture,
|
|
scale: scale,
|
|
).positioned(
|
|
right: 18 * scale,
|
|
bottom: 16 * scale,
|
|
),
|
|
].toStack(fit: StackFit.expand),
|
|
);
|
|
}
|
|
}
|
|
|
|
/// 摄像头 Texture 尚未创建时的明确等待或失败提示。
|
|
class _FaceCameraEmptyState extends StatelessWidget {
|
|
/// 创建与当前识别状态匹配的空画面。
|
|
const _FaceCameraEmptyState({required this.status, required this.scale});
|
|
|
|
/// 决定展示加载反馈还是设备异常提示的会话状态。
|
|
final FaceRecognitionSessionStatus status;
|
|
|
|
/// 宿主页面计算的尺寸缩放系数。
|
|
final double scale;
|
|
|
|
/// 构建不会造成画面尺寸跳变的居中状态。
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final failed = status == FaceRecognitionSessionStatus.failed;
|
|
return <Widget>[
|
|
if (failed)
|
|
Icon(
|
|
Icons.videocam_off_rounded,
|
|
color: Colors.white,
|
|
size: 42 * scale,
|
|
)
|
|
else
|
|
SizedBox(
|
|
width: 36 * scale,
|
|
height: 36 * scale,
|
|
child: const CircularProgressIndicator(
|
|
color: Colors.white,
|
|
strokeWidth: 3,
|
|
),
|
|
),
|
|
SizedBox(height: 12 * scale),
|
|
Text(
|
|
failed ? '摄像头画面暂不可用' : '正在准备摄像头画面',
|
|
style: TextStyle(
|
|
color: Colors.white,
|
|
fontSize: 16 * scale,
|
|
fontWeight: FontWeight.w700,
|
|
),
|
|
),
|
|
].toColumn(mainAxisSize: MainAxisSize.min).center();
|
|
}
|
|
}
|
|
|
|
/// 摄像头左上角的文字状态标识,确保状态不只依赖红绿颜色表达。
|
|
class _FaceCameraStatusPill extends StatelessWidget {
|
|
/// 创建当前识别阶段的状态标识。
|
|
const _FaceCameraStatusPill({
|
|
required this.status,
|
|
required this.text,
|
|
required this.color,
|
|
required this.scale,
|
|
});
|
|
|
|
/// 当前原生识别会话状态。
|
|
final FaceRecognitionSessionStatus status;
|
|
|
|
/// 业务页面提供的可选状态文案。
|
|
final String? text;
|
|
|
|
/// 业务页面提供的可选语义颜色。
|
|
final Color? color;
|
|
|
|
/// 宿主页面计算的尺寸缩放系数。
|
|
final double scale;
|
|
|
|
/// 构建包含状态点和文字的高对比标签。
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final resolvedText = text ??
|
|
switch (status) {
|
|
FaceRecognitionSessionStatus.initializing => '初始化中',
|
|
FaceRecognitionSessionStatus.success => '已识别',
|
|
FaceRecognitionSessionStatus.failed => '需要重试',
|
|
FaceRecognitionSessionStatus.cancelled => '已取消',
|
|
_ => '识别中',
|
|
};
|
|
final resolvedColor = color ??
|
|
switch (status) {
|
|
FaceRecognitionSessionStatus.failed ||
|
|
FaceRecognitionSessionStatus.cancelled =>
|
|
XfjDesignTokens.danger,
|
|
FaceRecognitionSessionStatus.initializing => XfjDesignTokens.warning,
|
|
_ => XfjDesignTokens.success,
|
|
};
|
|
return <Widget>[
|
|
DecoratedBox(
|
|
decoration: BoxDecoration(
|
|
color: resolvedColor,
|
|
shape: BoxShape.circle,
|
|
),
|
|
).width(10 * scale).height(10 * scale),
|
|
SizedBox(width: 8 * scale),
|
|
Text(
|
|
resolvedText,
|
|
style: TextStyle(
|
|
color: Colors.white,
|
|
fontSize: 14 * scale,
|
|
fontWeight: FontWeight.w700,
|
|
),
|
|
),
|
|
]
|
|
.toRow(mainAxisSize: MainAxisSize.min)
|
|
.padding(horizontal: 14 * scale, vertical: 9 * scale)
|
|
.decorated(
|
|
color: Colors.black.withValues(alpha: 0.62),
|
|
borderRadius: BorderRadius.circular(99),
|
|
);
|
|
}
|
|
}
|
|
|
|
/// 摄像头右下角的实际纹理分辨率标签。
|
|
class _FaceCameraResolutionLabel extends StatelessWidget {
|
|
/// 创建分辨率说明;纹理未就绪时使用插件既有默认分辨率。
|
|
const _FaceCameraResolutionLabel({
|
|
required this.texture,
|
|
required this.scale,
|
|
});
|
|
|
|
/// 当前预览纹理元数据。
|
|
final FaceRecognitionPreviewTexture? texture;
|
|
|
|
/// 宿主页面计算的尺寸缩放系数。
|
|
final double scale;
|
|
|
|
/// 构建不遮挡主要人脸区域的紧凑标签。
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Text(
|
|
'彩色摄像头 ${texture?.width ?? 1280} x ${texture?.height ?? 720}',
|
|
style: TextStyle(color: Colors.white, fontSize: 13 * scale),
|
|
).padding(horizontal: 12 * scale, vertical: 8 * scale).decorated(
|
|
color: Colors.black.withValues(alpha: 0.62),
|
|
borderRadius: BorderRadius.circular(10),
|
|
);
|
|
}
|
|
}
|