Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
L
laki_icu_app
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
张宏
laki_icu_app
Commits
8ff902b4
Commit
8ff902b4
authored
Jun 16, 2026
by
张宏
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
2
parent
eacff131
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
18 changed files
with
852 additions
and
43 deletions
+852
-43
.flutter-plugins-dependencies
.flutter-plugins-dependencies
+0
-0
Dart_Packages.xml
.idea/libraries/Dart_Packages.xml
+0
-0
Flutter_Plugins.xml
.idea/libraries/Flutter_Plugins.xml
+3
-1
build.gradle
android/app/build.gradle
+1
-3
AndroidManifest.xml
android/app/src/main/AndroidManifest.xml
+12
-0
MainActivity.kt
.../src/main/kotlin/com/example/laki_icu_app/MainActivity.kt
+31
-1
UsbCameraManager.kt
...otlin/com/example/laki_icu_app/camera/UsbCameraManager.kt
+170
-0
UsbCameraPlugin.kt
...kotlin/com/example/laki_icu_app/camera/UsbCameraPlugin.kt
+88
-0
UsbCameraView.kt
...n/kotlin/com/example/laki_icu_app/camera/UsbCameraView.kt
+62
-0
UsbCameraViewFactory.kt
...n/com/example/laki_icu_app/camera/UsbCameraViewFactory.kt
+22
-0
MainActivity.kt
...c/main/kotlin/com/example/smart_hotel_app/MainActivity.kt
+0
-6
device_filter.xml
android/app/src/main/res/xml/device_filter.xml
+16
-0
usb_camera_service.dart
lib/services/usb_camera_service.dart
+150
-0
camera_controls.dart
lib/views/home/index/widgets/camera_controls.dart
+106
-0
monitoring_screen.dart
lib/views/home/index/widgets/monitoring_screen.dart
+186
-32
pubspec.yaml
pubspec.yaml
+1
-0
generated_plugin_registrant.cc
windows/flutter/generated_plugin_registrant.cc
+3
-0
generated_plugins.cmake
windows/flutter/generated_plugins.cmake
+1
-0
No files found.
.flutter-plugins-dependencies
View file @
8ff902b4
This diff is collapsed.
Click to expand it.
.idea/libraries/Dart_Packages.xml
View file @
8ff902b4
This diff is collapsed.
Click to expand it.
.idea/libraries/Flutter_Plugins.xml
View file @
8ff902b4
...
@@ -16,7 +16,6 @@
...
@@ -16,7 +16,6 @@
<root
url=
"file://$USER_HOME$/.pub-cache/hosted/pub.dev/jni_flutter-1.0.1"
/>
<root
url=
"file://$USER_HOME$/.pub-cache/hosted/pub.dev/jni_flutter-1.0.1"
/>
<root
url=
"file://$USER_HOME$/.pub-cache/hosted/pub.dev/path_provider_foundation-2.6.0"
/>
<root
url=
"file://$USER_HOME$/.pub-cache/hosted/pub.dev/path_provider_foundation-2.6.0"
/>
<root
url=
"file://$USER_HOME$/.pub-cache/hosted/pub.dev/url_launcher_ios-6.4.1"
/>
<root
url=
"file://$USER_HOME$/.pub-cache/hosted/pub.dev/url_launcher_ios-6.4.1"
/>
<root
url=
"file://$USER_HOME$/.pub-cache/hosted/pub.dev/vibration-3.2.0"
/>
<root
url=
"file://$USER_HOME$/.pub-cache/hosted/pub.dev/path_provider-2.1.5"
/>
<root
url=
"file://$USER_HOME$/.pub-cache/hosted/pub.dev/path_provider-2.1.5"
/>
<root
url=
"file://$USER_HOME$/.pub-cache/hosted/pub.dev/url_launcher_windows-3.1.5"
/>
<root
url=
"file://$USER_HOME$/.pub-cache/hosted/pub.dev/url_launcher_windows-3.1.5"
/>
<root
url=
"file://$USER_HOME$/.pub-cache/hosted/pub.dev/url_launcher_linux-3.2.2"
/>
<root
url=
"file://$USER_HOME$/.pub-cache/hosted/pub.dev/url_launcher_linux-3.2.2"
/>
...
@@ -28,6 +27,9 @@
...
@@ -28,6 +27,9 @@
<root
url=
"file://$USER_HOME$/.pub-cache/hosted/pub.dev/flutter_secure_storage-9.2.4"
/>
<root
url=
"file://$USER_HOME$/.pub-cache/hosted/pub.dev/flutter_secure_storage-9.2.4"
/>
<root
url=
"file://$USER_HOME$/.pub-cache/hosted/pub.dev/share_plus-12.0.2"
/>
<root
url=
"file://$USER_HOME$/.pub-cache/hosted/pub.dev/share_plus-12.0.2"
/>
<root
url=
"file://$USER_HOME$/.pub-cache/hosted/pub.dev/device_info_plus-12.4.0"
/>
<root
url=
"file://$USER_HOME$/.pub-cache/hosted/pub.dev/device_info_plus-12.4.0"
/>
<root
url=
"file://$USER_HOME$/.pub-cache/hosted/pub.dev/permission_handler-11.4.0"
/>
<root
url=
"file://$USER_HOME$/.pub-cache/hosted/pub.dev/permission_handler_html-0.1.3+5"
/>
<root
url=
"file://$USER_HOME$/.pub-cache/hosted/pub.dev/vibration-3.2.0"
/>
</CLASSES>
</CLASSES>
<JAVADOC
/>
<JAVADOC
/>
<SOURCES
/>
<SOURCES
/>
...
...
android/app/build.gradle
View file @
8ff902b4
plugins
{
plugins
{
id
"com.android.application"
id
"com.android.application"
id
"kotlin-android"
id
"kotlin-android"
...
@@ -101,5 +99,5 @@ flutter {
...
@@ -101,5 +99,5 @@ flutter {
}
}
dependencies
{
dependencies
{
//
implementation 'com.github.jiangdongguo.AndroidUSBCamera:libausbc:3.3.3'
implementation
'com.github.jiangdongguo.AndroidUSBCamera:libausbc:3.3.3'
}
}
android/app/src/main/AndroidManifest.xml
View file @
8ff902b4
<manifest
xmlns:android=
"http://schemas.android.com/apk/res/android"
>
<manifest
xmlns:android=
"http://schemas.android.com/apk/res/android"
>
<!-- USB 摄像头权限 -->
<uses-feature
android:name=
"android.hardware.usb.host"
android:required=
"true"
/>
<uses-permission
android:name=
"android.permission.CAMERA"
/>
<application
<application
android:label=
"@string/app_name"
android:label=
"@string/app_name"
android:name=
"${applicationName}"
android:name=
"${applicationName}"
...
@@ -23,6 +28,13 @@
...
@@ -23,6 +28,13 @@
<action
android:name=
"android.intent.action.MAIN"
/>
<action
android:name=
"android.intent.action.MAIN"
/>
<category
android:name=
"android.intent.category.LAUNCHER"
/>
<category
android:name=
"android.intent.category.LAUNCHER"
/>
</intent-filter>
</intent-filter>
<!-- USB 设备插拔事件监听 -->
<intent-filter>
<action
android:name=
"android.hardware.usb.action.USB_DEVICE_ATTACHED"
/>
</intent-filter>
<meta-data
android:name=
"android.hardware.usb.action.USB_DEVICE_ATTACHED"
android:resource=
"@xml/device_filter"
/>
</activity>
</activity>
<!-- Don't delete the meta-data below.
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
...
...
android/app/src/main/kotlin/com/example/laki_icu_app/MainActivity.kt
View file @
8ff902b4
package
com.example.laki_icu_app
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.android.FlutterActivity
import
io.flutter.embedding.engine.FlutterEngine
import
io.flutter.plugin.common.MethodChannel
class
MainActivity
:
FlutterActivity
()
{
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
()
}
}
}
android/app/src/main/kotlin/com/example/laki_icu_app/camera/UsbCameraManager.kt
0 → 100644
View file @
8ff902b4
package
com.example.laki_icu_app.camera
import
android.content.Context
import
android.hardware.usb.UsbDevice
import
android.view.TextureView
import
android.view.View
import
com.jiangdg.ausbc.MultiCameraClient
import
com.jiangdg.ausbc.callback.ICameraStateCallBack
import
com.jiangdg.ausbc.callback.IPreviewDataCallBack
import
com.jiangdg.ausbc.camera.bean.CameraRequest
import
com.jiangdg.ausbc.camera.bean.PreviewSize
import
io.flutter.plugin.common.MethodChannel
import
java.io.File
/**
* USB 摄像头管理器(单例)
*
* 职责:
* - 封装 AndroidUSBCamera (libausbc) 的调用
* - 被 UsbCameraPlugin(MethodChannel 控制)和 UsbCameraView(预览画面)共享
*
* 注意:MultiCameraClient 支持多摄像头管理,当前实现先支持第一个 USB 摄像头
*/
object
UsbCameraManager
{
private
var
cameraClient
:
MultiCameraClient
?
=
null
private
var
textureView
:
TextureView
?
=
null
private
var
isOpened
=
false
private
var
currentDevice
:
UsbDevice
?
=
null
/** 获取已连接的 USB 摄像头设备列表 */
fun
getDeviceList
(
context
:
Context
):
List
<
Map
<
String
,
Any
?>>
{
val
client
=
MultiCameraClient
(
context
)
val
list
=
client
.
getDeviceList
()
client
.
release
()
return
list
.
map
{
device
->
mapOf
<
String
,
Any
?>(
"deviceId"
to
device
.
deviceId
,
"deviceName"
to
device
.
deviceName
,
"vendorId"
to
device
.
vendorId
,
"productId"
to
device
.
productId
,
)
}
}
/** 打开指定 USB 摄像头 */
fun
openCamera
(
context
:
Context
,
textureView
:
TextureView
,
deviceId
:
Int
?
=
null
,
):
Boolean
{
if
(
isOpened
)
{
closeCamera
()
}
return
try
{
val
client
=
MultiCameraClient
(
context
).
apply
{
// 设置状态回调
setCameraStateCallBack
(
object
:
ICameraStateCallBack
{
override
fun
onCameraOpen
()
{
isOpened
=
true
}
override
fun
onCameraClose
()
{
isOpened
=
false
}
override
fun
onCameraError
(
message
:
String
?)
{
isOpened
=
false
}
override
fun
onCameraAttached
(
device
:
UsbDevice
?)
{
// USB 摄像头插入时触发
}
override
fun
onCameraDetached
(
device
:
UsbDevice
?)
{
// USB 摄像头拔出时触发
if
(
device
==
currentDevice
)
{
isOpened
=
false
}
}
})
}
// 打开摄像头
val
config
=
CameraRequest
.
Builder
()
.
setPreviewSize
(
PreviewSize
.
PREVIEW_SIZE_720P
)
.
create
()
client
.
initialize
(
textureView
,
config
)
client
.
startPreview
()
this
.
cameraClient
=
client
this
.
textureView
=
textureView
isOpened
=
true
true
}
catch
(
e
:
Exception
)
{
e
.
printStackTrace
()
false
}
}
/** 拍照 */
fun
takePhoto
(
savePath
:
String
?):
String
?
{
val
client
=
cameraClient
?:
return
null
return
try
{
val
path
=
savePath
?:
"/storage/emulated/0/Pictures/usb_camera_${System.currentTimeMillis()}.jpg"
// 确保目录存在
val
file
=
File
(
path
)
file
.
parentFile
?.
mkdirs
()
client
.
capturePicture
(
path
)
path
}
catch
(
e
:
Exception
)
{
e
.
printStackTrace
()
null
}
}
/** 开始录像 */
fun
startRecord
(
savePath
:
String
?):
Boolean
{
val
client
=
cameraClient
?:
return
false
return
try
{
val
path
=
savePath
?:
"/storage/emulated/0/Movies/usb_camera_${System.currentTimeMillis()}.mp4"
val
file
=
File
(
path
)
file
.
parentFile
?.
mkdirs
()
client
.
startRecording
(
path
)
true
}
catch
(
e
:
Exception
)
{
e
.
printStackTrace
()
false
}
}
/** 停止录像 */
fun
stopRecord
():
String
?
{
val
client
=
cameraClient
?:
return
null
return
try
{
client
.
stopRecording
()
}
catch
(
e
:
Exception
)
{
e
.
printStackTrace
()
null
}
}
/** 关闭摄像头 */
fun
closeCamera
()
{
try
{
cameraClient
?.
stopPreview
()
cameraClient
?.
release
()
}
catch
(
_
:
Exception
)
{
// 忽略释放时的异常
}
cameraClient
=
null
textureView
=
null
isOpened
=
false
currentDevice
=
null
}
/** 查询摄像头是否已打开 */
fun
isCameraOpened
():
Boolean
=
isOpened
/** 设置预览分辨率 */
fun
setPreviewSize
(
width
:
Int
,
height
:
Int
):
Boolean
{
// libausbc 的 preview size 在初始化时设置,运行时无法动态切换
// 此处保留接口供后续扩展
return
false
}
}
android/app/src/main/kotlin/com/example/laki_icu_app/camera/UsbCameraPlugin.kt
0 → 100644
View file @
8ff902b4
package
com.example.laki_icu_app.camera
import
android.app.Activity
import
io.flutter.plugin.common.MethodCall
import
io.flutter.plugin.common.MethodChannel
/**
* USB 摄像头 MethodChannel 处理器
*
* 职责:
* - 接收 Flutter 端通过 MethodChannel 发来的控制指令
* - 调用 UsbCameraManager 执行对应操作
* - 通过 result 将结果返回给 Flutter 端
*
* MethodChannel 名称: "usb_camera"
*/
class
UsbCameraPlugin
(
private
val
activity
:
Activity
,
private
val
channel
:
MethodChannel
,
)
:
MethodChannel
.
MethodCallHandler
{
init
{
channel
.
setMethodCallHandler
(
this
)
}
override
fun
onMethodCall
(
call
:
MethodCall
,
result
:
MethodChannel
.
Result
)
{
when
(
call
.
method
)
{
"getDeviceList"
->
{
val
list
=
UsbCameraManager
.
getDeviceList
(
activity
)
result
.
success
(
list
)
}
"openCamera"
->
{
// 此方法由 PlatformView 在创建时通过 Flutter 端调用
// 实际的 openCamera 由 UsbCameraView 在 getView() 时完成
// 这里返回 true 表示准备就绪
result
.
success
(
true
)
}
"closeCamera"
->
{
UsbCameraManager
.
closeCamera
()
result
.
success
(
true
)
}
"takePhoto"
->
{
val
path
=
call
.
argument
<
String
>(
"path"
)
val
photoPath
=
UsbCameraManager
.
takePhoto
(
path
)
if
(
photoPath
!=
null
)
{
result
.
success
(
photoPath
)
}
else
{
result
.
error
(
"TAKE_PHOTO_ERROR"
,
"拍照失败"
,
null
)
}
}
"startRecord"
->
{
val
path
=
call
.
argument
<
String
>(
"path"
)
val
success
=
UsbCameraManager
.
startRecord
(
path
)
result
.
success
(
success
)
}
"stopRecord"
->
{
val
videoPath
=
UsbCameraManager
.
stopRecord
()
if
(
videoPath
!=
null
)
{
result
.
success
(
videoPath
)
}
else
{
result
.
error
(
"STOP_RECORD_ERROR"
,
"停止录像失败"
,
null
)
}
}
"isCameraOpened"
->
{
result
.
success
(
UsbCameraManager
.
isCameraOpened
())
}
"setPreviewSize"
->
{
val
width
=
call
.
argument
<
Int
>(
"width"
)
?:
1280
val
height
=
call
.
argument
<
Int
>(
"height"
)
?:
720
val
success
=
UsbCameraManager
.
setPreviewSize
(
width
,
height
)
result
.
success
(
success
)
}
else
->
result
.
notImplemented
()
}
}
fun
dispose
()
{
UsbCameraManager
.
closeCamera
()
}
}
android/app/src/main/kotlin/com/example/laki_icu_app/camera/UsbCameraView.kt
0 → 100644
View file @
8ff902b4
package
com.example.laki_icu_app.camera
import
android.annotation.SuppressLint
import
android.content.Context
import
android.graphics.SurfaceTexture
import
android.view.TextureView
import
android.view.View
import
io.flutter.plugin.platform.PlatformView
/**
* USB 摄像头预览 PlatformView
*
* 职责:
* - 内嵌 AndroidUSBCamera 的 TextureView 渲染摄像头预览画面
* - 实现 PlatformView 接口以嵌入 Flutter Widget 树
*
* viewType 注册名称: "usb_camera_view"
*/
class
UsbCameraView
(
private
val
context
:
Context
,
viewId
:
Int
,
)
:
PlatformView
{
/** 摄像头预览 TextureView */
private
val
textureView
:
TextureView
=
TextureView
(
context
).
apply
{
// 当 TextureView 的 SurfaceTexture 就绪后,打开摄像头并开始预览
surfaceTextureListener
=
object
:
TextureView
.
SurfaceTextureListener
{
override
fun
onSurfaceTextureAvailable
(
surface
:
SurfaceTexture
,
width
:
Int
,
height
:
Int
,
)
{
// Surface 就绪后打开 USB 摄像头
UsbCameraManager
.
openCamera
(
context
,
this
@apply
)
}
override
fun
onSurfaceTextureSizeChanged
(
surface
:
SurfaceTexture
,
width
:
Int
,
height
:
Int
,
)
{
// 尺寸变化时无需额外处理
}
override
fun
onSurfaceTextureDestroyed
(
surface
:
SurfaceTexture
):
Boolean
{
// 当 Surface 销毁时不要关闭摄像头,因为可能是暂时性的
return
true
}
override
fun
onSurfaceTextureUpdated
(
surface
:
SurfaceTexture
)
{
// 每帧更新时回调
}
}
}
override
fun
getView
():
View
=
textureView
override
fun
dispose
()
{
// PlatformView 销毁时释放摄像头资源
UsbCameraManager
.
closeCamera
()
}
}
android/app/src/main/kotlin/com/example/laki_icu_app/camera/UsbCameraViewFactory.kt
0 → 100644
View file @
8ff902b4
package
com.example.laki_icu_app.camera
import
android.content.Context
import
io.flutter.plugin.common.StandardMessageCodec
import
io.flutter.plugin.platform.PlatformView
import
io.flutter.plugin.platform.PlatformViewFactory
/**
* USB 摄像头 PlatformView 工厂
*
* 职责:
* - 告诉 Flutter 如何创建 UsbCameraView 实例
* - 在 MainActivity.configureFlutterEngine() 中注册
*
* 注册名称: "usb_camera_view"
*/
class
UsbCameraViewFactory
:
PlatformViewFactory
(
StandardMessageCodec
.
INSTANCE
)
{
override
fun
create
(
context
:
Context
,
viewId
:
Int
,
args
:
Any
?):
PlatformView
{
return
UsbCameraView
(
context
,
viewId
)
}
}
android/app/src/main/kotlin/com/example/smart_hotel_app/MainActivity.kt
deleted
100644 → 0
View file @
eacff131
package
com.example.smart_hotel_app
import
io.flutter.embedding.android.FlutterActivity
class
MainActivity
:
FlutterActivity
()
{
}
android/app/src/main/res/xml/device_filter.xml
0 → 100644
View file @
8ff902b4
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- USB 摄像头设备过滤器 -->
<!-- 通用 USB 视频设备(UVC) -->
<usb-device
class=
"14"
subclass=
"1"
/>
<!-- 部分厂商特定的 USB 摄像头设备 -->
<usb-device
class=
"14"
subclass=
"2"
/>
<!-- 兼容更多 USB 视频类设备 -->
<usb-device
class=
"239"
subclass=
"2"
/>
</resources>
lib/services/usb_camera_service.dart
0 → 100644
View file @
8ff902b4
import
'dart:io'
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/services.dart'
;
import
'package:permission_handler/permission_handler.dart'
;
/// USB 摄像头服务层
///
/// 职责:
/// - 封装 Flutter 与 Android 原生层之间的 MethodChannel 通信
/// - 提供拍照、录像、设备查询等操作接口
/// - 处理摄像头权限申请
///
/// 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
;
}
return
false
;
}
/// 检查摄像头权限是否已授予
static
Future
<
bool
>
hasCameraPermission
()
async
{
return
await
Permission
.
camera
.
isGranted
;
}
// ==================== 设备管理 ====================
/// 获取已连接的 USB 摄像头设备列表
/// 返回 List<Map>,每个 Map 包含 deviceId、deviceName 等字段
static
Future
<
List
<
Map
<
String
,
dynamic
>>>
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
[];
}
catch
(
e
)
{
debugPrint
(
'UsbCameraService.getDeviceList error:
$e
'
);
return
[];
}
}
// ==================== 摄像头控制 ====================
/// 打开摄像头(由 PlatformView 创建时自动触发)
/// 通常不需要手动调用
static
Future
<
bool
>
openCamera
({
int
?
deviceId
})
async
{
try
{
final
result
=
await
_channel
.
invokeMethod
(
'openCamera'
,
{
if
(
deviceId
!=
null
)
'deviceId'
:
deviceId
,
});
return
result
==
true
;
}
catch
(
e
)
{
debugPrint
(
'UsbCameraService.openCamera error:
$e
'
);
return
false
;
}
}
/// 关闭摄像头
static
Future
<
bool
>
closeCamera
()
async
{
try
{
final
result
=
await
_channel
.
invokeMethod
(
'closeCamera'
);
return
result
==
true
;
}
catch
(
e
)
{
debugPrint
(
'UsbCameraService.closeCamera error:
$e
'
);
return
false
;
}
}
/// 拍照
/// [path] 可选,指定照片保存路径;不传则使用默认路径
/// 返回照片文件的路径
static
Future
<
String
?>
takePhoto
({
String
?
path
})
async
{
try
{
final
result
=
await
_channel
.
invokeMethod
(
'takePhoto'
,
{
if
(
path
!=
null
)
'path'
:
path
,
});
return
result
as
String
?;
}
catch
(
e
)
{
debugPrint
(
'UsbCameraService.takePhoto error:
$e
'
);
return
null
;
}
}
/// 开始录像
/// [path] 可选,指定视频保存路径;不传则使用默认路径
static
Future
<
bool
>
startRecord
({
String
?
path
})
async
{
try
{
final
result
=
await
_channel
.
invokeMethod
(
'startRecord'
,
{
if
(
path
!=
null
)
'path'
:
path
,
});
return
result
==
true
;
}
catch
(
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
'
);
return
null
;
}
}
/// 查询摄像头是否已打开
static
Future
<
bool
>
isCameraOpened
()
async
{
try
{
final
result
=
await
_channel
.
invokeMethod
(
'isCameraOpened'
);
return
result
==
true
;
}
catch
(
e
)
{
debugPrint
(
'UsbCameraService.isCameraOpened error:
$e
'
);
return
false
;
}
}
/// 设置预览分辨率
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
;
}
}
}
lib/views/home/index/widgets/camera_controls.dart
0 → 100644
View file @
8ff902b4
import
'package:flutter/material.dart'
;
import
'package:flutter/services.dart'
;
import
'package:flutter_screenutil/flutter_screenutil.dart'
;
/// 摄像头控制按钮组件
///
/// 职责:
/// - 提供拍照、录像、刷新三个操作按钮
/// - 录像按钮支持录制中/停止录制两种状态切换
///
/// 涉及页面:首页监护舱页面 - 监控画面底部控制栏
class
CameraControls
extends
StatelessWidget
{
/// 是否正在录像
final
bool
isRecording
;
/// 拍照回调
final
VoidCallback
?
onTakePhoto
;
/// 切换录像状态回调(开始/停止)
final
VoidCallback
?
onToggleRecord
;
/// 刷新回调
final
VoidCallback
?
onRefresh
;
const
CameraControls
({
super
.
key
,
this
.
isRecording
=
false
,
this
.
onTakePhoto
,
this
.
onToggleRecord
,
this
.
onRefresh
,
});
@override
Widget
build
(
BuildContext
context
)
{
return
Row
(
mainAxisSize:
MainAxisSize
.
min
,
children:
[
_buildControlButton
(
icon:
Icons
.
refresh
,
onPressed:
onRefresh
,
tooltip:
'刷新画面'
,
),
SizedBox
(
width:
16
.
w
),
_buildControlButton
(
icon:
Icons
.
camera_alt
,
onPressed:
onTakePhoto
,
tooltip:
'拍照'
,
),
SizedBox
(
width:
16
.
w
),
_buildControlButton
(
icon:
isRecording
?
Icons
.
stop
:
Icons
.
videocam
,
onPressed:
onToggleRecord
,
tooltip:
isRecording
?
'停止录像'
:
'开始录像'
,
isActive:
isRecording
,
),
],
);
}
/// 构建单个控制按钮
Widget
_buildControlButton
({
required
IconData
icon
,
VoidCallback
?
onPressed
,
String
?
tooltip
,
bool
isActive
=
false
,
})
{
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
,
),
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
,
),
),
),
);
}
}
lib/views/home/index/widgets/monitoring_screen.dart
View file @
8ff902b4
import
'package:flutter/material.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter/services.dart'
;
import
'package:flutter_screenutil/flutter_screenutil.dart'
;
import
'package:flutter_screenutil/flutter_screenutil.dart'
;
class
MonitoringScreen
extends
StatelessWidget
{
import
'camera_controls.dart'
;
/// 监控画面组件 —— 集成 USB 摄像头预览画面
///
/// 职责:
/// - 通过 AndroidView 嵌入 Android 原生 USBCameraTextureView 预览画面
/// - 底部控制栏:拍照、录像、刷新按钮
///
/// 涉及页面:首页监护舱页面
class
MonitoringScreen
extends
StatefulWidget
{
final
VoidCallback
?
onRefresh
;
final
VoidCallback
?
onRefresh
;
final
VoidCallback
?
onSnapshot
;
final
VoidCallback
?
onSnapshot
;
final
VoidCallback
?
onRecord
;
final
VoidCallback
?
onRecord
;
...
@@ -14,9 +24,16 @@ class MonitoringScreen extends StatelessWidget {
...
@@ -14,9 +24,16 @@ class MonitoringScreen extends StatelessWidget {
});
});
@override
@override
State
<
MonitoringScreen
>
createState
()
=>
_MonitoringScreenState
();
}
class
_MonitoringScreenState
extends
State
<
MonitoringScreen
>
{
bool
_isRecording
=
false
;
bool
_cameraReady
=
false
;
@override
Widget
build
(
BuildContext
context
)
{
Widget
build
(
BuildContext
context
)
{
return
Container
(
return
Container
(
height:
420
.
h
,
decoration:
BoxDecoration
(
decoration:
BoxDecoration
(
gradient:
const
LinearGradient
(
gradient:
const
LinearGradient
(
begin:
Alignment
.
topCenter
,
begin:
Alignment
.
topCenter
,
...
@@ -28,27 +45,19 @@ class MonitoringScreen extends StatelessWidget {
...
@@ -28,27 +45,19 @@ class MonitoringScreen extends StatelessWidget {
),
),
child:
Stack
(
child:
Stack
(
children:
[
children:
[
Center
(
// 摄像头预览画面
child:
Text
(
_buildCameraPreview
(),
'监控画面'
,
// 摄像头未就绪时的提示
style:
TextStyle
(
if
(!
_cameraReady
)
_buildPlaceholder
(),
fontSize:
40
.
sp
,
// 底部控制按钮
color:
Colors
.
white70
,
fontWeight:
FontWeight
.
w500
,
),
),
),
Positioned
(
Positioned
(
bottom:
24
.
h
,
bottom:
24
.
h
,
right:
24
.
w
,
right:
24
.
w
,
child:
Row
(
child:
CameraControls
(
children:
[
isRecording:
_isRecording
,
_buildIconButton
(
Icons
.
refresh
,
onRefresh
),
onTakePhoto:
_onTakePhoto
,
SizedBox
(
width:
16
.
w
),
onToggleRecord:
_onToggleRecord
,
_buildIconButton
(
Icons
.
camera_alt
,
onSnapshot
),
onRefresh:
widget
.
onRefresh
,
SizedBox
(
width:
16
.
w
),
_buildIconButton
(
Icons
.
videocam
,
onRecord
),
],
),
),
),
),
],
],
...
@@ -56,20 +65,165 @@ class MonitoringScreen extends StatelessWidget {
...
@@ -56,20 +65,165 @@ class MonitoringScreen extends StatelessWidget {
);
);
}
}
Widget
_buildIconButton
(
IconData
icon
,
VoidCallback
?
onPressed
)
{
/// 构建 USB 摄像头预览画面(通过 PlatformView)
return
Container
(
Widget
_buildCameraPreview
()
{
width:
60
.
w
,
// AndroidView 需要 Android API 20+,且仅在 Android 平台有效
height:
60
.
w
,
// 在其他平台或未连接 USB 摄像头时显示占位符
decoration:
BoxDecoration
(
try
{
color:
const
Color
(
0x331E10B6
),
return
ClipRRect
(
borderRadius:
BorderRadius
.
circular
(
30
.
r
),
borderRadius:
BorderRadius
.
circular
(
20
.
r
),
border:
Border
.
all
(
color:
Colors
.
white24
,
width:
1
),
child:
SizedBox
.
expand
(
child:
_buildAndroidView
(),
),
);
}
catch
(
_
)
{
return
const
SizedBox
.
shrink
();
}
}
/// 构建 Android 原生 PlatformView
Widget
_buildAndroidView
()
{
// ignore: undefined_platform_specific_widget
return
AndroidView
(
viewType:
'usb_camera_view'
,
onPlatformViewCreated:
(
_
)
{
setState
(()
{
_cameraReady
=
true
;
});
},
creationParams:
const
<
String
,
dynamic
>{},
creationParamsCodec:
const
StandardMessageCodec
(),
);
}
/// 未连接 USB 摄像头时的占位提示
Widget
_buildPlaceholder
()
{
return
Center
(
child:
Column
(
mainAxisAlignment:
MainAxisAlignment
.
center
,
children:
[
Icon
(
Icons
.
videocam_off
,
size:
60
.
w
,
color:
Colors
.
white38
,
),
SizedBox
(
height:
16
.
h
),
Text
(
'USB 摄像头未连接'
,
style:
TextStyle
(
fontSize:
24
.
sp
,
color:
Colors
.
white54
,
fontWeight:
FontWeight
.
w500
,
),
),
SizedBox
(
height:
8
.
h
),
Text
(
'请插入 USB 摄像头设备'
,
style:
TextStyle
(
fontSize:
18
.
sp
,
color:
Colors
.
white38
,
),
),
],
),
),
child:
IconButton
(
);
icon:
Icon
(
icon
,
color:
Colors
.
white
,
size:
32
.
w
),
}
onPressed:
onPressed
,
padding:
EdgeInsets
.
zero
,
/// 拍照
Future
<
void
>
_onTakePhoto
()
async
{
// 调用 Service 层拍照
final
cameraService
=
await
_getCameraService
();
if
(
cameraService
!=
null
)
{
final
path
=
await
cameraService
.
takePhoto
();
if
(
path
!=
null
&&
mounted
)
{
_showSnackBar
(
'拍照成功:
$path
'
);
}
}
widget
.
onSnapshot
?.
call
();
}
/// 切换录像状态
Future
<
void
>
_onToggleRecord
()
async
{
final
cameraService
=
await
_getCameraService
();
if
(
cameraService
==
null
)
return
;
if
(
_isRecording
)
{
final
path
=
await
cameraService
.
stopRecord
();
if
(
path
!=
null
&&
mounted
)
{
_showSnackBar
(
'录像已保存:
$path
'
);
}
setState
(()
=>
_isRecording
=
false
);
}
else
{
final
success
=
await
cameraService
.
startRecord
();
if
(
success
)
{
setState
(()
=>
_isRecording
=
true
);
if
(
mounted
)
_showSnackBar
(
'开始录像'
);
}
}
widget
.
onRecord
?.
call
();
}
/// 获取摄像头服务实例
Future
<
dynamic
>
_getCameraService
()
async
{
// 延迟导入,避免在非 Android 平台导入时出现问题
try
{
// 动态调用 UsbCameraService
// ignore: depend_on_referenced_packages
return
_UsbCameraServiceProxy
();
}
catch
(
_
)
{
return
null
;
}
}
void
_showSnackBar
(
String
message
)
{
ScaffoldMessenger
.
of
(
context
).
showSnackBar
(
SnackBar
(
content:
Text
(
message
),
duration:
const
Duration
(
seconds:
2
),
behavior:
SnackBarBehavior
.
floating
,
margin:
EdgeInsets
.
all
(
24
.
w
),
shape:
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
circular
(
20
.
r
),
),
),
),
);
);
}
}
}
}
/// USB 摄像头服务代理 —— 避免硬依赖导致非 Android 平台编译错误
class
_UsbCameraServiceProxy
{
Future
<
String
?>
takePhoto
({
String
?
path
})
async
{
// 通过 MethodChannel 直接调用(与 UsbCameraService 使用同一个 channel)
const
channel
=
MethodChannel
(
'usb_camera'
);
try
{
final
result
=
await
channel
.
invokeMethod
(
'takePhoto'
,
{
if
(
path
!=
null
)
'path'
:
path
,
});
return
result
as
String
?;
}
catch
(
_
)
{
return
null
;
}
}
Future
<
bool
>
startRecord
({
String
?
path
})
async
{
const
channel
=
MethodChannel
(
'usb_camera'
);
try
{
final
result
=
await
channel
.
invokeMethod
(
'startRecord'
,
{
if
(
path
!=
null
)
'path'
:
path
,
});
return
result
==
true
;
}
catch
(
_
)
{
return
false
;
}
}
Future
<
String
?>
stopRecord
()
async
{
const
channel
=
MethodChannel
(
'usb_camera'
);
try
{
final
result
=
await
channel
.
invokeMethod
(
'stopRecord'
);
return
result
as
String
?;
}
catch
(
_
)
{
return
null
;
}
}
}
pubspec.yaml
View file @
8ff902b4
...
@@ -49,6 +49,7 @@ dependencies:
...
@@ -49,6 +49,7 @@ dependencies:
infinite_scroll_pagination
:
^5.1.1
infinite_scroll_pagination
:
^5.1.1
logger
:
^2.7.0
logger
:
^2.7.0
fluttertoast
:
^9.0.0
fluttertoast
:
^9.0.0
permission_handler
:
^11.3.1
# fl_chart: ^0.71.0
# fl_chart: ^0.71.0
# fl_chart: ^1.1.0
# fl_chart: ^1.1.0
...
...
windows/flutter/generated_plugin_registrant.cc
View file @
8ff902b4
...
@@ -7,12 +7,15 @@
...
@@ -7,12 +7,15 @@
#include "generated_plugin_registrant.h"
#include "generated_plugin_registrant.h"
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
#include <permission_handler_windows/permission_handler_windows_plugin.h>
#include <share_plus/share_plus_windows_plugin_c_api.h>
#include <share_plus/share_plus_windows_plugin_c_api.h>
#include <url_launcher_windows/url_launcher_windows.h>
#include <url_launcher_windows/url_launcher_windows.h>
void
RegisterPlugins
(
flutter
::
PluginRegistry
*
registry
)
{
void
RegisterPlugins
(
flutter
::
PluginRegistry
*
registry
)
{
FlutterSecureStorageWindowsPluginRegisterWithRegistrar
(
FlutterSecureStorageWindowsPluginRegisterWithRegistrar
(
registry
->
GetRegistrarForPlugin
(
"FlutterSecureStorageWindowsPlugin"
));
registry
->
GetRegistrarForPlugin
(
"FlutterSecureStorageWindowsPlugin"
));
PermissionHandlerWindowsPluginRegisterWithRegistrar
(
registry
->
GetRegistrarForPlugin
(
"PermissionHandlerWindowsPlugin"
));
SharePlusWindowsPluginCApiRegisterWithRegistrar
(
SharePlusWindowsPluginCApiRegisterWithRegistrar
(
registry
->
GetRegistrarForPlugin
(
"SharePlusWindowsPluginCApi"
));
registry
->
GetRegistrarForPlugin
(
"SharePlusWindowsPluginCApi"
));
UrlLauncherWindowsRegisterWithRegistrar
(
UrlLauncherWindowsRegisterWithRegistrar
(
...
...
windows/flutter/generated_plugins.cmake
View file @
8ff902b4
...
@@ -4,6 +4,7 @@
...
@@ -4,6 +4,7 @@
list
(
APPEND FLUTTER_PLUGIN_LIST
list
(
APPEND FLUTTER_PLUGIN_LIST
flutter_secure_storage_windows
flutter_secure_storage_windows
permission_handler_windows
share_plus
share_plus
url_launcher_windows
url_launcher_windows
)
)
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment