Browse Source
- 底部:原「AI」tab 改名「首页」;中间新增圆形「AI」按钮(不是常驻页,点了才打开对话页, 避免百炼连接和麦克风常驻);右边「我的」。设备、拾忆不再是 tab - 首页顶栏:左上角产品小图标(DeviceStatusChip,已连接绿圈+绿点+电量,未连接置灰), 点进设备页 /device;右上角「编辑」紧挨「日历」,日历点进拾忆页 /recall。 两个独立页复用原 DeviceTab / TodoTab(standalone 模式带返回箭头,TabScaffold 新增 onBack) - AI 按钮:openAiAssistant() 按「当前连着、没连就最近连过的设备」→ 品类默认编排里第一个对话助手: EaiCar 香薰 → EaiCar 车载助手,其余 → navigateToAIAssistant(EMAI / 移动精灵) - 首页网格不再显示对话助手(AgentModuleService.assistantIds:EMAI、EaiCar),只是不显示, 编排与设备按键唤醒找 agent 不受影响;拖拽排序改走 moveOnHome 换算下标 - 三个扫描页连上后改为回到首页再打开设备页(showDeviceAfterConnect) 验证:dart analyze 无 error;flutter test 221 全过;release 包装到 ALN-AL80 真机点过首页、 日历、设备、AI 按钮(打开 EMAI)。连着 EaiCar 时打开 EaiCar 助手这条未实测。 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>main
15 changed files with 405 additions and 77 deletions
@ -0,0 +1,145 @@ |
|||||
|
import 'package:device_plugin_interface/device_plugin_interface.dart'; |
||||
|
import 'package:flutter/material.dart'; |
||||
|
import 'package:flutter_screenutil/flutter_screenutil.dart'; |
||||
|
import 'package:get/get.dart'; |
||||
|
|
||||
|
import '../../../../core/theme/app_palette.dart'; |
||||
|
import '../../../../core/utils/device_connection.dart'; |
||||
|
import '../../../../devices/device_hub.dart'; |
||||
|
import 'battery_indicator.dart'; |
||||
|
|
||||
|
/// AI 页左上角的设备入口:产品小图标 + 电量,点击进设备页。 |
||||
|
/// |
||||
|
/// - 已连接:图标外圈绿色,右下角一个绿点,旁边显示电量; |
||||
|
/// - 未连接:图标置灰,不显示电量。 |
||||
|
/// |
||||
|
/// 图标取「当前连着的、没连就最近连过的」那台设备([DeviceConnection.currentIconImage]), |
||||
|
/// 从没连过时是通用图。**不认厂商**:电量按 [DeviceBattery.summary] 有什么显示什么, |
||||
|
/// TWS 耳机给的是左右较低的一侧,支架这类拿不到电量的设备就只显示图标。 |
||||
|
class DeviceStatusChip extends StatelessWidget { |
||||
|
const DeviceStatusChip({ |
||||
|
Key? key, |
||||
|
required this.isDarkMode, |
||||
|
required this.onTap, |
||||
|
}) : super(key: key); |
||||
|
|
||||
|
final bool isDarkMode; |
||||
|
final VoidCallback onTap; |
||||
|
|
||||
|
/// 置灰矩阵(ITU-R BT.601 亮度系数),给未连接时的产品图标用 |
||||
|
static const ColorFilter _grayscale = ColorFilter.matrix(<double>[ |
||||
|
0.299, 0.587, 0.114, 0, 0, // |
||||
|
0.299, 0.587, 0.114, 0, 0, // |
||||
|
0.299, 0.587, 0.114, 0, 0, // |
||||
|
0, 0, 0, 1, 0, |
||||
|
]); |
||||
|
|
||||
|
@override |
||||
|
Widget build(BuildContext context) { |
||||
|
final hub = DeviceHub.to; |
||||
|
return Obx(() { |
||||
|
// revision 随任一会话的 info(含电量)变化 +1,读一下让 Obx 收集到它 |
||||
|
hub.revision.value; |
||||
|
final bool connected = hub.anyConnected.value; |
||||
|
final DeviceInfo? info = hub.primaryInfo.value; |
||||
|
final DeviceBattery? battery = connected ? info?.battery : null; |
||||
|
final int? level = battery?.summary; |
||||
|
final bool charging = battery != null && |
||||
|
(battery.chargingSingle || |
||||
|
battery.chargingLeft || |
||||
|
battery.chargingRight); |
||||
|
|
||||
|
final Color ring = connected |
||||
|
? AppPalette.success |
||||
|
: (isDarkMode ? Colors.white24 : Colors.black12); |
||||
|
|
||||
|
Widget icon = Image.asset( |
||||
|
DeviceConnection.currentIconImage(), |
||||
|
width: 26.w, |
||||
|
height: 26.w, |
||||
|
fit: BoxFit.contain, |
||||
|
errorBuilder: (_, __, ___) => Icon( |
||||
|
Icons.devices_other, |
||||
|
size: 20.sp, |
||||
|
color: isDarkMode ? Colors.white54 : AppPalette.brand400, |
||||
|
), |
||||
|
); |
||||
|
if (!connected) { |
||||
|
icon = Opacity( |
||||
|
opacity: 0.55, |
||||
|
child: ColorFiltered(colorFilter: _grayscale, child: icon), |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
return GestureDetector( |
||||
|
behavior: HitTestBehavior.opaque, |
||||
|
onTap: onTap, |
||||
|
child: Row( |
||||
|
mainAxisSize: MainAxisSize.min, |
||||
|
children: [ |
||||
|
SizedBox( |
||||
|
width: 36.w, |
||||
|
height: 36.w, |
||||
|
child: Stack( |
||||
|
clipBehavior: Clip.none, |
||||
|
children: [ |
||||
|
Container( |
||||
|
width: 36.w, |
||||
|
height: 36.w, |
||||
|
alignment: Alignment.center, |
||||
|
decoration: BoxDecoration( |
||||
|
color: isDarkMode |
||||
|
? Colors.white.withOpacity(0.06) |
||||
|
: Colors.white, |
||||
|
shape: BoxShape.circle, |
||||
|
border: Border.all( |
||||
|
color: ring, |
||||
|
width: connected ? 1.6 : 1, |
||||
|
), |
||||
|
boxShadow: [ |
||||
|
BoxShadow( |
||||
|
color: Colors.black |
||||
|
.withOpacity(isDarkMode ? 0.2 : 0.06), |
||||
|
blurRadius: 6, |
||||
|
offset: const Offset(0, 2), |
||||
|
), |
||||
|
], |
||||
|
), |
||||
|
child: icon, |
||||
|
), |
||||
|
if (connected) |
||||
|
Positioned( |
||||
|
right: 0, |
||||
|
bottom: 0, |
||||
|
child: Container( |
||||
|
width: 9.w, |
||||
|
height: 9.w, |
||||
|
decoration: BoxDecoration( |
||||
|
color: AppPalette.success, |
||||
|
shape: BoxShape.circle, |
||||
|
border: Border.all( |
||||
|
color: isDarkMode |
||||
|
? const Color(0xFF0A0F1A) |
||||
|
: Colors.white, |
||||
|
width: 1.5, |
||||
|
), |
||||
|
), |
||||
|
), |
||||
|
), |
||||
|
], |
||||
|
), |
||||
|
), |
||||
|
if (level != null) ...[ |
||||
|
SizedBox(width: 6.w), |
||||
|
BatteryIndicator( |
||||
|
level: level, |
||||
|
isDarkMode: isDarkMode, |
||||
|
charging: charging, |
||||
|
), |
||||
|
], |
||||
|
], |
||||
|
), |
||||
|
); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue