Files
biz_xfj_cloud/lib/common/widgets/face/face_camera_preview.dart
T
2026-08-09 14:45:18 +08:00

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),
);
}
}