Files
feature-next-arch/lib/features/home/presentation/widgets/ImmersionHeader.dart
2026-08-07 08:49:29 +08:00

493 lines
18 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

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:cc_ui_kit/cc_ui_kit.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:maibu_satabot_v2/core/localization/app_localizations.dart';
import 'package:maibu_satabot_v2/features/devices/domain/entities/device_entity.dart';
import 'package:maibu_satabot_v2/features/home/presentation/widgets/common/commonFn.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../../../core/app/app_user_cubit.dart';
import '../../../../core/router/route_paths.dart';
import '../../../devices/presentation/bloc/devices_cubit.dart';
import '../../../devices/presentation/bloc/devices_state.dart';
import '../../../remote_control/presentation/bloc/remote_control_cubit.dart'; // 🔥 添加 RemoteControlCubit
// 导入你的主题文件以获取 offWhite
// import 'package:maibu_satabot_v2/core/theme/app_theme.dart';
class ImmersionHeader extends StatelessWidget {
final DeviceEntity device;
const ImmersionHeader({super.key, required this.device});
String _formatName(String name, {int keepLength = 20}) {
if (name.length <= keepLength) return name;
return name.substring(name.length - keepLength);
}
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
height: 310,
// 1. 修改这里:去掉 color: Colors.white,改用透明或背景色
decoration: const BoxDecoration(
color: Colors.transparent, // 设为透明,直接透出 Scaffold 的 offWhite
),
child: Stack(
children: [
// 背景文字占位 (SataBot)
Positioned(
top: 130,
left: 0,
right: 0,
child: Text(
'SataBot',
textAlign: TextAlign.center,
style: GoogleFonts.roboto(
fontSize: 80,
fontWeight: FontWeight.w900,
// 2. 既然背景变深了,背景字可以稍微再淡一点点
color: Colors.black.withOpacity(0.08),
),
),
),
// 机器大图
Transform.translate(
offset: const Offset(30, 80), // 正数向右移动(例如 20 像素),负数向左
child: Center(
child: Image.asset(
'assets/images/car.png',
width: 330,
height: 190,
fit: BoxFit.contain,
),
),
),
// 顶部状态信息
SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 10),
child: Row(
// 1. 依然保持顶部对齐
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// --- 左侧:标题 + 进度条 + 状态标签 (封装成一个 Column) ---
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min, // 尽可能收缩高度
children: [
// 设备名称和电量
Text(
_formatName(device.displayName),
style: GoogleFonts.roboto(
fontSize: 22,
fontWeight: FontWeight.w900,
),
),
const SizedBox(height: 4),
Row(
children: [
Text(
'${device.onlineStatus == 1 ? "100" : "--"} %',
style: GoogleFonts.roboto(
fontSize: 24,
fontWeight: FontWeight.w900,
),
),
const Icon(
Icons.chevron_right,
size: 20,
color: Colors.grey,
),
],
),
const SizedBox(height: 8),
// 2. 进度条现在紧跟在电量下面,不会被右侧挤走
_buildProgressBar(),
const SizedBox(height: 12),
// 3. 状态标签也紧跟其后
_buildStatusTag(device.isOnline, context),
],
),
),
// --- 右侧:图标栏 ---
Column(
mainAxisSize: MainAxisSize.min,
children: [
_buildCircleIcon(Icons.sync, () {
// 1. 触发 Cubit 请求最新设备列表
final username =
context.read<AppUserCubit>().state.user?.username ??
"";
// 🔥 修复:检查是否已有 targetDevice
final remoteControlState = context.read<RemoteControlCubit>().state;
if (remoteControlState.targetDevice == null) {
debugPrint('🔄 [ImmersionHeader] 无 targetDevice,开始加载设备列表');
context.read<DevicesCubit>().fetchAllDevices(username);
} else {
debugPrint('✅ [ImmersionHeader] 已有 targetDevice: ${remoteControlState.targetDevice!.deviceName},跳过加载');
}
// 2. 弹出窗口(窗口内部会根据状态显示转圈或列表)
_showDeviceSwitcher(context);
}),
const SizedBox(height: 20), // 这里你改大的间距,只会让两个图标拉开
// TCP状态指示灯 - 暂时关闭
// Container(
// padding: const EdgeInsets.all(8),
// decoration: BoxDecoration(
// shape: BoxShape.circle,
// color: Colors.blue.withOpacity(0.1),
// ),
// child: const TcpStatusIndicator(size: 14),
// ),
],
),
],
),
),
),
],
),
);
}
Widget _buildStatusTag(bool isOnline, BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
decoration: BoxDecoration(
// 在 offWhite 背景下,标签用纯白色会显得更精致
color: Colors.white,
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(color: Colors.black.withOpacity(0.02), blurRadius: 4),
],
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
CircleAvatar(
radius: 3,
backgroundColor: isOnline ? Colors.green : Colors.grey,
),
const SizedBox(width: 6),
Text(
isOnline
? AppLocalizations.of(context).translate('home.device_online')
: AppLocalizations.of(context).translate('home.device_offline'),
style: const TextStyle(fontSize: 12, color: Colors.black54),
overflow: TextOverflow.ellipsis,
),
],
),
);
}
// 辅助方法:进度条
Widget _buildProgressBar() {
return Container(
width: 120,
height: 6,
decoration: BoxDecoration(
color: Colors.blueAccent,
borderRadius: BorderRadius.circular(5),
),
);
}
Widget _buildCircleIcon(
IconData icon,
VoidCallback? voidCallback, { // 将回调函数放在这里
bool isAccent = false,
}) {
return GestureDetector(
onTap: voidCallback, // 绑定点击事件
child: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: isAccent ? Colors.blue.withOpacity(0.1) : Colors.white,
boxShadow: [
if (!isAccent)
BoxShadow(
color: Colors.black.withOpacity(0.02),
blurRadius: 4,
offset: const Offset(0, 2), // 稍微增加偏移感
),
],
),
child: Icon(
icon,
size: 30,
color: isAccent ? Colors.blue : Colors.black54,
),
),
);
}
/// 设备切换窗口
void _showDeviceSwitcher(BuildContext context) {
showModalBottomSheet(
context: context,
isScrollControlled: true,
useRootNavigator: true, // 💡 解决被三段导航栏遮挡的问题
backgroundColor: Colors.transparent,
builder: (modalContext) {
return Container(
height: MediaQuery.of(context).size.height * 0.75,
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
),
child: Column(
children: [
// 优化的控制条
Container(
margin: const EdgeInsets.symmetric(vertical: 12),
width: 36,
height: 5,
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: BorderRadius.circular(10),
),
),
Text(
AppLocalizations.of(
modalContext,
).translate('home.switch_device'),
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 10),
Container(
height: 0.5,
margin: const EdgeInsets.symmetric(horizontal: 20),
color: Colors.grey.withOpacity(0.1),
),
// 💡 动态内容区
Expanded(
child: BlocBuilder<DevicesCubit, DevicesState>(
builder: (context, state) {
// 1. 如果正在加载(查询或切换中),显示转圈
if (state.isLoading) {
return Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
const CircularProgressIndicator(color: Colors.blue),
const SizedBox(height: 16),
Text(
AppLocalizations.of(
context,
).translate('home.loading'),
style: TextStyle(color: Colors.grey[600]),
),
],
),
);
}
// 2. 列表展示
if (state.devices.isEmpty) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
Icons.description_outlined,
size: 60,
color: Colors.grey,
),
const SizedBox(height: 16),
Text(
AppLocalizations.of(
context,
).translate('home.no_devices'),
style: const TextStyle(
fontSize: 16,
color: Colors.grey,
),
),
],
),
);
}
return ListView.builder(
// 💡 底部留出安全距离,防止最后一条滚不上来
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).padding.bottom + 20,
top: 10,
),
itemCount: state.devices.length,
itemBuilder: (context, index) {
// 传入当前选中的 ID 方便做 UI 区分
final isSelected =
state.selectedDevice?.deviceName ==
state.devices[index].deviceName;
return _buildDeviceItem(
state.devices[index],
context,
isSelected,
);
},
);
},
),
),
],
),
);
},
);
}
Widget _buildDeviceItem(
DeviceEntity device,
BuildContext context,
bool isSelected,
) {
return GestureDetector(
onTap: () {
print("跳转到详情页: ${device.deviceAlias}");
// TODO: Navigator.push(...)
},
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white,
// 💡 如果是当前选中设备,边框颜色略深
border: Border.all(
color: isSelected
? Colors.blue.withOpacity(0.5)
: Colors.grey.withOpacity(0.3),
width: isSelected ? 1.5 : 1,
),
borderRadius: BorderRadius.circular(12),
boxShadow: [
if (isSelected)
BoxShadow(color: Colors.blue.withOpacity(0.05), blurRadius: 4),
],
),
child: Row(
children: [
// 设备图片
Transform.translate(
offset: const Offset(10, 0),
child: Image.asset(
'assets/images/car.png',
width: 100,
height: 80,
),
),
const SizedBox(width: 12),
// 设备信息
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
Icons.circle,
color: device.isOnline ? Colors.green : Colors.grey,
size: 12,
),
const SizedBox(width: 4),
Flexible(
child: Text(
(device.deviceAlias?.trim() ?? '').isEmpty
? AppLocalizations.of(
context,
).translate('home.unknown_device')
: device.deviceAlias!,
style: const TextStyle(fontWeight: FontWeight.bold),
// 关键属性:处理文本溢出
maxLines: 2, // 最多显示2行
overflow: TextOverflow.ellipsis, // 超出显示省略号
softWrap: true, // 允许换行
),
),
],
),
const SizedBox(height: 6),
Text(
AppLocalizations.of(
context,
).translate('home.click_to_view'),
style: const TextStyle(color: Colors.grey, fontSize: 12),
overflow: TextOverflow.ellipsis,
),
],
),
),
// 按钮组
Column(
children: [
CCPrimaryImageButton(
onPressed: isSelected
? null
: () async {
// 💡 执行切换逻辑
await context.read<DevicesCubit>().switchDevice(
device,
);
await LocationUtils.clearAllPlotCache();
// 切换成功后,UI 会自动更新(因为 BlocBuilder 在监听),我们可以关闭弹窗
if (context.mounted) {
Navigator.pop(context);
}
},
text: isSelected
? AppLocalizations.of(context).translate('home.in_use')
: AppLocalizations.of(context).translate('home.switch'),
width: 90,
height: 30,
fontSize: 14,
// 如果是当前设备,按钮颜色变灰
backgroundColor: isSelected ? Colors.grey : Colors.black,
),
const SizedBox(height: 8),
CCPrimaryButton(
onPressed: () {
context.push(
RoutePaths.machineDetails,
extra: device,
); // 把当前点击的这个设备对象传过去
print("通过按钮进入详情,设备名称:$device");
},
text: AppLocalizations.of(context).translate('home.details'),
width: 90,
height: 30,
fontSize: 14,
backgroundColor: Colors.white,
textColor: Colors.black,
),
],
),
],
),
),
);
}
}