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
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();
|
|
}
|
|
|