Files
flutterApp/lib/features/home/presentation/widgets/ImmersionHeader.dart
2026-01-27 18:44:49 +08:00

411 lines
14 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:google_fonts/google_fonts.dart';
import 'package:maibu_satabot_v2/features/devices/domain/entities/device_entity.dart';
import '../../../auth/presentation/bloc/app_user_cubit.dart';
import '../../../devices/presentation/bloc/devices_cubit.dart';
import '../../../devices/presentation/bloc/devices_state.dart';
// 导入你的主题文件以获取 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});
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
height: 380,
// 1. 修改这里:去掉 color: Colors.white,改用透明或背景色
decoration: const BoxDecoration(
color: Colors.transparent, // 设为透明,直接透出 Scaffold 的 offWhite
),
child: Stack(
children: [
// 背景文字占位 (SataBot)
Positioned(
top: 140,
left: 0,
right: 0,
child: Text(
'SataBot',
textAlign: TextAlign.center,
style: GoogleFonts.roboto(
fontSize: 100,
fontWeight: FontWeight.w900,
// 2. 既然背景变深了,背景字可以稍微再淡一点点
color: Colors.black.withOpacity(0.08),
),
),
),
// 机器大图
Transform.translate(
offset: const Offset(20, 90), // 正数向右移动(例如 20 像素),负数向左
child: Center(
child: Image.asset(
'assets/images/car.png',
width: 380,
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(
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),
],
),
),
// --- 右侧:图标栏 ---
Column(
mainAxisSize: MainAxisSize.min,
children: [
_buildCircleIcon(Icons.sync, () {
// 1. 触发 Cubit 请求最新设备列表
// 假设你的 username 存储在 AuthCubit 或类似的全局状态中
final username =
context.read<AppUserCubit>().state.user?.username ??
"";
context.read<DevicesCubit>().fetchAllDevices(username);
// 2. 弹出窗口(窗口内部会根据状态显示转圈或列表)
_showDeviceSwitcher(context);
}),
const SizedBox(height: 20), // 这里你改大的间距,只会让两个图标拉开
_buildCircleIcon(Icons.bluetooth, null, isAccent: true),
],
),
],
),
),
),
],
),
);
}
Widget _buildStatusTag(bool isOnline) {
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 ? "在线" : "离线",
style: const TextStyle(fontSize: 12, color: Colors.black54),
),
],
),
);
}
// 辅助方法:进度条
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),
),
),
const Text(
"切换设备",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
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(
"处理中...",
style: TextStyle(color: Colors.grey[600]),
),
],
),
);
}
// 2. 列表展示
if (state.devices.isEmpty) {
return const Center(child: Text("暂无可用设备"));
}
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),
Text(
device.deviceAlias ?? '未知设备',
style: const TextStyle(fontWeight: FontWeight.bold),
),
],
),
const Text(
"点击查看详情",
style: TextStyle(color: Colors.grey, fontSize: 12),
),
],
),
),
// 按钮组
Column(
children: [
CCPrimaryImageButton(
onPressed: isSelected
? null
: () async {
// 💡 执行切换逻辑
await context.read<DevicesCubit>().switchDevice(
device,
);
// 切换成功后,UI 会自动更新(因为 BlocBuilder 在监听),我们可以关闭弹窗
if (context.mounted) {
Navigator.pop(context);
}
},
text: isSelected ? "使用中" : "切换",
width: 80,
height: 30,
fontSize: 14,
// 如果是当前设备,按钮颜色变灰
backgroundColor: isSelected ? Colors.grey : Colors.black,
),
const SizedBox(height: 8),
CCPrimaryButton(
onPressed: () {
print("通过按钮进入详情");
},
text: "详情",
width: 80,
height: 30,
fontSize: 14,
backgroundColor: Colors.white,
textColor: Colors.black,
),
],
),
],
),
),
);
}
}