Files
flutterApp/lib/features/remote_control/presentation/widgets/top_status_bar.dart
Songzex 3c3b2ce4ee 修复远程遥控的顶部的布局优化,
完成实现远程遥控的电压和电量和模式实时从tcp获取和展示
2026-03-25 15:09:11 +08:00

427 lines
15 KiB
Dart
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart';
import 'package:go_router/go_router.dart';
import 'package:maibu_satabot_v2/features/remote_control/presentation/bloc/remote_control_state.dart';
import 'package:maibu_satabot_v2/features/remote_control/presentation/widgets/status_chip.dart';
import 'package:cc_ui_kit/cc_ui_kit.dart';
import '../../../../core/di/injection.dart';
import '../../../../core/logging/i_logger_service.dart';
import '../../../devices/presentation/bloc/devices_cubit.dart';
import '../../data/models/running_status_model.dart';
import '../bloc/remote_control_cubit.dart';
class TopStatusBar extends StatelessWidget {
const TopStatusBar({super.key});
@override
Widget build(BuildContext context) {
// 监听全局设备状态
final deviceState = context.watch<DevicesCubit>().state;
final device = deviceState.selectedDevice;
var _remoteControlCubit = context.read<RemoteControlCubit>();
ControlMode _parseControlMode(String modeString) {
switch (modeString.toUpperCase()) {
case 'BLE':
return ControlMode.BLE;
case 'LOCAL':
return ControlMode.LOCAL;
case 'TCP':
return ControlMode.TCP;
case 'NONE':
return ControlMode.NONE;
case 'OTHER':
return ControlMode.OTHER;
default:
return ControlMode.NONE;
}
}
// 监听局部遥控状态
final remoteState = context.watch<RemoteControlCubit>().state;
return Padding(
padding: const EdgeInsets.fromLTRB(1, 12, 1, 12),
child: Row(
children: [
// 1. 返回按钮 (对应 SimpleSmallFunctionButton)
_buildIconButton("assets/svgs/remote_back.svg", () => context.pop()),
const SizedBox(width: 8),
// 2. 控制状态 (对应 StatusChipLeft)
StatusChip(
text: remoteState.hasPermission ? "正在控制" : "未在控制",
color: remoteState.hasPermission
? const Color(0xFF1DB954)
: Colors.red,
icon: Icons.eighteen_mp,
breathing: !remoteState.hasPermission,
onTap: () {
if (!remoteState.hasPermission) {
// 弹出请求权限对话框逻辑
context.read<RemoteControlCubit>().togglePermissionDialog(true);
}
},
),
const SizedBox(width: 8),
// 3. 锁定状态 (对应 SmallFunctionButton)
_buildIconButton(
remoteState.isLocked
? "assets/svgs/remote_lock.svg"
: "assets/svgs/remote_lock_open.svg",
() => context.read<RemoteControlCubit>().toggleLock(),
isSelected: remoteState.isLocked,
),
const SizedBox(width: 8),
// 4. 刷新按钮
_buildIconButton("assets/svgs/remote_refresh.svg", () {}),
// const SizedBox(width: 8),
// 5. 火技能按钮 (对应 SmallFunctionButton)
//_buildIconButton("assets/svgs/fire.svg", () {}),
_buildSliderBox("火技能按钮", false,context),
const Spacer(),
_buildExpandIconButton(
iconPath: "assets/svgs/remote_expand.svg",
selectedIconPath: "assets/svgs/remote_unexpand.svg",
isSelected: remoteState.topRightIsExpanded,
onTap: (){
_remoteControlCubit.toggleTopLeftExpand();
},
),
AnimatedSize(
duration: const Duration(milliseconds: 100),
curve: Curves.easeInOut,
child: Row(
children: !remoteState.topRightIsExpanded
? [
const SizedBox(width: 8),
_buildSwitchIconButton(
iconPath: "assets/svgs/remote_recognition_off.svg",
selectedIconPath: "assets/svgs/remote_recognition_on.svg",
isSelected: remoteState.obstacleRecognitionFlag,
onTap: (){
_remoteControlCubit.toggleObstacleRecognition();
},
),
const SizedBox(width: 8),
_buildSwitchIconButton(
iconPath: "assets/svgs/remote_video_left_off.svg",
selectedIconPath: "assets/svgs/remote_video_left.svg",
isSelected: remoteState.showLeftPip,
onTap: (){
_remoteControlCubit.toggleLeftPip();
},
),
const SizedBox(width: 8),
_buildSwitchIconButton(
iconPath: "assets/svgs/remote_video_right_off.svg",
selectedIconPath: "assets/svgs/remote_video_right.svg",
isSelected: remoteState.showRightPip,
onTap: (){
_remoteControlCubit.toggleRightPip();
},
),
const SizedBox(width: 8),
_buildControlModeChip(remoteState.runningStatusModel.controlMode),
const SizedBox(width: 8),
_buildVoltageChip(remoteState.runningStatusModel.voltage),
const SizedBox(width: 8),
_buildPingChip(remoteState.ping),
const SizedBox(width: 8),
]
: [], // 折叠时数组为空
),
),
// TODO
// _buildControlModeChip( _parseControlMode(remoteState.runningStatusModel.controlMode)),
// const SizedBox(width: 8),
//_buildVoltageChip(remoteState.voltage),
// const SizedBox(width: 8),
// 5. 信号延迟 (对应 pingStatusChip)
//_buildPingChip(remoteState.ping),
// const SizedBox(width: 8),
// 6. 电量 (对应 StatusChipRight)
_buildBatteryChip(remoteState.battery),
const SizedBox(width: 8),
],
),
);
}
Widget _buildIconButton(
String svgAsset, // 改为 String 路径,例如 'assets/icons/stop.svg'
VoidCallback onTap, {
bool isSelected = false,
}) {
return InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(10),
child: Container(
width: 32,
height: 32,
decoration: BoxDecoration(
color: isSelected
? const Color(0xCC0078D4)
: Colors.grey.withOpacity(0.4),
borderRadius: BorderRadius.circular(10),
border: Border.all(color: Colors.white.withOpacity(0.3), width: 0.5),
),
padding: const EdgeInsets.all(7),
child: SvgPicture.asset(
svgAsset,
// 这里可以使用 colorFilter 来改变 SVG 的颜色(如果需要根据选中状态变色)
colorFilter: const ColorFilter.mode(Colors.white, BlendMode.srcIn),
),
),
);
}
Widget _buildSwitchIconButton({
required String iconPath, // 默认图标路径
required String selectedIconPath, // 选中后的图标路径
required VoidCallback onTap, // 点击事件
bool isSelected = false, // 是否选中状态
double size = 32, // 按钮尺寸
}) {
return InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(10),
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
// 增加颜色切换动画
width: size,
height: size,
decoration: BoxDecoration(
// 选中时为深蓝色,未选中时为半透明灰色
color: isSelected
? const Color(0xFF0078D4)
: Colors.grey.withOpacity(0.4),
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: isSelected
? Colors.transparent
: Colors.white.withOpacity(0.3),
width: 0.5,
),
boxShadow: isSelected
? [
BoxShadow(
color: const Color(0xFF0078D4).withOpacity(0.4),
blurRadius: 8,
offset: const Offset(0, 2),
),
]
: null,
),
padding: const EdgeInsets.all(7),
child: SvgPicture.asset(
// 根据状态切换图片
isSelected ? selectedIconPath : iconPath,
colorFilter: const ColorFilter.mode(Colors.white, BlendMode.srcIn),
),
),
);
}
Widget _buildPingChip(int ping) {
Color color = ping < 100
? Colors.green
: (ping < 200 ? Colors.orange : Colors.red);
return StatusChip(
text: "$ping ms",
color: color.withValues(alpha: 0.6),
svgColor: color,
icon: Icons.network_check,
);
}
Widget _buildControlModeChip(String mode) {
String displayText;
if(mode == ''){
displayText = '无模式';
}else{
displayText = mode;
}
// switch (mode) {
// case "蓝牙模式":
// displayText = '蓝牙模式';
// break;
// case "本地模式":
// displayText = '本地模式';
// break;
// case "TCP模式":
// displayText = 'TCP模式';
// break;
// case "":
// displayText = '无模式';
// break;
// case "其他模式":
// displayText = '其他模式';
// break;
// default:
// displayText = '未知模式';
// }
return StatusChip(
text: displayText,
color: Colors.blue.withValues(alpha: 0.6),
svgColor: Colors.blue,
svgPath: "assets/svgs/remote_net.svg",
);
}
Widget _buildExpandIconButton({
required String iconPath, // 默认图标路径
required String selectedIconPath, // 选中后的图标路径
required VoidCallback onTap, // 点击事件
bool isSelected = false, // 是否选中状态
double size = 28, // 按钮尺寸
}) {
return InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(10),
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
// 增加颜色切换动画
width: size,
height: size,
decoration: BoxDecoration(
// 选中时为深蓝色,未选中时为半透明灰色
color: Colors.grey.withOpacity(0.4),
borderRadius: BorderRadius.circular(15),
border: Border.all(
color: Colors.white.withOpacity(0.3),
width: 0.5,
),
boxShadow: null,
),
padding: const EdgeInsets.all(7),
child: SvgPicture.asset(
// 根据状态切换图片
isSelected ? selectedIconPath : iconPath,
colorFilter: const ColorFilter.mode(Colors.white, BlendMode.srcIn),
),
),
);
}
Widget _buildVoltageChip(String voltage) {
return StatusChip(
text: "$voltage V",
color: Color(0xFFFACC15).withValues(alpha: 0.6),
svgColor: Color(0xFFFACC15),
svgPath: "assets/svgs/remote_zap.svg",
);
}
Widget _buildBatteryChip(int level) {
// 1. 根据数值判断图片路径 (逻辑与之前一致)
String svgPath;
Color color;
if (level >= 90) {
svgPath = "assets/svgs/remote_battery_full.svg";
color = Colors.green;
} else if (level >= 50) {
svgPath = "assets/svgs/remote_battery_middle.svg";
color = Colors.greenAccent;
} else if (level >= 20) {
svgPath = "assets/svgs/remote_battery_low.svg";
color = Colors.orange;
} else if (level >= 10) {
svgPath = "assets/svgs/remote_battery_warning.svg";
color = Colors.redAccent;
} else {
svgPath = "assets/svgs/remote_battery_none.svg";
color = Colors.red;
}
// 2. 使用 StatusChip 包装,但 icon 传入 SvgPicture
return StatusChip(
text: "$level%",
color: color.withValues(alpha: 0.5),
svgColor: color,
// 如果你的 StatusChip 已经改为接受 Widget,则直接传 SvgPicture
// 如果 StatusChip 目前只接受 IconData,你需要按我之前的建议给它加个 leading 参数
svgPath: svgPath,
);
}
Widget _buildSliderBox(String label, bool isLeft, BuildContext context) {
// 1. 核心尺寸调整:足够宽的容器解决拥挤,高度匹配状态栏
const double boxWidth = 66; // 水平宽度放大,容纳左右图标
const double boxHeight = 32; // 高度和其他按钮保持一致
const double iconSize = 20; // 图标尺寸放大,避免过小拥挤
return SizedBox(
width: boxWidth,
height: boxHeight,
child: Center(
// 2. 关键:旋转组件将竖向CCExpandSlider转为水平(无direction参数的替代方案)
child: RotatedBox(
quarterTurns: 1, // 旋转90度(竖向→横向),若方向反了可改为3
child: SizedBox(
// 旋转后宽高互换,这里给CCExpandSlider足够的显示空间
width: boxHeight, // 旋转后对应原高度
height: boxWidth, // 旋转后对应原宽度(足够长,不拥挤)
child: CCExpandSlider(
label: " ",
// 3. 图标适配旋转后的方向(仍用上下箭头,旋转后变为左右)因为外边旋转了 90度 参数传递也要转换
svgStart: "assets/svgs/remote_flame_minus.svg", // 旋转后→左箭头
svgCenter: "assets/svgs/remote_flame.svg",
svgEnd: "assets/svgs/remote_flame_plus.svg", // 旋转后→右箭头
// 4. 放大图标尺寸,解决拥挤
iconSize: iconSize,
// 保留原有业务逻辑
onStart: () => _handleSliderAction(label, "start", context),
onCenter: () => _handleSliderAction(label, "center", context),
onEnd: () => _handleSliderAction(label, "end", context),
),
),
),
),
);
}
// 🔥 新增:统一的业务逻辑分发方法
void _handleSliderAction(String label, String action, BuildContext context) {
debugPrint('🎯 [Slider 业务] label: $label, action: $action');
// 🔥 根据 label 区分不同的设备
_handleChassisAction(action, context);
}
// 🔥 点火的业务逻辑(左滑块)
void _handleChassisAction(String action, BuildContext context) {
final cubit = context.read<RemoteControlCubit>();
switch (action) {
case "start":
debugPrint('2熄火');
// TODO: 发送 🔥点火指令
cubit.sendFireCommand(2);
break;
case "center":
debugPrint('️0熄火');
// TODO: 发送点火停止指令
cubit.sendFireCommand(0);
break;
case "end":
debugPrint('1点火)');
// TODO: 发送熄火后退指令
cubit.sendFireCommand(1);
break;
}
}
}