You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

573 lines
19 KiB

import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
import 'package:agent_service/agent_service.dart';
// 移除了dart:async导入,因为不再使用Timer
import '../../../data/services/novice_guide_service.dart';
import '../../../routes/app_routes.dart';
import '../../../core/utils/logger.dart';
import 'package:flutter/material.dart';
import '../../../data/services/bluetooth_manager.dart';
import '../../../data/services/ble_manager.dart';
import '../../../data/services/ota_check_service.dart';
import '../../../data/models/appconfig.dart';
import '../../../data/models/appconfig_model.dart';
/// 连接状态枚举
enum ConnectionStatus {
disconnected, // 未连接
connecting, // 连接中
connected, // 已连接
failed // 连接失败
}
/// 主页控制器,管理设备连接状态和信息展示
class HomeController extends GetxController {
static const String _tag = 'HomeCtrl';
static const String _lastDeviceNameKey = 'last_connected_device_name'; // 存储键
static const String _lastDeviceImageKey =
'last_connected_device_image'; // 存储上一次设备图片的键
static HomeController get to => Get.find();
final GetStorage _storage = GetStorage();
String? _lastConnectedDeviceImage; // 新增:存储上一次连接设备的图片链接
final _isFullyConnected = false.obs;
String? _lastConnectedDeviceName;
// 播报打断开关状态(与 AgentController 对齐)
final isTtsInterruptEnabled = false.obs; // 是否启用播报打断
// 移除了电池信息定时刷新相关变量
// OTA检查服务
late final OtaCheckService _otaCheckService;
bool get isFullyConnected => _isFullyConnected.value;
RxBool get isFullyConnectedRx => _isFullyConnected;
BluetoothDevice? get currentDevice => _bluetoothManager.currentDevice;
Rx<DeviceInfo?> get deviceInfo => _bleManager.deviceInfo;
static final RxInt leftEarBattery = 0.obs;
static final RxInt rightEarBattery = 0.obs;
final caseBattery = 0.obs;
final leftEarCharging = false.obs;
final rightEarCharging = false.obs;
final caseCharging = false.obs;
final firmwareVersion = ''.obs;
void updateDeviceInfo(DeviceInfo? newDeviceInfo) {
Logger.d(_tag, 'lxm--updateDeviceInfo被调用');
Logger.d(_tag, 'lxm--接收到的DeviceInfo: $newDeviceInfo');
if (newDeviceInfo != null) {
Logger.d(_tag, 'lxm--原始数据 - 左耳电量: ${newDeviceInfo.leftEarBattery}');
Logger.d(_tag, 'lxm--原始数据 - 右耳电量: ${newDeviceInfo.rightEarBattery}');
Logger.d(_tag, 'lxm--原始数据 - 充电仓电量: ${newDeviceInfo.caseBattery}');
Logger.d(_tag, 'lxm--原始数据 - 左耳充电: ${newDeviceInfo.leftEarCharging}');
Logger.d(_tag, 'lxm--原始数据 - 右耳充电: ${newDeviceInfo.rightEarCharging}');
Logger.d(_tag, 'lxm--原始数据 - 充电仓充电: ${newDeviceInfo.caseCharging}');
Logger.d(_tag, 'lxm--原始数据 - 固件版本: ${newDeviceInfo.firmwareVersion}');
} else {
Logger.d(_tag, 'lxm--接收到的DeviceInfo为null');
}
// 只有当新数据不为0时才更新电池值,否则保持原值
leftEarBattery.value = newDeviceInfo?.leftEarBattery ?? 0;
rightEarBattery.value = newDeviceInfo?.rightEarBattery ?? 0;
caseBattery.value = newDeviceInfo?.caseBattery ?? 0;
leftEarCharging.value = newDeviceInfo?.leftEarCharging ?? false;
rightEarCharging.value = newDeviceInfo?.rightEarCharging ?? false;
caseCharging.value = newDeviceInfo?.caseCharging ?? false;
firmwareVersion.value = newDeviceInfo?.firmwareVersion ?? '';
Logger.d(_tag, 'lxm--更新后的值 - 左耳电量: ${leftEarBattery.value}');
Logger.d(_tag, 'lxm--更新后的值 - 右耳电量: ${rightEarBattery.value}');
Logger.d(_tag, 'lxm--更新后的值 - 充电仓电量: ${caseBattery.value}');
Logger.d(_tag, 'lxm--更新后的值 - 左耳充电: ${leftEarCharging.value}');
Logger.d(_tag, 'lxm--更新后的值 - 右耳充电: ${rightEarCharging.value}');
Logger.d(_tag, 'lxm--更新后的值 - 充电仓充电: ${caseCharging.value}');
Logger.d(_tag, 'lxm--更新后的值 - 固件版本: ${firmwareVersion.value}');
// rightEarCharging.refresh();
//update();
}
Worker? _deviceInfoWorker;
Worker? _classicBluetoothWorker;
Worker? _bleConnectionWorker;
final _bluetoothManager = Get.find<BluetoothManager>();
final _bleManager = Get.find<BleManager>();
final noviceGuideService = Get.find<NoviceGuideService>();
@override
void onInit() {
super.onInit();
_otaCheckService = Get.find<OtaCheckService>();
_loadLastConnectedDeviceName(); // 加载持久化的设备名称
_initWithCachedInfo();
_loadTtsInterrupt(); // 加载播报打断开关初始状态
// 监听存储中的“agent_tts_interrupe”键变化,实现与 Agent 页同步
// 中文注释:当 Agent 页或 Home 页任意一处切换时,另一处通过此监听自动刷新开关状态
_storage.listenKey('agent_tts_interrupt', (value) {
try {
final bool newValue = value is bool ? value : (value == 'true');
if (isTtsInterruptEnabled.value != newValue) {
isTtsInterruptEnabled.value = newValue;
}
} catch (e) {
// 中文注释:忽略异常,保持现有状态,避免因类型转换失败导致崩溃
}
});
// 移除了电池信息定时刷新的启动
}
@override
void onClose() {
_deviceInfoWorker?.dispose();
_classicBluetoothWorker?.dispose();
_bleConnectionWorker?.dispose();
// 移除了定时器停止逻辑
super.onClose();
}
void _initWithCachedInfo() {
_setupStateListeners();
_checkConnectionStatus();
}
void _setupStateListeners() {
_deviceInfoWorker = ever(_bleManager.deviceInfo, (DeviceInfo? info) {
Logger.d(_tag, 'lxm--_deviceInfoWorker监听器被触发');
Logger.d(_tag, 'lxm--从BleManager接收到的DeviceInfo: $info');
if (info != null) {
Logger.d(_tag, 'lxm--DeviceInfo不为null,调用updateDeviceInfo');
updateDeviceInfo(info);
} else {
Logger.d(_tag, 'lxm--DeviceInfo为null,跳过更新');
}
//update();
});
_classicBluetoothWorker =
ever(_bluetoothManager.currentDeviceRx, (BluetoothDevice? device) {
if (device != null) {
_saveLastConnectedDeviceName(device.name); // 使用新的保存方法
}
_checkConnectionStatus();
});
_bleConnectionWorker = ever(_bleManager.connectionStateRx, (int state) {
_checkConnectionStatus();
});
}
/// 加载“播报打断”开关状态
/// 逻辑与 AgentController 保持一致:
/// 1)若本地不存在存储,则根据设备产品的 iscontinuouschat 取反作为默认值;
/// 2)若存在存储则直接读取;
/// 3)同步到原生服务 AgentService.isInterrupt。
Future<void> _loadTtsInterrupt() async {
try {
if (!_storage.hasData('agent_tts_interrupt')) {
final deviceName = getDeviceName();
final DBProduct? product = AppConfig.getproduct(deviceName);
if (deviceName.isNotEmpty && product != null) {
isTtsInterruptEnabled.value = !(product.iscontinuouschat ?? true);
} else {
isTtsInterruptEnabled.value = false;
}
} else {
isTtsInterruptEnabled.value =
_storage.read('agent_tts_interrupt') ?? false;
}
await AgentService.isInterrupt(isTtsInterruptEnabled.value);
} catch (e) {
Logger.e(_tag, '加载播报打断开关失败: $e');
isTtsInterruptEnabled.value = false;
}
}
/// 切换“播报打断”开关
/// 同步保存到本地,并通知原生服务。
Future<void> toggleTtsInterrupt() async {
try {
isTtsInterruptEnabled.toggle();
await _storage.write('agent_tts_interrupt', isTtsInterruptEnabled.value);
await AgentService.isInterrupt(isTtsInterruptEnabled.value);
} catch (e) {
Logger.e(_tag, '保存播报打断偏好失败: $e');
}
}
/// 从本地存储加载最后连接的设备名称
void _loadLastConnectedDeviceName() {
_lastConnectedDeviceName = _storage.read(_lastDeviceNameKey);
Logger.d(_tag, '加载最后连接的设备名称: $_lastConnectedDeviceName');
}
/// 保存最后连接的设备名称到本地存储
void _saveLastConnectedDeviceName(String deviceName) {
_lastConnectedDeviceName = deviceName;
_storage.write(_lastDeviceNameKey, deviceName);
Logger.d(_tag, '保存最后连接的设备名称: $deviceName');
}
/// 保存上一次连接设备的图片链接到本地存储
void _saveLastConnectedDeviceImage(String? imageUrl) {
if (imageUrl != null && imageUrl.isNotEmpty) {
_lastConnectedDeviceImage = imageUrl;
_storage.write(_lastDeviceImageKey, imageUrl);
Logger.d(_tag, '保存上一次连接设备的图片: $imageUrl');
}
}
Future<void> refreshDeviceConnection() async {
await _checkConnectionStatus();
// 如果设备已连接,主动刷新设备信息
if (_isFullyConnected.value) {
try {
Logger.d(_tag, '刷新设备信息');
await _bleManager.fetchDeviceInfo();
} catch (e) {
Logger.e(_tag, '刷新设备信息失败: $e');
}
}
}
void rescanDevices() {
Get.toNamed(Routes.pairing);
}
void openTranslation() {
AgentService.interruptCurrentResponse();
Get.toNamed(Routes.translationMiddleware);
}
/// 打开会议模块
/// 参数:无
/// 返回值:`Future<void>` 无返回数据
/// 异常:不显式抛出;内部仅记录日志
/// 说明:进入会议页前关闭唤醒;返回首页后根据录音状态决定是否开启唤醒(存在录音任务则不启用)
void openMeet() {
AgentService.interruptCurrentResponse();
Get.toNamed(Routes.meeting);
}
void exploreAIFeatures() {
// 优先读设备检测写入的 storage(YIDONG_PID 匹配时),否则降级到服务端 AGENT_TYPE
final agentType = GetStorage().read<String>('device_agent_type')
?? AppConfig.env('AGENT_TYPE')
?? 'stt_llm_tts';
if (agentType == 'yidong') {
Get.toNamed(Routes.mobileElf);
} else {
Get.toNamed(Routes.agent);
AgentService.interruptCurrentResponse();
}
}
Future<void> _checkConnectionStatus() async {
try {
final currentBleState = await _bleManager.getConnectionState();
print('fdp--_checkConnectionStatus--currentBleState: $currentBleState');
bool hasClassicBluetooth = currentDevice != null;
bool hasBleConnection = currentBleState == 2;
bool wasConnected = _isFullyConnected.value;
bool isNowConnected = hasClassicBluetooth && hasBleConnection;
//bool isGuideCompleted = _storage.read('home_guide') ?? false;
if (isNowConnected && currentDevice == null) {
isNowConnected = false;
}
if (_isFullyConnected.value != isNowConnected) {
_isFullyConnected.value = isNowConnected;
_isFullyConnected.refresh();
//update();
if (!wasConnected && isNowConnected) {
// 连接成功后等待1秒再获取设备信息
Future.delayed(const Duration(seconds: 1), () async {
await _bleManager.fetchDeviceInfo();
});
// 移除了连接成功后的电池信息刷新
// 连接成功后检查OTA升级
if (noviceGuideService.isHomeGuide) {
Future.delayed(const Duration(seconds: 1), () {
_checkOtaUpgrade();
});
}
}
// 移除了根据连接状态控制定时器的逻辑
}
//update();
} catch (e) {
Logger.e(_tag, '${'checkConnectionStatusFailed'.tr}: $e');
if (_isFullyConnected.value) {
_isFullyConnected.value = false;
_isFullyConnected.refresh();
// 移除了发生错误时停止定时器的逻辑
// 注意:不再重置OTA检查状态,因为现在由全局服务管理
//update();
}
}
}
Color getConnectionStatusColor() {
return _isFullyConnected.value ? Colors.green : Colors.grey;
}
String getConnectionStatusText() {
return _isFullyConnected.value ? 'connected'.tr : 'unconnected'.tr;
}
bool get shouldShowDeviceInfo => currentDevice != null;
String getDeviceName() {
if (_isFullyConnected.value && currentDevice == null) {
_checkConnectionStatus();
}
final deviceName = currentDevice?.name;
//final deviceName = '102';
if (deviceName != null && deviceName.isNotEmpty) {
return deviceName;
}
final bleDeviceName = deviceInfo.value?.name;
if (bleDeviceName != null && bleDeviceName.isNotEmpty) {
return bleDeviceName;
}
if (!_isFullyConnected.value &&
_lastConnectedDeviceName != null &&
_lastConnectedDeviceName!.isNotEmpty) {
return _lastConnectedDeviceName!;
}
return 'unknownEquipment'.tr;
}
String getSignalStrength() {
if (!_isFullyConnected.value) {
return 'zeroSignal'.tr;
}
final random = DateTime.now().millisecondsSinceEpoch % 3;
switch (random) {
case 0:
return 'good'.tr;
case 1:
return 'fine'.tr;
default:
return 'excellent'.tr;
}
}
String getDeviceId() {
return deviceInfo.value?.address ?? currentDevice?.address ?? 'unknown'.tr;
}
String getBatteryText() {
return deviceInfo.value?.batteryLevel != null &&
deviceInfo.value!.batteryLevel > 0
? '${deviceInfo.value?.batteryLevel}%'
: 'unknown'.tr;
}
String getEarbudsText() {
if (deviceInfo.value == null ||
(deviceInfo.value!.leftEarBattery <= 0 &&
deviceInfo.value!.rightEarBattery <= 0 ||
deviceInfo.value!.caseBattery > 100)) {
return 'unknown'.tr;
}
String leftStatus = deviceInfo.value!.leftEarBattery > 0
? '${deviceInfo.value!.leftEarBattery}%'
: 'N/A';
String rightStatus = deviceInfo.value!.rightEarBattery > 0
? '${deviceInfo.value!.rightEarBattery}%'
: 'N/A';
leftStatus =
deviceInfo.value!.leftEarCharging ? '$leftStatus ⚡' : leftStatus;
rightStatus =
deviceInfo.value!.rightEarCharging ? '$rightStatus ⚡' : rightStatus;
return '${'leftEar'.tr}: $leftStatus / ${'rightEar'.tr}: $rightStatus'; // 左耳: xxx / 右耳: xxx
}
String getCaseText() {
if (deviceInfo.value == null ||
deviceInfo.value!.caseBattery <= 0 ||
deviceInfo.value!.caseBattery > 100) {
return 'unknown'.tr;
}
String caseStatus = '${deviceInfo.value!.caseBattery}%';
if (deviceInfo.value!.caseCharging) {
caseStatus = '$caseStatus ⚡';
}
return '${'chargingCase'.tr}: $caseStatus'; // 充电仓: xxx
}
String getChargingText() {
if (deviceInfo.value == null) {
return 'unknown'.tr;
}
bool isCharging = deviceInfo.value!.leftEarCharging ||
deviceInfo.value!.rightEarCharging ||
deviceInfo.value!.caseCharging;
return isCharging ? 'charging'.tr : 'notCharging'.tr;
}
/// 获取当前设备类型
/// 返回设备类型,如果无法获取则返回未知类型
DeviceType getCurrentDeviceType() {
try {
final deviceName = getDeviceName();
if (deviceName == 'unknownEquipment'.tr) {
return DeviceType.unknown;
}
// 通过AppConfig获取产品信息
final product = AppConfig.getproduct(deviceName);
if (product != null) {
// 根据产品的devicetype字段返回对应的DeviceType
switch (product.devicetype) {
case 1:
return DeviceType.classicBluetoothHeadset;
case 2:
return DeviceType.bleBluetoothDoubleHeadset;
case 3:
return DeviceType.bleBluetoothSingleHeadset;
case 4:
return DeviceType.smartVoiceRecorder;
case 5:
return DeviceType.smartEye;
default:
return DeviceType.unknown;
}
}
return DeviceType.unknown;
} catch (e) {
Logger.e(_tag, '获取设备类型失败: $e');
return DeviceType.unknown;
}
}
/// 判断当前设备是否为单端耳机
bool isSingleHeadset() {
return getCurrentDeviceType() == DeviceType.bleBluetoothSingleHeadset ||
getCurrentDeviceType() == DeviceType.smartVoiceRecorder ||
getCurrentDeviceType() == DeviceType.smartEye;
}
String getVersionText() {
return deviceInfo.value?.firmwareVersion.isNotEmpty == true
? deviceInfo.value!.firmwareVersion
: 'unknown'.tr;
}
// 移除了电池信息定时刷新相关方法
/// 检查OTA固件升级
/// 在BLE连接成功后调用,检查是否需要升级
Future<void> _checkOtaUpgrade() async {
Logger.d(_tag, '触发OTA升级检查...');
// 打印当前OTA检查状态(用于调试)
final status = _otaCheckService.getCheckStatus();
Logger.d(_tag, 'OTA检查状态: $status');
// 使用全局OTA检查服务执行检查
final success =
await _otaCheckService.performOtaCheck(_isFullyConnected.value);
if (success) {
Logger.d(_tag, 'OTA检查已成功执行');
} else {
Logger.d(_tag, 'OTA检查被跳过或执行失败');
}
}
/// 调试方法:获取OTA检查状态
/// 可以在开发阶段用于调试
Map<String, dynamic> getOtaCheckStatus() {
return _otaCheckService.getCheckStatus();
}
/// 获取当前设备的产品图片路径
/// 如果没有配置产品图片或获取失败,返回null使用默认图片
String? getCurrentDeviceImage() {
try {
final deviceName = getDeviceName();
if (deviceName == 'unknownEquipment'.tr) {
Logger.d(_tag, '设备未知,使用默认图片');
return null;
}
// 通过AppConfig获取产品信息
if (!AppConfig.isInitialized()) {
Logger.w(_tag, 'AppConfig未初始化,使用默认图片');
return null;
}
final product = AppConfig.getproduct(deviceName);
if (product != null &&
product.productimage != null &&
product.productimage!.isNotEmpty) {
Logger.d(_tag, '获取到设备产品图片: ${product.productimage}');
_saveLastConnectedDeviceImage(product.productimage);
return product.productimage;
} else {
Logger.d(_tag, '设备 "$deviceName" 未配置产品图片,使用默认图片');
return null;
}
} catch (e) {
Logger.e(_tag, '获取设备产品图片失败: $e');
return null;
}
}
/// 获取设备图片的完整资源路径
/// 处理网络图片URL和本地资源路径
String getDeviceImagePath(bool isDarkMode) {
final productImage = getCurrentDeviceImage();
if (productImage != null) {
// 如果是网络图片URL
if (productImage.startsWith('http://') ||
productImage.startsWith('https://')) {
return productImage;
}
// 如果是本地资源路径,确保以assets/开头
else if (productImage.startsWith('assets/')) {
return productImage;
}
// 如果只是文件名,添加assets/images/前缀
else {
return 'assets/images/$productImage';
}
}
// 使用默认耳机图片
return 'assets/images/defaultPicture.png';
}
/// 判断是否为网络图片
bool isNetworkImage(String imagePath) {
return imagePath.startsWith('http://') || imagePath.startsWith('https://');
}
}