Commit cf3b74d6 authored by 张宏's avatar 张宏

清除ndk版本错误

parent 8983fa7f
{
"dart.flutterSdkPath": ".fvm/versions/3.41.9",
"java.configuration.updateBuildConfiguration": "interactive"
"java.configuration.updateBuildConfiguration": "disabled"
}
\ No newline at end of file
......@@ -24,7 +24,7 @@ if (flutterVersionName == null) {
android {
namespace "com.qialg.laki_icu_app"
compileSdkVersion flutter.compileSdkVersion
compileSdk = 36
ndkVersion flutter.ndkVersion
compileOptions {
......@@ -49,6 +49,9 @@ android {
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
ndk {
abiFilters 'armeabi-v7a', 'arm64-v8a'
}
}
flavorDimensions "environment"
......
......@@ -3,6 +3,7 @@
<!-- USB 摄像头权限 -->
<uses-feature android:name="android.hardware.usb.host" android:required="true" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<application
android:label="@string/app_name"
......
......@@ -4,42 +4,3 @@ import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity() {
}
// package com.example.laki_icu_app
// import com.example.laki_icu_app.camera.UsbCameraPlugin
// import com.example.laki_icu_app.camera.UsbCameraViewFactory
// import io.flutter.embedding.android.FlutterActivity
// import io.flutter.embedding.engine.FlutterEngine
// import io.flutter.plugin.common.MethodChannel
// class MainActivity : FlutterActivity() {
// companion object {
// const val USB_CAMERA_CHANNEL = "usb_camera"
// const val USB_CAMERA_VIEW_TYPE = "usb_camera_view"
// }
// private var usbCameraPlugin: UsbCameraPlugin? = null
// override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
// super.configureFlutterEngine(flutterEngine)
// // 注册 PlatformViewFactory —— 用于在 Flutter 中渲染摄像头预览画面
// flutterEngine
// .platformViewsController
// .registry
// .registerViewFactory(USB_CAMERA_VIEW_TYPE, UsbCameraViewFactory())
// // 注册 MethodChannel —— 用于 Flutter 与原生层通信(拍照、录像等控制指令)
// val channel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, USB_CAMERA_CHANNEL)
// usbCameraPlugin = UsbCameraPlugin(this, channel)
// }
// override fun onDestroy() {
// usbCameraPlugin?.dispose()
// super.onDestroy()
// }
// }
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">智能酒店</string>
<string name="app_name">Laki_icu</string>
</resources>
......@@ -23,6 +23,13 @@ rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
project.evaluationDependsOn(':app')
// :libnative 等第三方子模块未显式设置 ndkVersion,AGP 8.11 默认给 27,
// 与 ndk.dir (28.2) 和 :app/:jni 的 flutter.ndkVersion (28.2) 冲突。
// 在插件 apply 后立即注入 28.2,因为 :libnative 自己的 android{} 里没写 ndkVersion,不会被覆盖。
plugins.withId('com.android.library') {
android.ndkVersion = '28.2.13676358'
}
}
tasks.register("clean", Delete) {
......
......@@ -24,3 +24,35 @@ plugins {
}
include(":app")
// USB Camera 插件子模块引用
// flutter_usbcamera 的 build.gradle 写死了 implementation project(':libausbc')
// 必须在根项目 settings 中注册 :libausbc, :libuvc, :libnative
// 使用字符串 indexOf 从 .flutter-plugins-dependencies 中解析路径(避免正则转义问题)
val pluginsFile = java.io.File(rootProject.projectDir.parentFile, ".flutter-plugins-dependencies")
val text = pluginsFile.readText()
// JSON 格式: {"name":"flutter_usbcamera","path":"/some/path/",...}
// 找到 "flutter_usbcamera" 然后找它后面的第一个 "path" 值
val cameraKey = "\"flutter_usbcamera\""
val cameraIdx = text.indexOf(cameraKey)
if (cameraIdx >= 0) {
// 从 cameraKey 之后找 "\"path\":\""
val afterKey = text.substring(cameraIdx + cameraKey.length)
val pathKey = "\"path\":\""
val pathIdx = afterKey.indexOf(pathKey)
if (pathIdx >= 0) {
val pathStart = pathIdx + pathKey.length
val pathEnd = afterKey.indexOf("\"", pathStart)
if (pathEnd >= 0) {
val pluginPath = afterKey.substring(pathStart, pathEnd) + "/android"
include(":libuvc")
project(":libuvc").projectDir = java.io.File(pluginPath, "libuvc")
include(":libnative")
project(":libnative").projectDir = java.io.File(pluginPath, "libnative")
include(":libausbc")
project(":libausbc").projectDir = java.io.File(pluginPath, "libausbc")
}
}
}
......@@ -7,7 +7,7 @@
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>智能酒店</string>
<string>Laki_icu</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
......
import 'dart:async';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:flutter_usbcamera/flutter_usbcamera.dart';
import 'package:path_provider/path_provider.dart';
/// USB 摄像头服务层
///
/// 职责:
/// - 封装 Flutter 与 Android 原生层之间的 MethodChannel 通信
/// - 提供拍照、录像、设备查询等操作接口
/// - 处理摄像头权限申请
/// - 封装 [UsbCameraController],对上层提供统一的业务接口
/// - 监听设备热插拔事件,自动管理摄像头生命周期
/// - 将事件流中的拍摄完成回调转换为 Future 返回值
///
/// MethodChannel 名称: "usb_camera"
/// 涉及页面:首页监护舱 - 监控画面
class UsbCameraService {
static const _channel = MethodChannel('usb_camera');
// ==================== 权限管理 ====================
/// 检查并请求摄像头权限
/// 返回 true 表示权限已授予
static Future<bool> requestCameraPermission() async {
// iOS 需要 CAMERA 权限,Android 需要 CAMERA 权限
if (Platform.isAndroid) {
final status = await Permission.camera.request();
return status.isGranted;
} else if (Platform.isIOS) {
final status = await Permission.camera.request();
return status.isGranted;
static UsbCameraService? _instance;
final UsbCameraController _controller;
StreamSubscription<CameraEvent>? _eventSub;
// 事件广播流(供 UI 层订阅设备状态变化)
final StreamController<CameraEvent> _eventController =
StreamController<CameraEvent>.broadcast();
// 拍照/录像完成时的 Completer
Completer<String?>? _captureCompleter;
UsbCameraService._() : _controller = UsbCameraController();
/// 单例实例
static UsbCameraService get instance {
_instance ??= UsbCameraService._();
return _instance!;
}
/// USB 设备事件流(设备插拔、摄像头状态、拍摄回调等)
Stream<CameraEvent> get eventStream => _eventController.stream;
/// 当前控制器引用(供需要直接调用的场景)
UsbCameraController get controller => _controller;
/// 摄像头是否已打开
bool _isOpened = false;
bool get isOpened => _isOpened;
/// 是否正在录像
bool _isRecording = false;
bool get isRecording => _isRecording;
// ==================== 初始化与销毁 ====================
/// 初始化:注册 USB 监听,开始检测设备插拔
Future<void> initialize() async {
if (_eventSub != null) return; // 已初始化
// 监听插件事件流
_eventSub = UsbCameraController.events.listen(_handleEvent);
// 注册 USB 监听
await _controller.registerUsb();
debugPrint('[UsbCameraService] 初始化完成,USB 监听已注册');
}
/// 处理来自插件的所有事件
void _handleEvent(CameraEvent event) {
debugPrint('[UsbCameraService] 收到事件: ${event.type.name}, '
'deviceId=${event.deviceId}, state=${event.state}, path=${event.path}');
switch (event.type) {
case CameraEventType.onAttachDev:
// USB 摄像头插入 → 自动请求权限
if (event.deviceId != null) {
_controller.requestPermission(event.deviceId!);
}
break;
case CameraEventType.onDetachDev:
case CameraEventType.onDisConnectDev:
// 设备拔出或断开
_isOpened = false;
_isRecording = false;
break;
case CameraEventType.onConnectDev:
// 权限通过,自动打开摄像头
if (event.deviceId != null) {
_openCameraInternal(event.deviceId!);
}
break;
case CameraEventType.onCancelDev:
// 权限被拒绝
_isOpened = false;
break;
case CameraEventType.onCameraState:
if (event.state == 'opened') {
_isOpened = true;
} else if (event.state == 'closed' || event.state == 'error') {
_isOpened = false;
_isRecording = false;
}
break;
case CameraEventType.onCaptureBegin:
// 拍摄开始
break;
case CameraEventType.onCaptureComplete:
// 拍摄完成 → 解析 Completer
_isRecording = false;
if (_captureCompleter != null && !_captureCompleter!.isCompleted) {
_captureCompleter!.complete(event.path);
_captureCompleter = null;
}
break;
case CameraEventType.onCaptureError:
// 拍摄出错
_isRecording = false;
if (_captureCompleter != null && !_captureCompleter!.isCompleted) {
_captureCompleter!.completeError(
Exception(event.error ?? '拍摄失败'),
);
_captureCompleter = null;
}
break;
default:
break;
}
return false;
// 转发事件给所有订阅者
_eventController.add(event);
}
/// 检查摄像头权限是否已授予
static Future<bool> hasCameraPermission() async {
return await Permission.camera.isGranted;
/// 内部打开摄像头方法
Future<void> _openCameraInternal(int deviceId) async {
try {
await _controller.openCamera(
deviceId,
request: const CameraRequest(
previewWidth: 640,
previewHeight: 480,
renderMode: RenderMode.opengl,
previewFormat: PreviewFormat.mjpeg,
),
);
debugPrint('[UsbCameraService] 摄像头已打开: deviceId=$deviceId');
} catch (e) {
debugPrint('[UsbCameraService] 打开摄像头失败: $e');
}
}
// ==================== 设备管理 ====================
/// 获取已连接的 USB 摄像头设备列表
/// 返回 List<Map>,每个 Map 包含 deviceId、deviceName 等字段
static Future<List<Map<String, dynamic>>> getDeviceList() async {
/// 获取已连接的 USB 设备列表
Future<List<UsbDevice>> getDeviceList() async {
try {
final result = await _channel.invokeMethod('getDeviceList');
if (result is List) {
return result.cast<Map<dynamic, dynamic>>().map((e) {
return Map<String, dynamic>.from(e);
}).toList();
}
return [];
return await _controller.getDeviceList();
} catch (e) {
debugPrint('UsbCameraService.getDeviceList error: $e');
debugPrint('[UsbCameraService] getDeviceList error: $e');
return [];
}
}
// ==================== 摄像头控制 ====================
/// 打开摄像头(由 PlatformView 创建时自动触发)
/// 通常不需要手动调用
static Future<bool> openCamera({int? deviceId}) async {
/// 手动打开指定摄像头
Future<bool> openCamera(int deviceId, {CameraRequest? request}) async {
try {
final result = await _channel.invokeMethod('openCamera', {
if (deviceId != null) 'deviceId': deviceId,
});
return result == true;
await _controller.openCamera(
deviceId,
request: request ?? const CameraRequest(
previewWidth: 640,
previewHeight: 480,
renderMode: RenderMode.opengl,
previewFormat: PreviewFormat.mjpeg,
),
);
return true;
} catch (e) {
debugPrint('UsbCameraService.openCamera error: $e');
debugPrint('[UsbCameraService] openCamera error: $e');
return false;
}
}
// ==================== 摄像头控制 ====================
/// 关闭摄像头
static Future<bool> closeCamera() async {
Future<void> closeCamera() async {
try {
final result = await _channel.invokeMethod('closeCamera');
return result == true;
await _controller.closeCamera();
_isOpened = false;
_isRecording = false;
} catch (e) {
debugPrint('UsbCameraService.closeCamera error: $e');
return false;
debugPrint('[UsbCameraService] closeCamera error: $e');
}
}
/// 拍照
/// [path] 可选,指定照片保存路径;不传则使用默认路径
/// 返回照片文件的路径
static Future<String?> takePhoto({String? path}) async {
/// [customPath] 可选,指定照片保存路径;不传则自动生成
/// 返回照片文件路径
Future<String?> takePhoto({String? customPath}) async {
if (!_isOpened) {
debugPrint('[UsbCameraService] 摄像头未打开,无法拍照');
return null;
}
try {
final result = await _channel.invokeMethod('takePhoto', {
if (path != null) 'path': path,
});
return result as String?;
_captureCompleter = Completer<String?>();
final path = customPath ?? await _generatePhotoPath();
final success = await _controller.captureImage(path: path);
if (!success) {
_captureCompleter = null;
return null;
}
// 等待 onCaptureComplete 事件返回路径
final result = await _captureCompleter!.future;
return result;
} catch (e) {
debugPrint('UsbCameraService.takePhoto error: $e');
_captureCompleter = null;
debugPrint('[UsbCameraService] takePhoto error: $e');
return null;
}
}
/// 开始录像
/// [path] 可选,指定视频保存路径;不传则使用默认路径
static Future<bool> startRecord({String? path}) async {
/// [customPath] 可选,指定视频保存路径
Future<bool> startRecord({String? customPath}) async {
if (!_isOpened) {
debugPrint('[UsbCameraService] 摄像头未打开,无法录像');
return false;
}
try {
final result = await _channel.invokeMethod('startRecord', {
if (path != null) 'path': path,
});
return result == true;
final path = customPath ?? await _generateVideoPath();
final success = await _controller.captureVideoStart(path: path);
if (success) {
_isRecording = true;
}
return success;
} catch (e) {
debugPrint('UsbCameraService.startRecord error: $e');
debugPrint('[UsbCameraService] startRecord error: $e');
return false;
}
}
/// 停止录像
/// 返回视频文件的路径
static Future<String?> stopRecord() async {
try {
final result = await _channel.invokeMethod('stopRecord');
return result as String?;
} catch (e) {
debugPrint('UsbCameraService.stopRecord error: $e');
/// 返回视频文件路径
Future<String?> stopRecord() async {
if (!_isRecording) {
debugPrint('[UsbCameraService] 当前未在录像');
return null;
}
}
/// 查询摄像头是否已打开
static Future<bool> isCameraOpened() async {
try {
final result = await _channel.invokeMethod('isCameraOpened');
return result == true;
_captureCompleter = Completer<String?>();
final success = await _controller.captureVideoStop();
if (!success) {
_captureCompleter = null;
_isRecording = false;
return null;
}
// 等待 onCaptureComplete 事件返回路径
final result = await _captureCompleter!.future;
_isRecording = false;
return result;
} catch (e) {
debugPrint('UsbCameraService.isCameraOpened error: $e');
return false;
_captureCompleter = null;
_isRecording = false;
debugPrint('[UsbCameraService] stopRecord error: $e');
return null;
}
}
/// 设置预览分辨率
static Future<bool> setPreviewSize(int width, int height) async {
try {
final result = await _channel.invokeMethod('setPreviewSize', {
'width': width,
'height': height,
});
return result == true;
} catch (e) {
debugPrint('UsbCameraService.setPreviewSize error: $e');
return false;
// ==================== 路径生成 ====================
/// 生成照片保存路径
Future<String> _generatePhotoPath() async {
final dir = await _getCameraDirectory();
final timestamp = _timestamp();
return '${dir.path}/IMG_$timestamp.jpg';
}
/// 生成视频保存路径
Future<String> _generateVideoPath() async {
final dir = await _getCameraDirectory();
final timestamp = _timestamp();
return '${dir.path}/VID_$timestamp.mp4';
}
/// 获取 USB 摄像头存储目录
Future<Directory> _getCameraDirectory() async {
final baseDir = Platform.isAndroid
? await getExternalStorageDirectory()
: await getApplicationDocumentsDirectory();
final cameraDir = Directory('${baseDir!.path}/usb_camera');
if (!await cameraDir.exists()) {
await cameraDir.create(recursive: true);
}
return cameraDir;
}
/// 生成时间戳字符串
String _timestamp() {
final now = DateTime.now();
return '${now.year}${_pad(now.month)}${_pad(now.day)}_'
'${_pad(now.hour)}${_pad(now.minute)}${_pad(now.second)}';
}
String _pad(int value) => value.toString().padLeft(2, '0');
// ==================== 销毁 ====================
/// 释放所有资源
Future<void> dispose() async {
_captureCompleter = null;
_isOpened = false;
_isRecording = false;
await _eventSub?.cancel();
_eventSub = null;
await _eventController.close();
await _controller.dispose();
debugPrint('[UsbCameraService] 资源已释放');
}
}
......@@ -7,12 +7,16 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
/// 职责:
/// - 提供拍照、录像、刷新三个操作按钮
/// - 录像按钮支持录制中/停止录制两种状态切换
/// - 摄像头未就绪时按钮置灰不可操作
///
/// 涉及页面:首页监护舱页面 - 监控画面底部控制栏
class CameraControls extends StatelessWidget {
/// 是否正在录像
final bool isRecording;
/// 摄像头是否已就绪(已打开)
final bool cameraReady;
/// 拍照回调
final VoidCallback? onTakePhoto;
......@@ -25,6 +29,7 @@ class CameraControls extends StatelessWidget {
const CameraControls({
super.key,
this.isRecording = false,
this.cameraReady = false,
this.onTakePhoto,
this.onToggleRecord,
this.onRefresh,
......@@ -39,19 +44,22 @@ class CameraControls extends StatelessWidget {
icon: Icons.refresh,
onPressed: onRefresh,
tooltip: '刷新画面',
enabled: true, // 刷新按钮始终可用
),
SizedBox(width: 16.w),
_buildControlButton(
icon: Icons.camera_alt,
onPressed: onTakePhoto,
onPressed: cameraReady ? onTakePhoto : null,
tooltip: '拍照',
enabled: cameraReady,
),
SizedBox(width: 16.w),
_buildControlButton(
icon: isRecording ? Icons.stop : Icons.videocam,
onPressed: onToggleRecord,
onPressed: cameraReady ? onToggleRecord : null,
tooltip: isRecording ? '停止录像' : '开始录像',
isActive: isRecording,
enabled: cameraReady,
),
],
);
......@@ -63,41 +71,49 @@ class CameraControls extends StatelessWidget {
VoidCallback? onPressed,
String? tooltip,
bool isActive = false,
bool enabled = true,
}) {
final effectiveOpacity = enabled ? 1.0 : 0.35;
return Tooltip(
message: tooltip ?? '',
child: GestureDetector(
onTap: () {
HapticFeedback.lightImpact();
onPressed?.call();
},
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
width: 60.w,
height: 60.w,
decoration: BoxDecoration(
color: isActive
? const Color(0xCCFF4444)
: const Color(0x331E10B6),
borderRadius: BorderRadius.circular(30.r),
border: Border.all(
color: isActive ? Colors.redAccent : Colors.white24,
width: 1,
onTap: enabled
? () {
HapticFeedback.lightImpact();
onPressed?.call();
}
: null,
child: Opacity(
opacity: effectiveOpacity,
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
width: 60.w,
height: 60.w,
decoration: BoxDecoration(
color: isActive
? const Color(0xCCFF4444)
: const Color(0x331E10B6),
borderRadius: BorderRadius.circular(30.r),
border: Border.all(
color: isActive ? Colors.redAccent : Colors.white24,
width: 1,
),
boxShadow: isActive
? [
BoxShadow(
color: Colors.redAccent.withValues(alpha: 0.4),
blurRadius: 12.r,
spreadRadius: 2,
),
]
: null,
),
child: Icon(
icon,
color: Colors.white,
size: 32.w,
),
boxShadow: isActive
? [
BoxShadow(
color: Colors.redAccent.withValues(alpha: 0.4),
blurRadius: 12.r,
spreadRadius: 2,
),
]
: null,
),
child: Icon(
icon,
color: Colors.white,
size: 32.w,
),
),
),
......
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_usbcamera/flutter_usbcamera.dart';
import 'package:laki_icu_app/services/usb_camera_service.dart';
import 'camera_controls.dart';
......@@ -8,9 +10,9 @@ import 'camera_controls.dart';
/// 监控画面组件 —— 集成 USB 摄像头预览画面
///
/// 职责:
/// - 通过 AndroidView 嵌入 Android 原生 USBCameraTextureView 预览画面
/// - 通过 [UsbCameraPreview] 嵌入 USB 摄像头实时预览画面
/// - 监听 [UsbCameraService.eventStream] 处理设备热插拔和状态变化
/// - 底部控制栏:拍照、录像、刷新按钮
/// - 通过 [UsbCameraService] 与原生层通信
///
/// 涉及页面:首页监护舱页面
class MonitoringScreen extends StatefulWidget {
......@@ -30,13 +32,103 @@ class MonitoringScreen extends StatefulWidget {
}
class _MonitoringScreenState extends State<MonitoringScreen> {
bool _isRecording = false;
final UsbCameraService _cameraService = UsbCameraService.instance;
StreamSubscription<CameraEvent>? _eventSub;
bool _cameraReady = false;
bool _isRecording = false;
@override
void initState() {
super.initState();
_initCamera();
}
Future<void> _initCamera() async {
// 监听摄像头事件流
_eventSub = _cameraService.eventStream.listen(_onCameraEvent);
// 初始化 USB 监听
await _cameraService.initialize();
// 检查是否已有设备连接
_refreshState();
}
void _onCameraEvent(CameraEvent event) {
if (!mounted) return;
switch (event.type) {
case CameraEventType.onAttachDev:
// 设备插入
setState(() {});
break;
case CameraEventType.onDetachDev:
case CameraEventType.onDisConnectDev:
// 设备拔出
setState(() {
_cameraReady = false;
_isRecording = false;
});
break;
case CameraEventType.onConnectDev:
// 权限通过,摄像头即将打开
setState(() {});
break;
case CameraEventType.onCancelDev:
// 权限被拒绝
setState(() {
_cameraReady = false;
});
break;
case CameraEventType.onCameraState:
if (event.state == 'opened') {
setState(() => _cameraReady = true);
} else if (event.state == 'closed' || event.state == 'error') {
setState(() {
_cameraReady = false;
_isRecording = false;
});
}
break;
case CameraEventType.onCaptureComplete:
setState(() => _isRecording = false);
if (event.path != null && mounted) {
final isVideo = event.captureType == 'video' ||
(event.path?.endsWith('.mp4') ?? false);
_showSnackBar(isVideo ? '录像已保存' : '拍照成功');
}
break;
case CameraEventType.onCaptureError:
setState(() => _isRecording = false);
if (mounted) {
_showSnackBar('拍摄失败: ${event.error ?? '未知错误'}');
}
break;
default:
break;
}
}
void _refreshState() {
setState(() {
_cameraReady = _cameraService.isOpened;
_isRecording = _cameraService.isRecording;
});
}
@override
void dispose() {
// 退出页面时关闭摄像头
UsbCameraService.closeCamera();
_eventSub?.cancel();
_cameraService.dispose();
super.dispose();
}
......@@ -56,7 +148,7 @@ class _MonitoringScreenState extends State<MonitoringScreen> {
children: [
// 摄像头预览画面
_buildCameraPreview(),
// 摄像头未就绪时的提示
// 摄像头未就绪时的占位提示
if (!_cameraReady) _buildPlaceholder(),
// 底部控制按钮
Positioned(
......@@ -64,9 +156,10 @@ class _MonitoringScreenState extends State<MonitoringScreen> {
right: 24.w,
child: CameraControls(
isRecording: _isRecording,
cameraReady: _cameraReady,
onTakePhoto: _onTakePhoto,
onToggleRecord: _onToggleRecord,
onRefresh: widget.onRefresh,
onRefresh: _onRefresh,
),
),
],
......@@ -74,22 +167,11 @@ class _MonitoringScreenState extends State<MonitoringScreen> {
);
}
/// 构建 USB 摄像头预览画面(通过 PlatformView
/// 构建 USB 摄像头预览画面(通过 flutter_usbcamera 插件
Widget _buildCameraPreview() {
return ClipRRect(
borderRadius: BorderRadius.circular(20.r),
child: SizedBox.expand(
child: AndroidView(
viewType: 'usb_camera_view',
onPlatformViewCreated: (_) {
setState(() {
_cameraReady = true;
});
},
creationParams: const <String, dynamic>{},
creationParamsCodec: const StandardMessageCodec(),
),
),
child: const UsbCameraPreview(),
);
}
......@@ -128,31 +210,49 @@ class _MonitoringScreenState extends State<MonitoringScreen> {
/// 拍照
Future<void> _onTakePhoto() async {
final path = await UsbCameraService.takePhoto();
if (!_cameraReady) {
_showSnackBar('摄像头未就绪');
return;
}
final path = await _cameraService.takePhoto();
if (path != null && mounted) {
_showSnackBar('拍照成功: $path');
_showSnackBar('拍照成功');
}
widget.onSnapshot?.call();
}
/// 切换录像状态
Future<void> _onToggleRecord() async {
if (!_cameraReady) {
_showSnackBar('摄像头未就绪');
return;
}
if (_isRecording) {
final path = await UsbCameraService.stopRecord();
final path = await _cameraService.stopRecord();
if (path != null && mounted) {
_showSnackBar('录像已保存: $path');
_showSnackBar('录像已保存');
}
setState(() => _isRecording = false);
} else {
final success = await UsbCameraService.startRecord();
final success = await _cameraService.startRecord();
if (success) {
setState(() => _isRecording = true);
if (mounted) _showSnackBar('开始录像');
} else {
if (mounted) _showSnackBar('开始录像失败');
}
}
widget.onRecord?.call();
}
/// 刷新画面
void _onRefresh() {
_refreshState();
widget.onRefresh?.call();
}
void _showSnackBar(String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
......
......@@ -102,7 +102,7 @@ class LoginView extends StatelessWidget {
),
SizedBox(height: 40.h),
const Text(
'智能酒店管理系统',
'Laki icu 智能仓',
style: TextStyle(
color: Color(0xFF333333),
fontSize: 24,
......
......@@ -51,6 +51,7 @@ dependencies:
fluttertoast: ^9.0.0
permission_handler: ^11.3.1
flutter_usbcamera: ^0.0.1
path_provider: ^2.1.2
# flutter_usbcamera: ^0.0.1
# fl_chart: ^0.71.0
# fl_chart: ^1.1.0
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment