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.
 
 
 
 
 
 

231 lines
8.3 KiB

import 'dart:async';
import 'package:agent_service/agent_service.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
import '/core/utils/device_connection.dart';
import '/core/utils/id_verify_guard.dart';
import '/core/utils/toast_throttle.dart';
import '../../../core/utils/logger.dart';
import '../../../data/models/appconfig.dart';
import '../../../data/models/appconfig_model.dart';
import '../../../data/services/id_verify_service.dart';
import '../../../data/services/novice_guide_service.dart';
import '../../../data/services/ota_check_service.dart';
import '../../../devices/device_hub.dart';
import '../../../routes/app_routes.dart';
/// 主框架控制器:各功能入口的跳转与闸门、连上设备后的固件检查、播报打断开关。
///
/// 设备状态一律来自 [DeviceHub],这里不再自己维护任何连接/电量真相。
class HomeController extends GetxController {
static const String _tag = 'HomeCtrl';
static HomeController get to => Get.find();
final GetStorage _storage = GetStorage();
// 播报打断开关状态
final isTtsInterruptEnabled = false.obs;
late final OtaCheckService _otaCheckService;
final noviceGuideService = Get.find<NoviceGuideService>();
Worker? _connectedWorker;
/// 任一设备在线。保留旧名给还在用它的页面。
RxBool get isFullyConnectedRx => DeviceHub.to.anyConnected;
bool get isFullyConnected => isFullyConnectedRx.value;
/// 按设备名安全地查产品(AppConfig.getproduct 单向包含,这里双向)。
DBProduct? _findProductByName(String deviceName) {
if (!AppConfig.isInitialized()) return null;
final n = deviceName.trim();
if (n.isEmpty) return null;
for (final p in AppConfig.config.products) {
if (p.devicename.contains(n) || n.contains(p.devicename)) return p;
}
return null;
}
@override
void onInit() {
super.onInit();
_otaCheckService = Get.find<OtaCheckService>();
// 实名状态:InitialBinding 里那次拉取发生在登录之前、必然拿不到,
// 登录后所有流程都会落到 home,在这里补一次(已拉到过就不会重复请求)。
if (Get.isRegistered<IdVerifyService>()) {
IdVerifyService.to.ensureLoaded();
}
_loadTtsInterrupt();
_storage.listenKey('agent_tts_interrupt', (value) {
try {
final bool newValue = value is bool ? value : (value == 'true');
if (isTtsInterruptEnabled.value != newValue) {
isTtsInterruptEnabled.value = newValue;
}
} catch (_) {}
});
// 连接成功后检查 OTA 升级
_connectedWorker = ever<bool>(DeviceHub.to.anyConnected, (on) {
if (on && noviceGuideService.isHomeGuide) {
Future.delayed(const Duration(seconds: 1), _checkOtaUpgrade);
}
});
}
@override
void onClose() {
_connectedWorker?.dispose();
super.onClose();
}
/// 加载"播报打断"开关状态:本地没存过时按设备产品的 iscontinuouschat 取反。
Future<void> _loadTtsInterrupt() async {
try {
if (!_storage.hasData('agent_tts_interrupt')) {
final deviceName = getDeviceName();
final DBProduct? product = _findProductByName(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');
}
}
/// 刷新设备状态(点刷新按钮 / 定时)
Future<void> refreshDeviceConnection() => DeviceHub.to.refreshAll();
void rescanDevices() {
Get.toNamed(Routes.deviceCategory);
}
void openTranslation() {
AgentService.interruptCurrentResponse();
Get.toNamed(Routes.translationMiddleware);
}
/// 翻译跳转:按 translationId 进入对应翻译模式
/// translationId 取值:simultaneous / faceToFace / call / audioVideo
/// 其中 call 依赖设备连接且要正处于通话中
void openTranslationFeature(String translationId) {
if (IdVerifyGuard.blocked()) return;
final bool needDevice = translationId == 'call';
// 下面三条一律走 ToastThrottle:卡片只是灰着、点得动,用户会连点几下试
if (needDevice && DeviceConnection.any && !DeviceConnection.inCall) {
ToastThrottle.show(ToastThrottle.keyCallRequired,
title: 'callRequiredTitle'.tr,
message: 'callRequiredDesc'.tr,
window: ToastThrottle.gateWindow);
return;
}
if (needDevice && !DeviceConnection.any) {
ToastThrottle.show(ToastThrottle.keyConnectDevice,
title: 'connectDevice'.tr,
message: 'pleaseConnectDeepSoundDevice'.tr,
window: ToastThrottle.gateWindow);
return;
}
if (DeviceConnection.isBlockedByCall(translationId)) {
ToastThrottle.show(ToastThrottle.keyInCallBusy,
title: 'inCallBusyTitle'.tr,
message: 'inCallBusyDesc'.tr,
window: ToastThrottle.gateWindow);
return;
}
AgentService.interruptCurrentResponse();
Get.toNamed(Routes.translation,
arguments: {'translationId': translationId});
}
/// 打开通话录音:与卡片置灰条件统一,未连接设备/不在通话中则提示并返回
void openCallRecording() {
if (IdVerifyGuard.blocked()) return;
if (DeviceConnection.any && !DeviceConnection.inCall) {
ToastThrottle.show(ToastThrottle.keyCallRequired,
title: 'callRequiredTitle'.tr,
message: 'callRequiredDesc'.tr,
window: ToastThrottle.gateWindow);
return;
}
if (!DeviceConnection.any) {
ToastThrottle.show(ToastThrottle.keyConnectDevice,
title: 'connectDevice'.tr,
message: 'pleaseConnectDeepSoundDevice'.tr,
window: ToastThrottle.gateWindow);
return;
}
Get.toNamed(Routes.recording,
arguments: {'title': 'agentCallRecording', 'folder': 'call_recordings'});
}
/// 打开现场录音。不要求先连设备:连上了走设备录音通路,没连就用手机麦克风。
void openFieldRecording() {
if (DeviceConnection.isBlockedByCall('fieldRecording')) {
ToastThrottle.show(ToastThrottle.keyInCallBusy,
title: 'inCallBusyTitle'.tr,
message: 'inCallBusyDesc'.tr,
window: ToastThrottle.gateWindow);
return;
}
if (IdVerifyGuard.blocked()) return;
Get.toNamed(Routes.fieldRecording, arguments: {
'title': 'agentFieldRecording',
'folder': 'field_recordings',
'archiveToMeeting': true,
});
}
/// 打开会议模块
void openMeet() {
if (IdVerifyGuard.blocked()) return;
AgentService.interruptCurrentResponse();
Get.toNamed(Routes.meeting);
}
Color getConnectionStatusColor() =>
isFullyConnected ? Colors.green : Colors.grey;
String getConnectionStatusText() =>
isFullyConnected ? 'connected'.tr : 'unconnected'.tr;
/// 当前(或最近一次)连接设备的名字;从没连过返回「未知设备」
String getDeviceName() {
final n = DeviceHub.to.lastKnownName;
return n.isNotEmpty ? n : 'unknownEquipment'.tr;
}
String getDeviceId() => DeviceHub.to.primaryInfo.value?.macAddress ??
DeviceHub.to.primaryInfo.value?.id ??
'unknown'.tr;
String getVersionText() {
final v = DeviceHub.to.primaryInfo.value?.firmwareVersion ?? '';
return v.isNotEmpty ? v : 'unknown'.tr;
}
Future<void> _checkOtaUpgrade() async {
final success = await _otaCheckService.performOtaCheck(isFullyConnected);
Logger.d(_tag, success ? 'OTA检查已成功执行' : 'OTA检查被跳过或执行失败');
}
Map<String, dynamic> getOtaCheckStatus() => _otaCheckService.getCheckStatus();
}