564 lines
19 KiB
Dart
564 lines
19 KiB
Dart
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<Reply>? _replySubscription;
|
||
|
||
// 超时定时器(用于检测是否未收到回复)
|
||
Timer? _sendTimeoutTimer;
|
||
|
||
// 超时标志位(可选,防止重复处理)
|
||
bool _isTimeout = false;
|
||
|
||
@override
|
||
void onInit() async {
|
||
super.onInit();
|
||
_replySubscription =
|
||
EventBusManager().eventBus!.on<Reply>().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<String> 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<String> 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<void> 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, <int>[0, 0, 0, 0]);
|
||
}
|
||
}
|
||
|
||
sendFileToDevice(Uint8List data, List<int> token) {
|
||
showEasyLoading();
|
||
showBlueConnetctToastTimer(action: () {
|
||
dismissEasyLoading();
|
||
});
|
||
BlueManage().blueSendData(BlueManage().connectDeviceName,
|
||
(BluetoothConnectionState deviceConnectionState) async {
|
||
if (deviceConnectionState == BluetoothConnectionState.connected) {
|
||
final List<String>? privateKey =
|
||
await Storage.getStringList(saveBluePrivateKey);
|
||
final List<int> getPrivateKeyList =
|
||
changeStringListToIntList(privateKey!);
|
||
final List<String>? signKey =
|
||
await Storage.getStringList(saveBlueSignKey);
|
||
final List<int> 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<int> 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<void> _executeLogic(
|
||
VoicePackageConfigureConfirmationReply reply) async {
|
||
await _handlerVoicePackageConfigureConfirmation(reply);
|
||
}
|
||
|
||
_handlerVoicePackageConfigureConfirmation(
|
||
VoicePackageConfigureConfirmationReply reply,
|
||
) async {
|
||
showEasyLoading();
|
||
showBlueConnetctToastTimer(action: () {
|
||
dismissEasyLoading();
|
||
});
|
||
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<int> 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<int> token = reply.data.sublist(2, 6);
|
||
if (state.data != null) {
|
||
sendFileToDevice(state.data!, token);
|
||
}
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
void readLockLanguage() async {
|
||
showEasyLoading();
|
||
showBlueConnetctToastTimer(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();
|
||
}
|
||
});
|
||
}
|
||
}
|