import 'package:get/get.dart'; import 'package:dio/dio.dart'; import '../../../core/utils/logger.dart'; import '../../../data/services/ble_manager.dart'; import '../../../data/services/network/api.dart'; class WakeupVoiceController extends GetxController { // 是否启用唤醒词(原有本地词功能,可保留) final RxBool enabled = true.obs; final BleManager bleManager = Get.find(); // 本地自定义唤醒词(原有) final RxList wakeWords = ['你好,深声', 'Hey Voitrans'].obs; final RxString inputText = ''.obs; // 服务端唤醒语音包列表 final RxList voices = [].obs; // 当前设备的唤醒语音 id 与版本(由外部设备信息设置) final RxnInt currentDeviceVoiceId = RxnInt(53251); final RxnString currentDeviceVoiceVersion = RxnString('1.0.0'); // 当前页面选择的语音包 id final RxnInt selectedVoiceId = RxnInt(); // 加载状态 final RxBool isLoading = false.obs; final RxString loadError = ''.obs; // 设备状态(用于页面头部展示) final RxString deviceName = ''.obs; final RxBool isConnected = false.obs; // 预览/测试模式:跳过 onReady 自动拉取 bool skipAutoFetch = false; @override void onReady() { super.onReady(); _bindBleObservers(); // 允许通过路由参数传入当前设备唤醒语音信息 final args = Get.arguments; if (args is Map) { final id = args['wakeupVoiceId']; final ver = args['wakeupVoiceVersion']; if (id is int && ver is String) { setCurrentDeviceWakeVoice(id: id, version: ver); } final name = args['deviceName']; final connected = args['connected']; if (name is String) deviceName.value = name; if (connected is bool) isConnected.value = connected; } if (!skipAutoFetch) { fetchVoices(); } } void _bindBleObservers() { // 同步连接状态与设备名 try { isConnected.value = bleManager.isConnected; final info = bleManager.deviceInfo.value; if (info != null && info.name.isNotEmpty) { deviceName.value = info.name; } // 监听连接状态变化 ever(bleManager.connectionStateRx, (state) { isConnected.value = bleManager.isConnected; final info = bleManager.deviceInfo.value; if (info != null && info.name.isNotEmpty) { deviceName.value = info.name; } }); // 监听设备信息更新 ever(bleManager.deviceInfo, (info) { if (info != null && info.name.isNotEmpty) { deviceName.value = info.name; } }); // 监听升级进度 ever(bleManager.wakeupVoiceUpgradeProgress, (p) { upgradeProgress.value = p; if (p >= 100 && upgradingVoiceId.value != null) { final item = _findById(upgradingVoiceId.value); if (item != null) { currentDeviceVoiceId.value = item.id; currentDeviceVoiceVersion.value = item.version; selectedVoiceId.value = item.id; } upgradingVoiceId.value = null; } }); // 监听升级状态:仅当状态为“成功(2)”或“失败(3)”时弹出提示弹窗;升级中(1)或其他状态不提示 ever(bleManager.wakeupVoiceUpgradeStatus, (s) { // 升级状态字段未定义,暂存到日志 Logger.info('BLE 升级状态码: $s'); if (s == 2) { // 升级成功 Get.snackbar('成功', '唤醒语音升级成功'); } else if (s == 3) { // 升级失败 Get.snackbar('错误', '唤醒语音升级失败'); } }); } catch (e) { Logger.warning('绑定BLE观察者失败: $e'); } } // 供外部设置当前设备语音包信息 void setCurrentDeviceWakeVoice({required int id, required String version}) { currentDeviceVoiceId.value = id; currentDeviceVoiceVersion.value = version; // 若已加载列表,默认选中当前设备项 if (voices.isNotEmpty) { selectedVoiceId.value = id; } } Future fetchVoices() async { try { isLoading.value = true; loadError.value = ''; final res = await Api.getwakeupvoices(); Logger.info('获取唤醒语音列表成功 res: $res'); // 仅按约定结构解析:{ voices: [...] } if (res is! Map || res['voices'] is! List) { loadError.value = '接口返回异常:缺少 voices 列表'; voices.clear(); return; } final list = List>.from(res['voices'] as List); voices.assignAll(list .map((e) => WakeupVoiceItem.fromJson(e as Map)) .toList()); // 默认选择:优先当前设备项,否则第一个 if (currentDeviceVoiceId.value != null && voices.any((v) => v.id == currentDeviceVoiceId.value)) { selectedVoiceId.value = currentDeviceVoiceId.value; } else if (voices.isNotEmpty) { selectedVoiceId.value = voices.first.id; } } catch (e) { loadError.value = '加载唤醒语音失败: $e'; Get.snackbar('错误', loadError.value); } finally { isLoading.value = false; } } // 选择语音包 void selectVoice(int id) { selectedVoiceId.value = id; } // 动作按钮文案与状态 String get actionText { final sel = selectedVoiceId.value; final curId = currentDeviceVoiceId.value; final curVer = currentDeviceVoiceVersion.value; final selected = _findById(sel); if (sel == null || selected == null) return '处理'; if (curId == sel) { if (curVer != null && curVer != selected.version) { return '升级'; } return '已是最新'; } else { return '切换'; } } bool get actionEnabled { final sel = selectedVoiceId.value; final curId = currentDeviceVoiceId.value; final curVer = currentDeviceVoiceVersion.value; final selected = _findById(sel); if (sel == null || selected == null) return false; if (curId == sel) { return curVer != null && curVer != selected.version; // 版本不一致才可升级 } else { return true; // 选择其他可切换 } } Future onActionPressed() async { final label = actionText; if (!actionEnabled) return; switch (label) { case '升级': await upgradeWakeVoice(); break; case '切换': await switchWakeVoice(); break; default: break; } } Future upgradeWakeVoice() async { final sel = _findById(selectedVoiceId.value); if (sel == null) return; await _startUpgradeByItem(sel); } Future switchWakeVoice() async { final sel = _findById(selectedVoiceId.value); if (sel == null) return; await _startUpgradeByItem(sel); } // 直接切换到指定语音包(页面卡片按钮使用) Future switchWakeVoiceTo(WakeupVoiceItem item) async { selectedVoiceId.value = item.id; await _startUpgradeByItem(item); } // 直接升级到指定语音包(页面卡片按钮使用) Future upgradeWakeVoiceTo(WakeupVoiceItem item) async { selectedVoiceId.value = item.id; await _startUpgradeByItem(item); } // 升级相关状态 final RxnInt upgradingVoiceId = RxnInt(); final RxInt upgradeProgress = 0.obs; Future _startUpgradeByItem(WakeupVoiceItem item) async { try { if (item.packagaddress.isEmpty) { Get.snackbar('升级失败', '语音包地址为空'); return; } upgradingVoiceId.value = item.id; upgradeProgress.value = 0; final url = item.packagaddress; Logger.info('开始下载唤醒语音包: $url'); final bytes = await _downloadVoiceBytes(url); if (bytes.isEmpty) { Get.snackbar('升级失败', '语音包下载为空'); upgradingVoiceId.value = null; return; } Logger.info('下载完成,字节数: ${bytes.length},开始升级'); // 注意:MethodChannel 在 Android 端对 Uint8List 会编码为 byte[], // 而当前原生插件期望的是 List。为避免 ClassCastException,传递 List。 final ok = await bleManager.startWakeupVoicesUpgrade(bytes.toList()); if (!ok) { Get.snackbar('升级失败', 'BLE升级启动失败'); upgradingVoiceId.value = null; return; } Get.snackbar('升级已启动', '正在写入设备,请稍候'); } catch (e) { upgradingVoiceId.value = null; Logger.info('升级失败: $e'); Get.snackbar('升级失败', e.toString()); } } Future> _downloadVoiceBytes(String url) async { try { final dio = Dio(); final response = await dio.get>( url, options: Options( responseType: ResponseType.bytes, receiveTimeout: const Duration(seconds: 60), ), ); return response.data ?? []; } catch (e) { Logger.error('下载语音包失败: $url', e); return []; } } WakeupVoiceItem? _findById(int? id) { if (id == null) return null; for (final v in voices) { if (v.id == id) return v; } return null; } // 以下保留原有本地词功能(开关、增删) void toggleEnabled(bool value) { enabled.value = value; } void setInput(String value) { inputText.value = value; } String? validateWakeWord(String text) { final t = text.trim(); if (t.isEmpty) return '唤醒词不能为空'; if (t.length > 20) return '唤醒词过长(≤20个字符)'; if (wakeWords.contains(t)) return '重复的唤醒词'; return null; } bool addWakeWord(String text) { final err = validateWakeWord(text); if (err != null) { Get.snackbar('提示', err); return false; } if (wakeWords.length >= 5) { Get.snackbar('提示', '最多添加5个唤醒词'); return false; } wakeWords.add(text.trim()); inputText.value = ''; return true; } void removeWakeWord(String text) { wakeWords.remove(text); } Future saveChanges() async { Get.snackbar('保存成功', '唤醒词设置已更新'); } } class WakeupVoiceItem { final int id; final bool enable; final String name; final String desc; final String version; final String packagaddress; WakeupVoiceItem({ required this.id, required this.enable, required this.name, required this.desc, required this.version, required this.packagaddress, }); factory WakeupVoiceItem.fromJson(Map json) { return WakeupVoiceItem( id: (json['id'] ?? json['Id'] ?? 0) is String ? int.tryParse(json['id'] ?? json['Id']) ?? 0 : (json['id'] ?? json['Id'] ?? 0) as int, enable: (json['enable'] ?? json['Enable'] ?? false) == true || (json['enable'] ?? 'false').toString() == 'true', name: (json['name'] ?? '').toString(), desc: (json['desc'] ?? '').toString(), version: (json['version'] ?? '').toString(), packagaddress: (json['packagaddress'] ?? '').toString(), ); } // 从描述字段提取识别率与更新说明(供UI展示) } extension WakeupVoiceItemExt on WakeupVoiceController { String? extractAccuracy(WakeupVoiceItem item) { final d = item.desc; final reg = RegExp(r'(识别率|准确率)\s*([0-9]{1,3}(\.[0-9])?)%'); final m = reg.firstMatch(d); if (m != null) { return '识别率 ${m.group(2)}%'; } return null; } List extractUpdateNotes(WakeupVoiceItem item) { final d = item.desc.trim(); if (d.isEmpty) { return const ['提升识别准确率至99.2%', '优化唤醒提示音的响应', '修复复杂性延迟问题']; } // 按常见分隔符切分 final parts = d .split(RegExp(r'[;;\n\r]')) .map((e) => e.trim()) .where((e) => e.isNotEmpty) .toList(); return parts; } }