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
cf3b74d6
Commit
cf3b74d6
authored
Jun 17, 2026
by
张宏
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
清除ndk版本错误
parent
8983fa7f
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
224 additions
and
102 deletions
+224
-102
settings.json
.vscode/settings.json
+2
-1
build.gradle
android/app/build.gradle
+4
-1
AndroidManifest.xml
android/app/src/main/AndroidManifest.xml
+1
-0
MainActivity.kt
...pp/src/main/kotlin/com/qialg/laki_icu_app/MainActivity.kt
+0
-39
strings.xml
android/app/src/main/res/values/strings.xml
+1
-1
build.gradle
android/build.gradle
+7
-0
settings.gradle.kts
android/settings.gradle.kts
+32
-0
Info.plist
ios/Runner/Info.plist
+1
-1
usb_camera_service.dart
lib/services/usb_camera_service.dart
+0
-0
camera_controls.dart
lib/views/home/index/widgets/camera_controls.dart
+48
-32
monitoring_screen.dart
lib/views/home/index/widgets/monitoring_screen.dart
+126
-26
login_view.dart
lib/views/login/login_view.dart
+1
-1
pubspec.yaml
pubspec.yaml
+1
-0
No files found.
.vscode/settings.json
View file @
cf3b74d6
{
"dart.flutterSdkPath"
:
".fvm/versions/3.41.9"
,
"java.configuration.updateBuildConfiguration"
:
"
interactive
"
"java.configuration.updateBuildConfiguration"
:
"
disabled
"
}
\ No newline at end of file
android/app/build.gradle
View file @
cf3b74d6
...
...
@@ -24,7 +24,7 @@ if (flutterVersionName == null) {
android
{
namespace
"com.qialg.laki_icu_app"
compileSdk
Version
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"
...
...
android/app/src/main/AndroidManifest.xml
View file @
cf3b74d6
...
...
@@ -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"
...
...
android/app/src/main/kotlin/com/qialg/laki_icu_app/MainActivity.kt
View file @
cf3b74d6
...
...
@@ -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()
// }
// }
android/app/src/main/res/values/strings.xml
View file @
cf3b74d6
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string
name=
"app_name"
>
智能酒店
</string>
<string
name=
"app_name"
>
Laki_icu
</string>
</resources>
android/build.gradle
View file @
cf3b74d6
...
...
@@ -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
)
{
...
...
android/settings.gradle.kts
View file @
cf3b74d6
...
...
@@ -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")
}
}
}
ios/Runner/Info.plist
View file @
cf3b74d6
...
...
@@ -7,7 +7,7 @@
<
k
e
y
>
CFBundleDevelopmentRegion
<
/k
e
y
>
<
string
>
$
(
DEVELOPMENT_LANGUAGE
)<
/string
>
<
k
e
y
>
CFBundleDisplayName
<
/k
e
y
>
<
string
>
智能酒店
<
/string
>
<
string
>
Laki_icu
<
/string
>
<
k
e
y
>
CFBundleExecutable
<
/k
e
y
>
<
string
>
$
(
EXECUTABLE_NAME
)<
/string
>
<
k
e
y
>
CFBundleIdentifier
<
/k
e
y
>
...
...
lib/services/usb_camera_service.dart
View file @
cf3b74d6
This diff is collapsed.
Click to expand it.
lib/views/home/index/widgets/camera_controls.dart
View file @
cf3b74d6
...
...
@@ -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
,
),
),
),
...
...
lib/views/home/index/widgets/monitoring_screen.dart
View file @
cf3b74d6
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
UsbC
ameraService
.
stopRecord
();
final
path
=
await
_c
ameraService
.
stopRecord
();
if
(
path
!=
null
&&
mounted
)
{
_showSnackBar
(
'录像已保存
:
$path
'
);
_showSnackBar
(
'录像已保存'
);
}
setState
(()
=>
_isRecording
=
false
);
}
else
{
final
success
=
await
UsbC
ameraService
.
startRecord
();
final
success
=
await
_c
ameraService
.
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
(
...
...
lib/views/login/login_view.dart
View file @
cf3b74d6
...
...
@@ -102,7 +102,7 @@ class LoginView extends StatelessWidget {
),
SizedBox
(
height:
40
.
h
),
const
Text
(
'
智能酒店管理系统
'
,
'
Laki icu 智能仓
'
,
style:
TextStyle
(
color:
Color
(
0xFF333333
),
fontSize:
24
,
...
...
pubspec.yaml
View file @
cf3b74d6
...
...
@@ -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
...
...
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