import 'dart:async'; import 'dart:typed_data'; import 'package:crypto/crypto.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_blue_plus/flutter_blue_plus.dart'; import 'package:flutter_easyloading/flutter_easyloading.dart'; import 'package:get/get.dart'; import 'package:star_lock/app_settings/app_settings.dart'; import 'package:star_lock/blue/blue_manage.dart'; import 'package:star_lock/blue/io_protocol/io_getDeviceModel.dart'; import 'package:star_lock/blue/io_protocol/io_otaUpgrade.dart'; import 'package:star_lock/blue/io_protocol/io_processOtaUpgrade.dart'; import 'package:star_lock/blue/io_protocol/io_readVoicePackageFinalResult.dart'; import 'package:star_lock/blue/io_protocol/io_setVoicePackageFinalResult.dart'; import 'package:star_lock/blue/io_protocol/io_voicePackageConfigure.dart'; import 'package:star_lock/blue/io_protocol/io_voicePackageConfigureProcess.dart'; import 'package:star_lock/blue/io_reply.dart'; import 'package:star_lock/blue/io_tool/io_tool.dart'; import 'package:star_lock/blue/io_tool/manager_event_bus.dart'; import 'package:star_lock/login/login/entity/LoginEntity.dart'; import 'package:star_lock/login/selectCountryRegion/common/index.dart'; import 'package:star_lock/main/lockDetail/lockDetail/passthrough_item.dart'; import 'package:star_lock/main/lockDetail/lockSet/speechLanguageSettings/speech_language_settings_state.dart'; import 'package:star_lock/network/api_repository.dart'; import 'package:star_lock/tools/baseGetXController.dart'; import 'package:http/http.dart' as http; import 'package:star_lock/tools/commonDataManage.dart'; import 'package:star_lock/tools/eventBusEventManage.dart'; import 'package:star_lock/tools/storage.dart'; import 'package:star_lock/translations/app_dept.dart'; import 'package:star_lock/translations/current_locale_tool.dart'; class SpeechLanguageSettingsLogic extends BaseGetXController { final SpeechLanguageSettingsState state = SpeechLanguageSettingsState(); StreamSubscription? _replySubscription; // 超时定时器(用于检测是否未收到回复) Timer? _sendTimeoutTimer; // 超时标志位(可选,防止重复处理) bool _isTimeout = false; @override void onInit() async { super.onInit(); _replySubscription = EventBusManager().eventBus!.on().listen((Reply reply) async { if (reply is VoicePackageConfigureReply) { // 语言包配置开始 _handlerStartVoicePackageConfigure(reply); } else if (reply is VoicePackageConfigureProcessReply) { _handlerVoicePackageConfigureProcess(reply); } else if (reply is VoicePackageConfigureConfirmationReply) { handleVoiceConfigureThrottled(reply); } else if (reply is SetVoicePackageFinalResultReply) { handleSetResult(reply); } else if (reply is ReadLockCurrentVoicePacketReply) { handleLockCurrentVoicePacketResult(reply); } }); await initList(); readLockLanguage(); } /// 获取列表 /// //{ "vendor": "XL", "model": "JL-BLE-01"} initList() async { showEasyLoading(); try { final vendor = state.lockSetInfoData.value.lockBasicInfo?.vendor; final model = state.lockSetInfoData.value.lockBasicInfo?.model; final PassthroughListResponse entity = await ApiRepository.to.getPassthroughList(data: { 'vendor': vendor!, 'model': model!, }); if (entity.errorCode!.codeIsSuccessful) { final data = entity.data; final locales = appDept.deptSupportedLocales; state.languages.clear(); state.languages.add( PassthroughItem( lang: 'system', timbres: [], langText: '跟随系统'.tr, name: '跟随系统'.tr, ), ); data?.forEach((element) { final lang = element.lang; if (lang == 'zh_TW') { // 如果是台湾的话应该显示未简体中文 List parts = lang.split('_'); final indexOf = locales.indexOf(Locale(parts[0], parts[1])); final passthroughItem = PassthroughItem( lang: element.lang, timbres: element.timbres, langText: '简体中文'.tr + '(中国台湾)'.tr + '(Simplified Chinese TW)', name: element.name, ); state.languages.add(passthroughItem); } else { List parts = lang.split('_'); final indexOf = locales.indexOf(Locale(parts[0], parts[1])); final passthroughItem = PassthroughItem( lang: element.lang, timbres: element.timbres, langText: ExtensionLanguageType.fromLocale(locales[indexOf]).lanTitle, name: element.name, ); state.languages.add(passthroughItem); } }); state.languages.refresh(); final lang = state.lockSetInfoData.value.lockSettingInfo?.currentVoiceTimbre?.lang; final timbre = state.lockSetInfoData.value.lockSettingInfo?.currentVoiceTimbre?.timbre; // 传统 for 循环,直接通过索引访问 for (int i = 0; i < state.languages.length; i++) { final language = state.languages[i]; // 当前元素 if (language.lang == lang) { print('匹配到下标:$i,元素:$language'); final timbres = language.timbres; for (int j = 0; j < timbres.length; j++) { final item = timbres[j]; if (lang == language.lang && item.timbre == timbre) { state.selectSoundTypeIndex.value = item.isFemale; state.selectPassthroughListIndex.value = i; break; } } } } } } catch (e) { debugPrint('获取语音包出现错误:$e'); } finally { dismissEasyLoading(); } } void saveSpeechLanguageSettings() async { var language = state.languages[state.selectPassthroughListIndex.value]; if (language.lang == 'system') { // 如果选择了跟随系统 // 系统层的语言 // print(CurrentLocaleTool.convertLocale(Get.deviceLocale!)); // APP层的语言 Locale? currentLocale = Get.locale; // 直接获取最新语言 if (currentLocale != null) { final indexWhere = state.languages.indexWhere((element) => element.lang == currentLocale.toString()); state.selectPassthroughListIndex.value = indexWhere; } } language = state.languages[state.selectPassthroughListIndex.value]; final value = state.selectSoundTypeIndex.value; state.tempLangStr.value = language.lang; AppLog.log('下载的语言是:${state.tempLangStr}'); language.timbres.forEach((element) async { if (element.isFemale == value) { await downloadFile(element.timbrePackUrl); state.tempTimbreStr.value = element.timbre; return; } }); } void changeSelectIndex(int index) { state.selectLanguageIndex.value = index; } //下载语音包 Future downloadFile(String url) async { final http.Response response = await http.get(Uri.parse(url)); if (response.statusCode == 200) { state.data = response.bodyBytes; sendFileToDevice(response.bodyBytes, [0, 0, 0, 0]); } } sendFileToDevice(Uint8List data, List token) { showEasyLoading(); showBlueConnetctToastTimer(action: () { dismissEasyLoading(); }); BlueManage().blueSendData(BlueManage().connectDeviceName, (BluetoothConnectionState deviceConnectionState) async { if (deviceConnectionState == BluetoothConnectionState.connected) { final List? privateKey = await Storage.getStringList(saveBluePrivateKey); final List getPrivateKeyList = changeStringListToIntList(privateKey!); final List? signKey = await Storage.getStringList(saveBlueSignKey); final List signKeyDataList = changeStringListToIntList(signKey!); final String uid = await Storage.getUid() ?? ''; final String md5Str = md5.convert(data).toString().toUpperCase(); BlueManage().writeCharacteristicWithResponse( VoicePackageConfigure( lockID: BlueManage().connectDeviceName, userID: uid, keyID: BlueManage().connectDeviceName, platform: 0, product: 0, fwSize: data.length, fwMD5: md5Str, needAuthor: 1, token: token, signKey: signKeyDataList, privateKey: getPrivateKeyList) .packageData(), ); } else if (deviceConnectionState == BluetoothConnectionState.disconnected) { dismissEasyLoading(); cancelBlueConnetctToastTimer(); } }); } // 发送获取型号蓝牙命令 sendGetDeviceModelBleMessage() { showEasyLoading(); showBlueConnetctToastTimer(action: () { dismissEasyLoading(); }); BlueManage().blueSendData(BlueManage().connectDeviceName, (BluetoothConnectionState deviceConnectionState) async { if (deviceConnectionState == BluetoothConnectionState.connected) { BlueManage().writeCharacteristicWithResponse( GetDeviceModelCommand( lockID: BlueManage().connectDeviceName, ).packageData(), ); } else if (deviceConnectionState == BluetoothConnectionState.disconnected) { dismissEasyLoading(); cancelBlueConnetctToastTimer(); showBlueConnetctToast(); } }); } // 开始配置语音包 void _handlerStartVoicePackageConfigure(VoicePackageConfigureReply reply) async { final int status = reply.data[6]; switch (status) { case 0x00: //成功 cancelBlueConnetctToastTimer(); _startSendLanguageFile(); break; case 0x06: //无权限 final List token = reply.data.sublist(2, 6); print('收到token:$token'); if (state.data != null) { sendFileToDevice(state.data!, token); } break; default: showToast('获取设备型号失败'.tr); break; } } void _startSendLanguageFile() { if (state.data == null) return; state.voiceSubcontractingIndex = 0; state.voiceSubcontractingCount = (state.data!.length + state.voiceSubcontractingSize - 1) ~/ state.voiceSubcontractingSize; state.progress.value = 0.0; // 开始前重置进度 _sendNextPackage(); } void _handleSendTimeout() { _isTimeout = true; // 标记超时状态 dismissEasyLoading(); cancelBlueConnetctToastTimer(); showBlueConnetctToast(); // 重置状态,避免后续错误操作 state.voiceSubcontractingIndex = 0; state.voiceSubcontractingCount = 0; state.data = null; state.progress.value = 0.0; _isTimeout = false; // 标记超时状态 } void _sendNextPackage() async { // 若已超时,直接返回 if (_isTimeout) return; // 取消上一次未完成的定时器(避免重复触发) _sendTimeoutTimer?.cancel(); // 检查是否已完成所有分包发送 if (state.voiceSubcontractingIndex >= state.voiceSubcontractingCount) { print('所有分包已发送完成'); state.progress.value = 1.0; return; } // 启动 3 秒超时定时器 _sendTimeoutTimer = Timer(Duration(seconds: 3), () { _handleSendTimeout(); // 触发超时处理 }); if (state.voiceSubcontractingIndex >= state.voiceSubcontractingCount) { print('所有分包已发送完成'); state.progress.value = 1.0; // 发送完成 // 可在此处通知UI或做后续处理 return; } int start = state.voiceSubcontractingIndex * state.voiceSubcontractingSize; int end = start + state.voiceSubcontractingSize; if (end > state.data!.length) end = state.data!.length; Uint8List packageData = state.data!.sublist(start, end); // 更新分包进度 state.progress.value = (state.voiceSubcontractingIndex + 1) / state.voiceSubcontractingCount; EasyLoading.showProgress(state.progress.value, status: '正在发送数据 ${(state.progress.value * 100).toStringAsFixed(0)}%'); await _sendLanguageFileBleMessage( index: state.voiceSubcontractingIndex, data: packageData, ); } _sendLanguageFileBleMessage({required int index, required Uint8List data}) async { await BlueManage().blueSendData(BlueManage().connectDeviceName, (BluetoothConnectionState deviceConnectionState) async { if (deviceConnectionState == BluetoothConnectionState.connected) { await BlueManage().writeCharacteristicWithResponse( VoicePackageConfigureProcess( index: index, size: data.length, data: data, ).packageData(), ); } else if (deviceConnectionState == BluetoothConnectionState.disconnected) { dismissEasyLoading(); cancelBlueConnetctToastTimer(); // showBlueConnetctToast(); } }); } void _handlerVoicePackageConfigureProcess(VoicePackageConfigureProcessReply reply) { // 取消超时定时器(已收到回复,无需继续等待) _sendTimeoutTimer?.cancel(); _isTimeout = false; // 重置超时标志 final int status = reply.data[2]; switch (status) { case 0x00: cancelBlueConnetctToastTimer(); state.voiceSubcontractingIndex++; _sendNextPackage(); break; default: showToast('发送分包数据不成功'.tr); break; } } @override void dispose() async { await _replySubscription?.cancel(); _replySubscription = null; await BlueManage().disconnect(); dismissEasyLoading(); cancelBlueConnetctToastTimer(); EasyLoading.dismiss(); // 清理分包相关状态 state.progress.value = 0.0; state.voiceSubcontractingIndex = 0; state.voiceSubcontractingCount = 0; state.data = null; super.dispose(); } @override void onClose() async { await _replySubscription?.cancel(); _replySubscription = null; await BlueManage().disconnect(); dismissEasyLoading(); cancelBlueConnetctToastTimer(); EasyLoading.dismiss(); // 清理分包相关状态 state.progress.value = 0.0; state.voiceSubcontractingIndex = 0; state.voiceSubcontractingCount = 0; state.data = null; super.onClose(); } bool _isThrottled = false; void handleVoiceConfigureThrottled( VoicePackageConfigureConfirmationReply reply, ) { if (_isThrottled) return; _isThrottled = true; // 执行你的逻辑 _executeLogic(reply); // 设置节流时间(比如 1 秒) Future.delayed(Duration(seconds: 1), () { _isThrottled = false; }); } Future _executeLogic(VoicePackageConfigureConfirmationReply reply) async { await _handlerVoicePackageConfigureConfirmation(reply); } _handlerVoicePackageConfigureConfirmation( VoicePackageConfigureConfirmationReply reply, ) async { showEasyLoading(); showBlueConnetctToastTimer( action: () { dismissEasyLoading(); }, isShowBlueConnetctToast: false, ); final LoginEntity entity = await ApiRepository.to.settingCurrentVoiceTimbre( data: { 'lang': state.tempLangStr.value, 'timbre': state.tempTimbreStr.value, }, lockId: state.lockSetInfoData.value.lockId!, ); if (entity.errorCode!.codeIsSuccessful) { showSuccess('设置成功'.tr, something: () async { state.lockSetInfoData.value.lockSettingInfo?.currentVoiceTimbre?.lang = state.tempLangStr.value; state.lockSetInfoData.value.lockSettingInfo?.currentVoiceTimbre?.timbre = state.tempTimbreStr.value; await BlueManage().blueSendData(BlueManage().connectDeviceName, (BluetoothConnectionState deviceConnectionState) async { if (deviceConnectionState == BluetoothConnectionState.connected) { await BlueManage().writeCharacteristicWithResponse( SetVoicePackageFinalResult( lockID: BlueManage().connectDeviceName, languageCode: state.tempLangStr.value, ).packageData(), ); } else if (deviceConnectionState == BluetoothConnectionState.disconnected) { dismissEasyLoading(); cancelBlueConnetctToastTimer(); // showBlueConnetctToast(); } }); await Future.delayed(Duration(seconds: 1)); }); } } void handleSetResult(SetVoicePackageFinalResultReply reply) async { final int status = reply.data[2]; switch (status) { case 0x00: cancelBlueConnetctToastTimer(); dismissEasyLoading(); break; default: showToast('设置'.tr + '失败'.tr); break; } } void handleLockCurrentVoicePacketResult(ReadLockCurrentVoicePacketReply reply) { final int status = reply.data[2]; switch (status) { case 0x00: //成功 cancelBlueConnetctToastTimer(); const int languageCodeStartIndex = 3; const int languageCodeLength = 20; const int languageCodeEndIndex = languageCodeStartIndex + languageCodeLength; // 23 if (reply.data.length < languageCodeEndIndex) { throw Exception( 'Reply data is too short to contain LanguageCode. Expected at least $languageCodeEndIndex bytes, got ${reply.data.length}'); } List languageCodeBytes = reply.data.sublist(languageCodeStartIndex, languageCodeEndIndex); String languageCode = String.fromCharCodes(languageCodeBytes); languageCode = languageCode.trim(); // 移除首尾空格 languageCode = languageCode.replaceAll('\u0000', ''); // 移除空字符 (null bytes) if (languageCode != null && languageCode != '') { final indexWhere = state.languages.indexWhere((element) => element.lang == languageCode); if (indexWhere != -1) { print('锁板上的语言是:$languageCode,下标是:$indexWhere'); state.selectPassthroughListIndex.value = indexWhere; } } dismissEasyLoading(); break; case 0x06: //无权限 final List token = reply.data.sublist(2, 6); if (state.data != null) { sendFileToDevice(state.data!, token); } break; default: break; } } void readLockLanguage() async { showEasyLoading(); showBlueConnetctToastTimer( isShowBlueConnetctToast: false, action: () { dismissEasyLoading(); }); await BlueManage().blueSendData(BlueManage().connectDeviceName, (BluetoothConnectionState deviceConnectionState) async { if (deviceConnectionState == BluetoothConnectionState.connected) { await BlueManage().writeCharacteristicWithResponse( ReadLockCurrentVoicePacket( lockID: BlueManage().connectDeviceName, ).packageData(), ); } else if (deviceConnectionState == BluetoothConnectionState.disconnected) { dismissEasyLoading(); cancelBlueConnetctToastTimer(); // showBlueConnetctToast(); } }); } }