feat(device): 添加USB设备通信支持和程序参数优化

- 在AndroidManifest.xml中添加USB Host权限和设备过滤器配置
- 新增设备控制国际化词条包括速度档位、吹气时间等
- 重构数据库结构将速度相关字段统一为档位数值存储
- 添加通用KV存储方法用于settings表数据读写
- 优化首页导航实现tab间跳转和状态保持功能
- 更新程序详情页面布局和参数表单界面
- 移除模拟运行器相关测试代码
- 添加USB串口通信依赖包usb_serial
This commit is contained in:
Developer
2026-06-04 15:13:36 +08:00
parent 67e2c7c76c
commit d53c41c300
24 changed files with 795 additions and 635 deletions

View File

@@ -7,10 +7,12 @@ import '../../../shared/widgets/common_button.dart';
import '../providers/steps_provider.dart';
import '../widgets/step_list.dart';
import '../widgets/step_form.dart';
import '../../home/widgets/status_bar.dart';
import '../../device/providers/run_state_provider.dart';
import '../../programs/providers/programs_provider.dart';
/// 程序详情页面
/// 左侧步骤列表 + 右侧参数表单
/// 布局:顶部状态栏(含导航tab) + 子工具栏(返回/程序名/保存) + 左侧步骤列表 + 右侧参数表单
class ProgramDetailPage extends ConsumerStatefulWidget {
final String programId;
@@ -35,61 +37,31 @@ class _ProgramDetailPageState extends ConsumerState<ProgramDetailPage> {
final programsState = ref.watch(programsProvider);
final program = programsState.programs.where((p) => p.id == _programIdInt).firstOrNull;
final stepsState = ref.watch(stepsProvider(_programIdInt));
final runState = ref.watch(runStateProvider);
// 详情页从程序管理进入,高亮"程序管理"tab(index=1)
final tabs = <StatusBarTab>[
StatusBarTab(icon: Icons.dashboard, label: l10n?.deviceControl ?? '设备控制'),
StatusBarTab(icon: Icons.list_alt, label: l10n?.programs ?? '程序管理'),
StatusBarTab(icon: Icons.settings, label: l10n?.settings ?? '系统设置'),
];
return Scaffold(
body: Container(
color: AppTheme.backgroundColor,
child: Column(
children: [
// 顶部导航栏
Container(
height: 60,
padding: const EdgeInsets.symmetric(horizontal: 24),
decoration: BoxDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.1),
blurRadius: 4,
offset: const Offset(0, 2),
),
],
),
child: Row(
children: [
// 返回按钮
IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () => context.go('/programs'),
),
const SizedBox(width: 16),
// 程序名称
Text(
program?.name ?? (l10n?.detail ?? '程序详情'),
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.w600,
),
),
const Spacer(),
// 保存按钮
CommonButton(
text: l10n?.save ?? '保存',
icon: Icons.save,
type: ButtonType.primary,
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('已保存'),
backgroundColor: AppTheme.successColor,
),
);
},
),
],
),
// 顶部状态栏(含导航tab),tab点击跳回首页对应 tab
StatusBar(
isRunning: runState.status == RunStatus.running,
tabs: tabs,
currentTabIndex: 1,
onTabChanged: (index) => context.go('/?tab=$index'),
),
// 子工具栏:返回按钮 + 程序名 + 保存按钮
_buildSubToolbar(context, l10n, program?.name),
// 主内容区域
Expanded(
child: stepsState.isLoading
@@ -172,6 +144,57 @@ class _ProgramDetailPageState extends ConsumerState<ProgramDetailPage> {
);
}
/// 子工具栏:返回按钮 + 程序名 + 保存按钮
/// 位于状态栏下方,提供详情页特有的操作入口
Widget _buildSubToolbar(BuildContext context, AppLocalizations? l10n, String? programName) {
return Container(
height: 56,
padding: const EdgeInsets.symmetric(horizontal: 24),
decoration: BoxDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.06),
blurRadius: 4,
offset: const Offset(0, 2),
),
],
),
child: Row(
children: [
IconButton(
icon: const Icon(Icons.arrow_back),
tooltip: l10n?.backToHome ?? '返回',
onPressed: () => context.go('/?tab=1'),
),
const SizedBox(width: 8),
Text(
programName ?? (l10n?.detail ?? '程序详情'),
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: AppTheme.textHeading,
),
),
const Spacer(),
CommonButton(
text: l10n?.save ?? '保存',
icon: Icons.save,
type: ButtonType.primary,
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('已保存'),
backgroundColor: AppTheme.successColor,
),
);
},
),
],
),
);
}
/// 显示添加步骤对话框
void _showAddStepDialog(BuildContext context, WidgetRef ref) {
showDialog(

View File

@@ -34,9 +34,7 @@ class _StepFormState extends State<StepForm> {
late TextEditingController _blowTimeController;
String _position = 'A1';
String _mixSpeed = '中速';
String _blowSpeed = '中速';
int _needleSpeed = 5;
int _speed = 5;
@override
void initState() {
@@ -48,9 +46,7 @@ class _StepFormState extends State<StepForm> {
_blowTimeController = TextEditingController(text: '${widget.step?.blowTime ?? 0}');
_position = widget.step?.position ?? 'A1';
_mixSpeed = widget.step?.mixSpeed ?? '中速';
_blowSpeed = widget.step?.blowSpeed ?? '中速';
_needleSpeed = widget.step?.needleSpeed ?? 5;
_speed = widget.step?.speed ?? 5;
}
@override
@@ -179,53 +175,22 @@ class _StepFormState extends State<StepForm> {
),
const SizedBox(height: 16),
// 速度选择
Row(
children: [
Expanded(
child: DropdownButtonFormField<String>(
value: _mixSpeed,
decoration: InputDecoration(
labelText: l10n?.mixSpeed ?? '混合速度',
border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)),
),
items: Constants.speedOptions.map((s) => DropdownMenuItem(value: s, child: Text(s))).toList(),
onChanged: (value) {
if (value != null) setState(() => _mixSpeed = value);
},
),
),
const SizedBox(width: 16),
Expanded(
child: DropdownButtonFormField<String>(
value: _blowSpeed,
decoration: InputDecoration(
labelText: l10n?.blowSpeed ?? '吹气速度',
border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)),
),
items: Constants.speedOptions.map((s) => DropdownMenuItem(value: s, child: Text(s))).toList(),
onChanged: (value) {
if (value != null) setState(() => _blowSpeed = value);
},
),
),
],
),
const SizedBox(height: 16),
// 下针速度滑块
// 速度滑块
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('${l10n?.needleSpeed ?? '下针速度'}: $_needleSpeed', style: TextStyle(color: AppTheme.textPrimary)),
Text(
'${l10n?.speed ?? '速度'}: $_speed',
style: TextStyle(color: AppTheme.textPrimary),
),
Slider(
value: _needleSpeed.toDouble(),
value: _speed.toDouble(),
min: 1,
max: 10,
divisions: 9,
activeColor: AppTheme.primaryColor,
onChanged: (value) {
setState(() => _needleSpeed = value.round());
setState(() => _speed = value.round());
},
),
],
@@ -259,10 +224,8 @@ class _StepFormState extends State<StepForm> {
mixTime: int.tryParse(_mixTimeController.text) ?? 0,
magnetTime: int.tryParse(_magnetTimeController.text) ?? 0,
volume: int.tryParse(_volumeController.text) ?? 0,
mixSpeed: _mixSpeed,
blowSpeed: _blowSpeed,
blowTime: int.tryParse(_blowTimeController.text) ?? 0,
needleSpeed: _needleSpeed,
speed: _speed,
);
widget.onSave(step);